Initial v1.0.0 commit

This commit is contained in:
Jakub Vavřík
2021-01-28 17:37:47 +01:00
commit 1481d27782
4164 changed files with 1264675 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
package summary
import (
"github.com/rancher/wrangler/pkg/data"
)
func checkCattleTypes(obj data.Object, condition []Condition, summary Summary) Summary {
return checkRelease(obj, condition, summary)
}
func checkRelease(obj data.Object, condition []Condition, summary Summary) Summary {
if !isKind(obj, "Release", "catalog.cattle.io") {
return summary
}
if obj.String("status", "summary", "state") != "deployed" {
return summary
}
for _, resources := range obj.Slice("spec", "resources") {
summary.Relationships = append(summary.Relationships, Relationship{
Name: resources.String("name"),
Kind: resources.String("kind"),
APIVersion: resources.String("apiVersion"),
Type: "manages",
})
}
return summary
}

View File

@@ -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")
}

View File

@@ -0,0 +1,153 @@
package summary
import (
"github.com/rancher/wrangler/pkg/data"
"github.com/rancher/wrangler/pkg/data/convert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func checkPodSelector(obj data.Object, condition []Condition, summary Summary) Summary {
selector := obj.Map("spec", "selector")
if selector == nil {
return summary
}
if !isKind(obj, "ReplicaSet", "apps/", "extension/") &&
!isKind(obj, "DaemonSet", "apps/", "extension/") &&
!isKind(obj, "StatefulSet", "apps/", "extension/") &&
!isKind(obj, "Deployment", "apps/", "extension/") &&
!isKind(obj, "Job", "batch/") &&
!isKind(obj, "Service") {
return summary
}
_, hasMatch := selector["matchLabels"]
if !hasMatch {
_, hasMatch = selector["matchExpressions"]
}
sel := metav1.LabelSelector{}
if hasMatch {
if err := convert.ToObj(selector, &sel); err != nil {
return summary
}
} else {
sel.MatchLabels = map[string]string{}
for k, v := range selector {
sel.MatchLabels[k] = convert.ToString(v)
}
}
t := "creates"
if obj["kind"] == "Service" {
t = "selects"
}
summary.Relationships = append(summary.Relationships, Relationship{
Kind: "Pod",
APIVersion: "v1",
Type: t,
Selector: &sel,
})
return summary
}
func checkPod(obj data.Object, condition []Condition, summary Summary) Summary {
if !isKind(obj, "Pod") {
return summary
}
if obj.String("kind") != "Pod" || obj.String("apiVersion") != "v1" {
return summary
}
summary = checkPodConfigMaps(obj, condition, summary)
summary = checkPodSecrets(obj, condition, summary)
summary = checkPodServiceAccount(obj, condition, summary)
summary = checkPodProjectedVolume(obj, condition, summary)
summary = checkPodPullSecret(obj, condition, summary)
return summary
}
func checkPodPullSecret(obj data.Object, condition []Condition, summary Summary) Summary {
for _, pullSecret := range obj.Slice("imagePullSecrets") {
if name := pullSecret.String("name"); name != "" {
summary.Relationships = append(summary.Relationships, Relationship{
Name: name,
Kind: "Secret",
APIVersion: "v1",
Type: "uses",
})
}
}
return summary
}
func checkPodProjectedVolume(obj data.Object, condition []Condition, summary Summary) Summary {
for _, vol := range obj.Slice("spec", "volumes") {
for _, source := range vol.Slice("projected", "sources") {
if secretName := source.String("secret", "name"); secretName != "" {
summary.Relationships = append(summary.Relationships, Relationship{
Name: secretName,
Kind: "Secret",
APIVersion: "v1",
Type: "uses",
})
}
if configMap := source.String("configMap", "name"); configMap != "" {
summary.Relationships = append(summary.Relationships, Relationship{
Name: configMap,
Kind: "Secret",
APIVersion: "v1",
Type: "uses",
})
}
}
}
return summary
}
func checkPodConfigMaps(obj data.Object, condition []Condition, summary Summary) Summary {
names := map[string]bool{}
for _, vol := range obj.Slice("spec", "volumes") {
name := vol.String("configMap", "name")
if name == "" || names[name] {
continue
}
names[name] = true
summary.Relationships = append(summary.Relationships, Relationship{
Name: name,
Kind: "ConfigMap",
APIVersion: "v1",
Type: "uses",
})
}
return summary
}
func checkPodSecrets(obj data.Object, condition []Condition, summary Summary) Summary {
names := map[string]bool{}
for _, vol := range obj.Slice("spec", "volumes") {
name := vol.String("secret", "secretName")
if name == "" || names[name] {
continue
}
names[name] = true
summary.Relationships = append(summary.Relationships, Relationship{
Name: name,
Kind: "Secret",
APIVersion: "v1",
Type: "uses",
})
}
return summary
}
func checkPodServiceAccount(obj data.Object, condition []Condition, summary Summary) Summary {
saName := obj.String("spec", "serviceAccountName")
summary.Relationships = append(summary.Relationships, Relationship{
Name: saName,
Kind: "ServiceAccount",
APIVersion: "v1",
Type: "uses",
})
return summary
}

View File

@@ -0,0 +1,98 @@
package summary
import (
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
type SummarizedObject struct {
metav1.PartialObjectMetadata
Summary
}
type SummarizedObjectList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
Items []SummarizedObject `json:"items" protobuf:"bytes,2,rep,name=items"`
}
func Summarized(u runtime.Object) *SummarizedObject {
if s, ok := u.(*SummarizedObject); ok {
return s
}
s := &SummarizedObject{
Summary: Summarize(u),
}
s.APIVersion, s.Kind = u.GetObjectKind().GroupVersionKind().ToAPIVersionAndKind()
meta, err := meta.Accessor(u)
if err == nil {
s.Name = meta.GetName()
s.Namespace = meta.GetNamespace()
s.Generation = meta.GetGeneration()
s.UID = meta.GetUID()
s.ResourceVersion = meta.GetResourceVersion()
s.CreationTimestamp = meta.GetCreationTimestamp()
s.DeletionTimestamp = meta.GetDeletionTimestamp()
s.Labels = meta.GetLabels()
s.Annotations = meta.GetAnnotations()
}
return s
}
func (in *SummarizedObjectList) DeepCopyInto(out *SummarizedObjectList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]SummarizedObject, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
func (in *SummarizedObjectList) DeepCopy() *SummarizedObjectList {
if in == nil {
return nil
}
out := new(SummarizedObjectList)
in.DeepCopyInto(out)
return out
}
func (in *SummarizedObjectList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
func (in *SummarizedObject) DeepCopyInto(out *SummarizedObject) {
*out = *in
out.TypeMeta = in.TypeMeta
out.ObjectMeta = *in.ObjectMeta.DeepCopy()
out.Summary = *in.Summary.DeepCopy()
return
}
func (in *SummarizedObject) DeepCopy() *SummarizedObject {
if in == nil {
return nil
}
out := new(SummarizedObject)
in.DeepCopyInto(out)
return out
}
func (in *SummarizedObject) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}

View File

@@ -0,0 +1,425 @@
package summary
import (
"strings"
"time"
"github.com/rancher/wrangler/pkg/data"
"github.com/rancher/wrangler/pkg/data/convert"
"github.com/rancher/wrangler/pkg/kv"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
kstatus "sigs.k8s.io/cli-utils/pkg/kstatus/status"
)
const (
kindSep = ", Kind="
)
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",
"Upgraded": "upgrading",
"Waiting": "waiting",
"InitialRolesPopulated": "activating",
"ScalingActive": "pending",
"AbleToScale": "pending",
"RunCompleted": "running",
"Processed": "processed",
}
// 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{
checkStatusSummary,
checkErrors,
checkTransitioning,
checkActive,
checkPhase,
checkInitializing,
checkRemoving,
checkStandard,
checkLoadBalancer,
checkPod,
checkPodSelector,
checkOwner,
checkApplyOwned,
checkCattleTypes,
}
}
func checkOwner(obj data.Object, conditions []Condition, summary Summary) Summary {
ustr := &unstructured.Unstructured{
Object: obj,
}
for _, ownerref := range ustr.GetOwnerReferences() {
rel := Relationship{
Name: ownerref.Name,
Kind: ownerref.Kind,
APIVersion: ownerref.APIVersion,
Type: "owner",
Inbound: true,
}
if ownerref.Controller != nil && *ownerref.Controller {
rel.ControlledBy = true
}
summary.Relationships = append(summary.Relationships, rel)
}
return summary
}
func checkStatusSummary(obj data.Object, conditions []Condition, summary Summary) Summary {
summaryObj := obj.Map("status", "display")
if len(summaryObj) == 0 {
summaryObj = obj.Map("status", "summary")
if len(summaryObj) == 0 {
return summary
}
}
obj = summaryObj
if _, ok := obj["state"]; ok {
summary.State = obj.String("state")
}
if _, ok := obj["transitioning"]; ok {
summary.Transitioning = obj.Bool("transitioning")
}
if _, ok := obj["error"]; ok {
summary.Error = obj.Bool("error")
}
if _, ok := obj["message"]; ok {
summary.Message = append(summary.Message, obj.String("message"))
}
return summary
}
func checkStandard(obj data.Object, conditions []Condition, summary Summary) Summary {
if summary.State != "" {
return summary
}
// this is a hack to not call the standard summarizers on norman mapped objects
if strings.HasPrefix(obj.String("type"), "/") {
return summary
}
result, err := kstatus.Compute(&unstructured.Unstructured{Object: obj})
if err != nil {
return summary
}
switch result.Status {
case kstatus.InProgressStatus:
summary.State = "in-progress"
summary.Message = append(summary.Message, result.Message)
summary.Transitioning = true
case kstatus.FailedStatus:
summary.State = "failed"
summary.Message = append(summary.Message, result.Message)
summary.Error = true
case kstatus.CurrentStatus:
summary.State = "active"
summary.Message = append(summary.Message, result.Message)
case kstatus.TerminatingStatus:
summary.State = "removing"
summary.Message = append(summary.Message, result.Message)
summary.Transitioning = true
}
return summary
}
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())
if summary.State == "active" || summary.State == "" {
summary.State = "error"
}
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" ||
obj.String("spec", "type") == "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
}
func isKind(obj data.Object, kind string, apiGroups ...string) bool {
if obj.String("kind") != kind {
return false
}
if len(apiGroups) == 0 {
return obj.String("apiVersion") == "v1"
}
if len(apiGroups) == 0 {
apiGroups = []string{""}
}
for _, group := range apiGroups {
switch {
case group == "":
if obj.String("apiVersion") == "v1" {
return true
}
case group[len(group)-1] == '/':
if strings.HasPrefix(obj.String("apiVersion"), group) {
return true
}
default:
if obj.String("apiVersion") != group {
return true
}
}
}
return false
}
func checkApplyOwned(obj data.Object, conditions []Condition, summary Summary) Summary {
if len(obj.Slice("metadata", "ownerReferences")) > 0 {
return summary
}
annotations := obj.Map("metadata", "annotations")
gvkString := convert.ToString(annotations["objectset.rio.cattle.io/owner-gvk"])
i := strings.Index(gvkString, kindSep)
if i <= 0 {
return summary
}
name := convert.ToString(annotations["objectset.rio.cattle.io/owner-name"])
namespace := convert.ToString(annotations["objectset.rio.cattle.io/owner-namespace"])
apiVersion := gvkString[:i]
kind := gvkString[i+len(kindSep):]
rel := Relationship{
Name: name,
Namespace: namespace,
Kind: kind,
APIVersion: apiVersion,
Type: "applies",
Inbound: true,
}
summary.Relationships = append(summary.Relationships, rel)
return summary
}

View File

@@ -0,0 +1,123 @@
package summary
import (
"strings"
"github.com/rancher/wrangler/pkg/data"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
)
type Summary struct {
State string
Error bool
Transitioning bool
Message []string
Attributes map[string]interface{}
Relationships []Relationship
}
type Relationship struct {
Name string
Namespace string
ControlledBy bool
Kind string
APIVersion string
Inbound bool
Type string
Selector *metav1.LabelSelector
}
func (s Summary) String() string {
if !s.Transitioning && !s.Error {
return s.State
}
var msg string
if s.Transitioning {
msg = "[progressing"
}
if s.Error {
if len(msg) > 0 {
msg += ",error]"
} else {
msg = "error]"
}
} else {
msg += "]"
}
if len(s.Message) > 0 {
msg = msg + " " + s.Message[0]
}
return msg
}
func (s Summary) IsReady() bool {
return !s.Error && !s.Transitioning
}
func (s *Summary) DeepCopy() *Summary {
v := *s
return &v
}
func (s *Summary) DeepCopyInto(v *Summary) {
*v = *s
}
func dedupMessage(messages []string) []string {
if len(messages) <= 1 {
return messages
}
seen := map[string]bool{}
var result []string
for _, message := range messages {
message = strings.TrimSpace(message)
if message == "" {
continue
}
if seen[message] {
continue
}
seen[message] = true
result = append(result, message)
}
return result
}
func Summarize(runtimeObj runtime.Object) Summary {
var (
obj data.Object
summary Summary
)
if s, ok := runtimeObj.(*SummarizedObject); ok {
return s.Summary
}
unstr, ok := runtimeObj.(*unstructured.Unstructured)
if !ok {
return 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"
}
summary.State = strings.ToLower(summary.State)
summary.Message = dedupMessage(summary.Message)
return summary
}