Repository: metal3-io/baremetal-operator
Pull request #901 (2021-05-31 12:46:22)

Add new CRDs for BIOS configuration
Add a new CRD for the HostFirmwareSettings for BIOS configuration as described in the spec - https://github.com/metal3-io/metal3-docs/pull/173. A new CRD is necessary since its not practical to include the number of settings in BareMetalHost.
In addition a CRD is added for the FirmwareSchema used to provide a schema for setting the firmware values.

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

This pull-request has been approved by:
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **zaneb** after the PR has been reviewed.
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: Hi @bfournie. Thanks for your PR.

I'm waiting for a [metal3-io](https://github.com/orgs/metal3-io/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://github.com/orgs/metal3-io/people) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=metal3-io%2Fbaremetal-operator).

<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.
</details>

stbenjam: /ok-to-test
metal3-io-bot: @bfournie: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
gofmt | 3f1e24d7242857023d7a06bbee54d6ecde41563e | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/gofmt/1399719864523821056) | `/test gofmt`

<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: @bfournie: PR needs rebase.

<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.
</details>
metal3-io-bot: @bfournie: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
gofmt | 5af4aebf8d7865b5129ca24b47562120d1b4a3a3 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/gofmt/1405311924710674432) | `/test gofmt`

<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 -->
elfosardo: /test-integration
elfosardo: /lgtm
metal3-io-bot: @elfosardo: adding LGTM is restricted to approvers and reviewers in OWNERS files.

<details>

In response to [this](https://github.com/metal3-io/baremetal-operator/pull/901#issuecomment-863909475):

>/lgtm


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.
</details>
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/901#pullrequestreview-687230379" title="Approved">elfosardo</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **zaneb** after the PR has been reviewed.
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: @bfournie: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
manifestlint | 1717340c0f79fdfc0d1b18ec212a697c866a4e1a | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/manifestlint/1409320982224572416) | `/test manifestlint`

<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: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/metal3-io/baremetal-operator/pull/901#pullrequestreview-687230379" title="Approved">elfosardo</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **russellb** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @russellb` 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":["russellb"]} -->
metal3-io-bot: @bfournie: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
gofmt | db1396652f9dc39d88beab20333b7a00c53909e1 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/gofmt/1411011633710370816) | `/test gofmt`

<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 -->
bfournie: /test-integration
bfournie: /test-integration
elfosardo: /test-integration
bfournie: /test-integration
metal3-io-bot: @bfournie: PR needs rebase.

<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.
</details>
metal3-io-bot: @bfournie: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
markdownlint | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/markdownlint/1413521818194546688) | `/test markdownlint`

<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: @bfournie: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
markdownlint | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/markdownlint/1413521818194546688) | `/test markdownlint`
gofmt | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/gofmt/1413521817997414400) | `/test gofmt`

<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: @bfournie: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
markdownlint | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/markdownlint/1413521818194546688) | `/test markdownlint`
gofmt | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/gofmt/1413521817997414400) | `/test gofmt`
gosec | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/gosec/1413521818026774528) | `/test gosec`

<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: @bfournie: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
markdownlint | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/markdownlint/1413521818194546688) | `/test markdownlint`
gofmt | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/gofmt/1413521817997414400) | `/test gofmt`
gosec | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/gosec/1413521818026774528) | `/test gosec`
shellcheck | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/shellcheck/1413521818299404288) | `/test shellcheck`

<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: @bfournie: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
markdownlint | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/markdownlint/1413521818194546688) | `/test markdownlint`
gofmt | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/gofmt/1413521817997414400) | `/test gofmt`
gosec | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/gosec/1413521818026774528) | `/test gosec`
shellcheck | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/shellcheck/1413521818299404288) | `/test shellcheck`
gomod | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/gomod/1413521818064523264) | `/test gomod`

<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: @bfournie: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
markdownlint | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/markdownlint/1413521818194546688) | `/test markdownlint`
gofmt | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/gofmt/1413521817997414400) | `/test gofmt`
gosec | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/gosec/1413521818026774528) | `/test gosec`
shellcheck | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/shellcheck/1413521818299404288) | `/test shellcheck`
gomod | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/gomod/1413521818064523264) | `/test gomod`
unit | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/unit/1413521818311987200) | `/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: @bfournie: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
markdownlint | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/markdownlint/1413521818194546688) | `/test markdownlint`
gofmt | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/gofmt/1413521817997414400) | `/test gofmt`
gosec | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/gosec/1413521818026774528) | `/test gosec`
shellcheck | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/shellcheck/1413521818299404288) | `/test shellcheck`
gomod | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/gomod/1413521818064523264) | `/test gomod`
unit | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/unit/1413521818311987200) | `/test unit`
golint | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/golint/1413521818425233408) | `/test golint`

<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: @bfournie: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
markdownlint | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/markdownlint/1413521818194546688) | `/test markdownlint`
gofmt | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/gofmt/1413521817997414400) | `/test gofmt`
gosec | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/gosec/1413521818026774528) | `/test gosec`
shellcheck | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/shellcheck/1413521818299404288) | `/test shellcheck`
gomod | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/gomod/1413521818064523264) | `/test gomod`
unit | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/unit/1413521818311987200) | `/test unit`
golint | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/golint/1413521818425233408) | `/test golint`
manifestlint | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/manifestlint/1413521818446204928) | `/test manifestlint`

<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: @bfournie: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
markdownlint | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/markdownlint/1413521818194546688) | `/test markdownlint`
gofmt | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/gofmt/1413521817997414400) | `/test gofmt`
gosec | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/gosec/1413521818026774528) | `/test gosec`
shellcheck | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/shellcheck/1413521818299404288) | `/test shellcheck`
gomod | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/gomod/1413521818064523264) | `/test gomod`
unit | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/unit/1413521818311987200) | `/test unit`
golint | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/golint/1413521818425233408) | `/test golint`
manifestlint | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/manifestlint/1413521818446204928) | `/test manifestlint`
generate | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/generate/1413521818349735936) | `/test generate`

<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: @bfournie: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
unit | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/unit/1413521818311987200) | `/test unit`
golint | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/golint/1413521818425233408) | `/test golint`
generate | 9e08275301fe7df5e06cd5be4d050d3209d5e86d | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/generate/1413523954018029568) | `/test generate`

<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: @bfournie: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
golint | 9712791530ac403f85967e2a75cb767474616024 | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/golint/1413521818425233408) | `/test golint`
generate | 9e08275301fe7df5e06cd5be4d050d3209d5e86d | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/generate/1413523954018029568) | `/test generate`
unit | 9e08275301fe7df5e06cd5be4d050d3209d5e86d | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/unit/1413523953963503616) | `/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: @bfournie: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
generate | 9e08275301fe7df5e06cd5be4d050d3209d5e86d | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/generate/1413523954018029568) | `/test generate`
unit | 9e08275301fe7df5e06cd5be4d050d3209d5e86d | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/unit/1413523953963503616) | `/test unit`
golint | 9e08275301fe7df5e06cd5be4d050d3209d5e86d | [link](https://prow.apps.test.metal3.io/view/gs/metal3-prow/pr-logs/pull/metal3-io_baremetal-operator/901/golint/1413523954093527040) | `/test golint`

<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/PROJECT b/PROJECT
index 1031f087..e546544e 100644
--- a/PROJECT
+++ b/PROJECT
@@ -4,4 +4,10 @@ resources:
 - group: metal3.io
   kind: BareMetalHost
   version: v1alpha1
+- group: metal3.io
+  kind: HostFirmwareSettings
+  version: v1alpha1
+- group: metal3.io
+  kind: FirmwareSchema
+  version: v1alpha1
 version: "2"
diff --git a/apis/go.sum b/apis/go.sum
index b0c7635f..04c07372 100644
--- a/apis/go.sum
+++ b/apis/go.sum
@@ -584,6 +584,7 @@ k8s.io/apiextensions-apiserver v0.19.2/go.mod h1:EYNjpqIAvNZe+svXVx9j4uBaVhTB4C9
 k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
 k8s.io/apimachinery v0.20.2 h1:hFx6Sbt1oG0n6DZ+g4bFt5f6BoMkOjKWsQFu077M3Vg=
 k8s.io/apimachinery v0.20.2/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
+k8s.io/apimachinery v0.21.2 h1:vezUc/BHqWlQDnZ+XkrpXSmnANSLbpnlpwo0Lhk0gpc=
 k8s.io/apiserver v0.19.2/go.mod h1:FreAq0bJ2vtZFj9Ago/X0oNGC51GfubKK/ViOKfVAOA=
 k8s.io/client-go v0.19.2/go.mod h1:S5wPhCqyDNAlzM9CnEdgTGV4OqhsW3jGO1UM1epwfJA=
 k8s.io/code-generator v0.19.2/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk=
diff --git a/apis/metal3.io/v1alpha1/firmwareschema_types.go b/apis/metal3.io/v1alpha1/firmwareschema_types.go
new file mode 100644
index 00000000..58b0247b
--- /dev/null
+++ b/apis/metal3.io/v1alpha1/firmwareschema_types.go
@@ -0,0 +1,147 @@
+/*
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+)
+
+// Additional data describing the firmware setting
+type SettingSchema struct {
+
+	// The type of setting.
+	// +kubebuilder:validation:Enum=Enumeration;String;Integer;Boolean;Password
+	AttributeType string `json:"attribute_type,omitempty"`
+
+	// The allowable value for an Enumeration type setting.
+	AllowableValues []string `json:"allowable_values,omitempty"`
+
+	// The lowest value for an Integer type setting.
+	LowerBound *int `json:"lower_bound,omitempty"`
+
+	// The highest value for an Integer type setting.
+	UpperBound *int `json:"upper_bound,omitempty"`
+
+	// Minimum length for a String type setting.
+	MinLength *int `json:"min_length,omitempty"`
+
+	// Maximum length for a String type setting.
+	MaxLength *int `json:"max_length,omitempty"`
+
+	// Whether or not this setting is read only.
+	ReadOnly *bool `json:"read_only,omitempty"`
+
+	// Whether or not a reset is required after changing this setting.
+	ResetRequired *bool `json:"reset_required,omitempty"`
+
+	// Whether or not this setting's value is unique to this node, e.g.
+	// a serial number.
+	Unique *bool `json:"unique,omitempty"`
+}
+
+// FirmwareSchemaSpec defines the desired state of FirmwareSchema
+type FirmwareSchemaSpec struct {
+
+	// The hardware vendor associated with this schema
+	// +optional
+	HardwareVendor string `json:"hardwareVendor,omitempty"`
+
+	// The hardware model associated with this schema
+	// +optional
+	HardwareModel string `json:"hardwareModel,omitempty"`
+
+	// Map of firmware name to schema
+	Schema map[string]SettingSchema `json:"schema" required:"true"`
+}
+
+//+kubebuilder:object:root=true
+
+// FirmwareSchema is the Schema for the firmwareschemas API
+type FirmwareSchema struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec FirmwareSchemaSpec `json:"spec,omitempty"`
+}
+
+// Check whether the setting's name and value is valid using the schema
+func (host *FirmwareSchema) CheckSettingIsValid(name string, value intstr.IntOrString, schemas map[string]SettingSchema) bool {
+
+	schema, ok := schemas[name]
+	if !ok {
+		// The setting must exist in the status
+		return false
+	}
+
+	if schema.ReadOnly != nil && *schema.ReadOnly == true {
+		return false
+	}
+
+	// Check if valid based on type
+	switch schema.AttributeType {
+	case "Enumeration":
+		for _, av := range schema.AllowableValues {
+			if value.String() == av {
+				return true
+			}
+		}
+		return false
+
+	case "Integer":
+		if schema.LowerBound == nil || schema.UpperBound == nil {
+			// return true if no settings to check validity
+			return true
+		}
+		return (value.IntValue() >= *schema.LowerBound && value.IntValue() <= *schema.UpperBound)
+
+	case "String":
+		if schema.MinLength == nil || schema.MaxLength == nil {
+			// return true if no settings to check validity
+			return true
+		}
+		return (len(value.String()) >= *schema.MinLength && len(value.String()) <= *schema.MaxLength)
+
+	case "Boolean":
+		return (value.String() == "true" || value.String() == "false")
+
+	case "Password":
+		// Prevent sets of password types
+		return false
+
+	case "":
+		// allow the set as BIOS registry fields may not have been available
+		return true
+
+	default:
+		// Unexpected attribute type
+		return false
+	}
+}
+
+//+kubebuilder:object:root=true
+
+// FirmwareSchemaList contains a list of FirmwareSchema
+type FirmwareSchemaList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []FirmwareSchema `json:"items"`
+}
+
+func init() {
+	SchemeBuilder.Register(&FirmwareSchema{}, &FirmwareSchemaList{})
+}
diff --git a/apis/metal3.io/v1alpha1/hostfirmwaresettings_types.go b/apis/metal3.io/v1alpha1/hostfirmwaresettings_types.go
new file mode 100644
index 00000000..1d955899
--- /dev/null
+++ b/apis/metal3.io/v1alpha1/hostfirmwaresettings_types.go
@@ -0,0 +1,81 @@
+/*
+
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1alpha1
+
+import (
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+)
+
+// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.
+
+type SettingsMap map[string]string
+type DesiredSettingsMap map[string]intstr.IntOrString
+
+type SchemaReference struct {
+	// `namespace` is the namespace of the where the schema is stored.
+	Namespace string `json:"namespace"`
+	// `name` is the reference to the schema.
+	Name string `json:"name"`
+}
+
+// HostFirmwareSettingsSpec defines the desired state of HostFirmwareSettings
+type HostFirmwareSettingsSpec struct {
+
+	// Settings are the desired firmware settings stored as name/value pairs.
+	// This will be populated with the actual firmware settings and only
+	// contain the settings that can be modified (i.e. not ReadOnly), to
+	// facilitate making changes.
+	// +patchStrategy=merge
+	Settings DesiredSettingsMap `json:"settings" required:"true"`
+}
+
+// HostFirmwareSettingsStatus defines the observed state of HostFirmwareSettings
+type HostFirmwareSettingsStatus struct {
+	// FirmwareSchema is a reference to the Schema used to describe each
+	// FirmwareSetting. By default, this will be a Schema in the same
+	// Namespace as the settings but it can be overwritten in the Spec
+	FirmwareSchema *SchemaReference `json:"schema,omitempty"`
+
+	// Settings are the actual firmware settings stored as name/value pairs
+	Settings SettingsMap `json:"settings" required:"true"`
+}
+
+//+kubebuilder:object:root=true
+//+kubebuilder:subresource:status
+
+// HostFirmwareSettings is the Schema for the hostfirmwaresettings API
+type HostFirmwareSettings struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec   HostFirmwareSettingsSpec   `json:"spec,omitempty"`
+	Status HostFirmwareSettingsStatus `json:"status,omitempty"`
+}
+
+//+kubebuilder:object:root=true
+
+// HostFirmwareSettingsList contains a list of HostFirmwareSettings
+type HostFirmwareSettingsList struct {
+	metav1.TypeMeta `json:",inline"`
+	metav1.ListMeta `json:"metadata,omitempty"`
+	Items           []HostFirmwareSettings `json:"items"`
+}
+
+func init() {
+	SchemeBuilder.Register(&HostFirmwareSettings{}, &HostFirmwareSettingsList{})
+}
diff --git a/apis/metal3.io/v1alpha1/zz_generated.deepcopy.go b/apis/metal3.io/v1alpha1/zz_generated.deepcopy.go
index c0d9bbf8..44ab715e 100644
--- a/apis/metal3.io/v1alpha1/zz_generated.deepcopy.go
+++ b/apis/metal3.io/v1alpha1/zz_generated.deepcopy.go
@@ -265,6 +265,27 @@ func (in *CustomDeploy) DeepCopy() *CustomDeploy {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in DesiredSettingsMap) DeepCopyInto(out *DesiredSettingsMap) {
+	{
+		in := &in
+		*out = make(DesiredSettingsMap, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DesiredSettingsMap.
+func (in DesiredSettingsMap) DeepCopy() DesiredSettingsMap {
+	if in == nil {
+		return nil
+	}
+	out := new(DesiredSettingsMap)
+	in.DeepCopyInto(out)
+	return *out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Firmware) DeepCopyInto(out *Firmware) {
 	*out = *in
@@ -311,6 +332,86 @@ func (in *FirmwareConfig) DeepCopy() *FirmwareConfig {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *FirmwareSchema) DeepCopyInto(out *FirmwareSchema) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirmwareSchema.
+func (in *FirmwareSchema) DeepCopy() *FirmwareSchema {
+	if in == nil {
+		return nil
+	}
+	out := new(FirmwareSchema)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *FirmwareSchema) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *FirmwareSchemaList) DeepCopyInto(out *FirmwareSchemaList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]FirmwareSchema, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirmwareSchemaList.
+func (in *FirmwareSchemaList) DeepCopy() *FirmwareSchemaList {
+	if in == nil {
+		return nil
+	}
+	out := new(FirmwareSchemaList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *FirmwareSchemaList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *FirmwareSchemaSpec) DeepCopyInto(out *FirmwareSchemaSpec) {
+	*out = *in
+	if in.Schema != nil {
+		in, out := &in.Schema, &out.Schema
+		*out = make(map[string]SettingSchema, len(*in))
+		for key, val := range *in {
+			(*out)[key] = *val.DeepCopy()
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirmwareSchemaSpec.
+func (in *FirmwareSchemaSpec) DeepCopy() *FirmwareSchemaSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(FirmwareSchemaSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *HardwareDetails) DeepCopyInto(out *HardwareDetails) {
 	*out = *in
@@ -386,6 +487,114 @@ func (in *HardwareSystemVendor) DeepCopy() *HardwareSystemVendor {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *HostFirmwareSettings) DeepCopyInto(out *HostFirmwareSettings) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+	in.Spec.DeepCopyInto(&out.Spec)
+	in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostFirmwareSettings.
+func (in *HostFirmwareSettings) DeepCopy() *HostFirmwareSettings {
+	if in == nil {
+		return nil
+	}
+	out := new(HostFirmwareSettings)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *HostFirmwareSettings) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *HostFirmwareSettingsList) DeepCopyInto(out *HostFirmwareSettingsList) {
+	*out = *in
+	out.TypeMeta = in.TypeMeta
+	in.ListMeta.DeepCopyInto(&out.ListMeta)
+	if in.Items != nil {
+		in, out := &in.Items, &out.Items
+		*out = make([]HostFirmwareSettings, len(*in))
+		for i := range *in {
+			(*in)[i].DeepCopyInto(&(*out)[i])
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostFirmwareSettingsList.
+func (in *HostFirmwareSettingsList) DeepCopy() *HostFirmwareSettingsList {
+	if in == nil {
+		return nil
+	}
+	out := new(HostFirmwareSettingsList)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *HostFirmwareSettingsList) DeepCopyObject() runtime.Object {
+	if c := in.DeepCopy(); c != nil {
+		return c
+	}
+	return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *HostFirmwareSettingsSpec) DeepCopyInto(out *HostFirmwareSettingsSpec) {
+	*out = *in
+	if in.Settings != nil {
+		in, out := &in.Settings, &out.Settings
+		*out = make(DesiredSettingsMap, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostFirmwareSettingsSpec.
+func (in *HostFirmwareSettingsSpec) DeepCopy() *HostFirmwareSettingsSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(HostFirmwareSettingsSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *HostFirmwareSettingsStatus) DeepCopyInto(out *HostFirmwareSettingsStatus) {
+	*out = *in
+	if in.FirmwareSchema != nil {
+		in, out := &in.FirmwareSchema, &out.FirmwareSchema
+		*out = new(SchemaReference)
+		**out = **in
+	}
+	if in.Settings != nil {
+		in, out := &in.Settings, &out.Settings
+		*out = make(SettingsMap, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostFirmwareSettingsStatus.
+func (in *HostFirmwareSettingsStatus) DeepCopy() *HostFirmwareSettingsStatus {
+	if in == nil {
+		return nil
+	}
+	out := new(HostFirmwareSettingsStatus)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Image) DeepCopyInto(out *Image) {
 	*out = *in
@@ -562,6 +771,97 @@ func (in *RootDeviceHints) DeepCopy() *RootDeviceHints {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SchemaReference) DeepCopyInto(out *SchemaReference) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SchemaReference.
+func (in *SchemaReference) DeepCopy() *SchemaReference {
+	if in == nil {
+		return nil
+	}
+	out := new(SchemaReference)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SettingSchema) DeepCopyInto(out *SettingSchema) {
+	*out = *in
+	if in.AllowableValues != nil {
+		in, out := &in.AllowableValues, &out.AllowableValues
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
+	if in.LowerBound != nil {
+		in, out := &in.LowerBound, &out.LowerBound
+		*out = new(int)
+		**out = **in
+	}
+	if in.UpperBound != nil {
+		in, out := &in.UpperBound, &out.UpperBound
+		*out = new(int)
+		**out = **in
+	}
+	if in.MinLength != nil {
+		in, out := &in.MinLength, &out.MinLength
+		*out = new(int)
+		**out = **in
+	}
+	if in.MaxLength != nil {
+		in, out := &in.MaxLength, &out.MaxLength
+		*out = new(int)
+		**out = **in
+	}
+	if in.ReadOnly != nil {
+		in, out := &in.ReadOnly, &out.ReadOnly
+		*out = new(bool)
+		**out = **in
+	}
+	if in.ResetRequired != nil {
+		in, out := &in.ResetRequired, &out.ResetRequired
+		*out = new(bool)
+		**out = **in
+	}
+	if in.Unique != nil {
+		in, out := &in.Unique, &out.Unique
+		*out = new(bool)
+		**out = **in
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SettingSchema.
+func (in *SettingSchema) DeepCopy() *SettingSchema {
+	if in == nil {
+		return nil
+	}
+	out := new(SettingSchema)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in SettingsMap) DeepCopyInto(out *SettingsMap) {
+	{
+		in := &in
+		*out = make(SettingsMap, len(*in))
+		for key, val := range *in {
+			(*out)[key] = val
+		}
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SettingsMap.
+func (in SettingsMap) DeepCopy() SettingsMap {
+	if in == nil {
+		return nil
+	}
+	out := new(SettingsMap)
+	in.DeepCopyInto(out)
+	return *out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *SoftwareRAIDVolume) DeepCopyInto(out *SoftwareRAIDVolume) {
 	*out = *in
diff --git a/config/crd/bases/metal3.io_firmwareschemas.yaml b/config/crd/bases/metal3.io_firmwareschemas.yaml
new file mode 100644
index 00000000..02030c87
--- /dev/null
+++ b/config/crd/bases/metal3.io_firmwareschemas.yaml
@@ -0,0 +1,100 @@
+
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.4.1
+  creationTimestamp: null
+  name: firmwareschemas.metal3.io
+spec:
+  group: metal3.io
+  names:
+    kind: FirmwareSchema
+    listKind: FirmwareSchemaList
+    plural: firmwareschemas
+    singular: firmwareschema
+  scope: Namespaced
+  versions:
+  - name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        description: FirmwareSchema is the Schema for the firmwareschemas 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: FirmwareSchemaSpec defines the desired state of FirmwareSchema
+            properties:
+              hardwareModel:
+                description: The hardware model associated with this schema
+                type: string
+              hardwareVendor:
+                description: The hardware vendor associated with this schema
+                type: string
+              schema:
+                additionalProperties:
+                  description: Additional data describing the firmware setting
+                  properties:
+                    allowable_values:
+                      description: The allowable value for an Enumeration type setting.
+                      items:
+                        type: string
+                      type: array
+                    attribute_type:
+                      description: The type of setting.
+                      enum:
+                      - Enumeration
+                      - String
+                      - Integer
+                      - Boolean
+                      - Password
+                      type: string
+                    lower_bound:
+                      description: The lowest value for an Integer type setting.
+                      type: integer
+                    max_length:
+                      description: Maximum length for a String type setting.
+                      type: integer
+                    min_length:
+                      description: Minimum length for a String type setting.
+                      type: integer
+                    read_only:
+                      description: Whether or not this setting is read only.
+                      type: boolean
+                    reset_required:
+                      description: Whether or not a reset is required after changing
+                        this setting.
+                      type: boolean
+                    unique:
+                      description: Whether or not this setting's value is unique to
+                        this node, e.g. a serial number.
+                      type: boolean
+                    upper_bound:
+                      description: The highest value for an Integer type setting.
+                      type: integer
+                  type: object
+                description: Map of firmware name to schema
+                type: object
+            required:
+            - schema
+            type: object
+        type: object
+    served: true
+    storage: true
+status:
+  acceptedNames:
+    kind: ""
+    plural: ""
+  conditions: []
+  storedVersions: []
diff --git a/config/crd/bases/metal3.io_hostfirmwaresettings.yaml b/config/crd/bases/metal3.io_hostfirmwaresettings.yaml
new file mode 100644
index 00000000..e8dea176
--- /dev/null
+++ b/config/crd/bases/metal3.io_hostfirmwaresettings.yaml
@@ -0,0 +1,93 @@
+
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.4.1
+  creationTimestamp: null
+  name: hostfirmwaresettings.metal3.io
+spec:
+  group: metal3.io
+  names:
+    kind: HostFirmwareSettings
+    listKind: HostFirmwareSettingsList
+    plural: hostfirmwaresettings
+    singular: hostfirmwaresettings
+  scope: Namespaced
+  versions:
+  - name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        description: HostFirmwareSettings is the Schema for the hostfirmwaresettings
+          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: HostFirmwareSettingsSpec defines the desired state of HostFirmwareSettings
+            properties:
+              settings:
+                additionalProperties:
+                  anyOf:
+                  - type: integer
+                  - type: string
+                  x-kubernetes-int-or-string: true
+                description: Settings are the desired firmware settings stored as
+                  name/value pairs. This will be populated with the actual firmware
+                  settings and only contain the settings that can be modified (i.e.
+                  not ReadOnly), to facilitate making changes.
+                type: object
+            required:
+            - settings
+            type: object
+          status:
+            description: HostFirmwareSettingsStatus defines the observed state of
+              HostFirmwareSettings
+            properties:
+              schema:
+                description: FirmwareSchema is a reference to the Schema used to describe
+                  each FirmwareSetting. By default, this will be a Schema in the same
+                  Namespace as the settings but it can be overwritten in the Spec
+                properties:
+                  name:
+                    description: '`name` is the reference to the schema.'
+                    type: string
+                  namespace:
+                    description: '`namespace` is the namespace of the where the schema
+                      is stored.'
+                    type: string
+                required:
+                - name
+                - namespace
+                type: object
+              settings:
+                additionalProperties:
+                  type: string
+                description: Settings are the actual firmware settings stored as name/value
+                  pairs
+                type: object
+            required:
+            - settings
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+status:
+  acceptedNames:
+    kind: ""
+    plural: ""
+  conditions: []
+  storedVersions: []
diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml
index d6d78cc5..29db00a2 100644
--- a/config/crd/kustomization.yaml
+++ b/config/crd/kustomization.yaml
@@ -3,17 +3,23 @@
 # It should be run by config/default
 resources:
 - bases/metal3.io_baremetalhosts.yaml
+- bases/metal3.io_hostfirmwaresettings.yaml
+- bases/metal3.io_firmwareschemas.yaml
 # +kubebuilder:scaffold:crdkustomizeresource
 
 patchesStrategicMerge:
 # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
 # patches here are for enabling the conversion webhook for each CRD
 #- patches/webhook_in_baremetalhosts.yaml
+#- patches/webhook_in_hostfirmwaresettings.yaml
+#- patches/webhook_in_firmwareschemas.yaml
 # +kubebuilder:scaffold:crdkustomizewebhookpatch
 
 # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix.
 # patches here are for enabling the CA injection for each CRD
 #- patches/cainjection_in_baremetalhosts.yaml
+#- patches/cainjection_in_hostfirmwaresettings.yaml
+#- patches/cainjection_in_firmwareschemas.yaml
 # +kubebuilder:scaffold:crdkustomizecainjectionpatch
 
 # the following config is for teaching kustomize how to do kustomization for CRDs.
diff --git a/config/crd/patches/cainjection_in_firmwareschemas.yaml b/config/crd/patches/cainjection_in_firmwareschemas.yaml
new file mode 100644
index 00000000..dd50dd1a
--- /dev/null
+++ b/config/crd/patches/cainjection_in_firmwareschemas.yaml
@@ -0,0 +1,8 @@
+# The following patch adds a directive for certmanager to inject CA into the CRD
+# CRD conversion requires k8s 1.13 or later.
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
+  name: firmwareschemas.metal3.io
diff --git a/config/crd/patches/cainjection_in_hostfirmwaresettings.yaml b/config/crd/patches/cainjection_in_hostfirmwaresettings.yaml
new file mode 100644
index 00000000..4d81f6e4
--- /dev/null
+++ b/config/crd/patches/cainjection_in_hostfirmwaresettings.yaml
@@ -0,0 +1,8 @@
+# The following patch adds a directive for certmanager to inject CA into the CRD
+# CRD conversion requires k8s 1.13 or later.
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
+  name: hostfirmwaresettings.metal3.io
diff --git a/config/crd/patches/webhook_in_firmwareschemas.yaml b/config/crd/patches/webhook_in_firmwareschemas.yaml
new file mode 100644
index 00000000..f65cf2fb
--- /dev/null
+++ b/config/crd/patches/webhook_in_firmwareschemas.yaml
@@ -0,0 +1,17 @@
+# The following patch enables conversion webhook for CRD
+# CRD conversion requires k8s 1.13 or later.
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: firmwareschemas.metal3.io
+spec:
+  conversion:
+    strategy: Webhook
+    webhookClientConfig:
+      # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,
+      # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
+      caBundle: Cg==
+      service:
+        namespace: system
+        name: webhook-service
+        path: /convert
diff --git a/config/crd/patches/webhook_in_hostfirmwaresettings.yaml b/config/crd/patches/webhook_in_hostfirmwaresettings.yaml
new file mode 100644
index 00000000..2e32829d
--- /dev/null
+++ b/config/crd/patches/webhook_in_hostfirmwaresettings.yaml
@@ -0,0 +1,17 @@
+# The following patch enables conversion webhook for CRD
+# CRD conversion requires k8s 1.13 or later.
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  name: hostfirmwaresettings.metal3.io
+spec:
+  conversion:
+    strategy: Webhook
+    webhookClientConfig:
+      # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,
+      # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
+      caBundle: Cg==
+      service:
+        namespace: system
+        name: webhook-service
+        path: /convert
diff --git a/config/rbac/firmwareschema_editor_role.yaml b/config/rbac/firmwareschema_editor_role.yaml
new file mode 100644
index 00000000..be313f63
--- /dev/null
+++ b/config/rbac/firmwareschema_editor_role.yaml
@@ -0,0 +1,24 @@
+# permissions for end users to edit firmwareschemas.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: firmwareschema-editor-role
+rules:
+- apiGroups:
+  - metal3.io
+  resources:
+  - firmwareschemas
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - metal3.io
+  resources:
+  - firmwareschemas/status
+  verbs:
+  - get
diff --git a/config/rbac/firmwareschema_viewer_role.yaml b/config/rbac/firmwareschema_viewer_role.yaml
new file mode 100644
index 00000000..90691038
--- /dev/null
+++ b/config/rbac/firmwareschema_viewer_role.yaml
@@ -0,0 +1,20 @@
+# permissions for end users to view firmwareschemas.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: firmwareschema-viewer-role
+rules:
+- apiGroups:
+  - metal3.io
+  resources:
+  - firmwareschemas
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metal3.io
+  resources:
+  - firmwareschemas/status
+  verbs:
+  - get
diff --git a/config/rbac/hostfirmwaresettings_editor_role.yaml b/config/rbac/hostfirmwaresettings_editor_role.yaml
new file mode 100644
index 00000000..64b1d5da
--- /dev/null
+++ b/config/rbac/hostfirmwaresettings_editor_role.yaml
@@ -0,0 +1,24 @@
+# permissions for end users to edit hostfirmwaresettings.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: hostfirmwaresettings-editor-role
+rules:
+- apiGroups:
+  - metal3.io
+  resources:
+  - hostfirmwaresettings
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - metal3.io
+  resources:
+  - hostfirmwaresettings/status
+  verbs:
+  - get
diff --git a/config/rbac/hostfirmwaresettings_viewer_role.yaml b/config/rbac/hostfirmwaresettings_viewer_role.yaml
new file mode 100644
index 00000000..5235eb6b
--- /dev/null
+++ b/config/rbac/hostfirmwaresettings_viewer_role.yaml
@@ -0,0 +1,20 @@
+# permissions for end users to view hostfirmwaresettings.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: hostfirmwaresettings-viewer-role
+rules:
+- apiGroups:
+  - metal3.io
+  resources:
+  - hostfirmwaresettings
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - metal3.io
+  resources:
+  - hostfirmwaresettings/status
+  verbs:
+  - get
diff --git a/config/render/capm3.yaml b/config/render/capm3.yaml
index 786dc918..85bb20db 100644
--- a/config/render/capm3.yaml
+++ b/config/render/capm3.yaml
@@ -1078,6 +1078,197 @@ status:
   conditions: []
   storedVersions: []
 ---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.4.1
+  creationTimestamp: null
+  name: firmwareschemas.metal3.io
+spec:
+  group: metal3.io
+  names:
+    kind: FirmwareSchema
+    listKind: FirmwareSchemaList
+    plural: firmwareschemas
+    singular: firmwareschema
+  scope: Namespaced
+  versions:
+  - name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        description: FirmwareSchema is the Schema for the firmwareschemas 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: FirmwareSchemaSpec defines the desired state of FirmwareSchema
+            properties:
+              hardwareModel:
+                description: The hardware model associated with this schema
+                type: string
+              hardwareVendor:
+                description: The hardware vendor associated with this schema
+                type: string
+              schema:
+                additionalProperties:
+                  description: Additional data describing the firmware setting
+                  properties:
+                    allowable_values:
+                      description: The allowable value for an Enumeration type setting.
+                      items:
+                        type: string
+                      type: array
+                    attribute_type:
+                      description: The type of setting.
+                      enum:
+                      - Enumeration
+                      - String
+                      - Integer
+                      - Boolean
+                      - Password
+                      type: string
+                    lower_bound:
+                      description: The lowest value for an Integer type setting.
+                      type: integer
+                    max_length:
+                      description: Maximum length for a String type setting.
+                      type: integer
+                    min_length:
+                      description: Minimum length for a String type setting.
+                      type: integer
+                    read_only:
+                      description: Whether or not this setting is read only.
+                      type: boolean
+                    reset_required:
+                      description: Whether or not a reset is required after changing
+                        this setting.
+                      type: boolean
+                    unique:
+                      description: Whether or not this setting's value is unique to
+                        this node, e.g. a serial number.
+                      type: boolean
+                    upper_bound:
+                      description: The highest value for an Integer type setting.
+                      type: integer
+                  type: object
+                description: Map of firmware name to schema
+                type: object
+            required:
+            - schema
+            type: object
+        type: object
+    served: true
+    storage: true
+status:
+  acceptedNames:
+    kind: ""
+    plural: ""
+  conditions: []
+  storedVersions: []
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.4.1
+  creationTimestamp: null
+  name: hostfirmwaresettings.metal3.io
+spec:
+  group: metal3.io
+  names:
+    kind: HostFirmwareSettings
+    listKind: HostFirmwareSettingsList
+    plural: hostfirmwaresettings
+    singular: hostfirmwaresettings
+  scope: Namespaced
+  versions:
+  - name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        description: HostFirmwareSettings is the Schema for the hostfirmwaresettings
+          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: HostFirmwareSettingsSpec defines the desired state of HostFirmwareSettings
+            properties:
+              settings:
+                additionalProperties:
+                  anyOf:
+                  - type: integer
+                  - type: string
+                  x-kubernetes-int-or-string: true
+                description: Settings are the desired firmware settings stored as
+                  name/value pairs. This will be populated with the actual firmware
+                  settings and only contain the settings that can be modified (i.e.
+                  not ReadOnly), to facilitate making changes.
+                type: object
+            required:
+            - settings
+            type: object
+          status:
+            description: HostFirmwareSettingsStatus defines the observed state of
+              HostFirmwareSettings
+            properties:
+              schema:
+                description: FirmwareSchema is a reference to the Schema used to describe
+                  each FirmwareSetting. By default, this will be a Schema in the same
+                  Namespace as the settings but it can be overwritten in the Spec
+                properties:
+                  name:
+                    description: '`name` is the reference to the schema.'
+                    type: string
+                  namespace:
+                    description: '`namespace` is the namespace of the where the schema
+                      is stored.'
+                    type: string
+                required:
+                - name
+                - namespace
+                type: object
+              settings:
+                additionalProperties:
+                  type: string
+                description: Settings are the actual firmware settings stored as name/value
+                  pairs
+                type: object
+            required:
+            - settings
+            type: object
+        type: object
+    served: true
+    storage: true
+    subresources:
+      status: {}
+status:
+  acceptedNames:
+    kind: ""
+    plural: ""
+  conditions: []
+  storedVersions: []
+---
 apiVersion: rbac.authorization.k8s.io/v1
 kind: Role
 metadata:
diff --git a/config/samples/metal3.io_v1alpha1_firmwareschema.yaml b/config/samples/metal3.io_v1alpha1_firmwareschema.yaml
new file mode 100644
index 00000000..f5777284
--- /dev/null
+++ b/config/samples/metal3.io_v1alpha1_firmwareschema.yaml
@@ -0,0 +1,34 @@
+apiVersion: metal3.io/v1alpha1
+kind: FirmwareSchema
+metadata:
+  name: firmwareschema-sample
+spec:
+status:
+  referenceCount: 2
+  hardwareVendor: "VendorA"
+  hardwareModel: "ModelT"
+  schema:
+    - "ProcVirtualization":
+         attribute_type: "Enumeration"
+         allowable_values:
+           - "Enabled"
+           - "Disabled"
+         read_only: false
+    - "ProcCPUCores":
+         attribute_type: "Integer"
+         lower_bound: 1
+         read_only: true
+         upper_bound: 100
+    - "SRIOV":
+         allowable_values:
+           - "Enabled"
+           - "Disabled"
+    - "SystemModelName":
+         attribute_type: "String"
+         max_length: 40
+         min_length: 0
+         read_only: true
+    - "SerialNumber":
+         attribute_type: "String"
+         max_length: 16
+         min_length: 0
diff --git a/config/samples/metal3.io_v1alpha1_hostfirmwaresettings.yaml b/config/samples/metal3.io_v1alpha1_hostfirmwaresettings.yaml
new file mode 100644
index 00000000..268335bf
--- /dev/null
+++ b/config/samples/metal3.io_v1alpha1_hostfirmwaresettings.yaml
@@ -0,0 +1,17 @@
+apiVersion: metal3.io/v1alpha1
+kind: HostFirmwareSettings
+metadata:
+  name: hostfirmwaresettings-sample
+spec:
+  settings:
+    - "ProcVirtualization": "Enabled"
+    - "SRIOV": "Enabled"
+    - "NetworkBootRetryCount": "20"
+status:
+  settings:
+    - "ProcVirtualization": "Enabled"
+    - "ProcCPUCores": "100"
+    - "SRIOV": "Enabled"
+    - "SystemModelName":  "QYZ12345"
+    - "SerialNumber": "22654891"
+    - "NetworkBootRetryCount": "10"


Test patch:
diff --git a/apis/metal3.io/v1alpha1/hostfirmwaresettings_types_test.go b/apis/metal3.io/v1alpha1/hostfirmwaresettings_types_test.go
new file mode 100644
index 00000000..6d743f1a
--- /dev/null
+++ b/apis/metal3.io/v1alpha1/hostfirmwaresettings_types_test.go
@@ -0,0 +1,137 @@
+package v1alpha1
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/intstr"
+)
+
+func TestCheckSettingIsValid(t *testing.T) {
+
+	lower_bound := 1
+	upper_bound := 20
+	min_length := 1
+	max_length := 16
+	read_only := true
+
+	fwSchema := &FirmwareSchema{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "myfwschema",
+			Namespace: "myns",
+		},
+		Spec: FirmwareSchemaSpec{
+			Schema: make(map[string]SettingSchema),
+		},
+	}
+
+	fwSchema.Spec.Schema["AssetTag"] = SettingSchema{AttributeType: "String",
+		MinLength: &min_length, MaxLength: &max_length}
+	fwSchema.Spec.Schema["ProcVirtualization"] = SettingSchema{AttributeType: "Enumeration",
+		AllowableValues: []string{"Enabled", "Disabled"}}
+	fwSchema.Spec.Schema["NetworkBootRetryCount"] = SettingSchema{AttributeType: "Integer",
+		LowerBound: &lower_bound, UpperBound: &upper_bound}
+	fwSchema.Spec.Schema["SerialNumber"] = SettingSchema{AttributeType: "String",
+		MinLength: &min_length, MaxLength: &max_length, ReadOnly: &read_only}
+	fwSchema.Spec.Schema["QuietBoot"] = SettingSchema{AttributeType: "Boolean"}
+	fwSchema.Spec.Schema["SriovEnable"] = SettingSchema{} // fields intentionally excluded
+
+	for _, tc := range []struct {
+		Scenario string
+		Name     string
+		Value    intstr.IntOrString
+		Expected bool
+	}{
+		{
+			Scenario: "StringTypePass",
+			Name:     "AssetTag",
+			Value:    intstr.FromString("NewServer"),
+			Expected: true,
+		},
+		{
+			Scenario: "StringTypeFailUpper",
+			Name:     "AssetTag",
+			Value:    intstr.FromString("NewServerPutInServiceIn2021"),
+			Expected: false,
+		},
+		{
+			Scenario: "StringTypeFailLower",
+			Name:     "AssetTag",
+			Value:    intstr.FromString(""),
+			Expected: false,
+		},
+		{
+			Scenario: "EnumerationTypePass",
+			Name:     "ProcVirtualization",
+			Value:    intstr.FromString("Disabled"),
+			Expected: true,
+		},
+		{
+			Scenario: "EnumerationTypeFail",
+			Name:     "ProcVirtualization",
+			Value:    intstr.FromString("Foo"),
+			Expected: false,
+		},
+		{
+			Scenario: "IntegerTypePassAsString",
+			Name:     "NetworkBootRetryCount",
+			Value:    intstr.FromString("10"),
+			Expected: true,
+		},
+		{
+			Scenario: "IntegerTypePassAsInt",
+			Name:     "NetworkBootRetryCount",
+			Value:    intstr.FromInt(10),
+			Expected: true,
+		},
+		{
+			Scenario: "IntegerTypeFailUpper",
+			Name:     "NetworkBootRetryCount",
+			Value:    intstr.FromString("42"),
+			Expected: false,
+		},
+		{
+			Scenario: "IntegerTypeFailLower",
+			Name:     "NetworkBootRetryCount",
+			Value:    intstr.FromInt(0),
+			Expected: false,
+		},
+		{
+			Scenario: "BooleanTypePass",
+			Name:     "QuietBoot",
+			Value:    intstr.FromString("true"),
+			Expected: true,
+		},
+		{
+			Scenario: "BooleanTypeFail",
+			Name:     "QuietBoot",
+			Value:    intstr.FromString("Enabled"),
+			Expected: false,
+		},
+		{
+			Scenario: "ReadOnlyTypeFail",
+			Name:     "SerialNumber",
+			Value:    intstr.FromString("42"),
+			Expected: false,
+		},
+		{
+			Scenario: "MissingEnumerationField",
+			Name:     "SriovEnable",
+			Value:    intstr.FromString("Disabled"),
+			Expected: true,
+		},
+		{
+			Scenario: "UnknownSettingFail",
+			Name:     "IceCream",
+			Value:    intstr.FromString("Vanilla"),
+			Expected: false,
+		},
+	} {
+		t.Run(tc.Scenario, func(t *testing.T) {
+			actual := fwSchema.CheckSettingIsValid(tc.Name, tc.Value, fwSchema.Spec.Schema)
+			assert.Equal(t, tc.Expected, actual)
+		})
+	}
+}