sigs.k8s.io/cluster-api-provider-azure@v1.14.3/azure/errors.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package azure
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"net/http"
    24  	"time"
    25  
    26  	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
    27  	"github.com/Azure/go-autorest/autorest"
    28  	infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
    29  )
    30  
    31  // ResourceNotFound parses an error to check if its status code is Not Found (404).
    32  func ResourceNotFound(err error) bool {
    33  	return hasStatusCode(err, http.StatusNotFound)
    34  }
    35  
    36  // hasStatusCode returns true if an error is a DetailedError or ResponseError with a matching status code.
    37  func hasStatusCode(err error, statusCode int) bool {
    38  	derr := autorest.DetailedError{} // azure-sdk-for-go v1
    39  	if errors.As(err, &derr) {
    40  		return derr.StatusCode == statusCode
    41  	}
    42  	var rerr *azcore.ResponseError // azure-sdk-for-go v2
    43  	return errors.As(err, &rerr) && rerr.StatusCode == statusCode
    44  }
    45  
    46  // VMDeletedError is returned when a virtual machine is deleted outside of capz.
    47  type VMDeletedError struct {
    48  	ProviderID string
    49  }
    50  
    51  // Error returns the error string.
    52  func (vde VMDeletedError) Error() string {
    53  	return fmt.Sprintf("VM with provider id %q has been deleted", vde.ProviderID)
    54  }
    55  
    56  // ReconcileError represents an error that is not automatically recoverable
    57  // errorType indicates what type of action is required to recover. It can take two values:
    58  // 1. `Transient` - Can be recovered through manual intervention, will be requeued after.
    59  // 2. `Terminal` - Cannot be recovered, will not be requeued.
    60  type ReconcileError struct {
    61  	error
    62  	errorType    ReconcileErrorType
    63  	requestAfter time.Duration
    64  }
    65  
    66  // ReconcileErrorType represents the type of a ReconcileError.
    67  type ReconcileErrorType string
    68  
    69  const (
    70  	// TransientErrorType can be recovered, will be requeued after a configured time interval.
    71  	TransientErrorType ReconcileErrorType = "Transient"
    72  	// TerminalErrorType cannot be recovered, will not be requeued.
    73  	TerminalErrorType ReconcileErrorType = "Terminal"
    74  )
    75  
    76  // Error returns the error message for a ReconcileError.
    77  func (t ReconcileError) Error() string {
    78  	var errStr string
    79  	if t.error != nil {
    80  		errStr = t.error.Error()
    81  	}
    82  	switch t.errorType {
    83  	case TransientErrorType:
    84  		return fmt.Sprintf("%s. Object will be requeued after %s", errStr, t.requestAfter.String())
    85  	case TerminalErrorType:
    86  		return fmt.Sprintf("reconcile error that cannot be recovered occurred: %s. Object will not be requeued", errStr)
    87  	default:
    88  		return fmt.Sprintf("reconcile error occurred with unknown recovery type. The actual error is: %s", errStr)
    89  	}
    90  }
    91  
    92  // IsTransient returns if the ReconcileError is recoverable.
    93  func (t ReconcileError) IsTransient() bool {
    94  	return t.errorType == TransientErrorType
    95  }
    96  
    97  // IsTerminal returns if the ReconcileError is recoverable.
    98  func (t ReconcileError) IsTerminal() bool {
    99  	return t.errorType == TerminalErrorType
   100  }
   101  
   102  // Is returns true if the target is a ReconcileError.
   103  func (t ReconcileError) Is(target error) bool {
   104  	return errors.As(target, &ReconcileError{})
   105  }
   106  
   107  // RequeueAfter returns requestAfter value.
   108  func (t ReconcileError) RequeueAfter() time.Duration {
   109  	return t.requestAfter
   110  }
   111  
   112  // WithTransientError wraps the error in a ReconcileError with errorType as `Transient`.
   113  func WithTransientError(err error, requeueAfter time.Duration) ReconcileError {
   114  	return ReconcileError{error: err, errorType: TransientErrorType, requestAfter: requeueAfter}
   115  }
   116  
   117  // WithTerminalError wraps the error in a ReconcileError with errorType as `Terminal`.
   118  func WithTerminalError(err error) ReconcileError {
   119  	return ReconcileError{error: err, errorType: TerminalErrorType}
   120  }
   121  
   122  // OperationNotDoneError is used to represent a long-running operation that is not yet complete.
   123  type OperationNotDoneError struct {
   124  	Future *infrav1.Future
   125  }
   126  
   127  // NewOperationNotDoneError returns a new OperationNotDoneError wrapping a Future.
   128  func NewOperationNotDoneError(future *infrav1.Future) OperationNotDoneError {
   129  	return OperationNotDoneError{
   130  		Future: future,
   131  	}
   132  }
   133  
   134  // Error returns the error represented as a string.
   135  func (onde OperationNotDoneError) Error() string {
   136  	return fmt.Sprintf("operation type %s on Azure resource %s/%s is not done", onde.Future.Type, onde.Future.ResourceGroup, onde.Future.Name)
   137  }
   138  
   139  // Is returns true if the target is an OperationNotDoneError.
   140  func (onde OperationNotDoneError) Is(target error) bool {
   141  	return IsOperationNotDoneError(target)
   142  }
   143  
   144  // IsOperationNotDoneError returns true if the target is an OperationNotDoneError.
   145  func IsOperationNotDoneError(target error) bool {
   146  	reconcileErr := &ReconcileError{}
   147  	if errors.As(target, reconcileErr) {
   148  		return IsOperationNotDoneError(reconcileErr.error)
   149  	}
   150  	return errors.As(target, &OperationNotDoneError{})
   151  }
   152  
   153  // IsContextDeadlineExceededOrCanceledError checks if it's a context deadline
   154  // exceeded or canceled error.
   155  func IsContextDeadlineExceededOrCanceledError(err error) bool {
   156  	if err == nil {
   157  		return false
   158  	}
   159  	return errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled)
   160  }