...

Source file src/sigs.k8s.io/cli-utils/pkg/kstatus/status/core.go

Documentation: sigs.k8s.io/cli-utils/pkg/kstatus/status

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package status
     5  
     6  import (
     7  	"fmt"
     8  	"math"
     9  	"strings"
    10  	"time"
    11  
    12  	corev1 "k8s.io/api/core/v1"
    13  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    14  )
    15  
    16  // GetConditionsFn defines the signature for functions to compute the
    17  // status of a built-in resource.
    18  type GetConditionsFn func(*unstructured.Unstructured) (*Result, error)
    19  
    20  // legacyTypes defines the mapping from GroupKind to a function that can
    21  // compute the status for the given resource.
    22  var legacyTypes = map[string]GetConditionsFn{
    23  	"Service":                    serviceConditions,
    24  	"Pod":                        podConditions,
    25  	"Secret":                     alwaysReady,
    26  	"PersistentVolumeClaim":      pvcConditions,
    27  	"apps/StatefulSet":           stsConditions,
    28  	"apps/DaemonSet":             daemonsetConditions,
    29  	"extensions/DaemonSet":       daemonsetConditions,
    30  	"apps/Deployment":            deploymentConditions,
    31  	"extensions/Deployment":      deploymentConditions,
    32  	"apps/ReplicaSet":            replicasetConditions,
    33  	"extensions/ReplicaSet":      replicasetConditions,
    34  	"policy/PodDisruptionBudget": pdbConditions,
    35  	"batch/CronJob":              alwaysReady,
    36  	"ConfigMap":                  alwaysReady,
    37  	"batch/Job":                  jobConditions,
    38  	"apiextensions.k8s.io/CustomResourceDefinition": crdConditions,
    39  }
    40  
    41  const (
    42  	tooFewReady     = "LessReady"
    43  	tooFewAvailable = "LessAvailable"
    44  	tooFewUpdated   = "LessUpdated"
    45  	tooFewReplicas  = "LessReplicas"
    46  	extraPods       = "ExtraPods"
    47  
    48  	onDeleteUpdateStrategy = "OnDelete"
    49  
    50  	// How long a pod can be unscheduled before it is reported as
    51  	// unschedulable.
    52  	ScheduleWindow = 15 * time.Second
    53  )
    54  
    55  // GetLegacyConditionsFn returns a function that can compute the status for the
    56  // given resource, or nil if the resource type is not known.
    57  func GetLegacyConditionsFn(u *unstructured.Unstructured) GetConditionsFn {
    58  	gvk := u.GroupVersionKind()
    59  	g := gvk.Group
    60  	k := gvk.Kind
    61  	key := g + "/" + k
    62  	if g == "" {
    63  		key = k
    64  	}
    65  	return legacyTypes[key]
    66  }
    67  
    68  // alwaysReady Used for resources that are always ready
    69  func alwaysReady(u *unstructured.Unstructured) (*Result, error) {
    70  	return &Result{
    71  		Status:     CurrentStatus,
    72  		Message:    "Resource is always ready",
    73  		Conditions: []Condition{},
    74  	}, nil
    75  }
    76  
    77  // stsConditions return standardized Conditions for Statefulset
    78  //
    79  // StatefulSet does define the .status.conditions property, but the controller never
    80  // actually sets any Conditions. Thus, status must be computed only based on the other
    81  // properties under .status. We don't have any way to find out if a reconcile for a
    82  // StatefulSet has failed.
    83  func stsConditions(u *unstructured.Unstructured) (*Result, error) {
    84  	obj := u.UnstructuredContent()
    85  
    86  	// updateStrategy==ondelete is a user managed statefulset.
    87  	updateStrategy := GetStringField(obj, ".spec.updateStrategy.type", "")
    88  	if updateStrategy == onDeleteUpdateStrategy {
    89  		return &Result{
    90  			Status:     CurrentStatus,
    91  			Message:    "StatefulSet is using the ondelete update strategy",
    92  			Conditions: []Condition{},
    93  		}, nil
    94  	}
    95  
    96  	// Replicas
    97  	specReplicas := GetIntField(obj, ".spec.replicas", 1)
    98  	readyReplicas := GetIntField(obj, ".status.readyReplicas", 0)
    99  	currentReplicas := GetIntField(obj, ".status.currentReplicas", 0)
   100  	updatedReplicas := GetIntField(obj, ".status.updatedReplicas", 0)
   101  	statusReplicas := GetIntField(obj, ".status.replicas", 0)
   102  	partition := GetIntField(obj, ".spec.updateStrategy.rollingUpdate.partition", -1)
   103  
   104  	if specReplicas > statusReplicas {
   105  		message := fmt.Sprintf("Replicas: %d/%d", statusReplicas, specReplicas)
   106  		return newInProgressStatus(tooFewReplicas, message), nil
   107  	}
   108  
   109  	if specReplicas > readyReplicas {
   110  		message := fmt.Sprintf("Ready: %d/%d", readyReplicas, specReplicas)
   111  		return newInProgressStatus(tooFewReady, message), nil
   112  	}
   113  
   114  	if statusReplicas > specReplicas {
   115  		message := fmt.Sprintf("Pending termination: %d", statusReplicas-specReplicas)
   116  		return newInProgressStatus(extraPods, message), nil
   117  	}
   118  
   119  	// https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions
   120  	if partition != -1 {
   121  		if updatedReplicas < (specReplicas - partition) {
   122  			message := fmt.Sprintf("updated: %d/%d", updatedReplicas, specReplicas-partition)
   123  			return newInProgressStatus("PartitionRollout", message), nil
   124  		}
   125  		// Partition case All ok
   126  		return &Result{
   127  			Status:     CurrentStatus,
   128  			Message:    fmt.Sprintf("Partition rollout complete. updated: %d", updatedReplicas),
   129  			Conditions: []Condition{},
   130  		}, nil
   131  	}
   132  
   133  	if specReplicas > currentReplicas {
   134  		message := fmt.Sprintf("current: %d/%d", currentReplicas, specReplicas)
   135  		return newInProgressStatus("LessCurrent", message), nil
   136  	}
   137  
   138  	// Revision
   139  	currentRevision := GetStringField(obj, ".status.currentRevision", "")
   140  	updatedRevision := GetStringField(obj, ".status.updateRevision", "")
   141  	if currentRevision != updatedRevision {
   142  		message := "Waiting for updated revision to match current"
   143  		return newInProgressStatus("RevisionMismatch", message), nil
   144  	}
   145  
   146  	// All ok
   147  	return &Result{
   148  		Status:     CurrentStatus,
   149  		Message:    fmt.Sprintf("All replicas scheduled as expected. Replicas: %d", statusReplicas),
   150  		Conditions: []Condition{},
   151  	}, nil
   152  }
   153  
   154  // deploymentConditions return standardized Conditions for Deployment.
   155  //
   156  // For Deployments, we look at .status.conditions as well as the other properties
   157  // under .status. Status will be Failed if the progress deadline has been exceeded.
   158  func deploymentConditions(u *unstructured.Unstructured) (*Result, error) {
   159  	obj := u.UnstructuredContent()
   160  
   161  	progressing := false
   162  
   163  	// Check if progressDeadlineSeconds is set. If not, the controller will not set
   164  	// the `Progressing` condition, so it will always consider a deployment to be
   165  	// progressing. The use of math.MaxInt32 is due to special handling in the
   166  	// controller:
   167  	// https://github.com/kubernetes/kubernetes/blob/a3ccea9d8743f2ff82e41b6c2af6dc2c41dc7b10/pkg/controller/deployment/util/deployment_util.go#L886
   168  	progressDeadline := GetIntField(obj, ".spec.progressDeadlineSeconds", math.MaxInt32)
   169  	if progressDeadline == math.MaxInt32 {
   170  		progressing = true
   171  	}
   172  
   173  	available := false
   174  
   175  	objc, err := GetObjectWithConditions(obj)
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  
   180  	for _, c := range objc.Status.Conditions {
   181  		switch c.Type {
   182  		case "Progressing": // appsv1.DeploymentProgressing:
   183  			// https://github.com/kubernetes/kubernetes/blob/a3ccea9d8743f2ff82e41b6c2af6dc2c41dc7b10/pkg/controller/deployment/progress.go#L52
   184  			if c.Reason == "ProgressDeadlineExceeded" {
   185  				return &Result{
   186  					Status:     FailedStatus,
   187  					Message:    "Progress deadline exceeded",
   188  					Conditions: []Condition{{ConditionStalled, corev1.ConditionTrue, c.Reason, c.Message}},
   189  				}, nil
   190  			}
   191  			if c.Status == corev1.ConditionTrue && c.Reason == "NewReplicaSetAvailable" {
   192  				progressing = true
   193  			}
   194  		case "Available": // appsv1.DeploymentAvailable:
   195  			if c.Status == corev1.ConditionTrue {
   196  				available = true
   197  			}
   198  		}
   199  	}
   200  
   201  	// replicas
   202  	specReplicas := GetIntField(obj, ".spec.replicas", 1) // Controller uses 1 as default if not specified.
   203  	statusReplicas := GetIntField(obj, ".status.replicas", 0)
   204  	updatedReplicas := GetIntField(obj, ".status.updatedReplicas", 0)
   205  	readyReplicas := GetIntField(obj, ".status.readyReplicas", 0)
   206  	availableReplicas := GetIntField(obj, ".status.availableReplicas", 0)
   207  
   208  	// TODO spec.replicas zero case ??
   209  
   210  	if specReplicas > statusReplicas {
   211  		message := fmt.Sprintf("Replicas: %d/%d", statusReplicas, specReplicas)
   212  		return newInProgressStatus(tooFewReplicas, message), nil
   213  	}
   214  
   215  	if specReplicas > updatedReplicas {
   216  		message := fmt.Sprintf("Updated: %d/%d", updatedReplicas, specReplicas)
   217  		return newInProgressStatus(tooFewUpdated, message), nil
   218  	}
   219  
   220  	if statusReplicas > specReplicas {
   221  		message := fmt.Sprintf("Pending termination: %d", statusReplicas-specReplicas)
   222  		return newInProgressStatus(extraPods, message), nil
   223  	}
   224  
   225  	if updatedReplicas > availableReplicas {
   226  		message := fmt.Sprintf("Available: %d/%d", availableReplicas, updatedReplicas)
   227  		return newInProgressStatus(tooFewAvailable, message), nil
   228  	}
   229  
   230  	if specReplicas > readyReplicas {
   231  		message := fmt.Sprintf("Ready: %d/%d", readyReplicas, specReplicas)
   232  		return newInProgressStatus(tooFewReady, message), nil
   233  	}
   234  
   235  	// check conditions
   236  	if !progressing {
   237  		message := "ReplicaSet not Available"
   238  		return newInProgressStatus("ReplicaSetNotAvailable", message), nil
   239  	}
   240  	if !available {
   241  		message := "Deployment not Available"
   242  		return newInProgressStatus("DeploymentNotAvailable", message), nil
   243  	}
   244  	// All ok
   245  	return &Result{
   246  		Status:     CurrentStatus,
   247  		Message:    fmt.Sprintf("Deployment is available. Replicas: %d", statusReplicas),
   248  		Conditions: []Condition{},
   249  	}, nil
   250  }
   251  
   252  // replicasetConditions return standardized Conditions for Replicaset
   253  func replicasetConditions(u *unstructured.Unstructured) (*Result, error) {
   254  	obj := u.UnstructuredContent()
   255  
   256  	// Conditions
   257  	objc, err := GetObjectWithConditions(obj)
   258  	if err != nil {
   259  		return nil, err
   260  	}
   261  
   262  	for _, c := range objc.Status.Conditions {
   263  		// https://github.com/kubernetes/kubernetes/blob/a3ccea9d8743f2ff82e41b6c2af6dc2c41dc7b10/pkg/controller/replicaset/replica_set_utils.go
   264  		if c.Type == "ReplicaFailure" && c.Status == corev1.ConditionTrue {
   265  			message := "Replica Failure condition. Check Pods"
   266  			return newInProgressStatus("ReplicaFailure", message), nil
   267  		}
   268  	}
   269  
   270  	// Replicas
   271  	specReplicas := GetIntField(obj, ".spec.replicas", 1) // Controller uses 1 as default if not specified.
   272  	statusReplicas := GetIntField(obj, ".status.replicas", 0)
   273  	readyReplicas := GetIntField(obj, ".status.readyReplicas", 0)
   274  	availableReplicas := GetIntField(obj, ".status.availableReplicas", 0)
   275  	fullyLabelledReplicas := GetIntField(obj, ".status.fullyLabeledReplicas", 0)
   276  
   277  	if specReplicas > fullyLabelledReplicas {
   278  		message := fmt.Sprintf("Labelled: %d/%d", fullyLabelledReplicas, specReplicas)
   279  		return newInProgressStatus("LessLabelled", message), nil
   280  	}
   281  
   282  	if specReplicas > availableReplicas {
   283  		message := fmt.Sprintf("Available: %d/%d", availableReplicas, specReplicas)
   284  		return newInProgressStatus(tooFewAvailable, message), nil
   285  	}
   286  
   287  	if specReplicas > readyReplicas {
   288  		message := fmt.Sprintf("Ready: %d/%d", readyReplicas, specReplicas)
   289  		return newInProgressStatus(tooFewReady, message), nil
   290  	}
   291  
   292  	if statusReplicas > specReplicas {
   293  		message := fmt.Sprintf("Pending termination: %d", statusReplicas-specReplicas)
   294  		return newInProgressStatus(extraPods, message), nil
   295  	}
   296  	// All ok
   297  	return &Result{
   298  		Status:     CurrentStatus,
   299  		Message:    fmt.Sprintf("ReplicaSet is available. Replicas: %d", statusReplicas),
   300  		Conditions: []Condition{},
   301  	}, nil
   302  }
   303  
   304  // daemonsetConditions return standardized Conditions for DaemonSet
   305  func daemonsetConditions(u *unstructured.Unstructured) (*Result, error) {
   306  	// We check that the latest generation is equal to observed generation as
   307  	// part of checking generic properties but in that case, we are lenient and
   308  	// skip the check if those fields are unset. For daemonset, we know that if
   309  	// the daemonset controller has acted on a resource, these fields would not
   310  	// be unset. So, we ensure that here.
   311  	res, err := checkGenerationSet(u)
   312  	if err != nil || res != nil {
   313  		return res, err
   314  	}
   315  
   316  	obj := u.UnstructuredContent()
   317  
   318  	// replicas
   319  	desiredNumberScheduled := GetIntField(obj, ".status.desiredNumberScheduled", -1)
   320  	currentNumberScheduled := GetIntField(obj, ".status.currentNumberScheduled", 0)
   321  	updatedNumberScheduled := GetIntField(obj, ".status.updatedNumberScheduled", 0)
   322  	numberAvailable := GetIntField(obj, ".status.numberAvailable", 0)
   323  	numberReady := GetIntField(obj, ".status.numberReady", 0)
   324  
   325  	if desiredNumberScheduled == -1 {
   326  		message := "Missing .status.desiredNumberScheduled"
   327  		return newInProgressStatus("NoDesiredNumber", message), nil
   328  	}
   329  
   330  	if desiredNumberScheduled > currentNumberScheduled {
   331  		message := fmt.Sprintf("Current: %d/%d", currentNumberScheduled, desiredNumberScheduled)
   332  		return newInProgressStatus("LessCurrent", message), nil
   333  	}
   334  
   335  	if desiredNumberScheduled > updatedNumberScheduled {
   336  		message := fmt.Sprintf("Updated: %d/%d", updatedNumberScheduled, desiredNumberScheduled)
   337  		return newInProgressStatus(tooFewUpdated, message), nil
   338  	}
   339  
   340  	if desiredNumberScheduled > numberAvailable {
   341  		message := fmt.Sprintf("Available: %d/%d", numberAvailable, desiredNumberScheduled)
   342  		return newInProgressStatus(tooFewAvailable, message), nil
   343  	}
   344  
   345  	if desiredNumberScheduled > numberReady {
   346  		message := fmt.Sprintf("Ready: %d/%d", numberReady, desiredNumberScheduled)
   347  		return newInProgressStatus(tooFewReady, message), nil
   348  	}
   349  
   350  	// All ok
   351  	return &Result{
   352  		Status:     CurrentStatus,
   353  		Message:    fmt.Sprintf("All replicas scheduled as expected. Replicas: %d", desiredNumberScheduled),
   354  		Conditions: []Condition{},
   355  	}, nil
   356  }
   357  
   358  // checkGenerationSet checks that the metadata.generation and
   359  // status.observedGeneration fields are set.
   360  func checkGenerationSet(u *unstructured.Unstructured) (*Result, error) {
   361  	_, found, err := unstructured.NestedInt64(u.Object, "metadata", "generation")
   362  	if err != nil {
   363  		return nil, fmt.Errorf("looking up metadata.generation from resource: %w", err)
   364  	}
   365  	if !found {
   366  		message := fmt.Sprintf("%s metadata.generation not found", u.GetKind())
   367  		return &Result{
   368  			Status:     InProgressStatus,
   369  			Message:    message,
   370  			Conditions: []Condition{newReconcilingCondition("NoGeneration", message)},
   371  		}, nil
   372  	}
   373  
   374  	_, found, err = unstructured.NestedInt64(u.Object, "status", "observedGeneration")
   375  	if err != nil {
   376  		return nil, fmt.Errorf("looking up status.observedGeneration from resource: %w", err)
   377  	}
   378  	if !found {
   379  		message := fmt.Sprintf("%s status.observedGeneration not found", u.GetKind())
   380  		return &Result{
   381  			Status:     InProgressStatus,
   382  			Message:    message,
   383  			Conditions: []Condition{newReconcilingCondition("NoObservedGeneration", message)},
   384  		}, nil
   385  	}
   386  
   387  	return nil, nil
   388  }
   389  
   390  // pvcConditions return standardized Conditions for PVC
   391  func pvcConditions(u *unstructured.Unstructured) (*Result, error) {
   392  	obj := u.UnstructuredContent()
   393  
   394  	phase := GetStringField(obj, ".status.phase", "unknown")
   395  	if phase != "Bound" { // corev1.ClaimBound
   396  		message := fmt.Sprintf("PVC is not Bound. phase: %s", phase)
   397  		return newInProgressStatus("NotBound", message), nil
   398  	}
   399  	// All ok
   400  	return &Result{
   401  		Status:     CurrentStatus,
   402  		Message:    "PVC is Bound",
   403  		Conditions: []Condition{},
   404  	}, nil
   405  }
   406  
   407  // podConditions return standardized Conditions for Pod
   408  func podConditions(u *unstructured.Unstructured) (*Result, error) {
   409  	obj := u.UnstructuredContent()
   410  	objc, err := GetObjectWithConditions(obj)
   411  	if err != nil {
   412  		return nil, err
   413  	}
   414  	phase := GetStringField(obj, ".status.phase", "")
   415  
   416  	switch phase {
   417  	case "Succeeded":
   418  		return &Result{
   419  			Status:     CurrentStatus,
   420  			Message:    "Pod has completed successfully",
   421  			Conditions: []Condition{},
   422  		}, nil
   423  	case "Failed":
   424  		return &Result{
   425  			Status:     CurrentStatus,
   426  			Message:    "Pod has completed, but not successfully",
   427  			Conditions: []Condition{},
   428  		}, nil
   429  	case "Running":
   430  		if hasConditionWithStatus(objc.Status.Conditions, "Ready", corev1.ConditionTrue) {
   431  			return &Result{
   432  				Status:     CurrentStatus,
   433  				Message:    "Pod is Ready",
   434  				Conditions: []Condition{},
   435  			}, nil
   436  		}
   437  
   438  		containerNames, isCrashLooping, err := getCrashLoopingContainers(obj)
   439  		if err != nil {
   440  			return nil, err
   441  		}
   442  		if isCrashLooping {
   443  			return newFailedStatus("ContainerCrashLooping",
   444  				fmt.Sprintf("Containers in CrashLoop state: %s", strings.Join(containerNames, ","))), nil
   445  		}
   446  
   447  		return newInProgressStatus("PodRunningNotReady", "Pod is running but is not Ready"), nil
   448  	case "Pending":
   449  		c, found := getConditionWithStatus(objc.Status.Conditions, "PodScheduled", corev1.ConditionFalse)
   450  		if found && c.Reason == "Unschedulable" {
   451  			if time.Now().Add(-ScheduleWindow).Before(u.GetCreationTimestamp().Time) {
   452  				// We give the pod 15 seconds to be scheduled before we report it
   453  				// as unschedulable.
   454  				return newInProgressStatus("PodNotScheduled", "Pod has not been scheduled"), nil
   455  			}
   456  			return newFailedStatus("PodUnschedulable", "Pod could not be scheduled"), nil
   457  		}
   458  		return newInProgressStatus("PodPending", "Pod is in the Pending phase"), nil
   459  	default:
   460  		// If the controller hasn't observed the pod yet, there is no phase. We consider this as it
   461  		// still being in progress.
   462  		if phase == "" {
   463  			return newInProgressStatus("PodNotObserved", "Pod phase not available"), nil
   464  		}
   465  		return nil, fmt.Errorf("unknown phase %s", phase)
   466  	}
   467  }
   468  
   469  func getCrashLoopingContainers(obj map[string]interface{}) ([]string, bool, error) {
   470  	var containerNames []string
   471  	css, found, err := unstructured.NestedSlice(obj, "status", "containerStatuses")
   472  	if !found || err != nil {
   473  		return containerNames, found, err
   474  	}
   475  	for _, item := range css {
   476  		cs := item.(map[string]interface{})
   477  		n, found := cs["name"]
   478  		if !found {
   479  			continue
   480  		}
   481  		name := n.(string)
   482  		s, found := cs["state"]
   483  		if !found {
   484  			continue
   485  		}
   486  		state := s.(map[string]interface{})
   487  
   488  		ws, found := state["waiting"]
   489  		if !found {
   490  			continue
   491  		}
   492  		waitingState := ws.(map[string]interface{})
   493  
   494  		r, found := waitingState["reason"]
   495  		if !found {
   496  			continue
   497  		}
   498  		reason := r.(string)
   499  		if reason == "CrashLoopBackOff" {
   500  			containerNames = append(containerNames, name)
   501  		}
   502  	}
   503  	if len(containerNames) > 0 {
   504  		return containerNames, true, nil
   505  	}
   506  	return containerNames, false, nil
   507  }
   508  
   509  // pdbConditions computes the status for PodDisruptionBudgets. A PDB
   510  // is currently considered Current if the disruption controller has
   511  // observed the latest version of the PDB resource and has computed
   512  // the AllowedDisruptions. PDBs do have ObservedGeneration in the
   513  // Status object, so if this function gets called we know that
   514  // the controller has observed the latest changes.
   515  // The disruption controller does not set any conditions if
   516  // computing the AllowedDisruptions fails (and there are many ways
   517  // it can fail), but there is PR against OSS Kubernetes to address
   518  // this: https://github.com/kubernetes/kubernetes/pull/86929
   519  func pdbConditions(_ *unstructured.Unstructured) (*Result, error) {
   520  	// All ok
   521  	return &Result{
   522  		Status:     CurrentStatus,
   523  		Message:    "AllowedDisruptions has been computed.",
   524  		Conditions: []Condition{},
   525  	}, nil
   526  }
   527  
   528  // jobConditions return standardized Conditions for Job
   529  //
   530  // A job will have the InProgress status until it starts running. Then it will have the Current
   531  // status while the job is running and after it has been completed successfully. It
   532  // will have the Failed status if it the job has failed.
   533  func jobConditions(u *unstructured.Unstructured) (*Result, error) {
   534  	obj := u.UnstructuredContent()
   535  
   536  	parallelism := GetIntField(obj, ".spec.parallelism", 1)
   537  	completions := GetIntField(obj, ".spec.completions", parallelism)
   538  	succeeded := GetIntField(obj, ".status.succeeded", 0)
   539  	active := GetIntField(obj, ".status.active", 0)
   540  	failed := GetIntField(obj, ".status.failed", 0)
   541  	starttime := GetStringField(obj, ".status.startTime", "")
   542  
   543  	// Conditions
   544  	// https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/job/utils.go#L24
   545  	objc, err := GetObjectWithConditions(obj)
   546  	if err != nil {
   547  		return nil, err
   548  	}
   549  	for _, c := range objc.Status.Conditions {
   550  		switch c.Type {
   551  		case "Complete":
   552  			if c.Status == corev1.ConditionTrue {
   553  				message := fmt.Sprintf("Job Completed. succeeded: %d/%d", succeeded, completions)
   554  				return &Result{
   555  					Status:     CurrentStatus,
   556  					Message:    message,
   557  					Conditions: []Condition{},
   558  				}, nil
   559  			}
   560  		case "Failed":
   561  			if c.Status == corev1.ConditionTrue {
   562  				return newFailedStatus("JobFailed",
   563  					fmt.Sprintf("Job Failed. failed: %d/%d", failed, completions)), nil
   564  			}
   565  		}
   566  	}
   567  
   568  	// replicas
   569  	if starttime == "" {
   570  		message := "Job not started"
   571  		return newInProgressStatus("JobNotStarted", message), nil
   572  	}
   573  	return &Result{
   574  		Status:     CurrentStatus,
   575  		Message:    fmt.Sprintf("Job in progress. success:%d, active: %d, failed: %d", succeeded, active, failed),
   576  		Conditions: []Condition{},
   577  	}, nil
   578  }
   579  
   580  // serviceConditions return standardized Conditions for Service
   581  func serviceConditions(u *unstructured.Unstructured) (*Result, error) {
   582  	obj := u.UnstructuredContent()
   583  
   584  	specType := GetStringField(obj, ".spec.type", "ClusterIP")
   585  	specClusterIP := GetStringField(obj, ".spec.clusterIP", "")
   586  
   587  	if specType == "LoadBalancer" {
   588  		if specClusterIP == "" {
   589  			message := "ClusterIP not set. Service type: LoadBalancer"
   590  			return newInProgressStatus("NoIPAssigned", message), nil
   591  		}
   592  	}
   593  
   594  	return &Result{
   595  		Status:     CurrentStatus,
   596  		Message:    "Service is ready",
   597  		Conditions: []Condition{},
   598  	}, nil
   599  }
   600  
   601  func crdConditions(u *unstructured.Unstructured) (*Result, error) {
   602  	obj := u.UnstructuredContent()
   603  
   604  	objc, err := GetObjectWithConditions(obj)
   605  	if err != nil {
   606  		return nil, err
   607  	}
   608  
   609  	for _, c := range objc.Status.Conditions {
   610  		if c.Type == "NamesAccepted" && c.Status == corev1.ConditionFalse {
   611  			return newFailedStatus(c.Reason, c.Message), nil
   612  		}
   613  		if c.Type == "Established" {
   614  			if c.Status == corev1.ConditionFalse && c.Reason != "Installing" {
   615  				return newFailedStatus(c.Reason, c.Message), nil
   616  			}
   617  			if c.Status == corev1.ConditionTrue {
   618  				return &Result{
   619  					Status:     CurrentStatus,
   620  					Message:    "CRD is established",
   621  					Conditions: []Condition{},
   622  				}, nil
   623  			}
   624  		}
   625  	}
   626  	return newInProgressStatus("Installing", "Install in progress"), nil
   627  }
   628  

View as plain text