Repository: metal3-io/baremetal-operator
Pull request #601 (2020-07-20 14:58:04)

Add support for http_basic auth for ironic and inspector
This commit adds the support for BMO to interact with ironic when
using http_basic.

requires gophercloud 0.12.0

metal3-io-bot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/metal3-io/baremetal-operator/pull/601#" title="Author self-approved">iurygregory</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **zaneb**
You can assign the PR to them by writing `/assign @zaneb` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=metal3-io%2Fbaremetal-operator).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/metal3-io/baremetal-operator/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["zaneb"]} -->
metal3-io-bot: @iurygregory: The following test **failed**, say `/retest` to rerun them all:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
unit | 16ce27b7ec5983c7d8e357c055e488d071aa82fc | [link](https://prow.apps.ci.metal3.io/view/gcs/metal3-prow-artifacts/pr-logs/pull/metal3-io_baremetal-operator/601/unit/1285227544559226883) | `/test unit`



<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
metal3-io-bot: @iurygregory: The following tests **failed**, say `/retest` to rerun them all:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
unit | 16ce27b7ec5983c7d8e357c055e488d071aa82fc | [link](https://prow.apps.ci.metal3.io/view/gcs/metal3-prow-artifacts/pr-logs/pull/metal3-io_baremetal-operator/601/unit/1285227544559226883) | `/test unit`
govet | 16ce27b7ec5983c7d8e357c055e488d071aa82fc | [link](https://prow.apps.ci.metal3.io/view/gcs/metal3-prow-artifacts/pr-logs/pull/metal3-io_baremetal-operator/601/govet/1285227544559226886) | `/test govet`



<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
metal3-io-bot: @iurygregory: The following test **failed**, say `/retest` to rerun them all:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
unit | b6cec59503ff15c781198fe7e4d7edd124442425 | [link](https://prow.apps.ci.metal3.io/view/gcs/metal3-prow-artifacts/pr-logs/pull/metal3-io_baremetal-operator/601/unit/1285231697054404610) | `/test unit`



<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
metal3-io-bot: @iurygregory: The following tests **failed**, say `/retest` to rerun them all:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
unit | b6cec59503ff15c781198fe7e4d7edd124442425 | [link](https://prow.apps.ci.metal3.io/view/gcs/metal3-prow-artifacts/pr-logs/pull/metal3-io_baremetal-operator/601/unit/1285231697054404610) | `/test unit`
govet | 8ca00e225a961cf1336dac12d52c08795828f2e4 | [link](https://prow.apps.ci.metal3.io/view/gcs/metal3-prow-artifacts/pr-logs/pull/metal3-io_baremetal-operator/601/govet/1285650076101775362) | `/test govet`



<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
metal3-io-bot: @iurygregory: The following tests **failed**, say `/retest` to rerun them all:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
govet | 8ca00e225a961cf1336dac12d52c08795828f2e4 | [link](https://prow.apps.ci.metal3.io/view/gcs/metal3-prow-artifacts/pr-logs/pull/metal3-io_baremetal-operator/601/govet/1285650076101775362) | `/test govet`
unit | 8ca00e225a961cf1336dac12d52c08795828f2e4 | [link](https://prow.apps.ci.metal3.io/view/gcs/metal3-prow-artifacts/pr-logs/pull/metal3-io_baremetal-operator/601/unit/1285650076101775366) | `/test unit`



<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
metal3-io-bot: @iurygregory: The following tests **failed**, say `/retest` to rerun them all:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
unit | 8ca00e225a961cf1336dac12d52c08795828f2e4 | [link](https://prow.apps.ci.metal3.io/view/gcs/metal3-prow-artifacts/pr-logs/pull/metal3-io_baremetal-operator/601/unit/1285650076101775366) | `/test unit`
govet | ac81d4da2d08af65a6754559e56294fd8efc3893 | [link](https://prow.apps.ci.metal3.io/view/gcs/metal3-prow-artifacts/pr-logs/pull/metal3-io_baremetal-operator/601/govet/1285658380815306752) | `/test govet`



<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
metal3-io-bot: @iurygregory: The following tests **failed**, say `/retest` to rerun them all:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
govet | ac81d4da2d08af65a6754559e56294fd8efc3893 | [link](https://prow.apps.ci.metal3.io/view/gcs/metal3-prow-artifacts/pr-logs/pull/metal3-io_baremetal-operator/601/govet/1285658380815306752) | `/test govet`
unit | ac81d4da2d08af65a6754559e56294fd8efc3893 | [link](https://prow.apps.ci.metal3.io/view/gcs/metal3-prow-artifacts/pr-logs/pull/metal3-io_baremetal-operator/601/unit/1285658380815306754) | `/test unit`



<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
metal3-io-bot: @iurygregory: The following test **failed**, say `/retest` to rerun them all:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
unit | 0869812f0f374bbdb0cc4bb2d35bb7a3236d3bd7 | [link](https://prow.apps.ci.metal3.io/view/gcs/metal3-prow-artifacts/pr-logs/pull/metal3-io_baremetal-operator/601/unit/1285687195830587392) | `/test unit`



<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
iurygregory: /retest
metal3-io-bot: @iurygregory: The following test **failed**, say `/retest` to rerun them all:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
unit | 0869812f0f374bbdb0cc4bb2d35bb7a3236d3bd7 | [link](https://prow.apps.ci.metal3.io/view/gcs/metal3-prow-artifacts/pr-logs/pull/metal3-io_baremetal-operator/601/unit/1285826364007518208) | `/test unit`



<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
iurygregory: /test unit
metal3-io-bot: @iurygregory: The following test **failed**, say `/retest` to rerun them all:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
unit | 0869812f0f374bbdb0cc4bb2d35bb7a3236d3bd7 | [link](https://prow.apps.ci.metal3.io/view/gcs/metal3-prow-artifacts/pr-logs/pull/metal3-io_baremetal-operator/601/unit/1285835675286900736) | `/test unit`



<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
iurygregory: /retest
metal3-io-bot: @iurygregory: The following test **failed**, say `/retest` to rerun them all:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
unit | 0869812f0f374bbdb0cc4bb2d35bb7a3236d3bd7 | [link](https://prow.apps.ci.metal3.io/view/gcs/metal3-prow-artifacts/pr-logs/pull/metal3-io_baremetal-operator/601/unit/1285845993396375552) | `/test unit`



<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->

Patch:
diff --git a/Makefile b/Makefile
index 3c0ba25b..b4df956b 100644
--- a/Makefile
+++ b/Makefile
@@ -8,9 +8,8 @@ PACKAGES = $(foreach dir,$(CODE_DIRS),$(dir)/...)
 COVER_PROFILE = cover.out
 
 # See pkg/version.go for details
-SOURCE_GIT_COMMIT ?= $(shell git rev-parse --verify 'HEAD^{commit}')
-BUILD_VERSION ?= $(shell git describe --always --abbrev=40 --dirty)
-export LDFLAGS="-X github.com/metal3-io/baremetal-operator/pkg/version.Raw=${BUILD_VERSION} -X github.com/metal3-io/baremetal-operator/pkg/version.Commit=${SOURCE_GIT_COMMIT}"
+GIT_COMMIT="$(shell git rev-parse --verify 'HEAD^{commit}')"
+export LDFLAGS="-X github.com/metal3-io/baremetal-operator/pkg/version.Raw=$(shell git describe --always --abbrev=40 --dirty) -X github.com/metal3-io/baremetal-operator/pkg/version.Commit=${GIT_COMMIT}"
 
 # Set some variables the operator expects to have in order to work
 # Those need to be the same as in deploy/ironic_ci.env
@@ -44,7 +43,7 @@ show_dirs:
 .PHONY: generate
 generate: bin/operator-sdk ## Run the operator-sdk code generator
 	./bin/operator-sdk generate $(VERBOSE) k8s
-	./bin/operator-sdk generate $(VERBOSE) crds --crd-version=v1
+	./bin/operator-sdk generate $(VERBOSE) crds
 	openapi-gen \
 		--input-dirs ./pkg/apis/metal3/v1alpha1 \
 		--output-package ./pkg/apis/metal3/v1alpha1 \
diff --git a/cmd/get-hardware-details/main.go b/cmd/get-hardware-details/main.go
index 066adebb..3e82eaa4 100644
--- a/cmd/get-hardware-details/main.go
+++ b/cmd/get-hardware-details/main.go
@@ -7,27 +7,27 @@ import (
 	"fmt"
 	"os"
 
-	"github.com/gophercloud/gophercloud/openstack/baremetalintrospection/noauth"
 	"github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection"
+	"github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/clients"
 	"github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/hardwaredetails"
 )
 
+type options struct {
+	Endpoint   string
+	AuthConfig clients.AuthConfig
+	NodeID     string
+}
+
 func main() {
-	if len(os.Args) != 3 {
-		fmt.Println("Usage: get-hardware-details <inspector URI> <node UUID>")
-		return
-	}
+	opts := getOptions()
 
-	inspector, err := noauth.NewBareMetalIntrospectionNoAuth(
-		noauth.EndpointOpts{
-			IronicInspectorEndpoint: os.Args[1],
-		})
+	inspector, err := clients.InspectorClient(opts.Endpoint, opts.AuthConfig)
 	if err != nil {
 		fmt.Printf("could not get inspector client: %s", err)
 		os.Exit(1)
 	}
 
-	introData := introspection.GetIntrospectionData(inspector, os.Args[2])
+	introData := introspection.GetIntrospectionData(inspector, opts.NodeID)
 	data, err := introData.Extract()
 	if err != nil {
 		fmt.Printf("could not get introspection data: %s", err)
@@ -42,3 +42,19 @@ func main() {
 
 	fmt.Println(string(json))
 }
+
+func getOptions() (o options) {
+	if len(os.Args) != 3 {
+		fmt.Println("Usage: get-hardware-details <inspector URI> <node UUID>")
+		os.Exit(1)
+	}
+
+	var err error
+	o.Endpoint, o.AuthConfig, err = clients.ConfigFromEndpointURL(os.Args[1])
+	if err != nil {
+		fmt.Printf("Error: %s\n", err)
+		os.Exit(1)
+	}
+	o.NodeID = os.Args[2]
+	return
+}
diff --git a/cmd/make-bm-worker/main.go b/cmd/make-bm-worker/main.go
index e7b11ebd..4615bce7 100644
--- a/cmd/make-bm-worker/main.go
+++ b/cmd/make-bm-worker/main.go
@@ -16,7 +16,6 @@ func main() {
 	var disableCertificateVerification = flag.Bool("disableCertificateVerification", false, "will skip certificate validation when true")
 	var hardwareProfile = flag.String("hardwareprofile", "", "hardwareProfile to be used")
 	var macAddress = flag.String("boot-mac", "", "boot-mac for bootMACAddress")
-	var bootMode = flag.String("boot-mode", "", "boot-mode for host (UEFI or legacy)")
 	var verbose = flag.Bool("v", false, "turn on verbose output")
 	var consumer = flag.String(
 		"consumer", "", "specify name of a related, existing, consumer to link")
@@ -43,11 +42,6 @@ func main() {
 		os.Exit(1)
 	}
 
-	if *bootMode != "" && *bootMode != "UEFI" && *bootMode != "legacy" {
-		fmt.Fprintf(os.Stderr, "Invalid boot mode %q, use \"UEFI\" or \"legacy\"\n", *bootMode)
-		os.Exit(1)
-	}
-
 	template := templates.Template{
 		Name:                           strings.Replace(hostName, "_", "-", -1),
 		BMCAddress:                     *bmcAddress,
@@ -59,9 +53,6 @@ func main() {
 		Consumer:                       strings.TrimSpace(*consumer),
 		ConsumerNamespace:              strings.TrimSpace(*consumerNamespace),
 	}
-	if bootMode != nil {
-		template.BootMode = *bootMode
-	}
 	if *verbose {
 		fmt.Fprintf(os.Stderr, "%v", template)
 	}
diff --git a/cmd/make-bm-worker/templates/templates.go b/cmd/make-bm-worker/templates/templates.go
index f3477154..00d16847 100644
--- a/cmd/make-bm-worker/templates/templates.go
+++ b/cmd/make-bm-worker/templates/templates.go
@@ -28,9 +28,6 @@ spec:
 {{- end }}
 {{- if .BootMacAddress }}
   bootMACAddress: {{ .BootMacAddress }}
-{{- end }}
-{{- if .BootMode }}
-  bootMode: {{ .BootMode }}
 {{- end }}
   bmc:
     address: {{ .BMCAddress }}
@@ -54,7 +51,6 @@ type Template struct {
 	Password                       string
 	HardwareProfile                string
 	BootMacAddress                 string
-	BootMode                       string
 	Consumer                       string
 	ConsumerNamespace              string
 }
diff --git a/deploy/crds/metal3.io_baremetalhosts_crd.yaml b/deploy/crds/metal3.io_baremetalhosts_crd.yaml
index 37620ef4..8f481807 100644
--- a/deploy/crds/metal3.io_baremetalhosts_crd.yaml
+++ b/deploy/crds/metal3.io_baremetalhosts_crd.yaml
@@ -1,8 +1,37 @@
-apiVersion: apiextensions.k8s.io/v1
+apiVersion: apiextensions.k8s.io/v1beta1
 kind: CustomResourceDefinition
 metadata:
   name: baremetalhosts.metal3.io
 spec:
+  additionalPrinterColumns:
+  - JSONPath: .status.operationalStatus
+    description: Operational status
+    name: Status
+    type: string
+  - JSONPath: .status.provisioning.state
+    description: Provisioning status
+    name: Provisioning Status
+    type: string
+  - JSONPath: .spec.consumerRef.name
+    description: Consumer using this host
+    name: Consumer
+    type: string
+  - JSONPath: .spec.bmc.address
+    description: Address of management controller
+    name: BMC
+    type: string
+  - JSONPath: .status.hardwareProfile
+    description: The type of hardware detected
+    name: Hardware Profile
+    type: string
+  - JSONPath: .spec.online
+    description: Whether the host is online or not
+    name: Online
+    type: string
+  - JSONPath: .status.errorMessage
+    description: Most recent error
+    name: Error
+    type: string
   group: metal3.io
   names:
     kind: BareMetalHost
@@ -13,705 +42,677 @@ spec:
     - bmhost
     singular: baremetalhost
   scope: Namespaced
-  versions:
-  - additionalPrinterColumns:
-    - description: Operational status
-      jsonPath: .status.operationalStatus
-      name: Status
-      type: string
-    - description: Provisioning status
-      jsonPath: .status.provisioning.state
-      name: Provisioning Status
-      type: string
-    - description: Consumer using this host
-      jsonPath: .spec.consumerRef.name
-      name: Consumer
-      type: string
-    - description: Address of management controller
-      jsonPath: .spec.bmc.address
-      name: BMC
-      type: string
-    - description: The type of hardware detected
-      jsonPath: .status.hardwareProfile
-      name: Hardware Profile
-      type: string
-    - description: Whether the host is online or not
-      jsonPath: .spec.online
-      name: Online
-      type: string
-    - description: Most recent error
-      jsonPath: .status.errorMessage
-      name: Error
-      type: string
-    name: v1alpha1
-    schema:
-      openAPIV3Schema:
-        description: BareMetalHost is the Schema for the baremetalhosts API
-        properties:
-          apiVersion:
-            description: 'APIVersion defines the versioned schema of this representation
-              of an object. Servers should convert recognized schemas to the latest
-              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
-            type: string
-          kind:
-            description: 'Kind is a string value representing the REST resource this
-              object represents. Servers may infer this from the endpoint the client
-              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
-            type: string
-          metadata:
-            type: object
-          spec:
-            description: BareMetalHostSpec defines the desired state of BareMetalHost
-            properties:
-              bmc:
-                description: How do we connect to the BMC?
-                properties:
-                  address:
-                    description: Address holds the URL for accessing the controller
-                      on the network.
-                    type: string
-                  credentialsName:
-                    description: The name of the secret containing the BMC credentials
-                      (requires keys "username" and "password").
-                    type: string
-                  disableCertificateVerification:
-                    description: DisableCertificateVerification disables verification
-                      of server certificates when using HTTPS to connect to the BMC.
-                      This is required when the server certificate is self-signed,
-                      but is insecure because it allows a man-in-the-middle to intercept
-                      the connection.
-                    type: boolean
-                required:
-                - address
-                - credentialsName
-                type: object
-              bootMACAddress:
-                description: Which MAC address will PXE boot? This is optional for
-                  some types, but required for libvirt VMs driven by vbmc.
-                pattern: '[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}'
-                type: string
-              bootMode:
-                description: Select the method of initializing the hardware during
-                  boot. Defaults to UEFI.
-                enum:
-                - UEFI
-                - legacy
-                type: string
-              consumerRef:
-                description: ConsumerRef can be used to store information about something
-                  that is using a host. When it is not empty, the host is considered
-                  "in use".
-                properties:
-                  apiVersion:
-                    description: API version of the referent.
-                    type: string
-                  fieldPath:
-                    description: 'If referring to a piece of an object instead of
-                      an entire object, this string should contain a valid JSON/Go
-                      field access statement, such as desiredState.manifest.containers[2].
-                      For example, if the object reference is to a container within
-                      a pod, this would take on a value like: "spec.containers{name}"
-                      (where "name" refers to the name of the container that triggered
-                      the event) or if no container name is specified "spec.containers[2]"
-                      (container with index 2 in this pod). This syntax is chosen
-                      only to have some well-defined way of referencing a part of
-                      an object. TODO: this design is not final and this field is
-                      subject to change in the future.'
-                    type: string
-                  kind:
-                    description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
-                    type: string
-                  name:
-                    description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
-                    type: string
-                  namespace:
-                    description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
-                    type: string
-                  resourceVersion:
-                    description: 'Specific resourceVersion to which this reference
-                      is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency'
-                    type: string
-                  uid:
-                    description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids'
-                    type: string
-                type: object
-              description:
-                description: Description is a human-entered text used to help identify
-                  the host
-                type: string
-              externallyProvisioned:
-                description: ExternallyProvisioned means something else is managing
-                  the image running on the host and the operator should only manage
-                  the power status and hardware inventory inspection. If the Image
-                  field is filled in, this field is ignored.
-                type: boolean
-              hardwareProfile:
-                description: What is the name of the hardware profile for this host?
-                  It should only be necessary to set this when inspection cannot automatically
-                  determine the profile.
-                type: string
-              image:
-                description: Image holds the details of the image to be provisioned.
+  subresources:
+    status: {}
+  validation:
+    openAPIV3Schema:
+      description: BareMetalHost is the Schema for the baremetalhosts API
+      properties:
+        apiVersion:
+          description: 'APIVersion defines the versioned schema of this representation
+            of an object. Servers should convert recognized schemas to the latest
+            internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+          type: string
+        kind:
+          description: 'Kind is a string value representing the REST resource this
+            object represents. Servers may infer this from the endpoint the client
+            submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+          type: string
+        metadata:
+          type: object
+        spec:
+          description: BareMetalHostSpec defines the desired state of BareMetalHost
+          properties:
+            bmc:
+              description: How do we connect to the BMC?
+              properties:
+                address:
+                  description: Address holds the URL for accessing the controller
+                    on the network.
+                  type: string
+                credentialsName:
+                  description: The name of the secret containing the BMC credentials
+                    (requires keys "username" and "password").
+                  type: string
+                disableCertificateVerification:
+                  description: DisableCertificateVerification disables verification
+                    of server certificates when using HTTPS to connect to the BMC.
+                    This is required when the server certificate is self-signed, but
+                    is insecure because it allows a man-in-the-middle to intercept
+                    the connection.
+                  type: boolean
+              required:
+              - address
+              - credentialsName
+              type: object
+            bootMACAddress:
+              description: Which MAC address will PXE boot? This is optional for some
+                types, but required for libvirt VMs driven by vbmc.
+              pattern: '[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}'
+              type: string
+            bootMode:
+              description: Select the method of initializing the hardware during boot
+                to override the value based on the BMC driver.
+              enum:
+              - UEFI
+              - legacy
+              type: string
+            consumerRef:
+              description: ConsumerRef can be used to store information about something
+                that is using a host. When it is not empty, the host is considered
+                "in use".
+              properties:
+                apiVersion:
+                  description: API version of the referent.
+                  type: string
+                fieldPath:
+                  description: 'If referring to a piece of an object instead of an
+                    entire object, this string should contain a valid JSON/Go field
+                    access statement, such as desiredState.manifest.containers[2].
+                    For example, if the object reference is to a container within
+                    a pod, this would take on a value like: "spec.containers{name}"
+                    (where "name" refers to the name of the container that triggered
+                    the event) or if no container name is specified "spec.containers[2]"
+                    (container with index 2 in this pod). This syntax is chosen only
+                    to have some well-defined way of referencing a part of an object.
+                    TODO: this design is not final and this field is subject to change
+                    in the future.'
+                  type: string
+                kind:
+                  description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+                  type: string
+                name:
+                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
+                  type: string
+                namespace:
+                  description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'
+                  type: string
+                resourceVersion:
+                  description: 'Specific resourceVersion to which this reference is
+                    made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency'
+                  type: string
+                uid:
+                  description: 'UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids'
+                  type: string
+              type: object
+            description:
+              description: Description is a human-entered text used to help identify
+                the host
+              type: string
+            externallyProvisioned:
+              description: ExternallyProvisioned means something else is managing
+                the image running on the host and the operator should only manage
+                the power status and hardware inventory inspection. If the Image field
+                is filled in, this field is ignored.
+              type: boolean
+            hardwareProfile:
+              description: What is the name of the hardware profile for this host?
+                It should only be necessary to set this when inspection cannot automatically
+                determine the profile.
+              type: string
+            image:
+              description: Image holds the details of the image to be provisioned.
+              properties:
+                checksum:
+                  description: Checksum is the checksum for the image.
+                  type: string
+                checksumType:
+                  description: ChecksumType is the checksum algorithm for the image.
+                    e.g md5, sha256, sha512
+                  enum:
+                  - md5
+                  - sha256
+                  - sha512
+                  type: string
+                format:
+                  description: DiskFormat contains the format of the image (raw, qcow2,
+                    ...) Needs to be set to raw for raw images streaming
+                  enum:
+                  - raw
+                  - qcow2
+                  - vdi
+                  - vmdk
+                  type: string
+                url:
+                  description: URL is a location of an image to deploy.
+                  type: string
+              required:
+              - checksum
+              - url
+              type: object
+            metaData:
+              description: MetaData holds the reference to the Secret containing host
+                metadata (e.g. meta_data.json which is passed to Config Drive).
+              properties:
+                name:
+                  description: Name is unique within a namespace to reference a secret
+                    resource.
+                  type: string
+                namespace:
+                  description: Namespace defines the space within which the secret
+                    name must be unique.
+                  type: string
+              type: object
+            networkData:
+              description: NetworkData holds the reference to the Secret containing
+                network configuration (e.g content of network_data.json which is passed
+                to Config Drive).
+              properties:
+                name:
+                  description: Name is unique within a namespace to reference a secret
+                    resource.
+                  type: string
+                namespace:
+                  description: Namespace defines the space within which the secret
+                    name must be unique.
+                  type: string
+              type: object
+            online:
+              description: Should the server be online?
+              type: boolean
+            rootDeviceHints:
+              description: Provide guidance about how to choose the device for the
+                image being provisioned.
+              properties:
+                deviceName:
+                  description: A Linux device name like "/dev/vda". The hint must
+                    match the actual value exactly.
+                  type: string
+                hctl:
+                  description: A SCSI bus address like 0:0:0:0. The hint must match
+                    the actual value exactly.
+                  type: string
+                minSizeGigabytes:
+                  description: The minimum size of the device in Gigabytes.
+                  minimum: 0
+                  type: integer
+                model:
+                  description: A vendor-specific device identifier. The hint can be
+                    a substring of the actual value.
+                  type: string
+                rotational:
+                  description: True if the device should use spinning media, false
+                    otherwise.
+                  type: boolean
+                serialNumber:
+                  description: Device serial number. The hint must match the actual
+                    value exactly.
+                  type: string
+                vendor:
+                  description: The name of the vendor or manufacturer of the device.
+                    The hint can be a substring of the actual value.
+                  type: string
+                wwn:
+                  description: Unique storage identifier. The hint must match the
+                    actual value exactly.
+                  type: string
+                wwnVendorExtension:
+                  description: Unique vendor storage identifier. The hint must match
+                    the actual value exactly.
+                  type: string
+                wwnWithExtension:
+                  description: Unique storage identifier with the vendor extension
+                    appended. The hint must match the actual value exactly.
+                  type: string
+              type: object
+            taints:
+              description: Taints is the full, authoritative list of taints to apply
+                to the corresponding Machine. This list will overwrite any modifications
+                made to the Machine on an ongoing basis.
+              items:
+                description: The node this Taint is attached to has the "effect" on
+                  any pod that does not tolerate the Taint.
                 properties:
-                  checksum:
-                    description: Checksum is the checksum for the image.
+                  effect:
+                    description: Required. The effect of the taint on pods that do
+                      not tolerate the taint. Valid effects are NoSchedule, PreferNoSchedule
+                      and NoExecute.
                     type: string
-                  checksumType:
-                    description: ChecksumType is the checksum algorithm for the image.
-                      e.g md5, sha256, sha512
-                    enum:
-                    - md5
-                    - sha256
-                    - sha512
+                  key:
+                    description: Required. The taint key to be applied to a node.
                     type: string
-                  format:
-                    description: DiskFormat contains the format of the image (raw,
-                      qcow2, ...) Needs to be set to raw for raw images streaming
-                    enum:
-                    - raw
-                    - qcow2
-                    - vdi
-                    - vmdk
+                  timeAdded:
+                    description: TimeAdded represents the time at which the taint
+                      was added. It is only written for NoExecute taints.
+                    format: date-time
                     type: string
-                  url:
-                    description: URL is a location of an image to deploy.
+                  value:
+                    description: Required. The taint value corresponding to the taint
+                      key.
                     type: string
                 required:
-                - checksum
-                - url
-                type: object
-              metaData:
-                description: MetaData holds the reference to the Secret containing
-                  host metadata (e.g. meta_data.json which is passed to Config Drive).
-                properties:
-                  name:
-                    description: Name is unique within a namespace to reference a
-                      secret resource.
-                    type: string
-                  namespace:
-                    description: Namespace defines the space within which the secret
-                      name must be unique.
-                    type: string
-                type: object
-              networkData:
-                description: NetworkData holds the reference to the Secret containing
-                  network configuration (e.g content of network_data.json which is
-                  passed to Config Drive).
-                properties:
-                  name:
-                    description: Name is unique within a namespace to reference a
-                      secret resource.
-                    type: string
-                  namespace:
-                    description: Namespace defines the space within which the secret
-                      name must be unique.
-                    type: string
-                type: object
-              online:
-                description: Should the server be online?
-                type: boolean
-              rootDeviceHints:
-                description: Provide guidance about how to choose the device for the
-                  image being provisioned.
-                properties:
-                  deviceName:
-                    description: A Linux device name like "/dev/vda". The hint must
-                      match the actual value exactly.
-                    type: string
-                  hctl:
-                    description: A SCSI bus address like 0:0:0:0. The hint must match
-                      the actual value exactly.
-                    type: string
-                  minSizeGigabytes:
-                    description: The minimum size of the device in Gigabytes.
-                    minimum: 0
-                    type: integer
-                  model:
-                    description: A vendor-specific device identifier. The hint can
-                      be a substring of the actual value.
-                    type: string
-                  rotational:
-                    description: True if the device should use spinning media, false
-                      otherwise.
-                    type: boolean
-                  serialNumber:
-                    description: Device serial number. The hint must match the actual
-                      value exactly.
-                    type: string
-                  vendor:
-                    description: The name of the vendor or manufacturer of the device.
-                      The hint can be a substring of the actual value.
-                    type: string
-                  wwn:
-                    description: Unique storage identifier. The hint must match the
-                      actual value exactly.
-                    type: string
-                  wwnVendorExtension:
-                    description: Unique vendor storage identifier. The hint must match
-                      the actual value exactly.
-                    type: string
-                  wwnWithExtension:
-                    description: Unique storage identifier with the vendor extension
-                      appended. The hint must match the actual value exactly.
-                    type: string
+                - effect
+                - key
                 type: object
-              taints:
-                description: Taints is the full, authoritative list of taints to apply
-                  to the corresponding Machine. This list will overwrite any modifications
-                  made to the Machine on an ongoing basis.
-                items:
-                  description: The node this Taint is attached to has the "effect"
-                    on any pod that does not tolerate the Taint.
+              type: array
+            userData:
+              description: UserData holds the reference to the Secret containing the
+                user data to be passed to the host before it boots.
+              properties:
+                name:
+                  description: Name is unique within a namespace to reference a secret
+                    resource.
+                  type: string
+                namespace:
+                  description: Namespace defines the space within which the secret
+                    name must be unique.
+                  type: string
+              type: object
+          required:
+          - online
+          type: object
+        status:
+          description: BareMetalHostStatus defines the observed state of BareMetalHost
+          properties:
+            errorMessage:
+              description: the last error message reported by the provisioning subsystem
+              type: string
+            errorType:
+              description: ErrorType indicates the type of failure encountered when
+                the OperationalStatus is OperationalStatusError
+              enum:
+              - registration error
+              - inspection error
+              - provisioning error
+              - power management error
+              type: string
+            goodCredentials:
+              description: the last credentials we were able to validate as working
+              properties:
+                credentials:
+                  description: SecretReference represents a Secret Reference. It has
+                    enough information to retrieve secret in any namespace
                   properties:
-                    effect:
-                      description: Required. The effect of the taint on pods that
-                        do not tolerate the taint. Valid effects are NoSchedule, PreferNoSchedule
-                        and NoExecute.
+                    name:
+                      description: Name is unique within a namespace to reference
+                        a secret resource.
                       type: string
-                    key:
-                      description: Required. The taint key to be applied to a node.
+                    namespace:
+                      description: Namespace defines the space within which the secret
+                        name must be unique.
                       type: string
-                    timeAdded:
-                      description: TimeAdded represents the time at which the taint
-                        was added. It is only written for NoExecute taints.
-                      format: date-time
+                  type: object
+                credentialsVersion:
+                  type: string
+              type: object
+            hardware:
+              description: The hardware discovered to exist on the host.
+              properties:
+                cpu:
+                  description: CPU describes one processor on the host.
+                  properties:
+                    arch:
                       type: string
-                    value:
-                      description: Required. The taint value corresponding to the
-                        taint key.
+                    clockMegahertz:
+                      description: ClockSpeed is a clock speed in MHz
+                      type: number
+                    count:
+                      type: integer
+                    flags:
+                      items:
+                        type: string
+                      type: array
+                    model:
                       type: string
                   required:
-                  - effect
-                  - key
+                  - arch
+                  - clockMegahertz
+                  - count
+                  - flags
+                  - model
                   type: object
-                type: array
-              userData:
-                description: UserData holds the reference to the Secret containing
-                  the user data to be passed to the host before it boots.
-                properties:
-                  name:
-                    description: Name is unique within a namespace to reference a
-                      secret resource.
-                    type: string
-                  namespace:
-                    description: Namespace defines the space within which the secret
-                      name must be unique.
-                    type: string
-                type: object
-            required:
-            - online
-            type: object
-          status:
-            description: BareMetalHostStatus defines the observed state of BareMetalHost
-            properties:
-              errorMessage:
-                description: the last error message reported by the provisioning subsystem
-                type: string
-              errorType:
-                description: ErrorType indicates the type of failure encountered when
-                  the OperationalStatus is OperationalStatusError
-                enum:
-                - registration error
-                - inspection error
-                - provisioning error
-                - power management error
-                type: string
-              goodCredentials:
-                description: the last credentials we were able to validate as working
-                properties:
-                  credentials:
-                    description: SecretReference represents a Secret Reference. It
-                      has enough information to retrieve secret in any namespace
-                    properties:
-                      name:
-                        description: Name is unique within a namespace to reference
-                          a secret resource.
-                        type: string
-                      namespace:
-                        description: Namespace defines the space within which the
-                          secret name must be unique.
-                        type: string
-                    type: object
-                  credentialsVersion:
-                    type: string
-                type: object
-              hardware:
-                description: The hardware discovered to exist on the host.
-                properties:
-                  cpu:
-                    description: CPU describes one processor on the host.
-                    properties:
-                      arch:
-                        type: string
-                      clockMegahertz:
-                        description: ClockSpeed is a clock speed in MHz
-                        type: number
-                      count:
-                        type: integer
-                      flags:
-                        items:
-                          type: string
-                        type: array
-                      model:
-                        type: string
-                    required:
-                    - arch
-                    - clockMegahertz
-                    - count
-                    - flags
-                    - model
-                    type: object
-                  firmware:
-                    description: Firmware describes the firmware on the host.
-                    properties:
-                      bios:
-                        description: The BIOS for this firmware
-                        properties:
-                          date:
-                            description: The release/build date for this BIOS
-                            type: string
-                          vendor:
-                            description: The vendor name for this BIOS
-                            type: string
-                          version:
-                            description: The version of the BIOS
-                            type: string
-                        required:
-                        - date
-                        - vendor
-                        - version
-                        type: object
-                    required:
-                    - bios
-                    type: object
-                  hostname:
-                    type: string
-                  nics:
-                    items:
-                      description: NIC describes one network interface on the host.
-                      properties:
-                        ip:
-                          description: The IP address of the device
-                          type: string
-                        mac:
-                          description: The device MAC addr
-                          pattern: '[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}'
-                          type: string
-                        model:
-                          description: The name of the model, e.g. "virt-io"
-                          type: string
-                        name:
-                          description: The name of the NIC, e.g. "nic-1"
-                          type: string
-                        pxe:
-                          description: Whether the NIC is PXE Bootable
-                          type: boolean
-                        speedGbps:
-                          description: The speed of the device
-                          type: integer
-                        vlanId:
-                          description: The untagged VLAN ID
-                          format: int32
-                          maximum: 4094
-                          minimum: 0
-                          type: integer
-                        vlans:
-                          description: The VLANs available
-                          items:
-                            description: VLAN represents the name and ID of a VLAN
-                            properties:
-                              id:
-                                description: VLANID is a 12-bit 802.1Q VLAN identifier
-                                format: int32
-                                maximum: 4094
-                                minimum: 0
-                                type: integer
-                              name:
-                                type: string
-                            required:
-                            - id
-                            type: object
-                          type: array
-                      required:
-                      - ip
-                      - mac
-                      - model
-                      - name
-                      - pxe
-                      - speedGbps
-                      - vlanId
-                      type: object
-                    type: array
-                  ramMebibytes:
-                    type: integer
-                  storage:
-                    items:
-                      description: Storage describes one storage device (disk, SSD,
-                        etc.) on the host.
+                firmware:
+                  description: Firmware describes the firmware on the host.
+                  properties:
+                    bios:
+                      description: The BIOS for this firmware
                       properties:
-                        hctl:
-                          description: The SCSI location of the device
-                          type: string
-                        model:
-                          description: Hardware model
-                          type: string
-                        name:
-                          description: A name for the disk, e.g. "disk 1 (boot)"
+                        date:
+                          description: The release/build date for this BIOS
                           type: string
-                        rotational:
-                          description: Whether this disk represents rotational storage
-                          type: boolean
-                        serialNumber:
-                          description: The serial number of the device
-                          type: string
-                        sizeBytes:
-                          description: The size of the disk in Bytes
-                          format: int64
-                          type: integer
                         vendor:
-                          description: The name of the vendor of the device
-                          type: string
-                        wwn:
-                          description: The WWN of the device
-                          type: string
-                        wwnVendorExtension:
-                          description: The WWN Vendor extension of the device
+                          description: The vendor name for this BIOS
                           type: string
-                        wwnWithExtension:
-                          description: The WWN with the extension
+                        version:
+                          description: The version of the BIOS
                           type: string
                       required:
-                      - name
-                      - rotational
-                      - serialNumber
-                      - sizeBytes
+                      - date
+                      - vendor
+                      - version
                       type: object
-                    type: array
-                  systemVendor:
-                    description: HardwareSystemVendor stores details about the whole
-                      hardware system.
-                    properties:
-                      manufacturer:
-                        type: string
-                      productName:
-                        type: string
-                      serialNumber:
-                        type: string
-                    required:
-                    - manufacturer
-                    - productName
-                    - serialNumber
-                    type: object
-                required:
-                - cpu
-                - firmware
-                - hostname
-                - nics
-                - ramMebibytes
-                - storage
-                - systemVendor
-                type: object
-              hardwareProfile:
-                description: The name of the profile matching the hardware details.
-                type: string
-              lastUpdated:
-                description: LastUpdated identifies when this status was last observed.
-                format: date-time
-                type: string
-              operationHistory:
-                description: OperationHistory holds information about operations performed
-                  on this host.
-                properties:
-                  deprovision:
-                    description: OperationMetric contains metadata about an operation
-                      (inspection, provisioning, etc.) used for tracking metrics.
-                    properties:
-                      end:
-                        format: date-time
-                        nullable: true
-                        type: string
-                      start:
-                        format: date-time
-                        nullable: true
-                        type: string
-                    type: object
-                  inspect:
-                    description: OperationMetric contains metadata about an operation
-                      (inspection, provisioning, etc.) used for tracking metrics.
-                    properties:
-                      end:
-                        format: date-time
-                        nullable: true
-                        type: string
-                      start:
-                        format: date-time
-                        nullable: true
-                        type: string
-                    type: object
-                  provision:
-                    description: OperationMetric contains metadata about an operation
-                      (inspection, provisioning, etc.) used for tracking metrics.
-                    properties:
-                      end:
-                        format: date-time
-                        nullable: true
-                        type: string
-                      start:
-                        format: date-time
-                        nullable: true
-                        type: string
-                    type: object
-                  register:
-                    description: OperationMetric contains metadata about an operation
-                      (inspection, provisioning, etc.) used for tracking metrics.
-                    properties:
-                      end:
-                        format: date-time
-                        nullable: true
-                        type: string
-                      start:
-                        format: date-time
-                        nullable: true
-                        type: string
-                    type: object
-                type: object
-              operationalStatus:
-                description: OperationalStatus holds the status of the host
-                enum:
-                - ""
-                - OK
-                - discovered
-                - error
-                type: string
-              poweredOn:
-                description: indicator for whether or not the host is powered on
-                type: boolean
-              provisioning:
-                description: Information tracked by the provisioner.
-                properties:
-                  ID:
-                    description: The machine's UUID from the underlying provisioning
-                      tool
-                    type: string
-                  bootMode:
-                    description: BootMode indicates the boot mode used to provision
-                      the node
-                    enum:
-                    - UEFI
-                    - legacy
-                    type: string
-                  image:
-                    description: Image holds the details of the last image successfully
-                      provisioned to the host.
+                  required:
+                  - bios
+                  type: object
+                hostname:
+                  type: string
+                nics:
+                  items:
+                    description: NIC describes one network interface on the host.
                     properties:
-                      checksum:
-                        description: Checksum is the checksum for the image.
+                      ip:
+                        description: The IP address of the device
                         type: string
-                      checksumType:
-                        description: ChecksumType is the checksum algorithm for the
-                          image. e.g md5, sha256, sha512
-                        enum:
-                        - md5
-                        - sha256
-                        - sha512
+                      mac:
+                        description: The device MAC addr
+                        pattern: '[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}'
                         type: string
-                      format:
-                        description: DiskFormat contains the format of the image (raw,
-                          qcow2, ...) Needs to be set to raw for raw images streaming
-                        enum:
-                        - raw
-                        - qcow2
-                        - vdi
-                        - vmdk
+                      model:
+                        description: The name of the model, e.g. "virt-io"
                         type: string
-                      url:
-                        description: URL is a location of an image to deploy.
+                      name:
+                        description: The name of the NIC, e.g. "nic-1"
                         type: string
+                      pxe:
+                        description: Whether the NIC is PXE Bootable
+                        type: boolean
+                      speedGbps:
+                        description: The speed of the device
+                        type: integer
+                      vlanId:
+                        description: The untagged VLAN ID
+                        format: int32
+                        maximum: 4094
+                        minimum: 0
+                        type: integer
+                      vlans:
+                        description: The VLANs available
+                        items:
+                          description: VLAN represents the name and ID of a VLAN
+                          properties:
+                            id:
+                              description: VLANID is a 12-bit 802.1Q VLAN identifier
+                              format: int32
+                              maximum: 4094
+                              minimum: 0
+                              type: integer
+                            name:
+                              type: string
+                          required:
+                          - id
+                          type: object
+                        type: array
                     required:
-                    - checksum
-                    - url
+                    - ip
+                    - mac
+                    - model
+                    - name
+                    - pxe
+                    - speedGbps
+                    - vlanId
                     type: object
-                  rootDeviceHints:
-                    description: The RootDevicehints set by the user
+                  type: array
+                ramMebibytes:
+                  type: integer
+                storage:
+                  items:
+                    description: Storage describes one storage device (disk, SSD,
+                      etc.) on the host.
                     properties:
-                      deviceName:
-                        description: A Linux device name like "/dev/vda". The hint
-                          must match the actual value exactly.
-                        type: string
                       hctl:
-                        description: A SCSI bus address like 0:0:0:0. The hint must
-                          match the actual value exactly.
+                        description: The SCSI location of the device
                         type: string
-                      minSizeGigabytes:
-                        description: The minimum size of the device in Gigabytes.
-                        minimum: 0
-                        type: integer
                       model:
-                        description: A vendor-specific device identifier. The hint
-                          can be a substring of the actual value.
+                        description: Hardware model
+                        type: string
+                      name:
+                        description: A name for the disk, e.g. "disk 1 (boot)"
                         type: string
                       rotational:
-                        description: True if the device should use spinning media,
-                          false otherwise.
+                        description: Whether this disk represents rotational storage
                         type: boolean
                       serialNumber:
-                        description: Device serial number. The hint must match the
-                          actual value exactly.
+                        description: The serial number of the device
                         type: string
+                      sizeBytes:
+                        description: The size of the disk in Bytes
+                        format: int64
+                        type: integer
                       vendor:
-                        description: The name of the vendor or manufacturer of the
-                          device. The hint can be a substring of the actual value.
+                        description: The name of the vendor of the device
                         type: string
                       wwn:
-                        description: Unique storage identifier. The hint must match
-                          the actual value exactly.
+                        description: The WWN of the device
                         type: string
                       wwnVendorExtension:
-                        description: Unique vendor storage identifier. The hint must
-                          match the actual value exactly.
+                        description: The WWN Vendor extension of the device
                         type: string
                       wwnWithExtension:
-                        description: Unique storage identifier with the vendor extension
-                          appended. The hint must match the actual value exactly.
-                        type: string
-                    type: object
-                  state:
-                    description: An indiciator for what the provisioner is doing with
-                      the host.
-                    type: string
-                required:
-                - ID
-                - state
-                type: object
-              triedCredentials:
-                description: the last credentials we sent to the provisioning backend
-                properties:
-                  credentials:
-                    description: SecretReference represents a Secret Reference. It
-                      has enough information to retrieve secret in any namespace
-                    properties:
-                      name:
-                        description: Name is unique within a namespace to reference
-                          a secret resource.
-                        type: string
-                      namespace:
-                        description: Namespace defines the space within which the
-                          secret name must be unique.
+                        description: The WWN with the extension
                         type: string
+                    required:
+                    - name
+                    - rotational
+                    - serialNumber
+                    - sizeBytes
                     type: object
-                  credentialsVersion:
-                    type: string
-                type: object
-            required:
-            - errorMessage
-            - hardwareProfile
-            - operationHistory
-            - operationalStatus
-            - poweredOn
-            - provisioning
-            type: object
-        type: object
+                  type: array
+                systemVendor:
+                  description: HardwareSystemVendor stores details about the whole
+                    hardware system.
+                  properties:
+                    manufacturer:
+                      type: string
+                    productName:
+                      type: string
+                    serialNumber:
+                      type: string
+                  required:
+                  - manufacturer
+                  - productName
+                  - serialNumber
+                  type: object
+              required:
+              - cpu
+              - firmware
+              - hostname
+              - nics
+              - ramMebibytes
+              - storage
+              - systemVendor
+              type: object
+            hardwareProfile:
+              description: The name of the profile matching the hardware details.
+              type: string
+            lastUpdated:
+              description: LastUpdated identifies when this status was last observed.
+              format: date-time
+              type: string
+            operationHistory:
+              description: OperationHistory holds information about operations performed
+                on this host.
+              properties:
+                deprovision:
+                  description: OperationMetric contains metadata about an operation
+                    (inspection, provisioning, etc.) used for tracking metrics.
+                  properties:
+                    end:
+                      format: date-time
+                      nullable: true
+                      type: string
+                    start:
+                      format: date-time
+                      nullable: true
+                      type: string
+                  type: object
+                inspect:
+                  description: OperationMetric contains metadata about an operation
+                    (inspection, provisioning, etc.) used for tracking metrics.
+                  properties:
+                    end:
+                      format: date-time
+                      nullable: true
+                      type: string
+                    start:
+                      format: date-time
+                      nullable: true
+                      type: string
+                  type: object
+                provision:
+                  description: OperationMetric contains metadata about an operation
+                    (inspection, provisioning, etc.) used for tracking metrics.
+                  properties:
+                    end:
+                      format: date-time
+                      nullable: true
+                      type: string
+                    start:
+                      format: date-time
+                      nullable: true
+                      type: string
+                  type: object
+                register:
+                  description: OperationMetric contains metadata about an operation
+                    (inspection, provisioning, etc.) used for tracking metrics.
+                  properties:
+                    end:
+                      format: date-time
+                      nullable: true
+                      type: string
+                    start:
+                      format: date-time
+                      nullable: true
+                      type: string
+                  type: object
+              type: object
+            operationalStatus:
+              description: OperationalStatus holds the status of the host
+              enum:
+              - ""
+              - OK
+              - discovered
+              - error
+              type: string
+            poweredOn:
+              description: indicator for whether or not the host is powered on
+              type: boolean
+            provisioning:
+              description: Information tracked by the provisioner.
+              properties:
+                ID:
+                  description: The machine's UUID from the underlying provisioning
+                    tool
+                  type: string
+                bootMode:
+                  description: BootMode indicates the boot mode used to provision
+                    the node
+                  enum:
+                  - UEFI
+                  - legacy
+                  type: string
+                image:
+                  description: Image holds the details of the last image successfully
+                    provisioned to the host.
+                  properties:
+                    checksum:
+                      description: Checksum is the checksum for the image.
+                      type: string
+                    checksumType:
+                      description: ChecksumType is the checksum algorithm for the
+                        image. e.g md5, sha256, sha512
+                      enum:
+                      - md5
+                      - sha256
+                      - sha512
+                      type: string
+                    format:
+                      description: DiskFormat contains the format of the image (raw,
+                        qcow2, ...) Needs to be set to raw for raw images streaming
+                      enum:
+                      - raw
+                      - qcow2
+                      - vdi
+                      - vmdk
+                      type: string
+                    url:
+                      description: URL is a location of an image to deploy.
+                      type: string
+                  required:
+                  - checksum
+                  - url
+                  type: object
+                rootDeviceHints:
+                  description: The RootDevicehints set by the user
+                  properties:
+                    deviceName:
+                      description: A Linux device name like "/dev/vda". The hint must
+                        match the actual value exactly.
+                      type: string
+                    hctl:
+                      description: A SCSI bus address like 0:0:0:0. The hint must
+                        match the actual value exactly.
+                      type: string
+                    minSizeGigabytes:
+                      description: The minimum size of the device in Gigabytes.
+                      minimum: 0
+                      type: integer
+                    model:
+                      description: A vendor-specific device identifier. The hint can
+                        be a substring of the actual value.
+                      type: string
+                    rotational:
+                      description: True if the device should use spinning media, false
+                        otherwise.
+                      type: boolean
+                    serialNumber:
+                      description: Device serial number. The hint must match the actual
+                        value exactly.
+                      type: string
+                    vendor:
+                      description: The name of the vendor or manufacturer of the device.
+                        The hint can be a substring of the actual value.
+                      type: string
+                    wwn:
+                      description: Unique storage identifier. The hint must match
+                        the actual value exactly.
+                      type: string
+                    wwnVendorExtension:
+                      description: Unique vendor storage identifier. The hint must
+                        match the actual value exactly.
+                      type: string
+                    wwnWithExtension:
+                      description: Unique storage identifier with the vendor extension
+                        appended. The hint must match the actual value exactly.
+                      type: string
+                  type: object
+                state:
+                  description: An indiciator for what the provisioner is doing with
+                    the host.
+                  type: string
+              required:
+              - ID
+              - state
+              type: object
+            triedCredentials:
+              description: the last credentials we sent to the provisioning backend
+              properties:
+                credentials:
+                  description: SecretReference represents a Secret Reference. It has
+                    enough information to retrieve secret in any namespace
+                  properties:
+                    name:
+                      description: Name is unique within a namespace to reference
+                        a secret resource.
+                      type: string
+                    namespace:
+                      description: Namespace defines the space within which the secret
+                        name must be unique.
+                      type: string
+                  type: object
+                credentialsVersion:
+                  type: string
+              type: object
+          required:
+          - errorMessage
+          - hardwareProfile
+          - operationHistory
+          - operationalStatus
+          - poweredOn
+          - provisioning
+          type: object
+      type: object
+  version: v1alpha1
+  versions:
+  - name: v1alpha1
     served: true
     storage: true
-    subresources:
-      status: {}
diff --git a/docs/ironic-authentication.md b/docs/ironic-authentication.md
new file mode 100644
index 00000000..f7eb6338
--- /dev/null
+++ b/docs/ironic-authentication.md
@@ -0,0 +1,37 @@
+# Authenticating to Ironic
+
+Because hosts under the control of Metal³ need to contact the Ironic and Ironic
+Inspector APIs during inspection and provisioning, it is highly advisable to
+require authentication on those APIs, since the provisioned hosts running user
+workloads will remain connected to the provisioning network.
+
+## Configuration
+
+The `baremetal-operator` supports connecting to Ironic and Ironic Inspector
+configured with the following `auth_strategy` modes:
+
+* `noauth` (no authentication)
+* `http_basic` (HTTP [Basic access authentication](https://en.wikipedia.org/wiki/Basic_access_authentication))
+
+Note that Keystone authentication methods are not yet supported.
+
+Authentication configuration is read from the filesystem, beginning at the root
+directory specified in the environment variable `METAL3_AUTH_ROOT_DIR`. If this
+variable is empty or not specified, the default is `/opt/metal3/auth`.
+
+Within the root directory there are separate subdirectories, `ironic` for
+Ironic client configuration, and `ironic-inspector` for Ironic Inspector client
+configuration. (This allows the data to be populated from separate secrets when
+deploying in Kubernetes.)
+
+### `noauth`
+
+This is the default, and will be chosen if the auth root directory does not
+exist. In this mode, the baremetal-operator does not attempt to do any
+authentication against the Ironic APIs.
+
+### `http_basic`
+
+This mode is configured by files in each authentication subdirectory named
+`username` and `password`, and containing the Basic auth username and password,
+respectively.
diff --git a/go.mod b/go.mod
index e8d69d5b..90106fa8 100644
--- a/go.mod
+++ b/go.mod
@@ -4,11 +4,11 @@ go 1.14
 
 require (
 	github.com/go-logr/logr v0.1.0
-	github.com/go-openapi/spec v0.19.9
+	github.com/go-openapi/spec v0.19.4
 	github.com/gobuffalo/envy v1.7.1 // indirect
 	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
 	github.com/google/gofuzz v1.1.0 // indirect
-	github.com/gophercloud/gophercloud v0.6.0
+	github.com/gophercloud/gophercloud v0.12.0
 	github.com/hashicorp/golang-lru v0.5.4 // indirect
 	github.com/imdario/mergo v0.3.8 // indirect
 	github.com/onsi/ginkgo v1.12.0 // indirect
diff --git a/go.sum b/go.sum
index 2b09f1c9..6c133d53 100644
--- a/go.sum
+++ b/go.sum
@@ -284,8 +284,6 @@ github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3Hfo
 github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
 github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o=
 github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
-github.com/go-openapi/jsonreference v0.19.4 h1:3Vw+rh13uq2JFNxgnMTGE1rnoieU9FmyE1gvnyylsYg=
-github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
 github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
 github.com/go-openapi/loads v0.17.2/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
 github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
@@ -304,8 +302,6 @@ github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcs
 github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
 github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
 github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
-github.com/go-openapi/spec v0.19.9 h1:9z9cbFuZJ7AcvOHKIY+f6Aevb4vObNDkTEyoMfO7rAc=
-github.com/go-openapi/spec v0.19.9/go.mod h1:vqK/dIdLGCosfvYsQV3WfC7N3TiZSnGY2RZKoFK7X28=
 github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
 github.com/go-openapi/strfmt v0.17.2/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
 github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
@@ -410,6 +406,8 @@ github.com/gophercloud/gophercloud v0.2.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEo
 github.com/gophercloud/gophercloud v0.3.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
 github.com/gophercloud/gophercloud v0.6.0 h1:Xb2lcqZtml1XjgYZxbeayEemq7ASbeTp09m36gQFpEU=
 github.com/gophercloud/gophercloud v0.6.0/go.mod h1:GICNByuaEBibcjmjvI7QvYJSZEbGkcYwAR7EZK2WMqM=
+github.com/gophercloud/gophercloud v0.12.0 h1:mZrie07npp6ODiwHZolTicr5jV8Ogn43AvAsSMm6Ork=
+github.com/gophercloud/gophercloud v0.12.0/go.mod h1:gmC5oQqMDOMO1t1gq5DquX/yAU808e/4mzjjDA76+Ss=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
@@ -897,6 +895,7 @@ golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
@@ -963,6 +962,7 @@ golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
 golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -1023,6 +1023,7 @@ golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e h1:N7DeIrjYszNmSW409R3frPPwglRwMkXSBzwVbkOjLLA=
 golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU=
@@ -1034,8 +1035,6 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
 golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1080,6 +1079,7 @@ golang.org/x/tools v0.0.0-20191111182352-50fa39b762bc/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191203134012-c197fd4bf371/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
 golang.org/x/tools v0.0.0-20200327195553-82bb89366a1e/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
 golang.org/x/tools v0.0.0-20200430192856-2840dafb9ee1 h1:OlmCHPqCyX+EpIpxG55cfMJuINAFd7HHTdWwA3yyelQ=
diff --git a/pkg/apis/metal3/v1alpha1/baremetalhost_types.go b/pkg/apis/metal3/v1alpha1/baremetalhost_types.go
index 1ad1b84f..cc9e2bca 100644
--- a/pkg/apis/metal3/v1alpha1/baremetalhost_types.go
+++ b/pkg/apis/metal3/v1alpha1/baremetalhost_types.go
@@ -79,9 +79,8 @@ type BootMode string
 
 // Allowed boot mode from metal3
 const (
-	UEFI            BootMode = "UEFI"
-	Legacy          BootMode = "legacy"
-	DefaultBootMode BootMode = UEFI
+	UEFI   BootMode = "UEFI"
+	Legacy BootMode = "legacy"
 )
 
 // OperationalStatus represents the state of the host
@@ -227,8 +226,8 @@ type BareMetalHostSpec struct {
 	// being provisioned.
 	RootDeviceHints *RootDeviceHints `json:"rootDeviceHints,omitempty"`
 
-	// Select the method of initializing the hardware during
-	// boot. Defaults to UEFI.
+	// Select the method of initializing the hardware during boot to
+	// override the value based on the BMC driver.
 	// +optional
 	BootMode BootMode `json:"bootMode,omitempty"`
 
diff --git a/pkg/bmc/idrac_virtualmedia.go b/pkg/bmc/idrac_virtualmedia.go
deleted file mode 100644
index bf06ae2e..00000000
--- a/pkg/bmc/idrac_virtualmedia.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package bmc
-
-import (
-	"net/url"
-)
-
-func init() {
-	schemes := []string{"http", "https"}
-	registerFactory("idrac-virtualmedia", newRedfishiDracVirtualMediaAccessDetails, schemes)
-}
-
-func newRedfishiDracVirtualMediaAccessDetails(parsedURL *url.URL, disableCertificateVerification bool) (AccessDetails, error) {
-	return &redfishiDracVirtualMediaAccessDetails{
-		bmcType:                        parsedURL.Scheme,
-		host:                           parsedURL.Host,
-		path:                           parsedURL.Path,
-		disableCertificateVerification: disableCertificateVerification,
-	}, nil
-}
-
-type redfishiDracVirtualMediaAccessDetails struct {
-	bmcType                        string
-	host                           string
-	path                           string
-	disableCertificateVerification bool
-}
-
-func (a *redfishiDracVirtualMediaAccessDetails) Type() string {
-	return a.bmcType
-}
-
-// NeedsMAC returns true when the host is going to need a separate
-// port created rather than having it discovered.
-func (a *redfishiDracVirtualMediaAccessDetails) NeedsMAC() bool {
-	// For the inspection to work, we need a MAC address
-	// https://github.com/metal3-io/baremetal-operator/pull/284#discussion_r317579040
-	return true
-}
-
-func (a *redfishiDracVirtualMediaAccessDetails) DisableCertificateVerification() bool {
-	return a.disableCertificateVerification
-}
-
-// DriverInfo returns a data structure to pass as the DriverInfo
-// parameter when creating a node in Ironic. The structure is
-// pre-populated with the access information, and the caller is
-// expected to add any other information that might be needed (such as
-// the kernel and ramdisk locations).
-func (a *redfishiDracVirtualMediaAccessDetails) DriverInfo(bmcCreds Credentials) map[string]interface{} {
-	result := map[string]interface{}{
-		"redfish_system_id": a.path,
-		"redfish_username":  bmcCreds.Username,
-		"redfish_password":  bmcCreds.Password,
-		"redfish_address":   getRedfishAddress(a.bmcType, a.host),
-	}
-
-	if a.disableCertificateVerification {
-		result["redfish_verify_ca"] = false
-	}
-
-	return result
-}
-
-// iDrac Virtual Media Overrides
-
-func (a *redfishiDracVirtualMediaAccessDetails) Driver() string {
-	return "idrac"
-}
-
-func (a *redfishiDracVirtualMediaAccessDetails) BootInterface() string {
-	return "idrac-redfish-virtual-media"
-}
-
-func (a *redfishiDracVirtualMediaAccessDetails) ManagementInterface() string {
-	return "idrac-redfish"
-}
-
-func (a *redfishiDracVirtualMediaAccessDetails) PowerInterface() string {
-	return "idrac-redfish"
-}
-
-func (a *redfishiDracVirtualMediaAccessDetails) RAIDInterface() string {
-	return "no-raid"
-}
-
-func (a *redfishiDracVirtualMediaAccessDetails) VendorInterface() string {
-	return "no-vendor"
-}
diff --git a/pkg/bmc/redfish.go b/pkg/bmc/redfish.go
index fe624de9..37d33e4d 100644
--- a/pkg/bmc/redfish.go
+++ b/pkg/bmc/redfish.go
@@ -8,7 +8,10 @@ import (
 func init() {
 	schemes := []string{"http", "https"}
 	registerFactory("redfish", newRedfishAccessDetails, schemes)
+	registerFactory("redfish-virtualmedia", newRedfishVirtualMediaAccessDetails, schemes)
 	registerFactory("ilo5-redfish", newRedfishAccessDetails, schemes)
+	registerFactory("ilo5-virtualmedia", newRedfishVirtualMediaAccessDetails, schemes)
+	registerFactory("idrac-virtualmedia", newRedfishiDracVirtualMediaAccessDetails, schemes)
 }
 
 func redfishDetails(parsedURL *url.URL, disableCertificateVerification bool) *redfishAccessDetails {
@@ -24,6 +27,18 @@ func newRedfishAccessDetails(parsedURL *url.URL, disableCertificateVerification
 	return redfishDetails(parsedURL, disableCertificateVerification), nil
 }
 
+func newRedfishVirtualMediaAccessDetails(parsedURL *url.URL, disableCertificateVerification bool) (AccessDetails, error) {
+	return &redfishVirtualMediaAccessDetails{
+		*redfishDetails(parsedURL, disableCertificateVerification),
+	}, nil
+}
+
+func newRedfishiDracVirtualMediaAccessDetails(parsedURL *url.URL, disableCertificateVerification bool) (AccessDetails, error) {
+	return &redfishiDracVirtualMediaAccessDetails{
+		*redfishDetails(parsedURL, disableCertificateVerification),
+	}, nil
+}
+
 type redfishAccessDetails struct {
 	bmcType                        string
 	host                           string
@@ -31,6 +46,14 @@ type redfishAccessDetails struct {
 	disableCertificateVerification bool
 }
 
+type redfishVirtualMediaAccessDetails struct {
+	redfishAccessDetails
+}
+
+type redfishiDracVirtualMediaAccessDetails struct {
+	redfishAccessDetails
+}
+
 const redfishDefaultScheme = "https"
 
 func (a *redfishAccessDetails) Type() string {
@@ -53,30 +76,27 @@ func (a *redfishAccessDetails) DisableCertificateVerification() bool {
 	return a.disableCertificateVerification
 }
 
-func getRedfishAddress(bmcType, host string) string {
+// DriverInfo returns a data structure to pass as the DriverInfo
+// parameter when creating a node in Ironic. The structure is
+// pre-populated with the access information, and the caller is
+// expected to add any other information that might be needed (such as
+// the kernel and ramdisk locations).
+func (a *redfishAccessDetails) DriverInfo(bmcCreds Credentials) map[string]interface{} {
 	redfishAddress := []string{}
-	schemes := strings.Split(bmcType, "+")
+	schemes := strings.Split(a.bmcType, "+")
 	if len(schemes) > 1 {
 		redfishAddress = append(redfishAddress, schemes[1])
 	} else {
 		redfishAddress = append(redfishAddress, redfishDefaultScheme)
 	}
 	redfishAddress = append(redfishAddress, "://")
-	redfishAddress = append(redfishAddress, host)
-	return strings.Join(redfishAddress, "")
-}
+	redfishAddress = append(redfishAddress, a.host)
 
-// DriverInfo returns a data structure to pass as the DriverInfo
-// parameter when creating a node in Ironic. The structure is
-// pre-populated with the access information, and the caller is
-// expected to add any other information that might be needed (such as
-// the kernel and ramdisk locations).
-func (a *redfishAccessDetails) DriverInfo(bmcCreds Credentials) map[string]interface{} {
 	result := map[string]interface{}{
 		"redfish_system_id": a.path,
 		"redfish_username":  bmcCreds.Username,
 		"redfish_password":  bmcCreds.Password,
-		"redfish_address":   getRedfishAddress(a.bmcType, a.host),
+		"redfish_address":   strings.Join(redfishAddress, ""),
 	}
 
 	if a.disableCertificateVerification {
@@ -106,3 +126,35 @@ func (a *redfishAccessDetails) RAIDInterface() string {
 func (a *redfishAccessDetails) VendorInterface() string {
 	return ""
 }
+
+// Virtual Media Overrides
+
+func (a *redfishVirtualMediaAccessDetails) BootInterface() string {
+	return "redfish-virtual-media"
+}
+
+// iDrac Virtual Media Overrides
+
+func (a *redfishiDracVirtualMediaAccessDetails) Driver() string {
+	return "idrac"
+}
+
+func (a *redfishiDracVirtualMediaAccessDetails) BootInterface() string {
+	return "idrac-redfish-virtual-media"
+}
+
+func (a *redfishiDracVirtualMediaAccessDetails) ManagementInterface() string {
+	return "idrac-redfish"
+}
+
+func (a *redfishiDracVirtualMediaAccessDetails) PowerInterface() string {
+	return "idrac-redfish"
+}
+
+func (a *redfishiDracVirtualMediaAccessDetails) RAIDInterface() string {
+	return "no-raid"
+}
+
+func (a *redfishiDracVirtualMediaAccessDetails) VendorInterface() string {
+	return "no-vendor"
+}
diff --git a/pkg/bmc/redfish_virtualmedia.go b/pkg/bmc/redfish_virtualmedia.go
deleted file mode 100644
index 1339afb9..00000000
--- a/pkg/bmc/redfish_virtualmedia.go
+++ /dev/null
@@ -1,87 +0,0 @@
-package bmc
-
-import (
-	"net/url"
-)
-
-func init() {
-	schemes := []string{"http", "https"}
-	registerFactory("redfish-virtualmedia", newRedfishVirtualMediaAccessDetails, schemes)
-	registerFactory("ilo5-virtualmedia", newRedfishVirtualMediaAccessDetails, schemes)
-}
-
-func newRedfishVirtualMediaAccessDetails(parsedURL *url.URL, disableCertificateVerification bool) (AccessDetails, error) {
-	return &redfishVirtualMediaAccessDetails{
-		bmcType:                        parsedURL.Scheme,
-		host:                           parsedURL.Host,
-		path:                           parsedURL.Path,
-		disableCertificateVerification: disableCertificateVerification,
-	}, nil
-}
-
-type redfishVirtualMediaAccessDetails struct {
-	bmcType                        string
-	host                           string
-	path                           string
-	disableCertificateVerification bool
-}
-
-func (a *redfishVirtualMediaAccessDetails) Type() string {
-	return a.bmcType
-}
-
-// NeedsMAC returns true when the host is going to need a separate
-// port created rather than having it discovered.
-func (a *redfishVirtualMediaAccessDetails) NeedsMAC() bool {
-	// For the inspection to work, we need a MAC address
-	// https://github.com/metal3-io/baremetal-operator/pull/284#discussion_r317579040
-	return true
-}
-
-func (a *redfishVirtualMediaAccessDetails) Driver() string {
-	return "redfish"
-}
-
-func (a *redfishVirtualMediaAccessDetails) DisableCertificateVerification() bool {
-	return a.disableCertificateVerification
-}
-
-// DriverInfo returns a data structure to pass as the DriverInfo
-// parameter when creating a node in Ironic. The structure is
-// pre-populated with the access information, and the caller is
-// expected to add any other information that might be needed (such as
-// the kernel and ramdisk locations).
-func (a *redfishVirtualMediaAccessDetails) DriverInfo(bmcCreds Credentials) map[string]interface{} {
-	result := map[string]interface{}{
-		"redfish_system_id": a.path,
-		"redfish_username":  bmcCreds.Username,
-		"redfish_password":  bmcCreds.Password,
-		"redfish_address":   getRedfishAddress(a.bmcType, a.host),
-	}
-
-	if a.disableCertificateVerification {
-		result["redfish_verify_ca"] = false
-	}
-
-	return result
-}
-
-func (a *redfishVirtualMediaAccessDetails) BootInterface() string {
-	return "redfish-virtual-media"
-}
-
-func (a *redfishVirtualMediaAccessDetails) ManagementInterface() string {
-	return ""
-}
-
-func (a *redfishVirtualMediaAccessDetails) PowerInterface() string {
-	return ""
-}
-
-func (a *redfishVirtualMediaAccessDetails) RAIDInterface() string {
-	return ""
-}
-
-func (a *redfishVirtualMediaAccessDetails) VendorInterface() string {
-	return ""
-}
diff --git a/pkg/controller/baremetalhost/baremetalhost_controller.go b/pkg/controller/baremetalhost/baremetalhost_controller.go
index 4b9630b9..4b23b87f 100644
--- a/pkg/controller/baremetalhost/baremetalhost_controller.go
+++ b/pkg/controller/baremetalhost/baremetalhost_controller.go
@@ -40,10 +40,9 @@ import (
 )
 
 const (
-	hostErrorRetryDelay           = time.Second * 10
-	unmanagedRetryDelay           = time.Minute * 10
-	provisionerNotReadyRetryDelay = time.Second * 30
-	rebootAnnotationPrefix        = "reboot.metal3.io"
+	hostErrorRetryDelay    = time.Second * 10
+	unmanagedRetryDelay    = time.Minute * 10
+	rebootAnnotationPrefix = "reboot.metal3.io"
 )
 
 var runInTestMode bool
@@ -289,16 +288,6 @@ func (r *ReconcileBareMetalHost) Reconcile(request reconcile.Request) (result re
 		return reconcile.Result{}, errors.Wrap(err, "failed to create provisioner")
 	}
 
-	ready, err := prov.IsReady()
-
-	if err != nil {
-		return reconcile.Result{}, errors.Wrap(err, "failed to check services availability")
-	}
-	if !ready {
-		reqLogger.Info("provisioner is not ready", "RequeueAfter:", provisionerNotReadyRetryDelay)
-		return reconcile.Result{Requeue: true, RequeueAfter: provisionerNotReadyRetryDelay}, nil
-	}
-
 	stateMachine := newHostStateMachine(host, r, prov)
 	actResult := stateMachine.ReconcileState(info)
 	result, err = actResult.Result()
@@ -809,7 +798,7 @@ func (r *ReconcileBareMetalHost) actionManageReady(prov provisioner.Provisioner,
 	}
 
 	if info.host.NeedsProvisioning() {
-		// Ensure the provisioning settings we're going to use are stored.
+		// Ensure the root device hints we're going to use are stored.
 		dirty, err := saveHostProvisioningSettings(info.host)
 		if err != nil {
 			return actionError{errors.Wrap(err, "Could not save the host provisioning settings")}
@@ -844,18 +833,6 @@ func saveHostProvisioningSettings(host *metal3v1alpha1.BareMetalHost) (dirty boo
 		host.Status.Provisioning.RootDeviceHints = hintSource
 		dirty = true
 	}
-
-	// Make sure we have saved the current boot mode value, using a
-	// default if the spec does not include a value.
-	bootMode := host.Spec.BootMode
-	if bootMode == "" {
-		bootMode = metal3v1alpha1.DefaultBootMode
-	}
-	if bootMode != host.Status.Provisioning.BootMode {
-		host.Status.Provisioning.BootMode = bootMode
-		dirty = true
-	}
-
 	return
 }
 
diff --git a/pkg/provisioner/demo/demo.go b/pkg/provisioner/demo/demo.go
index 1374a724..37fe5c9f 100644
--- a/pkg/provisioner/demo/demo.go
+++ b/pkg/provisioner/demo/demo.go
@@ -303,8 +303,3 @@ func (p *demoProvisioner) PowerOff() (result provisioner.Result, err error) {
 
 	// return result, nil
 }
-
-// IsReady always returns true for the demo provisioner
-func (p *demoProvisioner) IsReady() (result bool, err error) {
-	return true, nil
-}
diff --git a/pkg/provisioner/fixture/fixture.go b/pkg/provisioner/fixture/fixture.go
index 05093972..4eb330d8 100644
--- a/pkg/provisioner/fixture/fixture.go
+++ b/pkg/provisioner/fixture/fixture.go
@@ -15,7 +15,7 @@ var log = logf.Log.WithName("fixture")
 var deprovisionRequeueDelay = time.Second * 10
 var provisionRequeueDelay = time.Second * 10
 
-// fixtureProvisioner implements the provisioning.fixtureProvisioner interface
+// Provisioner implements the provisioning.Provisioner interface
 // and uses Ironic to manage the host.
 type fixtureProvisioner struct {
 	// the host to be managed by this provisioner
@@ -28,23 +28,15 @@ type fixtureProvisioner struct {
 	publisher provisioner.EventPublisher
 	// state to manage the two-step adopt process
 	adopted bool
-	// counter to set the provisioner as ready
-	becomeReadyCounter int
 }
 
-// New returns a new Ironic FixtureProvisioner
+// New returns a new Ironic Provisioner
 func New(host *metal3v1alpha1.BareMetalHost, bmcCreds bmc.Credentials, publisher provisioner.EventPublisher) (provisioner.Provisioner, error) {
-	return NewMock(host, bmcCreds, publisher, 0)
-}
-
-// NewMock is used in tests to build a fixture provisioner and inject additional test parameters
-func NewMock(host *metal3v1alpha1.BareMetalHost, bmcCreds bmc.Credentials, publisher provisioner.EventPublisher, becomeReadyCounter int) (provisioner.Provisioner, error) {
 	p := &fixtureProvisioner{
-		host:               host,
-		bmcCreds:           bmcCreds,
-		log:                log.WithValues("host", host.Name),
-		publisher:          publisher,
-		becomeReadyCounter: becomeReadyCounter,
+		host:      host,
+		bmcCreds:  bmcCreds,
+		log:       log.WithValues("host", host.Name),
+		publisher: publisher,
 	}
 	return p, nil
 }
@@ -244,14 +236,3 @@ func (p *fixtureProvisioner) PowerOff() (result provisioner.Result, err error) {
 
 	return result, nil
 }
-
-// IsReady returns the current availability status of the provisioner
-func (p *fixtureProvisioner) IsReady() (result bool, err error) {
-	p.log.Info("checking provisioner status")
-
-	if p.becomeReadyCounter > 0 {
-		p.becomeReadyCounter--
-	}
-
-	return p.becomeReadyCounter == 0, nil
-}
diff --git a/pkg/provisioner/ironic/clients/auth.go b/pkg/provisioner/ironic/clients/auth.go
new file mode 100644
index 00000000..55a3340e
--- /dev/null
+++ b/pkg/provisioner/ironic/clients/auth.go
@@ -0,0 +1,106 @@
+package clients
+
+import (
+	"fmt"
+	"io/ioutil"
+	"net/url"
+	"os"
+	"path"
+	"path/filepath"
+	"strings"
+)
+
+// AuthType is the method of authenticating requests to the server
+type AuthType string
+
+const (
+	// NoAuth uses no authentication
+	NoAuth AuthType = "noauth"
+	// HTTPBasicAuth uses HTTP Basic Authentication
+	HTTPBasicAuth AuthType = "http_basic"
+)
+
+// AuthConfig contains data needed to configure authentication in the client
+type AuthConfig struct {
+	Type     AuthType
+	Username string
+	Password string
+}
+
+func authRoot() string {
+	env := os.Getenv("METAL3_AUTH_ROOT_DIR")
+	if env != "" {
+		return filepath.Clean(env)
+	}
+	return "/opt/metal3/auth"
+}
+
+func readAuthFile(filename string) (string, error) {
+	content, err := ioutil.ReadFile(filepath.Clean(filename))
+	return strings.TrimSpace(string(content)), err
+}
+
+func load(clientType string) (auth AuthConfig, err error) {
+	authPath := path.Join(authRoot(), clientType)
+
+	if _, err := os.Stat(authPath); err != nil {
+		if os.IsNotExist(err) {
+			auth.Type = NoAuth
+			return auth, nil
+		}
+		return auth, err
+	}
+	auth.Type = HTTPBasicAuth
+
+	auth.Username, err = readAuthFile(path.Join(authPath, "username"))
+	if err != nil {
+		return
+	}
+
+	auth.Password, err = readAuthFile(path.Join(authPath, "password"))
+	if err != nil {
+		return
+	}
+
+	if auth.Username == "" {
+		err = fmt.Errorf("Empty HTTP Basic Auth username")
+	} else if auth.Password == "" {
+		err = fmt.Errorf("Empty HTTP Basic Auth password")
+	}
+	return
+}
+
+// LoadAuth loads the Ironic and Inspector configuration from the environment
+func LoadAuth() (ironicAuth, inspectorAuth AuthConfig, err error) {
+	ironicAuth, err = load("ironic")
+	if err != nil {
+		return
+	}
+	inspectorAuth, err = load("ironic-inspector")
+	return
+}
+
+// ConfigFromEndpointURL returns an endpoint and an auth config from an
+// endpoint URL that may contain HTTP basic auth credentials.
+func ConfigFromEndpointURL(endpointURL string) (endpoint string, auth AuthConfig, err error) {
+	parsedURL, err := url.Parse(endpointURL)
+	if err != nil {
+		return
+	}
+
+	if parsedURL.User != nil {
+		var hasPasswd bool
+		auth.Type = HTTPBasicAuth
+		auth.Username = parsedURL.User.Username()
+		auth.Password, hasPasswd = parsedURL.User.Password()
+		if !hasPasswd {
+			err = fmt.Errorf("No password supplied for HTTP Basic Auth")
+		}
+		parsedURL.User = nil
+	} else {
+		auth.Type = NoAuth
+	}
+
+	endpoint = parsedURL.String()
+	return
+}
diff --git a/pkg/provisioner/ironic/clients/client.go b/pkg/provisioner/ironic/clients/client.go
new file mode 100644
index 00000000..398086b3
--- /dev/null
+++ b/pkg/provisioner/ironic/clients/client.go
@@ -0,0 +1,50 @@
+package clients
+
+import (
+	"fmt"
+
+	"github.com/gophercloud/gophercloud"
+	"github.com/gophercloud/gophercloud/openstack/baremetal/httpbasic"
+	"github.com/gophercloud/gophercloud/openstack/baremetal/noauth"
+	httpbasicintrospection "github.com/gophercloud/gophercloud/openstack/baremetalintrospection/httpbasic"
+	noauthintrospection "github.com/gophercloud/gophercloud/openstack/baremetalintrospection/noauth"
+)
+
+// IronicClient creates a client for Ironic
+func IronicClient(ironicEndpoint string, auth AuthConfig) (client *gophercloud.ServiceClient, err error) {
+	switch auth.Type {
+	case NoAuth:
+		client, err = noauth.NewBareMetalNoAuth(noauth.EndpointOpts{
+			IronicEndpoint: ironicEndpoint,
+		})
+	case HTTPBasicAuth:
+		client, err = httpbasic.NewBareMetalHTTPBasic(httpbasic.EndpointOpts{
+			IronicEndpoint:     ironicEndpoint,
+			IronicUser:         auth.Username,
+			IronicUserPassword: auth.Password,
+		})
+	default:
+		err = fmt.Errorf("Unknown auth type %s", auth.Type)
+	}
+	return
+}
+
+// InspectorClient creates a client for Ironic Inspector
+func InspectorClient(inspectorEndpoint string, auth AuthConfig) (client *gophercloud.ServiceClient, err error) {
+	switch auth.Type {
+	case NoAuth:
+		client, err = noauthintrospection.NewBareMetalIntrospectionNoAuth(
+			noauthintrospection.EndpointOpts{
+				IronicInspectorEndpoint: inspectorEndpoint,
+			})
+	case HTTPBasicAuth:
+		client, err = httpbasicintrospection.NewBareMetalIntrospectionHTTPBasic(httpbasicintrospection.EndpointOpts{
+			IronicInspectorEndpoint:     inspectorEndpoint,
+			IronicInspectorUser:         auth.Username,
+			IronicInspectorUserPassword: auth.Password,
+		})
+	default:
+		err = fmt.Errorf("Unknown auth type %s", auth.Type)
+	}
+	return
+}
diff --git a/pkg/provisioner/ironic/dependencies.go b/pkg/provisioner/ironic/dependencies.go
deleted file mode 100644
index 84444be1..00000000
--- a/pkg/provisioner/ironic/dependencies.go
+++ /dev/null
@@ -1,92 +0,0 @@
-package ironic
-
-import (
-	"strings"
-	"time"
-
-	"github.com/go-logr/logr"
-	"github.com/gophercloud/gophercloud"
-	"github.com/gophercloud/gophercloud/openstack/baremetal/v1/drivers"
-	"github.com/gophercloud/gophercloud/pagination"
-)
-
-const (
-	checkRequeueDelay = time.Second * 10
-)
-
-type ironicDependenciesChecker struct {
-	client    *gophercloud.ServiceClient
-	inspector *gophercloud.ServiceClient
-	log       logr.Logger
-}
-
-func newIronicDependenciesChecker(client *gophercloud.ServiceClient, inspector *gophercloud.ServiceClient, log logr.Logger) *ironicDependenciesChecker {
-	return &ironicDependenciesChecker{
-		client:    client,
-		inspector: inspector,
-		log:       log,
-	}
-}
-
-func (i *ironicDependenciesChecker) IsReady() (result bool, err error) {
-
-	ready, err := i.checkIronic()
-	if ready && err == nil {
-		ready = i.checkIronicInspector()
-	}
-
-	return ready, err
-}
-
-func (i *ironicDependenciesChecker) checkEndpoint(client *gophercloud.ServiceClient) (ready bool) {
-
-	// NOTE: Some versions of Ironic inspector returns 404 for /v1/ but 200 for /v1,
-	// which seems to be the default behavior for Flask. Remove the trailing slash
-	// from the client endpoint.
-	endpoint := strings.TrimSuffix(client.Endpoint, "/")
-
-	_, err := client.Get(endpoint, nil, nil)
-	if err != nil {
-		log.Info("error caught while checking endpoint", "endpoint", client.Endpoint, "error", err)
-	}
-
-	return err == nil
-}
-
-func (i *ironicDependenciesChecker) checkIronic() (ready bool, err error) {
-	ready = i.checkEndpoint(i.client)
-	if ready {
-		ready, err = i.checkIronicConductor()
-	}
-	return ready, err
-}
-
-func (i *ironicDependenciesChecker) checkIronicConductor() (ready bool, err error) {
-
-	pager := drivers.ListDrivers(i.client, drivers.ListDriversOpts{
-		Detail: false,
-	})
-	err = pager.Err
-
-	if err != nil {
-		return ready, err
-	}
-
-	driverCount := 0
-	pager.EachPage(func(page pagination.Page) (bool, error) {
-		actual, driverErr := drivers.ExtractDrivers(page)
-		if driverErr != nil {
-			return false, driverErr
-		}
-		driverCount += len(actual)
-		return true, nil
-	})
-	// If we have any drivers, conductor is up.
-	ready = driverCount > 0
-
-	return ready, err
-}
-
-func (i *ironicDependenciesChecker) checkIronicInspector() (ready bool) {
-	return i.checkEndpoint(i.inspector)
-}
diff --git a/pkg/provisioner/ironic/ironic.go b/pkg/provisioner/ironic/ironic.go
index a15e38ae..375e8fa3 100644
--- a/pkg/provisioner/ironic/ironic.go
+++ b/pkg/provisioner/ironic/ironic.go
@@ -9,10 +9,8 @@ import (
 	"sigs.k8s.io/yaml"
 
 	"github.com/gophercloud/gophercloud"
-	"github.com/gophercloud/gophercloud/openstack/baremetal/noauth"
 	"github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes"
 	"github.com/gophercloud/gophercloud/openstack/baremetal/v1/ports"
-	noauthintrospection "github.com/gophercloud/gophercloud/openstack/baremetalintrospection/noauth"
 	"github.com/gophercloud/gophercloud/openstack/baremetalintrospection/v1/introspection"
 
 	"github.com/pkg/errors"
@@ -24,6 +22,7 @@ import (
 	"github.com/metal3-io/baremetal-operator/pkg/bmc"
 	"github.com/metal3-io/baremetal-operator/pkg/hardware"
 	"github.com/metal3-io/baremetal-operator/pkg/provisioner"
+	"github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/clients"
 	"github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/devicehints"
 	"github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic/hardwaredetails"
 )
@@ -38,6 +37,8 @@ var deployKernelURL string
 var deployRamdiskURL string
 var ironicEndpoint string
 var inspectorEndpoint string
+var ironicAuth clients.AuthConfig
+var inspectorAuth clients.AuthConfig
 
 const (
 	// See nodes.Node.PowerState for details
@@ -47,14 +48,17 @@ const (
 	powerNone    = "None"
 )
 
-var bootModeCapabilities = map[metal3v1alpha1.BootMode]string{
-	metal3v1alpha1.UEFI:   "uefi",
-	metal3v1alpha1.Legacy: "bios",
-}
-
 func init() {
 	// NOTE(dhellmann): Use Fprintf() to report errors instead of
 	// logging, because logging is not configured yet in init().
+
+	var authErr error
+	ironicAuth, inspectorAuth, authErr = clients.LoadAuth()
+	if authErr != nil {
+		fmt.Fprintf(os.Stderr, "Cannot start: %s\n", authErr)
+		os.Exit(1)
+	}
+
 	deployKernelURL = os.Getenv("DEPLOY_KERNEL_URL")
 	if deployKernelURL == "" {
 		fmt.Fprintf(os.Stderr, "Cannot start: No DEPLOY_KERNEL_URL variable set\n")
@@ -103,7 +107,9 @@ type ironicProvisioner struct {
 func LogStartup() {
 	log.Info("ironic settings",
 		"endpoint", ironicEndpoint,
+		"ironicAuthType", ironicAuth.Type,
 		"inspectorEndpoint", inspectorEndpoint,
+		"inspectorAuthType", inspectorAuth.Type,
 		"deployKernelURL", deployKernelURL,
 		"deployRamdiskURL", deployRamdiskURL,
 	)
@@ -112,36 +118,35 @@ func LogStartup() {
 // A private function to construct an ironicProvisioner (rather than a
 // Provisioner interface) in a consistent way for tests.
 func newProvisioner(host *metal3v1alpha1.BareMetalHost, bmcCreds bmc.Credentials, publisher provisioner.EventPublisher) (*ironicProvisioner, error) {
-	client, err := noauth.NewBareMetalNoAuth(noauth.EndpointOpts{
-		IronicEndpoint: ironicEndpoint,
-	})
+	clientIronic, err := clients.IronicClient(ironicEndpoint, ironicAuth)
 	if err != nil {
 		return nil, err
 	}
-	bmcAccess, err := bmc.NewAccessDetails(host.Spec.BMC.Address, host.Spec.BMC.DisableCertificateVerification)
+
+	clientInspector, err := clients.InspectorClient(inspectorEndpoint, inspectorAuth)
 	if err != nil {
-		return nil, errors.Wrap(err, "failed to parse BMC address information")
+		return nil, err
 	}
-	inspector, err := noauthintrospection.NewBareMetalIntrospectionNoAuth(
-		noauthintrospection.EndpointOpts{
-			IronicInspectorEndpoint: inspectorEndpoint,
-		})
+
+	bmcAccess, err := bmc.NewAccessDetails(host.Spec.BMC.Address, host.Spec.BMC.DisableCertificateVerification)
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "failed to parse BMC address information")
 	}
+
 	// Ensure we have a microversion high enough to get the features
 	// we need.
-	client.Microversion = "1.56"
+	clientIronic.Microversion = "1.56"
 	p := &ironicProvisioner{
 		host:      host,
 		status:    &(host.Status.Provisioning),
 		bmcAccess: bmcAccess,
 		bmcCreds:  bmcCreds,
-		client:    client,
-		inspector: inspector,
+		client:    clientIronic,
+		inspector: clientInspector,
 		log:       log.WithValues("host", host.Name),
 		publisher: publisher,
 	}
+
 	return p, nil
 }
 
@@ -790,33 +795,12 @@ func (p *ironicProvisioner) getUpdateOptsForNode(ironicNode *nodes.Node) (update
 		},
 	)
 
-	// boot_mode
-	_, ok := ironicNode.InstanceInfo["deploy_boot_mode"]
-	if !ok {
-		op = nodes.AddOp
-	} else {
-		op = nodes.ReplaceOp
-	}
-	p.log.Info("setting boot_mode",
-		"operation", op,
-		"instance_info", ironicNode.InstanceInfo,
-		"bootMode", p.host.Status.Provisioning.BootMode,
-	)
-	updates = append(
-		updates,
-		nodes.UpdateOperation{
-			Op:    op,
-			Path:  "/instance_info/deploy_boot_mode",
-			Value: bootModeCapabilities[p.host.Status.Provisioning.BootMode],
-		},
-	)
-
 	return updates, nil
 }
 
 func (p *ironicProvisioner) startProvisioning(ironicNode *nodes.Node, hostConf provisioner.HostConfigData) (result provisioner.Result, err error) {
 
-	p.log.Info("starting provisioning", "node properties", ironicNode.Properties)
+	p.log.Info("starting provisioning")
 
 	updates, err := p.getUpdateOptsForNode(ironicNode)
 	_, err = nodes.Update(p.client, ironicNode.UUID, updates).Extract()
@@ -1394,11 +1378,3 @@ func (p *ironicProvisioner) softPowerOff() (result provisioner.Result, err error
 
 	return result, nil
 }
-
-// IsReady checks if the provisioning backend is available
-func (p *ironicProvisioner) IsReady() (result bool, err error) {
-	p.log.Info("verifying ironic provisioner dependencies")
-
-	checker := newIronicDependenciesChecker(p.client, p.inspector, p.log)
-	return checker.IsReady()
-}
diff --git a/pkg/provisioner/provisioner.go b/pkg/provisioner/provisioner.go
index 34fd7326..55beaf42 100644
--- a/pkg/provisioner/provisioner.go
+++ b/pkg/provisioner/provisioner.go
@@ -83,10 +83,6 @@ type Provisioner interface {
 	// PowerOff ensures the server is powered off independently of any image
 	// provisioning operation.
 	PowerOff() (result Result, err error)
-
-	// IsReady checks if the provisioning backend is available to accept
-	// all the incoming requests.
-	IsReady() (result bool, err error)
 }
 
 // Result holds the response from a call in the Provsioner API.


Test patch:
diff --git a/pkg/controller/baremetalhost/baremetalhost_controller_test.go b/pkg/controller/baremetalhost/baremetalhost_controller_test.go
index 8caa62a1..9e56227b 100644
--- a/pkg/controller/baremetalhost/baremetalhost_controller_test.go
+++ b/pkg/controller/baremetalhost/baremetalhost_controller_test.go
@@ -23,8 +23,6 @@ import (
 
 	metal3apis "github.com/metal3-io/baremetal-operator/pkg/apis"
 	metal3v1alpha1 "github.com/metal3-io/baremetal-operator/pkg/apis/metal3/v1alpha1"
-	"github.com/metal3-io/baremetal-operator/pkg/bmc"
-	"github.com/metal3-io/baremetal-operator/pkg/provisioner"
 	"github.com/metal3-io/baremetal-operator/pkg/provisioner/fixture"
 	"github.com/metal3-io/baremetal-operator/pkg/utils"
 )
@@ -72,7 +70,8 @@ func newHost(name string, spec *metal3v1alpha1.BareMetalHostSpec) *metal3v1alpha
 		TypeMeta: metav1.TypeMeta{
 			Kind:       "BareMetalHost",
 			APIVersion: "metal3.io/v1alpha1",
-		}, ObjectMeta: metav1.ObjectMeta{
+		},
+		ObjectMeta: metav1.ObjectMeta{
 			Name:      name,
 			Namespace: namespace,
 		},
@@ -95,7 +94,7 @@ func newDefaultHost(t *testing.T) *metal3v1alpha1.BareMetalHost {
 	return newDefaultNamedHost(t.Name(), t)
 }
 
-func newTestReconcilerWithProvisionerFactory(factory provisioner.Factory, initObjs ...runtime.Object) *ReconcileBareMetalHost {
+func newTestReconciler(initObjs ...runtime.Object) *ReconcileBareMetalHost {
 
 	c := fakeclient.NewFakeClient(initObjs...)
 
@@ -106,14 +105,10 @@ func newTestReconcilerWithProvisionerFactory(factory provisioner.Factory, initOb
 	return &ReconcileBareMetalHost{
 		client:             c,
 		scheme:             scheme.Scheme,
-		provisionerFactory: factory,
+		provisionerFactory: fixture.New,
 	}
 }
 
-func newTestReconciler(initObjs ...runtime.Object) *ReconcileBareMetalHost {
-	return newTestReconcilerWithProvisionerFactory(fixture.New, initObjs...)
-}
-
 type DoneFunc func(host *metal3v1alpha1.BareMetalHost, result reconcile.Result) bool
 
 func newRequest(host *metal3v1alpha1.BareMetalHost) reconcile.Request {
@@ -1164,107 +1159,3 @@ func TestUpdateRootDeviceHints(t *testing.T) {
 		})
 	}
 }
-
-// TestUpdateBootMode verifies that we apply the correct boot mode
-func TestUpdateBootMode(t *testing.T) {
-	testCases := []struct {
-		Scenario string
-		Host     metal3v1alpha1.BareMetalHost
-		Dirty    bool
-		Expected metal3v1alpha1.BootMode
-	}{
-		{
-			Scenario: "set default",
-			Host: metal3v1alpha1.BareMetalHost{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "myhost",
-					Namespace: "myns",
-					UID:       "27720611-e5d1-45d3-ba3a-222dcfaa4ca2",
-				},
-				Spec: metal3v1alpha1.BareMetalHostSpec{},
-				Status: metal3v1alpha1.BareMetalHostStatus{
-					HardwareProfile: "unknown",
-				},
-			},
-			Dirty:    true,
-			Expected: metal3v1alpha1.DefaultBootMode,
-		},
-		{
-			Scenario: "user value",
-			Host: metal3v1alpha1.BareMetalHost{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "myhost",
-					Namespace: "myns",
-					UID:       "27720611-e5d1-45d3-ba3a-222dcfaa4ca2",
-				},
-				Spec: metal3v1alpha1.BareMetalHostSpec{
-					BootMode: metal3v1alpha1.Legacy,
-				},
-				Status: metal3v1alpha1.BareMetalHostStatus{
-					HardwareProfile: "unknown",
-				},
-			},
-			Dirty:    true,
-			Expected: metal3v1alpha1.Legacy,
-		},
-		{
-			Scenario: "already set",
-			Host: metal3v1alpha1.BareMetalHost{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "myhost",
-					Namespace: "myns",
-					UID:       "27720611-e5d1-45d3-ba3a-222dcfaa4ca2",
-				},
-				Spec: metal3v1alpha1.BareMetalHostSpec{
-					BootMode:        metal3v1alpha1.Legacy,
-					HardwareProfile: "libvirt",
-				},
-				Status: metal3v1alpha1.BareMetalHostStatus{
-					HardwareProfile: "libvirt",
-					Provisioning: metal3v1alpha1.ProvisionStatus{
-						BootMode: metal3v1alpha1.Legacy,
-						RootDeviceHints: &metal3v1alpha1.RootDeviceHints{
-							DeviceName: "/dev/vda",
-						},
-					},
-				},
-			},
-			Dirty:    false,
-			Expected: metal3v1alpha1.Legacy,
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.Scenario, func(t *testing.T) {
-			dirty, err := saveHostProvisioningSettings(&tc.Host)
-			if err != nil {
-				t.Fatal(err)
-			}
-			assert.Equal(t, tc.Dirty, dirty, "dirty flag did not match")
-			assert.Equal(t, tc.Expected, tc.Host.Status.Provisioning.BootMode)
-		})
-	}
-}
-
-func TestProvisionerIsReady(t *testing.T) {
-	host := newDefaultHost(t)
-
-	var prov provisioner.Provisioner
-	r := newTestReconcilerWithProvisionerFactory(func(host *metal3v1alpha1.BareMetalHost, bmcCreds bmc.Credentials, publisher provisioner.EventPublisher) (provisioner provisioner.Provisioner, err error) {
-		if prov == nil {
-			prov, err = fixture.NewMock(host, bmcCreds, publisher, 5)
-		}
-		return prov, err
-	}, host)
-
-	tryReconcile(t, r, host,
-		func(host *metal3v1alpha1.BareMetalHost, result reconcile.Result) bool {
-			if prov == nil {
-				return false
-			}
-
-			ready, _ := prov.IsReady()
-			return ready
-		},
-	)
-}
diff --git a/pkg/provisioner/ironic/clients/auth_test.go b/pkg/provisioner/ironic/clients/auth_test.go
new file mode 100644
index 00000000..a304c138
--- /dev/null
+++ b/pkg/provisioner/ironic/clients/auth_test.go
@@ -0,0 +1,99 @@
+package clients
+
+import (
+	"testing"
+)
+
+func TestConfigFromEndpointURL(t *testing.T) {
+	testCases := []struct {
+		Scenario         string
+		URL              string
+		ExpectedEndpoint string
+		ExpectedAuth     AuthConfig
+		ExpectErr        bool
+	}{
+		{
+			Scenario:  "garbage",
+			URL:       "@foo::bar",
+			ExpectErr: true,
+		},
+		{
+			Scenario:         "no auth",
+			URL:              "https://example.test/v1",
+			ExpectedEndpoint: "https://example.test/v1",
+			ExpectedAuth: AuthConfig{
+				Type: NoAuth,
+			},
+		},
+		{
+			Scenario:         "username-only",
+			URL:              "https://user@example.test/v1",
+			ExpectedEndpoint: "https://example.test/v1",
+			ExpectedAuth: AuthConfig{
+				Type:     HTTPBasicAuth,
+				Username: "user",
+			},
+			ExpectErr: true,
+		},
+		{
+			Scenario:         "empty password",
+			URL:              "https://user:@example.test/v1",
+			ExpectedEndpoint: "https://example.test/v1",
+			ExpectedAuth: AuthConfig{
+				Type:     HTTPBasicAuth,
+				Username: "user",
+				Password: "",
+			},
+		},
+		{
+			Scenario:         "basic auth",
+			URL:              "https://user:pass@example.test/v1",
+			ExpectedEndpoint: "https://example.test/v1",
+			ExpectedAuth: AuthConfig{
+				Type:     HTTPBasicAuth,
+				Username: "user",
+				Password: "pass",
+			},
+		},
+		{
+			Scenario:         "IPv6 no auth",
+			URL:              "https://[::1]/v1",
+			ExpectedEndpoint: "https://[::1]/v1",
+			ExpectedAuth: AuthConfig{
+				Type: NoAuth,
+			},
+		},
+		{
+			Scenario:         "IPv6 basic auth",
+			URL:              "https://user:pass@[::1]/v1",
+			ExpectedEndpoint: "https://[::1]/v1",
+			ExpectedAuth: AuthConfig{
+				Type:     HTTPBasicAuth,
+				Username: "user",
+				Password: "pass",
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.Scenario, func(t *testing.T) {
+			ep, auth, err := ConfigFromEndpointURL(tc.URL)
+			if ep != tc.ExpectedEndpoint {
+				t.Errorf("Expected endpoint '%s', got '%s'",
+					tc.ExpectedEndpoint, ep)
+			}
+			if auth.Type != tc.ExpectedAuth.Type {
+				t.Errorf("Unexpected auth type %s", auth.Type)
+			}
+			if auth.Username != tc.ExpectedAuth.Username {
+				t.Errorf("Unexpected username '%s'", auth.Username)
+			}
+			if auth.Password != tc.ExpectedAuth.Password {
+				t.Errorf("Unexpected password '%s'", auth.Password)
+			}
+			if (err != nil) != tc.ExpectErr {
+				t.Errorf("Unexpected error %s", err)
+			}
+		})
+	}
+}
diff --git a/pkg/provisioner/ironic/ironic_test.go b/pkg/provisioner/ironic/ironic_test.go
index 19e19ccd..e26ac1ad 100644
--- a/pkg/provisioner/ironic/ironic_test.go
+++ b/pkg/provisioner/ironic/ironic_test.go
@@ -2,11 +2,6 @@ package ironic
 
 import (
 	"fmt"
-	"net"
-	"net/http"
-	"net/http/httptest"
-	"strconv"
-	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -25,115 +20,58 @@ func init() {
 	logf.SetLogger(logf.ZapLogger(true))
 }
 
-func TestProvisionerIsReady(t *testing.T) {
-
-	cases := []struct {
-		name      string
-		ironic    *mockServer
-		inspector *mockServer
-
-		expectedIronicCalls    string
-		expectedInspectorCalls string
-		expectedIsReady        bool
-		expectedError          string
-	}{
-		{
-			name:      "IsReady",
-			ironic:    newMockServer(6385).addDrivers(),
-			inspector: newMockServer(5050),
-
-			expectedIronicCalls:    "localhost:6385/v1;localhost:6385/v1/drivers;",
-			expectedInspectorCalls: "localhost:5050/v1;",
-			expectedIsReady:        true,
-		},
-		{
-			name:      "NoDriversLoaded",
-			ironic:    newMockServer(6385),
-			inspector: newMockServer(5050),
-
-			expectedIronicCalls: "localhost:6385/v1;localhost:6385/v1/drivers;",
-		},
-		{
-			name:      "IronicDown",
-			inspector: newMockServer(5050),
-
-			expectedIsReady: false,
-		},
-		{
-			name:   "InspectorDown",
-			ironic: newMockServer(6385).addDrivers(),
-
-			expectedIronicCalls: "localhost:6385/v1;localhost:6385/v1/drivers;",
-
-			expectedIsReady: false,
-		},
-		{
-			name:      "IronicNotOk",
-			ironic:    newMockServer(6385).setErrorCode(http.StatusInternalServerError),
-			inspector: newMockServer(5050),
-
-			expectedIsReady: false,
-
-			expectedIronicCalls: "localhost:6385/v1;",
+func TestGetUpdateOptsForNodeWithRootHints(t *testing.T) {
+	rotational := true
+	host := &metal3v1alpha1.BareMetalHost{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "myhost",
+			Namespace: "myns",
+			UID:       "27720611-e5d1-45d3-ba3a-222dcfaa4ca2",
 		},
-		{
-			name:      "IronicNotOkAndNotExpected",
-			ironic:    newMockServer(6385).setErrorCode(http.StatusBadGateway),
-			inspector: newMockServer(5050),
-
-			expectedIsReady: false,
-
-			expectedIronicCalls: "localhost:6385/v1;",
+		Spec: metal3v1alpha1.BareMetalHostSpec{
+			Image: &metal3v1alpha1.Image{
+				URL: "not-empty",
+			},
+			Online:          true,
+			HardwareProfile: "libvirt",
+			RootDeviceHints: &metal3v1alpha1.RootDeviceHints{
+				DeviceName:         "userd_devicename",
+				HCTL:               "1:2:3:4",
+				Model:              "userd_model",
+				Vendor:             "userd_vendor",
+				SerialNumber:       "userd_serial",
+				MinSizeGigabytes:   40,
+				WWN:                "userd_wwn",
+				WWNWithExtension:   "userd_with_extension",
+				WWNVendorExtension: "userd_vendor_extension",
+				Rotational:         &rotational,
+			},
 		},
-		{
-			name:      "InspectorNotOk",
-			ironic:    newMockServer(6385).addDrivers(),
-			inspector: newMockServer(5050).setErrorCode(http.StatusInternalServerError),
-
-			expectedIsReady: false,
-
-			expectedIronicCalls:    "localhost:6385/v1;localhost:6385/v1/drivers;",
-			expectedInspectorCalls: "localhost:5050/v1;",
+		Status: metal3v1alpha1.BareMetalHostStatus{
+			Provisioning: metal3v1alpha1.ProvisionStatus{
+				ID: "provisioning-id",
+				// Place the hints in the status field to pretend the
+				// controller has already reconciled partially.
+				RootDeviceHints: &metal3v1alpha1.RootDeviceHints{
+					DeviceName:         "userd_devicename",
+					HCTL:               "1:2:3:4",
+					Model:              "userd_model",
+					Vendor:             "userd_vendor",
+					SerialNumber:       "userd_serial",
+					MinSizeGigabytes:   40,
+					WWN:                "userd_wwn",
+					WWNWithExtension:   "userd_with_extension",
+					WWNVendorExtension: "userd_vendor_extension",
+					Rotational:         &rotational,
+				},
+			},
+			HardwareProfile: "libvirt",
 		},
 	}
 
-	for _, tc := range cases {
-		t.Run(tc.name, func(t *testing.T) {
-			if tc.ironic != nil {
-				tc.ironic.start()
-				defer tc.ironic.stop()
-			}
-
-			if tc.inspector != nil {
-				tc.inspector.start()
-				defer tc.inspector.stop()
-			}
-
-			prov, err := newProvisioner(makeHost(), bmc.Credentials{}, nil)
-			ready, err := prov.IsReady()
-
-			if tc.ironic != nil {
-				assert.Equal(t, tc.expectedIronicCalls, tc.ironic.requests)
-			}
-			if tc.inspector != nil {
-				assert.Equal(t, tc.expectedInspectorCalls, tc.inspector.requests)
-			}
-
-			if tc.expectedError != "" {
-				assert.Regexp(t, tc.expectedError, err)
-			} else {
-				assert.Nil(t, err)
-				assert.Equal(t, tc.expectedIsReady, ready)
-			}
-		})
-	}
-}
-
-func TestGetUpdateOptsForNodeWithRootHints(t *testing.T) {
-
 	eventPublisher := func(reason, message string) {}
 
-	prov, err := newProvisioner(makeHost(), bmc.Credentials{}, eventPublisher)
+	prov, err := newProvisioner(host, bmc.Credentials{}, eventPublisher)
 	ironicNode := &nodes.Node{}
 
 	patches, err := prov.getUpdateOptsForNode(ironicNode)
@@ -375,248 +313,3 @@ func TestGetUpdateOptsForNodeDell(t *testing.T) {
 		})
 	}
 }
-
-func TestGetUpdateOptsForNodeBootMode(t *testing.T) {
-
-	testCases := []struct {
-		Scenario string
-		BootMode metal3v1alpha1.BootMode
-		Node     nodes.Node
-		Expected nodes.UpdateOperation
-	}{
-		{
-			Scenario: "add uefi",
-			BootMode: metal3v1alpha1.UEFI,
-			Node:     nodes.Node{},
-			Expected: nodes.UpdateOperation{
-				Op:    nodes.AddOp,
-				Path:  "/instance_info/deploy_boot_mode",
-				Value: "uefi",
-			},
-		},
-		{
-			Scenario: "add uefi",
-			BootMode: metal3v1alpha1.Legacy,
-			Node:     nodes.Node{},
-			Expected: nodes.UpdateOperation{
-				Op:    nodes.AddOp,
-				Path:  "/instance_info/deploy_boot_mode",
-				Value: "bios",
-			},
-		},
-		{
-			Scenario: "replace uefi",
-			BootMode: metal3v1alpha1.UEFI,
-			Node: nodes.Node{
-				InstanceInfo: map[string]interface{}{
-					"deploy_boot_mode": "legacy",
-				},
-			},
-			Expected: nodes.UpdateOperation{
-				Op:    nodes.ReplaceOp,
-				Path:  "/instance_info/deploy_boot_mode",
-				Value: "uefi",
-			},
-		},
-		{
-			Scenario: "replace legacy",
-			BootMode: metal3v1alpha1.Legacy,
-			Node: nodes.Node{
-				InstanceInfo: map[string]interface{}{
-					"deploy_boot_mode": "legacy",
-				},
-			},
-			Expected: nodes.UpdateOperation{
-				Op:    nodes.ReplaceOp,
-				Path:  "/instance_info/deploy_boot_mode",
-				Value: "bios",
-			},
-		},
-	}
-
-	eventPublisher := func(reason, message string) {}
-
-	for _, tc := range testCases {
-		t.Run(tc.Scenario, func(t *testing.T) {
-			t.Logf("expected: %v", tc.Expected)
-
-			// Create a host with the boot mode in the status set to
-			// the test value. getUpdateOptsForNode() doesn't care
-			// where that value came from before it ended up in the
-			// status.
-			host := metal3v1alpha1.BareMetalHost{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "myhost",
-					Namespace: "myns",
-					UID:       "27720611-e5d1-45d3-ba3a-222dcfaa4ca2",
-				},
-				Spec: metal3v1alpha1.BareMetalHostSpec{
-					Image: &metal3v1alpha1.Image{
-						URL:          "not-empty",
-						Checksum:     "checksum",
-						ChecksumType: metal3v1alpha1.MD5,
-						DiskFormat:   pointer.StringPtr("raw"),
-					},
-					Online: true,
-				},
-				Status: metal3v1alpha1.BareMetalHostStatus{
-					HardwareProfile: "libvirt",
-					Provisioning: metal3v1alpha1.ProvisionStatus{
-						BootMode: tc.BootMode,
-					},
-				},
-			}
-
-			prov, err := newProvisioner(&host, bmc.Credentials{}, eventPublisher)
-			patches, err := prov.getUpdateOptsForNode(&tc.Node)
-			if err != nil {
-				t.Fatal(err)
-			}
-
-			t.Logf("patches: %v", patches)
-
-			var update nodes.UpdateOperation
-			for _, patch := range patches {
-				update = patch.(nodes.UpdateOperation)
-				if update.Path == tc.Expected.Path {
-					break
-				}
-			}
-			if update.Path != tc.Expected.Path {
-				t.Errorf("did not find %q in updates", tc.Expected.Path)
-				return
-			}
-			t.Logf("update: %v", update)
-			assert.Equal(t,
-				tc.Expected, update,
-				fmt.Sprintf("%s does not match", tc.Expected.Path),
-			)
-		})
-	}
-}
-
-func makeHost() *metal3v1alpha1.BareMetalHost {
-	rotational := true
-
-	return &metal3v1alpha1.BareMetalHost{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "myhost",
-			Namespace: "myns",
-			UID:       "27720611-e5d1-45d3-ba3a-222dcfaa4ca2",
-		},
-		Spec: metal3v1alpha1.BareMetalHostSpec{
-			Image: &metal3v1alpha1.Image{
-				URL: "not-empty",
-			},
-			Online:          true,
-			HardwareProfile: "libvirt",
-			RootDeviceHints: &metal3v1alpha1.RootDeviceHints{
-				DeviceName:         "userd_devicename",
-				HCTL:               "1:2:3:4",
-				Model:              "userd_model",
-				Vendor:             "userd_vendor",
-				SerialNumber:       "userd_serial",
-				MinSizeGigabytes:   40,
-				WWN:                "userd_wwn",
-				WWNWithExtension:   "userd_with_extension",
-				WWNVendorExtension: "userd_vendor_extension",
-				Rotational:         &rotational,
-			},
-		},
-		Status: metal3v1alpha1.BareMetalHostStatus{
-			Provisioning: metal3v1alpha1.ProvisionStatus{
-				ID: "provisioning-id",
-				// Place the hints in the status field to pretend the
-				// controller has already reconciled partially.
-				RootDeviceHints: &metal3v1alpha1.RootDeviceHints{
-					DeviceName:         "userd_devicename",
-					HCTL:               "1:2:3:4",
-					Model:              "userd_model",
-					Vendor:             "userd_vendor",
-					SerialNumber:       "userd_serial",
-					MinSizeGigabytes:   40,
-					WWN:                "userd_wwn",
-					WWNWithExtension:   "userd_with_extension",
-					WWNVendorExtension: "userd_vendor_extension",
-					Rotational:         &rotational,
-				},
-			},
-			HardwareProfile: "libvirt",
-		},
-	}
-}
-
-func newMockServer(port int) *mockServer {
-	return &mockServer{
-		port: strconv.Itoa(port),
-	}
-}
-
-type mockServer struct {
-	port      string
-	requests  string
-	server    *httptest.Server
-	drivers   string
-	errorCode int
-}
-
-func (m *mockServer) setErrorCode(code int) *mockServer {
-	m.errorCode = code
-
-	return m
-}
-
-func (m *mockServer) addDrivers() *mockServer {
-	m.drivers = `
-	{
-		"drivers": [{
-			"hosts": [
-			  "master-2.ostest.test.metalkube.org"
-			],
-			"links": [
-			  {
-				"href": "http://[fd00:1101::3]:6385/v1/drivers/fake-hardware",
-				"rel": "self"
-			  },
-			  {
-				"href": "http://[fd00:1101::3]:6385/drivers/fake-hardware",
-				"rel": "bookmark"
-			  }
-			],
-			"name": "fake-hardware"
-		}]
-	}
-	`
-	return m
-}
-
-func (m *mockServer) start() *mockServer {
-	listener, err := net.Listen("tcp", "127.0.0.1:"+m.port)
-	if err != nil {
-		panic(err)
-	}
-
-	m.server = httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		m.requests += r.Host + r.RequestURI + ";"
-
-		if m.errorCode != 0 {
-			http.Error(w, "An error", m.errorCode)
-			return
-		}
-
-		if strings.Contains(r.RequestURI, "/v1/drivers") {
-			w.Header().Set("Content-Type", "application/json")
-			fmt.Fprintf(w, m.drivers)
-		}
-	}))
-
-	m.server.Listener.Close()
-	m.server.Listener = listener
-	m.server.Start()
-
-	return m
-}
-
-func (m *mockServer) stop() {
-	m.server.Close()
-}