Repository: metal3-io/baremetal-operator
Pull request #1566 (2024-02-28 12:48:55)

🌱 Use real Context and make provisioners context-aware
There is no compelling reason to use TODO/Background: we have context available in reconcilers.

GopherCloud 2.0.0-beta2 will require context in its calls, so also pass the context to the provisioner.


Patch:
diff --git a/controllers/metal3.io/baremetalhost_controller.go b/controllers/metal3.io/baremetalhost_controller.go
index e22726a2..7b9d8346 100644
--- a/controllers/metal3.io/baremetalhost_controller.go
+++ b/controllers/metal3.io/baremetalhost_controller.go
@@ -70,6 +70,7 @@ type BareMetalHostReconciler struct {
 // Instead of passing a zillion arguments to the action of a phase,
 // hold them in a context.
 type reconcileInfo struct {
+	ctx               context.Context
 	log               logr.Logger
 	host              *metal3api.BareMetalHost
 	request           ctrl.Request
@@ -141,7 +142,7 @@ func (r *BareMetalHostReconciler) Reconcile(ctx context.Context, request ctrl.Re
 	}
 
 	// Consume hardwaredetails from annotation if present
-	hwdUpdated, err := r.updateHardwareDetails(request, host)
+	hwdUpdated, err := r.updateHardwareDetails(ctx, request, host)
 	if err != nil {
 		return ctrl.Result{}, errors.Wrap(err, "Could not update Hardware Details")
 	} else if hwdUpdated {
@@ -179,14 +180,14 @@ func (r *BareMetalHostReconciler) Reconcile(ctx context.Context, request ctrl.Re
 	case metal3api.StateNone, metal3api.StateUnmanaged:
 		bmcCreds = &bmc.Credentials{}
 	default:
-		bmcCreds, bmcCredsSecret, err = r.buildAndValidateBMCCredentials(request, host)
+		bmcCreds, bmcCredsSecret, err = r.buildAndValidateBMCCredentials(ctx, request, host)
 		if err != nil || bmcCreds == nil {
 			if !host.DeletionTimestamp.IsZero() {
 				// If we are in the process of deletion, try with empty credentials
 				bmcCreds = &bmc.Credentials{}
 				bmcCredsSecret = &corev1.Secret{}
 			} else {
-				return r.credentialsErrorResult(err, request, host)
+				return r.credentialsErrorResult(ctx, err, request, host)
 			}
 		} else {
 			haveCreds = true
@@ -195,13 +196,14 @@ func (r *BareMetalHostReconciler) Reconcile(ctx context.Context, request ctrl.Re
 
 	initialState := host.Status.Provisioning.State
 	info := &reconcileInfo{
+		ctx:            ctx,
 		log:            reqLogger.WithValues("provisioningState", initialState),
 		host:           host,
 		request:        request,
 		bmcCredsSecret: bmcCredsSecret,
 	}
 
-	prov, err := r.ProvisionerFactory.NewProvisioner(provisioner.BuildHostData(*host, *bmcCreds), info.publishEvent)
+	prov, err := r.ProvisionerFactory.NewProvisioner(ctx, provisioner.BuildHostData(*host, *bmcCreds), info.publishEvent)
 	if err != nil {
 		return ctrl.Result{}, errors.Wrap(err, "failed to create provisioner")
 	}
@@ -234,7 +236,7 @@ func (r *BareMetalHostReconciler) Reconcile(ctx context.Context, request ctrl.Re
 		info.log.Info("saving host status",
 			"operational status", host.OperationalStatus(),
 			"provisioning state", host.Status.Provisioning.State)
-		err = r.saveHostStatus(host)
+		err = r.saveHostStatus(ctx, host)
 		if err != nil {
 			return ctrl.Result{}, errors.Wrap(err,
 				fmt.Sprintf("failed to save host status after %q", initialState))
@@ -246,7 +248,7 @@ func (r *BareMetalHostReconciler) Reconcile(ctx context.Context, request ctrl.Re
 	}
 
 	for _, e := range info.events {
-		r.publishEvent(request, e)
+		r.publishEvent(ctx, request, e)
 	}
 
 	logResult(info, result)
@@ -256,7 +258,7 @@ func (r *BareMetalHostReconciler) Reconcile(ctx context.Context, request ctrl.Re
 
 // Consume inspect.metal3.io/hardwaredetails when either
 // inspect.metal3.io=disabled or there are no existing HardwareDetails.
-func (r *BareMetalHostReconciler) updateHardwareDetails(request ctrl.Request, host *metal3api.BareMetalHost) (bool, error) {
+func (r *BareMetalHostReconciler) updateHardwareDetails(ctx context.Context, request ctrl.Request, host *metal3api.BareMetalHost) (bool, error) {
 	updated := false
 	if host.Status.HardwareDetails == nil || inspectionDisabled(host) {
 		objHardwareDetails, err := r.getHardwareDetailsFromAnnotation(host)
@@ -265,11 +267,11 @@ func (r *BareMetalHostReconciler) updateHardwareDetails(request ctrl.Request, ho
 		}
 		if objHardwareDetails != nil {
 			host.Status.HardwareDetails = objHardwareDetails
-			err = r.saveHostStatus(host)
+			err = r.saveHostStatus(ctx, host)
 			if err != nil {
 				return updated, errors.Wrap(err, "Could not update hardwaredetails from annotation")
 			}
-			r.publishEvent(request, host.NewEvent("UpdateHardwareDetails", "Set HardwareDetails from annotation"))
+			r.publishEvent(ctx, request, host.NewEvent("UpdateHardwareDetails", "Set HardwareDetails from annotation"))
 			updated = true
 		}
 	}
@@ -278,13 +280,13 @@ func (r *BareMetalHostReconciler) updateHardwareDetails(request ctrl.Request, ho
 	annotations := host.GetAnnotations()
 	if _, present := annotations[metal3api.HardwareDetailsAnnotation]; present {
 		delete(host.Annotations, metal3api.HardwareDetailsAnnotation)
-		err := r.Update(context.TODO(), host)
+		err := r.Update(ctx, host)
 		if err != nil {
 			return updated, errors.Wrap(err, "Could not update removing hardwaredetails annotation")
 		}
 		// In the case where the value was not just consumed, generate an event
 		if !updated {
-			r.publishEvent(request, host.NewEvent("RemoveAnnotation", "HardwareDetails annotation ignored, status already set and inspection is not disabled"))
+			r.publishEvent(ctx, request, host.NewEvent("RemoveAnnotation", "HardwareDetails annotation ignored, status already set and inspection is not disabled"))
 		}
 	}
 	return updated, nil
@@ -338,18 +340,18 @@ func recordActionDelayed(info *reconcileInfo, state metal3api.ProvisioningState)
 	return actionDelayed{}
 }
 
-func (r *BareMetalHostReconciler) credentialsErrorResult(err error, request ctrl.Request, host *metal3api.BareMetalHost) (ctrl.Result, error) {
+func (r *BareMetalHostReconciler) credentialsErrorResult(ctx context.Context, err error, request ctrl.Request, host *metal3api.BareMetalHost) (ctrl.Result, error) {
 	switch err.(type) {
 	// In the event a credential secret is defined, but we cannot find it
 	// we requeue the host as we will not know if they create the secret
 	// at some point in the future.
 	case *ResolveBMCSecretRefError:
 		credentialsMissing.Inc()
-		saveErr := r.setErrorCondition(request, host, metal3api.RegistrationError, err.Error())
+		saveErr := r.setErrorCondition(ctx, request, host, metal3api.RegistrationError, err.Error())
 		if saveErr != nil {
 			return ctrl.Result{Requeue: true}, saveErr
 		}
-		r.publishEvent(request, host.NewEvent("BMCCredentialError", err.Error()))
+		r.publishEvent(ctx, request, host.NewEvent("BMCCredentialError", err.Error()))
 
 		return ctrl.Result{Requeue: true, RequeueAfter: hostErrorRetryDelay}, nil
 	// If a managed Host is missing a BMC address or secret, or
@@ -361,13 +363,13 @@ func (r *BareMetalHostReconciler) credentialsErrorResult(err error, request ctrl
 	case *EmptyBMCAddressError, *EmptyBMCSecretError,
 		*bmc.CredentialsValidationError, *bmc.UnknownBMCTypeError:
 		credentialsInvalid.Inc()
-		saveErr := r.setErrorCondition(request, host, metal3api.RegistrationError, err.Error())
+		saveErr := r.setErrorCondition(ctx, request, host, metal3api.RegistrationError, err.Error())
 		if saveErr != nil {
 			return ctrl.Result{Requeue: true}, saveErr
 		}
 		// Only publish the event if we do not have an error
 		// after saving so that we only publish one time.
-		r.publishEvent(request, host.NewEvent("BMCCredentialError", err.Error()))
+		r.publishEvent(ctx, request, host.NewEvent("BMCCredentialError", err.Error()))
 		return ctrl.Result{}, nil
 	default:
 		unhandledCredentialsError.Inc()
@@ -523,7 +525,7 @@ func (r *BareMetalHostReconciler) actionDeleting(prov provisioner.Provisioner, i
 	}
 
 	// Remove finalizer to allow deletion
-	secretManager := secretutils.NewSecretManager(info.log, r.Client, r.APIReader)
+	secretManager := secretutils.NewSecretManager(info.ctx, info.log, r.Client, r.APIReader)
 
 	err = secretManager.ReleaseSecret(info.bmcCredsSecret)
 	if err != nil {
@@ -534,7 +536,7 @@ func (r *BareMetalHostReconciler) actionDeleting(prov provisioner.Provisioner, i
 		info.host.Finalizers, metal3api.BareMetalHostFinalizer)
 	info.log.Info("cleanup is complete, removed finalizer",
 		"remaining", info.host.Finalizers)
-	if err := r.Update(context.Background(), info.host); err != nil {
+	if err := r.Update(info.ctx, info.host); err != nil {
 		return actionError{errors.Wrap(err, "failed to remove finalizer")}
 	}
 
@@ -640,7 +642,7 @@ func (r *BareMetalHostReconciler) preprovImageAvailable(info *reconcileInfo, ima
 			Name:      image.Spec.NetworkDataName,
 			Namespace: image.ObjectMeta.Namespace,
 		}
-		secretManager := r.secretManager(info.log)
+		secretManager := r.secretManager(info.ctx, info.log)
 		networkData, err := secretManager.AcquireSecret(secretKey, info.host, false)
 		if err != nil {
 			return false, err
@@ -704,7 +706,7 @@ func (r *BareMetalHostReconciler) getPreprovImage(info *reconcileInfo, formats [
 		Name:      info.host.Name,
 		Namespace: info.host.Namespace,
 	}
-	err := r.Get(context.TODO(), key, &preprovImage)
+	err := r.Get(info.ctx, key, &preprovImage)
 	if k8serrors.IsNotFound(err) {
 		info.log.Info("creating new PreprovisioningImage")
 		preprovImage = metal3api.PreprovisioningImage{
@@ -716,7 +718,7 @@ func (r *BareMetalHostReconciler) getPreprovImage(info *reconcileInfo, formats [
 			Spec: expectedSpec,
 		}
 		controllerutil.SetControllerReference(info.host, &preprovImage, r.Scheme())
-		err = r.Create(context.TODO(), &preprovImage)
+		err = r.Create(info.ctx, &preprovImage)
 		return nil, err
 	}
 	if err != nil {
@@ -740,7 +742,7 @@ func (r *BareMetalHostReconciler) getPreprovImage(info *reconcileInfo, formats [
 	}
 	if needsUpdate {
 		info.log.Info("updating PreprovisioningImage")
-		err = r.Update(context.TODO(), &preprovImage)
+		err = r.Update(info.ctx, &preprovImage)
 		return nil, err
 	}
 
@@ -801,7 +803,7 @@ func (r *BareMetalHostReconciler) registerHost(prov provisioner.Provisioner, inf
 	hostConf := &hostConfigData{
 		host:          info.host,
 		log:           info.log.WithName("host_config_data"),
-		secretManager: r.secretManager(info.log),
+		secretManager: r.secretManager(info.ctx, info.log),
 	}
 	preprovisioningNetworkData, err := hostConf.PreprovisioningNetworkData()
 	if err != nil {
@@ -970,7 +972,7 @@ func (r *BareMetalHostReconciler) actionInspecting(prov provisioner.Provisioner,
 		}
 
 		if dirty {
-			if err := r.Update(context.TODO(), info.host); err != nil {
+			if err := r.Update(info.ctx, info.host); err != nil {
 				return actionError{errors.Wrap(err, "failed to update the host after inspection start")}
 			}
 			return actionContinue{}
@@ -1015,17 +1017,17 @@ func (r *BareMetalHostReconciler) actionInspecting(prov provisioner.Provisioner,
 		},
 	}
 
-	err = r.Client.Get(context.Background(), hardwareDataKey, hardwareData)
+	err = r.Client.Get(info.ctx, hardwareDataKey, hardwareData)
 	if err == nil || !k8serrors.IsNotFound(err) {
 		// hardwareData found and we reached here due to request for another inspection.
 		// Delete it before re-creating.
 		if controllerutil.ContainsFinalizer(hardwareData, hardwareDataFinalizer) {
 			controllerutil.RemoveFinalizer(hardwareData, hardwareDataFinalizer)
 		}
-		if err := r.Update(context.Background(), hardwareData); err != nil {
+		if err := r.Update(info.ctx, hardwareData); err != nil {
 			return actionError{errors.Wrap(err, "failed to remove hardwareData finalizer")}
 		}
-		if err := r.Client.Delete(context.Background(), hd); err != nil {
+		if err := r.Client.Delete(info.ctx, hd); err != nil {
 			return actionError{errors.Wrap(err, "failed to delete hardwareData")}
 		}
 	}
@@ -1036,7 +1038,7 @@ func (r *BareMetalHostReconciler) actionInspecting(prov provisioner.Provisioner,
 	}
 
 	// either hardwareData was deleted above, or not found. We need to re-create it
-	if err := r.Client.Create(context.Background(), hd); err != nil {
+	if err := r.Client.Create(info.ctx, hd); err != nil {
 		return actionError{errors.Wrap(err, "failed to create hardwareData")}
 	}
 	info.log.Info(fmt.Sprintf("Created hardwareData %q in %q namespace\n", hd.Name, hd.Namespace))
@@ -1164,7 +1166,7 @@ func (r *BareMetalHostReconciler) actionProvisioning(prov provisioner.Provisione
 	hostConf := &hostConfigData{
 		host:          info.host,
 		log:           info.log.WithName("host_config_data"),
-		secretManager: r.secretManager(info.log),
+		secretManager: r.secretManager(info.ctx, info.log),
 	}
 	info.log.Info("provisioning")
 
@@ -1201,7 +1203,7 @@ func (r *BareMetalHostReconciler) actionProvisioning(prov provisioner.Provisione
 	}
 
 	if clearRebootAnnotations(info.host) {
-		if err := r.Update(context.TODO(), info.host); err != nil {
+		if err := r.Update(info.ctx, info.host); err != nil {
 			return actionError{errors.Wrap(err, "failed to remove reboot annotations from host")}
 		}
 		return actionContinue{}
@@ -1288,7 +1290,7 @@ func (r *BareMetalHostReconciler) actionDeprovisioning(prov provisioner.Provisio
 	}
 
 	if clearRebootAnnotations(info.host) {
-		if err = r.Update(context.TODO(), info.host); err != nil {
+		if err = r.Update(info.ctx, info.host); err != nil {
 			return actionError{errors.Wrap(err, "failed to remove reboot annotations from host")}
 		}
 		return actionContinue{}
@@ -1326,7 +1328,7 @@ func (r *BareMetalHostReconciler) manageHostPower(prov provisioner.Provisioner,
 		if _, suffixlessAnnotationExists := info.host.Annotations[metal3api.RebootAnnotationPrefix]; suffixlessAnnotationExists {
 			delete(info.host.Annotations, metal3api.RebootAnnotationPrefix)
 
-			if err = r.Update(context.TODO(), info.host); err != nil {
+			if err = r.Update(info.ctx, info.host); err != nil {
 				return actionError{errors.Wrap(err, "failed to remove reboot annotation from host")}
 			}
 
@@ -1498,7 +1500,7 @@ func saveHostProvisioningSettings(host *metal3api.BareMetalHost, info *reconcile
 func (r *BareMetalHostReconciler) createHostFirmwareSettings(info *reconcileInfo) error {
 	// Check if HostFirmwareSettings already exists
 	hfs := &metal3api.HostFirmwareSettings{}
-	if err := r.Get(context.TODO(), info.request.NamespacedName, hfs); err != nil {
+	if err := r.Get(info.ctx, info.request.NamespacedName, hfs); err != nil {
 		if k8serrors.IsNotFound(err) {
 			// A resource doesn't exist, create one
 			hfs.ObjectMeta = metav1.ObjectMeta{
@@ -1511,7 +1513,7 @@ func (r *BareMetalHostReconciler) createHostFirmwareSettings(info *reconcileInfo
 			if err = controllerutil.SetControllerReference(info.host, hfs, r.Scheme()); err != nil {
 				return errors.Wrap(err, "could not set bmh as controller")
 			}
-			if err = r.Create(context.TODO(), hfs); err != nil {
+			if err = r.Create(info.ctx, hfs); err != nil {
 				return errors.Wrap(err, "failure creating hostFirmwareSettings resource")
 			}
 
@@ -1528,7 +1530,7 @@ func (r *BareMetalHostReconciler) createHostFirmwareSettings(info *reconcileInfo
 // Get the stored firmware settings if there are valid changes.
 func (r *BareMetalHostReconciler) getHostFirmwareSettings(info *reconcileInfo) (dirty bool, hfs *metal3api.HostFirmwareSettings, err error) {
 	hfs = &metal3api.HostFirmwareSettings{}
-	if err = r.Get(context.TODO(), info.request.NamespacedName, hfs); err != nil {
+	if err = r.Get(info.ctx, info.request.NamespacedName, hfs); err != nil {
 		if !k8serrors.IsNotFound(err) {
 			// Error reading the object
 			return false, nil, errors.Wrap(err, "could not load host firmware settings")
@@ -1559,11 +1561,11 @@ func (r *BareMetalHostReconciler) getHostFirmwareSettings(info *reconcileInfo) (
 	return false, nil, nil
 }
 
-func (r *BareMetalHostReconciler) saveHostStatus(host *metal3api.BareMetalHost) error {
+func (r *BareMetalHostReconciler) saveHostStatus(ctx context.Context, host *metal3api.BareMetalHost) error {
 	t := metav1.Now()
 	host.Status.LastUpdated = &t
 
-	return r.Status().Update(context.TODO(), host)
+	return r.Status().Update(ctx, host)
 }
 
 func unmarshalStatusAnnotation(content []byte) (*metal3api.BareMetalHostStatus, error) {
@@ -1603,7 +1605,7 @@ func (r *BareMetalHostReconciler) getHardwareDetailsFromAnnotation(host *metal3a
 	return objHardwareDetails, nil
 }
 
-func (r *BareMetalHostReconciler) setErrorCondition(request ctrl.Request, host *metal3api.BareMetalHost, errType metal3api.ErrorType, message string) (err error) {
+func (r *BareMetalHostReconciler) setErrorCondition(ctx context.Context, request ctrl.Request, host *metal3api.BareMetalHost, errType metal3api.ErrorType, message string) (err error) {
 	reqLogger := r.Log.WithValues("baremetalhost", request.NamespacedName)
 
 	setErrorMessage(host, errType, message)
@@ -1612,7 +1614,7 @@ func (r *BareMetalHostReconciler) setErrorCondition(request ctrl.Request, host *
 		"adding error message",
 		"message", message,
 	)
-	err = r.saveHostStatus(host)
+	err = r.saveHostStatus(ctx, host)
 	if err != nil {
 		err = errors.Wrap(err, "failed to update error message")
 	}
@@ -1620,18 +1622,18 @@ func (r *BareMetalHostReconciler) setErrorCondition(request ctrl.Request, host *
 	return
 }
 
-func (r *BareMetalHostReconciler) secretManager(log logr.Logger) secretutils.SecretManager {
-	return secretutils.NewSecretManager(log, r.Client, r.APIReader)
+func (r *BareMetalHostReconciler) secretManager(ctx context.Context, log logr.Logger) secretutils.SecretManager {
+	return secretutils.NewSecretManager(ctx, log, r.Client, r.APIReader)
 }
 
 // Retrieve the secret containing the credentials for talking to the BMC.
-func (r *BareMetalHostReconciler) getBMCSecretAndSetOwner(request ctrl.Request, host *metal3api.BareMetalHost) (*corev1.Secret, error) {
+func (r *BareMetalHostReconciler) getBMCSecretAndSetOwner(ctx context.Context, request ctrl.Request, host *metal3api.BareMetalHost) (*corev1.Secret, error) {
 	if host.Spec.BMC.CredentialsName == "" {
 		return nil, &EmptyBMCSecretError{message: "The BMC secret reference is empty"}
 	}
 
 	reqLogger := r.Log.WithValues("baremetalhost", request.NamespacedName)
-	secretManager := r.secretManager(reqLogger)
+	secretManager := r.secretManager(ctx, reqLogger)
 
 	bmcCredsSecret, err := secretManager.AcquireSecret(host.CredentialsKey(), host, host.Status.Provisioning.State != metal3api.StateDeleting)
 	if err != nil {
@@ -1661,9 +1663,9 @@ func credentialsFromSecret(bmcCredsSecret *corev1.Secret) *bmc.Credentials {
 // Make sure the credentials for the management controller look
 // right and manufacture bmc.Credentials.  This does not actually try
 // to use the credentials.
-func (r *BareMetalHostReconciler) buildAndValidateBMCCredentials(request ctrl.Request, host *metal3api.BareMetalHost) (bmcCreds *bmc.Credentials, bmcCredsSecret *corev1.Secret, err error) {
+func (r *BareMetalHostReconciler) buildAndValidateBMCCredentials(ctx context.Context, request ctrl.Request, host *metal3api.BareMetalHost) (bmcCreds *bmc.Credentials, bmcCredsSecret *corev1.Secret, err error) {
 	// Retrieve the BMC secret from Kubernetes for this host
-	bmcCredsSecret, err = r.getBMCSecretAndSetOwner(request, host)
+	bmcCredsSecret, err = r.getBMCSecretAndSetOwner(ctx, request, host)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -1685,10 +1687,10 @@ func (r *BareMetalHostReconciler) buildAndValidateBMCCredentials(request ctrl.Re
 	return bmcCreds, bmcCredsSecret, nil
 }
 
-func (r *BareMetalHostReconciler) publishEvent(request ctrl.Request, event corev1.Event) {
+func (r *BareMetalHostReconciler) publishEvent(ctx context.Context, request ctrl.Request, event corev1.Event) {
 	reqLogger := r.Log.WithValues("baremetalhost", request.NamespacedName)
 	reqLogger.Info("publishing event", "reason", event.Reason, "message", event.Message)
-	err := r.Create(context.TODO(), &event)
+	err := r.Create(ctx, &event)
 	if err != nil {
 		reqLogger.Info("failed to record event, ignoring",
 			"reason", event.Reason, "message", event.Message, "error", err)
@@ -1766,7 +1768,7 @@ func (r *BareMetalHostReconciler) reconciletHostData(ctx context.Context, host *
 		if controllerutil.ContainsFinalizer(hardwareData, hardwareDataFinalizer) {
 			controllerutil.RemoveFinalizer(hardwareData, hardwareDataFinalizer)
 			reqLogger.Info("removing finalizer from hardwareData")
-			if err := r.Update(context.Background(), hardwareData); err != nil {
+			if err := r.Update(ctx, hardwareData); err != nil {
 				return ctrl.Result{}, errors.Wrap(err, "failed to remove hardwareData finalizer")
 			}
 		}
@@ -1818,7 +1820,7 @@ func (r *BareMetalHostReconciler) reconciletHostData(ctx context.Context, host *
 		newArch := hardwareData.Spec.HardwareDetails.CPU.Arch
 		reqLogger.Info("updating architecture", "Architecture", newArch)
 		host.Spec.Architecture = newArch
-		if err := r.Client.Update(context.Background(), host); err != nil {
+		if err := r.Client.Update(ctx, host); err != nil {
 			return ctrl.Result{}, errors.Wrap(err, "failed to update architecture")
 		}
 		return ctrl.Result{Requeue: true}, nil
diff --git a/controllers/metal3.io/bmceventsubscription_controller.go b/controllers/metal3.io/bmceventsubscription_controller.go
index 89311557..919a0444 100644
--- a/controllers/metal3.io/bmceventsubscription_controller.go
+++ b/controllers/metal3.io/bmceventsubscription_controller.go
@@ -100,7 +100,7 @@ func (r *BMCEventSubscriptionReconciler) Reconcile(ctx context.Context, request
 		return ctrl.Result{}, errors.Wrap(err, "failed add finalizer")
 	}
 
-	prov, ready, err := r.getProvisioner(request, host)
+	prov, ready, err := r.getProvisioner(ctx, request, host)
 
 	if err != nil {
 		return ctrl.Result{}, errors.Wrap(err, "failed to create provisioner")
@@ -172,7 +172,7 @@ func (r *BMCEventSubscriptionReconciler) createSubscription(ctx context.Context,
 		return nil
 	}
 
-	headers, err := r.getHTTPHeaders(*subscription)
+	headers, err := r.getHTTPHeaders(ctx, *subscription)
 
 	if err != nil {
 		reqLogger.Error(err, "failed to get http headers")
@@ -191,7 +191,7 @@ func (r *BMCEventSubscriptionReconciler) createSubscription(ctx context.Context,
 	return r.Status().Update(ctx, subscription)
 }
 
-func (r *BMCEventSubscriptionReconciler) deleteSubscription(_ context.Context, prov provisioner.Provisioner, subscription *metal3api.BMCEventSubscription) error {
+func (r *BMCEventSubscriptionReconciler) deleteSubscription(ctx context.Context, prov provisioner.Provisioner, subscription *metal3api.BMCEventSubscription) error {
 	reqLogger := r.Log.WithName("bmceventsubscription")
 	reqLogger.Info("deleting subscription")
 
@@ -205,7 +205,7 @@ func (r *BMCEventSubscriptionReconciler) deleteSubscription(_ context.Context, p
 			subscription.Finalizers, metal3api.BMCEventSubscriptionFinalizer)
 		reqLogger.Info("cleanup is complete, removed finalizer",
 			"remaining", subscription.Finalizers)
-		if err := r.Update(context.Background(), subscription); err != nil {
+		if err := r.Update(ctx, subscription); err != nil {
 			return err
 		}
 	}
@@ -213,10 +213,10 @@ func (r *BMCEventSubscriptionReconciler) deleteSubscription(_ context.Context, p
 	return nil
 }
 
-func (r *BMCEventSubscriptionReconciler) getProvisioner(request ctrl.Request, host *metal3api.BareMetalHost) (prov provisioner.Provisioner, ready bool, err error) {
+func (r *BMCEventSubscriptionReconciler) getProvisioner(ctx context.Context, request ctrl.Request, host *metal3api.BareMetalHost) (prov provisioner.Provisioner, ready bool, err error) {
 	reqLogger := r.Log.WithValues("bmceventsubscription", request.NamespacedName)
 
-	prov, err = r.ProvisionerFactory.NewProvisioner(provisioner.BuildHostDataNoBMC(*host), nil)
+	prov, err = r.ProvisionerFactory.NewProvisioner(ctx, provisioner.BuildHostDataNoBMC(*host), nil)
 	if err != nil {
 		return prov, ready, errors.Wrap(err, "failed to create provisioner")
 	}
@@ -234,7 +234,7 @@ func (r *BMCEventSubscriptionReconciler) getProvisioner(request ctrl.Request, ho
 	return prov, ready, nil
 }
 
-func (r *BMCEventSubscriptionReconciler) getHTTPHeaders(subscription metal3api.BMCEventSubscription) ([]map[string]string, error) {
+func (r *BMCEventSubscriptionReconciler) getHTTPHeaders(ctx context.Context, subscription metal3api.BMCEventSubscription) ([]map[string]string, error) {
 	headers := []map[string]string{}
 
 	if subscription.Spec.HTTPHeadersRef == nil {
@@ -247,7 +247,7 @@ func (r *BMCEventSubscriptionReconciler) getHTTPHeaders(subscription metal3api.B
 		Namespace: subscription.Spec.HTTPHeadersRef.Namespace,
 	}
 
-	err := r.Get(context.TODO(), secretKey, secret)
+	err := r.Get(ctx, secretKey, secret)
 
 	if err != nil {
 		return headers, err
diff --git a/controllers/metal3.io/hostfirmwaresettings_controller.go b/controllers/metal3.io/hostfirmwaresettings_controller.go
index 71cc84f6..46e23945 100644
--- a/controllers/metal3.io/hostfirmwaresettings_controller.go
+++ b/controllers/metal3.io/hostfirmwaresettings_controller.go
@@ -59,6 +59,7 @@ type HostFirmwareSettingsReconciler struct {
 }
 
 type rInfo struct {
+	ctx    context.Context
 	log    logr.Logger
 	hfs    *metal3api.HostFirmwareSettings
 	bmh    *metal3api.BareMetalHost
@@ -117,7 +118,7 @@ func (r *HostFirmwareSettingsReconciler) Reconcile(ctx context.Context, req ctrl
 
 	// Get the corresponding baremetalhost in this namespace, if one doesn't exist don't continue processing
 	bmh := &metal3api.BareMetalHost{}
-	if err = r.Get(context.TODO(), req.NamespacedName, bmh); err != nil {
+	if err = r.Get(ctx, req.NamespacedName, bmh); err != nil {
 		reqLogger.Info("could not get baremetalhost, not running reconciler")
 		if k8serrors.IsNotFound(err) {
 			return ctrl.Result{}, nil
@@ -132,7 +133,7 @@ func (r *HostFirmwareSettingsReconciler) Reconcile(ctx context.Context, req ctrl
 
 	// Fetch the HostFirmwareSettings
 	hfs := &metal3api.HostFirmwareSettings{}
-	info := &rInfo{log: reqLogger, hfs: hfs, bmh: bmh}
+	info := &rInfo{ctx: ctx, log: reqLogger, hfs: hfs, bmh: bmh}
 	if err = r.Get(ctx, req.NamespacedName, hfs); err != nil {
 		// The HFS resource may have been deleted
 		if k8serrors.IsNotFound(err) {
@@ -144,7 +145,7 @@ func (r *HostFirmwareSettingsReconciler) Reconcile(ctx context.Context, req ctrl
 	}
 
 	// Create a provisioner that can access Ironic API
-	prov, err := r.ProvisionerFactory.NewProvisioner(provisioner.BuildHostDataNoBMC(*bmh), info.publishEvent)
+	prov, err := r.ProvisionerFactory.NewProvisioner(ctx, provisioner.BuildHostDataNoBMC(*bmh), info.publishEvent)
 	if err != nil {
 		return ctrl.Result{}, errors.Wrap(err, "failed to create provisioner")
 	}
@@ -175,7 +176,7 @@ func (r *HostFirmwareSettingsReconciler) Reconcile(ctx context.Context, req ctrl
 	}
 
 	for _, e := range info.events {
-		r.publishEvent(req, e)
+		r.publishEvent(ctx, req, e)
 	}
 
 	// requeue to run again after delay
@@ -285,7 +286,7 @@ func (r *HostFirmwareSettingsReconciler) updateStatus(info *rInfo, settings meta
 
 		t := metav1.Now()
 		info.hfs.Status.LastUpdated = &t
-		return r.Status().Update(context.TODO(), info.hfs)
+		return r.Status().Update(info.ctx, info.hfs)
 	}
 	return nil
 }
@@ -298,7 +299,7 @@ func (r *HostFirmwareSettingsReconciler) getOrCreateFirmwareSchema(info *rInfo,
 	firmwareSchema := &metal3api.FirmwareSchema{}
 
 	// If a schema exists that matches, use that, otherwise create a new one
-	if err = r.Get(context.TODO(), client.ObjectKey{Namespace: info.hfs.ObjectMeta.Namespace, Name: schemaName},
+	if err = r.Get(info.ctx, client.ObjectKey{Namespace: info.hfs.ObjectMeta.Namespace, Name: schemaName},
 		firmwareSchema); err == nil {
 		info.log.Info("found existing firmwareSchema resource")
 
@@ -306,7 +307,7 @@ func (r *HostFirmwareSettingsReconciler) getOrCreateFirmwareSchema(info *rInfo,
 		if err = controllerutil.SetOwnerReference(info.hfs, firmwareSchema, r.Scheme()); err != nil {
 			return nil, errors.Wrap(err, "could not set owner of existing firmwareSchema")
 		}
-		if err = r.Update(context.TODO(), firmwareSchema); err != nil {
+		if err = r.Update(info.ctx, firmwareSchema); err != nil {
 			return nil, err
 		}
 
@@ -348,7 +349,7 @@ func (r *HostFirmwareSettingsReconciler) getOrCreateFirmwareSchema(info *rInfo,
 		return nil, errors.Wrap(err, "could not set owner of firmwareSchema")
 	}
 
-	if err = r.Create(context.TODO(), firmwareSchema); err != nil {
+	if err = r.Create(info.ctx, firmwareSchema); err != nil {
 		return nil, err
 	}
 
@@ -413,10 +414,10 @@ func (r *HostFirmwareSettingsReconciler) validateHostFirmwareSettings(info *rInf
 	return nil
 }
 
-func (r *HostFirmwareSettingsReconciler) publishEvent(request ctrl.Request, event corev1.Event) {
+func (r *HostFirmwareSettingsReconciler) publishEvent(ctx context.Context, request ctrl.Request, event corev1.Event) {
 	reqLogger := r.Log.WithValues("hostfirmwaresettings", request.NamespacedName)
 	reqLogger.Info("publishing event", "reason", event.Reason, "message", event.Message)
-	err := r.Create(context.TODO(), &event)
+	err := r.Create(ctx, &event)
 	if err != nil {
 		reqLogger.Info("failed to record event, ignoring",
 			"reason", event.Reason, "message", event.Message, "error", err)
diff --git a/controllers/metal3.io/preprovisioningimage_controller.go b/controllers/metal3.io/preprovisioningimage_controller.go
index 1ccd577a..b0801e45 100644
--- a/controllers/metal3.io/preprovisioningimage_controller.go
+++ b/controllers/metal3.io/preprovisioningimage_controller.go
@@ -105,7 +105,7 @@ func (r *PreprovisioningImageReconciler) Reconcile(ctx context.Context, req ctrl
 		return ctrl.Result{}, nil
 	}
 
-	changed, err := r.update(&img, log)
+	changed, err := r.update(ctx, &img, log)
 
 	if k8serrors.IsNotFound(err) {
 		delay := getErrorRetryDelay(img.Status)
@@ -136,7 +136,7 @@ func configChanged(img *metal3api.PreprovisioningImage, format metal3api.ImageFo
 		img.Status.NetworkData == networkDataStatus)
 }
 
-func (r *PreprovisioningImageReconciler) update(img *metal3api.PreprovisioningImage, log logr.Logger) (bool, error) {
+func (r *PreprovisioningImageReconciler) update(ctx context.Context, img *metal3api.PreprovisioningImage, log logr.Logger) (bool, error) {
 	generation := img.GetGeneration()
 
 	if !r.ImageProvider.SupportsArchitecture(img.Spec.Architecture) {
@@ -149,7 +149,7 @@ func (r *PreprovisioningImageReconciler) update(img *metal3api.PreprovisioningIm
 		return setError(generation, &img.Status, reasonImageConfigurationError, "No acceptable image format supported"), nil
 	}
 
-	secretManager := secretutils.NewSecretManager(log, r.Client, r.APIReader)
+	secretManager := secretutils.NewSecretManager(ctx, log, r.Client, r.APIReader)
 	networkData, secretStatus, err := getNetworkData(secretManager, img)
 	if err != nil {
 		if k8serrors.IsNotFound(err) {
diff --git a/pkg/provisioner/demo/demo.go b/pkg/provisioner/demo/demo.go
index cf82e879..f6741975 100644
--- a/pkg/provisioner/demo/demo.go
+++ b/pkg/provisioner/demo/demo.go
@@ -1,6 +1,7 @@
 package demo
 
 import (
+	"context"
 	"time"
 
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -66,7 +67,7 @@ type demoProvisioner struct {
 type Demo struct{}
 
 // NewProvisioner returns a new demo Provisioner.
-func (d Demo) NewProvisioner(hostData provisioner.HostData, publisher provisioner.EventPublisher) (provisioner.Provisioner, error) {
+func (d Demo) NewProvisioner(_ context.Context, hostData provisioner.HostData, publisher provisioner.EventPublisher) (provisioner.Provisioner, error) {
 	p := &demoProvisioner{
 		objectMeta: hostData.ObjectMeta,
 		provID:     hostData.ProvisionerID,
diff --git a/pkg/provisioner/fixture/fixture.go b/pkg/provisioner/fixture/fixture.go
index 693c993d..8005d07a 100644
--- a/pkg/provisioner/fixture/fixture.go
+++ b/pkg/provisioner/fixture/fixture.go
@@ -1,6 +1,7 @@
 package fixture
 
 import (
+	"context"
 	"time"
 
 	"github.com/go-logr/logr"
@@ -76,7 +77,7 @@ type Fixture struct {
 }
 
 // NewProvisioner returns a new Fixture Provisioner.
-func (f *Fixture) NewProvisioner(hostData provisioner.HostData, publisher provisioner.EventPublisher) (provisioner.Provisioner, error) {
+func (f *Fixture) NewProvisioner(_ context.Context, hostData provisioner.HostData, publisher provisioner.EventPublisher) (provisioner.Provisioner, error) {
 	p := &fixtureProvisioner{
 		provID:    hostData.ProvisionerID,
 		bmcCreds:  hostData.BMCCredentials,
diff --git a/pkg/provisioner/ironic/factory.go b/pkg/provisioner/ironic/factory.go
index 6815c1ca..8189e1e4 100644
--- a/pkg/provisioner/ironic/factory.go
+++ b/pkg/provisioner/ironic/factory.go
@@ -1,6 +1,7 @@
 package ironic
 
 import (
+	"context"
 	"errors"
 	"fmt"
 	"net/url"
@@ -78,7 +79,7 @@ func (f *ironicProvisionerFactory) init(havePreprovImgBuilder bool) error {
 	return nil
 }
 
-func (f ironicProvisionerFactory) ironicProvisioner(hostData provisioner.HostData, publisher provisioner.EventPublisher) (*ironicProvisioner, error) {
+func (f ironicProvisionerFactory) ironicProvisioner(ctx context.Context, hostData provisioner.HostData, publisher provisioner.EventPublisher) (*ironicProvisioner, error) {
 	provisionerLogger := f.log.WithValues("host", ironicNodeName(hostData.ObjectMeta))
 
 	p := &ironicProvisioner{
@@ -93,6 +94,7 @@ func (f ironicProvisionerFactory) ironicProvisioner(hostData provisioner.HostDat
 		log:                     provisionerLogger,
 		debugLog:                provisionerLogger.V(1),
 		publisher:               publisher,
+		ctx:                     ctx,
 	}
 
 	return p, nil
@@ -100,8 +102,8 @@ func (f ironicProvisionerFactory) ironicProvisioner(hostData provisioner.HostDat
 
 // NewProvisioner returns a new Ironic Provisioner using the global
 // configuration for finding the Ironic services.
-func (f ironicProvisionerFactory) NewProvisioner(hostData provisioner.HostData, publisher provisioner.EventPublisher) (provisioner.Provisioner, error) {
-	return f.ironicProvisioner(hostData, publisher)
+func (f ironicProvisionerFactory) NewProvisioner(ctx context.Context, hostData provisioner.HostData, publisher provisioner.EventPublisher) (provisioner.Provisioner, error) {
+	return f.ironicProvisioner(ctx, hostData, publisher)
 }
 
 func loadConfigFromEnv(havePreprovImgBuilder bool) (ironicConfig, error) {
diff --git a/pkg/provisioner/ironic/ironic.go b/pkg/provisioner/ironic/ironic.go
index 3117ca24..d1160fdb 100644
--- a/pkg/provisioner/ironic/ironic.go
+++ b/pkg/provisioner/ironic/ironic.go
@@ -1,6 +1,7 @@
 package ironic
 
 import (
+	"context"
 	"fmt"
 	"net"
 	"reflect"
@@ -110,6 +111,8 @@ type ironicProvisioner struct {
 	publisher provisioner.EventPublisher
 	// available API features
 	availableFeatures clients.AvailableFeatures
+	// request context
+	ctx context.Context
 }
 
 func (p *ironicProvisioner) bmcAccess() (bmc.AccessDetails, error) {
diff --git a/pkg/provisioner/provisioner.go b/pkg/provisioner/provisioner.go
index 24191e58..29a15d54 100644
--- a/pkg/provisioner/provisioner.go
+++ b/pkg/provisioner/provisioner.go
@@ -1,6 +1,7 @@
 package provisioner
 
 import (
+	"context"
 	"errors"
 	"time"
 
@@ -50,7 +51,7 @@ func BuildHostDataNoBMC(host metal3api.BareMetalHost) HostData {
 
 // Factory is the interface for creating new Provisioner objects.
 type Factory interface {
-	NewProvisioner(hostData HostData, publish EventPublisher) (Provisioner, error)
+	NewProvisioner(ctx context.Context, hostData HostData, publish EventPublisher) (Provisioner, error)
 }
 
 // HostConfigData retrieves host configuration data.
diff --git a/pkg/secretutils/secret_manager.go b/pkg/secretutils/secret_manager.go
index 981dc33a..f2bbc164 100644
--- a/pkg/secretutils/secret_manager.go
+++ b/pkg/secretutils/secret_manager.go
@@ -25,14 +25,16 @@ const (
 // client cache, labelling so that they will be included in the client cache,
 // and optionally setting an owner reference.
 type SecretManager struct {
+	ctx       context.Context
 	log       logr.Logger
 	client    client.Client
 	apiReader client.Reader
 }
 
 // NewSecretManager returns a new SecretManager.
-func NewSecretManager(log logr.Logger, cacheClient client.Client, apiReader client.Reader) SecretManager {
+func NewSecretManager(ctx context.Context, log logr.Logger, cacheClient client.Client, apiReader client.Reader) SecretManager {
 	return SecretManager{
+		ctx:       ctx,
 		log:       log.WithName("secret_manager"),
 		client:    cacheClient,
 		apiReader: apiReader,
@@ -45,7 +47,7 @@ func (sm *SecretManager) findSecret(key types.NamespacedName) (secret *corev1.Se
 	secret = &corev1.Secret{}
 
 	// Look for secret in the filtered cache
-	err = sm.client.Get(context.TODO(), key, secret)
+	err = sm.client.Get(sm.ctx, key, secret)
 	if err == nil {
 		return secret, nil
 	}
@@ -54,7 +56,7 @@ func (sm *SecretManager) findSecret(key types.NamespacedName) (secret *corev1.Se
 	}
 
 	// Secret not in cache; check API directly for unlabelled Secret
-	err = sm.apiReader.Get(context.TODO(), key, secret)
+	err = sm.apiReader.Get(sm.ctx, key, secret)
 	if err != nil {
 		return nil, err
 	}
@@ -106,7 +108,7 @@ func (sm *SecretManager) claimSecret(secret *corev1.Secret, owner client.Object,
 	}
 
 	if needsUpdate {
-		if err := sm.client.Update(context.TODO(), secret); err != nil {
+		if err := sm.client.Update(sm.ctx, secret); err != nil {
 			return errors.Wrap(err, fmt.Sprintf("failed to update secret %s in namespace %s", secret.ObjectMeta.Name, secret.ObjectMeta.Namespace))
 		}
 	}
@@ -156,7 +158,7 @@ func (sm *SecretManager) ReleaseSecret(secret *corev1.Secret) error {
 	secret.Finalizers = utils.FilterStringFromList(
 		secret.Finalizers, SecretsFinalizer)
 
-	if err := sm.client.Update(context.Background(), secret); err != nil {
+	if err := sm.client.Update(sm.ctx, secret); err != nil {
 		return errors.Wrap(err, fmt.Sprintf("failed to remove finalizer from secret %s in namespace %s",
 			secret.ObjectMeta.Name, secret.ObjectMeta.Namespace))
 	}


Test patch:
diff --git a/controllers/metal3.io/host_config_data_test.go b/controllers/metal3.io/host_config_data_test.go
index f763a6e8..24824e4e 100644
--- a/controllers/metal3.io/host_config_data_test.go
+++ b/controllers/metal3.io/host_config_data_test.go
@@ -69,7 +69,7 @@ func TestLabelSecrets(t *testing.T) {
 			hcd := &hostConfigData{
 				host:          host,
 				log:           baselog.WithName("host_config_data"),
-				secretManager: secretutils.NewSecretManager(baselog, c, c),
+				secretManager: secretutils.NewSecretManager(context.TODO(), baselog, c, c),
 			}
 
 			secret := newSecret(tc.name, map[string]string{"value": "somedata"})
@@ -343,7 +343,7 @@ func TestProvisionWithHostConfig(t *testing.T) {
 			hcd := &hostConfigData{
 				host:          tc.Host,
 				log:           baselog.WithName("host_config_data"),
-				secretManager: secretutils.NewSecretManager(baselog, c, c),
+				secretManager: secretutils.NewSecretManager(context.TODO(), baselog, c, c),
 			}
 
 			actualUserData, err := hcd.UserData()
diff --git a/controllers/metal3.io/host_state_machine_test.go b/controllers/metal3.io/host_state_machine_test.go
index 947a4302..fd02ecd2 100644
--- a/controllers/metal3.io/host_state_machine_test.go
+++ b/controllers/metal3.io/host_state_machine_test.go
@@ -1,6 +1,7 @@
 package controllers
 
 import (
+	"context"
 	"testing"
 	"time"
 
@@ -21,7 +22,7 @@ import (
 
 func testStateMachine(host *metal3api.BareMetalHost) *hostStateMachine {
 	r := newTestReconciler()
-	p, _ := r.ProvisionerFactory.NewProvisioner(provisioner.BuildHostData(*host, bmc.Credentials{}),
+	p, _ := r.ProvisionerFactory.NewProvisioner(context.TODO(), provisioner.BuildHostData(*host, bmc.Credentials{}),
 		func(reason, message string) {})
 	return newHostStateMachine(host, r, p, true)
 }
diff --git a/pkg/provisioner/ironic/ironic_test.go b/pkg/provisioner/ironic/ironic_test.go
index 89c1965b..d0e56d5f 100644
--- a/pkg/provisioner/ironic/ironic_test.go
+++ b/pkg/provisioner/ironic/ironic_test.go
@@ -1,6 +1,7 @@
 package ironic
 
 import (
+	"context"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -48,7 +49,7 @@ func newProvisionerWithSettings(host metal3api.BareMetalHost, bmcCreds bmc.Crede
 
 	factory := newTestProvisionerFactory()
 	factory.clientIronic = clientIronic
-	return factory.ironicProvisioner(hostData, publisher)
+	return factory.ironicProvisioner(context.TODO(), hostData, publisher)
 }
 
 func makeHost() metal3api.BareMetalHost {
@@ -133,7 +134,7 @@ func TestNewNoBMCDetails(t *testing.T) {
 	host.Spec.BMC = metal3api.BMCDetails{}
 
 	factory := newTestProvisionerFactory()
-	prov, err := factory.NewProvisioner(provisioner.BuildHostData(host, bmc.Credentials{}), nullEventPublisher)
+	prov, err := factory.NewProvisioner(context.TODO(), provisioner.BuildHostData(host, bmc.Credentials{}), nullEventPublisher)
 	assert.Equal(t, nil, err)
 	assert.NotEqual(t, nil, prov)
 }