gke-deploy/core/resource/ready.go (460 lines of code) (raw):

package resource import ( "context" "fmt" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) // IsReady returns true if a deployed object is ready. Please check the comments of each kind's // implementation for a description of what is considered to be ready for that kind of object. func IsReady(ctx context.Context, obj *Object) (bool, error) { kind := ObjectKind(obj) switch kind { case "DaemonSet": return daemonSetIsReady(ctx, obj) case "Deployment": return deploymentIsReady(ctx, obj) case "PersistentVolumeClaim": return persistentVolumeClaimIsReady(ctx, obj) case "Pod": return podIsReady(ctx, obj) case "PodDisruptionBudget": return podDisruptionBudgetIsReady(ctx, obj) case "ReplicaSet": return replicaSetIsReady(ctx, obj) case "ReplicationController": return replicationControllerIsReady(ctx, obj) case "Service": return serviceIsReady(ctx, obj) case "StatefulSet": return statefulSetIsReady(ctx, obj) default: return true, nil } } // daemonSetIsReady returns true if a deployed object with kind "DaemonSet" is ready. // This returns true if the following bullets are true: // * status.observedGeneration == metadata.generation // * status.numberAvailable == status.desiredNumberScheduled // * status.numberReady == status.desiredNumberScheduled func daemonSetIsReady(ctx context.Context, obj *Object) (bool, error) { generation, ok, err := unstructured.NestedInt64(obj.Object, "metadata", "generation") if err != nil { return false, fmt.Errorf("failed to get metadata.generation field: %v", err) } if !ok { return false, nil } observedGeneration, ok, err := unstructured.NestedInt64(obj.Object, "status", "observedGeneration") if err != nil { return false, fmt.Errorf("failed to get status.observedGeneration field: %v", err) } if !ok || observedGeneration != generation { return false, nil } desiredNumberScheduled, ok, err := unstructured.NestedInt64(obj.Object, "status", "desiredNumberScheduled") if err != nil { return false, fmt.Errorf("failed to get status.desiredNumberScheduled field: %v", err) } if !ok { return false, nil } numberAvailable, ok, err := unstructured.NestedInt64(obj.Object, "status", "numberAvailable") if err != nil { return false, fmt.Errorf("failed to get status.numberAvailable field: %v", err) } if !ok || numberAvailable != desiredNumberScheduled { return false, nil } numberReady, ok, err := unstructured.NestedInt64(obj.Object, "status", "numberReady") if err != nil { return false, fmt.Errorf("failed to get status.numberReady field: %v", err) } if !ok || numberReady != desiredNumberScheduled { return false, nil } return true, nil } // deploymentIsReady returns true if a deployed object with kind "Deployment" is ready. // This returns true if the following bullets are true: // * status.observedGeneration == metadata.generation // * status.replicas == spec.replicas // * status.readyReplicas == spec.replicas // * status.availableReplicas == spec.replicas // * status.conditions is not empty // * All items in status.conditions matches any: // * type == "Progressing" AND status == "True" AND reason == "NewReplicaSetAvailable" // * type == "Available" AND status == "True" // * All items in status.conditions do not match any: // * type == "ReplicaFailure" AND status ==" True" func deploymentIsReady(ctx context.Context, obj *Object) (bool, error) { generation, ok, err := unstructured.NestedInt64(obj.Object, "metadata", "generation") if err != nil { return false, fmt.Errorf("failed to get metadata.generation field: %v", err) } if !ok { return false, nil } observedGeneration, ok, err := unstructured.NestedInt64(obj.Object, "status", "observedGeneration") if err != nil { return false, fmt.Errorf("failed to get status.observedGeneration field: %v", err) } if !ok || observedGeneration != generation { return false, nil } specReplicas, ok, err := unstructured.NestedInt64(obj.Object, "spec", "replicas") if err != nil { return false, fmt.Errorf("failed to get spec.replicas field: %v", err) } if !ok { return false, nil } statusReplicas, _, err := unstructured.NestedInt64(obj.Object, "status", "replicas") if err != nil { return false, fmt.Errorf("failed to get status.replicas field: %v", err) } if statusReplicas != specReplicas { return false, nil } readyReplicas, _, err := unstructured.NestedInt64(obj.Object, "status", "readyReplicas") if err != nil { return false, fmt.Errorf("failed to get status.readyReplicas field: %v", err) } if readyReplicas != specReplicas { return false, nil } availableReplicas, _, err := unstructured.NestedInt64(obj.Object, "status", "availableReplicas") if err != nil { return false, fmt.Errorf("failed to get status.availableReplicas field: %v", err) } if availableReplicas != specReplicas { return false, nil } conditions, ok, err := unstructured.NestedSlice(obj.Object, "status", "conditions") if err != nil { return false, fmt.Errorf("failed to get status.conditions field: %v", err) } if !ok || len(conditions) == 0 { return false, nil } for _, c := range conditions { cMap, ok := c.(map[string]interface{}) if !ok { return false, fmt.Errorf("failed to convert conditions to map") } cType, ok, err := unstructured.NestedString(cMap, "type") if err != nil { return false, fmt.Errorf("failed to get type field: %v", err) } if !ok || cType == "" { return false, nil } switch cType { case "Available": status, ok, err := unstructured.NestedString(cMap, "status") if err != nil { return false, fmt.Errorf("failed to get status field: %v", err) } if !ok || status != "True" { return false, nil } case "Progressing": status, ok, err := unstructured.NestedString(cMap, "status") if err != nil { return false, fmt.Errorf("failed to get status field: %v", err) } if !ok || status == "" { return false, nil } reason, ok, err := unstructured.NestedString(cMap, "reason") if err != nil { return false, fmt.Errorf("failed to get reason field: %v", err) } if !ok || status != "True" || reason != "NewReplicaSetAvailable" { return false, nil } case "ReplicaFailure": status, ok, err := unstructured.NestedString(cMap, "status") if err != nil { return false, fmt.Errorf("failed to get status field: %v", err) } if !ok || status == "True" { return false, nil } default: return false, nil } } return true, nil } // persistentVolumeClaimIsReady returns true if a deployed object with kind "PersistentVolumeClaim" is ready. // This returns true if the following bullets are true: // * status.phase == "Bound" func persistentVolumeClaimIsReady(ctx context.Context, obj *Object) (bool, error) { phase, ok, err := unstructured.NestedString(obj.Object, "status", "phase") if err != nil { return false, fmt.Errorf("failed to get status.phase field: %v", err) } if !ok || phase == "" { return false, nil } return phase == "Bound", nil } // podIsReady returns true if a deployed object with kind "Pod" is ready. // This returns true if the following bullets are true: // * status.conditions contains at least one item that matches any: // * type == "Ready" AND status == "True" // * type == "Ready" AND reason == "PodCompleted" func podIsReady(ctx context.Context, obj *Object) (bool, error) { conditions, ok, err := unstructured.NestedSlice(obj.Object, "status", "conditions") if err != nil { return false, fmt.Errorf("failed to get status.conditions field: %v", err) } if !ok || len(conditions) == 0 { return false, nil } for _, c := range conditions { cMap, ok := c.(map[string]interface{}) if !ok { return false, fmt.Errorf("failed to convert conditions to map") } cType, ok, err := unstructured.NestedString(cMap, "type") if err != nil { return false, fmt.Errorf("failed to get type field: %v", err) } if !ok || cType == "" { return false, nil } switch cType { case "Ready": status, ok, err := unstructured.NestedString(cMap, "status") if err != nil { return false, fmt.Errorf("failed to get status field: %v", err) } if !ok { return false, nil } if status == "True" { return true, nil } reason, ok, err := unstructured.NestedString(cMap, "reason") if err != nil { return false, fmt.Errorf("failed to get reason field: %v", err) } if !ok { return false, nil } if reason == "PodCompleted" { return true, nil } default: // Skip } } return false, nil } // podDisruptionBudget returns true if a deployed object with kind "PodDisruptionBudget" is ready. // This returns true if the following bullets are true: // * status.observedGeneration == metadata.generation // * status.currentHealthy >= status.desiredHealthy func podDisruptionBudgetIsReady(ctx context.Context, obj *Object) (bool, error) { generation, ok, err := unstructured.NestedInt64(obj.Object, "metadata", "generation") if err != nil { return false, fmt.Errorf("failed to get metadata.generation field: %v", err) } if !ok { return false, nil } observedGeneration, ok, err := unstructured.NestedInt64(obj.Object, "status", "observedGeneration") if err != nil { return false, fmt.Errorf("failed to get status.observedGeneration field: %v", err) } if !ok || observedGeneration != generation { return false, nil } desiredHealthy, ok, err := unstructured.NestedInt64(obj.Object, "status", "desiredHealthy") if err != nil { return false, fmt.Errorf("failed to get status.desiredHealthy field: %v", err) } currentHealthy, ok, err := unstructured.NestedInt64(obj.Object, "status", "currentHealthy") if err != nil { return false, fmt.Errorf("failed to get status.currentHealthy field: %v", err) } if !ok || currentHealthy < desiredHealthy { return false, nil } return true, nil } // replicaSetIsReady returns true if a deployed object with kind "ReplicaSet" is ready. // This returns true if the following bullets are true: // * status.observedGeneration == metadata.generation // * status.replicas == spec.replicas // * status.readyReplicas == spec.replicas // * status.availableReplicas == spec.replicas // * All items in status.conditions do not match any: // * type == "ReplicaFailure" AND status == "True" func replicaSetIsReady(ctx context.Context, obj *Object) (bool, error) { generation, ok, err := unstructured.NestedInt64(obj.Object, "metadata", "generation") if err != nil { return false, fmt.Errorf("failed to get metadata.generation field: %v", err) } if !ok { return false, nil } observedGeneration, ok, err := unstructured.NestedInt64(obj.Object, "status", "observedGeneration") if err != nil { return false, fmt.Errorf("failed to get status.observedGeneration field: %v", err) } if !ok || observedGeneration != generation { return false, nil } specReplicas, ok, err := unstructured.NestedInt64(obj.Object, "spec", "replicas") if err != nil { return false, fmt.Errorf("failed to get spec.replicas field: %v", err) } if !ok { return false, nil } statusReplicas, ok, err := unstructured.NestedInt64(obj.Object, "status", "replicas") if err != nil { return false, fmt.Errorf("failed to get status.replicas field: %v", err) } if !ok || statusReplicas != specReplicas { return false, nil } readyReplicas, ok, err := unstructured.NestedInt64(obj.Object, "status", "readyReplicas") if err != nil { return false, fmt.Errorf("failed to get status.readyReplicas field: %v", err) } if !ok || readyReplicas != specReplicas { return false, nil } availableReplicas, ok, err := unstructured.NestedInt64(obj.Object, "status", "availableReplicas") if err != nil { return false, fmt.Errorf("failed to get status.availableReplicas field: %v", err) } if !ok || availableReplicas != specReplicas { return false, nil } conditions, ok, err := unstructured.NestedSlice(obj.Object, "status", "conditions") if err != nil { return false, fmt.Errorf("failed to get status.conditions field: %v", err) } if !ok || len(conditions) == 0 { return true, nil } for _, c := range conditions { cMap, ok := c.(map[string]interface{}) if !ok { return false, fmt.Errorf("failed to convert conditions to map") } cType, ok, err := unstructured.NestedString(cMap, "type") if err != nil { return false, fmt.Errorf("failed to get type field: %v", err) } if !ok || cType == "" { return false, nil } switch cType { case "ReplicaFailure": status, ok, err := unstructured.NestedString(cMap, "status") if err != nil { return false, fmt.Errorf("failed to get status field: %v", err) } if !ok || status == "True" { return false, nil } default: // Skip } } return true, nil } // replicationControllerIsReady returns true if a deployed object with kind "ReplicaSet" is ready. // This returns true if the following bullets are true: // * status.observedGeneration == metadata.generation // * status.replicas == spec.replicas // * status.readyReplicas == spec.replicas // * status.availableReplicas == spec.replicas func replicationControllerIsReady(ctx context.Context, obj *Object) (bool, error) { generation, ok, err := unstructured.NestedInt64(obj.Object, "metadata", "generation") if err != nil { return false, fmt.Errorf("failed to get metadata.generation field: %v", err) } if !ok { return false, nil } observedGeneration, ok, err := unstructured.NestedInt64(obj.Object, "status", "observedGeneration") if err != nil { return false, fmt.Errorf("failed to get status.observedGeneration field: %v", err) } if !ok || observedGeneration != generation { return false, nil } specReplicas, ok, err := unstructured.NestedInt64(obj.Object, "spec", "replicas") if err != nil { return false, fmt.Errorf("failed to get spec.replicas field: %v", err) } if !ok { return false, nil } statusReplicas, ok, err := unstructured.NestedInt64(obj.Object, "status", "replicas") if err != nil { return false, fmt.Errorf("failed to get status.replicas field: %v", err) } if !ok || statusReplicas != specReplicas { return false, nil } readyReplicas, ok, err := unstructured.NestedInt64(obj.Object, "status", "readyReplicas") if err != nil { return false, fmt.Errorf("failed to get status.readyReplicas field: %v", err) } if !ok || readyReplicas != specReplicas { return false, nil } availableReplicas, ok, err := unstructured.NestedInt64(obj.Object, "status", "availableReplicas") if err != nil { return false, fmt.Errorf("failed to get status.availableReplicas field: %v", err) } if !ok || availableReplicas != specReplicas { return false, nil } return true, nil } // serviceIsReady returns true if a deployed object with kind "Service" is ready. // This returns true if the following bullets are true: // * Any of the following are true // * type == "ClusterIP" (default) // * type == "NodePort" // * type == "ExternalName" // * type == "LoadBalancer" AND "spec.clusterIP" is not empty AND "status.loadBalancer.ingress" is // not empty AND all objects in "status.loadBalancer.ingress" has an "ip" that is not empty func serviceIsReady(ctx context.Context, obj *Object) (bool, error) { serviceType, ok, err := unstructured.NestedString(obj.Object, "spec", "type") if err != nil { return false, fmt.Errorf("failed to get spec.type field: %v", err) } if !ok || serviceType == "" { return false, nil } if serviceType == "ClusterIP" || serviceType == "NodePort" || serviceType == "ExternalName" { return true, nil } clusterIP, ok, err := unstructured.NestedString(obj.Object, "spec", "clusterIP") if err != nil { return false, fmt.Errorf("failed to get spec.clusterIP field: %v", err) } if !ok || clusterIP == "" { return false, nil } ingress, ok, err := unstructured.NestedSlice(obj.Object, "status", "loadBalancer", "ingress") if err != nil { return false, fmt.Errorf("failed to get status.loadBalancer.ingress field: %v", err) } if !ok || len(ingress) == 0 { return false, nil } for _, i := range ingress { iMap, ok := i.(map[string]interface{}) if !ok { return false, fmt.Errorf("failed to convert ingress to map") } ip, ok, err := unstructured.NestedString(iMap, "ip") if err != nil { return false, fmt.Errorf("failed to get ip field: %v", err) } if !ok || ip == "" { return false, nil } } return true, nil } // statefulSetIsReady returns true if a deployed object with kind "Service" is ready. // This returns true if the following bullets are true: // * status.observedGeneration == metadata.generation // * status.replicas == spec.replicas // * status.readyReplicas == spec.replicas // * status.currentReplicas == spec.replicas func statefulSetIsReady(ctx context.Context, obj *Object) (bool, error) { generation, ok, err := unstructured.NestedInt64(obj.Object, "metadata", "generation") if err != nil { return false, fmt.Errorf("failed to get metadata.generation field: %v", err) } if !ok { return false, nil } observedGeneration, ok, err := unstructured.NestedInt64(obj.Object, "status", "observedGeneration") if err != nil { return false, fmt.Errorf("failed to get status.observedGeneration field: %v", err) } if !ok || observedGeneration != generation { return false, nil } specReplicas, ok, err := unstructured.NestedInt64(obj.Object, "spec", "replicas") if err != nil { return false, fmt.Errorf("failed to get spec.replicas field: %v", err) } if !ok { return false, nil } statusReplicas, ok, err := unstructured.NestedInt64(obj.Object, "status", "replicas") if err != nil { return false, fmt.Errorf("failed to get status.replicas field: %v", err) } if !ok || statusReplicas != specReplicas { return false, nil } readyReplicas, ok, err := unstructured.NestedInt64(obj.Object, "status", "readyReplicas") if err != nil { return false, fmt.Errorf("failed to get status.readyReplicas field: %v", err) } if !ok || readyReplicas != specReplicas { return false, nil } currentReplicas, ok, err := unstructured.NestedInt64(obj.Object, "status", "currentReplicas") if err != nil { return false, fmt.Errorf("failed to get status.currentReplicas field: %v", err) } if !ok || currentReplicas != specReplicas { return false, nil } return true, nil }