| package utils |
| |
| import ( |
| "compress/gzip" |
| "encoding/base64" |
| "encoding/json" |
| "fmt" |
| "regexp" |
| "strconv" |
| "strings" |
| |
| log "github.com/sirupsen/logrus" |
| "k8s.io/apimachinery/pkg/api/meta" |
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| "k8s.io/apimachinery/pkg/runtime" |
| "k8s.io/apimachinery/pkg/version" |
| "k8s.io/client-go/discovery" |
| ) |
| |
| // Format v0.0.0(-master+$Format:%h$) |
| var gitVersionRe = regexp.MustCompile("v([0-9])+.([0-9])+.[0-9]+.*") |
| |
| // ServerVersion captures k8s major.minor version in a parsed form |
| type ServerVersion struct { |
| Major int |
| Minor int |
| } |
| |
| func parseGitVersion(gitVersion string) (ServerVersion, error) { |
| parsedVersion := gitVersionRe.FindStringSubmatch(gitVersion) |
| if len(parsedVersion) != 3 { |
| return ServerVersion{}, fmt.Errorf("Unable to parse git version %s", gitVersion) |
| } |
| var ret ServerVersion |
| var err error |
| ret.Major, err = strconv.Atoi(parsedVersion[1]) |
| if err != nil { |
| return ServerVersion{}, err |
| } |
| ret.Minor, err = strconv.Atoi(parsedVersion[2]) |
| if err != nil { |
| return ServerVersion{}, err |
| } |
| return ret, nil |
| } |
| |
| // ParseVersion parses version.Info into a ServerVersion struct |
| func ParseVersion(v *version.Info) (ServerVersion, error) { |
| var ret ServerVersion |
| var err error |
| ret.Major, err = strconv.Atoi(v.Major) |
| if err != nil { |
| // Try to parse using GitVersion |
| return parseGitVersion(v.GitVersion) |
| } |
| |
| // trim "+" in minor version (happened on GKE) |
| v.Minor = strings.TrimSuffix(v.Minor, "+") |
| ret.Minor, err = strconv.Atoi(v.Minor) |
| if err != nil { |
| // Try to parse using GitVersion |
| return parseGitVersion(v.GitVersion) |
| } |
| return ret, err |
| } |
| |
| // FetchVersion fetches version information from discovery client, and parses |
| func FetchVersion(v discovery.ServerVersionInterface) (ret ServerVersion, err error) { |
| version, err := v.ServerVersion() |
| if err != nil { |
| return ServerVersion{}, err |
| } |
| return ParseVersion(version) |
| } |
| |
| // GetDefaultVersion returns a default server version. This value will be updated |
| // periodically to match a current/popular version corresponding to the age of this code |
| // Current default version: 1.8 |
| func GetDefaultVersion() ServerVersion { |
| return ServerVersion{Major: 1, Minor: 8} |
| } |
| |
| // Compare returns -1/0/+1 iff v is less than / equal / greater than major.minor |
| func (v ServerVersion) Compare(major, minor int) int { |
| a := v.Major |
| b := major |
| |
| if a == b { |
| a = v.Minor |
| b = minor |
| } |
| |
| var res int |
| if a > b { |
| res = 1 |
| } else if a == b { |
| res = 0 |
| } else { |
| res = -1 |
| } |
| return res |
| } |
| |
| func (v ServerVersion) String() string { |
| return fmt.Sprintf("%d.%d", v.Major, v.Minor) |
| } |
| |
| // SetMetaDataAnnotation sets an annotation value |
| func SetMetaDataAnnotation(obj metav1.Object, key, value string) { |
| a := obj.GetAnnotations() |
| if a == nil { |
| a = make(map[string]string) |
| } |
| a[key] = value |
| obj.SetAnnotations(a) |
| } |
| |
| // DeleteMetaDataAnnotation removes an annotation value |
| func DeleteMetaDataAnnotation(obj metav1.Object, key string) { |
| a := obj.GetAnnotations() |
| if a != nil { |
| delete(a, key) |
| obj.SetAnnotations(a) |
| } |
| } |
| |
| // SetMetaDataLabel sets an annotation value |
| func SetMetaDataLabel(obj metav1.Object, key, value string) { |
| l := obj.GetLabels() |
| if l == nil { |
| l = make(map[string]string) |
| } |
| l[key] = value |
| obj.SetLabels(l) |
| } |
| |
| // DeleteMetaDataLabel removes a label value |
| func DeleteMetaDataLabel(obj metav1.Object, key string) { |
| l := obj.GetLabels() |
| if l != nil { |
| delete(l, key) |
| obj.SetLabels(l) |
| } |
| } |
| |
| // ResourceNameFor returns a lowercase plural form of a type, for |
| // human messages. Returns lowercased kind if discovery lookup fails. |
| func ResourceNameFor(mapper meta.RESTMapper, o runtime.Object) string { |
| gvk := o.GetObjectKind().GroupVersionKind() |
| mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version) |
| if err != nil { |
| log.Debugf("RESTMapper failed for %s (%s), falling back to kind", gvk, err) |
| return strings.ToLower(gvk.Kind) |
| } |
| |
| return mapping.Resource.Resource |
| } |
| |
| // FqName returns "namespace.name" |
| func FqName(o metav1.Object) string { |
| if o.GetNamespace() == "" { |
| return o.GetName() |
| } |
| return fmt.Sprintf("%s.%s", o.GetNamespace(), o.GetName()) |
| } |
| |
| // CompactEncodeObject returns a compact string representation |
| // (json->gzip->base64) of an object, intended for use in |
| // last-applied-configuration annotation. |
| func CompactEncodeObject(o runtime.Object) (string, error) { |
| var buf strings.Builder |
| b64enc := base64.NewEncoder(base64.StdEncoding, &buf) |
| zw := gzip.NewWriter(b64enc) |
| jsenc := json.NewEncoder(zw) |
| jsenc.SetEscapeHTML(false) |
| jsenc.SetIndent("", "") |
| |
| if err := jsenc.Encode(o); err != nil { |
| return "", err |
| } |
| |
| zw.Close() |
| b64enc.Close() |
| |
| return buf.String(), nil |
| } |
| |
| // CompactDecodeObject does the reverse of CompactEncodeObject. |
| func CompactDecodeObject(data string, into runtime.Object) error { |
| zr, err := gzip.NewReader( |
| base64.NewDecoder(base64.StdEncoding, |
| strings.NewReader(data))) |
| if err != nil { |
| return err |
| } |
| |
| jsdec := json.NewDecoder(zr) |
| return jsdec.Decode(into) |
| } |