...

Source file src/sigs.k8s.io/cli-utils/pkg/apply/filter/dependency-filter.go

Documentation: sigs.k8s.io/cli-utils/pkg/apply/filter

     1  // Copyright 2020 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package filter
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    11  	"sigs.k8s.io/cli-utils/pkg/apis/actuation"
    12  	"sigs.k8s.io/cli-utils/pkg/apply/taskrunner"
    13  	"sigs.k8s.io/cli-utils/pkg/common"
    14  	"sigs.k8s.io/cli-utils/pkg/object"
    15  )
    16  
    17  //go:generate stringer -type=Relationship -linecomment
    18  type Relationship int
    19  
    20  const (
    21  	RelationshipDependent  Relationship = iota // Dependent
    22  	RelationshipDependency                     // Dependency
    23  )
    24  
    25  //go:generate stringer -type=Phase -linecomment
    26  type Phase int
    27  
    28  const (
    29  	PhaseActuation Phase = iota // Actuation
    30  	PhaseReconcile              // Reconcile
    31  )
    32  
    33  // DependencyFilter implements ValidationFilter interface to determine if an
    34  // object can be applied or deleted based on the status of it's dependencies.
    35  type DependencyFilter struct {
    36  	TaskContext       *taskrunner.TaskContext
    37  	ActuationStrategy actuation.ActuationStrategy
    38  	DryRunStrategy    common.DryRunStrategy
    39  }
    40  
    41  const DependencyFilterName = "DependencyFilter"
    42  
    43  // Name returns the name of the filter for logs and events.
    44  func (dnrf DependencyFilter) Name() string {
    45  	return DependencyFilterName
    46  }
    47  
    48  // Filter returns an error if the specified object should be skipped because at
    49  // least one of its dependencies is Not Found or Not Reconciled.
    50  // Typed Errors:
    51  // - DependencyPreventedActuationError
    52  // - DependencyActuationMismatchError
    53  func (dnrf DependencyFilter) Filter(obj *unstructured.Unstructured) error {
    54  	id := object.UnstructuredToObjMetadata(obj)
    55  
    56  	switch dnrf.ActuationStrategy {
    57  	case actuation.ActuationStrategyApply:
    58  		// For apply, check dependencies (outgoing)
    59  		for _, depID := range dnrf.TaskContext.Graph().Dependencies(id) {
    60  			err := dnrf.filterByRelationship(id, depID, RelationshipDependency)
    61  			if err != nil {
    62  				return err
    63  			}
    64  		}
    65  	case actuation.ActuationStrategyDelete:
    66  		// For delete, check dependents (incoming)
    67  		for _, depID := range dnrf.TaskContext.Graph().Dependents(id) {
    68  			err := dnrf.filterByRelationship(id, depID, RelationshipDependent)
    69  			if err != nil {
    70  				return err
    71  			}
    72  		}
    73  	default:
    74  		return NewFatalError(fmt.Errorf("invalid actuation strategy: %q", dnrf.ActuationStrategy))
    75  	}
    76  	return nil
    77  }
    78  
    79  func (dnrf DependencyFilter) filterByRelationship(aID, bID object.ObjMetadata, relationship Relationship) error {
    80  	// Dependency on an invalid object is considered an invalid dependency, making both objects invalid.
    81  	// For applies: don't prematurely apply something that depends on something that hasn't been applied (because invalid).
    82  	// For deletes: don't prematurely delete something that depends on something that hasn't been deleted (because invalid).
    83  	// These can't be caught be subsequent checks, because invalid objects aren't in the inventory.
    84  	if dnrf.TaskContext.IsInvalidObject(bID) {
    85  		// Should have been caught in validation
    86  		return NewFatalError(fmt.Errorf("invalid %s: %s",
    87  			strings.ToLower(relationship.String()),
    88  			bID))
    89  	}
    90  
    91  	status, found := dnrf.TaskContext.InventoryManager().ObjectStatus(bID)
    92  	if !found {
    93  		// Status is registered during planning.
    94  		// So if status is not found, the object is external (NYI) or invalid.
    95  		// Should have been caught in validation.
    96  		return NewFatalError(fmt.Errorf("unknown %s actuation strategy: %s",
    97  			strings.ToLower(relationship.String()), bID))
    98  	}
    99  
   100  	// Dependencies must have the same actuation strategy.
   101  	// If there is a mismatch, skip both.
   102  	if status.Strategy != dnrf.ActuationStrategy {
   103  		// Skip!
   104  		return &DependencyActuationMismatchError{
   105  			Object:           aID,
   106  			Strategy:         dnrf.ActuationStrategy,
   107  			Relationship:     relationship,
   108  			Relation:         bID,
   109  			RelationStrategy: status.Strategy,
   110  		}
   111  	}
   112  
   113  	switch status.Actuation {
   114  	case actuation.ActuationPending:
   115  		// If actuation is still pending, dependency sorting is probably broken.
   116  		return NewFatalError(fmt.Errorf("premature %s: %s %s actuation %s: %s",
   117  			strings.ToLower(dnrf.ActuationStrategy.String()),
   118  			strings.ToLower(relationship.String()),
   119  			strings.ToLower(status.Strategy.String()),
   120  			strings.ToLower(status.Actuation.String()),
   121  			bID))
   122  	case actuation.ActuationSkipped, actuation.ActuationFailed:
   123  		// Skip!
   124  		return &DependencyPreventedActuationError{
   125  			Object:                  aID,
   126  			Strategy:                dnrf.ActuationStrategy,
   127  			Relationship:            relationship,
   128  			Relation:                bID,
   129  			RelationPhase:           PhaseActuation,
   130  			RelationActuationStatus: status.Actuation,
   131  			RelationReconcileStatus: status.Reconcile,
   132  		}
   133  	case actuation.ActuationSucceeded:
   134  		// Don't skip!
   135  	default:
   136  		// Should never happen
   137  		return NewFatalError(fmt.Errorf("invalid %s actuation status %q: %s",
   138  			strings.ToLower(relationship.String()),
   139  			strings.ToLower(status.Actuation.String()),
   140  			bID))
   141  	}
   142  
   143  	// DryRun skips WaitTasks, so reconcile status can be ignored
   144  	if dnrf.DryRunStrategy.ClientOrServerDryRun() {
   145  		// Don't skip!
   146  		return nil
   147  	}
   148  
   149  	switch status.Reconcile {
   150  	case actuation.ReconcilePending:
   151  		// If reconcile is still pending, dependency sorting is probably broken.
   152  		return NewFatalError(fmt.Errorf("premature %s: %s %s reconcile %s: %s",
   153  			strings.ToLower(dnrf.ActuationStrategy.String()),
   154  			strings.ToLower(relationship.String()),
   155  			strings.ToLower(status.Strategy.String()),
   156  			strings.ToLower(status.Reconcile.String()),
   157  			bID))
   158  	case actuation.ReconcileSkipped, actuation.ReconcileFailed, actuation.ReconcileTimeout:
   159  		// Skip!
   160  		return &DependencyPreventedActuationError{
   161  			Object:                  aID,
   162  			Strategy:                dnrf.ActuationStrategy,
   163  			Relationship:            relationship,
   164  			Relation:                bID,
   165  			RelationPhase:           PhaseReconcile,
   166  			RelationActuationStatus: status.Actuation,
   167  			RelationReconcileStatus: status.Reconcile,
   168  		}
   169  	case actuation.ReconcileSucceeded:
   170  		// Don't skip!
   171  	default:
   172  		// Should never happen
   173  		return NewFatalError(fmt.Errorf("invalid %s reconcile status %q: %s",
   174  			strings.ToLower(relationship.String()),
   175  			strings.ToLower(status.Reconcile.String()),
   176  			bID))
   177  	}
   178  
   179  	// Don't skip!
   180  	return nil
   181  }
   182  
   183  type DependencyPreventedActuationError struct {
   184  	Object       object.ObjMetadata
   185  	Strategy     actuation.ActuationStrategy
   186  	Relationship Relationship
   187  
   188  	Relation                object.ObjMetadata
   189  	RelationPhase           Phase
   190  	RelationActuationStatus actuation.ActuationStatus
   191  	RelationReconcileStatus actuation.ReconcileStatus
   192  }
   193  
   194  func (e *DependencyPreventedActuationError) Error() string {
   195  	switch e.RelationPhase {
   196  	case PhaseActuation:
   197  		return fmt.Sprintf("%s %s %s %s: %s",
   198  			strings.ToLower(e.Relationship.String()),
   199  			strings.ToLower(e.Strategy.String()),
   200  			strings.ToLower(e.RelationPhase.String()),
   201  			strings.ToLower(e.RelationActuationStatus.String()),
   202  			e.Relation)
   203  	case PhaseReconcile:
   204  		return fmt.Sprintf("%s %s %s %s: %s",
   205  			strings.ToLower(e.Relationship.String()),
   206  			strings.ToLower(e.Strategy.String()),
   207  			strings.ToLower(e.RelationPhase.String()),
   208  			strings.ToLower(e.RelationReconcileStatus.String()),
   209  			e.Relation)
   210  	default:
   211  		return fmt.Sprintf("invalid phase: %s", e.RelationPhase)
   212  	}
   213  }
   214  
   215  func (e *DependencyPreventedActuationError) Is(err error) bool {
   216  	if err == nil {
   217  		return false
   218  	}
   219  	tErr, ok := err.(*DependencyPreventedActuationError)
   220  	if !ok {
   221  		return false
   222  	}
   223  	return e.Object == tErr.Object &&
   224  		e.Strategy == tErr.Strategy &&
   225  		e.Relationship == tErr.Relationship &&
   226  		e.Relation == tErr.Relation &&
   227  		e.RelationPhase == tErr.RelationPhase &&
   228  		e.RelationActuationStatus == tErr.RelationActuationStatus &&
   229  		e.RelationReconcileStatus == tErr.RelationReconcileStatus
   230  }
   231  
   232  type DependencyActuationMismatchError struct {
   233  	Object       object.ObjMetadata
   234  	Strategy     actuation.ActuationStrategy
   235  	Relationship Relationship
   236  
   237  	Relation         object.ObjMetadata
   238  	RelationStrategy actuation.ActuationStrategy
   239  }
   240  
   241  func (e *DependencyActuationMismatchError) Error() string {
   242  	return fmt.Sprintf("%s scheduled for %s: %s",
   243  		strings.ToLower(e.Relationship.String()),
   244  		strings.ToLower(e.RelationStrategy.String()),
   245  		e.Relation)
   246  }
   247  
   248  func (e *DependencyActuationMismatchError) Is(err error) bool {
   249  	if err == nil {
   250  		return false
   251  	}
   252  	tErr, ok := err.(*DependencyActuationMismatchError)
   253  	if !ok {
   254  		return false
   255  	}
   256  	return e.Object == tErr.Object &&
   257  		e.Strategy == tErr.Strategy &&
   258  		e.Relationship == tErr.Relationship &&
   259  		e.Relation == tErr.Relation &&
   260  		e.RelationStrategy == tErr.RelationStrategy
   261  }
   262  

View as plain text