diff --git a/pkg/kube/object_patch/operation.go b/pkg/kube/object_patch/operation.go index b7e1f625..dd69192b 100644 --- a/pkg/kube/object_patch/operation.go +++ b/pkg/kube/object_patch/operation.go @@ -25,6 +25,7 @@ type OperationSpec struct { JSONPatch interface{} `json:"jsonPatch,omitempty" yaml:"jsonPatch,omitempty"` IgnoreMissingObject bool `json:"ignoreMissingObject" yaml:"ignoreMissingObject"` + IgnoreHookError bool `json:"ignoreHookError" yaml:"ignoreHookError"` } type OperationType string @@ -43,6 +44,25 @@ const ( JSONPatch OperationType = "JSONPatch" ) +// GetPatchStatusOperationsOnHookError returns list of Patch/Filter operations eligible for execution on Hook Error +func GetPatchStatusOperationsOnHookError(operations []Operation) []Operation { + patchStatusOperations := make([]Operation, 0) + for _, op := range operations { + switch operation := op.(type) { + case *filterOperation: + if operation.subresource == "/status" && operation.ignoreHookError { + patchStatusOperations = append(patchStatusOperations, operation) + } + case *patchOperation: + if operation.subresource == "/status" && operation.ignoreHookError { + patchStatusOperations = append(patchStatusOperations, operation) + } + } + } + + return patchStatusOperations +} + func ParseOperations(specBytes []byte) ([]Operation, error) { log.Debugf("parsing patcher operations:\n%s", specBytes) @@ -120,6 +140,7 @@ type patchOperation struct { patchType types.PatchType patch interface{} ignoreMissingObject bool + ignoreHookError bool } func (op *patchOperation) Description() string { @@ -137,6 +158,7 @@ type filterOperation struct { // Patch options. filterFunc func(*unstructured.Unstructured) (*unstructured.Unstructured, error) ignoreMissingObject bool + ignoreHookError bool } func (op *filterOperation) Description() string { @@ -175,18 +197,21 @@ func NewFromOperationSpec(spec OperationSpec) Operation { spec.ApiVersion, spec.Kind, spec.Namespace, spec.Name, WithSubresource(spec.Subresource), WithIgnoreMissingObject(spec.IgnoreMissingObject), + WithIgnoreHookError(spec.IgnoreHookError), ) case MergePatch: return NewMergePatchOperation(spec.MergePatch, spec.ApiVersion, spec.Kind, spec.Namespace, spec.Name, WithSubresource(spec.Subresource), WithIgnoreMissingObject(spec.IgnoreMissingObject), + WithIgnoreHookError(spec.IgnoreHookError), ) case JSONPatch: return NewJSONPatchOperation(spec.JSONPatch, spec.ApiVersion, spec.Kind, spec.Namespace, spec.Name, WithSubresource(spec.Subresource), WithIgnoreMissingObject(spec.IgnoreMissingObject), + WithIgnoreHookError(spec.IgnoreHookError), ) } diff --git a/pkg/kube/object_patch/options.go b/pkg/kube/object_patch/options.go index fed6d6d6..c6900ee9 100644 --- a/pkg/kube/object_patch/options.go +++ b/pkg/kube/object_patch/options.go @@ -45,10 +45,31 @@ func (s *subresourceHolder) applyToFilter(operation *filterOperation) { operation.subresource = s.subresource } +type ignoreHookError struct { + ignoreError bool +} + +// IgnoreHookError allows applying patches for a Status subresource even if the hook fails +func IgnoreHookError() *ignoreHookError { + return WithIgnoreHookError(true) +} + +func WithIgnoreHookError(ignoreError bool) *ignoreHookError { + return &ignoreHookError{ignoreError: ignoreError} +} + type ignoreMissingObject struct { ignore bool } +func (i *ignoreHookError) applyToPatch(operation *patchOperation) { + operation.ignoreHookError = i.ignoreError +} + +func (i *ignoreHookError) applyToFilter(operation *filterOperation) { + operation.ignoreHookError = i.ignoreError +} + // IgnoreMissingObject do not return error if object exists for Patch and Filter operations. func IgnoreMissingObject() *ignoreMissingObject { return WithIgnoreMissingObject(true) diff --git a/pkg/kube/object_patch/patch.go b/pkg/kube/object_patch/patch.go index f87a485c..2a0410e6 100644 --- a/pkg/kube/object_patch/patch.go +++ b/pkg/kube/object_patch/patch.go @@ -152,6 +152,7 @@ func (o *ObjectPatcher) executeCreateOperation(op *createOperation) error { // Other options: // - WithSubresource — a subresource argument for Patch or Update API call. // - IgnoreMissingObject — do not return error if the specified object is missing. +// - IgnoreHookError — allows applying patches for a Status subresource even if the hook fails func (o *ObjectPatcher) executePatchOperation(op *patchOperation) error { if op.patchType == types.MergePatchType { log.Debug("Started MergePatchObject") @@ -190,6 +191,11 @@ func (o *ObjectPatcher) executePatchOperation(op *patchOperation) error { // executeFilterOperation retrieves a specified object, modified it with // filterFunc and calls update. + +// Other options: +// - WithSubresource — a subresource argument for Patch or Update API call. +// - IgnoreMissingObject — do not return error if the specified object is missing. +// - IgnoreHookError — allows applying patches for a Status subresource even if the hook fails func (o *ObjectPatcher) executeFilterOperation(op *filterOperation) error { var err error diff --git a/pkg/kube/object_patch/patch_collector.go b/pkg/kube/object_patch/patch_collector.go index 87dba476..0d2c365e 100644 --- a/pkg/kube/object_patch/patch_collector.go +++ b/pkg/kube/object_patch/patch_collector.go @@ -43,6 +43,7 @@ func (dop *PatchCollector) Delete(apiVersion, kind, namespace, name string, opti // Options: // - WithSubresource — a subresource argument for Patch call. // - IgnoreMissingObject — do not return error if the specified object is missing. +// - IgnoreHookError — allows applying patches for a Status subresource even if the hook fails func (dop *PatchCollector) MergePatch(mergePatch interface{}, apiVersion, kind, namespace, name string, options ...PatchOption) { dop.add(NewMergePatchOperation(mergePatch, apiVersion, kind, namespace, name, options...)) } @@ -52,6 +53,7 @@ func (dop *PatchCollector) MergePatch(mergePatch interface{}, apiVersion, kind, // Options: // - WithSubresource — a subresource argument for Patch call. // - IgnoreMissingObject — do not return error if the specified object is missing. +// - IgnoreHookError — allows applying patches for a Status subresource even if the hook fails func (dop *PatchCollector) JSONPatch(jsonPatch interface{}, apiVersion, kind, namespace, name string, options ...PatchOption) { dop.add(NewJSONPatchOperation(jsonPatch, apiVersion, kind, namespace, name, options...)) } @@ -62,6 +64,7 @@ func (dop *PatchCollector) JSONPatch(jsonPatch interface{}, apiVersion, kind, na // Options: // - WithSubresource — a subresource argument for Patch call. // - IgnoreMissingObject — do not return error if the specified object is missing. +// - IgnoreHookError — allows applying patches for a Status subresource even if the hook fails // // Note: do not modify and return argument in filterFunc, // use FromUnstructured to instantiate a concrete type or modify after DeepCopy. diff --git a/pkg/kube/object_patch/validation.go b/pkg/kube/object_patch/validation.go index 1a5233dd..e8055a67 100644 --- a/pkg/kube/object_patch/validation.go +++ b/pkg/kube/object_patch/validation.go @@ -56,6 +56,8 @@ definitions: type: string ignoreMissingObject: type: boolean + ignoreHookError: + type: boolean type: object additionalProperties: false @@ -71,6 +73,7 @@ properties: jqFilter: {} mergePatch: {} ignoreMissingObject: {} + ignoreHookError: {} oneOf: - allOf: diff --git a/pkg/shell-operator/operator.go b/pkg/shell-operator/operator.go index 30a86f2c..1b9b3e90 100644 --- a/pkg/shell-operator/operator.go +++ b/pkg/shell-operator/operator.go @@ -608,6 +608,17 @@ func (op *ShellOperator) HandleRunHook(t task.Task, taskHook *hook.Hook, hookMet result, err := taskHook.Run(hookMeta.BindingType, hookMeta.BindingContext, hookLogLabels) if err != nil { + if result != nil && len(result.KubernetesPatchBytes) > 0 { + operations, patchStatusErr := object_patch.ParseOperations(result.KubernetesPatchBytes) + if patchStatusErr != nil { + return fmt.Errorf("%s: couldn't patch status: %s", err, patchStatusErr) + } + + patchStatusErr = op.ObjectPatcher.ExecuteOperations(object_patch.GetPatchStatusOperationsOnHookError(operations)) + if patchStatusErr != nil { + return fmt.Errorf("%s: couldn't patch status: %s", err, patchStatusErr) + } + } return err }