diff --git a/go.mod b/go.mod index 4030e08c..b0107a57 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/knative/pkg v0.0.0-20190817231834-12ee58e32cc8 github.com/pkg/errors v0.8.1 github.com/rancher/norman v0.0.0-20200211155126-fc45a55d4dfd + github.com/rancher/wrangler v0.4.2-0.20200214231136-099089b8a398 github.com/sirupsen/logrus v1.4.2 golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c // indirect k8s.io/api v0.17.2 diff --git a/go.sum b/go.sum index 4727e7ea..133c315b 100644 --- a/go.sum +++ b/go.sum @@ -472,6 +472,8 @@ github.com/rancher/pkg v0.0.0-20190514055449-b30ab9de040e h1:j6+HqCET/NLPBtew2m5 github.com/rancher/pkg v0.0.0-20190514055449-b30ab9de040e/go.mod h1:XbYHTPaXuw8ZY9bylhYKQh/nJxDaTKk3YhAxPl4Qy/k= github.com/rancher/wrangler v0.4.1 h1:kQLE6syPbX4ciwU4OF+EHIPT9zj6r6pyN83u9Gsv5eg= github.com/rancher/wrangler v0.4.1/go.mod h1:1cR91WLhZgkZ+U4fV9nVuXqKurWbgXcIReU4wnQvTN8= +github.com/rancher/wrangler v0.4.2-0.20200214231136-099089b8a398 h1:1C/Fp1aSEL4Pl4hzlQsdaqzcCB9qMgalbVkvzzPIrPk= +github.com/rancher/wrangler v0.4.2-0.20200214231136-099089b8a398/go.mod h1:1cR91WLhZgkZ+U4fV9nVuXqKurWbgXcIReU4wnQvTN8= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rlmcpherson/s3gof3r v0.5.0/go.mod h1:s7vv7SMDPInkitQMuZzH615G7yWHdrU2r/Go7Bo71Rs= diff --git a/vendor/github.com/rancher/wrangler/pkg/data/convert/convert.go b/vendor/github.com/rancher/wrangler/pkg/data/convert/convert.go new file mode 100644 index 00000000..47af9bc7 --- /dev/null +++ b/vendor/github.com/rancher/wrangler/pkg/data/convert/convert.go @@ -0,0 +1,261 @@ +package convert + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + "time" + "unicode" +) + +func Singular(value interface{}) interface{} { + if slice, ok := value.([]string); ok { + if len(slice) == 0 { + return nil + } + return slice[0] + } + if slice, ok := value.([]interface{}); ok { + if len(slice) == 0 { + return nil + } + return slice[0] + } + return value +} + +func ToStringNoTrim(value interface{}) string { + if t, ok := value.(time.Time); ok { + return t.Format(time.RFC3339) + } + single := Singular(value) + if single == nil { + return "" + } + return fmt.Sprint(single) +} + +func ToString(value interface{}) string { + return strings.TrimSpace(ToStringNoTrim(value)) +} + +func ToTimestamp(value interface{}) (int64, error) { + str := ToString(value) + if str == "" { + return 0, errors.New("invalid date") + } + t, err := time.Parse(time.RFC3339, str) + if err != nil { + return 0, err + } + return t.UnixNano() / 1000000, nil +} + +func ToBool(value interface{}) bool { + value = Singular(value) + + b, ok := value.(bool) + if ok { + return b + } + + str := strings.ToLower(ToString(value)) + return str == "true" || str == "t" || str == "yes" || str == "y" +} + +func ToNumber(value interface{}) (int64, error) { + value = Singular(value) + + i, ok := value.(int64) + if ok { + return i, nil + } + f, ok := value.(float64) + if ok { + return int64(f), nil + } + if n, ok := value.(json.Number); ok { + i, err := n.Int64() + if err == nil { + return i, nil + } + f, err := n.Float64() + return int64(f), err + } + return strconv.ParseInt(ToString(value), 10, 64) +} + +func ToFloat(value interface{}) (float64, error) { + value = Singular(value) + + f64, ok := value.(float64) + if ok { + return f64, nil + } + + f32, ok := value.(float32) + if ok { + return float64(f32), nil + } + + if n, ok := value.(json.Number); ok { + i, err := n.Int64() + if err == nil { + return float64(i), nil + } + f, err := n.Float64() + return float64(f), err + } + return strconv.ParseFloat(ToString(value), 64) +} + +func Capitalize(s string) string { + if len(s) <= 1 { + return strings.ToUpper(s) + } + + return strings.ToUpper(s[:1]) + s[1:] +} + +func Uncapitalize(s string) string { + if len(s) <= 1 { + return strings.ToLower(s) + } + + return strings.ToLower(s[:1]) + s[1:] +} + +func LowerTitle(input string) string { + runes := []rune(input) + for i := 0; i < len(runes); i++ { + if unicode.IsUpper(runes[i]) && + (i == 0 || + i == len(runes)-1 || + unicode.IsUpper(runes[i+1])) { + runes[i] = unicode.ToLower(runes[i]) + } else { + break + } + } + + return string(runes) +} + +func IsEmptyValue(v interface{}) bool { + if v == nil || v == "" || v == 0 || v == false { + return true + } + if m, ok := v.(map[string]interface{}); ok { + return len(m) == 0 + } + if s, ok := v.([]interface{}); ok { + return len(s) == 0 + } + return false +} + +func ToMapInterface(obj interface{}) map[string]interface{} { + v, _ := obj.(map[string]interface{}) + return v +} + +func ToInterfaceSlice(obj interface{}) []interface{} { + if v, ok := obj.([]interface{}); ok { + return v + } + return nil +} + +func ToMapSlice(obj interface{}) []map[string]interface{} { + if v, ok := obj.([]map[string]interface{}); ok { + return v + } + vs, _ := obj.([]interface{}) + var result []map[string]interface{} + for _, item := range vs { + if v, ok := item.(map[string]interface{}); ok { + result = append(result, v) + } else { + return nil + } + } + + return result +} + +func ToStringSlice(data interface{}) []string { + if v, ok := data.([]string); ok { + return v + } + if v, ok := data.([]interface{}); ok { + var result []string + for _, item := range v { + result = append(result, ToString(item)) + } + return result + } + return nil +} + +func ToObj(data interface{}, into interface{}) error { + bytes, err := json.Marshal(data) + if err != nil { + return err + } + return json.Unmarshal(bytes, into) +} + +func EncodeToMap(obj interface{}) (map[string]interface{}, error) { + if m, ok := obj.(map[string]interface{}); ok { + return m, nil + } + + b, err := json.Marshal(obj) + if err != nil { + return nil, err + } + result := map[string]interface{}{} + dec := json.NewDecoder(bytes.NewBuffer(b)) + dec.UseNumber() + return result, dec.Decode(&result) +} + +func ToJSONKey(str string) string { + parts := strings.Split(str, "_") + for i := 1; i < len(parts); i++ { + parts[i] = strings.Title(parts[i]) + } + + return strings.Join(parts, "") +} + +func ToYAMLKey(str string) string { + var result []rune + cap := false + + for i, r := range []rune(str) { + if i == 0 { + if unicode.IsUpper(r) { + cap = true + } + result = append(result, unicode.ToLower(r)) + continue + } + + if unicode.IsUpper(r) { + if cap { + result = append(result, unicode.ToLower(r)) + } else { + result = append(result, '_', unicode.ToLower(r)) + } + } else { + cap = false + result = append(result, r) + } + } + + return string(result) +} diff --git a/vendor/github.com/rancher/wrangler/pkg/data/data.go b/vendor/github.com/rancher/wrangler/pkg/data/data.go new file mode 100644 index 00000000..e279a299 --- /dev/null +++ b/vendor/github.com/rancher/wrangler/pkg/data/data.go @@ -0,0 +1,59 @@ +package data + +import ( + "github.com/rancher/wrangler/pkg/data/convert" +) + +type List []map[string]interface{} + +type Object map[string]interface{} + +func New() Object { + return map[string]interface{}{} +} + +func (o Object) Map(names ...string) Object { + v := GetValueN(o, names...) + m := convert.ToMapInterface(v) + return m +} + +func (o Object) Slice(names ...string) (result []Object) { + v := GetValueN(o, names...) + for _, item := range convert.ToInterfaceSlice(v) { + result = append(result, convert.ToMapInterface(item)) + } + return +} + +func (o Object) Values() (result []Object) { + for k := range o { + result = append(result, o.Map(k)) + } + return +} + +func (o Object) String(names ...string) string { + v := GetValueN(o, names...) + return convert.ToString(v) +} + +func (o Object) StringSlice(names ...string) []string { + v := GetValueN(o, names...) + return convert.ToStringSlice(v) +} + +func (o Object) Set(key string, obj interface{}) { + if o == nil { + return + } + o[key] = obj +} + +func (o Object) SetNested(obj interface{}, key ...string) { + PutValue(o, obj, key...) +} + +func (o Object) Bool(key ...string) bool { + return convert.ToBool(GetValueN(o, key...)) +} diff --git a/vendor/github.com/rancher/wrangler/pkg/data/values.go b/vendor/github.com/rancher/wrangler/pkg/data/values.go new file mode 100644 index 00000000..87ff6159 --- /dev/null +++ b/vendor/github.com/rancher/wrangler/pkg/data/values.go @@ -0,0 +1,58 @@ +package data + +func RemoveValue(data map[string]interface{}, keys ...string) (interface{}, bool) { + for i, key := range keys { + if i == len(keys)-1 { + val, ok := data[key] + delete(data, key) + return val, ok + } + data, _ = data[key].(map[string]interface{}) + } + + return nil, false +} + +func GetValueN(data map[string]interface{}, keys ...string) interface{} { + val, _ := GetValue(data, keys...) + return val +} + +func GetValue(data map[string]interface{}, keys ...string) (interface{}, bool) { + for i, key := range keys { + if i == len(keys)-1 { + val, ok := data[key] + return val, ok + } + data, _ = data[key].(map[string]interface{}) + } + + return nil, false +} + +func PutValue(data map[string]interface{}, val interface{}, keys ...string) { + if data == nil { + return + } + + // This is so ugly + for i, key := range keys { + if i == len(keys)-1 { + data[key] = val + } else { + newData, ok := data[key] + if ok { + newMap, ok := newData.(map[string]interface{}) + if ok { + data = newMap + } else { + return + } + } else { + newMap := map[string]interface{}{} + data[key] = newMap + data = newMap + } + } + } +} diff --git a/vendor/github.com/rancher/wrangler/pkg/kv/split.go b/vendor/github.com/rancher/wrangler/pkg/kv/split.go new file mode 100644 index 00000000..7368bde4 --- /dev/null +++ b/vendor/github.com/rancher/wrangler/pkg/kv/split.go @@ -0,0 +1,45 @@ +package kv + +import "strings" + +// Like split but if there is only one item return "", item +func RSplit(s, sep string) (string, string) { + parts := strings.SplitN(s, sep, 2) + if len(parts) == 1 { + return "", strings.TrimSpace(parts[0]) + } + return strings.TrimSpace(parts[0]), strings.TrimSpace(safeIndex(parts, 1)) +} + +func Split(s, sep string) (string, string) { + parts := strings.SplitN(s, sep, 2) + return strings.TrimSpace(parts[0]), strings.TrimSpace(safeIndex(parts, 1)) +} + +func SplitLast(s, sep string) (string, string) { + idx := strings.LastIndex(s, sep) + if idx > -1 { + return strings.TrimSpace(s[:idx]), strings.TrimSpace(s[idx+1:]) + } + return s, "" +} + +func SplitMap(s, sep string) map[string]string { + return SplitMapFromSlice(strings.Split(s, sep)) +} + +func SplitMapFromSlice(parts []string) map[string]string { + result := map[string]string{} + for _, part := range parts { + k, v := Split(part, "=") + result[k] = v + } + return result +} + +func safeIndex(parts []string, idx int) string { + if len(parts) <= idx { + return "" + } + return parts[idx] +} diff --git a/vendor/github.com/rancher/wrangler/pkg/summary/condition.go b/vendor/github.com/rancher/wrangler/pkg/summary/condition.go new file mode 100644 index 00000000..bc1be5f3 --- /dev/null +++ b/vendor/github.com/rancher/wrangler/pkg/summary/condition.go @@ -0,0 +1,45 @@ +package summary + +import ( + "encoding/json" + + "github.com/rancher/wrangler/pkg/data" +) + +func getRawConditions(obj data.Object) []data.Object { + statusAnn := obj.String("metadata", "annotations", "cattle.io/status") + if statusAnn != "" { + status := data.Object{} + if err := json.Unmarshal([]byte(statusAnn), &status); err == nil { + return append(obj.Slice("status", "conditions"), status.Slice("conditions")...) + } + } + return obj.Slice("status", "conditions") +} + +func getConditions(obj data.Object) (result []Condition) { + for _, condition := range getRawConditions(obj) { + result = append(result, Condition{d: condition}) + } + return +} + +type Condition struct { + d data.Object +} + +func (c Condition) Type() string { + return c.d.String("type") +} + +func (c Condition) Status() string { + return c.d.String("status") +} + +func (c Condition) Reason() string { + return c.d.String("reason") +} + +func (c Condition) Message() string { + return c.d.String("message") +} diff --git a/vendor/github.com/rancher/wrangler/pkg/summary/summarizers.go b/vendor/github.com/rancher/wrangler/pkg/summary/summarizers.go new file mode 100644 index 00000000..6bdf871d --- /dev/null +++ b/vendor/github.com/rancher/wrangler/pkg/summary/summarizers.go @@ -0,0 +1,257 @@ +package summary + +import ( + "strings" + "time" + + "github.com/rancher/wrangler/pkg/data" + "github.com/rancher/wrangler/pkg/data/convert" + "github.com/rancher/wrangler/pkg/kv" +) + +var ( + // True == + // False == error + // Unknown == transitioning + TransitioningUnknown = map[string]string{ + "Active": "activating", + "AddonDeploy": "provisioning", + "AgentDeployed": "provisioning", + "BackingNamespaceCreated": "configuring", + "Built": "building", + "CertsGenerated": "provisioning", + "ConfigOK": "configuring", + "Created": "creating", + "CreatorMadeOwner": "configuring", + "DefaultNamespaceAssigned": "configuring", + "DefaultNetworkPolicyCreated": "configuring", + "DefaultProjectCreated": "configuring", + "DockerProvisioned": "provisioning", + "Deployed": "deploying", + "Drained": "draining", + "Downloaded": "downloading", + "etcd": "provisioning", + "Inactive": "deactivating", + "Initialized": "initializing", + "Installed": "installing", + "NodesCreated": "provisioning", + "Pending": "pending", + "PodScheduled": "scheduling", + "Provisioned": "provisioning", + "Refreshed": "refreshed", + "Registered": "registering", + "Removed": "removing", + "Saved": "saving", + "Updated": "updating", + "Updating": "updating", + "Waiting": "waiting", + "InitialRolesPopulated": "activating", + "ScalingActive": "pending", + "AbleToScale": "pending", + "RunCompleted": "running", + } + + // True == error + // False == + // Unknown == + ErrorTrue = map[string]bool{ + "OutOfDisk": true, + "MemoryPressure": true, + "DiskPressure": true, + "NetworkUnavailable": true, + "KernelHasNoDeadlock": true, + "Unschedulable": true, + "ReplicaFailure": true, + } + + // True == + // False == error + // Unknown == + ErrorFalse = map[string]bool{ + "Failed": true, + "Progressing": true, + } + + // True == + // False == transitioning + // Unknown == error + TransitioningFalse = map[string]string{ + "Completed": "activating", + "Ready": "unavailable", + "Available": "updating", + "Progressing": "inactive", + } + + Summarizers []Summarizer +) + +type Summarizer func(obj data.Object, conditions []Condition, summary Summary) Summary + +func init() { + Summarizers = []Summarizer{ + checkErrors, + checkTransitioning, + checkActive, + checkPhase, + checkInitializing, + checkRemoving, + checkLoadBalancer, + } +} + +func checkErrors(_ data.Object, conditions []Condition, summary Summary) Summary { + for _, c := range conditions { + if (ErrorFalse[c.Type()] && c.Status() == "False") || c.Reason() == "Error" { + summary.Error = true + summary.Message = append(summary.Message, c.Message()) + break + } + } + + if summary.Error { + return summary + } + + for _, c := range conditions { + if ErrorTrue[c.Type()] && c.Status() == "True" { + summary.Error = true + summary.Message = append(summary.Message, c.Message()) + } + } + return summary +} + +func checkTransitioning(_ data.Object, conditions []Condition, summary Summary) Summary { + for _, c := range conditions { + newState, ok := TransitioningUnknown[c.Type()] + if !ok { + continue + } + + if c.Status() == "False" { + summary.Error = true + summary.State = newState + summary.Message = append(summary.Message, c.Message()) + } else if c.Status() == "Unknown" && summary.State == "" { + summary.Transitioning = true + summary.State = newState + summary.Message = append(summary.Message, c.Message()) + } + } + + for _, c := range conditions { + if summary.State != "" { + break + } + newState, ok := TransitioningFalse[c.Type()] + if !ok { + continue + } + if c.Status() == "False" { + summary.Transitioning = true + summary.State = newState + summary.Message = append(summary.Message, c.Message()) + } else if c.Status() == "Unknown" { + summary.Error = true + summary.State = newState + summary.Message = append(summary.Message, c.Message()) + } + } + + return summary +} + +func checkActive(obj data.Object, _ []Condition, summary Summary) Summary { + if summary.State != "" { + return summary + } + + switch obj.String("spec", "active") { + case "true": + summary.State = "active" + case "false": + summary.State = "inactive" + } + + return summary +} + +func checkPhase(obj data.Object, _ []Condition, summary Summary) Summary { + phase := obj.String("status", "phase") + if phase == "Succeeded" { + summary.State = "succeeded" + summary.Transitioning = false + } else if phase != "" && summary.State == "" { + summary.State = phase + } + return summary +} + +func checkInitializing(obj data.Object, conditions []Condition, summary Summary) Summary { + apiVersion := obj.String("apiVersion") + _, hasConditions := obj.Map("status")["conditions"] + if summary.State == "" && hasConditions && len(conditions) == 0 && strings.Contains(apiVersion, "cattle.io") { + val := obj.String("metadata", "created") + if i, err := convert.ToTimestamp(val); err == nil { + if time.Unix(i/1000, 0).Add(5 * time.Second).After(time.Now()) { + summary.State = "initializing" + summary.Transitioning = true + } + } + } + return summary +} + +func checkRemoving(obj data.Object, conditions []Condition, summary Summary) Summary { + removed := obj.String("metadata", "removed") + if removed == "" { + return summary + } + + summary.State = "removing" + summary.Transitioning = true + + finalizers := obj.StringSlice("metadata", "finalizers") + if len(finalizers) == 0 { + finalizers = obj.StringSlice("spec", "finalizers") + } + + for _, cond := range conditions { + if cond.Type() == "Removed" && (cond.Status() == "Unknown" || cond.Status() == "False") && cond.Message() != "" { + summary.Message = append(summary.Message, cond.Message()) + } + } + + if len(finalizers) == 0 { + return summary + } + + _, f := kv.RSplit(finalizers[0], "controller.cattle.io/") + if f == "foregroundDeletion" { + f = "object cleanup" + } + + summary.Message = append(summary.Message, "waiting on "+f) + if i, err := convert.ToTimestamp(removed); err == nil { + if time.Unix(i/1000, 0).Add(5 * time.Minute).Before(time.Now()) { + summary.Error = true + } + } + + return summary +} + +func checkLoadBalancer(obj data.Object, _ []Condition, summary Summary) Summary { + if (summary.State == "active" || summary.State == "") && + obj.String("kind") == "Service" && + obj.String("spec", "serviceKind") == "LoadBalancer" { + addresses := obj.Slice("status", "loadBalancer", "ingress") + if len(addresses) == 0 { + summary.State = "pending" + summary.Transitioning = true + summary.Message = append(summary.Message, "Load balancer is being provisioned") + } + } + + return summary +} diff --git a/vendor/github.com/rancher/wrangler/pkg/summary/summary.go b/vendor/github.com/rancher/wrangler/pkg/summary/summary.go new file mode 100644 index 00000000..89dd2d12 --- /dev/null +++ b/vendor/github.com/rancher/wrangler/pkg/summary/summary.go @@ -0,0 +1,36 @@ +package summary + +import ( + "github.com/rancher/wrangler/pkg/data" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +type Summary struct { + State string + Error bool + Transitioning bool + Message []string +} + +func Summarize(unstr *unstructured.Unstructured) Summary { + var ( + obj data.Object + summary Summary + ) + + if unstr != nil { + obj = unstr.Object + } + + conditions := getConditions(obj) + + for _, summarizer := range Summarizers { + summary = summarizer(obj, conditions, summary) + } + + if summary.State == "" { + summary.State = "active" + } + + return summary +} diff --git a/vendor/modules.txt b/vendor/modules.txt index ed2cd49b..466ad867 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -107,9 +107,13 @@ github.com/rancher/norman/types/factory github.com/rancher/norman/types/mapper github.com/rancher/norman/types/slice github.com/rancher/norman/types/values -# github.com/rancher/wrangler v0.4.1 +# github.com/rancher/wrangler v0.4.2-0.20200214231136-099089b8a398 +github.com/rancher/wrangler/pkg/data +github.com/rancher/wrangler/pkg/data/convert +github.com/rancher/wrangler/pkg/kv github.com/rancher/wrangler/pkg/name github.com/rancher/wrangler/pkg/ratelimit +github.com/rancher/wrangler/pkg/summary # github.com/sirupsen/logrus v1.4.2 github.com/sirupsen/logrus # github.com/spf13/pflag v1.0.5