[
  {
    "path": ".devcontainer/Dockerfile",
    "content": "FROM mcr.microsoft.com/devcontainers/go:1-1.22-bullseye\nRUN apt update\nRUN apt install ipmitool -y\n\n"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "// For format details, see https://aka.ms/devcontainer.json. For config options, see the\n// README at: https://github.com/devcontainers/templates/tree/main/src/go\n{\n\t\"name\": \"Go\",\n\t// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile\n\t// \"image\": \"mcr.microsoft.com/devcontainers/go:1-1.21-bullseye\",\n\t\"build\": {\n\t\t\"dockerfile\": \"Dockerfile\"\n\t},\n\n\t// Features to add to the dev container. More info: https://containers.dev/features.\n\t// \"features\": {},\n\n\t// Use 'forwardPorts' to make a list of ports inside the container available locally.\n\t// \"forwardPorts\": [],\n\n\t// Use 'postCreateCommand' to run commands after the container is created.\n\t// \"postCreateCommand\": \"go version\",\n\n\t// Configure tool-specific properties.\n\t\"customizations\": {\n\t\t\"vscode\": {\n\t\t\t\"extensions\": [\n\t\t\t\t\"ms-vscode.makefile-tools\",\n\t\t\t\t\"zxh404.vscode-proto3\",\n\t\t\t\t\"humao.rest-client\"\n\t\t\t]\n\t\t}\n\t}\n\n\t// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.\n\t// \"remoteUser\": \"root\"\n}\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/default.md",
    "content": "Please don't forget to include the following information in your issue:\n\n- The HW vendor impacted by this issue (if applicable)\n\n- The HW model number, BMC firmware and/or BIOS versions impacted by this issue (if applicable)\n\n- What version of bmclib exhibits this behavior (if applicable)\n\n- Detailed steps to verify it\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "## What does this PR implement/change/remove?\n\n### Checklist\n- [ ] Tests added\n- [ ] Similar commits squashed\n\n### The HW vendor this change applies to (if applicable)\n\n### The HW model number, product name this change applies to (if applicable)\n\n### The BMC firmware and/or BIOS versions that this change applies to (if applicable)\n\n### What version of tooling - vendor specific or opensource does this change depend on (if applicable)\n\n## Description for changelog/release notes\n\n```\n```\n"
  },
  {
    "path": ".github/mergify.yml",
    "content": "queue_rules:\n  - name: default\n    batch_size: 1\n    queue_conditions:\n      - base=main\n      - \"#approved-reviews-by>=1\"\n      - \"#changes-requested-reviews-by=0\"\n      - check-success='lint'\n      - check-success='test'\n      - label!=do-not-merge\n      - label=ready-to-merge\n    merge_conditions:\n      - base=main\n      - \"#approved-reviews-by>=1\"\n      - \"#changes-requested-reviews-by=0\"\n      - check-success='lint'\n      - check-success='test'\n      - label!=do-not-merge\n      - label=ready-to-merge\n    merge_method: merge\n    commit_message_template: |\n      {{ title }} (#{{ number }})\n\npull_request_rules:\n  - name: refactored queue action rule\n    conditions: []\n    actions:\n      queue:\nmerge_queue:\n  max_parallel_checks: 1\n"
  },
  {
    "path": ".github/workflows/ci.yaml",
    "content": "name: For each commit and PR\non:\n  push:\n  pull_request:\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    env:\n      CGO_ENABLED: 1\n    steps:\n    - name: Checkout code\n      uses: actions/checkout@v3\n    - name: Install Go\n      uses: actions/setup-go@v3\n      with:\n        go-version-file: go.mod\n    - name: Run golangci-lint\n      uses: golangci/golangci-lint-action@v3\n      with:\n        args: -v --config .golangci.yml --timeout=5m\n        version: latest\n    - name: make all-checks\n      run: make all-checks\n  test:\n    runs-on: ubuntu-latest\n    env:\n      CGO_ENABLED: 1\n    steps:\n    - name: Checkout code\n      uses: actions/checkout@v3\n      with:\n        fetch-depth: 2\n    - name: Install Go\n      uses: actions/setup-go@v3\n      with:\n        go-version-file: go.mod\n    - name: make all-tests\n      run: make all-tests\n    - name: upload codecov\n      run: bash <(curl -s https://codecov.io/bash)\n"
  },
  {
    "path": ".gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, build with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n# Swap\n[._]*.s[a-v][a-z]\n[._]*.sw[a-p]\n[._]s[a-rt-v][a-z]\n[._]ss[a-gi-z]\n[._]sw[a-p]\n\n# Session\nSession.vim\n\n# Temporary\n.netrwhist\n*~\n# Auto-generated tag files\ntags\n# Persistent undo\n[._]*.un~\n\n# vscode\n*.code-workspace\n# added by lint-install\nout/\n\ncoverage.txt\n"
  },
  {
    "path": ".golangci.yml",
    "content": "  govet:\n    auto-fix: true\n    linters-settings:\n    enable:\n     - fieldalignment\n    check-shadowing: true\n    settings:\n      printf:\n        funcs:\n          - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof\n          - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf\n          - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf\n          - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf\n  golint:\n    min-confidence: 0\n  gocyclo:\n    min-complexity: 10\n  maligned:\n    suggest-new: true\n  dupl:\n    threshold: 100\n  goconst:\n    min-len: 2\n    min-occurrences: 2\n  depguard:\n    list-type: blacklist\n    packages:\n      # logging is allowed only by logutils.Log, logrus\n      # is allowed to use only in logutils package\n      - github.com/sirupsen/logrus\n  misspell:\n    locale: US\n    auto-fix: true\n  lll:\n    line-length: 140\n  goimports:\n    local-prefixes: github.com/golangci/golangci-lint\n  gocritic:\n    auto-fix: true\n    enabled-tags:\n      - performance\n      - style\n      - experimental\n    disabled-checks:\n      - wrapperFunc\n  gofumpt:\n    extra-rules: true\n    auto-fix: true\n  wsl:\n    auto-fix: true\n  stylecheck:\n    auto-fix: true\n\nlinters:\n  enable:\n    - errcheck\n    - gosimple\n    - govet\n    - gofmt\n    - gocyclo\n    - ineffassign\n    - stylecheck\n    - deadcode\n    - staticcheck\n    - structcheck\n    - unused\n    - prealloc\n    - typecheck\n    - varcheck\n    # additional linters\n    - bodyclose\n    - gocritic\n    - whitespace\n    - wsl\n    - goimports\n    - golint\n    - misspell\n    - goerr113\n    - noctx\n  enable-all: false\n  disable-all: true\n\nrun:\n  skip-dirs:\n\n\nissues:\n  exclude-rules:\n    - linters:\n        - gosec\n      text: \"weak cryptographic primitive\"\n\n    - linters:\n        - stylecheck\n      text: \"ST1016\"\n  exclude:\n    # Default excludes from `golangci-lint run --help` with EXC0002 removed\n    # EXC0001 errcheck: Almost all programs ignore errors on these functions and in most cases it's ok\n    - Error return value of .((os\\.)?std(out|err)\\..*|.*Close|.*Flush|os\\.Remove(All)?|.*print(f|ln)?|os\\.(Un)?Setenv). is not checked\n    # EXC0002 golint: Annoying issue about not having a comment. The rare codebase has such comments\n    # - (comment on exported (method|function|type|const)|should have( a package)? comment|comment should be of the form)\n    # EXC0003 golint: False positive when tests are defined in package 'test'\n    - func name will be used as test\\.Test.* by other packages, and that stutters; consider calling this\n    # EXC0004 govet: Common false positives\n    - (possible misuse of unsafe.Pointer|should have signature)\n    # EXC0005 staticcheck: Developers tend to write in C-style with an explicit 'break' in a 'switch', so it's ok to ignore\n    - ineffective break statement. Did you mean to break out of the outer loop\n    # EXC0006 gosec: Too many false-positives on 'unsafe' usage\n    - Use of unsafe calls should be audited\n    # EXC0007 gosec: Too many false-positives for parametrized shell calls\n    - Subprocess launch(ed with variable|ing should be audited)\n    # EXC0008 gosec: Duplicated errcheck checks\n    - (G104|G307)\n    # EXC0009 gosec: Too many issues in popular repos\n    - (Expect directory permissions to be 0750 or less|Expect file permissions to be 0600 or less)\n    # EXC0010 gosec: False positive is triggered by 'src, err := ioutil.ReadFile(filename)'\n    - Potential file inclusion via variable\nexclude-use-default: false\n\n# golangci.com configuration\n# https://github.com/golangci/golangci/wiki/Configuration\n#service:\n#  golangci-lint-version: 1.15.x # use the fixed version to not introduce new linters unexpectedly\n#  prepare:\n#    - echo \"here I can run custom commands, but no preparation needed for this repo\"\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [2022] [bmclib authors]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "Makefile",
    "content": "help:\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | cut -d\":\" -f2,3 | awk 'BEGIN {FS = \":.*?## \"}; {printf \"\\033[32m%-30s\\033[0m %s\\n\", $$1, $$2}'\n\n.PHONY: test\ntest: ## Run unit tests\n\tgo test -v -covermode=atomic -race ./...\n\n.PHONY: cover\ncover: ## Run unit tests with coverage report\n\tgo test -coverprofile=coverage.txt ./...\n\tgo tool cover -func=coverage.txt\n\n.PHONY: all-tests\nall-tests: test cover ## run all tests\n\n.PHONY: all-checks\nall-checks: lint ## run all formatters\n\tgo mod tidy\n\tgo vet ./...\n\n-include lint.mk"
  },
  {
    "path": "README.md",
    "content": "# bmclib v2 - board management controller library\n\n[![Status](https://github.com/bmc-toolbox/bmclib/actions/workflows/ci.yaml/badge.svg)](https://github.com/bmc-toolbox/bmclib/actions)\n[![Go Report Card](https://goreportcard.com/badge/github.com/bmc-toolbox/bmclib)](https://goreportcard.com/report/github.com/bmc-toolbox/bmclib/v2)\n[![GoDoc](https://godoc.org/github.com/bmc-toolbox/bmclib/v2?status.svg)](https://godoc.org/github.com/bmc-toolbox/bmclib/v2)\n\nbmclib v2 is a library to abstract interacting with baseboard management controllers.\n\n## Supported BMC interfaces.\n\n - [Redfish](https://github.com/bmc-toolbox/bmclib/tree/main/providers/redfish)\n - [IPMItool](https://github.com/bmc-toolbox/bmclib/tree/main/providers/ipmitool)\n - [Intel AMT](https://github.com/bmc-toolbox/bmclib/tree/main/providers/intelamt)\n - [Asrockrack](https://github.com/bmc-toolbox/bmclib/tree/main/providers/asrockrack)\n - [RPC](providers/rpc/)\n\n## Installation\n\n```bash\ngo get github.com/bmc-toolbox/bmclib/v2\n```\n\n## Import\n\n```go\nimport (\n  bmclib \"github.com/bmc-toolbox/bmclib/v2\"\n)\n```\n\n### Usage\n\nThe snippet below connects to a BMC and retrieves the device hardware, firmware inventory.\n\n```go\nimport (\n  bmclib \"github.com/bmc-toolbox/bmclib/v2\"\n)\n\n    // setup logger\n    l := logrus.New()\n    l.Level = logrus.DebugLevel\n    logger := logrusr.New(l)\n\n    clientOpts := []bmclib.Option{bmclib.WithLogger(logger)}\n\n    // init client\n    client := bmclib.NewClient(*host, \"admin\", \"hunter2\", clientOpts...)\n\n    // open BMC session\n    err := client.Open(ctx)\n    if err != nil {\n        log.Fatal(err, \"bmc login failed\")\n    }\n\n    defer client.Close(ctx)\n\n    // retrieve inventory data\n    inventory, err := client.Inventory(ctx)\n    if err != nil {\n        l.Error(err)\n    }\n\n    b, err := json.MarshalIndent(inventory, \"\", \"  \")\n    if err != nil {\n        l.Error(err)\n    }\n\n    fmt.Println(string(b))\n```\n\nMore sample code can be found in [examples](./examples/)\n\n## BMC connections\n\nbmclib performs queries on BMCs using [multiple `drivers`](https://github.com/bmc-toolbox/bmclib/blob/main/bmc/connection.go#L30),\nthese `drivers` are the various services exposed by a BMC - `redfish` `IPMI` `SSH` and `vendor API` which is basically a custom vendor API endpoint.\n\nThe bmclib client determines which driver to use for an action like `Power cycle` or `Create user`\nbased on its availability or through a compatibility test (when enabled).\n\nWhen querying multiple BMCs through bmclib its often useful to to limit the BMCs and\ndrivers that bmclib will attempt to use to connect, the options to limit or filter\nout BMCs are described below,\n\nQuery just using the `redfish` endpoint.\n\n```go\ncl := bmclib.NewClient(\"192.168.1.1\", \"admin\", \"hunter2\")\ncl.Registry.Drivers = cl.Registry.Using(\"redfish\")\n```\n\nQuery using the `redfish` endpoint and fall back to `IPMI`\n\n```go\nclient := bmclib.NewClient(\"192.168.1.1\", \"admin\", \"hunter2\")\n\n// overwrite registered drivers by appending Redfish, IPMI drivers in order\ndrivers := append(registrar.Drivers{}, bmcClient.Registry.Using(\"redfish\")...)\ndrivers = append(drivers, bmcClient.Registry.Using(\"ipmi\")...)\nclient.Registry.Drivers = driver\n```\n\nFilter drivers to query based on compatibility, this will attempt to check if the driver is\n[compatible](https://github.com/bmc-toolbox/bmclib/blob/main/providers/redfish/redfish.go#L70)\nideally, this method should be invoked when the client is ready to perform a BMC action.\n\n```go\nclient := bmclib.NewClient(\"192.168.1.1\", \"admin\", \"hunter2\")\nclient.Registry.Drivers = cl.Registry.FilterForCompatible(ctx)\n```\n\nIgnore the Redfish endpoint completely on BMCs running a specific Redfish version.\n\nNote: this version should match the one returned through `curl -k  \"https://<BMC IP>/redfish/v1\" | jq .RedfishVersion`\n\n```go\nopt := bmclib.WithRedfishVersionsNotCompatible([]string{\"1.5.0\"})\n\nclient := bmclib.NewClient(\"192.168.1.1\", \"admin\", \"hunter2\", opt...)\ncl.Registry.Drivers = cl.Registry.FilterForCompatible(ctx)\n```\n\n## Timeouts\n\nbmclib can be configured to apply timeouts to BMC interactions. The following options are available.\n\n**Total max timeout only** - The total time bmclib will wait for all BMC interactions to complete. This is specified using a single `context.WithTimeout` or `context.WithDeadline` that is passed to all method call. With this option, the per provider; per interaction timeout is calculated by the total max timeout divided by the number of providers (currently there are 4 providers).\n\n```go\ncl := bmclib.NewClient(host, user, pass, bmclib.WithLogger(log))\n\nctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\ndefer cancel()\n\nif err = cl.Open(ctx); err != nil {\n  return(err)\n}\ndefer cl.Close(ctx)\n\nstate, err := cl.GetPowerState(ctx)\n```\n\n**Total max timeout and a per provider; per interaction timeout** - The total time bmclib will wait for all BMC interactions to complete. This is specified using a single `context.WithTimeout` or `context.WithDeadline` that is passed to all method call. This is honored above all timeouts. The per provider; per interaction timeout is specified using `bmclib.WithPerProviderTimeout` in the Client constructor.\n\n```go\ncl := bmclib.NewClient(host, user, pass, bmclib.WithLogger(log), bmclib.WithPerProviderTimeout(15*time.Second))\n\nctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)\ndefer cancel()\n\nif err = cl.Open(ctx); err != nil {\n  return(err)\n}\ndefer cl.Close(ctx)\n\nstate, err := cl.GetPowerState(ctx)\n```\n\n**Per provider; per interaction timeout. No total max timeout** - The time bmclib will wait for a specific provider to complete. This is specified using `bmclib.WithPerProviderTimeout` in the Client constructor.\n\n```go\ncl := bmclib.NewClient(host, user, pass, bmclib.WithLogger(log), bmclib.WithPerProviderTimeout(15*time.Second))\n\nctx := context.Background()\n\nif err = cl.Open(ctx); err != nil {\n  return(err)\n}\ndefer cl.Close(ctx)\n\nstate, err := cl.GetPowerState(ctx)\n```\n\n**Default timeout** - If no timeout is specified with a context or with `bmclib.WithPerProviderTimeout` the default is used. 30 seconds per provider; per interaction.\n\n```go\ncl := bmclib.NewClient(host, user, pass, bmclib.WithLogger(log))\n\nctx := context.Background()\n\nif err = cl.Open(ctx); err != nil {\n  return(err)\n}\ndefer cl.Close(ctx)\n\nstate, err := cl.GetPowerState(ctx)\n```\n\n## Filtering\n\nThe `bmclib.Client` can be configured to filter BMC calls based on a few different criteria. Filtering modifies the order and/or the number of providers for BMC calls. This filtering can be permanent or on a one-time basis.\n\nAll providers are stored in a registry (see [`Client.Registry`](https://github.com/bmc-toolbox/bmclib/blob/b5cdfa3ffe026d3cc3257953abe3234b278ca20a/client.go#L29)) and the default order for providers in the registry is `ipmitool`, `asrockrack`, `gofish`, `IntelAMT`. The default order is defined [here](https://github.com/bmc-toolbox/bmclib/blob/b5cdfa3ffe026d3cc3257953abe3234b278ca20a/client.go#L152).\n\n### Permanent Filtering\n\nPermanent filtering modifies the order and/or the number of providers for BMC calls for all client methods (for example: `Open`, `SetPowerState`, etc) calls.\n\n```go\ncl := bmclib.NewClient(host, user, pass)\n// This will modify the order for all subsequent BMC calls\ncl.Registry.Drivers = cl.Registry.PreferDriver(\"gofish\")\nif err := cl.Open(ctx); err != nil {\n  return(err)\n}\n```\n\nThe following permanent filters are available:\n\n- `cl.Registry.PreferDriver(\"gofish\")` - This moves the `gofish` provider to be the first provider in the registry.\n- `cl.Registry.Supports(providers.FeaturePowerSet)` - This removes any provider from the registry that does not support the setting the power state.\n- `cl.Registry.Using(\"redfish\")` - This removes any provider from the registry that does not support the `redfish` protocol.\n- `cl.Registry.For(\"gofish\")` - This removes any provider from the registry that is not the `gofish` provider.\n- `cl.Registry.PreferProtocol(\"redfish\")` - This moves any provider that implements the `redfish` protocol to the beginning of the registry.\n\n### One-time Filtering\n\nOne-time filtering modifies the order and/or the number of providers for BMC calls only for a single method call.\n\n```Go\ncl := bmclib.NewClient(host, user, pass)\n// This will modify the order for only this BMC call\nif err := cl.PreferProvider(\"gofish\").Open(ctx); err != nil {\n  return(err)\n}\n```\n\nThe following one-time filters are available:\n\n- `cl.PreferProtocol(\"gofish\").GetPowerState(ctx)` - This moves the `gofish` provider to be the first provider in the registry.\n- `cl.Supports(providers.FeaturePowerSet).GetPowerState(ctx)` - This removes any provider from the registry that does not support the setting the power state.\n- `cl.Using(\"redfish\").GetPowerState(ctx)` - This removes any provider from the registry that does not support the `redfish` protocol.\n- `cl.For(\"gofish\").GetPowerState(ctx)` - This removes any provider from the registry that is not the `gofish` provider.\n- `cl.PreferProtocol(\"redfish\").GetPowerState(ctx)` - This moves any provider that implements the `redfish` protocol to the beginning of the registry.\n\n### Tracing\n\nTo collect trace telemetry, set the `WithTraceProvider()` option on the client\nwhich results in trace spans being collected for each client method.\n\n```go\ncl := bmclib.NewClient(\n          host,\n          user,\n          pass,\n          bmclib.WithLogger(log),\n          bmclib.WithTracerProvider(otel.GetTracerProvider()),\n      )\n```\n\n## Versions\n\nThe current bmclib version is `v2` and is being developed on the `main` branch.\n\nThe previous bmclib version is in maintenance mode and can be found here [v1](https://github.com/bmc-toolbox/bmclib/v1).\n\n## Go version in `go.mod`\n\nAs a library we will only bump the version of Go in the `go.mod` file when there are required dependencies in bmclib that necessitate\na version bump. When consuming bmclib in your project, we recommend always building with the latest Go version but this\nshould be in your hands as a user as much as possible.\n\n## Acknowledgments\n\nbmclib v2 interfaces with Redfish on BMCs through the Gofish library https://github.com/stmcginnis/gofish\n\nbmclib was originally developed for [Booking.com](http://www.booking.com). With approval from [Booking.com](http://www.booking.com),\nthe code and specification were generalized and published as Open Source on github, for which the authors would like to express their gratitude.\n\n### Authors\n\n- [Joel Rebello](https://github.com/joelrebel)\n- [Jacob Weinstock](https://github.com/jacobweinstock)\n"
  },
  {
    "path": "bmc/bios.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/hashicorp/go-multierror\"\n\t\"github.com/pkg/errors\"\n)\n\ntype BiosConfigurationGetter interface {\n\tGetBiosConfiguration(ctx context.Context) (biosConfig map[string]string, err error)\n}\n\ntype biosConfigurationGetterProvider struct {\n\tname string\n\tBiosConfigurationGetter\n}\n\ntype BiosConfigurationSetter interface {\n\tSetBiosConfiguration(ctx context.Context, biosConfig map[string]string) (err error)\n\tSetBiosConfigurationFromFile(ctx context.Context, cfg string) (err error)\n}\n\ntype biosConfigurationSetterProvider struct {\n\tname string\n\tBiosConfigurationSetter\n}\n\ntype BiosConfigurationResetter interface {\n\tResetBiosConfiguration(ctx context.Context) (err error)\n}\n\ntype biosConfigurationResetterProvider struct {\n\tname string\n\tBiosConfigurationResetter\n}\n\nfunc biosConfiguration(ctx context.Context, generic []biosConfigurationGetterProvider) (biosConfig map[string]string, metadata Metadata, err error) {\n\tmetadata = newMetadata()\nLoop:\n\tfor _, elem := range generic {\n\t\tif elem.BiosConfigurationGetter == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\t\t\tbreak Loop\n\t\tdefault:\n\t\t\tmetadata.ProvidersAttempted = append(metadata.ProvidersAttempted, elem.name)\n\t\t\tbiosConfig, vErr := elem.GetBiosConfiguration(ctx)\n\t\t\tif vErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(vErr, \"provider: %v\", elem.name))\n\t\t\t\terr = multierror.Append(err, vErr)\n\t\t\t\tcontinue\n\n\t\t\t}\n\t\t\tmetadata.SuccessfulProvider = elem.name\n\t\t\treturn biosConfig, metadata, nil\n\t\t}\n\t}\n\n\treturn biosConfig, metadata, multierror.Append(err, errors.New(\"failure to get bios configuration\"))\n}\n\nfunc setBiosConfiguration(ctx context.Context, generic []biosConfigurationSetterProvider, biosConfig map[string]string) (metadata Metadata, err error) {\n\tmetadata = newMetadata()\nLoop:\n\tfor _, elem := range generic {\n\t\tif elem.BiosConfigurationSetter == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\t\t\tbreak Loop\n\t\tdefault:\n\t\t\tmetadata.ProvidersAttempted = append(metadata.ProvidersAttempted, elem.name)\n\t\t\tvErr := elem.SetBiosConfiguration(ctx, biosConfig)\n\t\t\tif vErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(vErr, \"provider: %v\", elem.name))\n\t\t\t\terr = multierror.Append(err, vErr)\n\t\t\t\tcontinue\n\n\t\t\t}\n\t\t\tmetadata.SuccessfulProvider = elem.name\n\t\t\treturn metadata, nil\n\t\t}\n\t}\n\n\treturn metadata, multierror.Append(err, errors.New(\"failure to set bios configuration\"))\n}\n\nfunc setBiosConfigurationFromFile(ctx context.Context, generic []biosConfigurationSetterProvider, cfg string) (metadata Metadata, err error) {\n\tmetadata = newMetadata()\nLoop:\n\tfor _, elem := range generic {\n\t\tif elem.BiosConfigurationSetter == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\t\t\tbreak Loop\n\t\tdefault:\n\t\t\tmetadata.ProvidersAttempted = append(metadata.ProvidersAttempted, elem.name)\n\t\t\tvErr := elem.SetBiosConfigurationFromFile(ctx, cfg)\n\t\t\tif vErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(vErr, \"provider: %v\", elem.name))\n\t\t\t\terr = multierror.Append(err, vErr)\n\t\t\t\tcontinue\n\n\t\t\t}\n\t\t\tmetadata.SuccessfulProvider = elem.name\n\t\t\treturn metadata, nil\n\t\t}\n\t}\n\n\treturn metadata, multierror.Append(err, errors.New(\"failure to set bios configuration from file\"))\n}\n\nfunc resetBiosConfiguration(ctx context.Context, generic []biosConfigurationResetterProvider) (metadata Metadata, err error) {\n\tmetadata = newMetadata()\nLoop:\n\tfor _, elem := range generic {\n\t\tif elem.BiosConfigurationResetter == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\t\t\tbreak Loop\n\t\tdefault:\n\t\t\tmetadata.ProvidersAttempted = append(metadata.ProvidersAttempted, elem.name)\n\t\t\tvErr := elem.ResetBiosConfiguration(ctx)\n\t\t\tif vErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(vErr, \"provider: %v\", elem.name))\n\t\t\t\terr = multierror.Append(err, vErr)\n\t\t\t\tcontinue\n\n\t\t\t}\n\t\t\tmetadata.SuccessfulProvider = elem.name\n\t\t\treturn metadata, nil\n\t\t}\n\t}\n\n\treturn metadata, multierror.Append(err, errors.New(\"failure to reset bios configuration\"))\n}\n\nfunc GetBiosConfigurationInterfaces(ctx context.Context, generic []interface{}) (biosConfig map[string]string, metadata Metadata, err error) {\n\timplementations := make([]biosConfigurationGetterProvider, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := biosConfigurationGetterProvider{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase BiosConfigurationGetter:\n\t\t\ttemp.BiosConfigurationGetter = p\n\t\t\timplementations = append(implementations, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a BiosConfigurationGetter implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(implementations) == 0 {\n\t\treturn biosConfig, metadata, multierror.Append(\n\t\t\terr,\n\t\t\terrors.Wrap(\n\t\t\t\tbmclibErrs.ErrProviderImplementation,\n\t\t\t\t(\"no BiosConfigurationGetter implementations found\"),\n\t\t\t),\n\t\t)\n\t}\n\n\treturn biosConfiguration(ctx, implementations)\n}\n\nfunc SetBiosConfigurationInterfaces(ctx context.Context, generic []interface{}, biosConfig map[string]string) (metadata Metadata, err error) {\n\timplementations := make([]biosConfigurationSetterProvider, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := biosConfigurationSetterProvider{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase BiosConfigurationSetter:\n\t\t\ttemp.BiosConfigurationSetter = p\n\t\t\timplementations = append(implementations, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a BiosConfigurationSetter implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(implementations) == 0 {\n\t\treturn metadata, multierror.Append(\n\t\t\terr,\n\t\t\terrors.Wrap(\n\t\t\t\tbmclibErrs.ErrProviderImplementation,\n\t\t\t\t(\"no BiosConfigurationSetter implementations found\"),\n\t\t\t),\n\t\t)\n\t}\n\n\treturn setBiosConfiguration(ctx, implementations, biosConfig)\n}\n\nfunc SetBiosConfigurationFromFileInterfaces(ctx context.Context, generic []interface{}, cfg string) (metadata Metadata, err error) {\n\timplementations := make([]biosConfigurationSetterProvider, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := biosConfigurationSetterProvider{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase BiosConfigurationSetter:\n\t\t\ttemp.BiosConfigurationSetter = p\n\t\t\timplementations = append(implementations, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a BiosConfigurationSetterFromFile implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(implementations) == 0 {\n\t\treturn metadata, multierror.Append(\n\t\t\terr,\n\t\t\terrors.Wrap(\n\t\t\t\tbmclibErrs.ErrProviderImplementation,\n\t\t\t\t(\"no BiosConfigurationSetterFromFile implementations found\"),\n\t\t\t),\n\t\t)\n\t}\n\n\treturn setBiosConfigurationFromFile(ctx, implementations, cfg)\n}\n\nfunc ResetBiosConfigurationInterfaces(ctx context.Context, generic []interface{}) (metadata Metadata, err error) {\n\timplementations := make([]biosConfigurationResetterProvider, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := biosConfigurationResetterProvider{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase BiosConfigurationResetter:\n\t\t\ttemp.BiosConfigurationResetter = p\n\t\t\timplementations = append(implementations, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a BiosConfigurationResetter implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(implementations) == 0 {\n\t\treturn metadata, multierror.Append(\n\t\t\terr,\n\t\t\terrors.Wrap(\n\t\t\t\tbmclibErrs.ErrProviderImplementation,\n\t\t\t\t(\"no BiosConfigurationResetter implementations found\"),\n\t\t\t),\n\t\t)\n\t}\n\n\treturn resetBiosConfiguration(ctx, implementations)\n}\n"
  },
  {
    "path": "bmc/bmc.go",
    "content": "package bmc\n\nimport (\n\t\"strings\"\n\n\t\"go.opentelemetry.io/otel/attribute\"\n\t\"go.opentelemetry.io/otel/trace\"\n)\n\n// Metadata represents details about a bmc method\ntype Metadata struct {\n\t// SuccessfulProvider is the name of the provider that successfully executed\n\tSuccessfulProvider string\n\t// ProvidersAttempted is a slice of all providers that were attempt to execute\n\tProvidersAttempted []string\n\t// SuccessfulOpenConns is a slice of provider names that were opened successfully\n\tSuccessfulOpenConns []string\n\t// SuccessfulCloseConns is a slice of provider names that were closed successfully\n\tSuccessfulCloseConns []string\n\t// FailedProviderDetail holds the failed providers error messages for called methods\n\tFailedProviderDetail map[string]string\n}\n\nfunc newMetadata() Metadata {\n\treturn Metadata{\n\t\tFailedProviderDetail: make(map[string]string),\n\t}\n}\n\nfunc (m *Metadata) RegisterSpanAttributes(host string, span trace.Span) {\n\tspan.SetAttributes(attribute.String(\"host\", host))\n\n\tspan.SetAttributes(attribute.String(\"successful-provider\", m.SuccessfulProvider))\n\n\tspan.SetAttributes(\n\t\tattribute.String(\"successful-open-conns\", strings.Join(m.SuccessfulOpenConns, \",\")),\n\t)\n\n\tspan.SetAttributes(\n\t\tattribute.String(\"successful-close-conns\", strings.Join(m.SuccessfulCloseConns, \",\")),\n\t)\n\n\tspan.SetAttributes(\n\t\tattribute.String(\"attempted-providers\", strings.Join(m.ProvidersAttempted, \",\")),\n\t)\n\n\tfor p, e := range m.FailedProviderDetail {\n\t\tspan.SetAttributes(\n\t\t\tattribute.String(\"provider-errs-\"+p, e),\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "bmc/boot_device.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/hashicorp/go-multierror\"\n\t\"github.com/pkg/errors\"\n)\n\ntype BootDeviceType string\n\nconst (\n\tBootDeviceTypeBIOS        BootDeviceType = \"bios\"\n\tBootDeviceTypeCDROM       BootDeviceType = \"cdrom\"\n\tBootDeviceTypeDiag        BootDeviceType = \"diag\"\n\tBootDeviceTypeFloppy      BootDeviceType = \"floppy\"\n\tBootDeviceTypeDisk        BootDeviceType = \"disk\"\n\tBootDeviceTypeNone        BootDeviceType = \"none\"\n\tBootDeviceTypePXE         BootDeviceType = \"pxe\"\n\tBootDeviceTypeRemoteDrive BootDeviceType = \"remote_drive\"\n\tBootDeviceTypeSDCard      BootDeviceType = \"sd_card\"\n\tBootDeviceTypeUSB         BootDeviceType = \"usb\"\n\tBootDeviceTypeUtil        BootDeviceType = \"utilities\"\n\tBootDeviceUefiHTTP        BootDeviceType = \"uefi_http\"\n)\n\n// BootDeviceSetter sets the next boot device for a machine\ntype BootDeviceSetter interface {\n\tBootDeviceSet(ctx context.Context, bootDevice string, setPersistent, efiBoot bool) (ok bool, err error)\n}\n\n// BootDeviceOverrideGetter gets boot override settings for a machine\ntype BootDeviceOverrideGetter interface {\n\tBootDeviceOverrideGet(ctx context.Context) (override BootDeviceOverride, err error)\n}\n\n// bootDeviceProviders is an internal struct to correlate an implementation/provider and its name\ntype bootDeviceProviders struct {\n\tname             string\n\tbootDeviceSetter BootDeviceSetter\n}\n\n// bootOverrideProvider is an internal struct to correlate an implementation/provider and its name\ntype bootOverrideProvider struct {\n\tname               string\n\tbootOverrideGetter BootDeviceOverrideGetter\n}\n\ntype BootDeviceOverride struct {\n\tIsPersistent bool\n\tIsEFIBoot    bool\n\tDevice       BootDeviceType\n}\n\n// setBootDevice sets the next boot device.\n//\n// setPersistent persists the next boot device.\n// efiBoot sets up the device to boot off UEFI instead of legacy.\nfunc setBootDevice(ctx context.Context, timeout time.Duration, bootDevice string, setPersistent, efiBoot bool, b []bootDeviceProviders) (ok bool, metadata Metadata, err error) {\n\tmetadataLocal := newMetadata()\n\n\tfor _, elem := range b {\n\t\tif elem.bootDeviceSetter == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn false, metadata, err\n\t\tdefault:\n\t\t\tmetadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)\n\t\t\tctx, cancel := context.WithTimeout(ctx, timeout)\n\t\t\tdefer cancel()\n\t\t\tok, setErr := elem.bootDeviceSetter.BootDeviceSet(ctx, bootDevice, setPersistent, efiBoot)\n\t\t\tif setErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(setErr, \"provider: %v\", elem.name))\n\t\t\t\tmetadataLocal.FailedProviderDetail[elem.name] = setErr.Error()\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif !ok {\n\t\t\t\terr = multierror.Append(err, fmt.Errorf(\"provider: %v, failed to set boot device\", elem.name))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tmetadataLocal.SuccessfulProvider = elem.name\n\t\t\treturn ok, metadataLocal, nil\n\t\t}\n\t}\n\treturn ok, metadataLocal, multierror.Append(err, errors.New(\"failed to set boot device\"))\n}\n\n// SetBootDeviceFromInterfaces identifies implementations of the BootDeviceSetter interface and passes the found implementations to the setBootDevice() wrapper\nfunc SetBootDeviceFromInterfaces(ctx context.Context, timeout time.Duration, bootDevice string, setPersistent, efiBoot bool, generic []interface{}) (ok bool, metadata Metadata, err error) {\n\tbdSetters := make([]bootDeviceProviders, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := bootDeviceProviders{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase BootDeviceSetter:\n\t\t\ttemp.bootDeviceSetter = p\n\t\t\tbdSetters = append(bdSetters, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a BootDeviceSetter implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(bdSetters) == 0 {\n\t\treturn ok, metadata, multierror.Append(err, errors.New(\"no BootDeviceSetter implementations found\"))\n\t}\n\treturn setBootDevice(ctx, timeout, bootDevice, setPersistent, efiBoot, bdSetters)\n}\n\n// getBootDeviceOverride gets the boot device override settings for the given provider,\n// and updates the given metadata with provider attempts and errors.\nfunc getBootDeviceOverride(\n\tctx context.Context,\n\ttimeout time.Duration,\n\tprovider *bootOverrideProvider,\n\tmetadata *Metadata,\n) (override BootDeviceOverride, ok bool, err error) {\n\tselect {\n\tcase <-ctx.Done():\n\t\terr = multierror.Append(err, ctx.Err())\n\t\treturn override, ok, err\n\tdefault:\n\t\tmetadata.ProvidersAttempted = append(metadata.ProvidersAttempted, provider.name)\n\t\tctx, cancel := context.WithTimeout(ctx, timeout)\n\t\tdefer cancel()\n\n\t\toverride, err = provider.bootOverrideGetter.BootDeviceOverrideGet(ctx)\n\t\tif err != nil {\n\t\t\tmetadata.FailedProviderDetail[provider.name] = err.Error()\n\t\t\treturn override, ok, nil\n\t\t}\n\n\t\tmetadata.SuccessfulProvider = provider.name\n\t\treturn override, true, nil\n\t}\n}\n\n// GetBootDeviceOverrideFromInterface will get boot device override settings from the first successful\n// call to a BootDeviceOverrideGetter in the array of providers.\nfunc GetBootDeviceOverrideFromInterface(\n\tctx context.Context,\n\ttimeout time.Duration,\n\tproviders []interface{},\n) (override BootDeviceOverride, metadata Metadata, err error) {\n\tmetadata = newMetadata()\n\n\tfor _, elem := range providers {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\tswitch p := elem.(type) {\n\t\tcase BootDeviceOverrideGetter:\n\t\t\tprovider := &bootOverrideProvider{name: getProviderName(elem), bootOverrideGetter: p}\n\t\t\toverride, ok, getErr := getBootDeviceOverride(ctx, timeout, provider, &metadata)\n\t\t\tif getErr != nil || ok {\n\t\t\t\treturn override, metadata, getErr\n\t\t\t}\n\t\tdefault:\n\t\t\te := fmt.Errorf(\"not a BootDeviceOverrideGetter implementation: %T\", p)\n\t\t\terr = multierror.Append(err, e)\n\t\t}\n\t}\n\n\tif len(metadata.ProvidersAttempted) == 0 {\n\t\terr = multierror.Append(err, errors.New(\"no BootDeviceOverrideGetter implementations found\"))\n\t} else {\n\t\terr = multierror.Append(err, errors.New(\"failed to get boot device override settings\"))\n\t}\n\n\treturn override, metadata, err\n}\n"
  },
  {
    "path": "bmc/boot_device_test.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"fmt\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/hashicorp/go-multierror\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype bootDeviceTester struct {\n\tMakeNotOK    bool\n\tMakeErrorOut bool\n}\n\nfunc (b *bootDeviceTester) BootDeviceSet(ctx context.Context, bootDevice string, setPersistent, efiBoot bool) (ok bool, err error) {\n\tif b.MakeErrorOut {\n\t\treturn ok, errors.New(\"boot device set failed\")\n\t}\n\tif b.MakeNotOK {\n\t\treturn false, nil\n\t}\n\treturn true, nil\n}\n\nfunc (b *bootDeviceTester) Name() string {\n\treturn \"test provider\"\n}\n\nfunc TestSetBootDevice(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\tbootDevice   string\n\t\tmakeErrorOut bool\n\t\tmakeNotOk    bool\n\t\twant         bool\n\t\terr          error\n\t\tctxTimeout   time.Duration\n\t}{\n\t\t\"success\":               {bootDevice: \"pxe\", want: true},\n\t\t\"not ok return\":         {bootDevice: \"pxe\", want: false, makeNotOk: true, err: &multierror.Error{Errors: []error{errors.New(\"provider: test provider, failed to set boot device\"), errors.New(\"failed to set boot device\")}}},\n\t\t\"error\":                 {bootDevice: \"pxe\", want: false, makeErrorOut: true, err: &multierror.Error{Errors: []error{errors.New(\"provider: test provider: boot device set failed\"), errors.New(\"failed to set boot device\")}}},\n\t\t\"error context timeout\": {bootDevice: \"pxe\", want: false, makeErrorOut: true, err: &multierror.Error{Errors: []error{errors.New(\"context deadline exceeded\")}}, ctxTimeout: time.Nanosecond * 1},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\ttestImplementation := bootDeviceTester{MakeErrorOut: tc.makeErrorOut, MakeNotOK: tc.makeNotOk}\n\t\t\texpectedResult := tc.want\n\t\t\tif tc.ctxTimeout == 0 {\n\t\t\t\ttc.ctxTimeout = time.Second * 3\n\t\t\t}\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)\n\t\t\tdefer cancel()\n\t\t\tresult, _, err := setBootDevice(ctx, 0, tc.bootDevice, false, false, []bootDeviceProviders{{\"test provider\", &testImplementation}})\n\t\t\tif err != nil {\n\t\t\t\tif tc.err != nil {\n\t\t\t\t\tdiff := cmp.Diff(err.Error(), tc.err.Error())\n\t\t\t\t\tif diff != \"\" {\n\t\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdiff := cmp.Diff(result, expectedResult)\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSetBootDeviceFromInterfaces(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\tbootDevice        string\n\t\terr               error\n\t\tbadImplementation bool\n\t\twant              bool\n\t\twithName          bool\n\t}{\n\t\t\"success\":                  {bootDevice: \"pxe\", want: true},\n\t\t\"success with metadata\":    {bootDevice: \"pxe\", want: true, withName: true},\n\t\t\"no implementations found\": {bootDevice: \"pxe\", want: false, badImplementation: true, err: &multierror.Error{Errors: []error{errors.New(\"not a BootDeviceSetter implementation: *struct {}\"), errors.New(\"no BootDeviceSetter implementations found\")}}},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tvar generic []interface{}\n\t\t\tif tc.badImplementation {\n\t\t\t\tbadImplementation := struct{}{}\n\t\t\t\tgeneric = []interface{}{&badImplementation}\n\t\t\t} else {\n\t\t\t\ttestImplementation := bootDeviceTester{}\n\t\t\t\tgeneric = []interface{}{&testImplementation}\n\t\t\t}\n\t\t\texpectedResult := tc.want\n\t\t\tresult, metadata, err := SetBootDeviceFromInterfaces(context.Background(), 0, tc.bootDevice, false, false, generic)\n\t\t\tif err != nil {\n\t\t\t\tdiff := cmp.Diff(tc.err.Error(), err.Error())\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdiff := cmp.Diff(result, expectedResult)\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif tc.withName {\n\t\t\t\tif diff := cmp.Diff(metadata.SuccessfulProvider, \"test provider\"); diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype mockBootDeviceOverrideGetter struct {\n\toverrideReturn BootDeviceOverride\n\terrReturn      error\n}\n\nfunc (m *mockBootDeviceOverrideGetter) Name() string {\n\treturn \"Mock\"\n}\n\nfunc (m *mockBootDeviceOverrideGetter) BootDeviceOverrideGet(_ context.Context) (BootDeviceOverride, error) {\n\treturn m.overrideReturn, m.errReturn\n}\n\nfunc TestBootDeviceOverrideGet(t *testing.T) {\n\tsuccessOverride := BootDeviceOverride{\n\t\tIsPersistent: false,\n\t\tIsEFIBoot:    true,\n\t\tDevice:       BootDeviceTypeDisk,\n\t}\n\n\tsuccessMetadata := &Metadata{\n\t\tSuccessfulProvider:   \"Mock\",\n\t\tProvidersAttempted:   []string{\"Mock\"},\n\t\tSuccessfulOpenConns:  nil,\n\t\tSuccessfulCloseConns: []string(nil),\n\t\tFailedProviderDetail: map[string]string{},\n\t}\n\n\tmixedMetadata := &Metadata{\n\t\tSuccessfulProvider:   \"Mock\",\n\t\tProvidersAttempted:   []string{\"Mock\", \"Mock\"},\n\t\tSuccessfulOpenConns:  nil,\n\t\tSuccessfulCloseConns: []string(nil),\n\t\tFailedProviderDetail: map[string]string{\"Mock\": \"foo-failure\"},\n\t}\n\n\tfailMetadata := &Metadata{\n\t\tSuccessfulProvider:   \"\",\n\t\tProvidersAttempted:   []string{\"Mock\"},\n\t\tSuccessfulOpenConns:  nil,\n\t\tSuccessfulCloseConns: []string(nil),\n\t\tFailedProviderDetail: map[string]string{\"Mock\": \"foo-failure\"},\n\t}\n\n\temptyMetadata := &Metadata{\n\t\tFailedProviderDetail: make(map[string]string),\n\t}\n\n\ttestCases := []struct {\n\t\tname               string\n\t\thasCanceledContext bool\n\t\texpectedErrorMsg   string\n\t\texpectedMetadata   *Metadata\n\t\texpectedOverride   BootDeviceOverride\n\t\tgetters            []interface{}\n\t}{\n\t\t{\n\t\t\tname:             \"success\",\n\t\t\texpectedMetadata: successMetadata,\n\t\t\texpectedOverride: successOverride,\n\t\t\tgetters: []interface{}{\n\t\t\t\t&mockBootDeviceOverrideGetter{overrideReturn: successOverride},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:             \"multiple getters\",\n\t\t\texpectedMetadata: mixedMetadata,\n\t\t\texpectedOverride: successOverride,\n\t\t\tgetters: []interface{}{\n\t\t\t\t\"not a getter\",\n\t\t\t\t&mockBootDeviceOverrideGetter{errReturn: fmt.Errorf(\"foo-failure\")},\n\t\t\t\t&mockBootDeviceOverrideGetter{overrideReturn: successOverride},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:             \"error\",\n\t\t\texpectedMetadata: failMetadata,\n\t\t\texpectedErrorMsg: \"failed to get boot device override settings\",\n\t\t\tgetters: []interface{}{\n\t\t\t\t&mockBootDeviceOverrideGetter{errReturn: fmt.Errorf(\"foo-failure\")},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:             \"nil BootDeviceOverrideGetters\",\n\t\t\texpectedMetadata: emptyMetadata,\n\t\t\texpectedErrorMsg: \"no BootDeviceOverrideGetter implementations found\",\n\t\t},\n\t\t{\n\t\t\tname:             \"nil BootDeviceOverrideGetter\",\n\t\t\texpectedMetadata: emptyMetadata,\n\t\t\texpectedErrorMsg: \"no BootDeviceOverrideGetter implementations found\",\n\t\t\tgetters:          []interface{}{nil},\n\t\t},\n\t\t{\n\t\t\tname:               \"with canceled context\",\n\t\t\thasCanceledContext: true,\n\t\t\texpectedMetadata:   emptyMetadata,\n\t\t\texpectedErrorMsg:   \"context canceled\",\n\t\t\tgetters: []interface{}{\n\t\t\t\t&mockBootDeviceOverrideGetter{},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\tt.Run(testCase.name, func(t *testing.T) {\n\t\t\tctx, cancel := context.WithCancel(context.Background())\n\t\t\tdefer cancel()\n\n\t\t\tif testCase.hasCanceledContext {\n\t\t\t\tcancel()\n\t\t\t}\n\n\t\t\toverride, metadata, err := GetBootDeviceOverrideFromInterface(ctx, 0, testCase.getters)\n\n\t\t\tif testCase.expectedErrorMsg != \"\" {\n\t\t\t\tassert.ErrorContains(t, err, testCase.expectedErrorMsg)\n\t\t\t} else {\n\t\t\t\tassert.Nil(t, err)\n\t\t\t}\n\t\t\tassert.Equal(t, testCase.expectedOverride, override)\n\t\t\tassert.Equal(t, testCase.expectedMetadata, &metadata)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "bmc/connection.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/hashicorp/go-multierror\"\n\t\"github.com/pkg/errors\"\n)\n\n// Opener interface for opening a connection to a BMC\ntype Opener interface {\n\tOpen(ctx context.Context) error\n}\n\n// Closer interface for closing a connection to a BMC\ntype Closer interface {\n\tClose(ctx context.Context) error\n}\n\n// connectionProviders is an internal struct to correlate an implementation/provider and its name\ntype connectionProviders struct {\n\tname   string\n\tcloser Closer\n}\n\n// OpenConnectionFromInterfaces will try all opener interfaces and remove failed ones.\n// The reason failed ones need to be removed is so that when other methods are called (like powerstate)\n// implementations that have connections wont nil pointer error when their connection fails.\nfunc OpenConnectionFromInterfaces(ctx context.Context, timeout time.Duration, providers []interface{}) (opened []interface{}, metadata Metadata, err error) {\n\tmetadata = newMetadata()\n\n\t// Return immediately if the context is done.\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn nil, metadata, multierror.Append(err, ctx.Err())\n\tdefault:\n\t}\n\n\t// Create a context with the specified timeout. This is done for backward compatibility but\n\t// we should consider removing the timeout parameter alltogether given the context will\n\t// container the timeout.\n\tctx, cancel := context.WithTimeout(ctx, timeout)\n\tdefer cancel()\n\n\t// result facilitates communication of data between the concurrent opener goroutines and\n\t// the the parent goroutine.\n\ttype result struct {\n\t\tProviderName string\n\t\tOpener       Opener\n\t\tErr          error\n\t}\n\n\t// Create a channel to communicate results between opener goroutines and the parent goroutine.\n\tresults := make(chan result)\n\n\t// Use a WaitGroup to control closing of the results channel when all opener goroutines finish.\n\tvar wg sync.WaitGroup\n\n\t// For every provider, launch a goroutine that attempts to open a connection and report\n\t// back via the results channel what happened.\n\tfor _, elem := range providers {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\tswitch p := elem.(type) {\n\t\tcase Opener:\n\t\t\tproviderName := getProviderName(elem)\n\t\t\tmetadata.ProvidersAttempted = append(metadata.ProvidersAttempted, providerName)\n\n\t\t\twg.Add(1)\n\t\t\tgo func(provider Opener, providerName string) {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tres := result{ProviderName: providerName, Opener: provider}\n\n\t\t\t\tif err := provider.Open(ctx); err != nil {\n\t\t\t\t\tres.Err = errors.WithMessagef(err, \"provider: %v\", providerName)\n\t\t\t\t}\n\n\t\t\t\tresults <- res\n\t\t\t}(p, providerName)\n\n\t\tdefault:\n\t\t\terr = multierror.Append(err, fmt.Errorf(\"not a Opener implementation: %T\", p))\n\t\t}\n\t}\n\n\t// Launch the goroutine to close the results channel ensuring we can exit the for-range over\n\t// the results channel below.\n\tgo func() { wg.Wait(); close(results) }()\n\n\t// Gather and handle results from the opener goroutines.\n\tfor res := range results {\n\t\tif res.Err != nil {\n\t\t\terr = multierror.Append(err, res.Err)\n\t\t\tmetadata.FailedProviderDetail[res.ProviderName] = res.Err.Error()\n\t\t\tcontinue\n\t\t}\n\n\t\topened = append(opened, res.Opener)\n\t\tmetadata.SuccessfulOpenConns = append(metadata.SuccessfulOpenConns, res.ProviderName)\n\t}\n\n\tif len(opened) == 0 {\n\t\treturn nil, metadata, multierror.Append(err, errors.New(\"no Opener implementations found\"))\n\t}\n\n\treturn opened, metadata, nil\n}\n\n// closeConnection closes a connection to a BMC, trying all interface implementations passed in\nfunc closeConnection(ctx context.Context, c []connectionProviders) (metadata Metadata, err error) {\n\tmetadata = newMetadata()\n\tvar connClosed bool\n\n\tfor _, elem := range c {\n\t\tif elem.closer == nil {\n\t\t\tcontinue\n\t\t}\n\t\tmetadata.ProvidersAttempted = append(metadata.ProvidersAttempted, elem.name)\n\t\tcloseErr := elem.closer.Close(ctx)\n\t\tif closeErr != nil {\n\t\t\terr = multierror.Append(err, errors.WithMessagef(closeErr, \"provider: %v\", elem.name))\n\t\t\tmetadata.FailedProviderDetail[elem.name] = closeErr.Error()\n\t\t\tcontinue\n\t\t}\n\t\tconnClosed = true\n\t\tmetadata.SuccessfulCloseConns = append(metadata.SuccessfulCloseConns, elem.name)\n\t}\n\tif connClosed {\n\t\treturn metadata, nil\n\t}\n\treturn metadata, multierror.Append(err, errors.New(\"failed to close connection\"))\n}\n\n// CloseConnectionFromInterfaces identifies implementations of the Closer() interface and and passes the found implementations to the closeConnection() wrapper\nfunc CloseConnectionFromInterfaces(ctx context.Context, generic []interface{}) (metadata Metadata, err error) {\n\tmetadata = newMetadata()\n\n\tclosers := make([]connectionProviders, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := connectionProviders{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase Closer:\n\t\t\ttemp.closer = p\n\t\t\tclosers = append(closers, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a Closer implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(closers) == 0 {\n\t\treturn metadata, multierror.Append(err, errors.New(\"no Closer implementations found\"))\n\t}\n\treturn closeConnection(ctx, closers)\n}\n"
  },
  {
    "path": "bmc/connection_test.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/hashicorp/go-multierror\"\n\t\"go.uber.org/goleak\"\n)\n\ntype connTester1 struct {\n\tMakeErrorOut bool\n}\n\nfunc (r *connTester1) Open(ctx context.Context) (err error) {\n\tif r.MakeErrorOut {\n\t\treturn errors.New(\"open connection failed\")\n\t}\n\treturn nil\n}\n\nfunc (r *connTester1) Close(ctx context.Context) (err error) {\n\tif r.MakeErrorOut {\n\t\treturn errors.New(\"close connection failed\")\n\t}\n\treturn nil\n}\n\nfunc (p *connTester1) Name() string {\n\treturn \"test provider\"\n}\n\ntype connTester2 struct{}\n\nfunc (r *connTester2) Open(ctx context.Context) (err error) {\n\t<-ctx.Done()\n\treturn nil\n}\n\nfunc (r *connTester2) Close(ctx context.Context) (err error) {\n\treturn nil\n}\n\nfunc (p *connTester2) Name() string {\n\treturn \"test provider 2\"\n}\n\nfunc TestOpenConnectionFromInterfaces(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\terr                   error\n\t\tmakeErrorOut          bool\n\t\tbadImplementation     bool\n\t\twithMetadata          bool\n\t\twithMultipleProviders bool\n\t\tctxTimeout            time.Duration\n\t}{\n\t\t\"success\":                      {},\n\t\t\"success with metadata\":        {withMetadata: true},\n\t\t\"error context deadline\":       {err: &multierror.Error{Errors: []error{errors.New(\"context deadline exceeded\"), errors.New(\"no Opener implementations found\")}}, ctxTimeout: time.Nanosecond * 1},\n\t\t\"error failed open\":            {makeErrorOut: true, err: &multierror.Error{Errors: []error{errors.New(\"provider: test provider: open connection failed\"), errors.New(\"no Opener implementations found\")}}},\n\t\t\"no implementations found\":     {badImplementation: true, err: &multierror.Error{Errors: []error{errors.New(\"not a Opener implementation: *struct {}\"), errors.New(\"no Opener implementations found\")}}},\n\t\t\"multiple providers attempted\": {withMultipleProviders: true},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tdefer goleak.VerifyNone(t)\n\n\t\t\tvar generic []interface{}\n\t\t\tif tc.badImplementation {\n\t\t\t\tbadImplementation := struct{}{}\n\t\t\t\tgeneric = append(generic, &badImplementation)\n\t\t\t} else {\n\t\t\t\ttestImplementation := &connTester1{MakeErrorOut: tc.makeErrorOut}\n\t\t\t\tgeneric = append(generic, testImplementation)\n\t\t\t}\n\n\t\t\tif tc.withMultipleProviders {\n\t\t\t\tgeneric = append(generic, &connTester2{})\n\t\t\t}\n\n\t\t\tif tc.ctxTimeout == 0 {\n\t\t\t\ttc.ctxTimeout = time.Second * 3\n\t\t\t}\n\t\t\tctx := context.Background()\n\t\t\topened, metadata, err := OpenConnectionFromInterfaces(ctx, tc.ctxTimeout, generic)\n\t\t\tif err != nil {\n\t\t\t\tif tc.err != nil {\n\t\t\t\t\tif diff := cmp.Diff(err.Error(), tc.err.Error()); diff != \"\" {\n\t\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\texpected := []interface{}{&connTester1{}}\n\t\t\t\tif tc.withMultipleProviders {\n\t\t\t\t\texpected = append(expected, &connTester2{})\n\t\t\t\t}\n\n\t\t\t\tif diff := cmp.Diff(opened, expected); diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif tc.withMetadata {\n\t\t\t\tif diff := cmp.Diff(metadata.SuccessfulOpenConns, []string{\"test provider\"}); diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCloseConnection(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\tmakeErrorOut bool\n\t\terr          error\n\t\tctxTimeout   time.Duration\n\t}{\n\t\t\"success\":                {},\n\t\t\"error context deadline\": {err: &multierror.Error{Errors: []error{errors.New(\"context deadline exceeded\")}}, ctxTimeout: time.Nanosecond * 1},\n\t\t\"error\":                  {makeErrorOut: true, err: &multierror.Error{Errors: []error{errors.New(\"provider: test provider: close connection failed\"), errors.New(\"failed to close connection\")}}},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\ttestImplementation := connTester1{MakeErrorOut: tc.makeErrorOut}\n\t\t\tif tc.ctxTimeout == 0 {\n\t\t\t\ttc.ctxTimeout = time.Second * 3\n\t\t\t}\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)\n\t\t\tdefer cancel()\n\t\t\t_, err := closeConnection(ctx, []connectionProviders{{\"test provider\", &testImplementation}})\n\t\t\tif err != nil {\n\t\t\t\tdiff := cmp.Diff(tc.err.Error(), err.Error())\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCloseConnectionFromInterfaces(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\terr               error\n\t\tbadImplementation bool\n\t\twithMetadata      bool\n\t}{\n\t\t\"success\":                  {},\n\t\t\"success with metadata\":    {withMetadata: true},\n\t\t\"no implementations found\": {badImplementation: true, err: &multierror.Error{Errors: []error{errors.New(\"not a Closer implementation: *struct {}\"), errors.New(\"no Closer implementations found\")}}},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tvar generic []interface{}\n\t\t\tif tc.badImplementation {\n\t\t\t\tbadImplementation := struct{}{}\n\t\t\t\tgeneric = []interface{}{&badImplementation}\n\t\t\t} else {\n\t\t\t\ttestImplementation := connTester1{}\n\t\t\t\tgeneric = []interface{}{&testImplementation}\n\t\t\t}\n\n\t\t\tmetadata, err := CloseConnectionFromInterfaces(context.Background(), generic)\n\t\t\tif err != nil {\n\t\t\t\tdiff := cmp.Diff(err.Error(), tc.err.Error())\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif tc.withMetadata {\n\t\t\t\tif diff := cmp.Diff(metadata.SuccessfulCloseConns, []string{\"test provider\"}); diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "bmc/firmware.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/constants\"\n\tbconsts \"github.com/bmc-toolbox/bmclib/v2/constants\"\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\n\t\"github.com/hashicorp/go-multierror\"\n\t\"github.com/pkg/errors\"\n)\n\n// FirmwareInstaller defines an interface to upload and initiate a firmware install\ntype FirmwareInstaller interface {\n\t// FirmwareInstall uploads firmware update payload to the BMC returning the task ID\n\t//\n\t// parameters:\n\t// component - the component slug for the component update being installed.\n\t// operationsApplyTime - one of the OperationApplyTime constants\n\t// forceInstall - purge the install task queued/scheduled firmware install BMC task (if any).\n\t// reader - the io.reader to the firmware update file.\n\t//\n\t// return values:\n\t// taskID - A taskID is returned if the update process on the BMC returns an identifier for the update process.\n\tFirmwareInstall(ctx context.Context, component string, operationApplyTime string, forceInstall bool, reader io.Reader) (taskID string, err error)\n}\n\n// firmwareInstallerProvider is an internal struct to correlate an implementation/provider and its name\ntype firmwareInstallerProvider struct {\n\tname string\n\tFirmwareInstaller\n}\n\n// firmwareInstall uploads and initiates firmware update for the component\nfunc firmwareInstall(ctx context.Context, component, operationApplyTime string, forceInstall bool, reader io.Reader, generic []firmwareInstallerProvider) (taskID string, metadata Metadata, err error) {\n\tmetadata = newMetadata()\n\n\tfor _, elem := range generic {\n\t\tif elem.FirmwareInstaller == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn taskID, metadata, err\n\t\tdefault:\n\t\t\tmetadata.ProvidersAttempted = append(metadata.ProvidersAttempted, elem.name)\n\t\t\ttaskID, vErr := elem.FirmwareInstall(ctx, component, operationApplyTime, forceInstall, reader)\n\t\t\tif vErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(vErr, \"provider: %v\", elem.name))\n\t\t\t\tmetadata.FailedProviderDetail[elem.name] = err.Error()\n\t\t\t\tcontinue\n\n\t\t\t}\n\t\t\tmetadata.SuccessfulProvider = elem.name\n\t\t\treturn taskID, metadata, nil\n\t\t}\n\t}\n\n\treturn taskID, metadata, multierror.Append(err, errors.New(\"failure in FirmwareInstall\"))\n}\n\n// FirmwareInstallFromInterfaces identifies implementations of the FirmwareInstaller interface and passes the found implementations to the firmwareInstall() wrapper\nfunc FirmwareInstallFromInterfaces(ctx context.Context, component, operationApplyTime string, forceInstall bool, reader io.Reader, generic []interface{}) (taskID string, metadata Metadata, err error) {\n\tmetadata = newMetadata()\n\n\timplementations := make([]firmwareInstallerProvider, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := firmwareInstallerProvider{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase FirmwareInstaller:\n\t\t\ttemp.FirmwareInstaller = p\n\t\t\timplementations = append(implementations, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a FirmwareInstaller implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(implementations) == 0 {\n\t\treturn taskID, metadata, multierror.Append(\n\t\t\terr,\n\t\t\terrors.Wrap(\n\t\t\t\tbmclibErrs.ErrProviderImplementation,\n\t\t\t\t(\"no FirmwareInstaller implementations found\"),\n\t\t\t),\n\t\t)\n\t}\n\n\treturn firmwareInstall(ctx, component, operationApplyTime, forceInstall, reader, implementations)\n}\n\n// Note: this interface is to be deprecated in favour of a more generic FirmwareTaskVerifier.\n//\n// FirmwareInstallVerifier defines an interface to check firmware install status\ntype FirmwareInstallVerifier interface {\n\t// FirmwareInstallStatus returns the status of the firmware install process.\n\t//\n\t// parameters:\n\t// installVersion (required) - the version this method should check is installed.\n\t// component (optional) - the component slug for the component update being installed.\n\t// taskID (optional) - the task identifier.\n\t//\n\t// return values:\n\t// status - returns one of the FirmwareInstall statuses (see devices/constants.go).\n\tFirmwareInstallStatus(ctx context.Context, installVersion, component, taskID string) (status string, err error)\n}\n\n// firmwareInstallVerifierProvider is an internal struct to correlate an implementation/provider and its name\ntype firmwareInstallVerifierProvider struct {\n\tname string\n\tFirmwareInstallVerifier\n}\n\n// firmwareInstallStatus returns the status of the firmware install process\nfunc firmwareInstallStatus(ctx context.Context, installVersion, component, taskID string, generic []firmwareInstallVerifierProvider) (status string, metadata Metadata, err error) {\n\tmetadata = newMetadata()\n\n\tfor _, elem := range generic {\n\t\tif elem.FirmwareInstallVerifier == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn status, metadata, err\n\t\tdefault:\n\t\t\tmetadata.ProvidersAttempted = append(metadata.ProvidersAttempted, elem.name)\n\t\t\tstatus, vErr := elem.FirmwareInstallStatus(ctx, installVersion, component, taskID)\n\t\t\tif vErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(vErr, \"provider: %v\", elem.name))\n\t\t\t\tmetadata.FailedProviderDetail[elem.name] = err.Error()\n\t\t\t\tcontinue\n\n\t\t\t}\n\t\t\tmetadata.SuccessfulProvider = elem.name\n\t\t\treturn status, metadata, nil\n\t\t}\n\t}\n\n\treturn status, metadata, multierror.Append(err, errors.New(\"failure in FirmwareInstallStatus\"))\n}\n\n// FirmwareInstallStatusFromInterfaces identifies implementations of the FirmwareInstallVerifier interface and passes the found implementations to the firmwareInstallStatus() wrapper.\nfunc FirmwareInstallStatusFromInterfaces(ctx context.Context, installVersion, component, taskID string, generic []interface{}) (status string, metadata Metadata, err error) {\n\tmetadata = newMetadata()\n\n\timplementations := make([]firmwareInstallVerifierProvider, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := firmwareInstallVerifierProvider{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase FirmwareInstallVerifier:\n\t\t\ttemp.FirmwareInstallVerifier = p\n\t\t\timplementations = append(implementations, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a FirmwareInstallVerifier implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(implementations) == 0 {\n\t\treturn taskID, metadata, multierror.Append(\n\t\t\terr,\n\t\t\terrors.Wrap(\n\t\t\t\tbmclibErrs.ErrProviderImplementation,\n\t\t\t\t(\"no FirmwareInstallVerifier implementations found\"),\n\t\t\t),\n\t\t)\n\t}\n\n\treturn firmwareInstallStatus(ctx, installVersion, component, taskID, implementations)\n}\n\n// FirmwareInstallProvider defines an interface to upload and initiate a firmware install in the same implementation method\n//\n// Its intended to deprecate the FirmwareInstall interface\ntype FirmwareInstallProvider interface {\n\t// FirmwareInstallUploadAndInitiate uploads _and_ initiates the firmware install process.\n\t//\n\t// return values:\n\t// taskID - A taskID is returned if the update process on the BMC returns an identifier for the update process.\n\tFirmwareInstallUploadAndInitiate(ctx context.Context, component string, file *os.File) (taskID string, err error)\n}\n\n// firmwareInstallProvider is an internal struct to correlate an implementation/provider and its name\ntype firmwareInstallProvider struct {\n\tname string\n\tFirmwareInstallProvider\n}\n\n// firmwareInstall uploads and initiates firmware update for the component\nfunc firmwareInstallUploadAndInitiate(ctx context.Context, component string, file *os.File, generic []firmwareInstallProvider) (taskID string, metadata Metadata, err error) {\n\tmetadata = newMetadata()\n\n\tfor _, elem := range generic {\n\t\tif elem.FirmwareInstallProvider == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn taskID, metadata, err\n\t\tdefault:\n\t\t\tmetadata.ProvidersAttempted = append(metadata.ProvidersAttempted, elem.name)\n\t\t\ttaskID, vErr := elem.FirmwareInstallUploadAndInitiate(ctx, component, file)\n\t\t\tif vErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(vErr, \"provider: %v\", elem.name))\n\t\t\t\tmetadata.FailedProviderDetail[elem.name] = err.Error()\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tmetadata.SuccessfulProvider = elem.name\n\t\t\treturn taskID, metadata, nil\n\t\t}\n\t}\n\n\treturn taskID, metadata, multierror.Append(err, errors.New(\"failure in FirmwareInstallUploadAndInitiate\"))\n}\n\n// FirmwareInstallUploadAndInitiateFromInterfaces identifies implementations of the FirmwareInstallProvider interface and passes the found implementations to the firmwareInstallUploadAndInitiate() wrapper\nfunc FirmwareInstallUploadAndInitiateFromInterfaces(ctx context.Context, component string, file *os.File, generic []interface{}) (taskID string, metadata Metadata, err error) {\n\tmetadata = newMetadata()\n\n\timplementations := make([]firmwareInstallProvider, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := firmwareInstallProvider{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase FirmwareInstallProvider:\n\t\t\ttemp.FirmwareInstallProvider = p\n\t\t\timplementations = append(implementations, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a FirmwareInstallProvider implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(implementations) == 0 {\n\t\treturn taskID, metadata, multierror.Append(\n\t\t\terr,\n\t\t\terrors.Wrap(\n\t\t\t\tbmclibErrs.ErrProviderImplementation,\n\t\t\t\t(\"no FirmwareInstallProvider implementations found\"),\n\t\t\t),\n\t\t)\n\t}\n\n\treturn firmwareInstallUploadAndInitiate(ctx, component, file, implementations)\n}\n\n// FirmwareInstallerUploaded defines an interface to install firmware that was previously uploaded with FirmwareUpload\ntype FirmwareInstallerUploaded interface {\n\t// FirmwareInstallUploaded uploads firmware update payload to the BMC returning the firmware install task ID\n\t//\n\t// parameters:\n\t// component - the component slug for the component update being installed.\n\t// uploadTaskID - the taskID for the firmware upload verify task (returned by FirmwareUpload)\n\t//\n\t// return values:\n\t// installTaskID - A installTaskID is returned if the update process on the BMC returns an identifier for the firmware install process.\n\tFirmwareInstallUploaded(ctx context.Context, component, uploadTaskID string) (taskID string, err error)\n}\n\n// firmwareInstallerProvider is an internal struct to correlate an implementation/provider and its name\ntype firmwareInstallerWithOptionsProvider struct {\n\tname string\n\tFirmwareInstallerUploaded\n}\n\n// firmwareInstallUploaded uploads and initiates firmware update for the component\nfunc firmwareInstallUploaded(ctx context.Context, component, uploadTaskID string, generic []firmwareInstallerWithOptionsProvider) (installTaskID string, metadata Metadata, err error) {\n\tmetadata = newMetadata()\n\n\tfor _, elem := range generic {\n\t\tif elem.FirmwareInstallerUploaded == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn installTaskID, metadata, err\n\t\tdefault:\n\t\t\tmetadata.ProvidersAttempted = append(metadata.ProvidersAttempted, elem.name)\n\t\t\tvar vErr error\n\t\t\tinstallTaskID, vErr = elem.FirmwareInstallUploaded(ctx, component, uploadTaskID)\n\t\t\tif vErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(vErr, \"provider: %v\", elem.name))\n\t\t\t\tmetadata.FailedProviderDetail[elem.name] = err.Error()\n\t\t\t\tcontinue\n\n\t\t\t}\n\t\t\tmetadata.SuccessfulProvider = elem.name\n\t\t\treturn installTaskID, metadata, nil\n\t\t}\n\t}\n\n\treturn installTaskID, metadata, multierror.Append(err, errors.New(\"failure in FirmwareInstallUploaded\"))\n}\n\n// FirmwareInstallerUploadedFromInterfaces identifies implementations of the FirmwareInstallUploaded interface and passes the found implementations to the firmwareInstallUploaded() wrapper\nfunc FirmwareInstallerUploadedFromInterfaces(ctx context.Context, component, uploadTaskID string, generic []interface{}) (installTaskID string, metadata Metadata, err error) {\n\tmetadata = newMetadata()\n\n\timplementations := make([]firmwareInstallerWithOptionsProvider, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := firmwareInstallerWithOptionsProvider{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase FirmwareInstallerUploaded:\n\t\t\ttemp.FirmwareInstallerUploaded = p\n\t\t\timplementations = append(implementations, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a FirmwareInstallerUploaded implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(implementations) == 0 {\n\t\treturn installTaskID, metadata, multierror.Append(\n\t\t\terr,\n\t\t\terrors.Wrap(\n\t\t\t\tbmclibErrs.ErrProviderImplementation,\n\t\t\t\t(\"no FirmwareInstallerUploaded implementations found\"),\n\t\t\t),\n\t\t)\n\t}\n\n\treturn firmwareInstallUploaded(ctx, component, uploadTaskID, implementations)\n}\n\ntype FirmwareInstallStepsGetter interface {\n\tFirmwareInstallSteps(ctx context.Context, component string) ([]constants.FirmwareInstallStep, error)\n}\n\n// firmwareInstallStepsGetterProvider is an internal struct to correlate an implementation/provider and its name\ntype firmwareInstallStepsGetterProvider struct {\n\tname string\n\tFirmwareInstallStepsGetter\n}\n\n// FirmwareInstallStepsFromInterfaces identifies implementations of the FirmwareInstallStepsGetter interface and passes the found implementations to the firmwareInstallSteps() wrapper.\nfunc FirmwareInstallStepsFromInterfaces(ctx context.Context, component string, generic []interface{}) (steps []constants.FirmwareInstallStep, metadata Metadata, err error) {\n\tmetadata = newMetadata()\n\n\timplementations := make([]firmwareInstallStepsGetterProvider, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := firmwareInstallStepsGetterProvider{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase FirmwareInstallStepsGetter:\n\t\t\ttemp.FirmwareInstallStepsGetter = p\n\t\t\timplementations = append(implementations, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a FirmwareInstallStepsGetter implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(implementations) == 0 {\n\t\treturn steps, metadata, multierror.Append(\n\t\t\terr,\n\t\t\terrors.Wrap(\n\t\t\t\tbmclibErrs.ErrProviderImplementation,\n\t\t\t\t(\"no FirmwareInstallStepsGetter implementations found\"),\n\t\t\t),\n\t\t)\n\t}\n\n\treturn firmwareInstallSteps(ctx, component, implementations)\n}\n\nfunc firmwareInstallSteps(ctx context.Context, component string, generic []firmwareInstallStepsGetterProvider) (steps []constants.FirmwareInstallStep, metadata Metadata, err error) {\n\tmetadata = newMetadata()\n\n\tfor _, elem := range generic {\n\t\tif elem.FirmwareInstallStepsGetter == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn steps, metadata, err\n\t\tdefault:\n\t\t\tmetadata.ProvidersAttempted = append(metadata.ProvidersAttempted, elem.name)\n\t\t\tsteps, vErr := elem.FirmwareInstallSteps(ctx, component)\n\t\t\tif vErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(vErr, \"provider: %v\", elem.name))\n\t\t\t\tmetadata.FailedProviderDetail[elem.name] = err.Error()\n\t\t\t\tcontinue\n\n\t\t\t}\n\t\t\tmetadata.SuccessfulProvider = elem.name\n\t\t\treturn steps, metadata, nil\n\t\t}\n\t}\n\n\treturn steps, metadata, multierror.Append(err, errors.New(\"failure in FirmwareInstallSteps\"))\n}\n\ntype FirmwareUploader interface {\n\tFirmwareUpload(ctx context.Context, component string, file *os.File) (uploadVerifyTaskID string, err error)\n}\n\n// firmwareUploaderProvider is an internal struct to correlate an implementation/provider and its name\ntype firmwareUploaderProvider struct {\n\tname string\n\tFirmwareUploader\n}\n\n// FirmwareUploaderFromInterfaces identifies implementations of the FirmwareUploader interface and passes the found implementations to the firmwareUpload() wrapper.\nfunc FirmwareUploadFromInterfaces(ctx context.Context, component string, file *os.File, generic []interface{}) (taskID string, metadata Metadata, err error) {\n\tmetadata = newMetadata()\n\n\timplementations := make([]firmwareUploaderProvider, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := firmwareUploaderProvider{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase FirmwareUploader:\n\t\t\ttemp.FirmwareUploader = p\n\t\t\timplementations = append(implementations, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a FirmwareUploader implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(implementations) == 0 {\n\t\treturn taskID, metadata, multierror.Append(\n\t\t\terr,\n\t\t\terrors.Wrap(\n\t\t\t\tbmclibErrs.ErrProviderImplementation,\n\t\t\t\t(\"no FirmwareUploader implementations found\"),\n\t\t\t),\n\t\t)\n\t}\n\n\treturn firmwareUpload(ctx, component, file, implementations)\n}\n\nfunc firmwareUpload(ctx context.Context, component string, file *os.File, generic []firmwareUploaderProvider) (taskID string, metadata Metadata, err error) {\n\tmetadata = newMetadata()\n\n\tfor _, elem := range generic {\n\t\tif elem.FirmwareUploader == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn taskID, metadata, err\n\t\tdefault:\n\t\t\tmetadata.ProvidersAttempted = append(metadata.ProvidersAttempted, elem.name)\n\t\t\ttaskID, vErr := elem.FirmwareUpload(ctx, component, file)\n\t\t\tif vErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(vErr, \"provider: %v\", elem.name))\n\t\t\t\tmetadata.FailedProviderDetail[elem.name] = err.Error()\n\t\t\t\tcontinue\n\n\t\t\t}\n\t\t\tmetadata.SuccessfulProvider = elem.name\n\t\t\treturn taskID, metadata, nil\n\t\t}\n\t}\n\n\treturn taskID, metadata, multierror.Append(err, errors.New(\"failure in FirmwareUpload\"))\n}\n\n// FirmwareTaskVerifier defines an interface to check the status for firmware related tasks queued on the BMC.\n// these could be a an firmware upload and verify task or a firmware install task.\n//\n// This is to replace the FirmwareInstallVerifier interface\ntype FirmwareTaskVerifier interface {\n\t// FirmwareTaskStatus returns the status of the firmware upload process.\n\t//\n\t// parameters:\n\t// kind (required) - The FirmwareInstallStep\n\t// component (optional) - the component slug for the component that the firmware was uploaded for.\n\t// taskID (required) - the task identifier.\n\t// installVersion (optional) -  the firmware version being installed as part of the task if applicable.\n\t//\n\t// return values:\n\t// state - returns one of the FirmwareTask statuses (see devices/constants.go).\n\t// status - returns firmware task progress or other arbitrary task information.\n\tFirmwareTaskStatus(ctx context.Context, kind bconsts.FirmwareInstallStep, component, taskID, installVersion string) (state constants.TaskState, status string, err error)\n}\n\n// firmwareTaskVerifierProvider is an internal struct to correlate an implementation/provider and its name\ntype firmwareTaskVerifierProvider struct {\n\tname string\n\tFirmwareTaskVerifier\n}\n\n// firmwareTaskStatus returns the status of the firmware upload process.\n\nfunc firmwareTaskStatus(ctx context.Context, kind bconsts.FirmwareInstallStep, component, taskID, installVersion string, generic []firmwareTaskVerifierProvider) (state constants.TaskState, status string, metadata Metadata, err error) {\n\tmetadata = newMetadata()\n\n\tfor _, elem := range generic {\n\t\tif elem.FirmwareTaskVerifier == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn state, status, metadata, err\n\t\tdefault:\n\t\t\tmetadata.ProvidersAttempted = append(metadata.ProvidersAttempted, elem.name)\n\t\t\tstate, status, vErr := elem.FirmwareTaskStatus(ctx, kind, component, taskID, installVersion)\n\t\t\tif vErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(vErr, \"provider: %v\", elem.name))\n\t\t\t\tmetadata.FailedProviderDetail[elem.name] = err.Error()\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tmetadata.SuccessfulProvider = elem.name\n\t\t\treturn state, status, metadata, nil\n\t\t}\n\t}\n\n\treturn state, status, metadata, multierror.Append(err, errors.New(\"failure in FirmwareTaskStatus\"))\n}\n\n// FirmwareTaskStatusFromInterfaces identifies implementations of the FirmwareTaskVerifier interface and passes the found implementations to the firmwareTaskStatus() wrapper.\nfunc FirmwareTaskStatusFromInterfaces(ctx context.Context, kind bconsts.FirmwareInstallStep, component, taskID, installVersion string, generic []interface{}) (state constants.TaskState, status string, metadata Metadata, err error) {\n\tmetadata = newMetadata()\n\n\timplementations := make([]firmwareTaskVerifierProvider, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := firmwareTaskVerifierProvider{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase FirmwareTaskVerifier:\n\t\t\ttemp.FirmwareTaskVerifier = p\n\t\t\timplementations = append(implementations, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a FirmwareTaskVerifier implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(implementations) == 0 {\n\t\treturn state, status, metadata, multierror.Append(\n\t\t\terr,\n\t\t\terrors.Wrap(\n\t\t\t\tbmclibErrs.ErrProviderImplementation,\n\t\t\t\t(\"no FirmwareTaskVerifier implementations found\"),\n\t\t\t),\n\t\t)\n\t}\n\n\treturn firmwareTaskStatus(ctx, kind, component, taskID, installVersion, implementations)\n}\n"
  },
  {
    "path": "bmc/firmware_test.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/constants\"\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/bmc-toolbox/common\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype firmwareInstallTester struct {\n\treturnTaskID string\n\treturnError  error\n}\n\nfunc (f *firmwareInstallTester) FirmwareInstall(ctx context.Context, component, applyAt string, forceInstall bool, reader io.Reader) (taskID string, err error) {\n\treturn f.returnTaskID, f.returnError\n}\n\nfunc (r *firmwareInstallTester) Name() string {\n\treturn \"foo\"\n}\n\nfunc TestFirmwareInstall(t *testing.T) {\n\ttestCases := []struct {\n\t\ttestName           string\n\t\tcomponent          string\n\t\tapplyAt            string\n\t\tforceInstall       bool\n\t\treader             io.Reader\n\t\treturnTaskID       string\n\t\treturnError        error\n\t\tctxTimeout         time.Duration\n\t\tproviderName       string\n\t\tprovidersAttempted int\n\t}{\n\t\t{\"success with metadata\", common.SlugBIOS, string(constants.OnReset), false, nil, \"1234\", nil, 5 * time.Second, \"foo\", 1},\n\t\t{\"failure with metadata\", common.SlugBIOS, string(constants.OnReset), false, nil, \"1234\", bmclibErrs.ErrNon200Response, 5 * time.Second, \"foo\", 1},\n\t\t{\"failure with context timeout\", common.SlugBIOS, string(constants.OnReset), false, nil, \"1234\", context.DeadlineExceeded, 1 * time.Nanosecond, \"foo\", 1},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\ttestImplementation := firmwareInstallTester{returnTaskID: tc.returnTaskID, returnError: tc.returnError}\n\t\t\tif tc.ctxTimeout == 0 {\n\t\t\t\ttc.ctxTimeout = time.Second * 3\n\t\t\t}\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)\n\t\t\tdefer cancel()\n\t\t\ttaskID, metadata, err := firmwareInstall(ctx, tc.component, tc.applyAt, tc.forceInstall, tc.reader, []firmwareInstallerProvider{{tc.providerName, &testImplementation}})\n\t\t\tif tc.returnError != nil {\n\t\t\t\tassert.ErrorIs(t, err, tc.returnError)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tassert.Equal(t, tc.returnTaskID, taskID)\n\t\t\tassert.Equal(t, tc.providerName, metadata.SuccessfulProvider)\n\t\t\tassert.Equal(t, tc.providersAttempted, len(metadata.ProvidersAttempted))\n\t\t})\n\t}\n}\nfunc TestFirmwareInstallFromInterfaces(t *testing.T) {\n\ttestCases := []struct {\n\t\ttestName          string\n\t\tcomponent         string\n\t\tapplyAt           string\n\t\tforceInstall      bool\n\t\treader            io.Reader\n\t\treturnTaskID      string\n\t\treturnError       error\n\t\tproviderName      string\n\t\tbadImplementation bool\n\t}{\n\t\t{\"success with metadata\", common.SlugBIOS, string(constants.OnReset), false, nil, \"1234\", nil, \"foo\", false},\n\t\t{\"failure with metadata\", common.SlugBIOS, string(constants.OnReset), false, nil, \"1234\", bmclibErrs.ErrProviderImplementation, \"foo\", true},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\tvar generic []interface{}\n\t\t\tif tc.badImplementation {\n\t\t\t\tbadImplementation := struct{}{}\n\t\t\t\tgeneric = []interface{}{&badImplementation}\n\t\t\t} else {\n\t\t\t\ttestImplementation := &firmwareInstallTester{returnTaskID: tc.returnTaskID, returnError: tc.returnError}\n\t\t\t\tgeneric = []interface{}{testImplementation}\n\t\t\t}\n\t\t\ttaskID, metadata, err := FirmwareInstallFromInterfaces(context.Background(), tc.component, tc.applyAt, tc.forceInstall, tc.reader, generic)\n\t\t\tif tc.returnError != nil {\n\t\t\t\tassert.ErrorIs(t, err, tc.returnError)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tassert.Equal(t, tc.returnTaskID, taskID)\n\t\t\tassert.Equal(t, tc.providerName, metadata.SuccessfulProvider)\n\t\t})\n\t}\n}\n\ntype firmwareInstallStatusTester struct {\n\treturnStatus string\n\treturnError  error\n}\n\nfunc (f *firmwareInstallStatusTester) FirmwareInstallStatus(ctx context.Context, installVersion, component, taskID string) (status string, err error) {\n\treturn f.returnStatus, f.returnError\n}\n\nfunc (r *firmwareInstallStatusTester) Name() string {\n\treturn \"foo\"\n}\n\nfunc TestFirmwareInstallStatus(t *testing.T) {\n\ttestCases := []struct {\n\t\ttestName           string\n\t\tcomponent          string\n\t\tinstallVersion     string\n\t\ttaskID             string\n\t\treturnStatus       string\n\t\treturnError        error\n\t\tctxTimeout         time.Duration\n\t\tproviderName       string\n\t\tprovidersAttempted int\n\t}{\n\t\t{\"success with metadata\", common.SlugBIOS, \"1.1\", \"1234\", constants.FirmwareInstallComplete, nil, 5 * time.Second, \"foo\", 1},\n\t\t{\"failure with metadata\", common.SlugBIOS, \"1.1\", \"1234\", constants.FirmwareInstallFailed, bmclibErrs.ErrNon200Response, 5 * time.Second, \"foo\", 1},\n\t\t{\"failure with context timeout\", common.SlugBIOS, \"1.1\", \"1234\", \"\", context.DeadlineExceeded, 1 * time.Nanosecond, \"foo\", 1},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\ttestImplementation := firmwareInstallStatusTester{returnStatus: tc.returnStatus, returnError: tc.returnError}\n\t\t\tif tc.ctxTimeout == 0 {\n\t\t\t\ttc.ctxTimeout = time.Second * 4\n\t\t\t}\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)\n\t\t\tdefer cancel()\n\t\t\ttaskID, metadata, err := firmwareInstallStatus(ctx, tc.installVersion, tc.component, tc.taskID, []firmwareInstallVerifierProvider{{tc.providerName, &testImplementation}})\n\t\t\tif tc.returnError != nil {\n\t\t\t\tassert.ErrorIs(t, err, tc.returnError)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tassert.Equal(t, tc.returnStatus, taskID)\n\t\t\tassert.Equal(t, tc.providerName, metadata.SuccessfulProvider)\n\t\t\tassert.Equal(t, tc.providersAttempted, len(metadata.ProvidersAttempted))\n\t\t})\n\t}\n}\n\nfunc TestFirmwareInstallStatusFromInterfaces(t *testing.T) {\n\ttestCases := []struct {\n\t\ttestName          string\n\t\tcomponent         string\n\t\tinstallVersion    string\n\t\ttaskID            string\n\t\treturnStatus      string\n\t\treturnError       error\n\t\tproviderName      string\n\t\tbadImplementation bool\n\t}{\n\t\t{\"success with metadata\", common.SlugBIOS, \"1.1\", \"1234\", \"status-done\", nil, \"foo\", false},\n\t\t{\"failure with bad implementation\", common.SlugBIOS, \"1.1\", \"1234\", \"status-done\", bmclibErrs.ErrProviderImplementation, \"foo\", true},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\tvar generic []interface{}\n\t\t\tif tc.badImplementation {\n\t\t\t\tbadImplementation := struct{}{}\n\t\t\t\tgeneric = []interface{}{&badImplementation}\n\t\t\t} else {\n\t\t\t\ttestImplementation := &firmwareInstallStatusTester{returnStatus: tc.returnStatus, returnError: tc.returnError}\n\t\t\t\tgeneric = []interface{}{testImplementation}\n\t\t\t}\n\t\t\tstatus, metadata, err := FirmwareInstallStatusFromInterfaces(context.Background(), tc.component, tc.installVersion, tc.taskID, generic)\n\t\t\tif tc.returnError != nil {\n\t\t\t\tassert.ErrorIs(t, err, tc.returnError)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tassert.Equal(t, tc.returnStatus, status)\n\t\t\tassert.Equal(t, tc.providerName, metadata.SuccessfulProvider)\n\t\t})\n\t}\n}\n\ntype firmwareInstallUploadAndInitiateTester struct {\n\treturnTaskID string\n\treturnError  error\n}\n\nfunc (f *firmwareInstallUploadAndInitiateTester) FirmwareInstallUploadAndInitiate(ctx context.Context, component string, file *os.File) (taskID string, err error) {\n\treturn f.returnTaskID, f.returnError\n}\n\nfunc (r *firmwareInstallUploadAndInitiateTester) Name() string {\n\treturn \"foo\"\n}\n\nfunc TestFirmwareInstallUploadAndInitiate(t *testing.T) {\n\ttestCases := []struct {\n\t\ttestName           string\n\t\tcomponent          string\n\t\tfile               *os.File\n\t\treturnTaskID       string\n\t\treturnError        error\n\t\tctxTimeout         time.Duration\n\t\tproviderName       string\n\t\tprovidersAttempted int\n\t}{\n\t\t{\"success with metadata\", \"componentA\", &os.File{}, \"1234\", nil, 5 * time.Second, \"foo\", 1},\n\t\t{\"failure with metadata\", \"componentB\", &os.File{}, \"1234\", errors.New(\"failed to upload and initiate\"), 5 * time.Second, \"foo\", 1},\n\t\t{\"failure with context timeout\", \"componentC\", &os.File{}, \"\", context.DeadlineExceeded, 1 * time.Nanosecond, \"foo\", 1},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\ttestImplementation := &firmwareInstallUploadAndInitiateTester{returnTaskID: tc.returnTaskID, returnError: tc.returnError}\n\t\t\tif tc.ctxTimeout == 0 {\n\t\t\t\ttc.ctxTimeout = time.Second * 3\n\t\t\t}\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)\n\t\t\tdefer cancel()\n\t\t\ttaskID, metadata, err := firmwareInstallUploadAndInitiate(ctx, tc.component, tc.file, []firmwareInstallProvider{{tc.providerName, testImplementation}})\n\t\t\tif tc.returnError != nil {\n\t\t\t\tassert.ErrorIs(t, err, tc.returnError)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tassert.Equal(t, tc.returnTaskID, taskID)\n\t\t\tassert.Equal(t, tc.providerName, metadata.SuccessfulProvider)\n\t\t\tassert.Equal(t, tc.providersAttempted, len(metadata.ProvidersAttempted))\n\t\t})\n\t}\n}\n\nfunc TestFirmwareInstallUploadAndInitiateFromInterfaces(t *testing.T) {\n\ttestCases := []struct {\n\t\ttestName          string\n\t\tcomponent         string\n\t\tfile              *os.File\n\t\treturnTaskID      string\n\t\treturnError       error\n\t\tproviderName      string\n\t\tbadImplementation bool\n\t}{\n\t\t{\"success with metadata\", \"componentA\", &os.File{}, \"1234\", nil, \"foo\", false},\n\t\t{\"failure with bad implementation\", \"componentB\", &os.File{}, \"1234\", bmclibErrs.ErrProviderImplementation, \"foo\", true},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\tvar generic []interface{}\n\t\t\tif tc.badImplementation {\n\t\t\t\tbadImplementation := struct{}{}\n\t\t\t\tgeneric = []interface{}{&badImplementation}\n\t\t\t} else {\n\t\t\t\ttestImplementation := &firmwareInstallUploadAndInitiateTester{returnTaskID: tc.returnTaskID, returnError: tc.returnError}\n\t\t\t\tgeneric = []interface{}{testImplementation}\n\t\t\t}\n\t\t\ttaskID, metadata, err := FirmwareInstallUploadAndInitiateFromInterfaces(context.Background(), tc.component, tc.file, generic)\n\t\t\tif tc.returnError != nil {\n\t\t\t\tassert.ErrorIs(t, err, tc.returnError)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tassert.Equal(t, tc.returnTaskID, taskID)\n\t\t\tassert.Equal(t, tc.providerName, metadata.SuccessfulProvider)\n\t\t})\n\t}\n}\n\ntype firmwareInstallUploadTester struct {\n\tTaskID string\n\tErr    error\n}\n\nfunc (f *firmwareInstallUploadTester) FirmwareInstallUploaded(ctx context.Context, component, uploadTaskID string) (taskID string, err error) {\n\treturn f.TaskID, f.Err\n}\n\nfunc (r *firmwareInstallUploadTester) Name() string {\n\treturn \"foo\"\n}\n\nfunc TestFirmwareInstallUploaded(t *testing.T) {\n\ttestCases := []struct {\n\t\ttestName           string\n\t\tcomponent          string\n\t\tuploadTaskID       string\n\t\treturnTaskID       string\n\t\treturnError        error\n\t\tctxTimeout         time.Duration\n\t\tproviderName       string\n\t\tprovidersAttempted int\n\t}{\n\t\t{\"success with metadata\", common.SlugBIOS, \"1234\", \"5678\", nil, 5 * time.Second, \"foo\", 1},\n\t\t{\"failure with metadata\", common.SlugBIOS, \"1234\", \"\", bmclibErrs.ErrNon200Response, 5 * time.Second, \"foo\", 1},\n\t\t{\"failure with context timeout\", common.SlugBIOS, \"1234\", \"\", context.DeadlineExceeded, 1 * time.Nanosecond, \"foo\", 1},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\tmockImplementation := &firmwareInstallUploadTester{TaskID: tc.returnTaskID, Err: tc.returnError}\n\t\t\tif tc.ctxTimeout == 0 {\n\t\t\t\ttc.ctxTimeout = time.Second * 4\n\t\t\t}\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)\n\t\t\tdefer cancel()\n\n\t\t\ttaskID, metadata, err := firmwareInstallUploaded(ctx, tc.component, tc.uploadTaskID, []firmwareInstallerWithOptionsProvider{{tc.providerName, mockImplementation}})\n\t\t\tif tc.returnError != nil {\n\t\t\t\tassert.ErrorIs(t, err, tc.returnError)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tassert.Equal(t, tc.returnTaskID, taskID)\n\t\t\tassert.Equal(t, tc.providerName, metadata.SuccessfulProvider)\n\t\t\tassert.Equal(t, tc.providersAttempted, len(metadata.ProvidersAttempted))\n\t\t})\n\t}\n}\n\nfunc TestFirmwareInstallerUploadedFromInterfaces(t *testing.T) {\n\ttestCases := []struct {\n\t\ttestName          string\n\t\tcomponent         string\n\t\tuploadTaskID      string\n\t\treturnTaskID      string\n\t\treturnError       error\n\t\tproviderName      string\n\t\tbadImplementation bool\n\t}{\n\t\t{\"success with metadata\", common.SlugBIOS, \"1234\", \"5678\", nil, \"foo\", false},\n\t\t{\"failure with bad implementation\", common.SlugBIOS, \"1234\", \"\", bmclibErrs.ErrProviderImplementation, \"foo\", true},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\tvar generic []interface{}\n\t\t\tif tc.badImplementation {\n\t\t\t\tbadImplementation := struct{}{}\n\t\t\t\tgeneric = []interface{}{&badImplementation}\n\t\t\t} else {\n\t\t\t\tmockImplementation := &firmwareInstallUploadTester{TaskID: tc.returnTaskID, Err: tc.returnError}\n\t\t\t\tgeneric = []interface{}{mockImplementation}\n\t\t\t}\n\n\t\t\tinstallTaskID, metadata, err := FirmwareInstallerUploadedFromInterfaces(context.Background(), tc.component, tc.uploadTaskID, generic)\n\t\t\tif tc.returnError != nil {\n\t\t\t\tassert.ErrorIs(t, err, tc.returnError)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tassert.Equal(t, tc.returnTaskID, installTaskID)\n\t\t\tassert.Equal(t, tc.providerName, metadata.SuccessfulProvider)\n\t\t})\n\t}\n}\n\ntype firmwareUploadTester struct {\n\treturnTaskID string\n\treturnError  error\n}\n\nfunc (f *firmwareUploadTester) FirmwareUpload(ctx context.Context, component string, file *os.File) (uploadVerifyTaskID string, err error) {\n\treturn f.returnTaskID, f.returnError\n}\n\nfunc (r *firmwareUploadTester) Name() string {\n\treturn \"foo\"\n}\n\nfunc TestFirmwareUpload(t *testing.T) {\n\ttestCases := []struct {\n\t\ttestName           string\n\t\tcomponent          string\n\t\tfile               *os.File\n\t\treturnTaskID       string\n\t\treturnError        error\n\t\tctxTimeout         time.Duration\n\t\tproviderName       string\n\t\tprovidersAttempted int\n\t}{\n\t\t{\"success with metadata\", common.SlugBIOS, nil, \"1234\", nil, 5 * time.Second, \"foo\", 1},\n\t\t{\"failure with metadata\", common.SlugBIOS, nil, \"1234\", bmclibErrs.ErrNon200Response, 5 * time.Second, \"foo\", 1},\n\t\t{\"failure with context timeout\", common.SlugBIOS, nil, \"1234\", context.DeadlineExceeded, 1 * time.Nanosecond, \"foo\", 1},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\ttestImplementation := firmwareUploadTester{returnTaskID: tc.returnTaskID, returnError: tc.returnError}\n\t\t\tif tc.ctxTimeout == 0 {\n\t\t\t\ttc.ctxTimeout = time.Second * 3\n\t\t\t}\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)\n\t\t\tdefer cancel()\n\t\t\ttaskID, metadata, err := firmwareUpload(ctx, tc.component, tc.file, []firmwareUploaderProvider{{tc.providerName, &testImplementation}})\n\t\t\tif tc.returnError != nil {\n\t\t\t\tassert.ErrorIs(t, err, tc.returnError)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tassert.Equal(t, tc.returnTaskID, taskID)\n\t\t\tassert.Equal(t, tc.providerName, metadata.SuccessfulProvider)\n\t\t\tassert.Equal(t, tc.providersAttempted, len(metadata.ProvidersAttempted))\n\t\t})\n\t}\n}\n\ntype firmwareInstallStepsGetterTester struct {\n\tSteps []constants.FirmwareInstallStep\n\tErr   error\n}\n\nfunc (m *firmwareInstallStepsGetterTester) FirmwareInstallSteps(ctx context.Context, component string) ([]constants.FirmwareInstallStep, error) {\n\treturn m.Steps, m.Err\n}\n\nfunc (m *firmwareInstallStepsGetterTester) Name() string {\n\treturn \"foo\"\n}\n\nfunc TestFirmwareInstallStepsFromInterfaces(t *testing.T) {\n\ttestCases := []struct {\n\t\ttestName          string\n\t\tcomponent         string\n\t\treturnSteps       []constants.FirmwareInstallStep\n\t\treturnError       error\n\t\tproviderName      string\n\t\tbadImplementation bool\n\t}{\n\t\t{\"success with metadata\", common.SlugBIOS, []constants.FirmwareInstallStep{constants.FirmwareInstallStepUpload, constants.FirmwareInstallStepInstallStatus}, nil, \"foo\", false},\n\t\t{\"failure with bad implementation\", common.SlugBIOS, nil, bmclibErrs.ErrProviderImplementation, \"foo\", true},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\tvar generic []interface{}\n\t\t\tif tc.badImplementation {\n\t\t\t\tbadImplementation := struct{}{}\n\t\t\t\tgeneric = []interface{}{&badImplementation}\n\t\t\t} else {\n\t\t\t\tmockImplementation := &firmwareInstallStepsGetterTester{Steps: tc.returnSteps, Err: tc.returnError}\n\t\t\t\tgeneric = []interface{}{mockImplementation}\n\t\t\t}\n\n\t\t\tsteps, metadata, err := FirmwareInstallStepsFromInterfaces(context.Background(), tc.component, generic)\n\t\t\tif tc.returnError != nil {\n\t\t\t\tassert.ErrorIs(t, err, tc.returnError)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tassert.Equal(t, tc.returnSteps, steps)\n\t\t\tassert.Equal(t, tc.providerName, metadata.SuccessfulProvider)\n\t\t})\n\t}\n}\n\ntype firmwareInstallStepsTester struct {\n\treturnSteps []constants.FirmwareInstallStep\n\treturnError error\n}\n\nfunc (f *firmwareInstallStepsTester) FirmwareInstallSteps(ctx context.Context, component string) (steps []constants.FirmwareInstallStep, err error) {\n\treturn f.returnSteps, f.returnError\n}\n\nfunc (r *firmwareInstallStepsTester) Name() string {\n\treturn \"foo\"\n}\n\nfunc TestFirmwareInstallSteps(t *testing.T) {\n\ttestCases := []struct {\n\t\ttestName           string\n\t\tcomponent          string\n\t\treturnSteps        []constants.FirmwareInstallStep\n\t\treturnError        error\n\t\tctxTimeout         time.Duration\n\t\tproviderName       string\n\t\tprovidersAttempted int\n\t}{\n\t\t{\"success with metadata\", common.SlugBIOS, []constants.FirmwareInstallStep{constants.FirmwareInstallStepUpload, constants.FirmwareInstallStepInstallStatus}, nil, 5 * time.Second, \"foo\", 1},\n\t\t{\"failure with metadata\", common.SlugBIOS, nil, bmclibErrs.ErrNon200Response, 5 * time.Second, \"foo\", 1},\n\t\t{\"failure with context timeout\", common.SlugBIOS, nil, context.DeadlineExceeded, 1 * time.Nanosecond, \"foo\", 1},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\ttestImplementation := firmwareInstallStepsTester{returnSteps: tc.returnSteps, returnError: tc.returnError}\n\t\t\tif tc.ctxTimeout == 0 {\n\t\t\t\ttc.ctxTimeout = time.Second * 3\n\t\t\t}\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)\n\t\t\tdefer cancel()\n\t\t\tsteps, metadata, err := firmwareInstallSteps(ctx, tc.component, []firmwareInstallStepsGetterProvider{{tc.providerName, &testImplementation}})\n\t\t\tif tc.returnError != nil {\n\t\t\t\tassert.ErrorIs(t, err, tc.returnError)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tassert.Equal(t, tc.returnSteps, steps)\n\t\t\tassert.Equal(t, tc.providerName, metadata.SuccessfulProvider)\n\t\t\tassert.Equal(t, tc.providersAttempted, len(metadata.ProvidersAttempted))\n\t\t})\n\t}\n}\n\ntype firmwareTaskStatusTester struct {\n\treturnState  constants.TaskState\n\treturnStatus string\n\treturnError  error\n}\n\nfunc (f *firmwareTaskStatusTester) FirmwareTaskStatus(ctx context.Context, kind constants.FirmwareInstallStep, component, taskID, installVersion string) (state constants.TaskState, status string, err error) {\n\treturn f.returnState, f.returnStatus, f.returnError\n}\n\nfunc (r *firmwareTaskStatusTester) Name() string {\n\treturn \"foo\"\n}\n\nfunc TestFirmwareTaskStatus(t *testing.T) {\n\ttestCases := []struct {\n\t\ttestName           string\n\t\tkind               constants.FirmwareInstallStep\n\t\tcomponent          string\n\t\ttaskID             string\n\t\tinstallVersion     string\n\t\treturnState        constants.TaskState\n\t\treturnStatus       string\n\t\treturnError        error\n\t\tctxTimeout         time.Duration\n\t\tproviderName       string\n\t\tprovidersAttempted int\n\t}{\n\t\t{\"success with metadata\", constants.FirmwareInstallStepUpload, common.SlugBIOS, \"1234\", \"1.0\", constants.FirmwareInstallComplete, \"Upload completed\", nil, 5 * time.Second, \"foo\", 1},\n\t\t{\"failure with metadata\", constants.FirmwareInstallStepUpload, common.SlugBIOS, \"1234\", \"1.0\", constants.FirmwareInstallFailed, \"Upload failed\", bmclibErrs.ErrNon200Response, 5 * time.Second, \"foo\", 1},\n\t\t{\"failure with context timeout\", constants.FirmwareInstallStepUpload, common.SlugBIOS, \"1234\", \"1.0\", \"\", \"\", context.DeadlineExceeded, 1 * time.Nanosecond, \"foo\", 1},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\ttestImplementation := firmwareTaskStatusTester{returnState: tc.returnState, returnStatus: tc.returnStatus, returnError: tc.returnError}\n\t\t\tif tc.ctxTimeout == 0 {\n\t\t\t\ttc.ctxTimeout = time.Second * 3\n\t\t\t}\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)\n\t\t\tdefer cancel()\n\t\t\tstate, status, metadata, err := firmwareTaskStatus(ctx, tc.kind, tc.component, tc.taskID, tc.installVersion, []firmwareTaskVerifierProvider{{tc.providerName, &testImplementation}})\n\t\t\tif tc.returnError != nil {\n\t\t\t\tassert.ErrorIs(t, err, tc.returnError)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tassert.Equal(t, tc.returnState, state)\n\t\t\tassert.Equal(t, tc.returnStatus, status)\n\t\t\tassert.Equal(t, tc.providerName, metadata.SuccessfulProvider)\n\t\t\tassert.Equal(t, tc.providersAttempted, len(metadata.ProvidersAttempted))\n\t\t})\n\t}\n}\n\nfunc TestFirmwareTaskStatusFromInterfaces(t *testing.T) {\n\ttestCases := []struct {\n\t\ttestName           string\n\t\tkind               constants.FirmwareInstallStep\n\t\tcomponent          string\n\t\ttaskID             string\n\t\tinstallVersion     string\n\t\treturnState        constants.TaskState\n\t\treturnStatus       string\n\t\treturnError        error\n\t\tctxTimeout         time.Duration\n\t\tproviderName       string\n\t\tprovidersAttempted int\n\t}{\n\t\t{\"success with metadata\", constants.FirmwareInstallStepUpload, common.SlugBIOS, \"1234\", \"1.0\", constants.Complete, \"uploading\", nil, 5 * time.Second, \"foo\", 1},\n\t\t{\"failure with metadata\", constants.FirmwareInstallStepUpload, common.SlugBIOS, \"1234\", \"1.0\", constants.Failed, \"failed\", bmclibErrs.ErrNon200Response, 5 * time.Second, \"foo\", 1},\n\t\t{\"failure with context timeout\", constants.FirmwareInstallStepUpload, common.SlugBIOS, \"1234\", \"1.0\", \"\", \"\", context.DeadlineExceeded, 1 * time.Nanosecond, \"foo\", 1},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\ttestImplementation := firmwareTaskStatusTester{\n\t\t\t\treturnState:  tc.returnState,\n\t\t\t\treturnStatus: tc.returnStatus,\n\t\t\t\treturnError:  tc.returnError,\n\t\t\t}\n\t\t\tif tc.ctxTimeout == 0 {\n\t\t\t\ttc.ctxTimeout = time.Second * 3\n\t\t\t}\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)\n\t\t\tdefer cancel()\n\t\t\tstate, status, metadata, err := FirmwareTaskStatusFromInterfaces(ctx, tc.kind, tc.component, tc.taskID, tc.installVersion, []interface{}{&testImplementation})\n\t\t\tif tc.returnError != nil {\n\t\t\t\tassert.ErrorIs(t, err, tc.returnError)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tassert.Equal(t, tc.returnState, state)\n\t\t\tassert.Equal(t, tc.returnStatus, status)\n\t\t\tassert.Equal(t, tc.providerName, metadata.SuccessfulProvider)\n\t\t\tassert.Equal(t, tc.providersAttempted, len(metadata.ProvidersAttempted))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "bmc/floppy.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/hashicorp/go-multierror\"\n\t\"github.com/pkg/errors\"\n)\n\n// FloppyImageMounter defines methods to upload a floppy image\ntype FloppyImageMounter interface {\n\tMountFloppyImage(ctx context.Context, image io.Reader) (err error)\n}\n\n// floppyImageUploaderProvider is an internal struct to correlate an implementation/provider and its name\ntype floppyImageUploaderProvider struct {\n\tname string\n\timpl FloppyImageMounter\n}\n\n// mountFloppyImage is a wrapper method to invoke methods for the FloppyImageMounter interface\nfunc mountFloppyImage(ctx context.Context, image io.Reader, p []floppyImageUploaderProvider) (metadata Metadata, err error) {\n\tvar metadataLocal Metadata\n\n\tfor _, elem := range p {\n\t\tif elem.impl == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn metadata, err\n\t\tdefault:\n\t\t\tmetadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)\n\t\t\tuploadErr := elem.impl.MountFloppyImage(ctx, image)\n\t\t\tif uploadErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(uploadErr, \"provider: %v\", elem.name))\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tmetadataLocal.SuccessfulProvider = elem.name\n\t\t\treturn metadataLocal, nil\n\t\t}\n\t}\n\n\treturn metadataLocal, multierror.Append(err, errors.New(\"failed to mount floppy image\"))\n}\n\n// MountFloppyImageFromInterfaces identifies implementations of the FloppyImageMounter interface and passes the found implementations to the mountFloppyImage() wrapper\nfunc MountFloppyImageFromInterfaces(ctx context.Context, image io.Reader, p []interface{}) (metadata Metadata, err error) {\n\tproviders := make([]floppyImageUploaderProvider, 0)\n\tfor _, elem := range p {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := floppyImageUploaderProvider{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase FloppyImageMounter:\n\t\t\ttemp.impl = p\n\t\t\tproviders = append(providers, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a FloppyImageMounter implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\n\tif len(providers) == 0 {\n\t\treturn metadata, multierror.Append(\n\t\t\terr,\n\t\t\terrors.Wrap(\n\t\t\t\tbmclibErrs.ErrProviderImplementation,\n\t\t\t\t\"no FloppyImageMounter implementations found\",\n\t\t\t),\n\t\t)\n\n\t}\n\n\treturn mountFloppyImage(ctx, image, providers)\n}\n\n// FloppyImageMounter defines methods to unmount a floppy image\ntype FloppyImageUnmounter interface {\n\tUnmountFloppyImage(ctx context.Context) (err error)\n}\n\n// floppyImageUnmounterProvider is an internal struct to correlate an implementation/provider and its name\ntype floppyImageUnmounterProvider struct {\n\tname string\n\timpl FloppyImageUnmounter\n}\n\n// unmountFloppyImage is a wrapper method to invoke methods for the FloppyImageUnmounter interface\nfunc unmountFloppyImage(ctx context.Context, p []floppyImageUnmounterProvider) (metadata Metadata, err error) {\n\tvar metadataLocal Metadata\n\n\tfor _, elem := range p {\n\t\tif elem.impl == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn metadata, err\n\t\tdefault:\n\t\t\tmetadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)\n\t\t\tuploadErr := elem.impl.UnmountFloppyImage(ctx)\n\t\t\tif uploadErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(uploadErr, \"provider: %v\", elem.name))\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tmetadataLocal.SuccessfulProvider = elem.name\n\t\t\treturn metadataLocal, nil\n\t\t}\n\t}\n\n\treturn metadataLocal, multierror.Append(err, errors.New(\"failed to unmount floppy image\"))\n}\n\n// MountFloppyImageFromInterfaces identifies implementations of the FloppyImageUnmounter interface and passes the found implementations to the unmountFloppyImage() wrapper\nfunc UnmountFloppyImageFromInterfaces(ctx context.Context, p []interface{}) (metadata Metadata, err error) {\n\tproviders := make([]floppyImageUnmounterProvider, 0)\n\tfor _, elem := range p {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := floppyImageUnmounterProvider{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase FloppyImageUnmounter:\n\t\t\ttemp.impl = p\n\t\t\tproviders = append(providers, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a FloppyImageUnmounter implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\n\tif len(providers) == 0 {\n\t\treturn metadata, multierror.Append(\n\t\t\terr,\n\t\t\terrors.Wrap(\n\t\t\t\tbmclibErrs.ErrProviderImplementation,\n\t\t\t\t\"no FloppyImageUnmounter implementations found\",\n\t\t\t),\n\t\t)\n\t}\n\n\treturn unmountFloppyImage(ctx, providers)\n}\n"
  },
  {
    "path": "bmc/floppy_test.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"testing\"\n\t\"time\"\n\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype mountFloppyImageTester struct {\n\treturnError error\n}\n\nfunc (p *mountFloppyImageTester) MountFloppyImage(ctx context.Context, reader io.Reader) (err error) {\n\treturn p.returnError\n}\n\nfunc (p *mountFloppyImageTester) Name() string {\n\treturn \"foo\"\n}\n\nfunc TestMountFloppyFromInterfaces(t *testing.T) {\n\ttestCases := []struct {\n\t\ttestName           string\n\t\timage              io.Reader\n\t\treturnError        error\n\t\tctxTimeout         time.Duration\n\t\tproviderName       string\n\t\tprovidersAttempted int\n\t\tbadImplementation  bool\n\t}{\n\t\t{\"success with metadata\", nil, nil, 5 * time.Second, \"foo\", 1, false},\n\t\t{\"failure with bad implementation\", nil, bmclibErrs.ErrProviderImplementation, 1 * time.Nanosecond, \"foo\", 1, true},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\tvar generic []interface{}\n\t\t\tif tc.badImplementation {\n\t\t\t\tbadImplementation := struct{}{}\n\t\t\t\tgeneric = []interface{}{&badImplementation}\n\t\t\t} else {\n\t\t\t\ttestImplementation := &mountFloppyImageTester{returnError: tc.returnError}\n\t\t\t\tgeneric = []interface{}{testImplementation}\n\t\t\t}\n\t\t\tmetadata, err := MountFloppyImageFromInterfaces(context.Background(), tc.image, generic)\n\t\t\tif tc.returnError != nil {\n\t\t\t\tassert.ErrorContains(t, err, tc.returnError.Error())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tassert.Equal(t, tc.returnError, err)\n\t\t\tassert.Equal(t, tc.providerName, metadata.SuccessfulProvider)\n\t\t})\n\t}\n}\n\ntype unmountFloppyImageTester struct {\n\treturnError error\n}\n\nfunc (p *unmountFloppyImageTester) UnmountFloppyImage(ctx context.Context) (err error) {\n\treturn p.returnError\n}\n\nfunc (p *unmountFloppyImageTester) Name() string {\n\treturn \"foo\"\n}\n\nfunc TestUnmountFloppyFromInterfaces(t *testing.T) {\n\ttestCases := []struct {\n\t\ttestName           string\n\t\treturnError        error\n\t\tctxTimeout         time.Duration\n\t\tproviderName       string\n\t\tprovidersAttempted int\n\t\tbadImplementation  bool\n\t}{\n\t\t{\"success with metadata\", nil, 5 * time.Second, \"foo\", 1, false},\n\t\t{\"failure with bad implementation\", bmclibErrs.ErrProviderImplementation, 1 * time.Nanosecond, \"foo\", 1, true},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\tvar generic []interface{}\n\t\t\tif tc.badImplementation {\n\t\t\t\tbadImplementation := struct{}{}\n\t\t\t\tgeneric = []interface{}{&badImplementation}\n\t\t\t} else {\n\t\t\t\ttestImplementation := &unmountFloppyImageTester{returnError: tc.returnError}\n\t\t\t\tgeneric = []interface{}{testImplementation}\n\t\t\t}\n\t\t\tmetadata, err := UnmountFloppyImageFromInterfaces(context.Background(), generic)\n\t\t\tif tc.returnError != nil {\n\t\t\t\tassert.ErrorContains(t, err, tc.returnError.Error())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tassert.Equal(t, tc.returnError, err)\n\t\t\tassert.Equal(t, tc.providerName, metadata.SuccessfulProvider)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "bmc/inventory.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/hashicorp/go-multierror\"\n\t\"github.com/pkg/errors\"\n\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/bmc-toolbox/common\"\n)\n\n// InventoryGetter defines methods to retrieve device hardware and firmware inventory\ntype InventoryGetter interface {\n\tInventory(ctx context.Context) (device *common.Device, err error)\n}\n\ntype inventoryGetterProvider struct {\n\tname string\n\tInventoryGetter\n}\n\n// inventory returns hardware and firmware inventory\nfunc inventory(ctx context.Context, generic []inventoryGetterProvider) (device *common.Device, metadata Metadata, err error) {\n\tvar metadataLocal Metadata\n\n\tfor _, elem := range generic {\n\t\tif elem.InventoryGetter == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn device, metadata, err\n\t\tdefault:\n\t\t\tmetadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)\n\t\t\tdevice, vErr := elem.Inventory(ctx)\n\t\t\tif vErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(vErr, \"provider: %v\", elem.name))\n\t\t\t\terr = multierror.Append(err, vErr)\n\t\t\t\tcontinue\n\n\t\t\t}\n\t\t\tmetadataLocal.SuccessfulProvider = elem.name\n\t\t\treturn device, metadataLocal, nil\n\t\t}\n\t}\n\n\treturn device, metadataLocal, multierror.Append(err, errors.New(\"failure to get device inventory\"))\n}\n\n// GetInventoryFromInterfaces identifies implementations of the InventoryGetter interface and passes the found implementations to the inventory() wrapper method\nfunc GetInventoryFromInterfaces(ctx context.Context, generic []interface{}) (device *common.Device, metadata Metadata, err error) {\n\tmetadata = newMetadata()\n\n\timplementations := make([]inventoryGetterProvider, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := inventoryGetterProvider{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase InventoryGetter:\n\t\t\ttemp.InventoryGetter = p\n\t\t\timplementations = append(implementations, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a InventoryGetter implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(implementations) == 0 {\n\t\treturn device, metadata, multierror.Append(\n\t\t\terr,\n\t\t\terrors.Wrap(\n\t\t\t\tbmclibErrs.ErrProviderImplementation,\n\t\t\t\t(\"no InventoryGetter implementations found\"),\n\t\t\t),\n\t\t)\n\t}\n\n\treturn inventory(ctx, implementations)\n}\n"
  },
  {
    "path": "bmc/inventory_test.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/errors\"\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/bmc-toolbox/common\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype inventoryGetterTester struct {\n\treturnDevice *common.Device\n\treturnError  error\n}\n\nfunc (f *inventoryGetterTester) Inventory(ctx context.Context) (device *common.Device, err error) {\n\treturn f.returnDevice, f.returnError\n}\n\nfunc (f *inventoryGetterTester) Name() string {\n\treturn \"foo\"\n}\n\nfunc TestInventory(t *testing.T) {\n\ttestCases := []struct {\n\t\ttestName           string\n\t\treturnDevice       *common.Device\n\t\treturnError        error\n\t\tctxTimeout         time.Duration\n\t\tproviderName       string\n\t\tprovidersAttempted int\n\t}{\n\t\t{\"success with metadata\", &common.Device{Common: common.Common{Vendor: \"foo\"}}, nil, 5 * time.Second, \"foo\", 1},\n\t\t{\"failure with metadata\", nil, errors.ErrNon200Response, 5 * time.Second, \"foo\", 1},\n\t\t{\"failure with context timeout\", nil, context.DeadlineExceeded, 1 * time.Nanosecond, \"foo\", 1},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\ttestImplementation := inventoryGetterTester{returnDevice: tc.returnDevice, returnError: tc.returnError}\n\t\t\tif tc.ctxTimeout == 0 {\n\t\t\t\ttc.ctxTimeout = time.Second * 3\n\t\t\t}\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)\n\t\t\tdefer cancel()\n\t\t\tdevice, metadata, err := inventory(ctx, []inventoryGetterProvider{{tc.providerName, &testImplementation}})\n\t\t\tif tc.returnError != nil {\n\t\t\t\tassert.ErrorIs(t, err, tc.returnError)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tassert.Equal(t, tc.returnDevice, device)\n\t\t\tassert.Equal(t, tc.providerName, metadata.SuccessfulProvider)\n\t\t\tassert.Equal(t, tc.providersAttempted, len(metadata.ProvidersAttempted))\n\t\t})\n\t}\n}\n\nfunc TestInventoryFromInterfaces(t *testing.T) {\n\ttestCases := []struct {\n\t\ttestName           string\n\t\treturnDevice       *common.Device\n\t\treturnError        error\n\t\tctxTimeout         time.Duration\n\t\tproviderName       string\n\t\tprovidersAttempted int\n\t\tbadImplementation  bool\n\t}{\n\t\t{\"success with metadata\", &common.Device{Common: common.Common{Vendor: \"foo\"}}, nil, 5 * time.Second, \"foo\", 1, false},\n\t\t{\"failure with bad implementation\", nil, bmclibErrs.ErrProviderImplementation, 5 * time.Second, \"foo\", 1, true},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\tvar generic []interface{}\n\t\t\tif tc.badImplementation {\n\t\t\t\tbadImplementation := struct{}{}\n\t\t\t\tgeneric = []interface{}{&badImplementation}\n\t\t\t} else {\n\t\t\t\ttestImplementation := &inventoryGetterTester{returnDevice: tc.returnDevice, returnError: tc.returnError}\n\t\t\t\tgeneric = []interface{}{testImplementation}\n\t\t\t}\n\t\t\tdevice, metadata, err := GetInventoryFromInterfaces(context.Background(), generic)\n\t\t\tif tc.returnError != nil {\n\t\t\t\tassert.ErrorIs(t, err, tc.returnError)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tassert.Equal(t, tc.returnDevice, device)\n\t\t\tassert.Equal(t, tc.providerName, metadata.SuccessfulProvider)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "bmc/nmi.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/hashicorp/go-multierror\"\n)\n\ntype NMISender interface {\n\tSendNMI(ctx context.Context) error\n}\n\nfunc sendNMI(ctx context.Context, timeout time.Duration, sender NMISender, metadata *Metadata) error {\n\tsenderName := getProviderName(sender)\n\tctx, cancel := context.WithTimeout(ctx, timeout)\n\tdefer cancel()\n\n\tmetadata.ProvidersAttempted = append(metadata.ProvidersAttempted, senderName)\n\n\terr := sender.SendNMI(ctx)\n\tif err != nil {\n\t\tmetadata.FailedProviderDetail[senderName] = err.Error()\n\t\treturn err\n\t}\n\n\tmetadata.SuccessfulProvider = senderName\n\n\treturn nil\n}\n\n// SendNMIFromInterface will look for providers that implement NMISender\n// and attempt to call SendNMI until a provider is successful,\n// or all providers have been exhausted.\nfunc SendNMIFromInterface(\n\tctx context.Context,\n\ttimeout time.Duration,\n\tproviders []interface{},\n) (metadata Metadata, err error) {\n\tmetadata = newMetadata()\n\n\tfor _, provider := range providers {\n\t\tsender, ok := provider.(NMISender)\n\t\tif !ok {\n\t\t\terr = multierror.Append(err, fmt.Errorf(\"not an NMISender implementation: %T\", provider))\n\t\t\tcontinue\n\t\t}\n\n\t\tsendNMIErr := sendNMI(ctx, timeout, sender, &metadata)\n\t\tif sendNMIErr != nil {\n\t\t\terr = multierror.Append(err, sendNMIErr)\n\t\t\tcontinue\n\t\t}\n\t\treturn metadata, nil\n\t}\n\n\tif len(metadata.ProvidersAttempted) == 0 {\n\t\terr = multierror.Append(err, errors.New(\"no NMISender implementations found\"))\n\t} else {\n\t\terr = multierror.Append(err, errors.New(\"failed to send NMI\"))\n\t}\n\n\treturn metadata, err\n}\n"
  },
  {
    "path": "bmc/nmi_test.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype mockNMISender struct {\n\terr error\n}\n\nfunc (m *mockNMISender) SendNMI(ctx context.Context) error {\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn ctx.Err()\n\tdefault:\n\t\treturn m.err\n\t}\n}\n\nfunc (m *mockNMISender) Name() string {\n\treturn \"mock\"\n}\n\nfunc TestSendNMIFromInterface(t *testing.T) {\n\ttestCases := []struct {\n\t\tname             string\n\t\tmockSenders      []interface{}\n\t\terrMsg           string\n\t\tisTimedout       bool\n\t\texpectedMetadata Metadata\n\t}{\n\t\t{\n\t\t\tname:        \"success\",\n\t\t\tmockSenders: []interface{}{&mockNMISender{}},\n\t\t\texpectedMetadata: Metadata{\n\t\t\t\tSuccessfulProvider:   \"mock\",\n\t\t\t\tProvidersAttempted:   []string{\"mock\"},\n\t\t\t\tFailedProviderDetail: make(map[string]string),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"success with multiple senders\",\n\t\t\tmockSenders: []interface{}{\n\t\t\t\tnil,\n\t\t\t\t\"foo\",\n\t\t\t\t&mockNMISender{err: errors.New(\"err from sender\")},\n\t\t\t\t&mockNMISender{},\n\t\t\t},\n\t\t\texpectedMetadata: Metadata{\n\t\t\t\tSuccessfulProvider:   \"mock\",\n\t\t\t\tProvidersAttempted:   []string{\"mock\", \"mock\"},\n\t\t\t\tFailedProviderDetail: map[string]string{\"mock\": \"err from sender\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:        \"not an nmisender\",\n\t\t\tmockSenders: []interface{}{nil},\n\t\t\terrMsg:      \"not an NMISender\",\n\t\t\texpectedMetadata: Metadata{\n\t\t\t\tFailedProviderDetail: make(map[string]string),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:        \"no nmisenders\",\n\t\t\tmockSenders: []interface{}{},\n\t\t\terrMsg:      \"no NMISender implementations found\",\n\t\t\texpectedMetadata: Metadata{\n\t\t\t\tFailedProviderDetail: make(map[string]string),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:        \"timed out\",\n\t\t\tmockSenders: []interface{}{&mockNMISender{}},\n\t\t\tisTimedout:  true,\n\t\t\terrMsg:      \"context deadline exceeded\",\n\t\t\texpectedMetadata: Metadata{\n\t\t\t\tProvidersAttempted:   []string{\"mock\"},\n\t\t\t\tFailedProviderDetail: map[string]string{\"mock\": \"context deadline exceeded\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:        \"error from nmisender\",\n\t\t\tmockSenders: []interface{}{&mockNMISender{err: errors.New(\"foobar\")}},\n\t\t\terrMsg:      \"foobar\",\n\t\t\texpectedMetadata: Metadata{\n\t\t\t\tProvidersAttempted:   []string{\"mock\"},\n\t\t\t\tFailedProviderDetail: map[string]string{\"mock\": \"foobar\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:        \"error when fail to send\",\n\t\t\tmockSenders: []interface{}{&mockNMISender{err: errors.New(\"err from sender\")}},\n\t\t\terrMsg:      \"failed to send NMI\",\n\t\t\texpectedMetadata: Metadata{\n\t\t\t\tProvidersAttempted:   []string{\"mock\"},\n\t\t\t\tFailedProviderDetail: map[string]string{\"mock\": \"err from sender\"},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range testCases {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ttimeout := time.Second * 60\n\t\t\tif tt.isTimedout {\n\t\t\t\ttimeout = 0\n\t\t\t}\n\n\t\t\tmetadata, err := SendNMIFromInterface(context.Background(), timeout, tt.mockSenders)\n\n\t\t\tif tt.errMsg == \"\" {\n\t\t\t\tassert.NoError(t, err)\n\t\t\t} else {\n\t\t\t\tassert.ErrorContains(t, err, tt.errMsg)\n\t\t\t}\n\n\t\t\tassert.Equal(t, tt.expectedMetadata, metadata)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "bmc/postcode.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/hashicorp/go-multierror\"\n\t\"github.com/pkg/errors\"\n)\n\n// PostCodeGetter defines methods to retrieve device BIOS/UEFI POST code\ntype PostCodeGetter interface {\n\t// PostCode retrieves the BIOS/UEFI POST code from a device\n\t//\n\t// returns 'status' which is a (bmclib specific) string identifier for the POST code\n\t// and 'code' with the actual POST code returned to bmclib by the device\n\tPostCode(ctx context.Context) (status string, code int, err error)\n}\n\ntype postCodeGetterProvider struct {\n\tname string\n\tPostCodeGetter\n}\n\n// postCode returns the device BIOS/UEFI POST code\nfunc postCode(ctx context.Context, generic []postCodeGetterProvider) (status string, code int, metadata Metadata, err error) {\n\tvar metadataLocal Metadata\n\n\tfor _, elem := range generic {\n\t\tif elem.PostCodeGetter == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn status, code, metadata, err\n\t\tdefault:\n\t\t\tmetadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)\n\t\t\tstatus, code, vErr := elem.PostCode(ctx)\n\t\t\tif vErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(vErr, \"provider: %v\", elem.name))\n\t\t\t\terr = multierror.Append(err, vErr)\n\t\t\t\tcontinue\n\n\t\t\t}\n\t\t\tmetadataLocal.SuccessfulProvider = elem.name\n\t\t\treturn status, code, metadataLocal, nil\n\t\t}\n\t}\n\n\treturn status, code, metadataLocal, multierror.Append(err, errors.New(\"failure to get device POST code\"))\n}\n\n// GetPostCodeFromInterfaces identifies implementations of the PostCodeGetter interface and passes the found implementations to the postCode() wrapper method.\nfunc GetPostCodeInterfaces(ctx context.Context, generic []interface{}) (status string, code int, metadata Metadata, err error) {\n\timplementations := make([]postCodeGetterProvider, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := postCodeGetterProvider{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase PostCodeGetter:\n\t\t\ttemp.PostCodeGetter = p\n\t\t\timplementations = append(implementations, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a PostCodeGetter implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(implementations) == 0 {\n\t\treturn status, code, metadata, multierror.Append(\n\t\t\terr,\n\t\t\terrors.Wrap(\n\t\t\t\tbmclibErrs.ErrProviderImplementation,\n\t\t\t\t(\"no PostCodeGetter implementations found\"),\n\t\t\t),\n\t\t)\n\t}\n\n\treturn postCode(ctx, implementations)\n}\n"
  },
  {
    "path": "bmc/postcode_test.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/constants\"\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype postCodeGetterTester struct {\n\treturnStatus string\n\treturnCode   int\n\treturnError  error\n}\n\nfunc (p *postCodeGetterTester) PostCode(ctx context.Context) (status string, code int, err error) {\n\treturn p.returnStatus, p.returnCode, p.returnError\n}\n\nfunc (p *postCodeGetterTester) Name() string {\n\treturn \"foo\"\n}\n\nfunc TestPostCode(t *testing.T) {\n\ttestCases := []struct {\n\t\ttestName           string\n\t\treturnStatus       string\n\t\treturnCode         int\n\t\treturnError        error\n\t\tctxTimeout         time.Duration\n\t\tproviderName       string\n\t\tprovidersAttempted int\n\t}{\n\t\t{\"success with metadata\", constants.POSTStateOS, 164, nil, 5 * time.Second, \"foo\", 1},\n\t\t{\"failure with metadata\", constants.POSTCodeUnknown, 0, bmclibErrs.ErrNon200Response, 5 * time.Second, \"foo\", 1},\n\t\t{\"failure with context timeout\", \"\", 0, context.DeadlineExceeded, 1 * time.Nanosecond, \"foo\", 1},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\ttestImplementation := postCodeGetterTester{returnStatus: tc.returnStatus, returnCode: tc.returnCode, returnError: tc.returnError}\n\t\t\tif tc.ctxTimeout == 0 {\n\t\t\t\ttc.ctxTimeout = time.Second * 3\n\t\t\t}\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)\n\t\t\tdefer cancel()\n\t\t\tstatus, code, metadata, err := postCode(ctx, []postCodeGetterProvider{{tc.providerName, &testImplementation}})\n\t\t\tif tc.returnError != nil {\n\t\t\t\tassert.ErrorIs(t, err, tc.returnError)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tassert.Equal(t, tc.returnStatus, status)\n\t\t\tassert.Equal(t, tc.returnCode, code)\n\t\t\tassert.Equal(t, tc.providerName, metadata.SuccessfulProvider)\n\t\t\tassert.Equal(t, tc.providersAttempted, len(metadata.ProvidersAttempted))\n\t\t})\n\t}\n}\n\nfunc TestPostCodeFromInterfaces(t *testing.T) {\n\ttestCases := []struct {\n\t\ttestName           string\n\t\treturnStatus       string\n\t\treturnCode         int\n\t\treturnError        error\n\t\tctxTimeout         time.Duration\n\t\tproviderName       string\n\t\tprovidersAttempted int\n\t\tbadImplementation  bool\n\t}{\n\t\t{\"success with metadata\", constants.POSTStateOS, 164, nil, 5 * time.Second, \"foo\", 1, false},\n\t\t{\"failure with bad implementation\", \"\", 0, bmclibErrs.ErrProviderImplementation, 1 * time.Nanosecond, \"foo\", 1, true},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\tvar generic []interface{}\n\t\t\tif tc.badImplementation {\n\t\t\t\tbadImplementation := struct{}{}\n\t\t\t\tgeneric = []interface{}{&badImplementation}\n\t\t\t} else {\n\t\t\t\ttestImplementation := &postCodeGetterTester{returnStatus: tc.returnStatus, returnCode: tc.returnCode, returnError: tc.returnError}\n\t\t\t\tgeneric = []interface{}{testImplementation}\n\t\t\t}\n\t\t\tstatus, code, metadata, err := GetPostCodeInterfaces(context.Background(), generic)\n\t\t\tif tc.returnError != nil {\n\t\t\t\tassert.ErrorIs(t, err, tc.returnError)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tassert.Equal(t, tc.returnStatus, status)\n\t\t\tassert.Equal(t, tc.returnCode, code)\n\t\t\tassert.Equal(t, tc.providerName, metadata.SuccessfulProvider)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "bmc/power.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/hashicorp/go-multierror\"\n\t\"github.com/pkg/errors\"\n)\n\n// PowerSetter sets the power state of a BMC\ntype PowerSetter interface {\n\t// PowerSet sets the power state of a Machine through a BMC.\n\t// While the state's accepted are ultimately up to what the implementation\n\t// expects, implementations should generally try to provide support for the following\n\t// states, modeled after the functionality available in `ipmitool chassis power`.\n\t//\n\t// \"on\": Power up chassis. should not error if the machine is already on\n\t// \"off\": Hard powers down chassis. should not error if the machine is already off\n\t// \"soft\": Initiate a soft-shutdown of OS via ACPI.\n\t// \"reset\": soft down and then power on. simulates a reboot from the host OS.\n\t// \"cycle\": hard power down followed by a power on. simulates pressing a power button\n\t// to turn the machine off then pressing the button again to turn it on.\n\tPowerSet(ctx context.Context, state string) (ok bool, err error)\n}\n\n// PowerStateGetter gets the power state of a BMC\ntype PowerStateGetter interface {\n\tPowerStateGet(ctx context.Context) (state string, err error)\n}\n\n// powerProviders is an internal struct to correlate an implementation/provider and its name\ntype powerProviders struct {\n\tname             string\n\tpowerStateGetter PowerStateGetter\n\tpowerSetter      PowerSetter\n}\n\n// setPowerState sets the power state for a BMC, trying all interface implementations passed in\nfunc setPowerState(ctx context.Context, timeout time.Duration, state string, p []powerProviders) (ok bool, m Metadata, err error) {\n\tmetadataLocal := Metadata{\n\t\tFailedProviderDetail: make(map[string]string),\n\t}\n\n\tfor _, elem := range p {\n\t\tif elem.powerSetter == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn false, metadataLocal, err\n\t\tdefault:\n\t\t\tmetadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)\n\t\t\tctx, cancel := context.WithTimeout(ctx, timeout)\n\t\t\tdefer cancel()\n\t\t\tok, setErr := elem.powerSetter.PowerSet(ctx, state)\n\t\t\tif setErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(setErr, \"provider: %v\", elem.name))\n\t\t\t\tmetadataLocal.FailedProviderDetail[elem.name] = setErr.Error()\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif !ok {\n\t\t\t\terr = multierror.Append(err, fmt.Errorf(\"provider: %v, failed to set power state\", elem.name))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tmetadataLocal.SuccessfulProvider = elem.name\n\t\t\treturn ok, metadataLocal, nil\n\t\t}\n\t}\n\treturn ok, metadataLocal, multierror.Append(err, errors.New(\"failed to set power state\"))\n}\n\n// SetPowerStateFromInterfaces identifies implementations of the PostStateSetter interface and passes the found implementations to the setPowerState() wrapper.\nfunc SetPowerStateFromInterfaces(ctx context.Context, timeout time.Duration, state string, generic []interface{}) (ok bool, metadata Metadata, err error) {\n\tmetadata = newMetadata()\n\n\tpowerSetter := make([]powerProviders, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := powerProviders{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase PowerSetter:\n\t\t\ttemp.powerSetter = p\n\t\t\tpowerSetter = append(powerSetter, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a PowerSetter implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(powerSetter) == 0 {\n\t\treturn ok, metadata, multierror.Append(err, errors.New(\"no PowerSetter implementations found\"))\n\t}\n\treturn setPowerState(ctx, timeout, state, powerSetter)\n}\n\n// getPowerState gets the power state for a BMC, trying all interface implementations passed in\nfunc getPowerState(ctx context.Context, timeout time.Duration, p []powerProviders) (state string, m Metadata, err error) {\n\tmetadataLocal := Metadata{\n\t\tFailedProviderDetail: make(map[string]string),\n\t}\n\tfor _, elem := range p {\n\t\tif elem.powerStateGetter == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn state, metadataLocal, err\n\t\tdefault:\n\t\t\tmetadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)\n\t\t\tctx, cancel := context.WithTimeout(ctx, timeout)\n\t\t\tdefer cancel()\n\t\t\tstate, stateErr := elem.powerStateGetter.PowerStateGet(ctx)\n\t\t\tif stateErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(stateErr, \"provider: %v\", elem.name))\n\t\t\t\tmetadataLocal.FailedProviderDetail[elem.name] = stateErr.Error()\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tmetadataLocal.SuccessfulProvider = elem.name\n\t\t\treturn state, metadataLocal, nil\n\t\t}\n\t}\n\treturn state, metadataLocal, multierror.Append(err, errors.New(\"failed to get power state\"))\n}\n\n// GetPowerStateFromInterfaces identifies implementations of the PostStateGetter interface and passes the found implementations to the getPowerState() wrapper.\nfunc GetPowerStateFromInterfaces(ctx context.Context, timeout time.Duration, generic []interface{}) (state string, metadata Metadata, err error) {\n\tmetadata = newMetadata()\n\n\tpowerStateGetter := make([]powerProviders, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := powerProviders{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase PowerStateGetter:\n\t\t\ttemp.powerStateGetter = p\n\t\t\tpowerStateGetter = append(powerStateGetter, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a PowerStateGetter implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(powerStateGetter) == 0 {\n\t\treturn state, metadata, multierror.Append(err, errors.New(\"no PowerStateGetter implementations found\"))\n\t}\n\treturn getPowerState(ctx, timeout, powerStateGetter)\n}\n"
  },
  {
    "path": "bmc/power_test.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/hashicorp/go-multierror\"\n)\n\ntype powerTester struct {\n\tMakeNotOK    bool\n\tMakeErrorOut bool\n}\n\nfunc (p *powerTester) PowerSet(ctx context.Context, state string) (ok bool, err error) {\n\tif p.MakeErrorOut {\n\t\treturn ok, errors.New(\"power set failed\")\n\t}\n\tif p.MakeNotOK {\n\t\treturn false, nil\n\t}\n\treturn true, nil\n}\n\nfunc (p *powerTester) PowerStateGet(ctx context.Context) (state string, err error) {\n\tif p.MakeErrorOut {\n\t\treturn state, errors.New(\"power state get failed\")\n\t}\n\treturn \"on\", nil\n}\n\nfunc (p *powerTester) Name() string {\n\treturn \"test provider\"\n}\n\nfunc TestSetPowerState(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\tstate        string\n\t\tmakeErrorOut bool\n\t\tmakeNotOk    bool\n\t\twant         bool\n\t\terr          error\n\t\tctxTimeout   time.Duration\n\t}{\n\t\t\"success\":               {state: \"off\", want: true},\n\t\t\"not ok return\":         {state: \"off\", want: false, makeNotOk: true, err: &multierror.Error{Errors: []error{errors.New(\"provider: test provider, failed to set power state\"), errors.New(\"failed to set power state\")}}},\n\t\t\"error\":                 {state: \"off\", want: false, makeErrorOut: true, err: &multierror.Error{Errors: []error{errors.New(\"provider: test provider: power set failed\"), errors.New(\"failed to set power state\")}}},\n\t\t\"error context timeout\": {state: \"off\", want: false, makeErrorOut: true, err: &multierror.Error{Errors: []error{errors.New(\"context deadline exceeded\")}}, ctxTimeout: time.Nanosecond * 1},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\ttestImplementation := powerTester{MakeErrorOut: tc.makeErrorOut, MakeNotOK: tc.makeNotOk}\n\t\t\texpectedResult := tc.want\n\t\t\tif tc.ctxTimeout == 0 {\n\t\t\t\ttc.ctxTimeout = time.Second * 3\n\t\t\t}\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)\n\t\t\tdefer cancel()\n\t\t\tresult, _, err := setPowerState(ctx, 0, tc.state, []powerProviders{{\"test provider\", nil, &testImplementation}})\n\t\t\tif err != nil {\n\t\t\t\tdiff := cmp.Diff(err.Error(), tc.err.Error())\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdiff := cmp.Diff(result, expectedResult)\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSetPowerStateFromInterfaces(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\tstate             string\n\t\terr               error\n\t\tbadImplementation bool\n\t\twant              bool\n\t\twithMetadata      bool\n\t}{\n\t\t\"success\":                  {state: \"off\", want: true},\n\t\t\"success with metadata\":    {state: \"on\", want: true, withMetadata: true},\n\t\t\"no implementations found\": {state: \"on\", want: false, badImplementation: true, err: &multierror.Error{Errors: []error{errors.New(\"not a PowerSetter implementation: *struct {}\"), errors.New(\"no PowerSetter implementations found\")}}},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tvar generic []interface{}\n\t\t\tif tc.badImplementation {\n\t\t\t\tbadImplementation := struct{}{}\n\t\t\t\tgeneric = []interface{}{&badImplementation}\n\t\t\t} else {\n\t\t\t\ttestImplementation := powerTester{}\n\t\t\t\tgeneric = []interface{}{&testImplementation}\n\t\t\t}\n\t\t\texpectedResult := tc.want\n\t\t\tresult, metadata, err := SetPowerStateFromInterfaces(context.Background(), 0, tc.state, generic)\n\t\t\tif err != nil {\n\t\t\t\tif tc.err != nil {\n\t\t\t\t\tdiff := cmp.Diff(err.Error(), tc.err.Error())\n\t\t\t\t\tif diff != \"\" {\n\t\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdiff := cmp.Diff(result, expectedResult)\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif tc.withMetadata {\n\t\t\t\tif diff := cmp.Diff(metadata.SuccessfulProvider, \"test provider\"); diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetPowerState(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\tstate      string\n\t\tmakeFail   bool\n\t\terr        error\n\t\tctxTimeout time.Duration\n\t}{\n\t\t\"success\":              {state: \"on\", err: nil},\n\t\t\"failure\":              {state: \"on\", makeFail: true, err: &multierror.Error{Errors: []error{errors.New(\"provider: test provider: power state get failed\"), errors.New(\"failed to get power state\")}}},\n\t\t\"fail context timeout\": {state: \"on\", makeFail: true, err: &multierror.Error{Errors: []error{errors.New(\"context deadline exceeded\")}}, ctxTimeout: time.Nanosecond * 1},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\ttestImplementation := powerTester{MakeErrorOut: tc.makeFail}\n\t\t\texpectedResult := tc.state\n\t\t\tif tc.ctxTimeout == 0 {\n\t\t\t\ttc.ctxTimeout = time.Second * 3\n\t\t\t}\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)\n\t\t\tdefer cancel()\n\t\t\tresult, _, err := getPowerState(ctx, 0, []powerProviders{{\"test provider\", &testImplementation, nil}})\n\t\t\tif err != nil {\n\t\t\t\tdiff := cmp.Diff(err.Error(), tc.err.Error())\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdiff := cmp.Diff(result, expectedResult)\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetPowerStateFromInterfaces(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\tstate             string\n\t\terr               error\n\t\tbadImplementation bool\n\t\twant              string\n\t\twithMetadata      bool\n\t}{\n\t\t\"success\":                  {state: \"on\", want: \"on\"},\n\t\t\"success with metadata\":    {state: \"on\", want: \"on\", withMetadata: true},\n\t\t\"no implementations found\": {state: \"on\", want: \"\", badImplementation: true, err: &multierror.Error{Errors: []error{errors.New(\"not a PowerStateGetter implementation: *struct {}\"), errors.New(\"no PowerStateGetter implementations found\")}}},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tvar generic []interface{}\n\t\t\tif tc.badImplementation {\n\t\t\t\tbadImplementation := struct{}{}\n\t\t\t\tgeneric = []interface{}{&badImplementation}\n\t\t\t} else {\n\t\t\t\ttestImplementation := powerTester{}\n\t\t\t\tgeneric = []interface{}{&testImplementation}\n\t\t\t}\n\t\t\texpectedResult := tc.want\n\t\t\tresult, metadata, err := GetPowerStateFromInterfaces(context.Background(), 0, generic)\n\t\t\tif err != nil {\n\t\t\t\tif tc.err != nil {\n\t\t\t\t\tdiff := cmp.Diff(err.Error(), tc.err.Error())\n\t\t\t\t\tif diff != \"\" {\n\t\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdiff := cmp.Diff(result, expectedResult)\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif tc.withMetadata {\n\t\t\t\tif diff := cmp.Diff(metadata.SuccessfulProvider, \"test provider\"); diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "bmc/provider.go",
    "content": "package bmc\n\nimport \"fmt\"\n\n// Provider interface describes details about a provider\ntype Provider interface {\n\t// Name of the provider\n\tName() string\n}\n\n// getProviderName returns the name a provider supplies if they implement the Provider interface\n// if not implemented then the concrete type is returned\nfunc getProviderName(provider interface{}) string {\n\tif provider == nil {\n\t\treturn \"\"\n\t}\n\tswitch p := provider.(type) {\n\tcase Provider:\n\t\treturn p.Name()\n\t}\n\treturn fmt.Sprintf(\"%T\", provider)\n}\n"
  },
  {
    "path": "bmc/reset.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/hashicorp/go-multierror\"\n\t\"github.com/pkg/errors\"\n)\n\n// BMCResetter for resetting a BMC.\n// resetType: \"warm\" resets the management console without rebooting the BMC\n// resetType: \"cold\" reboots the BMC\ntype BMCResetter interface {\n\tBmcReset(ctx context.Context, resetType string) (ok bool, err error)\n}\n\n// bmcProviders is an internal struct to correlate an implementation/provider and its name\ntype bmcProviders struct {\n\tname        string\n\tbmcResetter BMCResetter\n}\n\n// resetBMC tries all implementations for a success BMC reset\nfunc resetBMC(ctx context.Context, timeout time.Duration, resetType string, b []bmcProviders) (ok bool, metadata Metadata, err error) {\n\tvar metadataLocal Metadata\n\n\tfor _, elem := range b {\n\t\tif elem.bmcResetter == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn false, metadata, err\n\t\tdefault:\n\t\t\tmetadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)\n\t\t\tctx, cancel := context.WithTimeout(ctx, timeout)\n\t\t\tdefer cancel()\n\t\t\tok, setErr := elem.bmcResetter.BmcReset(ctx, resetType)\n\t\t\tif setErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(setErr, \"provider: %v\", elem.name))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif !ok {\n\t\t\t\terr = multierror.Append(err, fmt.Errorf(\"provider: %v, failed to reset BMC\", elem.name))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tmetadataLocal.SuccessfulProvider = elem.name\n\t\t\treturn ok, metadataLocal, nil\n\t\t}\n\t}\n\treturn ok, metadataLocal, multierror.Append(err, errors.New(\"failed to reset BMC\"))\n}\n\n// ResetBMCFromInterfaces identifies implementations of the BMCResetter interface and passes them to the resetBMC() wrapper method.\nfunc ResetBMCFromInterfaces(ctx context.Context, timeout time.Duration, resetType string, generic []interface{}) (ok bool, metadata Metadata, err error) {\n\tmetadata = newMetadata()\n\n\tbmcSetters := make([]bmcProviders, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := bmcProviders{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase BMCResetter:\n\t\t\ttemp.bmcResetter = p\n\t\t\tbmcSetters = append(bmcSetters, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a BMCResetter implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(bmcSetters) == 0 {\n\t\treturn ok, metadata, multierror.Append(err, errors.New(\"no BMCResetter implementations found\"))\n\t}\n\treturn resetBMC(ctx, timeout, resetType, bmcSetters)\n}\n"
  },
  {
    "path": "bmc/reset_test.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/hashicorp/go-multierror\"\n)\n\ntype resetTester struct {\n\tMakeNotOK    bool\n\tMakeErrorOut bool\n}\n\nfunc (r *resetTester) BmcReset(ctx context.Context, resetType string) (ok bool, err error) {\n\tif r.MakeErrorOut {\n\t\treturn ok, errors.New(\"bmc reset failed\")\n\t}\n\tif r.MakeNotOK {\n\t\treturn false, nil\n\t}\n\treturn true, nil\n}\n\nfunc (r *resetTester) Name() string {\n\treturn \"test provider\"\n}\n\nfunc TestResetBMC(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\tresetType    string\n\t\tmakeErrorOut bool\n\t\tmakeNotOk    bool\n\t\twant         bool\n\t\terr          error\n\t\tctxTimeout   time.Duration\n\t}{\n\t\t\"success\":               {resetType: \"cold\", want: true},\n\t\t\"not ok return\":         {resetType: \"warm\", want: false, makeNotOk: true, err: &multierror.Error{Errors: []error{errors.New(\"provider: test provider, failed to reset BMC\"), errors.New(\"failed to reset BMC\")}}},\n\t\t\"error\":                 {resetType: \"cold\", want: false, makeErrorOut: true, err: &multierror.Error{Errors: []error{errors.New(\"provider: test provider: bmc reset failed\"), errors.New(\"failed to reset BMC\")}}},\n\t\t\"error context timeout\": {resetType: \"cold\", want: false, makeErrorOut: true, err: &multierror.Error{Errors: []error{errors.New(\"context deadline exceeded\")}}, ctxTimeout: time.Nanosecond * 1},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\ttestImplementation := resetTester{MakeErrorOut: tc.makeErrorOut, MakeNotOK: tc.makeNotOk}\n\t\t\texpectedResult := tc.want\n\t\t\tif tc.ctxTimeout == 0 {\n\t\t\t\ttc.ctxTimeout = time.Second * 3\n\t\t\t}\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)\n\t\t\tdefer cancel()\n\t\t\tresult, _, err := resetBMC(ctx, 0, tc.resetType, []bmcProviders{{\"test provider\", &testImplementation}})\n\t\t\tif err != nil {\n\t\t\t\tdiff := cmp.Diff(err.Error(), tc.err.Error())\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdiff := cmp.Diff(result, expectedResult)\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestResetBMCFromInterfaces(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\tresetType         string\n\t\terr               error\n\t\tbadImplementation bool\n\t\twant              bool\n\t\twithName          bool\n\t}{\n\t\t\"success\":                  {resetType: \"cold\", want: true},\n\t\t\"success with metadata\":    {resetType: \"cold\", want: true, withName: true},\n\t\t\"no implementations found\": {resetType: \"warm\", want: false, badImplementation: true, err: &multierror.Error{Errors: []error{errors.New(\"not a BMCResetter implementation: *struct {}\"), errors.New(\"no BMCResetter implementations found\")}}},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tvar generic []interface{}\n\t\t\tif tc.badImplementation {\n\t\t\t\tbadImplementation := struct{}{}\n\t\t\t\tgeneric = []interface{}{&badImplementation}\n\t\t\t} else {\n\t\t\t\ttestImplementation := resetTester{}\n\t\t\t\tgeneric = []interface{}{&testImplementation}\n\t\t\t}\n\t\t\texpectedResult := tc.want\n\t\t\tresult, metadata, err := ResetBMCFromInterfaces(context.Background(), 0, tc.resetType, generic)\n\t\t\tif err != nil {\n\t\t\t\tif tc.err != nil {\n\t\t\t\t\tdiff := cmp.Diff(err.Error(), tc.err.Error())\n\t\t\t\t\tif diff != \"\" {\n\t\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdiff := cmp.Diff(result, expectedResult)\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif tc.withName {\n\t\t\t\tif diff := cmp.Diff(metadata.SuccessfulProvider, \"test provider\"); diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "bmc/screenshot.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/hashicorp/go-multierror\"\n\t\"github.com/pkg/errors\"\n)\n\n// ScreenshotGetter interface provides methods to query for a BMC screen capture.\ntype ScreenshotGetter interface {\n\tScreenshot(ctx context.Context) (image []byte, fileType string, err error)\n}\n\ntype screenshotGetterProvider struct {\n\tname string\n\tScreenshotGetter\n}\n\n// screenshot returns an image capture of the video output.\nfunc screenshot(ctx context.Context, generic []screenshotGetterProvider) (image []byte, fileType string, metadata Metadata, err error) {\n\tvar metadataLocal Metadata\n\n\tfor _, elem := range generic {\n\t\tif elem.ScreenshotGetter == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn image, fileType, metadata, err\n\t\tdefault:\n\t\t\tmetadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)\n\t\t\timage, fileType, vErr := elem.Screenshot(ctx)\n\t\t\tif vErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(vErr, \"provider: %v\", elem.name))\n\t\t\t\tcontinue\n\n\t\t\t}\n\t\t\tmetadataLocal.SuccessfulProvider = elem.name\n\t\t\treturn image, fileType, metadataLocal, nil\n\t\t}\n\t}\n\n\treturn image, fileType, metadataLocal, multierror.Append(err, errors.New(\"failed to capture screenshot\"))\n}\n\n// ScreenshotFromInterfaces identifies implementations of the ScreenshotGetter interface and passes the found implementations to the screenshot() wrapper method.\nfunc ScreenshotFromInterfaces(ctx context.Context, generic []interface{}) (image []byte, fileType string, metadata Metadata, err error) {\n\timplementations := make([]screenshotGetterProvider, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := screenshotGetterProvider{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase ScreenshotGetter:\n\t\t\ttemp.ScreenshotGetter = p\n\t\t\timplementations = append(implementations, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a ScreenshotGetter implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(implementations) == 0 {\n\t\treturn image, fileType, metadata, multierror.Append(\n\t\t\terr,\n\t\t\terrors.Wrap(\n\t\t\t\tbmclibErrs.ErrProviderImplementation,\n\t\t\t\t(\"no ScreenshotGetter implementations found\"),\n\t\t\t),\n\t\t)\n\t}\n\n\treturn screenshot(ctx, implementations)\n}\n"
  },
  {
    "path": "bmc/screenshot_test.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/hashicorp/go-multierror\"\n\t\"gopkg.in/go-playground/assert.v1\"\n)\n\ntype screenshotTester struct {\n\tMakeErrorOut bool\n}\n\nfunc (r *screenshotTester) Screenshot(ctx context.Context) (img []byte, fileType string, err error) {\n\tif r.MakeErrorOut {\n\t\treturn nil, \"\", errors.New(\"crappy bmc is crappy\")\n\t}\n\n\treturn []byte(`foobar`), \"png\", nil\n}\n\nfunc (r *screenshotTester) Name() string {\n\treturn \"test screenshot provider\"\n}\n\nfunc TestScreenshot(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\tmakeErrorOut           bool\n\t\twantImage              []byte\n\t\twantFileType           string\n\t\twantSuccessfulProvider string\n\t\twantProvidersAttempted []string\n\t\twantErr                error\n\t\tctxTimeout             time.Duration\n\t}{\n\t\t\"success\":               {false, []byte(`foobar`), \"png\", \"test provider\", []string{\"test provider\"}, nil, 1 * time.Second},\n\t\t\"error\":                 {true, nil, \"\", \"\", []string{\"test provider\"}, &multierror.Error{Errors: []error{errors.New(\"provider: test provider: crappy bmc is crappy\"), errors.New(\"failed to capture screenshot\")}}, 1 * time.Second},\n\t\t\"error context timeout\": {true, nil, \"\", \"\", nil, &multierror.Error{Errors: []error{errors.New(\"context deadline exceeded\")}}, 1 * time.Nanosecond},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\ttestImplementation := screenshotTester{MakeErrorOut: tc.makeErrorOut}\n\t\t\tif tc.ctxTimeout == 0 {\n\t\t\t\ttc.ctxTimeout = time.Second * 3\n\t\t\t}\n\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)\n\t\t\tdefer cancel()\n\t\t\timage, fileType, metadata, err := screenshot(ctx, []screenshotGetterProvider{{\"test provider\", &testImplementation}})\n\t\t\tif err != nil {\n\t\t\t\tif tc.wantErr == nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tassert.Equal(t, tc.wantErr.Error(), err.Error())\n\t\t\t} else {\n\t\t\t\tassert.Equal(t, tc.wantImage, image)\n\t\t\t\tassert.Equal(t, tc.wantFileType, fileType)\n\t\t\t}\n\n\t\t\tassert.Equal(t, tc.wantProvidersAttempted, metadata.ProvidersAttempted)\n\t\t\tassert.Equal(t, tc.wantSuccessfulProvider, metadata.SuccessfulProvider)\n\t\t})\n\t}\n}\n\nfunc TestScreenshotFromInterfaces(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\twantImage              []byte\n\t\twantFileType           string\n\t\twantSuccessfulProvider string\n\t\twantProvidersAttempted []string\n\t\twantErr                error\n\t\tbadImplementation      bool\n\t}{\n\t\t\"success with metadata\":    {[]byte(`foobar`), \"png\", \"test screenshot provider\", []string{\"test screenshot provider\"}, nil, false},\n\t\t\"no implementations found\": {nil, \"\", \"\", nil, &multierror.Error{Errors: []error{errors.New(\"not a ScreenshotGetter implementation: *struct {}\"), errors.New(\"no ScreenshotGetter implementations found: error in provider implementation\")}}, true},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tvar generic []interface{}\n\t\t\tif tc.badImplementation {\n\t\t\t\tbadImplementation := struct{}{}\n\t\t\t\tgeneric = []interface{}{&badImplementation}\n\t\t\t} else {\n\t\t\t\ttestImplementation := screenshotTester{}\n\t\t\t\tgeneric = []interface{}{&testImplementation}\n\t\t\t}\n\t\t\timage, fileType, metadata, err := ScreenshotFromInterfaces(context.Background(), generic)\n\t\t\tif err != nil {\n\t\t\t\tif tc.wantErr == nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tassert.Equal(t, tc.wantErr.Error(), err.Error())\n\t\t\t} else {\n\t\t\t\tassert.Equal(t, tc.wantImage, image)\n\t\t\t\tassert.Equal(t, tc.wantFileType, fileType)\n\t\t\t}\n\n\t\t\tassert.Equal(t, tc.wantProvidersAttempted, metadata.ProvidersAttempted)\n\t\t\tassert.Equal(t, tc.wantSuccessfulProvider, metadata.SuccessfulProvider)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "bmc/sel.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/hashicorp/go-multierror\"\n\t\"github.com/pkg/errors\"\n)\n\n// System Event Log Services for related services\ntype SystemEventLog interface {\n\tClearSystemEventLog(ctx context.Context) (err error)\n\tGetSystemEventLog(ctx context.Context) (entries [][]string, err error)\n\tGetSystemEventLogRaw(ctx context.Context) (eventlog string, err error)\n}\n\ntype systemEventLogProviders struct {\n\tname                   string\n\tsystemEventLogProvider SystemEventLog\n}\n\ntype SystemEventLogEntries [][]string\n\nfunc clearSystemEventLog(ctx context.Context, timeout time.Duration, s []systemEventLogProviders) (metadata Metadata, err error) {\n\tvar metadataLocal Metadata\n\n\tfor _, elem := range s {\n\t\tif elem.systemEventLogProvider == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn metadata, err\n\t\tdefault:\n\t\t\tmetadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)\n\t\t\tctx, cancel := context.WithTimeout(ctx, timeout)\n\t\t\tdefer cancel()\n\t\t\tselErr := elem.systemEventLogProvider.ClearSystemEventLog(ctx)\n\t\t\tif selErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(selErr, \"provider: %v\", elem.name))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tmetadataLocal.SuccessfulProvider = elem.name\n\t\t\treturn metadataLocal, nil\n\t\t}\n\n\t}\n\n\treturn metadataLocal, multierror.Append(err, errors.New(\"failed to reset System Event Log\"))\n}\n\nfunc ClearSystemEventLogFromInterfaces(ctx context.Context, timeout time.Duration, generic []interface{}) (metadata Metadata, err error) {\n\tselServices := make([]systemEventLogProviders, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := systemEventLogProviders{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase SystemEventLog:\n\t\t\ttemp.systemEventLogProvider = p\n\t\t\tselServices = append(selServices, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a SystemEventLog service implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(selServices) == 0 {\n\t\treturn metadata, multierror.Append(err, errors.New(\"no SystemEventLog implementations found\"))\n\t}\n\treturn clearSystemEventLog(ctx, timeout, selServices)\n}\n\nfunc getSystemEventLog(ctx context.Context, timeout time.Duration, s []systemEventLogProviders) (sel SystemEventLogEntries, metadata Metadata, err error) {\n\tvar metadataLocal Metadata\n\n\tfor _, elem := range s {\n\t\tif elem.systemEventLogProvider == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn sel, metadata, err\n\t\tdefault:\n\t\t\tmetadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)\n\t\t\tctx, cancel := context.WithTimeout(ctx, timeout)\n\t\t\tdefer cancel()\n\n\t\t\tsel, selErr := elem.systemEventLogProvider.GetSystemEventLog(ctx)\n\t\t\tif selErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(selErr, \"provider: %v\", elem.name))\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tmetadataLocal.SuccessfulProvider = elem.name\n\t\t\treturn sel, metadataLocal, nil\n\t\t}\n\n\t}\n\n\treturn nil, metadataLocal, multierror.Append(err, errors.New(\"failed to get System Event Log\"))\n}\n\nfunc GetSystemEventLogFromInterfaces(ctx context.Context, timeout time.Duration, generic []interface{}) (sel SystemEventLogEntries, metadata Metadata, err error) {\n\tselServices := make([]systemEventLogProviders, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := systemEventLogProviders{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase SystemEventLog:\n\t\t\ttemp.systemEventLogProvider = p\n\t\t\tselServices = append(selServices, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a SystemEventLog service implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(selServices) == 0 {\n\t\treturn sel, metadata, multierror.Append(err, errors.New(\"no SystemEventLog implementations found\"))\n\t}\n\treturn getSystemEventLog(ctx, timeout, selServices)\n}\n\nfunc getSystemEventLogRaw(ctx context.Context, timeout time.Duration, s []systemEventLogProviders) (eventlog string, metadata Metadata, err error) {\n\tvar metadataLocal Metadata\n\n\tfor _, elem := range s {\n\t\tif elem.systemEventLogProvider == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn eventlog, metadata, err\n\t\tdefault:\n\t\t\tmetadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)\n\t\t\tctx, cancel := context.WithTimeout(ctx, timeout)\n\t\t\tdefer cancel()\n\n\t\t\teventlog, selErr := elem.systemEventLogProvider.GetSystemEventLogRaw(ctx)\n\t\t\tif selErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(selErr, \"provider: %v\", elem.name))\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tmetadataLocal.SuccessfulProvider = elem.name\n\t\t\treturn eventlog, metadataLocal, nil\n\t\t}\n\n\t}\n\n\treturn eventlog, metadataLocal, multierror.Append(err, errors.New(\"failed to get System Event Log\"))\n}\n\nfunc GetSystemEventLogRawFromInterfaces(ctx context.Context, timeout time.Duration, generic []interface{}) (eventlog string, metadata Metadata, err error) {\n\tselServices := make([]systemEventLogProviders, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := systemEventLogProviders{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase SystemEventLog:\n\t\t\ttemp.systemEventLogProvider = p\n\t\t\tselServices = append(selServices, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a SystemEventLog service implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(selServices) == 0 {\n\t\treturn eventlog, metadata, multierror.Append(err, errors.New(\"no SystemEventLog implementations found\"))\n\t}\n\treturn getSystemEventLogRaw(ctx, timeout, selServices)\n}\n"
  },
  {
    "path": "bmc/sel_test.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\ntype mockSystemEventLogService struct {\n\tname string\n\terr  error\n}\n\nfunc (m *mockSystemEventLogService) ClearSystemEventLog(ctx context.Context) error {\n\treturn m.err\n}\n\nfunc (m *mockSystemEventLogService) GetSystemEventLog(ctx context.Context) (entries [][]string, err error) {\n\treturn nil, m.err\n}\n\nfunc (m *mockSystemEventLogService) GetSystemEventLogRaw(ctx context.Context) (eventlog string, err error) {\n\treturn \"\", m.err\n}\n\nfunc (m *mockSystemEventLogService) Name() string {\n\treturn m.name\n}\n\nfunc TestClearSystemEventLog(t *testing.T) {\n\tctx := context.Background()\n\ttimeout := 1 * time.Second\n\n\t// Test with a mock SystemEventLogService that returns nil\n\tmockService := &mockSystemEventLogService{name: \"mock1\", err: nil}\n\tmetadata, err := clearSystemEventLog(ctx, timeout, []systemEventLogProviders{{name: mockService.name, systemEventLogProvider: mockService}})\n\tassert.Nil(t, err)\n\tassert.Equal(t, mockService.name, metadata.SuccessfulProvider)\n\n\t// Test with a mock SystemEventLogService that returns an error\n\tmockService = &mockSystemEventLogService{name: \"mock2\", err: errors.New(\"mock error\")}\n\tmetadata, err = clearSystemEventLog(ctx, timeout, []systemEventLogProviders{{name: mockService.name, systemEventLogProvider: mockService}})\n\tassert.NotNil(t, err)\n\tassert.NotEqual(t, mockService.name, metadata.SuccessfulProvider)\n}\n\nfunc TestClearSystemEventLogFromInterfaces(t *testing.T) {\n\tctx := context.Background()\n\ttimeout := 1 * time.Second\n\n\t// Test with an empty slice\n\tmetadata, err := ClearSystemEventLogFromInterfaces(ctx, timeout, []interface{}{})\n\tassert.NotNil(t, err)\n\tassert.Empty(t, metadata.SuccessfulProvider)\n\n\t// Test with a slice containing a non-SystemEventLog object\n\tmetadata, err = ClearSystemEventLogFromInterfaces(ctx, timeout, []interface{}{\"not a SystemEventLog Service\"})\n\tassert.NotNil(t, err)\n\tassert.Empty(t, metadata.SuccessfulProvider)\n\n\t// Test with a slice containing a mock SystemEventLogService that returns nil\n\tmockService := &mockSystemEventLogService{name: \"mock1\"}\n\tmetadata, err = ClearSystemEventLogFromInterfaces(ctx, timeout, []interface{}{mockService})\n\tassert.Nil(t, err)\n\tassert.Equal(t, mockService.name, metadata.SuccessfulProvider)\n}\n\nfunc TestGetSystemEventLog(t *testing.T) {\n\tctx := context.Background()\n\ttimeout := 1 * time.Second\n\n\t// Test with a mock SystemEventLogService that returns nil\n\tmockService := &mockSystemEventLogService{name: \"mock1\", err: nil}\n\t_, _, err := getSystemEventLog(ctx, timeout, []systemEventLogProviders{{name: mockService.name, systemEventLogProvider: mockService}})\n\tassert.Nil(t, err)\n\n\t// Test with a mock SystemEventLogService that returns an error\n\tmockService = &mockSystemEventLogService{name: \"mock2\", err: errors.New(\"mock error\")}\n\t_, _, err = getSystemEventLog(ctx, timeout, []systemEventLogProviders{{name: mockService.name, systemEventLogProvider: mockService}})\n\tassert.NotNil(t, err)\n}\n\nfunc TestGetSystemEventLogFromInterfaces(t *testing.T) {\n\tctx := context.Background()\n\ttimeout := 1 * time.Second\n\n\t// Test with an empty slice\n\t_, _, err := GetSystemEventLogFromInterfaces(ctx, timeout, []interface{}{})\n\tassert.NotNil(t, err)\n\n\t// Test with a slice containing a non-SystemEventLog object\n\t_, _, err = GetSystemEventLogFromInterfaces(ctx, timeout, []interface{}{\"not a SystemEventLog Service\"})\n\tassert.NotNil(t, err)\n\n\t// Test with a slice containing a mock SystemEventLogService that returns nil\n\tmockService := &mockSystemEventLogService{name: \"mock1\"}\n\t_, _, err = GetSystemEventLogFromInterfaces(ctx, timeout, []interface{}{mockService})\n\tassert.Nil(t, err)\n}\n\nfunc TestGetSystemEventLogRaw(t *testing.T) {\n\tctx := context.Background()\n\ttimeout := 1 * time.Second\n\n\t// Test with a mock SystemEventLogService that returns nil\n\tmockService := &mockSystemEventLogService{name: \"mock1\", err: nil}\n\t_, _, err := getSystemEventLogRaw(ctx, timeout, []systemEventLogProviders{{name: mockService.name, systemEventLogProvider: mockService}})\n\tassert.Nil(t, err)\n\n\t// Test with a mock SystemEventLogService that returns an error\n\tmockService = &mockSystemEventLogService{name: \"mock2\", err: errors.New(\"mock error\")}\n\t_, _, err = getSystemEventLogRaw(ctx, timeout, []systemEventLogProviders{{name: mockService.name, systemEventLogProvider: mockService}})\n\tassert.NotNil(t, err)\n}\n\nfunc TestGetSystemEventLogRawFromInterfaces(t *testing.T) {\n\tctx := context.Background()\n\ttimeout := 1 * time.Second\n\n\t// Test with an empty slice\n\t_, _, err := GetSystemEventLogRawFromInterfaces(ctx, timeout, []interface{}{})\n\tassert.NotNil(t, err)\n\n\t// Test with a slice containing a non-SystemEventLog object\n\t_, _, err = GetSystemEventLogRawFromInterfaces(ctx, timeout, []interface{}{\"not a SystemEventLog Service\"})\n\tassert.NotNil(t, err)\n\n\t// Test with a slice containing a mock SystemEventLogService that returns nil\n\tmockService := &mockSystemEventLogService{name: \"mock1\"}\n\t_, _, err = GetSystemEventLogRawFromInterfaces(ctx, timeout, []interface{}{mockService})\n\tassert.Nil(t, err)\n}\n"
  },
  {
    "path": "bmc/sol.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/hashicorp/go-multierror\"\n\t\"github.com/pkg/errors\"\n)\n\n// SOLDeactivator for deactivating SOL sessions on a BMC.\ntype SOLDeactivator interface {\n\tDeactivateSOL(ctx context.Context) (err error)\n}\n\n// deactivatorProvider is an internal struct to correlate an implementation/provider and its name\ntype deactivatorProvider struct {\n\tname           string\n\tsolDeactivator SOLDeactivator\n}\n\n// deactivateSOL tries all implementations for a successful SOL deactivation\nfunc deactivateSOL(ctx context.Context, timeout time.Duration, b []deactivatorProvider) (metadata Metadata, err error) {\n\tvar metadataLocal Metadata\n\n\tfor _, elem := range b {\n\t\tif elem.solDeactivator == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn metadata, err\n\t\tdefault:\n\t\t\tmetadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)\n\t\t\tctx, cancel := context.WithTimeout(ctx, timeout)\n\t\t\tdefer cancel()\n\t\t\tnewErr := elem.solDeactivator.DeactivateSOL(ctx)\n\t\t\tif newErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(newErr, \"provider: %v\", elem.name))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tmetadataLocal.SuccessfulProvider = elem.name\n\t\t\treturn metadataLocal, nil\n\t\t}\n\t}\n\treturn metadataLocal, multierror.Append(err, errors.New(\"failed to deactivate SOL session\"))\n}\n\n// DeactivateSOLFromInterfaces identifies implementations of the SOLDeactivator interface and passes them to the deactivateSOL() wrapper method.\nfunc DeactivateSOLFromInterfaces(ctx context.Context, timeout time.Duration, generic []interface{}) (metadata Metadata, err error) {\n\tdeactivators := make([]deactivatorProvider, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := deactivatorProvider{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase SOLDeactivator:\n\t\t\ttemp.solDeactivator = p\n\t\t\tdeactivators = append(deactivators, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not an SOLDeactivator implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(deactivators) == 0 {\n\t\treturn metadata, multierror.Append(err, errors.New(\"no SOLDeactivator implementations found\"))\n\t}\n\treturn deactivateSOL(ctx, timeout, deactivators)\n}\n"
  },
  {
    "path": "bmc/sol_test.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/hashicorp/go-multierror\"\n)\n\ntype solTermTester struct {\n\tMakeErrorOut bool\n}\n\nfunc (r *solTermTester) DeactivateSOL(ctx context.Context) (err error) {\n\tif r.MakeErrorOut {\n\t\treturn errors.New(\"SOL deactivation failed\")\n\t}\n\treturn nil\n}\n\nfunc (r *solTermTester) Name() string {\n\treturn \"test provider\"\n}\n\nfunc TestDeactivateSOL(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\tmakeErrorOut bool\n\t\terr          error\n\t\tctxTimeout   time.Duration\n\t}{\n\t\t\"success\":               {makeErrorOut: false},\n\t\t\"error\":                 {makeErrorOut: true, err: &multierror.Error{Errors: []error{errors.New(\"provider: test provider: SOL deactivation failed\"), errors.New(\"failed to deactivate SOL session\")}}},\n\t\t\"error context timeout\": {makeErrorOut: false, err: &multierror.Error{Errors: []error{errors.New(\"context deadline exceeded\")}}, ctxTimeout: time.Nanosecond * 1},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\ttestImplementation := solTermTester{MakeErrorOut: tc.makeErrorOut}\n\t\t\tif tc.ctxTimeout == 0 {\n\t\t\t\ttc.ctxTimeout = time.Second * 3\n\t\t\t}\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)\n\t\t\tdefer cancel()\n\t\t\t_, err := deactivateSOL(ctx, 0, []deactivatorProvider{{\"test provider\", &testImplementation}})\n\t\t\tvar diff string\n\t\t\tif err != nil && tc.err != nil {\n\t\t\t\tdiff = cmp.Diff(err.Error(), tc.err.Error())\n\t\t\t} else {\n\t\t\t\tdiff = cmp.Diff(err, tc.err)\n\t\t\t}\n\t\t\tif diff != \"\" {\n\t\t\t\tt.Fatal(diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDeactivateSOLFromInterfaces(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\terr               error\n\t\tbadImplementation bool\n\t\twithName          bool\n\t}{\n\t\t\"success\":                  {},\n\t\t\"success with metadata\":    {withName: true},\n\t\t\"no implementations found\": {badImplementation: true, err: &multierror.Error{Errors: []error{errors.New(\"not an SOLDeactivator implementation: *struct {}\"), errors.New(\"no SOLDeactivator implementations found\")}}},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tvar generic []interface{}\n\t\t\tif tc.badImplementation {\n\t\t\t\tbadImplementation := struct{}{}\n\t\t\t\tgeneric = []interface{}{&badImplementation}\n\t\t\t} else {\n\t\t\t\ttestImplementation := solTermTester{}\n\t\t\t\tgeneric = []interface{}{&testImplementation}\n\t\t\t}\n\t\t\tmetadata, err := DeactivateSOLFromInterfaces(context.Background(), 0, generic)\n\t\t\tvar diff string\n\t\t\tif err != nil && tc.err != nil {\n\t\t\t\tdiff = cmp.Diff(err.Error(), tc.err.Error())\n\t\t\t} else {\n\t\t\t\tdiff = cmp.Diff(err, tc.err)\n\t\t\t}\n\t\t\tif diff != \"\" {\n\t\t\t\tt.Fatal(diff)\n\t\t\t}\n\t\t\tif tc.withName {\n\t\t\t\tif diff := cmp.Diff(metadata.SuccessfulProvider, \"test provider\"); diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "bmc/user.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/hashicorp/go-multierror\"\n)\n\n// UserCreator creates a user on a BMC\ntype UserCreator interface {\n\tUserCreate(ctx context.Context, user, pass, role string) (ok bool, err error)\n}\n\n// UserUpdater updates a user on a BMC\ntype UserUpdater interface {\n\tUserUpdate(ctx context.Context, user, pass, role string) (ok bool, err error)\n}\n\n// UserDeleter deletes a user on a BMC\ntype UserDeleter interface {\n\tUserDelete(ctx context.Context, user string) (ok bool, err error)\n}\n\n// UserReader lists all users on a BMC\ntype UserReader interface {\n\tUserRead(ctx context.Context) (users []map[string]string, err error)\n}\n\n// userProviders is an internal struct used to correlate an implementation/provider with its name\ntype userProviders struct {\n\tname        string\n\tuserCreator UserCreator\n\tuserUpdater UserUpdater\n\tuserDeleter UserDeleter\n\tuserReader  UserReader\n}\n\n// createUser creates a user using the passed in implementation\nfunc createUser(ctx context.Context, timeout time.Duration, user, pass, role string, u []userProviders) (ok bool, metadata Metadata, err error) {\n\tvar metadataLocal Metadata\n\n\tfor _, elem := range u {\n\t\tif elem.userCreator == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn false, metadata, err\n\t\tdefault:\n\t\t\tmetadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)\n\t\t\tctx, cancel := context.WithTimeout(ctx, timeout)\n\t\t\tdefer cancel()\n\t\t\tok, createErr := elem.userCreator.UserCreate(ctx, user, pass, role)\n\t\t\tif createErr != nil {\n\t\t\t\terr = multierror.Append(err, createErr)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif !ok {\n\t\t\t\terr = multierror.Append(err, errors.New(\"failed to create user\"))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tmetadataLocal.SuccessfulProvider = elem.name\n\t\t\treturn ok, metadataLocal, nil\n\t\t}\n\t}\n\treturn ok, metadataLocal, multierror.Append(err, errors.New(\"failed to create user\"))\n}\n\n// CreateUsersFromInterfaces identifies implementations of the UserCreator interface and passes them to the createUser() wrapper method.\nfunc CreateUserFromInterfaces(ctx context.Context, timeout time.Duration, user, pass, role string, generic []interface{}) (ok bool, metadata Metadata, err error) {\n\tuserCreators := make([]userProviders, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := userProviders{name: getProviderName(elem)}\n\t\tswitch u := elem.(type) {\n\t\tcase UserCreator:\n\t\t\ttemp.userCreator = u\n\t\t\tuserCreators = append(userCreators, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a UserCreator implementation: %T\", u)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(userCreators) == 0 {\n\t\treturn ok, metadata, multierror.Append(err, errors.New(\"no UserCreator implementations found\"))\n\t}\n\treturn createUser(ctx, timeout, user, pass, role, userCreators)\n}\n\n// updateUser updates a user's settings\nfunc updateUser(ctx context.Context, timeout time.Duration, user, pass, role string, u []userProviders) (ok bool, metadata Metadata, err error) {\n\tvar metadataLocal Metadata\n\n\tfor _, elem := range u {\n\t\tif elem.userUpdater == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn false, metadata, err\n\t\tdefault:\n\t\t\tmetadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)\n\t\t\tctx, cancel := context.WithTimeout(ctx, timeout)\n\t\t\tdefer cancel()\n\t\t\tok, UpdateErr := elem.userUpdater.UserUpdate(ctx, user, pass, role)\n\t\t\tif UpdateErr != nil {\n\t\t\t\terr = multierror.Append(err, UpdateErr)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif !ok {\n\t\t\t\terr = multierror.Append(err, errors.New(\"failed to update user\"))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tmetadataLocal.SuccessfulProvider = elem.name\n\t\t\treturn ok, metadataLocal, nil\n\t\t}\n\t}\n\treturn ok, metadataLocal, multierror.Append(err, errors.New(\"failed to update user\"))\n}\n\n// UpdateUsersFromInterfaces identifies implementations of the UserUpdater interface and passes them to the updateUser() wrapper method.\nfunc UpdateUserFromInterfaces(ctx context.Context, timeout time.Duration, user, pass, role string, generic []interface{}) (ok bool, metadata Metadata, err error) {\n\tuserUpdaters := make([]userProviders, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := userProviders{name: getProviderName(elem)}\n\t\tswitch u := elem.(type) {\n\t\tcase UserUpdater:\n\t\t\ttemp.userUpdater = u\n\t\t\tuserUpdaters = append(userUpdaters, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a UserUpdater implementation: %T\", u)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(userUpdaters) == 0 {\n\t\treturn ok, metadata, multierror.Append(err, errors.New(\"no UserUpdater implementations found\"))\n\t}\n\treturn updateUser(ctx, timeout, user, pass, role, userUpdaters)\n}\n\n// deleteUser deletes a user from a BMC\nfunc deleteUser(ctx context.Context, timeout time.Duration, user string, u []userProviders) (ok bool, metadata Metadata, err error) {\n\tvar metadataLocal Metadata\n\n\tfor _, elem := range u {\n\t\tif elem.userDeleter == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn false, metadata, err\n\t\tdefault:\n\t\t\tmetadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)\n\t\t\tctx, cancel := context.WithTimeout(ctx, timeout)\n\t\t\tdefer cancel()\n\t\t\tok, deleteErr := elem.userDeleter.UserDelete(ctx, user)\n\t\t\tif deleteErr != nil {\n\t\t\t\terr = multierror.Append(err, deleteErr)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif !ok {\n\t\t\t\terr = multierror.Append(err, errors.New(\"failed to delete user\"))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tmetadataLocal.SuccessfulProvider = elem.name\n\t\t\treturn ok, metadataLocal, nil\n\t\t}\n\t}\n\treturn ok, metadataLocal, multierror.Append(err, errors.New(\"failed to delete user\"))\n}\n\n// DeleteUsersFromInterfaces identifies implementations of the UserDeleter interface and passes them to the deleteUser() wrapper method.\nfunc DeleteUserFromInterfaces(ctx context.Context, timeout time.Duration, user string, generic []interface{}) (ok bool, metadata Metadata, err error) {\n\tuserDeleters := make([]userProviders, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := userProviders{name: getProviderName(elem)}\n\t\tswitch u := elem.(type) {\n\t\tcase UserDeleter:\n\t\t\ttemp.userDeleter = u\n\t\t\tuserDeleters = append(userDeleters, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a UserDeleter implementation: %T\", u)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(userDeleters) == 0 {\n\t\treturn ok, metadata, multierror.Append(err, errors.New(\"no UserDeleter implementations found\"))\n\t}\n\treturn deleteUser(ctx, timeout, user, userDeleters)\n}\n\n// readUsers returns all users from a BMC\nfunc readUsers(ctx context.Context, timeout time.Duration, u []userProviders) (users []map[string]string, metadata Metadata, err error) {\n\tvar metadataLocal Metadata\n\n\tfor _, elem := range u {\n\t\tif elem.userReader == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn users, metadata, err\n\t\tdefault:\n\t\t\tmetadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)\n\t\t\tctx, cancel := context.WithTimeout(ctx, timeout)\n\t\t\tdefer cancel()\n\t\t\tusers, readErr := elem.userReader.UserRead(ctx)\n\t\t\tif readErr != nil {\n\t\t\t\terr = multierror.Append(err, readErr)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tmetadataLocal.SuccessfulProvider = elem.name\n\t\t\treturn users, metadataLocal, nil\n\t\t}\n\t}\n\treturn users, metadataLocal, multierror.Append(err, errors.New(\"failed to read users\"))\n}\n\n// ReadUsersFromInterfaces identifies implementations of the UserReader interface and passes them to the readUsers() wrapper method.\nfunc ReadUsersFromInterfaces(ctx context.Context, timeout time.Duration, generic []interface{}) (users []map[string]string, metadata Metadata, err error) {\n\tuserReaders := make([]userProviders, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := userProviders{name: getProviderName(elem)}\n\t\tswitch u := elem.(type) {\n\t\tcase UserReader:\n\t\t\ttemp.userReader = u\n\t\t\tuserReaders = append(userReaders, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a UserReader implementation: %T\", u)\n\t\t\terr = multierror.Append(errors.New(e))\n\t\t}\n\t}\n\tif len(userReaders) == 0 {\n\t\treturn users, metadata, multierror.Append(err, errors.New(\"no UserReader implementations found\"))\n\t}\n\treturn readUsers(ctx, timeout, userReaders)\n}\n"
  },
  {
    "path": "bmc/user_test.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/hashicorp/go-multierror\"\n)\n\ntype userTester struct {\n\tMakeNotOK    bool\n\tMakeErrorOut bool\n}\n\nfunc (p *userTester) UserCreate(ctx context.Context, user, pass, role string) (ok bool, err error) {\n\tif p.MakeErrorOut {\n\t\treturn ok, errors.New(\"create user failed\")\n\t}\n\tif p.MakeNotOK {\n\t\treturn false, nil\n\t}\n\treturn true, nil\n}\n\nfunc (p *userTester) UserUpdate(ctx context.Context, user, pass, role string) (ok bool, err error) {\n\tif p.MakeErrorOut {\n\t\treturn ok, errors.New(\"update user failed\")\n\t}\n\tif p.MakeNotOK {\n\t\treturn false, nil\n\t}\n\treturn true, nil\n}\n\nfunc (p *userTester) UserDelete(ctx context.Context, user string) (ok bool, err error) {\n\tif p.MakeErrorOut {\n\t\treturn ok, errors.New(\"delete user failed\")\n\t}\n\tif p.MakeNotOK {\n\t\treturn false, nil\n\t}\n\treturn true, nil\n}\n\nfunc (p *userTester) UserRead(ctx context.Context) (users []map[string]string, err error) {\n\tif p.MakeErrorOut {\n\t\treturn users, errors.New(\"read users failed\")\n\t}\n\n\tusers = []map[string]string{\n\t\t{\n\t\t\t\"Auth\":   \"true\",\n\t\t\t\"Callin\": \"true\",\n\t\t\t\"ID\":     \"2\",\n\t\t\t\"Link\":   \"false\",\n\t\t\t\"Name\":   \"ADMIN\",\n\t\t},\n\t}\n\treturn users, nil\n}\n\nfunc (p *userTester) Name() string {\n\treturn \"test provider\"\n}\n\nfunc TestUserCreate(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\tmakeErrorOut bool\n\t\tmakeNotOk    bool\n\t\twant         bool\n\t\terr          error\n\t\tctxTimeout   time.Duration\n\t}{\n\t\t\"success\":               {want: true},\n\t\t\"not ok return\":         {want: false, makeNotOk: true, err: &multierror.Error{Errors: []error{errors.New(\"failed to create user\"), errors.New(\"failed to create user\")}}},\n\t\t\"error\":                 {makeErrorOut: true, err: &multierror.Error{Errors: []error{errors.New(\"create user failed\"), errors.New(\"failed to create user\")}}},\n\t\t\"error context timeout\": {makeErrorOut: true, err: &multierror.Error{Errors: []error{errors.New(\"context deadline exceeded\")}}, ctxTimeout: time.Nanosecond * 1},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\ttestImplementation := userTester{MakeErrorOut: tc.makeErrorOut, MakeNotOK: tc.makeNotOk}\n\t\t\texpectedResult := tc.want\n\t\t\tuser := \"ADMIN\"\n\t\t\tpass := \"ADMIN\"\n\t\t\trole := \"admin\"\n\t\t\tif tc.ctxTimeout == 0 {\n\t\t\t\ttc.ctxTimeout = time.Second * 3\n\t\t\t}\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)\n\t\t\tdefer cancel()\n\t\t\tresult, _, err := createUser(ctx, 0, user, pass, role, []userProviders{{\"\", &testImplementation, nil, nil, nil}})\n\t\t\tif err != nil {\n\t\t\t\tdiff := cmp.Diff(err.Error(), tc.err.Error())\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdiff := cmp.Diff(result, expectedResult)\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCreateUserFromInterfaces(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\terr               error\n\t\tbadImplementation bool\n\t\twant              bool\n\t\twithMetadata      bool\n\t}{\n\t\t\"success\":                  {want: true},\n\t\t\"success with metadata\":    {want: true, withMetadata: true},\n\t\t\"no implementations found\": {badImplementation: true, err: &multierror.Error{Errors: []error{errors.New(\"not a UserCreator implementation: *struct {}\"), errors.New(\"no UserCreator implementations found\")}}},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tvar generic []interface{}\n\t\t\tif tc.badImplementation {\n\t\t\t\tbadImplementation := struct{}{}\n\t\t\t\tgeneric = []interface{}{&badImplementation}\n\t\t\t} else {\n\t\t\t\ttestImplementation := userTester{}\n\t\t\t\tgeneric = []interface{}{&testImplementation}\n\t\t\t}\n\t\t\texpectedResult := tc.want\n\t\t\tuser := \"ADMIN\"\n\t\t\tpass := \"ADMIN\"\n\t\t\trole := \"admin\"\n\t\t\tresult, metadata, err := CreateUserFromInterfaces(context.Background(), 0, user, pass, role, generic)\n\t\t\tif err != nil {\n\t\t\t\tif tc.err != nil {\n\t\t\t\t\tdiff := cmp.Diff(err.Error(), tc.err.Error())\n\t\t\t\t\tif diff != \"\" {\n\t\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdiff := cmp.Diff(result, expectedResult)\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif tc.withMetadata {\n\t\t\t\tif diff := cmp.Diff(metadata.SuccessfulProvider, \"test provider\"); diff != \"\" {\n\t\t\t\t\tt.Logf(\"%+v\", metadata)\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateUser(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\tmakeErrorOut bool\n\t\tmakeNotOk    bool\n\t\twant         bool\n\t\terr          error\n\t\tctxTimeout   time.Duration\n\t}{\n\t\t\"success\":               {want: true},\n\t\t\"not ok return\":         {want: false, makeNotOk: true, err: &multierror.Error{Errors: []error{errors.New(\"failed to update user\"), errors.New(\"failed to update user\")}}},\n\t\t\"error\":                 {makeErrorOut: true, err: &multierror.Error{Errors: []error{errors.New(\"update user failed\"), errors.New(\"failed to update user\")}}},\n\t\t\"error context timeout\": {makeErrorOut: true, err: &multierror.Error{Errors: []error{errors.New(\"context deadline exceeded\")}}, ctxTimeout: time.Nanosecond * 1},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\ttestImplementation := userTester{MakeErrorOut: tc.makeErrorOut, MakeNotOK: tc.makeNotOk}\n\t\t\texpectedResult := tc.want\n\t\t\tuser := \"ADMIN\"\n\t\t\tpass := \"ADMIN\"\n\t\t\trole := \"admin\"\n\t\t\tif tc.ctxTimeout == 0 {\n\t\t\t\ttc.ctxTimeout = time.Second * 3\n\t\t\t}\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)\n\t\t\tdefer cancel()\n\t\t\tresult, _, err := updateUser(ctx, 0, user, pass, role, []userProviders{{\"\", nil, &testImplementation, nil, nil}})\n\t\t\tif err != nil {\n\t\t\t\tdiff := cmp.Diff(err.Error(), tc.err.Error())\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdiff := cmp.Diff(result, expectedResult)\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUpdateUserFromInterfaces(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\terr               error\n\t\tbadImplementation bool\n\t\twant              bool\n\t\twithMetadata      bool\n\t}{\n\t\t\"success\":                  {want: true},\n\t\t\"success with metadata\":    {want: true, withMetadata: true},\n\t\t\"no implementations found\": {badImplementation: true, err: &multierror.Error{Errors: []error{errors.New(\"not a UserUpdater implementation: *struct {}\"), errors.New(\"no UserUpdater implementations found\")}}},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tvar generic []interface{}\n\t\t\tif tc.badImplementation {\n\t\t\t\tbadImplementation := struct{}{}\n\t\t\t\tgeneric = []interface{}{&badImplementation}\n\t\t\t} else {\n\t\t\t\ttestImplementation := userTester{}\n\t\t\t\tgeneric = []interface{}{&testImplementation}\n\t\t\t}\n\t\t\texpectedResult := tc.want\n\t\t\tuser := \"ADMIN\"\n\t\t\tpass := \"ADMIN\"\n\t\t\trole := \"admin\"\n\t\t\tresult, metadata, err := UpdateUserFromInterfaces(context.Background(), 0, user, pass, role, generic)\n\t\t\tif err != nil {\n\t\t\t\tif tc.err != nil {\n\t\t\t\t\tdiff := cmp.Diff(err.Error(), tc.err.Error())\n\t\t\t\t\tif diff != \"\" {\n\t\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdiff := cmp.Diff(result, expectedResult)\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif tc.withMetadata {\n\t\t\t\tif diff := cmp.Diff(metadata.SuccessfulProvider, \"test provider\"); diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDeleteUser(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\tmakeErrorOut bool\n\t\tmakeNotOk    bool\n\t\twant         bool\n\t\terr          error\n\t\tctxTimeout   time.Duration\n\t}{\n\t\t\"success\":               {want: true},\n\t\t\"not ok return\":         {want: false, makeNotOk: true, err: &multierror.Error{Errors: []error{errors.New(\"failed to delete user\"), errors.New(\"failed to delete user\")}}},\n\t\t\"error\":                 {makeErrorOut: true, err: &multierror.Error{Errors: []error{errors.New(\"delete user failed\"), errors.New(\"failed to delete user\")}}},\n\t\t\"error context timeout\": {makeErrorOut: true, err: &multierror.Error{Errors: []error{errors.New(\"context deadline exceeded\")}}, ctxTimeout: time.Nanosecond * 1},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\ttestImplementation := userTester{MakeErrorOut: tc.makeErrorOut, MakeNotOK: tc.makeNotOk}\n\t\t\texpectedResult := tc.want\n\t\t\tuser := \"ADMIN\"\n\t\t\tif tc.ctxTimeout == 0 {\n\t\t\t\ttc.ctxTimeout = time.Second * 3\n\t\t\t}\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)\n\t\t\tdefer cancel()\n\t\t\tresult, _, err := deleteUser(ctx, 0, user, []userProviders{{\"\", nil, nil, &testImplementation, nil}})\n\t\t\tif err != nil {\n\t\t\t\tdiff := cmp.Diff(err.Error(), tc.err.Error())\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdiff := cmp.Diff(result, expectedResult)\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDeleteUserFromInterfaces(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\terr               error\n\t\tbadImplementation bool\n\t\twant              bool\n\t\twithMetadata      bool\n\t}{\n\t\t\"success\":                  {want: true},\n\t\t\"success with metadata\":    {want: true, withMetadata: true},\n\t\t\"no implementations found\": {badImplementation: true, err: &multierror.Error{Errors: []error{errors.New(\"not a UserDeleter implementation: *struct {}\"), errors.New(\"no UserDeleter implementations found\")}}},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tvar generic []interface{}\n\t\t\tif tc.badImplementation {\n\t\t\t\tbadImplementation := struct{}{}\n\t\t\t\tgeneric = []interface{}{&badImplementation}\n\t\t\t} else {\n\t\t\t\ttestImplementation := userTester{}\n\t\t\t\tgeneric = []interface{}{&testImplementation}\n\t\t\t}\n\t\t\texpectedResult := tc.want\n\t\t\tuser := \"ADMIN\"\n\t\t\tresult, metadata, err := DeleteUserFromInterfaces(context.Background(), 0, user, generic)\n\t\t\tif err != nil {\n\t\t\t\tif tc.err != nil {\n\t\t\t\t\tdiff := cmp.Diff(err.Error(), tc.err.Error())\n\t\t\t\t\tif diff != \"\" {\n\t\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdiff := cmp.Diff(result, expectedResult)\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif tc.withMetadata {\n\t\t\t\tif diff := cmp.Diff(metadata.SuccessfulProvider, \"test provider\"); diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestReadUsers(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\tmakeErrorOut bool\n\t\twant         bool\n\t\terr          error\n\t\tctxTimeout   time.Duration\n\t}{\n\t\t\"success\":               {want: true},\n\t\t\"not ok return\":         {want: false, makeErrorOut: true, err: &multierror.Error{Errors: []error{errors.New(\"read users failed\"), errors.New(\"failed to read users\")}}},\n\t\t\"error context timeout\": {want: false, makeErrorOut: true, err: &multierror.Error{Errors: []error{errors.New(\"context deadline exceeded\")}}, ctxTimeout: time.Nanosecond * 1},\n\t}\n\n\tusers := []map[string]string{\n\t\t{\n\t\t\t\"Auth\":   \"true\",\n\t\t\t\"Callin\": \"true\",\n\t\t\t\"ID\":     \"2\",\n\t\t\t\"Link\":   \"false\",\n\t\t\t\"Name\":   \"ADMIN\",\n\t\t},\n\t}\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\ttestImplementation := userTester{MakeErrorOut: tc.makeErrorOut}\n\t\t\texpectedResult := users\n\t\t\tif tc.ctxTimeout == 0 {\n\t\t\t\ttc.ctxTimeout = time.Second * 3\n\t\t\t}\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)\n\t\t\tdefer cancel()\n\t\t\tresult, _, err := readUsers(ctx, 0, []userProviders{{\"\", nil, nil, nil, &testImplementation}})\n\t\t\tif err != nil {\n\t\t\t\tdiff := cmp.Diff(err.Error(), tc.err.Error())\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdiff := cmp.Diff(result, expectedResult)\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestReadUsersFromInterfaces(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\terr               error\n\t\tbadImplementation bool\n\t\twant              bool\n\t\twithMetadata      bool\n\t}{\n\t\t\"success\":                  {want: true},\n\t\t\"success with metadata\":    {want: true, withMetadata: true},\n\t\t\"no implementations found\": {badImplementation: true, err: &multierror.Error{Errors: []error{errors.New(\"not a UserReader implementation: *struct {}\"), errors.New(\"no UserReader implementations found\")}}},\n\t}\n\n\tusers := []map[string]string{\n\t\t{\n\t\t\t\"Auth\":   \"true\",\n\t\t\t\"Callin\": \"true\",\n\t\t\t\"ID\":     \"2\",\n\t\t\t\"Link\":   \"false\",\n\t\t\t\"Name\":   \"ADMIN\",\n\t\t},\n\t}\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tvar generic []interface{}\n\t\t\tif tc.badImplementation {\n\t\t\t\tbadImplementation := struct{}{}\n\t\t\t\tgeneric = []interface{}{&badImplementation}\n\t\t\t} else {\n\t\t\t\ttestImplementation := userTester{}\n\t\t\t\tgeneric = []interface{}{&testImplementation}\n\t\t\t}\n\t\t\texpectedResult := users\n\t\t\tresult, metadata, err := ReadUsersFromInterfaces(context.Background(), 0, generic)\n\t\t\tif err != nil {\n\t\t\t\tif tc.err != nil {\n\t\t\t\t\tdiff := cmp.Diff(err.Error(), tc.err.Error())\n\t\t\t\t\tif diff != \"\" {\n\t\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdiff := cmp.Diff(result, expectedResult)\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif tc.withMetadata {\n\t\t\t\tif diff := cmp.Diff(metadata.SuccessfulProvider, \"test provider\"); diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "bmc/virtual_media.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/hashicorp/go-multierror\"\n\t\"github.com/pkg/errors\"\n)\n\n// VirtualMediaSetter controls the virtual media attached to a machine\ntype VirtualMediaSetter interface {\n\tSetVirtualMedia(ctx context.Context, kind string, mediaURL string) (ok bool, err error)\n}\n\n// VirtualMediaProviders is an internal struct to correlate an implementation/provider and its name\ntype virtualMediaProviders struct {\n\tname               string\n\tvirtualMediaSetter VirtualMediaSetter\n}\n\n// setVirtualMedia sets the virtual media.\nfunc setVirtualMedia(ctx context.Context, kind string, mediaURL string, b []virtualMediaProviders) (ok bool, metadata Metadata, err error) {\n\tvar metadataLocal Metadata\n\n\tfor _, elem := range b {\n\t\tif elem.virtualMediaSetter == nil {\n\t\t\tcontinue\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terr = multierror.Append(err, ctx.Err())\n\n\t\t\treturn false, metadata, err\n\t\tdefault:\n\t\t\tmetadataLocal.ProvidersAttempted = append(metadataLocal.ProvidersAttempted, elem.name)\n\t\t\tok, setErr := elem.virtualMediaSetter.SetVirtualMedia(ctx, kind, mediaURL)\n\t\t\tif setErr != nil {\n\t\t\t\terr = multierror.Append(err, errors.WithMessagef(setErr, \"provider: %v\", elem.name))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif !ok {\n\t\t\t\terr = multierror.Append(err, fmt.Errorf(\"provider: %v, failed to set virtual media\", elem.name))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tmetadataLocal.SuccessfulProvider = elem.name\n\t\t\treturn ok, metadataLocal, nil\n\t\t}\n\t}\n\treturn ok, metadataLocal, multierror.Append(err, errors.New(\"failed to set virtual media\"))\n}\n\n// SetVirtualMediaFromInterfaces identifies implementations of the virtualMediaSetter interface and passes the found implementations to the setVirtualMedia() wrapper\nfunc SetVirtualMediaFromInterfaces(ctx context.Context, kind string, mediaURL string, generic []interface{}) (ok bool, metadata Metadata, err error) {\n\tbdSetters := make([]virtualMediaProviders, 0)\n\tfor _, elem := range generic {\n\t\tif elem == nil {\n\t\t\tcontinue\n\t\t}\n\t\ttemp := virtualMediaProviders{name: getProviderName(elem)}\n\t\tswitch p := elem.(type) {\n\t\tcase VirtualMediaSetter:\n\t\t\ttemp.virtualMediaSetter = p\n\t\t\tbdSetters = append(bdSetters, temp)\n\t\tdefault:\n\t\t\te := fmt.Sprintf(\"not a VirtualMediaSetter implementation: %T\", p)\n\t\t\terr = multierror.Append(err, errors.New(e))\n\t\t}\n\t}\n\tif len(bdSetters) == 0 {\n\t\treturn ok, metadata, multierror.Append(err, errors.New(\"no VirtualMediaSetter implementations found\"))\n\t}\n\treturn setVirtualMedia(ctx, kind, mediaURL, bdSetters)\n}\n"
  },
  {
    "path": "bmc/virtual_media_test.go",
    "content": "package bmc\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/hashicorp/go-multierror\"\n)\n\ntype virtualMediaTester struct {\n\tMakeNotOK    bool\n\tMakeErrorOut bool\n}\n\nfunc (r *virtualMediaTester) SetVirtualMedia(ctx context.Context, kind string, mediaURL string) (ok bool, err error) {\n\tif r.MakeErrorOut {\n\t\treturn ok, errors.New(\"setting virtual media failed\")\n\t}\n\tif r.MakeNotOK {\n\t\treturn false, nil\n\t}\n\treturn true, nil\n}\n\nfunc (r *virtualMediaTester) Name() string {\n\treturn \"test provider\"\n}\n\nfunc TestSetVirtualMedia(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\tkind         string\n\t\tmediaURL     string\n\t\tmakeErrorOut bool\n\t\tmakeNotOk    bool\n\t\twant         bool\n\t\terr          error\n\t\tctxTimeout   time.Duration\n\t}{\n\t\t\"success\":               {kind: \"cdrom\", mediaURL: \"example.com/some.iso\", want: true},\n\t\t\"not ok return\":         {kind: \"cdrom\", mediaURL: \"example.com/some.iso\", want: false, makeNotOk: true, err: &multierror.Error{Errors: []error{errors.New(\"provider: test provider, failed to set virtual media\"), errors.New(\"failed to set virtual media\")}}},\n\t\t\"error\":                 {kind: \"cdrom\", mediaURL: \"example.com/some.iso\", want: false, makeErrorOut: true, err: &multierror.Error{Errors: []error{errors.New(\"provider: test provider: setting virtual media failed\"), errors.New(\"failed to set virtual media\")}}},\n\t\t\"error context timeout\": {kind: \"cdrom\", mediaURL: \"example.com/some.iso\", want: false, makeErrorOut: true, err: &multierror.Error{Errors: []error{errors.New(\"context deadline exceeded\")}}, ctxTimeout: time.Nanosecond * 1},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\ttestImplementation := virtualMediaTester{MakeErrorOut: tc.makeErrorOut, MakeNotOK: tc.makeNotOk}\n\t\t\texpectedResult := tc.want\n\t\t\tif tc.ctxTimeout == 0 {\n\t\t\t\ttc.ctxTimeout = time.Second * 3\n\t\t\t}\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), tc.ctxTimeout)\n\t\t\tdefer cancel()\n\t\t\tresult, _, err := setVirtualMedia(ctx, tc.kind, tc.mediaURL, []virtualMediaProviders{{\"test provider\", &testImplementation}})\n\t\t\tif err != nil {\n\t\t\t\tdiff := cmp.Diff(err.Error(), tc.err.Error())\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdiff := cmp.Diff(result, expectedResult)\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSetVirtualMediaFromInterfaces(t *testing.T) {\n\ttestCases := map[string]struct {\n\t\tkind              string\n\t\tmediaURL          string\n\t\terr               error\n\t\tbadImplementation bool\n\t\twant              bool\n\t\twithName          bool\n\t}{\n\t\t\"success\":                  {kind: \"cdrom\", mediaURL: \"example.com/some.iso\", want: true},\n\t\t\"success with metadata\":    {kind: \"cdrom\", mediaURL: \"example.com/some.iso\", want: true, withName: true},\n\t\t\"no implementations found\": {kind: \"cdrom\", mediaURL: \"example.com/some.iso\", want: false, badImplementation: true, err: &multierror.Error{Errors: []error{errors.New(\"not a VirtualMediaSetter implementation: *struct {}\"), errors.New(\"no VirtualMediaSetter implementations found\")}}},\n\t}\n\n\tfor name, tc := range testCases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tvar generic []interface{}\n\t\t\tif tc.badImplementation {\n\t\t\t\tbadImplementation := struct{}{}\n\t\t\t\tgeneric = []interface{}{&badImplementation}\n\t\t\t} else {\n\t\t\t\ttestImplementation := virtualMediaTester{}\n\t\t\t\tgeneric = []interface{}{&testImplementation}\n\t\t\t}\n\t\t\texpectedResult := tc.want\n\t\t\tresult, metadata, err := SetVirtualMediaFromInterfaces(context.Background(), tc.kind, tc.mediaURL, generic)\n\t\t\tif err != nil {\n\t\t\t\tif tc.err != nil {\n\t\t\t\t\tdiff := cmp.Diff(err.Error(), tc.err.Error())\n\t\t\t\t\tif diff != \"\" {\n\t\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdiff := cmp.Diff(result, expectedResult)\n\t\t\t\tif diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif tc.withName {\n\t\t\t\tif diff := cmp.Diff(metadata.SuccessfulProvider, \"test provider\"); diff != \"\" {\n\t\t\t\t\tt.Fatal(diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "client.go",
    "content": "// Package bmclib client.go is intended to be the main public API.\n// Its purpose is to make interacting with bmclib as friendly as possible.\npackage bmclib\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"dario.cat/mergo\"\n\t\"github.com/bmc-toolbox/bmclib/v2/bmc\"\n\t\"github.com/bmc-toolbox/bmclib/v2/constants\"\n\t\"github.com/bmc-toolbox/bmclib/v2/internal/httpclient\"\n\t\"github.com/bmc-toolbox/bmclib/v2/providers/asrockrack\"\n\t\"github.com/bmc-toolbox/bmclib/v2/providers/dell\"\n\t\"github.com/bmc-toolbox/bmclib/v2/providers/homeassistant\"\n\t\"github.com/bmc-toolbox/bmclib/v2/providers/intelamt\"\n\t\"github.com/bmc-toolbox/bmclib/v2/providers/ipmitool\"\n\t\"github.com/bmc-toolbox/bmclib/v2/providers/openbmc\"\n\t\"github.com/bmc-toolbox/bmclib/v2/providers/redfish\"\n\t\"github.com/bmc-toolbox/bmclib/v2/providers/rpc\"\n\t\"github.com/bmc-toolbox/bmclib/v2/providers/supermicro\"\n\t\"github.com/bmc-toolbox/common\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/jacobweinstock/registrar\"\n\t\"go.opentelemetry.io/otel/attribute\"\n\toteltrace \"go.opentelemetry.io/otel/trace\"\n\ttracenoop \"go.opentelemetry.io/otel/trace/noop\"\n)\n\nconst (\n\t// default connection timeout\n\tdefaultConnectTimeout = 30 * time.Second\n\tpkgName               = \"github.com/bmc-toolbox/bmclib\"\n)\n\n// Client for BMC interactions\ntype Client struct {\n\tAuth     Auth\n\tLogger   logr.Logger\n\tRegistry *registrar.Registry\n\n\thttpClient             *http.Client\n\thttpClientSetupFuncs   []func(*http.Client)\n\tmdLock                 *sync.Mutex\n\tmetadata               *bmc.Metadata\n\tperProviderTimeout     func(context.Context) time.Duration\n\toneTimeRegistry        *registrar.Registry\n\toneTimeRegistryEnabled bool\n\tproviderConfig         providerConfig\n\ttraceprovider          oteltrace.TracerProvider\n}\n\n// Auth details for connecting to a BMC\ntype Auth struct {\n\tHost string\n\tUser string\n\tPass string\n}\n\n// providerConfig contains per provider specific configuration.\ntype providerConfig struct {\n\tipmitool      ipmitool.Config\n\tasrock        asrockrack.Config\n\tgofish        redfish.Config\n\tintelamt      intelamt.Config\n\tdell          dell.Config\n\tsupermicro    supermicro.Config\n\trpc           rpc.Provider\n\topenbmc       openbmc.Config\n\thomeassistant homeassistant.Config\n}\n\n// NewClient returns a new Client struct\nfunc NewClient(host, user, pass string, opts ...Option) *Client {\n\tdefaultClient := &Client{\n\t\tLogger:                 logr.Discard(),\n\t\tRegistry:               registrar.NewRegistry(),\n\t\toneTimeRegistryEnabled: false,\n\t\toneTimeRegistry:        registrar.NewRegistry(),\n\t\thttpClient:             httpclient.Build(),\n\t\ttraceprovider:          tracenoop.NewTracerProvider(),\n\t\tproviderConfig: providerConfig{\n\t\t\tipmitool: ipmitool.Config{\n\t\t\t\tPort: \"623\",\n\t\t\t},\n\t\t\tasrock: asrockrack.Config{\n\t\t\t\tPort: \"443\",\n\t\t\t},\n\t\t\tgofish: redfish.Config{\n\t\t\t\tPort:                  \"443\",\n\t\t\t\tVersionsNotCompatible: []string{},\n\t\t\t},\n\t\t\tintelamt: intelamt.Config{\n\t\t\t\tHostScheme: \"http\",\n\t\t\t\tPort:       16992,\n\t\t\t},\n\t\t\tdell: dell.Config{\n\t\t\t\tPort:                  \"443\",\n\t\t\t\tVersionsNotCompatible: []string{},\n\t\t\t},\n\t\t\tsupermicro: supermicro.Config{\n\t\t\t\tPort: \"443\",\n\t\t\t},\n\t\t\trpc: rpc.Provider{},\n\t\t\topenbmc: openbmc.Config{\n\t\t\t\tPort: \"443\",\n\t\t\t},\n\t\t\thomeassistant: homeassistant.Config{},\n\t\t},\n\t}\n\n\tfor _, opt := range opts {\n\t\topt(defaultClient)\n\t}\n\tfor _, setupFunc := range defaultClient.httpClientSetupFuncs {\n\t\tsetupFunc(defaultClient.httpClient)\n\t}\n\n\tdefaultClient.Registry.Logger = defaultClient.Logger\n\tdefaultClient.Auth.Host = host\n\tdefaultClient.Auth.User = user\n\tdefaultClient.Auth.Pass = pass\n\t// len of 0 means that no Registry, with any registered providers, was passed in.\n\tif len(defaultClient.Registry.Drivers) == 0 {\n\t\tdefaultClient.registerProviders()\n\t}\n\tdefaultClient.mdLock = &sync.Mutex{}\n\tif defaultClient.perProviderTimeout == nil {\n\t\tdefaultClient.perProviderTimeout = defaultClient.defaultTimeout\n\t}\n\n\treturn defaultClient\n}\n\nfunc (c *Client) defaultTimeout(ctx context.Context) time.Duration {\n\tdeadline, ok := ctx.Deadline()\n\tif !ok {\n\t\treturn defaultConnectTimeout\n\t}\n\n\tl := len(c.registry().Drivers)\n\tif l == 0 {\n\t\treturn time.Until(deadline)\n\t}\n\treturn time.Until(deadline) / time.Duration(l)\n}\n\nfunc (c *Client) registerHomeAssistantProvider() error {\n\tdriverHA := homeassistant.New(c.Auth.Host, c.Auth.Pass)\n\tc.providerConfig.homeassistant.Logger = c.Logger\n\thttpClient := *c.httpClient\n\thttpClient.Transport = c.httpClient.Transport.(*http.Transport).Clone()\n\tc.providerConfig.homeassistant.HTTPClient = &httpClient\n\n\tif err := mergo.Merge(driverHA, c.providerConfig.homeassistant, mergo.WithOverride); err != nil {\n\t\treturn fmt.Errorf(\"failed to merge user specified homeassistant config with the config defaults, homeassistant provider not available: %w\", err)\n\t}\n\tc.Registry.Register(homeassistant.ProviderName, homeassistant.ProviderProtocol, homeassistant.Features, nil, driverHA)\n\n\treturn nil\n}\n\nfunc (c *Client) registerRPCProvider() error {\n\tdriverRPC := rpc.New(c.providerConfig.rpc.ConsumerURL, c.Auth.Host, c.providerConfig.rpc.Opts.HMAC.Secrets)\n\tc.providerConfig.rpc.Logger = c.Logger\n\thttpClient := *c.httpClient\n\thttpClient.Transport = c.httpClient.Transport.(*http.Transport).Clone()\n\tc.providerConfig.rpc.HTTPClient = &httpClient\n\tif err := mergo.Merge(driverRPC, c.providerConfig.rpc, mergo.WithOverride, mergo.WithTransformers(&rpc.Provider{})); err != nil {\n\t\treturn fmt.Errorf(\"failed to merge user specified rpc config with the config defaults, rpc provider not available: %w\", err)\n\t}\n\tc.Registry.Register(rpc.ProviderName, rpc.ProviderProtocol, rpc.Features, nil, driverRPC)\n\n\treturn nil\n}\n\n// register ipmitool provider\nfunc (c *Client) registerIPMIProvider() error {\n\tipmiOpts := []ipmitool.Option{\n\t\tipmitool.WithLogger(c.Logger),\n\t\tipmitool.WithPort(c.providerConfig.ipmitool.Port),\n\t\tipmitool.WithCipherSuite(c.providerConfig.ipmitool.CipherSuite),\n\t\tipmitool.WithIpmitoolPath(c.providerConfig.ipmitool.IpmitoolPath),\n\t}\n\n\tdriverIpmitool, err := ipmitool.New(c.Auth.Host, c.Auth.User, c.Auth.Pass, ipmiOpts...)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tc.Registry.Register(ipmitool.ProviderName, ipmitool.ProviderProtocol, ipmitool.Features, nil, driverIpmitool)\n\n\treturn nil\n}\n\n// register ASRR vendorapi provider\nfunc (c *Client) registerASRRProvider() {\n\tasrHttpClient := *c.httpClient\n\tasrHttpClient.Transport = c.httpClient.Transport.(*http.Transport).Clone()\n\tdriverAsrockrack := asrockrack.NewWithOptions(c.Auth.Host+\":\"+c.providerConfig.asrock.Port, c.Auth.User, c.Auth.Pass, c.Logger, asrockrack.WithHTTPClient(&asrHttpClient))\n\tc.Registry.Register(asrockrack.ProviderName, asrockrack.ProviderProtocol, asrockrack.Features, nil, driverAsrockrack)\n}\n\n// register gofish provider\nfunc (c *Client) registerGofishProvider() {\n\tgfHttpClient := *c.httpClient\n\tgfHttpClient.Transport = c.httpClient.Transport.(*http.Transport).Clone()\n\tgofishOpts := []redfish.Option{\n\t\tredfish.WithHttpClient(&gfHttpClient),\n\t\tredfish.WithVersionsNotCompatible(c.providerConfig.gofish.VersionsNotCompatible),\n\t\tredfish.WithUseBasicAuth(c.providerConfig.gofish.UseBasicAuth),\n\t\tredfish.WithPort(c.providerConfig.gofish.Port),\n\t\tredfish.WithEtagMatchDisabled(c.providerConfig.gofish.DisableEtagMatch),\n\t\tredfish.WithSystemName(c.providerConfig.gofish.SystemName),\n\t}\n\n\tdriverGoFish := redfish.New(c.Auth.Host, c.Auth.User, c.Auth.Pass, c.Logger, gofishOpts...)\n\tc.Registry.Register(redfish.ProviderName, redfish.ProviderProtocol, redfish.Features, nil, driverGoFish)\n}\n\n// register Intel AMT provider\nfunc (c *Client) registerIntelAMTProvider() {\n\n\tiamtOpts := []intelamt.Option{\n\t\tintelamt.WithLogger(c.Logger),\n\t\tintelamt.WithHostScheme(c.providerConfig.intelamt.HostScheme),\n\t\tintelamt.WithPort(c.providerConfig.intelamt.Port),\n\t}\n\tdriverAMT := intelamt.New(c.Auth.Host, c.Auth.User, c.Auth.Pass, iamtOpts...)\n\tc.Registry.Register(intelamt.ProviderName, intelamt.ProviderProtocol, intelamt.Features, nil, driverAMT)\n}\n\n// register Dell gofish provider\nfunc (c *Client) registerDellProvider() {\n\tdellGofishHttpClient := *c.httpClient\n\t//dellGofishHttpClient.Transport = c.httpClient.Transport.(*http.Transport).Clone()\n\tdellGofishOpts := []dell.Option{\n\t\tdell.WithHttpClient(&dellGofishHttpClient),\n\t\tdell.WithVersionsNotCompatible(c.providerConfig.dell.VersionsNotCompatible),\n\t\tdell.WithUseBasicAuth(c.providerConfig.dell.UseBasicAuth),\n\t\tdell.WithPort(c.providerConfig.dell.Port),\n\t}\n\tdriverGoFishDell := dell.New(c.Auth.Host, c.Auth.User, c.Auth.Pass, c.Logger, dellGofishOpts...)\n\tc.Registry.Register(dell.ProviderName, redfish.ProviderProtocol, dell.Features, nil, driverGoFishDell)\n}\n\n// register supermicro vendorapi provider\nfunc (c *Client) registerSupermicroProvider() {\n\tsmcHttpClient := *c.httpClient\n\tsmcHttpClient.Transport = c.httpClient.Transport.(*http.Transport).Clone()\n\tdriverSupermicro := supermicro.NewClient(\n\t\tc.Auth.Host,\n\t\tc.Auth.User,\n\t\tc.Auth.Pass,\n\t\tc.Logger,\n\t\tsupermicro.WithHttpClient(&smcHttpClient),\n\t\tsupermicro.WithPort(c.providerConfig.supermicro.Port),\n\t)\n\n\tc.Registry.Register(supermicro.ProviderName, supermicro.ProviderProtocol, supermicro.Features, nil, driverSupermicro)\n}\n\nfunc (c *Client) registerOpenBMCProvider() {\n\thttpClient := *c.httpClient\n\thttpClient.Transport = c.httpClient.Transport.(*http.Transport).Clone()\n\tdriver := openbmc.New(\n\t\tc.Auth.Host,\n\t\tc.Auth.User,\n\t\tc.Auth.Pass,\n\t\tc.Logger,\n\t\topenbmc.WithHttpClient(&httpClient),\n\t\topenbmc.WithPort(c.providerConfig.openbmc.Port),\n\t)\n\n\tc.Registry.Register(openbmc.ProviderName, openbmc.ProviderProtocol, openbmc.Features, nil, driver)\n}\n\nfunc (c *Client) registerProviders() {\n\t// register the homeassistant provider, if options for it were provided\n\tif c.providerConfig.homeassistant.SwitchEntityID != \"\" {\n\t\t// when the homeassistant provider is to be used, we won't register any other providers.\n\t\terr := c.registerHomeAssistantProvider()\n\t\tif err == nil {\n\t\t\tc.Logger.Info(\"note: with the homeassistant provider registered, no other providers will be registered and available\")\n\t\t\treturn\n\t\t}\n\t\tc.Logger.Info(\"failed to register homeassistant provider, falling back to registering all other providers\", \"error\", err.Error())\n\n\t}\n\n\t// register the rpc provider\n\t// without the consumer URL there is no way to send RPC requests.\n\tif c.providerConfig.rpc.ConsumerURL != \"\" {\n\t\t// when the rpc provider is to be used, we won't register any other providers.\n\t\terr := c.registerRPCProvider()\n\t\tif err == nil {\n\t\t\tc.Logger.Info(\"note: with the rpc provider registered, no other providers will be registered and available\")\n\t\t\treturn\n\t\t}\n\t\tc.Logger.Info(\"failed to register rpc provider, falling back to registering all other providers\", \"error\", err.Error())\n\t}\n\n\tif err := c.registerIPMIProvider(); err != nil {\n\t\tc.Logger.Info(\"ipmitool provider not available\", \"error\", err.Error())\n\t}\n\n\tc.registerASRRProvider()\n\tc.registerGofishProvider()\n\tc.registerIntelAMTProvider()\n\tc.registerDellProvider()\n\tc.registerSupermicroProvider()\n\tc.registerOpenBMCProvider()\n}\n\n// GetMetadata returns the metadata that is populated after each BMC function/method call\nfunc (c *Client) GetMetadata() bmc.Metadata {\n\tif c.metadata != nil {\n\t\treturn *c.metadata\n\t}\n\treturn bmc.Metadata{}\n}\n\n// setMetadata wraps setting metadata with a mutex for cases where users are\n// making calls to multiple *Client.X functions/methods across goroutines\nfunc (c *Client) setMetadata(metadata bmc.Metadata) {\n\t// a mutex is created with the NewClient func, in the case\n\t// where a user doesn't call NewClient we handle by checking if\n\t// the mutex is nil\n\tif c.mdLock != nil {\n\t\tc.mdLock.Lock()\n\t\tdefer c.mdLock.Unlock()\n\t}\n\tc.metadata = &metadata\n}\n\n// registry will return the oneTimeRegistry if the oneTimeRegistryEnabled is true.\nfunc (c *Client) registry() *registrar.Registry {\n\tif c.oneTimeRegistryEnabled {\n\t\tc.oneTimeRegistryEnabled = false\n\t\treturn c.oneTimeRegistry\n\t}\n\n\treturn c.Registry\n}\n\nfunc (c *Client) RegisterSpanAttributes(m bmc.Metadata, span oteltrace.Span) {\n\tspan.SetAttributes(attribute.String(\"host\", c.Auth.Host))\n\n\tspan.SetAttributes(attribute.String(\"successful-provider\", m.SuccessfulProvider))\n\n\tspan.SetAttributes(\n\t\tattribute.String(\"successful-open-conns\", strings.Join(m.SuccessfulOpenConns, \",\")),\n\t)\n\n\tspan.SetAttributes(\n\t\tattribute.String(\"successful-close-conns\", strings.Join(m.SuccessfulCloseConns, \",\")),\n\t)\n\n\tspan.SetAttributes(\n\t\tattribute.String(\"attempted-providers\", strings.Join(m.ProvidersAttempted, \",\")),\n\t)\n\n\tfor p, e := range m.FailedProviderDetail {\n\t\tspan.SetAttributes(\n\t\t\tattribute.String(\"provider-errs-\"+p, e),\n\t\t)\n\t}\n}\n\n// Open calls the OpenConnectionFromInterfaces library function\n// Any providers/drivers that do not successfully connect are removed\n// from the client.Registry.Drivers. If client.Registry.Drivers ends up\n// being empty then we error.\nfunc (c *Client) Open(ctx context.Context) error {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"Open\")\n\tdefer span.End()\n\n\tifs, metadata, err := bmc.OpenConnectionFromInterfaces(ctx, c.perProviderTimeout(ctx), c.registry().GetDriverInterfaces())\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\tdefer c.setMetadata(metadata)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar reg registrar.Drivers\n\tfor _, elem := range c.Registry.Drivers {\n\t\tfor _, em := range ifs {\n\t\t\tif em == elem.DriverInterface {\n\t\t\t\telem.DriverInterface = em\n\t\t\t\treg = append(reg, elem)\n\t\t\t}\n\t\t}\n\t}\n\tc.Registry.Drivers = reg\n\n\treturn nil\n}\n\n// Close pass through to library function\nfunc (c *Client) Close(ctx context.Context) (err error) {\n\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"Close\")\n\tdefer span.End()\n\n\t// Generally, we always want the close function to run.\n\t// We don't want a context timeout or cancellation to prevent this.\n\t// But because the current model is to pass just a single context to all\n\t// functions, we need to create a new context here allowing closing connections.\n\t// This is a short term solution, and we should consider a better/more holistic model.\n\tif err := ctx.Err(); err != nil {\n\t\tvar done context.CancelFunc\n\t\tctx, done = context.WithTimeout(context.Background(), defaultConnectTimeout)\n\t\tdefer done()\n\t}\n\tmetadata, err := bmc.CloseConnectionFromInterfaces(ctx, c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn err\n}\n\n// FilterForCompatible removes any drivers/providers that are not compatible. It wraps the\n// Client.Registry.FilterForCompatible func in order to provide a per provider timeout.\nfunc (c *Client) FilterForCompatible(ctx context.Context) {\n\tperProviderTimeout, cancel := context.WithTimeout(ctx, c.perProviderTimeout(ctx))\n\tdefer cancel()\n\n\treg := c.registry().FilterForCompatible(perProviderTimeout)\n\tc.Registry.Drivers = reg\n}\n\n// GetPowerState pass through to library function\nfunc (c *Client) GetPowerState(ctx context.Context) (state string, err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"GetPowerState\")\n\tdefer span.End()\n\n\tstate, metadata, err := bmc.GetPowerStateFromInterfaces(ctx, c.perProviderTimeout(ctx), c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn state, err\n}\n\n// SetPowerState pass through to library function\nfunc (c *Client) SetPowerState(ctx context.Context, state string) (ok bool, err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"SetPowerState\")\n\tdefer span.End()\n\n\tok, metadata, err := bmc.SetPowerStateFromInterfaces(ctx, c.perProviderTimeout(ctx), state, c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn ok, err\n}\n\n// CreateUser pass through to library function\nfunc (c *Client) CreateUser(ctx context.Context, user, pass, role string) (ok bool, err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"CreateUser\")\n\tdefer span.End()\n\n\tok, metadata, err := bmc.CreateUserFromInterfaces(ctx, c.perProviderTimeout(ctx), user, pass, role, c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn ok, err\n}\n\n// UpdateUser pass through to library function\nfunc (c *Client) UpdateUser(ctx context.Context, user, pass, role string) (ok bool, err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"UpdateUser\")\n\tdefer span.End()\n\n\tok, metadata, err := bmc.UpdateUserFromInterfaces(ctx, c.perProviderTimeout(ctx), user, pass, role, c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn ok, err\n}\n\n// DeleteUser pass through to library function\nfunc (c *Client) DeleteUser(ctx context.Context, user string) (ok bool, err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"DeleteUser\")\n\tdefer span.End()\n\n\tok, metadata, err := bmc.DeleteUserFromInterfaces(ctx, c.perProviderTimeout(ctx), user, c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn ok, err\n}\n\n// ReadUsers pass through to library function\nfunc (c *Client) ReadUsers(ctx context.Context) (users []map[string]string, err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"ReadUsers\")\n\tdefer span.End()\n\n\tusers, metadata, err := bmc.ReadUsersFromInterfaces(ctx, c.perProviderTimeout(ctx), c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn users, err\n}\n\n// GetBootDeviceOverride pass through to library function\nfunc (c *Client) GetBootDeviceOverride(ctx context.Context) (override bmc.BootDeviceOverride, err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"GetBootDeviceOverride\")\n\tdefer span.End()\n\n\toverride, metadata, err := bmc.GetBootDeviceOverrideFromInterface(ctx, c.perProviderTimeout(ctx), c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\n\treturn override, err\n}\n\n// SetBootDevice pass through to library function\nfunc (c *Client) SetBootDevice(ctx context.Context, bootDevice string, setPersistent, efiBoot bool) (ok bool, err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"SetBootDevice\")\n\tdefer span.End()\n\n\tok, metadata, err := bmc.SetBootDeviceFromInterfaces(ctx, c.perProviderTimeout(ctx), bootDevice, setPersistent, efiBoot, c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn ok, err\n}\n\n// SetVirtualMedia controls the virtual media simulated by the BMC as being connected to the\n// server. Specifically, the method ejects any currently attached virtual media, and then if\n// mediaURL isn't empty, attaches a virtual media device of type kind whose contents are\n// streamed from the indicated URL.\nfunc (c *Client) SetVirtualMedia(ctx context.Context, kind string, mediaURL string) (ok bool, err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"SetVirtualMedia\")\n\tdefer span.End()\n\n\tok, metadata, err := bmc.SetVirtualMediaFromInterfaces(ctx, kind, mediaURL, c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn ok, err\n}\n\n// ResetBMC pass through to library function\nfunc (c *Client) ResetBMC(ctx context.Context, resetType string) (ok bool, err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"ResetBMC\")\n\tdefer span.End()\n\n\tok, metadata, err := bmc.ResetBMCFromInterfaces(ctx, c.perProviderTimeout(ctx), resetType, c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn ok, err\n}\n\n// DeactivateSOL pass through library function to deactivate active SOL sessions\nfunc (c *Client) DeactivateSOL(ctx context.Context) (err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"DeactivateSOL\")\n\tdefer span.End()\n\tmetadata, err := bmc.DeactivateSOLFromInterfaces(ctx, c.perProviderTimeout(ctx), c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\treturn err\n}\n\n// Inventory pass through library function to collect hardware and firmware inventory\nfunc (c *Client) Inventory(ctx context.Context) (device *common.Device, err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"Inventory\")\n\tdefer span.End()\n\n\tdevice, metadata, err := bmc.GetInventoryFromInterfaces(ctx, c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\treturn device, err\n}\n\nfunc (c *Client) GetBiosConfiguration(ctx context.Context) (biosConfig map[string]string, err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"GetBiosConfiguration\")\n\tdefer span.End()\n\n\tbiosConfig, metadata, err := bmc.GetBiosConfigurationInterfaces(ctx, c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn biosConfig, err\n}\n\nfunc (c *Client) SetBiosConfiguration(ctx context.Context, biosConfig map[string]string) (err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"SetBiosConfiguration\")\n\tdefer span.End()\n\n\tmetadata, err := bmc.SetBiosConfigurationInterfaces(ctx, c.registry().GetDriverInterfaces(), biosConfig)\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn err\n}\n\nfunc (c *Client) SetBiosConfigurationFromFile(ctx context.Context, cfg string) (err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"SetBiosConfigurationFromFile\")\n\tdefer span.End()\n\n\tmetadata, err := bmc.SetBiosConfigurationFromFileInterfaces(ctx, c.registry().GetDriverInterfaces(), cfg)\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn err\n}\n\nfunc (c *Client) ResetBiosConfiguration(ctx context.Context) (err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"ResetBiosConfiguration\")\n\tdefer span.End()\n\n\tmetadata, err := bmc.ResetBiosConfigurationInterfaces(ctx, c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn err\n}\n\n// FirmwareInstall pass through library function to upload firmware and install firmware\nfunc (c *Client) FirmwareInstall(ctx context.Context, component string, operationApplyTime string, forceInstall bool, reader io.Reader) (taskID string, err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"FirmwareInstall\")\n\tdefer span.End()\n\n\ttaskID, metadata, err := bmc.FirmwareInstallFromInterfaces(ctx, component, operationApplyTime, forceInstall, reader, c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn taskID, err\n}\n\n// Note: this interface is to be deprecated in favour of a more generic FirmwareTaskStatus.\n//\n// FirmwareInstallStatus pass through library function to check firmware install status\nfunc (c *Client) FirmwareInstallStatus(ctx context.Context, installVersion, component, taskID string) (status string, err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"FirmwareInstallStatus\")\n\tdefer span.End()\n\n\tstatus, metadata, err := bmc.FirmwareInstallStatusFromInterfaces(ctx, installVersion, component, taskID, c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn status, err\n\n}\n\n// PostCodeGetter pass through library function to return the BIOS/UEFI POST code\nfunc (c *Client) PostCode(ctx context.Context) (status string, code int, err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"PostCode\")\n\tdefer span.End()\n\n\tstatus, code, metadata, err := bmc.GetPostCodeInterfaces(ctx, c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn status, code, err\n}\n\nfunc (c *Client) Screenshot(ctx context.Context) (image []byte, fileType string, err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"Screenshot\")\n\tdefer span.End()\n\n\timage, fileType, metadata, err := bmc.ScreenshotFromInterfaces(ctx, c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn image, fileType, err\n}\n\nfunc (c *Client) ClearSystemEventLog(ctx context.Context) (err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"ClearSystemEventLog\")\n\tdefer span.End()\n\n\tmetadata, err := bmc.ClearSystemEventLogFromInterfaces(ctx, c.perProviderTimeout(ctx), c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn err\n}\n\nfunc (c *Client) MountFloppyImage(ctx context.Context, image io.Reader) (err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"MountFloppyImage\")\n\tdefer span.End()\n\n\tmetadata, err := bmc.MountFloppyImageFromInterfaces(ctx, image, c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn err\n}\n\nfunc (c *Client) UnmountFloppyImage(ctx context.Context) (err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"UnmountFloppyImage\")\n\tdefer span.End()\n\n\tmetadata, err := bmc.UnmountFloppyImageFromInterfaces(ctx, c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn err\n}\n\n// FirmwareInstallSteps return the order of actions required install firmware for a component.\nfunc (c *Client) FirmwareInstallSteps(ctx context.Context, component string) (actions []constants.FirmwareInstallStep, err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"FirmwareInstallSteps\")\n\tdefer span.End()\n\n\tstatus, metadata, err := bmc.FirmwareInstallStepsFromInterfaces(ctx, component, c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn status, err\n}\n\n// FirmwareUpload just uploads the firmware for install, it returns a task ID to verify the upload status.\nfunc (c *Client) FirmwareUpload(ctx context.Context, component string, file *os.File) (uploadVerifyTaskID string, err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"FirmwareUpload\")\n\tdefer span.End()\n\n\tuploadVerifyTaskID, metadata, err := bmc.FirmwareUploadFromInterfaces(ctx, component, file, c.Registry.GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn uploadVerifyTaskID, err\n}\n\n// FirmwareTaskStatus pass through library function to check firmware task statuses\nfunc (c *Client) FirmwareTaskStatus(ctx context.Context, kind constants.FirmwareInstallStep, component, taskID, installVersion string) (state constants.TaskState, status string, err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"FirmwareTaskStatus\")\n\tdefer span.End()\n\n\tstate, status, metadata, err := bmc.FirmwareTaskStatusFromInterfaces(ctx, kind, component, taskID, installVersion, c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn state, status, err\n}\n\n// FirmwareInstallUploaded kicks off firmware install for a firmware uploaded with FirmwareUpload.\nfunc (c *Client) FirmwareInstallUploaded(ctx context.Context, component, uploadVerifyTaskID string) (installTaskID string, err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"FirmwareInstallUploaded\")\n\tdefer span.End()\n\n\tinstallTaskID, metadata, err := bmc.FirmwareInstallerUploadedFromInterfaces(ctx, component, uploadVerifyTaskID, c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn installTaskID, err\n}\n\nfunc (c *Client) FirmwareInstallUploadAndInitiate(ctx context.Context, component string, file *os.File) (taskID string, err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"FirmwareInstallUploadAndInitiate\")\n\tdefer span.End()\n\n\ttaskID, metadata, err := bmc.FirmwareInstallUploadAndInitiateFromInterfaces(ctx, component, file, c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\tmetadata.RegisterSpanAttributes(c.Auth.Host, span)\n\n\treturn taskID, err\n}\n\n// GetSystemEventLog queries for the SEL and returns the entries in an opinionated format.\nfunc (c *Client) GetSystemEventLog(ctx context.Context) (entries bmc.SystemEventLogEntries, err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"GetSystemEventLog\")\n\tdefer span.End()\n\n\tentries, metadata, err := bmc.GetSystemEventLogFromInterfaces(ctx, c.perProviderTimeout(ctx), c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\treturn entries, err\n}\n\n// GetSystemEventLogRaw queries for the SEL and returns the raw response.\nfunc (c *Client) GetSystemEventLogRaw(ctx context.Context) (eventlog string, err error) {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"GetSystemEventLogRaw\")\n\tdefer span.End()\n\n\teventlog, metadata, err := bmc.GetSystemEventLogRawFromInterfaces(ctx, c.perProviderTimeout(ctx), c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\treturn eventlog, err\n}\n\n// SendNMI tells the BMC to issue an NMI to the device\nfunc (c *Client) SendNMI(ctx context.Context) error {\n\tctx, span := c.traceprovider.Tracer(pkgName).Start(ctx, \"SendNMI\")\n\tdefer span.End()\n\n\tmetadata, err := bmc.SendNMIFromInterface(ctx, c.perProviderTimeout(ctx), c.registry().GetDriverInterfaces())\n\tc.setMetadata(metadata)\n\n\treturn err\n}\n"
  },
  {
    "path": "client_test.go",
    "content": "package bmclib\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/logging\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/jacobweinstock/registrar\"\n\t\"gopkg.in/go-playground/assert.v1\"\n)\n\nfunc TestBMC(t *testing.T) {\n\tt.Skip(\"needs ipmitool and real ipmi server\")\n\thost := \"127.0.0.1\"\n\tuser := \"admin\"\n\tpass := \"admin\"\n\n\tlog := logging.DefaultLogger()\n\tctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)\n\tdefer cancel()\n\tcl := NewClient(host, user, pass, WithLogger(log), WithPerProviderTimeout(5*time.Second))\n\tif err := cl.Open(ctx); err != nil {\n\t\tt.Logf(\"%+v\", cl.GetMetadata())\n\t\tt.Fatal(err)\n\t}\n\tdefer cl.Close(ctx)\n\tt.Logf(\"metadata for Open: %+v\", cl.GetMetadata())\n\n\tcl.Registry.Drivers = cl.Registry.PreferDriver(\"non-existent\")\n\tstate, err := cl.GetPowerState(ctx)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Log(state)\n\tt.Logf(\"metadata for GetPowerState: %+v\", cl.GetMetadata())\n\n\tcl.Registry.Drivers = cl.Registry.PreferDriver(\"ipmitool\")\n\tstate, err = cl.PreferProvider(\"gofish\").GetPowerState(ctx)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Log(state)\n\tt.Logf(\"metadata for GetPowerState: %+v\", cl.GetMetadata())\n\n\tusers, err := cl.ReadUsers(ctx)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Log(users)\n\tt.Logf(\"metadata for ReadUsers: %+v\", cl.GetMetadata())\n\n\tt.Fatal()\n}\n\nfunc TestWithRedfishVersionsNotCompatible(t *testing.T) {\n\thost := \"127.0.0.1\"\n\tuser := \"ADMIN\"\n\tpass := \"ADMIN\"\n\n\ttests := []struct {\n\t\tname     string\n\t\tversions []string\n\t}{\n\t\t{\n\t\t\t\"no versions\",\n\t\t\t[]string{},\n\t\t},\n\t\t{\n\t\t\t\"with versions\",\n\t\t\t[]string{\"1.2.3\", \"4.5.6\"},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcl := NewClient(host, user, pass, WithRedfishVersionsNotCompatible(tt.versions))\n\t\t\tassert.Equal(t, tt.versions, cl.providerConfig.gofish.VersionsNotCompatible)\n\t\t})\n\t}\n}\n\nfunc TestWithRedfishBasicAuth(t *testing.T) {\n\thost := \"127.0.0.1\"\n\tuser := \"ADMIN\"\n\tpass := \"ADMIN\"\n\n\ttests := []struct {\n\t\tname    string\n\t\tenabled bool\n\t}{\n\t\t{\n\t\t\t\"disabled\",\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"enabled\",\n\t\t\ttrue,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar opts []Option\n\t\t\tif tt.enabled {\n\t\t\t\topts = append(opts, WithRedfishUseBasicAuth(true))\n\t\t\t}\n\n\t\t\tcl := NewClient(host, user, pass, opts...)\n\t\t\tassert.Equal(t, tt.enabled, cl.providerConfig.gofish.UseBasicAuth)\n\t\t})\n\t}\n}\n\nfunc TestWithConnectionTimeout(t *testing.T) {\n\thost := \"127.0.0.1\"\n\tuser := \"ADMIN\"\n\tpass := \"ADMIN\"\n\n\ttests := []struct {\n\t\tname    string\n\t\ttimeout time.Duration\n\t}{\n\t\t{\n\t\t\t\"no connection timeout\",\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t\"with timeout\",\n\t\t\t5 * time.Second,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcl := NewClient(host, user, pass, WithPerProviderTimeout(tt.timeout))\n\t\t\tassert.Equal(t, tt.timeout, cl.perProviderTimeout(nil))\n\t\t})\n\t}\n}\n\nfunc TestDefaultTimeout(t *testing.T) {\n\ttests := map[string]struct {\n\t\tctx  context.Context\n\t\twant func(n int) time.Duration\n\t}{\n\t\t\"no per provider timeout\": {\n\t\t\tctx:  context.Background(),\n\t\t\twant: func(n int) time.Duration { return 30 * time.Second },\n\t\t},\n\t\t\"with per provider timeout\": {\n\t\t\tctx: func() context.Context {\n\t\t\t\tc, d := context.WithTimeout(context.Background(), 5*time.Second)\n\t\t\t\tdefer d()\n\t\t\t\treturn c\n\t\t\t}(),\n\t\t\twant: func(n int) time.Duration {\n\t\t\t\tv := (4999 * time.Millisecond / time.Duration(n))\n\t\t\t\treturn v.Round(time.Millisecond)\n\t\t\t},\n\t\t},\n\t}\n\tfor name, tt := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tc := NewClient(\"\", \"\", \"\")\n\t\t\tgot := c.defaultTimeout(tt.ctx)\n\t\t\tif !equalWithinErrorMargin(got.Round(time.Millisecond), tt.want(len(c.Registry.Drivers))) {\n\t\t\t\tdiff := cmp.Diff(got.Round(time.Millisecond), tt.want(len(c.Registry.Drivers)))\n\t\t\t\tt.Errorf(\"unexpected timeout (-want +got):\\n%s\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc equalWithinErrorMargin(a, b time.Duration) bool {\n\treturn (a - b) < 10*time.Millisecond\n}\n\ntype testProvider struct {\n\tPName        string\n\tPowerstate   string\n\tBootdeviceOK bool\n\tErr          error\n}\n\nfunc (t *testProvider) Name() string {\n\tif t.PName != \"\" {\n\t\treturn t.PName\n\t}\n\treturn \"tester\"\n}\n\nfunc (t *testProvider) Open(ctx context.Context) error {\n\treturn t.Err\n}\n\nfunc (t *testProvider) Close(ctx context.Context) error {\n\treturn t.Err\n}\n\nfunc (t *testProvider) PowerStateGet(ctx context.Context) (string, error) {\n\treturn t.Powerstate, t.Err\n}\n\nfunc (t *testProvider) PowerSet(ctx context.Context, state string) error {\n\treturn t.Err\n}\n\nfunc (t *testProvider) BootDeviceSet(ctx context.Context, bootDevice string, setPersistent, efiBoot bool) (ok bool, err error) {\n\treturn t.BootdeviceOK, t.Err\n}\n\nfunc registryNames(r []*registrar.Driver) []string {\n\tvar names []string\n\tfor _, d := range r {\n\t\tnames = append(names, d.Name)\n\t}\n\treturn names\n}\n\nfunc TestOpenFiltered(t *testing.T) {\n\tregistry := registrar.NewRegistry()\n\tregistry.Register(\"tester1\", \"tester1\", nil, nil, &testProvider{PName: \"tester1\"})\n\tregistry.Register(\"tester2\", \"tester2\", nil, nil, &testProvider{PName: \"tester2\"})\n\tregistry.Register(\"tester3\", \"tester3\", nil, nil, &testProvider{PName: \"tester3\"})\n\tcl := NewClient(\"\", \"\", \"\", WithRegistry(registry))\n\tif err := cl.PreferProvider(\"tester3\").Open(context.Background()); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer cl.Close(context.Background())\n\twant := []string{\"tester3\", \"tester1\", \"tester2\"}\n\tif diff := cmp.Diff(cl.GetMetadata().ProvidersAttempted, want); diff != \"\" {\n\t\tt.Errorf(\"diff: %s\", diff)\n\t}\n\twant = []string{\"tester1\", \"tester2\", \"tester3\"}\n\tif diff := cmp.Diff(registryNames(cl.Registry.Drivers), want); diff != \"\" {\n\t\tt.Errorf(\"diff: %s\", diff)\n\t}\n}\n"
  },
  {
    "path": "constants/constants.go",
    "content": "package constants\n\ntype (\n\t// Redfish operation apply time parameter\n\tOperationApplyTime string\n\n\t// The FirmwareInstallStep identifies each phase of a firmware install process.\n\tFirmwareInstallStep string\n\n\tTaskState string\n)\n\nconst (\n\t// EnvEnableDebug is the const for the environment variable to cause bmclib to dump debugging debugging information.\n\t// the valid parameter for this environment variable is 'true'\n\tEnvEnableDebug = \"DEBUG_BMCLIB\"\n\n\t// Vendor constants\n\n\t// HP is the constant that defines the vendor HP\n\tHP = \"HP\"\n\t// Dell is the constant that defines the vendor Dell\n\tDell = \"Dell\"\n\t// Supermicro is the constant that defines the vendor Supermicro\n\tSupermicro = \"Supermicro\"\n\t// Cloudline is the constant that defines the cloudlines\n\tCloudline = \"Cloudline\"\n\t// Quanta is the contant to identify Quanta hardware\n\tQuanta = \"Quanta\"\n\t// Quanta is the contant to identify Intel hardware\n\tIntel = \"Intel\"\n\n\t// Redfish firmware apply at constants\n\t// FirmwareApplyImmediate sets the firmware to be installed immediately after upload\n\tImmediate OperationApplyTime = \"Immediate\"\n\t//FirmwareApplyOnReset sets the firmware to be install on device power cycle/reset\n\tOnReset OperationApplyTime = \"OnReset\"\n\t// FirmwareOnStartUpdateRequest sets the firmware install to begin after the start request has been sent.\n\tOnStartUpdateRequest OperationApplyTime = \"OnStartUpdateRequest\"\n\n\t// TODO: rename FirmwareInstall* task status names to FirmwareTaskState and declare a type.\n\n\t// Firmware install states returned by bmclib provider FirmwareInstallStatus implementations\n\t//\n\t// The redfish from the redfish spec are exposed as a smaller set of bmclib states for callers\n\t// https://www.dmtf.org/sites/default/files/standards/documents/DSP2046_2020.3.pdf\n\n\t// FirmwareInstallInitializing indicates the device is performing init actions to install the update\n\t// this covers the redfish states - 'starting', 'downloading'\n\t// no action is required from the callers part in this state\n\tFirmwareInstallInitializing           = \"initializing\"\n\tInitializing                TaskState = \"initializing\"\n\n\t// FirmwareInstallQueued indicates the device has queued the update, but has not started the update task yet\n\t// this covers the redfish states - 'pending', 'new'\n\t// no action is required from the callers part in this state\n\tFirmwareInstallQueued           = \"queued\"\n\tQueued                TaskState = \"queued\"\n\n\t// FirmwareInstallRunner indicates the device is installing the update\n\t// this covers the redfish states - 'running', 'stopping', 'cancelling'\n\t// no action is required from the callers part in this state\n\tFirmwareInstallRunning           = \"running\"\n\tRunning                TaskState = \"running\"\n\n\t// FirmwareInstallComplete indicates the device completed the firmware install\n\t// this covers the redfish state - 'complete'\n\tFirmwareInstallComplete           = \"complete\"\n\tComplete                TaskState = \"complete\"\n\n\t// FirmwareInstallFailed indicates the firmware install failed\n\t// this covers the redfish states - 'interrupted', 'killed', 'exception', 'cancelled', 'suspended'\n\tFirmwareInstallFailed           = \"failed\"\n\tFailed                TaskState = \"failed\"\n\n\t// FirmwareInstallPowerCycleHost indicates the firmware install requires a host power cycle\n\tFirmwareInstallPowerCycleHost           = \"powercycle-host\"\n\tPowerCycleHost                TaskState = \"powercycle-host\"\n\n\tFirmwareInstallUnknown           = \"unknown\"\n\tUnknown                TaskState = \"unknown\"\n\n\t// FirmwareInstallStepUploadInitiateInstall identifies the step to upload _and_ initialize the firmware install.\n\t// as part of the same call.\n\tFirmwareInstallStepUploadInitiateInstall FirmwareInstallStep = \"upload-initiate-install\"\n\n\t// FirmwareInstallStepInstallStatus identifies the step to verify the status of the firmware install.\n\tFirmwareInstallStepInstallStatus FirmwareInstallStep = \"install-status\"\n\n\t// FirmwareInstallStepUpload identifies the upload step in the firmware install process.\n\tFirmwareInstallStepUpload FirmwareInstallStep = \"upload\"\n\n\t// FirmwareInstallStepUploadStatus identifies the step to verify the upload status as part of the firmware install status.\n\tFirmwareInstallStepUploadStatus FirmwareInstallStep = \"upload-status\"\n\n\t// FirmwareInstallStepInstallUploaded identifies the step to install firmware uploaded in FirmwareInstallStepUpload.\n\tFirmwareInstallStepInstallUploaded FirmwareInstallStep = \"install-uploaded\"\n\n\t// FirmwareInstallStepPowerOffHost indicates the host requires to be powered off.\n\tFirmwareInstallStepPowerOffHost FirmwareInstallStep = \"power-off-host\"\n\n\t// FirmwareInstallStepResetBMCPostInstall indicates the BMC requires a reset after the install.\n\tFirmwareInstallStepResetBMCPostInstall FirmwareInstallStep = \"reset-bmc-post-install\"\n\n\t// FirmwareInstallStepResetBMCOnInstallFailure indicates the BMC requires a reset if an install fails.\n\tFirmwareInstallStepResetBMCOnInstallFailure FirmwareInstallStep = \"reset-bmc-on-install-failure\"\n\n\t// device BIOS/UEFI POST code bmclib identifiers\n\tPOSTStateBootINIT = \"boot-init/pxe\"\n\tPOSTStateUEFI     = \"uefi\"\n\tPOSTStateOS       = \"grub/os\"\n\tPOSTCodeUnknown   = \"unknown\"\n)\n\n// ListSupportedVendors  returns a list of supported vendors\nfunc ListSupportedVendors() []string {\n\treturn []string{HP, Dell, Supermicro}\n}\n"
  },
  {
    "path": "doc.go",
    "content": "// Copyright 2022 The bmclib Authors. All rights reserved.\n// Use of this source code is governed by an Apache that can be found in the LICENSE file.\n\n/*\nPackage bmclib abstracts interacting with Baseboard Management controllers.\n\nsee the examples directory for usage.\n*/\n\npackage bmclib\n"
  },
  {
    "path": "errors/errors.go",
    "content": "package errors\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n)\n\nvar (\n\t// ErrLoginFailed is returned when we fail to login to a bmc\n\tErrLoginFailed = errors.New(\"failed to login\")\n\n\t// ErrLogoutFailed is returned when we fail to logout from a bmc\n\tErrLogoutFailed = errors.New(\"failed to logout\")\n\n\t// ErrNotAuthenticated is returned when the session is not active.\n\tErrNotAuthenticated = errors.New(\"not authenticated\")\n\n\t// ErrNon200Response is returned when bmclib recieves an unexpected non-200 status code for a query\n\tErrNon200Response = errors.New(\"non-200 response returned for the endpoint\")\n\n\t// ErrNotImplemented is returned for not implemented methods called\n\tErrNotImplemented = errors.New(\"this feature hasn't been implemented yet\")\n\n\t// ErrRetrievingUserAccounts is returned when bmclib is unable to retrieve user accounts from the BMC\n\tErrRetrievingUserAccounts = errors.New(\"error retrieving user accounts\")\n\n\t// ErrInvalidUserRole is returned when the given user account role is not valid\n\tErrInvalidUserRole = errors.New(\"invalid user account role\")\n\n\t// ErrUserParamsRequired is returned when all the required user parameters are not provided - username, password, role\n\tErrUserParamsRequired = errors.New(\"username, password and role are required parameters\")\n\n\t// ErrUserAccountExists is returned when a user account with the username is already present\n\tErrUserAccountExists = errors.New(\"user account already exists\")\n\n\t// ErrNoUserSlotsAvailable is returned when there are no user account slots available\n\tErrNoUserSlotsAvailable = errors.New(\"no user account slots available\")\n\n\t// ErrUserAccountNotFound is returned when the user account is not present\n\tErrUserAccountNotFound = errors.New(\"given user account does not exist\")\n\n\t// ErrUserAccountUpdate is returned when the user account failed to be updated\n\tErrUserAccountUpdate = errors.New(\"user account attributes could not be updated\")\n\n\t// ErrRedfishVersionIncompatible is returned when a given version of redfish doesn't support a feature\n\tErrRedfishVersionIncompatible = errors.New(\"operation not supported in this redfish version\")\n\n\t// ErrRedfishChassisOdataID is returned when no compatible Chassis Odata IDs were identified\n\tErrRedfishChassisOdataID = errors.New(\"no compatible Chassis Odata IDs identified\")\n\n\t// ErrRedfishSystemOdataID is returned when no compatible System Odata IDs were identified\n\tErrRedfishSystemOdataID = errors.New(\"no compatible System Odata IDs identified\")\n\n\t// ErrRedfishManagerOdataID is returned when no compatible Manager Odata IDs were identified\n\tErrRedfishManagerOdataID = errors.New(\"no compatible Manager Odata IDs identified\")\n\n\t// ErrRedfishServiceNil is returned when a redfish method is invoked on a nil redfish (gofish) Service object\n\tErrRedfishServiceNil = errors.New(\"redfish connection returned a nil redfish Service object\")\n\n\t// ErrRedfishSoftwareInventory is returned when software inventory could not be collected over redfish\n\tErrRedfishSoftwareInventory = errors.New(\"error collecting redfish software inventory\")\n\n\t// ErrFirmwareUpload is returned when a firmware upload method fails\n\tErrFirmwareUpload = errors.New(\"error uploading firmware\")\n\n\t// ErrFirmwareInstall is returned for firmware install failures\n\tErrFirmwareInstall = errors.New(\"error updating firmware\")\n\n\t// ErrFirmwareInstallUploaded is returned for a firmware install call on a firmware previously uploaded.\n\tErrFirmwareInstallUploaded = errors.New(\"error installing uploaded firmware\")\n\n\t// ErrFirmwareInstallStatus is returned for firmware install status read\n\tErrFirmwareInstallStatus = errors.New(\"error querying firmware install status\")\n\n\t// ErrFirmwareTaskStatus is returned when a query for the firmware upload status fails\n\tErrFirmwareTaskStatus = errors.New(\"error querying firmware upload status\")\n\n\t// ErrFirmwareVerifyTask indicates a firmware verify task is in progress or did not complete successfully,\n\tErrFirmwareVerifyTask = errors.New(\"error firmware upload verify task\")\n\n\t// ErrRedfishUpdateService is returned on redfish update service errors\n\tErrRedfishUpdateService = errors.New(\"redfish update service error\")\n\n\t// ErrTaskNotFound is returned when the (redfish) task could not be found\n\tErrTaskNotFound = errors.New(\"task not found\")\n\n\t// ErrTaskPurge is returned when a (redfish) task could not be purged\n\tErrTaskPurge = errors.New(\"unable to purge task\")\n\n\t// ErrPowerStatusRead is returned when a power status read query fails\n\tErrPowerStatusRead = errors.New(\"error returning power status\")\n\n\t// ErrPowerStatusSet is returned when a power status set query fails\n\tErrPowerStatusSet = errors.New(\"error setting power status\")\n\n\t// ErrProviderImplementation is returned when theres an error in the BMC provider implementation\n\tErrProviderImplementation = errors.New(\"error in provider implementation\")\n\n\t// ErrCompatibilityCheck is returned when the compatibility probe failed to complete successfully.\n\tErrCompatibilityCheck = errors.New(\"compatibility check failed\")\n\n\t// ErrNoBiosAttributes is returned when no bios attributes are available from the BMC.\n\tErrNoBiosAttributes = errors.New(\"no BIOS attributes available\")\n\n\t// ErrScreenshot is returned when screen capture fails.\n\tErrScreenshot = errors.New(\"error in capturing screen\")\n\n\t// ErrIncompatibleProvider is returned by Open() when the device is not compatible with the provider\n\tErrIncompatibleProvider = errors.New(\"provider not compatible with device\")\n\n\t// ErrBMCColdResetRequired is returned when a BMC cold reset is required.\n\tErrBMCColdResetRequired = errors.New(\"BMC cold reset required\")\n\n\t// ErrHostPowercycleRequired is returned when a host powercycle is required.\n\tErrHostPowercycleRequired = errors.New(\"Host power cycle required\")\n\n\t// ErrSessionExpired is returned when the BMC session is not valid\n\t// the receiver can then choose to request a new session.\n\tErrSessionExpired = errors.New(\"session expired\")\n\n\t// ErrSystemVendorModel is returned when the system vendor, model attributes could not be identified.\n\tErrSystemVendorModel = errors.New(\"error identifying system vendor, model attributes\")\n\n\t// ErrRedfishNoSystems is returned when the API of the device provides and empty array of systems.\n\tErrRedfishNoSystems = errors.New(\"redfish: no Systems were found on the device\")\n\n\t// ErrBMCUpdating is returned when the BMC is going through an update and will not serve other queries.\n\tErrBMCUpdating = errors.New(\"a BMC firmware update is in progress\")\n)\n\ntype ErrUnsupportedHardware struct {\n\tmsg string\n}\n\nfunc (e *ErrUnsupportedHardware) Error() string {\n\treturn fmt.Sprintf(\"Hardware not supported: %s\", e.msg)\n}\n\nfunc NewErrUnsupportedHardware(s string) error {\n\treturn &ErrUnsupportedHardware{s}\n}\n"
  },
  {
    "path": "examples/bios/doc.go",
    "content": "/*\nbios is an example commmand that retrieves BIOS configuration information and prints it out\n\n\t$ BMC_HOST=1.2.3.4 BMC_USERNAME=foo BMC_PASSWORD=bar go run ./examples/bios/main.go\n*/\npackage main\n"
  },
  {
    "path": "examples/bios/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\tbmclib \"github.com/bmc-toolbox/bmclib/v2\"\n\t\"github.com/bmc-toolbox/bmclib/v2/providers\"\n\tlogrusr \"github.com/bombsimon/logrusr/v2\"\n\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc main() {\n\tctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)\n\tdefer cancel()\n\n\t// Command line option flag parsing\n\tuser := flag.String(\"user\", \"\", \"Username to login with\")\n\tpass := flag.String(\"password\", \"\", \"Username to login with\")\n\thost := flag.String(\"host\", \"\", \"BMC hostname to connect to\")\n\tmode := flag.String(\"mode\", \"get\", \"Mode [get,set,reset]\")\n\tdfile := flag.String(\"file\", \"\", \"Read data from file\")\n\n\tflag.Parse()\n\n\t// Logger configuration\n\tl := logrus.New()\n\tl.Level = logrus.DebugLevel\n\t// l.Level = logrus.TraceLevel\n\tlogger := logrusr.New(l)\n\tlogger.V(9)\n\n\t// bmclib client abstraction\n\tclientOpts := []bmclib.Option{bmclib.WithLogger(logger)}\n\n\tclient := bmclib.NewClient(*host, *user, *pass, clientOpts...)\n\tclient.Registry.Drivers = client.Registry.Supports(\n\t\tproviders.FeatureGetBiosConfiguration,\n\t\tproviders.FeatureSetBiosConfiguration,\n\t\tproviders.FeatureResetBiosConfiguration,\n\t\tproviders.FeatureSetBiosConfigurationFromFile)\n\n\terr := client.Open(ctx)\n\tif err != nil {\n\t\tl.Fatal(err, \"bmc login failed\")\n\t}\n\n\tdefer client.Close(ctx)\n\n\t// Operating mode selection\n\tswitch strings.ToLower(*mode) {\n\tcase \"get\":\n\t\t// retrieve bios configuration\n\t\tbiosConfig, err := client.GetBiosConfiguration(ctx)\n\t\tif err != nil {\n\t\t\tl.Fatal(err)\n\t\t}\n\n\t\tfmt.Printf(\"biosConfig: %#v\\n\", biosConfig)\n\tcase \"set\":\n\t\texampleConfig := make(map[string]string)\n\n\t\tif *dfile != \"\" {\n\t\t\tjsonFile, err := os.Open(*dfile)\n\t\t\tif err != nil {\n\t\t\t\tl.Fatal(err)\n\t\t\t}\n\n\t\t\tdefer jsonFile.Close()\n\n\t\t\tjsonData, _ := io.ReadAll(jsonFile)\n\n\t\t\terr = json.Unmarshal(jsonData, &exampleConfig)\n\t\t\tif err != nil {\n\t\t\t\tl.Fatal(err)\n\t\t\t}\n\t\t} else {\n\t\t\texampleConfig[\"TpmSecurity\"] = \"Off\"\n\t\t}\n\n\t\tfmt.Println(\"Attempting to set BIOS configuration:\")\n\t\tfmt.Printf(\"exampleConfig: %+v\\n\", exampleConfig)\n\n\t\terr := client.SetBiosConfiguration(ctx, exampleConfig)\n\t\tif err != nil {\n\t\t\tl.Error(err)\n\t\t}\n\tcase \"setfile\":\n\t\tfmt.Println(\"Attempting to set BIOS configuration:\")\n\n\t\tcontents, err := os.ReadFile(*dfile)\n\t\tif err != nil {\n\t\t\tl.Fatal(err)\n\t\t}\n\n\t\terr = client.SetBiosConfigurationFromFile(ctx, string(contents))\n\t\tif err != nil {\n\t\t\tl.Error(err)\n\t\t}\n\tcase \"reset\":\n\t\terr := client.ResetBiosConfiguration(ctx)\n\t\tif err != nil {\n\t\t\tl.Error(err)\n\t\t}\n\tdefault:\n\t\tl.Fatal(\"Unknown mode: \" + *mode)\n\t}\n}\n"
  },
  {
    "path": "examples/create-users/doc.go",
    "content": "/*\ncreate-users is an example commmand that utilizes the 'v1' bmclib interface\nmethods to create user entries in a BMC using the redfish driver.\n\n\t$ go run ./examples/v1/create-users/main.go -h\n\tUsage of /tmp/go-build440589615/b001/exe/main:\n\t\t-cert-pool string\n\t\t\t\t\tPath to an file containing x509 CAs. An empty string uses the system CAs. Only takes effect when --secure-tls=true\n\t\t-dry-run\n\t\t\t\t\tConnect to the BMC but do not create users\n\t\t-host string\n\t\t\t\t\tBMC hostname to connect to\n\t\t-password string\n\t\t\t\t\tUsername to login with\n\t\t-port int\n\t\t\t\t\tBMC port to connect to (default 443)\n\t\t-secure-tls\n\t\t\t\t\tEnable secure TLS\n\t\t-user string\n\t\t\t\t\tUsername to login with\n\t\t-user-csv string\n\t\t\t\t\tA CSV file of users to create containing 3 columns: username, password, role\n*/\npackage main\n"
  },
  {
    "path": "examples/create-users/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"crypto/x509\"\n\t\"encoding/csv\"\n\t\"flag\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"time\"\n\n\tbmclib \"github.com/bmc-toolbox/bmclib/v2\"\n\t\"github.com/bombsimon/logrusr/v2\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc main() {\n\tuser := flag.String(\"user\", \"\", \"Username to login with\")\n\tpass := flag.String(\"password\", \"\", \"Username to login with\")\n\thost := flag.String(\"host\", \"\", \"BMC hostname to connect to\")\n\twithSecureTLS := flag.Bool(\"secure-tls\", false, \"Enable secure TLS\")\n\tcertPoolFile := flag.String(\"cert-pool\", \"\", \"Path to an file containing x509 CAs. An empty string uses the system CAs. Only takes effect when --secure-tls=true\")\n\tuserCSV := flag.String(\"user-csv\", \"\", \"A CSV file of users to create containing 3 columns: username, password, role\")\n\tdryRun := flag.Bool(\"dry-run\", false, \"Connect to the BMC but do not create users\")\n\tflag.Parse()\n\n\tl := logrus.New()\n\tl.Level = logrus.DebugLevel\n\tlogger := logrusr.New(l)\n\n\tif *host == \"\" || *user == \"\" || *pass == \"\" {\n\t\tl.Fatal(\"required host/user/pass parameters not defined\")\n\t}\n\n\tclientOpts := []bmclib.Option{bmclib.WithLogger(logger)}\n\n\tif *withSecureTLS {\n\t\tvar pool *x509.CertPool\n\t\tif *certPoolFile != \"\" {\n\t\t\tpool = x509.NewCertPool()\n\t\t\tdata, err := ioutil.ReadFile(*certPoolFile)\n\t\t\tif err != nil {\n\t\t\t\tl.Fatal(err)\n\t\t\t}\n\t\t\tpool.AppendCertsFromPEM(data)\n\t\t}\n\t\t// a nil pool uses the system certs\n\t\tclientOpts = append(clientOpts, bmclib.WithSecureTLS(pool))\n\t}\n\n\tcl := bmclib.NewClient(*host, *user, *pass, clientOpts...)\n\tcl.Registry.Drivers = cl.Registry.Using(\"redfish\")\n\n\tctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)\n\tdefer cancel()\n\n\terr := cl.Open(ctx)\n\tif err != nil {\n\t\tl.WithError(err).Fatal(err, \"BMC login failed\")\n\t}\n\tdefer cl.Close(ctx)\n\n\tfh, err := os.Open(*userCSV)\n\tif err != nil {\n\t\tl.WithError(err).WithField(\"file\", *userCSV).Fatal()\n\t}\n\tdefer fh.Close()\n\treader := csv.NewReader(fh)\n\ti := 0\n\tfor {\n\t\trecord, err := reader.Read()\n\t\ti++\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\tl.WithError(err).Fatal()\n\t\t}\n\t\tif len(record) != 3 {\n\t\t\tl.WithField(\"line\", i).WithField(\"length\", len(record)).Infof(\"line did not have 3 columns\")\n\t\t\tcontinue\n\t\t}\n\t\tif !*dryRun {\n\t\t\t_, err = cl.CreateUser(ctx, record[0], record[1], record[2])\n\t\t\tif err != nil {\n\t\t\t\tl.WithError(err).Error(\"error creating user\")\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tl.WithFields(logrus.Fields(map[string]interface{}{\n\t\t\t\"user\": record[0],\n\t\t\t\"role\": record[2],\n\t\t})).Info(\"created user\")\n\t}\n\n\tl.WithField(\"count\", i).Info(\"created users\")\n\n}\n"
  },
  {
    "path": "examples/floppy-image/doc.go",
    "content": "/*\ninventory is an example commmand that utilizes the 'v1' bmclib interface\nmethods to upload and mount, unmount a floppy image.\n\n\t    # mount image\n\t\t$ go run examples/floppy-image/main.go \\\n\t\t\t-host 10.1.2.3 \\\n\t\t\t-user ADMIN \\\n\t\t\t-password hunter2 \\\n\t\t\t-image /tmp/floppy.img\n\n\t\t# un-mount image\n\t\t$ go run examples/floppy-image/main.go \\\n\t\t\t-host 10.1.2.3 \\\n\t\t\t-user ADMIN \\\n\t\t\t-password hunter2 \\\n\t\t\t-unmount\n*/\npackage main\n"
  },
  {
    "path": "examples/floppy-image/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"crypto/x509\"\n\t\"flag\"\n\t\"log\"\n\t\"os\"\n\t\"time\"\n\n\tbmclib \"github.com/bmc-toolbox/bmclib/v2\"\n\t\"github.com/bombsimon/logrusr/v2\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc main() {\n\tctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)\n\tdefer cancel()\n\n\tuser := flag.String(\"user\", \"\", \"Username to login with\")\n\tpass := flag.String(\"password\", \"\", \"Username to login with\")\n\thost := flag.String(\"host\", \"\", \"BMC hostname to connect to\")\n\timagePath := flag.String(\"image\", \"\", \"The .img file to be uploaded\")\n\tunmountImage := flag.Bool(\"unmount\", false, \"Unmount floppy image\")\n\n\twithSecureTLS := flag.Bool(\"secure-tls\", false, \"Enable secure TLS\")\n\tcertPoolFile := flag.String(\"cert-pool\", \"\", \"Path to an file containing x509 CAs. An empty string uses the system CAs. Only takes effect when --secure-tls=true\")\n\tflag.Parse()\n\n\tl := logrus.New()\n\tl.Level = logrus.DebugLevel\n\tlogger := logrusr.New(l)\n\n\tclientOpts := []bmclib.Option{bmclib.WithLogger(logger)}\n\n\tif *withSecureTLS {\n\t\tvar pool *x509.CertPool\n\t\tif *certPoolFile != \"\" {\n\t\t\tpool = x509.NewCertPool()\n\t\t\tdata, err := os.ReadFile(*certPoolFile)\n\t\t\tif err != nil {\n\t\t\t\tl.Fatal(err)\n\t\t\t}\n\t\t\tpool.AppendCertsFromPEM(data)\n\t\t}\n\t\t// a nil pool uses the system certs\n\t\tclientOpts = append(clientOpts, bmclib.WithSecureTLS(pool))\n\t}\n\n\tcl := bmclib.NewClient(*host, *user, *pass, clientOpts...)\n\terr := cl.Open(ctx)\n\tif err != nil {\n\t\tlog.Fatal(err, \"bmc login failed\")\n\t}\n\n\tdefer cl.Close(ctx)\n\n\tif *unmountImage {\n\t\tif err := cl.UnmountFloppyImage(ctx); err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\n\t\treturn\n\t}\n\n\t// open file handle\n\tfh, err := os.Open(*imagePath)\n\tif err != nil {\n\t\tl.Fatal(err)\n\t}\n\tdefer fh.Close()\n\n\terr = cl.MountFloppyImage(ctx, fh)\n\tif err != nil {\n\t\tl.Fatal(err)\n\t}\n\n\tl.WithField(\"img\", *imagePath).Info(\"image mounted successfully\")\n}\n"
  },
  {
    "path": "examples/homeassistant/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2\"\n\t\"github.com/bmc-toolbox/bmclib/v2/logging\"\n\t\"github.com/bmc-toolbox/bmclib/v2/providers/homeassistant\"\n)\n\nfunc main() {\n\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\tdefer cancel()\n\t// Start the test consumer\n\ttime.Sleep(100 * time.Millisecond)\n\n\tlog := logging.ZeroLogger(\"info\")\n\topts := []bmclib.Option{\n\t\tbmclib.WithLogger(log),\n\t\t//bmclib.WithPerProviderTimeout(5 * time.Second),\n\t\tbmclib.WithHomeAssistantOpt(homeassistant.Config{\n\t\t\tSwitchEntityID:             \"switch.shellypstripg4_98a3167b747c_switch_0\",\n\t\t\tPowerOperationDelaySeconds: 2,\n\t\t}),\n\t}\n\thost := \"http://some.homeassistant.instance:8123\"\n\tuser := \"notuseduser\"\n\tpass := \"ey.....hk\"\n\tc := bmclib.NewClient(host, user, pass, opts...)\n\tif err := c.Open(ctx); err != nil {\n\t\tpanic(err)\n\t}\n\tdefer c.Close(ctx)\n\n\tok3, err := c.SetBootDevice(ctx, \"pxe\", false, false)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tlog.Info(\"set boot device\", \"ok3\", ok3)\n\n\tstate, err := c.GetPowerState(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tlog.Info(\"power state\", \"state\", state)\n\tlog.Info(\"metadata for GetPowerState\", \"metadata\", c.GetMetadata())\n\n\tok, err := c.SetPowerState(ctx, \"on\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tlog.Info(\"set power state ON\", \"ok\", ok)\n\tlog.Info(\"metadata for SetPowerState ON\", \"metadata\", c.GetMetadata())\n\n\tok2, err := c.SetPowerState(ctx, \"off\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tlog.Info(\"set power state OFF\", \"ok2\", ok2)\n\tlog.Info(\"metadata for SetPowerState OFF\", \"metadata\", c.GetMetadata())\n\n\t<-ctx.Done()\n}\n"
  },
  {
    "path": "examples/install-firmware/doc.go",
    "content": "/*\ninstall-firmware is an example command that utilizes the 'v1' bmclib interface\nmethods to flash a firmware image to a BMC.\n\nNote: The example installs the firmware and polls until the status until the install is complete,\nand if required by the install process - power cycles the host.\n\n\t\t\t\t\t\t\t$ go run ./examples/v1/install-firmware/main.go -h\n\t\t\t\t\t\t\tUsage of /tmp/go-build2950657412/b001/exe/main:\n\t\t\t\t\t\t\t\t-cert-pool string\n\t\t\t\t\t\t\t\t\t\t\tPath to an file containing x509 CAs. An empty string uses the system CAs. Only takes effect when -secure-tls=true\n\t\t\t\t\t\t\t\t-firmware string\n\t\t\t\t\t\t\t\t\t\t\tThe local path of the firmware to install\n\t\t\t\t\t\t\t\t-host string\n\t\t\t\t\t\t\t\t\t\t\tBMC hostname to connect to\n\t\t\t\t\t\t\t\t-password string\n\t\t\t\t\t\t\t\t\t\t\tUsername to login with\n\t\t\t\t\t\t\t\t-port int\n\t\t\t\t\t\t\t\t\t\t\tBMC port to connect to (default 443)\n\t\t\t\t\t\t\t\t-secure-tls\n\t\t\t\t\t\t\t\t\t\t\tEnable secure TLS\n\t\t\t\t\t\t\t\t-user string\n\t\t\t\t\t\t\t\t\t\t\tUsername to login with\n\t\t\t\t\t\t\t\t-version string\n\t\t\t\t\t\t\t\t\t\t\tThe firmware version being installed\n\n\t\t\t\t\t\t   # install bios firmware on a supermicro X11\n\t\t\t\t\t\t   #\n\t\t\t\t\t\t   $ go run .  -host 192.168.1.1 -user ADMIN -password hunter2 -component bios -firmware BIOS_X11DPH-0981_20220208_3.6_STD.bin\n\t\t\t\t\t\t   INFO[0007] set firmware install mode                     component=BIOS ip=\"https://192.168.1.1\" model=X11DPH-T\n\t\t\t\t\t\t   INFO[0011] uploading firmware                            component=BIOS ip=\"https://192.168.1.1\" model=X11DPH-T\n\t\t\t\t\t\t   INFO[0091] verifying uploaded firmware                   component=BIOS ip=\"https://192.168.1.1\" model=X11DPH-T\n\t\t\t\t\t\t   INFO[0105] initiating firmware install                   component=BIOS ip=\"https://192.168.1.1\" model=X11DPH-T\n\t\t\t\t\t\t   INFO[0115] firmware install running                      component=bios state=running\n\t\t\t\t\t\t   INFO[0132] firmware install running                      component=bios state=running\n\t\t\t\t\t\t   ...\n\t\t\t\t\t\t   ...\n\t                       INFO[0628] firmware install running                      component=bios state=running\n\t                       INFO[0635] host powercycle required                      component=bios state=powercycle-host\n\t                       INFO[0637] host power cycled, all done!                  component=bios state=powercycle-host\n\n\n\n\t\t\t\t\t\t\t# install bmc firmware on a supermicro X11\n\t\t\t\t\t\t\t#\n\t\t\t\t\t\t\t$ go run .  -host 192.168.1.1 -user ADMIN -password hunter2 -component bmc -firmware BMC_X11AST2500-4101MS_20220225_01.74.02_STD.bin\n\t\t\t                INFO[0007] setting device to firmware install mode       component=BMC ip=\"https://192.168.1.1\"\n\t\t\t                INFO[0009] uploading firmware                            ip=\"https://192.168.1.1\"\n\t\t\t                INFO[0045] verifying uploaded firmware                   ip=\"https://192.168.1.1\"\n\t\t\t                INFO[0047] initiating firmware install                   ip=\"https://192.168.1.1\"\n\t\t\t                INFO[0079] firmware install running                      component=bmc state=running\n\t\t\t                INFO[0085] firmware install running                      component=bmc state=running\n\t\t\t                ...\n\t\t\t\t\t\t\t...\n\t\t\t\t\t\t\tINFO[0233] firmware install running                      component=bmc state=running\n\t\t                    INFO[0238] firmware install completed                    component=bmc state=complete\n*/\npackage main\n"
  },
  {
    "path": "examples/install-firmware/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"crypto/x509\"\n\t\"errors\"\n\t\"flag\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\tbmclib \"github.com/bmc-toolbox/bmclib/v2\"\n\t\"github.com/bmc-toolbox/bmclib/v2/constants\"\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/bombsimon/logrusr/v2\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc main() {\n\tuser := flag.String(\"user\", \"\", \"Username to login with\")\n\tpass := flag.String(\"password\", \"\", \"Username to login with\")\n\thost := flag.String(\"host\", \"\", \"BMC hostname to connect to\")\n\tcomponent := flag.String(\"component\", \"\", \"Component to be updated (bmc, bios.. etc)\")\n\twithSecureTLS := flag.Bool(\"secure-tls\", false, \"Enable secure TLS\")\n\tcertPoolPath := flag.String(\"cert-pool\", \"\", \"Path to an file containing x509 CAs. An empty string uses the system CAs. Only takes effect when --secure-tls=true\")\n\tfirmwarePath := flag.String(\"firmware\", \"\", \"The local path of the firmware to install\")\n\tfirmwareVersion := flag.String(\"version\", \"\", \"The firmware version being installed\")\n\n\tflag.Parse()\n\n\tctx, cancel := context.WithTimeout(context.Background(), 60*time.Minute)\n\tdefer cancel()\n\n\tl := logrus.New()\n\tl.Level = logrus.TraceLevel\n\tlogger := logrusr.New(l)\n\n\tif *host == \"\" || *user == \"\" || *pass == \"\" {\n\t\tl.Fatal(\"required host/user/pass parameters not defined\")\n\t}\n\n\tif *component == \"\" {\n\t\tl.Fatal(\"component parameter required (must be a component slug - bmc, bios etc)\")\n\t}\n\n\tclientOpts := []bmclib.Option{\n\t\tbmclib.WithLogger(logger),\n\t\tbmclib.WithPerProviderTimeout(time.Minute * 30),\n\t}\n\n\tif *withSecureTLS {\n\t\tvar pool *x509.CertPool\n\t\tif *certPoolPath != \"\" {\n\t\t\tpool = x509.NewCertPool()\n\t\t\tdata, err := ioutil.ReadFile(*certPoolPath)\n\t\t\tif err != nil {\n\t\t\t\tl.Fatal(err)\n\t\t\t}\n\t\t\tpool.AppendCertsFromPEM(data)\n\t\t}\n\t\t// a nil pool uses the system certs\n\t\tclientOpts = append(clientOpts, bmclib.WithSecureTLS(pool))\n\t}\n\n\tcl := bmclib.NewClient(*host, *user, *pass, clientOpts...)\n\terr := cl.Open(ctx)\n\tif err != nil {\n\t\tl.Fatal(err, \"bmc login failed\")\n\t}\n\n\tdefer cl.Close(ctx)\n\n\t// open file handle\n\tfh, err := os.Open(*firmwarePath)\n\tif err != nil {\n\t\tl.Fatal(err)\n\t}\n\tdefer fh.Close()\n\n\ttaskID, err := cl.FirmwareInstall(ctx, *component, string(constants.OnReset), true, fh)\n\tif err != nil {\n\t\tl.Fatal(err)\n\t}\n\n\tfor {\n\t\tif ctx.Err() != nil {\n\t\t\tl.Fatal(ctx.Err())\n\t\t}\n\n\t\tstate, err := cl.FirmwareInstallStatus(ctx, *firmwareVersion, *component, taskID)\n\t\tif err != nil {\n\t\t\t// when its under update a connection refused is returned\n\t\t\tif strings.Contains(err.Error(), \"connection refused\") || strings.Contains(err.Error(), \"operation timed out\") {\n\t\t\t\tl.Info(\"BMC refused connection, BMC most likely resetting...\")\n\t\t\t\ttime.Sleep(2 * time.Second)\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif errors.Is(err, bmclibErrs.ErrSessionExpired) || strings.Contains(err.Error(), \"session expired\") {\n\t\t\t\terr := cl.Open(ctx)\n\t\t\t\tif err != nil {\n\t\t\t\t\tl.Fatal(err, \"bmc re-login failed\")\n\t\t\t\t}\n\n\t\t\t\tl.WithFields(logrus.Fields{\"state\": state, \"component\": *component}).Info(\"BMC session expired, logging in...\")\n\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tlog.Fatal(err)\n\t\t}\n\n\t\tswitch state {\n\t\tcase constants.FirmwareInstallRunning, constants.FirmwareInstallInitializing:\n\t\t\tl.WithFields(logrus.Fields{\"state\": state, \"component\": *component}).Info(\"firmware install running\")\n\n\t\tcase constants.FirmwareInstallFailed:\n\t\t\tl.WithFields(logrus.Fields{\"state\": state, \"component\": *component}).Info(\"firmware install failed\")\n\t\t\tos.Exit(1)\n\n\t\tcase constants.FirmwareInstallComplete:\n\t\t\tl.WithFields(logrus.Fields{\"state\": state, \"component\": *component}).Info(\"firmware install completed\")\n\t\t\tos.Exit(0)\n\n\t\tcase constants.FirmwareInstallPowerCycleHost:\n\t\t\tl.WithFields(logrus.Fields{\"state\": state, \"component\": *component}).Info(\"host powercycle required\")\n\n\t\t\tif _, err := cl.SetPowerState(ctx, \"cycle\"); err != nil {\n\t\t\t\tl.WithFields(logrus.Fields{\"state\": state, \"component\": *component}).Info(\"error power cycling host for install\")\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\n\t\t\tl.WithFields(logrus.Fields{\"state\": state, \"component\": *component}).Info(\"host power cycled, all done!\")\n\t\t\tos.Exit(0)\n\t\tdefault:\n\t\t\tl.WithFields(logrus.Fields{\"state\": state, \"component\": *component}).Info(\"unknown state returned\")\n\t\t}\n\n\t\ttime.Sleep(2 * time.Second)\n\t}\n}\n"
  },
  {
    "path": "examples/inventory/doc.go",
    "content": "/*\ninventory is an example commmand that utilizes the 'v1' bmclib interface\nmethods to gather inventory from a BMC using the redfish driver.\n\n\t$ go run ./examples/v1/inventory/main.go -h\n\tUsage of /tmp/go-build1853609647/b001/exe/main:\n\t\t-cert-pool string\n\t\t\t\t\tPath to an file containing x509 CAs. An empty string uses the system CAs. Only takes effect when --secure-tls=true\n\t\t-host string\n\t\t\t\t\tBMC hostname to connect to\n\t\t-password string\n\t\t\t\t\tUsername to login with\n\t\t-port int\n\t\t\t\t\tBMC port to connect to (default 443)\n\t\t-secure-tls\n\t\t\t\t\tEnable secure TLS\n\t\t-user string\n\t\t\t\t\tUsername to login with\n*/\npackage main\n"
  },
  {
    "path": "examples/inventory/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"crypto/x509\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\tbmclib \"github.com/bmc-toolbox/bmclib/v2\"\n\t\"github.com/bombsimon/logrusr/v2\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc main() {\n\tctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)\n\tdefer cancel()\n\n\tuser := flag.String(\"user\", \"\", \"Username to login with\")\n\tpass := flag.String(\"password\", \"\", \"Username to login with\")\n\thost := flag.String(\"host\", \"\", \"BMC hostname to connect to\")\n\tincompatibleRedfishVersions := flag.String(\"incompatible-redfish-versions\", \"\", \"Comma separated list of redfish versions to deem incompatible\")\n\twithSecureTLS := flag.Bool(\"secure-tls\", false, \"Enable secure TLS\")\n\tcertPoolFile := flag.String(\"cert-pool\", \"\", \"Path to an file containing x509 CAs. An empty string uses the system CAs. Only takes effect when --secure-tls=true\")\n\tflag.Parse()\n\n\tl := logrus.New()\n\tl.Level = logrus.DebugLevel\n\tlogger := logrusr.New(l)\n\n\tclientOpts := []bmclib.Option{bmclib.WithLogger(logger)}\n\n\tif *withSecureTLS {\n\t\tvar pool *x509.CertPool\n\t\tif *certPoolFile != \"\" {\n\t\t\tpool = x509.NewCertPool()\n\t\t\tdata, err := os.ReadFile(*certPoolFile)\n\t\t\tif err != nil {\n\t\t\t\tl.Fatal(err)\n\t\t\t}\n\t\t\tpool.AppendCertsFromPEM(data)\n\t\t}\n\t\t// a nil pool uses the system certs\n\t\tclientOpts = append(clientOpts, bmclib.WithSecureTLS(pool))\n\t}\n\n\tif len(*incompatibleRedfishVersions) > 0 {\n\t\t// blacklist a redfish version\n\t\tclientOpts = append(\n\t\t\tclientOpts,\n\t\t\tbmclib.WithRedfishVersionsNotCompatible(strings.Split(*incompatibleRedfishVersions, \",\")))\n\t}\n\n\tcl := bmclib.NewClient(*host, *user, *pass, clientOpts...)\n\n\tcl.Registry.Drivers = cl.Registry.FilterForCompatible(ctx)\n\n\terr := cl.Open(ctx)\n\tif err != nil {\n\t\tlog.Fatal(err, \"bmc login failed\")\n\t}\n\n\tdefer cl.Close(ctx)\n\n\tinv, err := cl.Inventory(ctx)\n\tif err != nil {\n\t\tl.Error(err)\n\t}\n\n\tb, err := json.MarshalIndent(inv, \"\", \"  \")\n\tif err != nil {\n\t\tl.Error(err)\n\t}\n\n\tfmt.Println(string(b))\n}\n"
  },
  {
    "path": "examples/inventory/output.json",
    "content": "{\n  \"oem\": false,\n  \"vendor\": \"Dell Inc.\",\n  \"model\": \"PowerEdge R6515\",\n  \"serial\": \"FOOBAR\",\n  \"bios\": {\n    \"description\": \"BIOS Configuration Current Settings\",\n    \"firmware\": {\n      \"installed\": \"2.2.4\",\n      \"software_id\": \"159\",\n      \"previous\": [\n        {\n          \"installed\": \"1.3.1\",\n          \"software_id\": \"159\"\n        }\n      ],\n      \"metadata\": {\n        \"name\": \"BIOS\"\n      }\n    }\n  },\n  \"bmc\": {\n    \"id\": \"iDRAC.Embedded.1\",\n    \"description\": \"BMC\",\n    \"vendor\": \"Dell Inc.\",\n    \"model\": \"PowerEdge R6515\",\n    \"status\": {\n      \"Health\": \"OK\",\n      \"State\": \"Enabled\"\n    },\n    \"firmware\": {\n      \"installed\": \"5.00.00.00\",\n      \"software_id\": \"25227\",\n      \"previous\": [\n        {\n          \"installed\": \"4.10.10.10\",\n          \"software_id\": \"25227\"\n        }\n      ],\n      \"metadata\": {\n        \"name\": \"Integrated Dell Remote Access Controller\"\n      }\n    }\n  },\n  \"mainboard\": {},\n  \"cplds\": [\n    {\n      \"description\": \"System CPLD\",\n      \"vendor\": \"Dell Inc.\",\n      \"model\": \"PowerEdge R6515\",\n      \"firmware\": {\n        \"installed\": \"1.0.3\",\n        \"software_id\": \"27763\",\n        \"metadata\": {\n          \"name\": \"System CPLD\"\n        }\n      }\n    }\n  ],\n  \"tpms\": [\n    {\n      \"interface_type\": \"TPM2_0\",\n      \"firmware\": {\n        \"installed\": \"1.3.1.0\",\n        \"software_id\": \"109673\",\n        \"metadata\": {\n          \"name\": \"TPM\"\n        }\n      },\n      \"status\": {\n        \"Health\": \"\",\n        \"State\": \"Enabled\"\n      }\n    }\n  ],\n  \"cpus\": [\n    {\n      \"id\": \"CPU.Socket.1\",\n      \"description\": \"Represents the properties of a Processor attached to this System\",\n      \"vendor\": \"AMD\",\n      \"model\": \"AMD EPYC 7402P 24-Core Processor\",\n      \"slot\": \"CPU.Socket.1\",\n      \"architecture\": \"x86\",\n      \"clock_speeed_hz\": 3900,\n      \"cores\": 24,\n      \"threads\": 48,\n      \"status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n      },\n      \"firmware\": {\n        \"installed\": \"0x830104D\"\n      }\n    }\n  ],\n  \"memory\": [\n    {\n      \"description\": \"DIMM A7\",\n      \"slot\": \"DIMM.Socket.A7\",\n      \"type\": \"DRAM\",\n      \"vendor\": \"Hynix Semiconductor\",\n      \"serial\": \"537385EF\",\n      \"size_bytes\": 8192,\n      \"part_number\": \"HMA81GR7CJR8N-XN\",\n      \"clock_speed_hz\": 3200,\n      \"status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n      }\n    },\n    {\n      \"description\": \"DIMM A6\",\n      \"slot\": \"DIMM.Socket.A6\",\n      \"type\": \"DRAM\",\n      \"vendor\": \"Hynix Semiconductor\",\n      \"serial\": \"53738538\",\n      \"size_bytes\": 8192,\n      \"part_number\": \"HMA81GR7CJR8N-XN\",\n      \"clock_speed_hz\": 3200,\n      \"status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n      }\n    },\n    {\n      \"description\": \"DIMM A3\",\n      \"slot\": \"DIMM.Socket.A3\",\n      \"type\": \"DRAM\",\n      \"vendor\": \"Hynix Semiconductor\",\n      \"serial\": \"53738536\",\n      \"size_bytes\": 8192,\n      \"part_number\": \"HMA81GR7CJR8N-XN\",\n      \"clock_speed_hz\": 3200,\n      \"status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n      }\n    },\n    {\n      \"description\": \"DIMM A8\",\n      \"slot\": \"DIMM.Socket.A8\",\n      \"type\": \"DRAM\",\n      \"vendor\": \"Hynix Semiconductor\",\n      \"serial\": \"5373856E\",\n      \"size_bytes\": 8192,\n      \"part_number\": \"HMA81GR7CJR8N-XN\",\n      \"clock_speed_hz\": 3200,\n      \"status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n      }\n    },\n    {\n      \"description\": \"DIMM A2\",\n      \"slot\": \"DIMM.Socket.A2\",\n      \"type\": \"DRAM\",\n      \"vendor\": \"Hynix Semiconductor\",\n      \"serial\": \"5373858E\",\n      \"size_bytes\": 8192,\n      \"part_number\": \"HMA81GR7CJR8N-XN\",\n      \"clock_speed_hz\": 3200,\n      \"status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n      }\n    },\n    {\n      \"description\": \"DIMM A4\",\n      \"slot\": \"DIMM.Socket.A4\",\n      \"type\": \"DRAM\",\n      \"vendor\": \"Hynix Semiconductor\",\n      \"serial\": \"537385CA\",\n      \"size_bytes\": 8192,\n      \"part_number\": \"HMA81GR7CJR8N-XN\",\n      \"clock_speed_hz\": 3200,\n      \"status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n      }\n    },\n    {\n      \"description\": \"DIMM A5\",\n      \"slot\": \"DIMM.Socket.A5\",\n      \"type\": \"DRAM\",\n      \"vendor\": \"Hynix Semiconductor\",\n      \"serial\": \"53738537\",\n      \"size_bytes\": 8192,\n      \"part_number\": \"HMA81GR7CJR8N-XN\",\n      \"clock_speed_hz\": 3200,\n      \"status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n      }\n    },\n    {\n      \"description\": \"DIMM A1\",\n      \"slot\": \"DIMM.Socket.A1\",\n      \"type\": \"DRAM\",\n      \"vendor\": \"Hynix Semiconductor\",\n      \"serial\": \"53738568\",\n      \"size_bytes\": 8192,\n      \"part_number\": \"HMA81GR7CJR8N-XN\",\n      \"clock_speed_hz\": 3200,\n      \"status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n      }\n    }\n  ],\n  \"nics\": [\n    {\n      \"id\": \"NIC.Embedded.1\",\n      \"description\": \"Embedded NIC 1 Port 1 Partition 1\",\n      \"speed_bits\": 6,\n      \"oem\": false,\n      \"metadata\": null,\n      \"status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n      },\n      \"firmware\": {\n        \"installed\": \"1.5.12\"\n      }\n    },\n    {\n      \"id\": \"NIC.Slot.3\",\n      \"description\": \"NIC in Slot 3 Port 2 Partition 1\",\n      \"vendor\": \"Intel Corporation\",\n      \"model\": \"Intel(R) 10GbE 2P X710 Adapter\",\n      \"serial\": \"MYFLMIT06404GD\",\n      \"speed_bits\": 100006,\n      \"macaddress\": \"F8:F2:1E:A6:89:A1\",\n      \"oem\": false,\n      \"metadata\": null,\n      \"status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n      },\n      \"firmware\": {\n        \"installed\": \"20.0.17\",\n        \"software_id\": \"102302\",\n        \"previous\": [\n          {\n            \"installed\": \"19.5.12\",\n            \"software_id\": \"102301\"\n          },\n          {\n            \"installed\": \"19.5.12\",\n            \"software_id\": \"102302\"\n          }\n        ],\n        \"metadata\": {\n          \"name\": \"Intel(R) Ethernet Converged Network Adapter X710 - F8:F2:1E:A6:89:A1\"\n        }\n      }\n    }\n  ],\n  \"drives\": [\n    {\n      \"id\": \"Disk.Bay.1:Enclosure.Internal.0-1:NonRAID.Integrated.1-1\",\n      \"name\": \"MZ7LH480HBHQ0D3\",\n      \"drive_type\": \"SSD\",\n      \"description\": \"Disk 1 in Backplane 1 of Integrated Storage Controller 1\",\n      \"serial\": \"S5YJNE0N503923\",\n      \"storage_controller\": \"NonRAID.Integrated.1-1\",\n      \"vendor\": \"SAMSUNG\",\n      \"model\": \"MZ7LH480HBHQ0D3\",\n      \"protocol\": \"SATA\",\n      \"capacity_bytes\": 480103980544,\n      \"block_size_bytes\": 512,\n      \"capable_speed_gbps\": 6,\n      \"negotiated_speed_gbps\": 6,\n      \"firmware\": {\n        \"installed\": \"HG58\",\n        \"software_id\": \"108622\",\n        \"metadata\": {\n          \"name\": \"Disk 1 in Backplane 1 of Integrated Storage Controller 1\"\n        }\n      },\n      \"status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n      }\n    },\n    {\n      \"id\": \"Disk.Bay.0:Enclosure.Internal.0-1:NonRAID.Integrated.1-1\",\n      \"name\": \"MZ7LH480HBHQ0D3\",\n      \"drive_type\": \"SSD\",\n      \"description\": \"Disk 0 in Backplane 1 of Integrated Storage Controller 1\",\n      \"serial\": \"S5YJNE0N503924\",\n      \"storage_controller\": \"NonRAID.Integrated.1-1\",\n      \"vendor\": \"SAMSUNG\",\n      \"model\": \"MZ7LH480HBHQ0D3\",\n      \"protocol\": \"SATA\",\n      \"capacity_bytes\": 480103980544,\n      \"block_size_bytes\": 512,\n      \"capable_speed_gbps\": 6,\n      \"negotiated_speed_gbps\": 6,\n      \"firmware\": {\n        \"installed\": \"HG58\",\n        \"software_id\": \"108622\",\n        \"metadata\": {\n          \"name\": \"Disk 0 in Backplane 1 of Integrated Storage Controller 1\"\n        }\n      },\n      \"status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n      }\n    },\n    {\n      \"id\": \"Disk.Direct.0-0:AHCI.Slot.2-1\",\n      \"name\": \"SSDSCKKB240G8R\",\n      \"drive_type\": \"SSD\",\n      \"description\": \"Disk 0 on AHCI Controller in slot 2\",\n      \"serial\": \"PHYH011400V5240J\",\n      \"storage_controller\": \"AHCI.Slot.2-1\",\n      \"vendor\": \"INTEL\",\n      \"model\": \"SSDSCKKB240G8R\",\n      \"protocol\": \"SATA\",\n      \"capacity_bytes\": 240057409536,\n      \"block_size_bytes\": 512,\n      \"capable_speed_gbps\": 6,\n      \"negotiated_speed_gbps\": 6,\n      \"firmware\": {\n        \"installed\": \"DL6R\",\n        \"software_id\": \"108313\",\n        \"metadata\": {\n          \"name\": \"Disk 0 on AHCI Controller in slot 2\"\n        }\n      },\n      \"status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n      }\n    },\n    {\n      \"id\": \"Disk.Direct.1-1:AHCI.Slot.2-1\",\n      \"name\": \"SSDSCKKB240G8R\",\n      \"drive_type\": \"SSD\",\n      \"description\": \"Disk 1 on AHCI Controller in slot 2\",\n      \"serial\": \"PHYH011303RM240J\",\n      \"storage_controller\": \"AHCI.Slot.2-1\",\n      \"vendor\": \"INTEL\",\n      \"model\": \"SSDSCKKB240G8R\",\n      \"protocol\": \"SATA\",\n      \"capacity_bytes\": 240057409536,\n      \"block_size_bytes\": 512,\n      \"capable_speed_gbps\": 6,\n      \"negotiated_speed_gbps\": 6,\n      \"firmware\": {\n        \"installed\": \"DL6R\",\n        \"software_id\": \"108313\",\n        \"metadata\": {\n          \"name\": \"Disk 1 on AHCI Controller in slot 2\"\n        }\n      },\n      \"status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n      }\n    }\n  ],\n  \"storage_controller\": [\n    {\n      \"description\": \"Dell HBA330 Mini\",\n      \"vendor\": \"DELL\",\n      \"speed_gbps\": 12,\n      \"oem\": false,\n      \"status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n      },\n      \"metadata\": null,\n      \"firmware\": {\n        \"installed\": \"16.17.01.00\",\n        \"software_id\": \"104298\",\n        \"metadata\": {\n          \"name\": \"Dell HBA330 Mini\"\n        }\n      }\n    },\n    {\n      \"description\": \"BOSS-S1\",\n      \"vendor\": \"DELL\",\n      \"speed_gbps\": 6,\n      \"oem\": false,\n      \"status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n      },\n      \"metadata\": null,\n      \"firmware\": {\n        \"installed\": \"2.5.13.3024\",\n        \"software_id\": \"106883\",\n        \"metadata\": {\n          \"name\": \"BOSS-S1\"\n        }\n      }\n    }\n  ],\n  \"power_supplies\": [\n    {\n      \"description\": \"PS1 Status\",\n      \"vendor\": \"DELL\",\n      \"model\": \"PWR SPLY,550W,RDNT,DELTA\",\n      \"serial\": \"CNDED000330FBL\",\n      \"power_capacity_watts\": 550,\n      \"oem\": false,\n      \"status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n      },\n      \"firmware\": {\n        \"installed\": \"00.0C.7D\"\n      }\n    },\n    {\n      \"description\": \"PS2 Status\",\n      \"vendor\": \"DELL\",\n      \"model\": \"PWR SPLY,550W,RDNT,DELTA\",\n      \"serial\": \"CNDED000330FFI\",\n      \"power_capacity_watts\": 550,\n      \"oem\": false,\n      \"status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n      },\n      \"firmware\": {\n        \"installed\": \"00.0C.7D\"\n      }\n    }\n  ],\n  \"enclosures\": [\n    {\n      \"id\": \"System.Embedded.1\",\n      \"description\": \"It represents the properties for physical components for any system.It represent racks, rackmount servers, blades, standalone, modular systems,enclosures, and all other containers.The non-cpu/device centric parts of the schema are all accessed either directly or indirectly through this resource.\",\n      \"chassis_type\": \"RackMount\",\n      \"vendor\": \"Dell Inc.\",\n      \"model\": \"PowerEdge R6515\",\n      \"firmware\": {},\n      \"status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n      }\n    },\n    {\n      \"id\": \"Enclosure.Internal.0-1:NonRAID.Integrated.1-1\",\n      \"description\": \"Backplane 1 on Connector 0 of Integrated Storage Controller 1\",\n      \"chassis_type\": \"StorageEnclosure\",\n      \"model\": \"BP14G+ 0:1\",\n      \"firmware\": {\n        \"installed\": \"HG58\",\n        \"software_id\": \"108622\",\n        \"metadata\": {\n          \"name\": \"Disk 1 in Backplane 1 of Integrated Storage Controller 1\"\n        }\n      },\n      \"status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n      }\n    },\n    {\n      \"id\": \"Enclosure.Internal.0-1\",\n      \"description\": \"PCIe SSD Backplane 1\",\n      \"chassis_type\": \"StorageEnclosure\",\n      \"model\": \"PCIe SSD Backplane 1\",\n      \"firmware\": {\n        \"installed\": \"HG58\",\n        \"software_id\": \"108622\",\n        \"metadata\": {\n          \"name\": \"Disk 1 in Backplane 1 of Integrated Storage Controller 1\"\n        }\n      },\n      \"status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n      }\n    }\n  ],\n  \"status\": {\n    \"Health\": \"\",\n    \"State\": \"\"\n  }\n}"
  },
  {
    "path": "examples/reset_bmc/reset_bmc.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2\"\n\t\"github.com/bombsimon/logrusr/v2\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc main() {\n\tctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)\n\tdefer cancel()\n\n\t// set BMC parameters here\n\thost := \"10.211.132.157\"\n\tuser := \"root\"\n\tpass := \"yxvZdxAQ38ZWlZ\"\n\n\tl := logrus.New()\n\tl.Level = logrus.DebugLevel\n\tlogger := logrusr.New(l)\n\n\tif host == \"\" || user == \"\" || pass == \"\" {\n\t\tlog.Fatal(\"required host/user/pass parameters not defined\")\n\t}\n\n\tos.Setenv(\"DEBUG_BMCLIB\", \"true\")\n\tdefer os.Unsetenv(\"DEBUG_BMCLIB\")\n\n\tcl := bmclib.NewClient(host, user, pass, bmclib.WithLogger(logger))\n\n\terr := cl.Open(ctx)\n\tif err != nil {\n\t\tlog.Fatal(err, \"bmc login failed\")\n\t}\n\n\tdefer cl.Close(ctx)\n\n\t_, err = cl.ResetBMC(ctx, \"GracefulRestart\")\n\tif err != nil {\n\t\tl.Error(err)\n\t}\n\n}\n"
  },
  {
    "path": "examples/rpc/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2\"\n\t\"github.com/bmc-toolbox/bmclib/v2/logging\"\n\t\"github.com/bmc-toolbox/bmclib/v2/providers/rpc\"\n)\n\nfunc main() {\n\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\tdefer cancel()\n\t// Start the test consumer\n\tgo testConsumer(ctx)\n\ttime.Sleep(100 * time.Millisecond)\n\n\tlog := logging.ZeroLogger(\"info\")\n\topts := []bmclib.Option{\n\t\tbmclib.WithLogger(log),\n\t\tbmclib.WithPerProviderTimeout(5 * time.Second),\n\t\tbmclib.WithRPCOpt(rpc.Provider{\n\t\t\tConsumerURL: \"http://localhost:8800\",\n\t\t\t// Opts are not required.\n\t\t\tOpts: rpc.Opts{\n\t\t\t\tHMAC: rpc.HMACOpts{\n\t\t\t\t\tSecrets: rpc.Secrets{rpc.SHA256: {\"superSecret1\"}},\n\t\t\t\t},\n\t\t\t\tSignature: rpc.SignatureOpts{\n\t\t\t\t\tHeaderName:             \"X-Bespoke-Signature\",\n\t\t\t\t\tIncludedPayloadHeaders: []string{\"X-Bespoke-Timestamp\"},\n\t\t\t\t},\n\t\t\t\tRequest: rpc.RequestOpts{\n\t\t\t\t\tTimestampHeader: \"X-Bespoke-Timestamp\",\n\t\t\t\t},\n\t\t\t},\n\t\t}),\n\t}\n\thost := \"127.0.1.1\"\n\tuser := \"admin\"\n\tpass := \"admin\"\n\tc := bmclib.NewClient(host, user, pass, opts...)\n\tif err := c.Open(ctx); err != nil {\n\t\tpanic(err)\n\t}\n\tdefer c.Close(ctx)\n\n\tstate, err := c.GetPowerState(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tlog.Info(\"power state\", \"state\", state)\n\tlog.Info(\"metadata for GetPowerState\", \"metadata\", c.GetMetadata())\n\n\tok, err := c.SetPowerState(ctx, \"on\")\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tlog.Info(\"set power state\", \"ok\", ok)\n\tlog.Info(\"metadata for SetPowerState\", \"metadata\", c.GetMetadata())\n\n\t<-ctx.Done()\n}\n\nfunc testConsumer(ctx context.Context) error {\n\thttp.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\treq := rpc.RequestPayload{}\n\t\tif err := json.NewDecoder(r.Body).Decode(&req); err != nil {\n\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t\trp := rpc.ResponsePayload{\n\t\t\tID:   req.ID,\n\t\t\tHost: req.Host,\n\t\t}\n\t\tswitch req.Method {\n\t\tcase rpc.PowerGetMethod:\n\t\t\trp.Result = \"on\"\n\t\tcase rpc.PowerSetMethod:\n\n\t\tcase rpc.BootDeviceMethod:\n\n\t\tcase rpc.PingMethod:\n\t\t\trp.Result = \"pong\"\n\t\tdefault:\n\t\t\tw.WriteHeader(http.StatusNotFound)\n\t\t}\n\t\tb, _ := json.Marshal(rp)\n\t\tw.Write(b)\n\t})\n\n\treturn http.ListenAndServe(\":8800\", nil)\n}\n"
  },
  {
    "path": "examples/screenshot/doc.go",
    "content": "/*\nstatus is an example commmand that utilizes the 'v1' bmclib interface methods\nto capture a screenshot.\n\n\t$ go run ./examples/v1/status/main.go -h\n\tUsage of /tmp/go-build1941100323/b001/exe/main:\n\t\t-cert-pool string\n\t\t\t\t\tPath to an file containing x509 CAs. An empty string uses the system CAs. Only takes effect when --secure-tls=true\n\t\t-host string\n\t\t\t\t\tBMC hostname to connect to\n\t\t-password string\n\t\t\t\t\tUsername to login with\n\t\t-port int\n\t\t\t\t\tBMC port to connect to (default 443)\n\t\t-secure-tls\n\t\t\t\t\tEnable secure TLS\n\t\t-user string\n\t\t\t\t\tUsername to login with\n*/\npackage main\n"
  },
  {
    "path": "examples/screenshot/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"crypto/x509\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2\"\n\t\"github.com/bmc-toolbox/bmclib/v2/providers\"\n\t\"github.com/bombsimon/logrusr/v2\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc main() {\n\tuser := flag.String(\"user\", \"\", \"Username to login with\")\n\tpass := flag.String(\"password\", \"\", \"Username to login with\")\n\thost := flag.String(\"host\", \"\", \"BMC hostname to connect to\")\n\tport := flag.String(\"port\", \"443\", \"BMC port to connect to\")\n\twithSecureTLS := flag.Bool(\"secure-tls\", false, \"Enable secure TLS\")\n\tcertPoolFile := flag.String(\"cert-pool\", \"\", \"Path to an file containing x509 CAs. An empty string uses the system CAs. Only takes effect when --secure-tls=true\")\n\tflag.Parse()\n\n\tl := logrus.New()\n\tl.Level = logrus.DebugLevel\n\tlogger := logrusr.New(l)\n\n\tif *host == \"\" || *user == \"\" || *pass == \"\" {\n\t\tl.Fatal(\"required host/user/pass parameters not defined\")\n\t}\n\n\tclientOpts := []bmclib.Option{\n\t\tbmclib.WithLogger(logger),\n\t\tbmclib.WithRedfishPort(*port),\n\t}\n\n\tif *withSecureTLS {\n\t\tvar pool *x509.CertPool\n\t\tif *certPoolFile != \"\" {\n\t\t\tpool = x509.NewCertPool()\n\t\t\tdata, err := ioutil.ReadFile(*certPoolFile)\n\t\t\tif err != nil {\n\t\t\t\tl.Fatal(err)\n\t\t\t}\n\t\t\tpool.AppendCertsFromPEM(data)\n\t\t}\n\t\t// a nil pool uses the system certs\n\t\tclientOpts = append(clientOpts, bmclib.WithSecureTLS(pool))\n\t}\n\n\tcl := bmclib.NewClient(*host, *user, *pass, clientOpts...)\n\tcl.Registry.Drivers = cl.Registry.Supports(providers.FeatureScreenshot)\n\n\tctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)\n\tdefer cancel()\n\n\terr := cl.Open(ctx)\n\tif err != nil {\n\t\tl.WithError(err).Fatal(err, \"BMC login failed\")\n\n\t\treturn\n\t}\n\tdefer cl.Close(ctx)\n\n\timage, fileType, err := cl.Screenshot(ctx)\n\tif err != nil {\n\t\tl.WithError(err).Error()\n\n\t\treturn\n\t}\n\n\tfilename := fmt.Sprintf(\"screenshot.\" + fileType)\n\tfh, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0600)\n\tif err != nil {\n\t\tl.WithError(err).Error()\n\n\t\treturn\n\t}\n\n\tdefer fh.Close()\n\n\t_, err = fh.Write(image)\n\tif err != nil {\n\t\tl.WithError(err).Error()\n\n\t\treturn\n\t}\n\n\tl.Info(\"screenshot saved as: \" + filename)\n}\n"
  },
  {
    "path": "examples/sel/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"crypto/x509\"\n\t\"flag\"\n\t\"io/ioutil\"\n\t\"time\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2\"\n\t\"github.com/bmc-toolbox/bmclib/v2/providers\"\n\t\"github.com/bombsimon/logrusr/v2\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc main() {\n\tuser := flag.String(\"user\", \"\", \"Username to login with\")\n\tpass := flag.String(\"password\", \"\", \"Username to login with\")\n\thost := flag.String(\"host\", \"\", \"BMC hostname to connect to\")\n\twithSecureTLS := flag.Bool(\"secure-tls\", false, \"Enable secure TLS\")\n\tcertPoolFile := flag.String(\"cert-pool\", \"\", \"Path to an file containing x509 CAs. An empty string uses the system CAs. Only takes effect when --secure-tls=true\")\n\taction := flag.String(\"action\", \"get\", \"Action to perform on the System Event Log (clear|get)\")\n\tflag.Parse()\n\n\tl := logrus.New()\n\tl.Level = logrus.DebugLevel\n\tlogger := logrusr.New(l)\n\n\tif *host == \"\" || *user == \"\" || *pass == \"\" {\n\t\tl.Fatal(\"required host/user/pass parameters not defined\")\n\t}\n\n\tclientOpts := []bmclib.Option{\n\t\tbmclib.WithLogger(logger),\n\t\tbmclib.WithRedfishUseBasicAuth(true),\n\t}\n\n\tif *withSecureTLS {\n\t\tvar pool *x509.CertPool\n\t\tif *certPoolFile != \"\" {\n\t\t\tpool = x509.NewCertPool()\n\t\t\tdata, err := ioutil.ReadFile(*certPoolFile)\n\t\t\tif err != nil {\n\t\t\t\tl.Fatal(err)\n\t\t\t}\n\t\t\tpool.AppendCertsFromPEM(data)\n\t\t}\n\t\t// a nil pool uses the system certs\n\t\tclientOpts = append(clientOpts, bmclib.WithSecureTLS(pool))\n\t}\n\n\tcl := bmclib.NewClient(*host, *user, *pass, clientOpts...)\n\tcl.Registry.Drivers = cl.Registry.Supports(providers.FeatureClearSystemEventLog)\n\n\tctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)\n\tdefer cancel()\n\n\terr := cl.Open(ctx)\n\tif err != nil {\n\t\tl.WithError(err).Fatal(err, \"BMC login failed\")\n\t}\n\tdefer cl.Close(ctx)\n\n\tswitch *action {\n\tcase \"get\":\n\t\tentries, err := cl.GetSystemEventLog(ctx)\n\t\tif err != nil {\n\t\t\tl.WithError(err).Fatal(err, \"failed to get System Event Log\")\n\t\t}\n\t\tl.Info(\"System Event Log entries\", \"entries\", entries)\n\t\treturn\n\tcase \"get-raw\":\n\t\teventlog, err := cl.GetSystemEventLogRaw(ctx)\n\t\tif err != nil {\n\t\t\tl.WithError(err).Fatal(err, \"failed to get System Event Log Raw\")\n\t\t}\n\t\tl.Info(\"System Event Log\", \"eventlog\", eventlog)\n\t\treturn\n\tcase \"clear\":\n\t\terr = cl.ClearSystemEventLog(ctx)\n\t\tif err != nil {\n\t\t\tl.WithError(err).Fatal(err, \"failed to clear System Event Log\")\n\t\t}\n\t\tl.Info(\"System Event Log cleared\")\n\t\treturn\n\tdefault:\n\t\tl.Fatal(\"invalid action\")\n\t}\n}\n"
  },
  {
    "path": "examples/status/doc.go",
    "content": "/*\nstatus is an example commmand that utilizes the 'v1' bmclib interface methods\nto gather the BMC version, power state, and bios version from a BMC using the\nredfish driver.\n\n\t$ go run ./examples/v1/status/main.go -h\n\tUsage of /tmp/go-build1941100323/b001/exe/main:\n\t\t-cert-pool string\n\t\t\t\t\tPath to an file containing x509 CAs. An empty string uses the system CAs. Only takes effect when --secure-tls=true\n\t\t-host string\n\t\t\t\t\tBMC hostname to connect to\n\t\t-password string\n\t\t\t\t\tUsername to login with\n\t\t-port int\n\t\t\t\t\tBMC port to connect to (default 443)\n\t\t-secure-tls\n\t\t\t\t\tEnable secure TLS\n\t\t-user string\n\t\t\t\t\tUsername to login with\n*/\npackage main\n"
  },
  {
    "path": "examples/status/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"crypto/x509\"\n\t\"flag\"\n\t\"io/ioutil\"\n\t\"time\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2\"\n\t\"github.com/bombsimon/logrusr/v2\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nfunc main() {\n\tuser := flag.String(\"user\", \"\", \"Username to login with\")\n\tpass := flag.String(\"password\", \"\", \"Username to login with\")\n\thost := flag.String(\"host\", \"\", \"BMC hostname to connect to\")\n\twithSecureTLS := flag.Bool(\"secure-tls\", false, \"Enable secure TLS\")\n\tcertPoolFile := flag.String(\"cert-pool\", \"\", \"Path to an file containing x509 CAs. An empty string uses the system CAs. Only takes effect when --secure-tls=true\")\n\tflag.Parse()\n\n\tl := logrus.New()\n\tl.Level = logrus.DebugLevel\n\tlogger := logrusr.New(l)\n\n\tif *host == \"\" || *user == \"\" || *pass == \"\" {\n\t\tl.Fatal(\"required host/user/pass parameters not defined\")\n\t}\n\n\tclientOpts := []bmclib.Option{\n\t\tbmclib.WithLogger(logger),\n\t\tbmclib.WithRedfishUseBasicAuth(true),\n\t}\n\n\tif *withSecureTLS {\n\t\tvar pool *x509.CertPool\n\t\tif *certPoolFile != \"\" {\n\t\t\tpool = x509.NewCertPool()\n\t\t\tdata, err := ioutil.ReadFile(*certPoolFile)\n\t\t\tif err != nil {\n\t\t\t\tl.Fatal(err)\n\t\t\t}\n\t\t\tpool.AppendCertsFromPEM(data)\n\t\t}\n\t\t// a nil pool uses the system certs\n\t\tclientOpts = append(clientOpts, bmclib.WithSecureTLS(pool))\n\t}\n\n\tcl := bmclib.NewClient(*host, *user, *pass, clientOpts...)\n\tcl.Registry.Drivers = cl.Registry.Using(\"redfish\")\n\n\tctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)\n\tdefer cancel()\n\n\terr := cl.Open(ctx)\n\tif err != nil {\n\t\tl.WithError(err).Fatal(err, \"BMC login failed\")\n\t}\n\tdefer cl.Close(ctx)\n\n\tstate, err := cl.GetPowerState(ctx)\n\tif err != nil {\n\t\tl.WithError(err).Error()\n\t}\n\tl.WithField(\"power-state\", state).Info()\n}\n"
  },
  {
    "path": "examples/virtualmedia/doc.go",
    "content": "/*\nVirtual Media is an example command to mount and umount virtual media (ISO) on a BMC.\n\n\t# mount an ISO\n\t$ go run examples/virtualmedia/main.go \\\n\t\t-host 10.1.2.3 \\\n\t\t-user root \\\n\t\t-password calvin \\\n\t\t-iso http://example.com/image.iso\n\n\t# unmount an ISO\n\t$ go run examples/virtualmedia/main.go \\\n\t\t-host 10.1.2.3 \\\n\t\t-user root \\\n\t\t-password calvin \\\n\t\t-iso \"\"\n*/\npackage main\n"
  },
  {
    "path": "examples/virtualmedia/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2\"\n\t\"github.com/go-logr/logr\"\n)\n\nfunc main() {\n\n\tctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)\n\tdefer cancel()\n\n\tuser := flag.String(\"user\", \"\", \"BMC username, required\")\n\tpass := flag.String(\"password\", \"\", \"BMC password, required\")\n\thost := flag.String(\"host\", \"\", \"BMC hostname or IP address, required\")\n\tisoURL := flag.String(\"iso\", \"\", \"The HTTP URL to the ISO to be mounted, leave empty to unmount\")\n\tflag.Parse()\n\n\tif *user == \"\" || *pass == \"\" || *host == \"\" {\n\t\tfmt.Fprintln(os.Stderr, \"user, password, and host are required\")\n\t\tflag.PrintDefaults()\n\t\tos.Exit(1)\n\t}\n\n\tl := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{AddSource: true}))\n\tlog := logr.FromSlogHandler(l.Handler())\n\n\tcl := bmclib.NewClient(*host, *user, *pass, bmclib.WithLogger(log))\n\tif err := cl.Open(ctx); err != nil {\n\t\tpanic(err)\n\t}\n\tdefer cl.Close(ctx)\n\n\tok, err := cl.SetVirtualMedia(ctx, \"CD\", *isoURL)\n\tif err != nil {\n\t\tlog.Info(\"debugging\", \"metadata\", cl.GetMetadata())\n\t\tpanic(err)\n\t}\n\tif !ok {\n\t\tlog.Info(\"debugging\", \"metadata\", cl.GetMetadata())\n\t\tpanic(\"failed virtual media operation\")\n\t}\n\tlog.Info(\"virtual media operation successful\", \"metadata\", cl.GetMetadata())\n}\n"
  },
  {
    "path": "filter.go",
    "content": "package bmclib\n\nimport \"github.com/jacobweinstock/registrar\"\n\n// PreferProvider reorders the registry to have the given provider first.\n// This is a one time/temporary reordering of the providers in the registry.\n// This reorder is not preserved. It is only used for the call that uses the returned Client.\n// Update the Client.Registry to make the change permanent. For example, `cl.Registry.Drivers = cl.Registry.PreferDriver(\"ipmitool\")`\nfunc (c *Client) PreferProvider(name string) *Client {\n\tc.oneTimeRegistry.Drivers = c.Registry.PreferDriver(name)\n\tc.oneTimeRegistryEnabled = true\n\n\treturn c\n}\n\n// Supports removes any provider from the registry that does not support the given features.\n// This is a one time/temporary reordering of the providers in the registry.\n// This reorder is not preserved. It is only used for the call that uses the returned Client.\nfunc (c *Client) Supports(features ...registrar.Feature) *Client {\n\tc.oneTimeRegistry.Drivers = c.Registry.Supports(features...)\n\tc.oneTimeRegistryEnabled = true\n\n\treturn c\n}\n\n// Using removes any provider from the registry that does not support the given protocol.\n// This is a one time/temporary reordering of the providers in the registry.\n// This reorder is not preserved. It is only used for the call that uses the returned Client.\nfunc (c *Client) Using(protocol string) *Client {\n\tc.oneTimeRegistry.Drivers = c.Registry.Using(protocol)\n\tc.oneTimeRegistryEnabled = true\n\n\treturn c\n}\n\n// For removes any provider from the registry that is not the given provider.\n// This is a one time/temporary reordering of the providers in the registry.\n// This reorder is not preserved. It is only used for the call that uses the returned Client.\nfunc (c *Client) For(provider string) *Client {\n\tc.oneTimeRegistry.Drivers = c.Registry.For(provider)\n\tc.oneTimeRegistryEnabled = true\n\n\treturn c\n}\n\n// PreferProtocol reorders the providers in the registry to have the given protocol first. Matching providers order is preserved.\n// This is a one time/temporary reordering of the providers in the registry.\n// This reorder is not preserved. It is only used for the call that uses the returned Client.\nfunc (c *Client) PreferProtocol(protocols ...string) *Client {\n\tc.oneTimeRegistry.Drivers = c.Registry.PreferProtocol(protocols...)\n\tc.oneTimeRegistryEnabled = true\n\n\treturn c\n}\n"
  },
  {
    "path": "fixtures/internal/sum/ChangeBiosConfig",
    "content": "Supermicro Update Manager (for UEFI BIOS) 2.14.0 (2024/02/15) (ARM64)\nCopyright(C) 2013-2024 Super Micro Computer, Inc. All rights reserved.\n.................\u001b[0m\n\u001b[0m\n\n\u001b[1m\u001b[1m\u001b[1mNote:\u001b[0m\u001b[0m \u001b[0mNo BIOS setting has been changed.\n\n\u001b[0m\u001b[1m\u001b[1m\u001b[1mStatus:\u001b[0m\u001b[0m \u001b[0mThe BIOS configuration is updated for 10.145.129.168\n\n\u001b[0m\u001b[1m\u001b[1m\u001b[1mNote:\u001b[0m\u001b[0m \u001b[0mYou have to reboot or power up the system for the changes to take effect.\n\n"
  },
  {
    "path": "fixtures/internal/sum/ChangeBiosConfig-Changed",
    "content": "Supermicro Update Manager (for UEFI BIOS) 2.14.0 (2024/02/15) (ARM64)\nCopyright(C) 2013-2024 Super Micro Computer, Inc. All rights reserved.\n..................\u001b[0m\n\u001b[0m\u001b[1m\u001b[1m\u001b[1mStatus:\u001b[0m\u001b[0m \u001b[0mThe BIOS configuration is updated for 10.145.129.168\n\n\u001b[0m\u001b[1m\u001b[1m\u001b[1mNote:\u001b[0m\u001b[0m \u001b[0mYou have to reboot or power up the system for the changes to take effect.\n\n"
  },
  {
    "path": "fixtures/internal/sum/ChangeBiosConfig-Changed-Reboot",
    "content": "Supermicro Update Manager (for UEFI BIOS) 2.14.0 (2024/02/15) (ARM64)\nCopyright(C) 2013-2024 Super Micro Computer, Inc. All rights reserved.\n.................\u001b[0m\n\u001b[0m\u001b[1m\u001b[1m\u001b[1mStatus:\u001b[0m\u001b[0m \u001b[0mThe managed system 10.145.129.168 is rebooting.\n\n.............................\u001b[0mDone\n....\n.................\u001b[0m\n\u001b[0m\n\n\u001b[1m\u001b[1m\u001b[1mNote:\u001b[0m\u001b[0m \u001b[0mNo BIOS setting has been changed.\n\n\u001b[0m\u001b[1m\u001b[1m\u001b[1mStatus:\u001b[0m\u001b[0m \u001b[0mThe BIOS configuration is updated for 10.145.129.168\n\n\u001b[0m\u001b[0m\u001b[0;33m\u001b[0;33m\u001b[0;33mWARNING\u001b[0m\u001b[0m: \u001b[0mWithout option --post_complete, please manually confirm the managed system is POST complete before executing next action.\n\n"
  },
  {
    "path": "fixtures/internal/sum/GetBIOSInfo",
    "content": "Supermicro Update Manager (for UEFI BIOS) 2.14.0 (2024/02/15) (ARM64)\nCopyright(C) 2013-2024 Super Micro Computer, Inc. All rights reserved.\n....\nManaged system..........................10.145.129.168\n    Board ID............................1B0F\n    BIOS build date.....................2022/09/16\n    BIOS version........................1.9\n"
  },
  {
    "path": "fixtures/internal/sum/GetBiosConfiguration",
    "content": "Supermicro Update Manager (for UEFI BIOS) 2.14.0 (2024/02/15) (ARM64)\nCopyright(C) 2013-2024 Super Micro Computer, Inc. All rights reserved.\n.......................\n<?xml version=\"1.0\" encoding=\"ISO-8859-1\" standalone=\"yes\"?>\n<BiosCfg>\n  <!--Supermicro Update Manager 2.14.0 (2024/02/15)-->\n  <!--File generated at 2024-08-02_20:07:34-->\n  <Menu name=\"Main\">\n    <Information />\n    <Subtitle></Subtitle>\n    <Subtitle></Subtitle>\n    <Subtitle>Supermicro X11SCM-F</Subtitle>\n    <Text>BIOS Version(1.9)</Text>\n    <Text>Build Date(09/16/2022)</Text>\n    <Text>CPLD Version(03.B3.05)</Text>\n    <Subtitle></Subtitle>\n    <Subtitle>Memory Information</Subtitle>\n    <Text>Total Memory(32768 MB)</Text>\n  </Menu>\n  <Menu name=\"Advanced\">\n    <Information />\n    <Menu name=\"Boot Feature\">\n      <Information>\n        <Help><![CDATA[Boot Feature Configuration Page]]></Help>\n      </Information>\n      <Setting name=\"Fast Boot\" order=\"1\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[Enables or disables boot with initialization of a minimal set of devices required to launch active boot option. Has no effect for BBS boot options.]]></Help>\n        </Information>\n      </Setting>\n      <Subtitle></Subtitle>\n      <Setting name=\"Quiet Boot\" checkedStatus=\"Checked\" type=\"CheckBox\">\n        <!--Checked/Unchecked-->\n        <Information>\n          <DefaultStatus>Checked</DefaultStatus>\n          <Help><![CDATA[Enables or disables Quiet Boot option]]></Help>\n        </Information>\n      </Setting>\n      <Subtitle></Subtitle>\n      <Setting name=\"Bootup NumLock State\" selectedOption=\"On\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"1\">On</Option>\n            <Option value=\"0\">Off</Option>\n          </AvailableOptions>\n          <DefaultOption>On</DefaultOption>\n          <Help><![CDATA[Select the keyboard NumLock state]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Option ROM Messages\" selectedOption=\"Force BIOS\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"1\">Force BIOS</Option>\n            <Option value=\"0\">Keep Current</Option>\n          </AvailableOptions>\n          <DefaultOption>Force BIOS</DefaultOption>\n          <Help><![CDATA[Set display mode for Option ROM]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Wait For &quot;F1&quot; If Error\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Enable- BIOS will wait for user to press \"F1\" if some error happens. Disable- BIOS will continue to POST, user interaction not required]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Re-try Boot\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Legacy Boot</Option>\n            <Option value=\"2\">EFI Boot</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[Decide how to retry boot devices which fail to boot.]]></Help>\n        </Information>\n      </Setting>\n      <Subtitle></Subtitle>\n      <Setting name=\"Watch Dog Function\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[Enable or disable to turn on 5-minute watch dog timer. Upon timeout, JWD1 jumper determines system behavior.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Restore on AC Power Loss\" selectedOption=\"Last State\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Stay Off</Option>\n            <Option value=\"1\">Power On</Option>\n            <Option value=\"2\">Last State</Option>\n          </AvailableOptions>\n          <DefaultOption>Last State</DefaultOption>\n          <Help><![CDATA[Stay Off: System always remains off.\r\nPower On: System always turns on.\r\nLast State: System returns to previous state before AC lost.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Power Button Function\" selectedOption=\"Instant Off\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"1\">Instant Off</Option>\n            <Option value=\"0\">4 Seconds Override</Option>\n          </AvailableOptions>\n          <DefaultOption>Instant Off</DefaultOption>\n          <Help><![CDATA[Instant Off: Turn off system immediately in legacy OS.\r\n4 Seconds Override: Turn off system after depressed for 4 seconds.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"DeepSx Power Policies\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"4\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[configure the DeepSx Mode configuration.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Correctable Error Threshold\" numericValue=\"100\" type=\"Numeric\">\n        <Information>\n          <MaxValue>32767</MaxValue>\n          <MinValue>1</MinValue>\n          <StepSize>1</StepSize>\n          <DefaultValue>100</DefaultValue>\n          <Help><![CDATA[Fake item for MCEON support,Correctable Error Threshold]]></Help>\n        </Information>\n      </Setting>\n    </Menu>\n    <Menu name=\"CPU Configuration\">\n      <Information>\n        <Help><![CDATA[CPU Configuration Parameters]]></Help>\n      </Information>\n      <Subtitle>CPU Configuration</Subtitle>\n      <Subtitle></Subtitle>\n      <Text>Intel(R) Xeon(R) E-2278G CPU @ 3.40GHz()</Text>\n      <Text>CPU Signature(0x906ED)</Text>\n      <Text>Microcode Revision(F4)</Text>\n      <Text>CPU Speed(3400 MHz)</Text>\n      <Text>L1 Data Cache(32 KB x 8)</Text>\n      <Text>L1 Instruction Cache(32 KB x 8)</Text>\n      <Text>L2 Cache(256 KB x 8)</Text>\n      <Text>L3 Cache(16 MB)</Text>\n      <Text>L4 Cache(N/A)</Text>\n      <Text>VMX(Supported)</Text>\n      <Text>SMX/TXT(Supported)</Text>\n      <Subtitle></Subtitle>\n      <Setting name=\"CPU Flex Ratio Override\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[Enable/Disable CPU Flex Ratio Programming]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"CPU Flex Ratio Settings\" numericValue=\"34\" type=\"Numeric\">\n        <Information>\n          <MaxValue>63</MaxValue>\n          <MinValue>0</MinValue>\n          <StepSize>0</StepSize>\n          <DefaultValue>20</DefaultValue>\n          <Help><![CDATA[This value must be between Max Efficiency Ratio (LFM) and Maximum non-turbo ratio set by Hardware (HFM).]]></Help>\n          <WorkIf><![CDATA[ ( CPU Flex Ratio Override != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"Hardware Prefetcher\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[To turn on/off the MLC streamer prefetcher.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Adjacent Cache Line Prefetch\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[To turn on/off prefetching of adjacent cache lines.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Intel (VMX) Virtualization Technology\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[When enabled, a VMM can utilize the additional hardware capabilities provided by Vanderpool Technology.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Active Processor Cores\" selectedOption=\"All\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">All</Option>\n            <Option value=\"1\">1</Option>\n            <Option value=\"2\">2</Option>\n            <Option value=\"3\">3</Option>\n            <Option value=\"4\">4</Option>\n            <Option value=\"5\">5</Option>\n            <Option value=\"6\">6</Option>\n            <Option value=\"7\">7</Option>\n          </AvailableOptions>\n          <DefaultOption>All</DefaultOption>\n          <Help><![CDATA[Number of cores to enable in each processor package.]]></Help>\n          <WorkIf><![CDATA[ (   TXT Support != 1 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"Hyper-Threading\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Enabled or Disabled Hyper-Threading Technology.]]></Help>\n          <WorkIf><![CDATA[ (   TXT Support != 1 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"BIST\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[Enable/Disable BIST (Built-In Self Test) on reset]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"AES\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Enable/Disable AES (Advanced Encryption Standard)]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Boot Performance Mode\" selectedOption=\"Max Non-Turbo Performance\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Power Saving</Option>\n            <Option value=\"1\">Max Non-Turbo Performance</Option>\n            <Option value=\"2\">Turbo Performance</Option>\n          </AvailableOptions>\n          <DefaultOption>Max Non-Turbo Performance</DefaultOption>\n          <Help><![CDATA[Select the performance state that the BIOS will set starting from reset vector.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Intel(R) SpeedStep(tm)\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Allows more than two frequency ranges to be supported.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Intel(R) Speed Shift Technology\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[Enable/Disable Intel(R) Speed Shift Technology support. Enabling will expose the CPPC v2 interface to allow for hardware controlled P-states.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Turbo Mode\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[When Turbo Mode is enabled, the CPU temperature could hit 100C. CPU throttling at this time is a normal behavior. A proper review on other thermal indexes will help you determine the real system state.]]></Help>\n          <WorkIf><![CDATA[ ( ( Intel(R) SpeedStep(tm) != 0 ) || ( Intel(R) Speed Shift Technology != 0 ) ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"Always Turbo Mode\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[When enable system will run in always turbo.]]></Help>\n          <WorkIf><![CDATA[ ( ( Intel(R) SpeedStep(tm) != 0 ) || ( Intel(R) Speed Shift Technology != 0 ) ) and ( Turbo Mode != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"Monitor/Mwait\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Enable/Disable Monitor/Mwait]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"C states\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Enable/Disable CPU Power Management. Allows CPU to go to C states when it's not 100% utilized.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"  Enhanced C-states\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Enable/Disable C1E. When enabled, CPU will switch to minimum speed when all cores enter C-State.]]></Help>\n          <WorkIf><![CDATA[ ( C states != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  C-State Auto Demotion\" selectedOption=\"C1 and C3\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">C1</Option>\n            <Option value=\"2\">C3</Option>\n            <Option value=\"3\">C1 and C3</Option>\n          </AvailableOptions>\n          <DefaultOption>C1 and C3</DefaultOption>\n          <Help><![CDATA[Configure C-State Auto Demotion]]></Help>\n          <WorkIf><![CDATA[ ( C states != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  C-State Un-demotion\" selectedOption=\"C1 and C3\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">C1</Option>\n            <Option value=\"2\">C3</Option>\n            <Option value=\"3\">C1 and C3</Option>\n          </AvailableOptions>\n          <DefaultOption>C1 and C3</DefaultOption>\n          <Help><![CDATA[Configure C-State Un-demotion]]></Help>\n          <WorkIf><![CDATA[ ( C states != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  Package C-State Demotion\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[  Package C-State Demotion]]></Help>\n          <WorkIf><![CDATA[ ( C states != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  Package C-State Un-demotion\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[  Package C-State Un-demotion]]></Help>\n          <WorkIf><![CDATA[ ( C states != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"CState Pre-Wake\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Disable - Sets bit 30 of POWER_CTL MSR(0x1FC) to 1 to disable the Cstate Pre-Wake]]></Help>\n          <WorkIf><![CDATA[ ( C states != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"Package C State Limit\" selectedOption=\"Auto\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">C0/C1</Option>\n            <Option value=\"1\">C2</Option>\n            <Option value=\"2\">C3</Option>\n            <Option value=\"3\">C6</Option>\n            <Option value=\"4\">C7</Option>\n            <Option value=\"5\">C7S</Option>\n            <Option value=\"6\">C8</Option>\n            <Option value=\"7\">C9</Option>\n            <Option value=\"8\">C10</Option>\n            <Option value=\"254\">Cpu Default</Option>\n            <Option value=\"255\">Auto</Option>\n          </AvailableOptions>\n          <DefaultOption>Auto</DefaultOption>\n          <Help><![CDATA[Maximum Package C State Limit Setting. Cpu Default: Leaves to Factory default value.Auto: Initializes to deepest available Package C State Limit.]]></Help>\n          <WorkIf><![CDATA[ ( C states != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"Power Limit 1 Override\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[Enable/Disable Power Limit 1 override. If this option is disabled, BIOS will program the default values for Power Limit 1 and Power Limit 1 Time Window.]]></Help>\n          <WorkIf><![CDATA[ ( C states != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"Power Limit 1\" numericValue=\"0\" type=\"Numeric\">\n        <Information>\n          <MaxValue>4095875</MaxValue>\n          <MinValue>0</MinValue>\n          <StepSize>125</StepSize>\n          <DefaultValue>0</DefaultValue>\n          <Help><![CDATA[Power Limit 1 in Milli Watts. BIOS will round to the nearest 1/8W when programming. 0 = no custom override. For 12.50W, enter 12500. Overclocking SKU: Value must be between Max and Min Power Limits (specified by PACKAGE_POWER_SKU_MSR). Other SKUs: This value must be between Min Power Limit and TDP Limit. If value is 0, BIOS will program TDP value.]]></Help>\n          <WorkIf><![CDATA[ ( C states != 0 ) and ( Power Limit 1 Override != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"Power Limit 1 Time Window\" selectedOption=\"0\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">0</Option>\n            <Option value=\"1\">1</Option>\n            <Option value=\"2\">2</Option>\n            <Option value=\"3\">3</Option>\n            <Option value=\"4\">4</Option>\n            <Option value=\"5\">5</Option>\n            <Option value=\"6\">6</Option>\n            <Option value=\"7\">7</Option>\n            <Option value=\"8\">8</Option>\n            <Option value=\"10\">10</Option>\n            <Option value=\"12\">12</Option>\n            <Option value=\"14\">14</Option>\n            <Option value=\"16\">16</Option>\n            <Option value=\"20\">20</Option>\n            <Option value=\"24\">24</Option>\n            <Option value=\"28\">28</Option>\n            <Option value=\"32\">32</Option>\n            <Option value=\"40\">40</Option>\n            <Option value=\"48\">48</Option>\n            <Option value=\"56\">56</Option>\n            <Option value=\"64\">64</Option>\n            <Option value=\"80\">80</Option>\n            <Option value=\"96\">96</Option>\n            <Option value=\"112\">112</Option>\n            <Option value=\"128\">128</Option>\n          </AvailableOptions>\n          <DefaultOption>0</DefaultOption>\n          <Help><![CDATA[Power Limit 1 Time Window value in seconds. The value may vary from 0 to 128. 0 = use default value (28 sec). Defines time window which TDP value should be maintained.]]></Help>\n          <WorkIf><![CDATA[ ( C states != 0 ) and ( Power Limit 1 Override != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"Power Limit 2 Override\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Enable/Disable Power Limit 2 override. If this option is disabled, BIOS will program the default values for Power Limit 2.]]></Help>\n          <WorkIf><![CDATA[ ( C states != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"Power Limit 2\" numericValue=\"0\" type=\"Numeric\">\n        <Information>\n          <MaxValue>4095875</MaxValue>\n          <MinValue>0</MinValue>\n          <StepSize>125</StepSize>\n          <DefaultValue>0</DefaultValue>\n          <Help><![CDATA[Power Limit 2 value in Milli Watts. BIOS will round to the nearest 1/8W when programming. If the value is 0, BIOS will program this value as 1.25*TDP. For 12.50W, enter 12500. Processor applies control policies such that the package power does not exceed this limit.]]></Help>\n          <WorkIf><![CDATA[ ( C states != 0 ) and ( Power Limit 2 Override != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Subtitle></Subtitle>\n      <!--Valid if:  ( C states != 0 ) -->\n      <Setting name=\"ACPI T-States\" checkedStatus=\"Unchecked\" type=\"CheckBox\">\n        <!--Checked/Unchecked-->\n        <Information>\n          <DefaultStatus>Unchecked</DefaultStatus>\n          <Help><![CDATA[Enable/Disable ACPI T-States.]]></Help>\n          <WorkIf><![CDATA[ ( C states != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n    </Menu>\n    <Menu name=\"Chipset Configuration\">\n      <Information>\n        <Help><![CDATA[System Chipset configuration.]]></Help>\n      </Information>\n      <Subtitle>WARNING: Setting wrong values in below sections may cause</Subtitle>\n      <Subtitle>         system to malfunction.</Subtitle>\n      <Menu name=\"System Agent (SA) Configuration\">\n        <Information>\n          <Help><![CDATA[System Agent (SA) Parameters]]></Help>\n        </Information>\n        <Subtitle>System Agent (SA) Configuration</Subtitle>\n        <Subtitle></Subtitle>\n        <Text>SA PCIe Code Version(7.0.88.80)</Text>\n        <Text>VT-d(Supported)</Text>\n        <Subtitle></Subtitle>\n        <Menu name=\"Memory Configuration\">\n          <Information>\n            <Help><![CDATA[Memory Configuration Parameters]]></Help>\n          </Information>\n          <Subtitle>Memory Configuration</Subtitle>\n          <Subtitle></Subtitle>\n          <Text>Memory RC Version(0.7.1.115)</Text>\n          <Text>Memory Frequency( 2667 MHz)</Text>\n          <Text>Memory Timings (tCL-tRCD-tRP-tRAS)(19-19-19-43)</Text>\n          <Subtitle></Subtitle>\n          <Text>DIMMA1(Not Present)</Text>\n          <Text>DIMMA2(Populated &amp; Enabled)</Text>\n          <Text>    Size(16384 MB (DDR4))</Text>\n          <Text>    Number of Ranks(2)</Text>\n          <Text>    Manufacturer(Micron Technology)</Text>\n          <Text>DIMMB1(Not Present)</Text>\n          <Text>DIMMB2(Populated &amp; Enabled)</Text>\n          <Text>    Size(16384 MB (DDR4))</Text>\n          <Text>    Number of Ranks(2)</Text>\n          <Text>    Manufacturer(Micron Technology)</Text>\n          <Subtitle></Subtitle>\n          <Setting name=\"Maximum Memory Frequency\" selectedOption=\"Auto\" type=\"Option\">\n            <Information>\n              <AvailableOptions>\n                <Option value=\"0\">Auto</Option>\n                <Option value=\"1067\">1067</Option>\n                <Option value=\"1200\">1200</Option>\n                <Option value=\"1333\">1333</Option>\n                <Option value=\"1400\">1400</Option>\n                <Option value=\"1600\">1600</Option>\n                <Option value=\"1800\">1800</Option>\n                <Option value=\"1867\">1867</Option>\n                <Option value=\"2000\">2000</Option>\n                <Option value=\"2133\">2133</Option>\n                <Option value=\"2200\">2200</Option>\n                <Option value=\"2400\">2400</Option>\n                <Option value=\"2600\">2600</Option>\n                <Option value=\"2667\">2667</Option>\n                <Option value=\"2800\">2800</Option>\n                <Option value=\"2933\">2933</Option>\n                <Option value=\"3000\">3000</Option>\n                <Option value=\"3200\">3200</Option>\n                <Option value=\"3400\">3400</Option>\n                <Option value=\"3467\">3467</Option>\n                <Option value=\"3600\">3600</Option>\n                <Option value=\"3733\">3733</Option>\n                <Option value=\"3800\">3800</Option>\n                <Option value=\"4000\">4000</Option>\n                <Option value=\"4200\">4200</Option>\n                <Option value=\"4267\">4267</Option>\n                <Option value=\"4400\">4400</Option>\n                <Option value=\"4533\">4533</Option>\n                <Option value=\"4600\">4600</Option>\n                <Option value=\"4800\">4800</Option>\n                <Option value=\"5000\">5000</Option>\n                <Option value=\"5067\">5067</Option>\n                <Option value=\"5200\">5200</Option>\n                <Option value=\"5333\">5333</Option>\n                <Option value=\"5400\">5400</Option>\n                <Option value=\"5600\">5600</Option>\n                <Option value=\"5800\">5800</Option>\n                <Option value=\"5867\">5867</Option>\n                <Option value=\"6000\">6000</Option>\n                <Option value=\"6133\">6133</Option>\n                <Option value=\"6200\">6200</Option>\n              </AvailableOptions>\n              <DefaultOption>Auto</DefaultOption>\n              <Help><![CDATA[Maximum Memory Frequency Selections in Mhz. Valid values should match the refclk, i.e. divide by 133 or 100]]></Help>\n            </Information>\n          </Setting>\n          <Setting name=\"Max TOLUD\" selectedOption=\"Dynamic\" type=\"Option\">\n            <Information>\n              <AvailableOptions>\n                <Option value=\"0\">Dynamic</Option>\n                <Option value=\"1\">1 GB</Option>\n                <Option value=\"2\">1.25 GB</Option>\n                <Option value=\"3\">1.5 GB</Option>\n                <Option value=\"4\">1.75 GB</Option>\n                <Option value=\"5\">2 GB</Option>\n                <Option value=\"6\">2.25 GB</Option>\n                <Option value=\"7\">2.5 GB</Option>\n                <Option value=\"8\">2.75 GB</Option>\n                <Option value=\"9\">3 GB</Option>\n                <Option value=\"10\">3.25 GB</Option>\n                <Option value=\"11\">3.5 GB</Option>\n              </AvailableOptions>\n              <DefaultOption>Dynamic</DefaultOption>\n              <Help><![CDATA[Maximum Value of TOLUD. Dynamic assignment would adjust TOLUD automatically based on largest MMIO length of installed graphic controller]]></Help>\n            </Information>\n          </Setting>\n          <Setting name=\"Memory Scrambler\" selectedOption=\"Enabled\" type=\"Option\">\n            <Information>\n              <AvailableOptions>\n                <Option value=\"0\">Disabled</Option>\n                <Option value=\"1\">Enabled</Option>\n              </AvailableOptions>\n              <DefaultOption>Enabled</DefaultOption>\n              <Help><![CDATA[Enable/Disable Memory Scrambler support.]]></Help>\n            </Information>\n          </Setting>\n          <Setting name=\"Fast Boot\" order=\"2\" selectedOption=\"Enabled\" type=\"Option\">\n            <Information>\n              <AvailableOptions>\n                <Option value=\"0\">Disabled</Option>\n                <Option value=\"1\">Enabled</Option>\n              </AvailableOptions>\n              <DefaultOption>Enabled</DefaultOption>\n              <Help><![CDATA[Enable/Disable fast path thru the MRC]]></Help>\n            </Information>\n          </Setting>\n          <Setting name=\"REFRESH_2X_MODE\" selectedOption=\"Disabled\" type=\"Option\">\n            <Information>\n              <AvailableOptions>\n                <Option value=\"0\">Disabled</Option>\n                <Option value=\"1\">1- Enabled for WARM or HOT</Option>\n                <Option value=\"2\">2- Enabled HOT only</Option>\n              </AvailableOptions>\n              <DefaultOption>Disabled</DefaultOption>\n              <Help><![CDATA[0- Disabled 1-iMC enables 2xRef when Warm and Hot 2- iMC enables 2xRef when Hot]]></Help>\n            </Information>\n          </Setting>\n        </Menu>\n        <Menu name=\"DMI/OPI Configuration\">\n          <Information>\n            <Help><![CDATA[Control various DMI functions.]]></Help>\n          </Information>\n          <Subtitle>DMI/OPI Configuration</Subtitle>\n          <Subtitle></Subtitle>\n          <Text>DMI(X4  Gen3)</Text>\n          <Subtitle></Subtitle>\n          <Setting name=\"DMI Link ASPM Control\" order=\"1\" selectedOption=\"L1\" type=\"Option\">\n            <Information>\n              <AvailableOptions>\n                <Option value=\"0\">Disabled</Option>\n                <Option value=\"1\">L0s</Option>\n                <Option value=\"2\">L1</Option>\n                <Option value=\"3\">L0sL1</Option>\n              </AvailableOptions>\n              <DefaultOption>L1</DefaultOption>\n              <Help><![CDATA[Enable/Disable the control of Active State Power Management on SA side of the DMI Link.]]></Help>\n            </Information>\n          </Setting>\n          <Setting name=\"DMI Extended Sync Control\" selectedOption=\"Disabled\" type=\"Option\">\n            <Information>\n              <AvailableOptions>\n                <Option value=\"1\">Enabled</Option>\n                <Option value=\"0\">Disabled</Option>\n              </AvailableOptions>\n              <DefaultOption>Disabled</DefaultOption>\n              <Help><![CDATA[Enable DMI Extended Synchronization.]]></Help>\n            </Information>\n          </Setting>\n          <Setting name=\"DMI De-emphasis Control\" selectedOption=\"-3.5 dB\" type=\"Option\">\n            <Information>\n              <AvailableOptions>\n                <Option value=\"0\">-6 dB</Option>\n                <Option value=\"1\">-3.5 dB</Option>\n              </AvailableOptions>\n              <DefaultOption>-3.5 dB</DefaultOption>\n              <Help><![CDATA[Configure the De-emphasis control on DMI]]></Help>\n            </Information>\n          </Setting>\n        </Menu>\n        <Menu name=\"PEG Port Configuration\">\n          <Information>\n            <Help><![CDATA[PEG Port Options]]></Help>\n          </Information>\n          <Subtitle>PEG Port Configuration</Subtitle>\n          <Subtitle></Subtitle>\n          <Text>CPU Slot6 PCI-E 3.0 X16(x8  Gen3)</Text>\n          <Setting name=\"  Enable Root Port\" selectedOption=\"Auto\" type=\"Option\">\n            <Information>\n              <AvailableOptions>\n                <Option value=\"0\">Disabled</Option>\n                <Option value=\"1\">Enabled</Option>\n                <Option value=\"2\">Auto</Option>\n              </AvailableOptions>\n              <DefaultOption>Auto</DefaultOption>\n              <Help><![CDATA[Enable or Disable the Root Port]]></Help>\n            </Information>\n          </Setting>\n          <Setting name=\"  Max Link Speed\" selectedOption=\"Auto\" type=\"Option\">\n            <Information>\n              <AvailableOptions>\n                <Option value=\"0\">Auto</Option>\n                <Option value=\"1\">Gen1</Option>\n                <Option value=\"2\">Gen2</Option>\n                <Option value=\"3\">Gen3</Option>\n              </AvailableOptions>\n              <DefaultOption>Auto</DefaultOption>\n              <Help><![CDATA[Configure PEG 0:1:0 Max Speed]]></Help>\n            </Information>\n          </Setting>\n          <Setting name=\"  Max Link Width\" selectedOption=\"Auto\" type=\"Option\">\n            <Information>\n              <AvailableOptions>\n                <Option value=\"0\">Auto</Option>\n                <Option value=\"1\">Force X1</Option>\n                <Option value=\"2\">Force X2</Option>\n                <Option value=\"3\">Force X4</Option>\n                <Option value=\"4\">Force X8</Option>\n              </AvailableOptions>\n              <DefaultOption>Auto</DefaultOption>\n              <Help><![CDATA[Force PEG link to retrain to X1/2/4/8]]></Help>\n            </Information>\n          </Setting>\n          <Setting name=\"  ASPM\" selectedOption=\"Auto\" type=\"Option\">\n            <Information>\n              <AvailableOptions>\n                <Option value=\"0\">Disabled</Option>\n                <Option value=\"4\">Auto</Option>\n                <Option value=\"1\">ASPM L0s</Option>\n                <Option value=\"2\">ASPM L1</Option>\n                <Option value=\"3\">ASPM L0sL1</Option>\n              </AvailableOptions>\n              <DefaultOption>Auto</DefaultOption>\n              <Help><![CDATA[Control ASPM support for the PEG 0.  This has no effect if PEG is not the currently active device.]]></Help>\n            </Information>\n          </Setting>\n          <Setting name=\"    ASPM L0s\" selectedOption=\"Both Root and Endpoint Ports\" type=\"Option\">\n            <Information>\n              <AvailableOptions>\n                <Option value=\"1\">Root Port Only</Option>\n                <Option value=\"2\">Endpoint Port Only</Option>\n                <Option value=\"3\">Both Root and Endpoint Ports</Option>\n              </AvailableOptions>\n              <DefaultOption>Both Root and Endpoint Ports</DefaultOption>\n              <Help><![CDATA[PEG0: Enable PCIe ASPM L0s.]]></Help>\n              <WorkIf><![CDATA[ ( ( (   ASPM != 0 ) && (   ASPM != 2 ) ) && (   ASPM != 4 ) ) ]]></WorkIf>\n            </Information>\n          </Setting>\n          <Setting name=\"  De-emphasis Control\" selectedOption=\"-3.5 dB\" type=\"Option\">\n            <Information>\n              <AvailableOptions>\n                <Option value=\"0\">-6 dB</Option>\n                <Option value=\"1\">-3.5 dB</Option>\n              </AvailableOptions>\n              <DefaultOption>-3.5 dB</DefaultOption>\n              <Help><![CDATA[PEG0: Configure the De-emphasis control on PEG]]></Help>\n            </Information>\n          </Setting>\n          <Setting name=\"  Power Limit Value\" numericValue=\"75\" type=\"Numeric\">\n            <Information>\n              <MaxValue>255</MaxValue>\n              <MinValue>0</MinValue>\n              <StepSize>1</StepSize>\n              <DefaultValue>75</DefaultValue>\n              <Help><![CDATA[Sets the upper limit on power supplied by slot. Power limit (in Watts) is calculated by multiplying this value by the Slot Power Limit Scale. Values 0-255]]></Help>\n            </Information>\n          </Setting>\n          <Setting name=\"  Power Limit Scale\" selectedOption=\"1.0x\" type=\"Option\">\n            <Information>\n              <AvailableOptions>\n                <Option value=\"0\">1.0x</Option>\n                <Option value=\"1\">0.1x</Option>\n                <Option value=\"2\">0.01x</Option>\n                <Option value=\"3\">0.001x</Option>\n              </AvailableOptions>\n              <DefaultOption>1.0x</DefaultOption>\n              <Help><![CDATA[Select the scale used for the Slot Power Limit Value.]]></Help>\n            </Information>\n          </Setting>\n          <Setting name=\"  Physical Slot Number\" numericValue=\"1\" type=\"Numeric\">\n            <Information>\n              <MaxValue>8191</MaxValue>\n              <MinValue>0</MinValue>\n              <StepSize>1</StepSize>\n              <DefaultValue>1</DefaultValue>\n              <Help><![CDATA[Set the physical slot number attached to this Port. The number has to be globally unique within the chassis. Values  0-8191]]></Help>\n            </Information>\n          </Setting>\n          <Setting name=\"  Max Payload size\" selectedOption=\"Auto\" type=\"Option\">\n            <Information>\n              <AvailableOptions>\n                <Option value=\"255\">Auto</Option>\n                <Option value=\"0\">128</Option>\n                <Option value=\"1\">256 TLP</Option>\n              </AvailableOptions>\n              <DefaultOption>Auto</DefaultOption>\n              <Help><![CDATA[Select PEG0 Max Payload Size; Choose Auto(Default Device Capability) or force to 128/256 Bytes]]></Help>\n            </Information>\n          </Setting>\n          <Subtitle></Subtitle>\n          <Setting name=\"Program PCIe ASPM after OpROM\" selectedOption=\"Disabled\" type=\"Option\">\n            <Information>\n              <AvailableOptions>\n                <Option value=\"0\">Disabled</Option>\n                <Option value=\"1\">Enabled</Option>\n              </AvailableOptions>\n              <DefaultOption>Disabled</DefaultOption>\n              <Help><![CDATA[Enabled: PCIe ASPM will be programmed after OpROM.\r\nDisabled: PCIe ASPM will be programmed before OpROM.]]></Help>\n            </Information>\n          </Setting>\n        </Menu>\n        <Subtitle></Subtitle>\n        <Setting name=\"VT-d\" selectedOption=\"Enabled\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"0\">Disabled</Option>\n              <Option value=\"1\">Enabled</Option>\n            </AvailableOptions>\n            <DefaultOption>Enabled</DefaultOption>\n            <Help><![CDATA[VT-d capability]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"Software Guard Extensions (SGX)\" selectedOption=\"Software Controlled\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"0\">Disabled</Option>\n              <Option value=\"1\">Enabled</Option>\n              <Option value=\"2\">Software Controlled</Option>\n            </AvailableOptions>\n            <DefaultOption>Software Controlled</DefaultOption>\n            <Help><![CDATA[Enable/Disable Software Guard Extensions (SGX)]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"Select Owner EPOCH input type\" selectedOption=\"No Change in Owner EPOCHs\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"0\">No Change in Owner EPOCHs</Option>\n              <Option value=\"1\">Change to New Random Owner EPOCHs</Option>\n              <Option value=\"2\">Manual User Defined Owner EPOCHs</Option>\n            </AvailableOptions>\n            <DefaultOption>No Change in Owner EPOCHs</DefaultOption>\n            <Help><![CDATA[There are three Owner EPOCH modes (Each EPOCH is 64bit): no change in owner epoch, change to new random owner epoch and manually entered by user. After generating new epoch via 'Change to New Random Owner EPOCHs', the selection reverts back to 'No Change in Owner Epochs', this is to ensure Epoch stays same, across Sx states. After the user enters epoch values manually, the values will not be visible, for security reasons.]]></Help>\n            <WorkIf><![CDATA[ ( Software Guard Extensions (SGX) != 0 ) ]]></WorkIf>\n          </Information>\n        </Setting>\n        <Setting name=\"Software Guard Extensions Epoch 0\" numericValue=\"6142344250440060711\" type=\"Numeric\">\n          <Information>\n            <MaxValue>18446744073709551615</MaxValue>\n            <MinValue>0</MinValue>\n            <StepSize>1</StepSize>\n            <DefaultValue>6142344250440060711</DefaultValue>\n            <Help><![CDATA[Software Guard Extensions Epoch 0]]></Help>\n            <WorkIf><![CDATA[ ( Software Guard Extensions (SGX) != 0 ) and ( Select Owner EPOCH input type == 2 ) ]]></WorkIf>\n          </Information>\n        </Setting>\n        <Setting name=\"Software Guard Extensions Epoch 1\" numericValue=\"15521488688214965697\" type=\"Numeric\">\n          <Information>\n            <MaxValue>18446744073709551615</MaxValue>\n            <MinValue>0</MinValue>\n            <StepSize>1</StepSize>\n            <DefaultValue>15521488688214965697</DefaultValue>\n            <Help><![CDATA[Software Guard Extensions Epoch 1]]></Help>\n            <WorkIf><![CDATA[ ( Software Guard Extensions (SGX) != 0 ) and ( Select Owner EPOCH input type == 2 ) ]]></WorkIf>\n          </Information>\n        </Setting>\n        <Setting name=\"SGX Launch Control Policy\" selectedOption=\"Unlocked\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"0\">Unlocked</Option>\n              <Option value=\"1\">Intel Locked</Option>\n              <Option value=\"2\">Locked</Option>\n            </AvailableOptions>\n            <DefaultOption>Unlocked</DefaultOption>\n            <Help><![CDATA[Software Guard Extensions (SGX) Launch Control Policy. Options are:\r\nIntel Locked - Select Intel's Launch Enclave.\r\nUnlocked - Enable OS/VMM configuration of Launch Enclave.\r\nLocked - Allow owner to configure Launch Enclave.]]></Help>\n            <WorkIf><![CDATA[ ( Software Guard Extensions (SGX) != 0 ) ]]></WorkIf>\n          </Information>\n        </Setting>\n        <Setting name=\"SGX LE Public Key Hash 0\" numericValue=\"0\" type=\"Numeric\">\n          <Information>\n            <MaxValue>18446744073709551615</MaxValue>\n            <MinValue>0</MinValue>\n            <StepSize>1</StepSize>\n            <DefaultValue>0</DefaultValue>\n            <Help><![CDATA[Bytes 0 - 7 of Software Guard Extensions (SGX) Launch Enclave Public Key Hash]]></Help>\n            <WorkIf><![CDATA[ ( Software Guard Extensions (SGX) != 0 ) and ( SGX Launch Control Policy == 2 ) ]]></WorkIf>\n          </Information>\n        </Setting>\n        <Setting name=\"SGX LE Public Key Hash 1\" numericValue=\"0\" type=\"Numeric\">\n          <Information>\n            <MaxValue>18446744073709551615</MaxValue>\n            <MinValue>0</MinValue>\n            <StepSize>1</StepSize>\n            <DefaultValue>0</DefaultValue>\n            <Help><![CDATA[Byte 8 - 15 of Software Guard Extensions (SGX) Launch Enclave Public Key Hash]]></Help>\n            <WorkIf><![CDATA[ ( Software Guard Extensions (SGX) != 0 ) and ( SGX Launch Control Policy == 2 ) ]]></WorkIf>\n          </Information>\n        </Setting>\n        <Setting name=\"SGX LE Public Key Hash 2\" numericValue=\"0\" type=\"Numeric\">\n          <Information>\n            <MaxValue>18446744073709551615</MaxValue>\n            <MinValue>0</MinValue>\n            <StepSize>1</StepSize>\n            <DefaultValue>0</DefaultValue>\n            <Help><![CDATA[Bytes 16 - 23 of Software Guard Extensions (SGX) Launch Enclave Public Key Hash]]></Help>\n            <WorkIf><![CDATA[ ( Software Guard Extensions (SGX) != 0 ) and ( SGX Launch Control Policy == 2 ) ]]></WorkIf>\n          </Information>\n        </Setting>\n        <Setting name=\"SGX LE Public Key Hash 3\" numericValue=\"0\" type=\"Numeric\">\n          <Information>\n            <MaxValue>18446744073709551615</MaxValue>\n            <MinValue>0</MinValue>\n            <StepSize>1</StepSize>\n            <DefaultValue>0</DefaultValue>\n            <Help><![CDATA[Bytes 24 - 31 of Software Guard Extensions (SGX) Launch Enclave Public Key Hash]]></Help>\n            <WorkIf><![CDATA[ ( Software Guard Extensions (SGX) != 0 ) and ( SGX Launch Control Policy == 2 ) ]]></WorkIf>\n          </Information>\n        </Setting>\n        <Setting name=\"PRMRR Size\" selectedOption=\"INVALID PRMRR\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"0\">INVALID PRMRR</Option>\n              <!--Option ValidIf:  ( Software Guard Extensions (SGX) != 1 ) -->\n            </AvailableOptions>\n            <DefaultOption>INVALID PRMRR</DefaultOption>\n            <Help><![CDATA[Setting the PRMRR Size]]></Help>\n            <WorkIf><![CDATA[ ( Software Guard Extensions (SGX) != 0 ) and ( Software Guard Extensions (SGX) != 2 ) ]]></WorkIf>\n          </Information>\n        </Setting>\n        <Setting name=\"GNA Device (B0:D8:F0)\" selectedOption=\"Enabled\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"1\">Enabled</Option>\n              <Option value=\"0\">Disabled</Option>\n            </AvailableOptions>\n            <DefaultOption>Enabled</DefaultOption>\n            <Help><![CDATA[Enable/Disable SA GNA Device.]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"X2APIC Opt Out\" selectedOption=\"Disabled\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"1\">Enabled</Option>\n              <Option value=\"0\">Disabled</Option>\n            </AvailableOptions>\n            <DefaultOption>Disabled</DefaultOption>\n            <Help><![CDATA[Enable/Disable X2APIC_OPT_OUT bit]]></Help>\n            <WorkIf><![CDATA[ ( VT-d != 0 ) ]]></WorkIf>\n          </Information>\n        </Setting>\n      </Menu>\n      <Menu name=\"PCH-IO Configuration\">\n        <Information>\n          <Help><![CDATA[PCH Parameters]]></Help>\n        </Information>\n        <Subtitle>PCH-IO Configuration</Subtitle>\n        <Subtitle></Subtitle>\n        <Menu name=\"PCI Express Configuration\">\n          <Information>\n            <Help><![CDATA[PCI Express Configuration settings]]></Help>\n          </Information>\n          <Subtitle>PCI Express Configuration</Subtitle>\n          <Subtitle></Subtitle>\n          <Setting name=\"DMI Link ASPM Control\" order=\"2\" selectedOption=\"Auto\" type=\"Option\">\n            <Information>\n              <AvailableOptions>\n                <Option value=\"0\">Disabled</Option>\n                <Option value=\"1\">L0s</Option>\n                <Option value=\"2\">L1</Option>\n                <Option value=\"3\">L0sL1</Option>\n                <Option value=\"4\">Auto</Option>\n              </AvailableOptions>\n              <DefaultOption>Auto</DefaultOption>\n              <Help><![CDATA[The control of Active State Power Management of the DMI Link.]]></Help>\n            </Information>\n          </Setting>\n          <Setting name=\"Peer Memory Write Enable\" selectedOption=\"Disabled\" type=\"Option\">\n            <Information>\n              <AvailableOptions>\n                <Option value=\"0\">Disabled</Option>\n                <Option value=\"1\">Enabled</Option>\n              </AvailableOptions>\n              <DefaultOption>Disabled</DefaultOption>\n              <Help><![CDATA[Peer Memory Write Enable/Disable]]></Help>\n            </Information>\n          </Setting>\n          <Menu name=\"M.2-P_1\">\n            <Information>\n              <Help><![CDATA[PCI Express Root Port Settings.]]></Help>\n            </Information>\n            <Setting name=\"M.2-P_1 ASPM Support\" selectedOption=\"Auto\" type=\"Option\">\n              <Information>\n                <AvailableOptions>\n                  <Option value=\"0\">Disabled</Option>\n                  <Option value=\"1\">L0s</Option>\n                  <Option value=\"2\">L1</Option>\n                  <Option value=\"3\">L0sL1</Option>\n                  <Option value=\"4\">Auto</Option>\n                </AvailableOptions>\n                <DefaultOption>Auto</DefaultOption>\n                <Help><![CDATA[Set the ASPM Level:\r\nForce L0s - Force all links to L0s State\r\nAUTO - BIOS auto configure\r\nDISABLE - Disables ASPM]]></Help>\n              </Information>\n            </Setting>\n            <Setting name=\"M.2-P_1 Substates\" selectedOption=\"L1.1 &amp; L1.2\" type=\"Option\">\n              <Information>\n                <AvailableOptions>\n                  <Option value=\"0\">Disabled</Option>\n                  <Option value=\"1\">L1.1</Option>\n                  <Option value=\"2\">L1.1 &amp; L1.2</Option>\n                </AvailableOptions>\n                <DefaultOption>L1.1 &amp; L1.2</DefaultOption>\n                <Help><![CDATA[PCI Express L1 Substates settings.]]></Help>\n              </Information>\n            </Setting>\n            <Setting name=\"M.2-P_1 PCIe Speed\" selectedOption=\"Auto\" type=\"Option\">\n              <Information>\n                <AvailableOptions>\n                  <Option value=\"0\">Auto</Option>\n                  <Option value=\"1\">Gen1</Option>\n                  <Option value=\"2\">Gen2</Option>\n                  <Option value=\"3\">Gen3</Option>\n                </AvailableOptions>\n                <DefaultOption>Auto</DefaultOption>\n                <Help><![CDATA[Configure PCIe Speed]]></Help>\n              </Information>\n            </Setting>\n          </Menu>\n          <Menu name=\"M.2-H_2\">\n            <Information>\n              <Help><![CDATA[PCI Express Root Port Settings.]]></Help>\n            </Information>\n            <Setting name=\"M.2-H_2 ASPM Support\" selectedOption=\"Auto\" type=\"Option\">\n              <Information>\n                <AvailableOptions>\n                  <Option value=\"0\">Disabled</Option>\n                  <Option value=\"1\">L0s</Option>\n                  <Option value=\"2\">L1</Option>\n                  <Option value=\"3\">L0sL1</Option>\n                  <Option value=\"4\">Auto</Option>\n                </AvailableOptions>\n                <DefaultOption>Auto</DefaultOption>\n                <Help><![CDATA[Set the ASPM Level:\r\nForce L0s - Force all links to L0s State\r\nAUTO - BIOS auto configure\r\nDISABLE - Disables ASPM]]></Help>\n              </Information>\n            </Setting>\n            <Setting name=\"M.2-H_2 L1 Substates\" selectedOption=\"L1.1 &amp; L1.2\" type=\"Option\">\n              <Information>\n                <AvailableOptions>\n                  <Option value=\"0\">Disabled</Option>\n                  <Option value=\"1\">L1.1</Option>\n                  <Option value=\"2\">L1.1 &amp; L1.2</Option>\n                </AvailableOptions>\n                <DefaultOption>L1.1 &amp; L1.2</DefaultOption>\n                <Help><![CDATA[PCI Express L1 Substates settings.]]></Help>\n              </Information>\n            </Setting>\n            <Setting name=\"M.2-H_2 PCIe Speed\" selectedOption=\"Auto\" type=\"Option\">\n              <Information>\n                <AvailableOptions>\n                  <Option value=\"0\">Auto</Option>\n                  <Option value=\"1\">Gen1</Option>\n                  <Option value=\"2\">Gen2</Option>\n                  <Option value=\"3\">Gen3</Option>\n                </AvailableOptions>\n                <DefaultOption>Auto</DefaultOption>\n                <Help><![CDATA[Configure PCIe Speed]]></Help>\n              </Information>\n            </Setting>\n          </Menu>\n        </Menu>\n        <Subtitle></Subtitle>\n        <Setting name=\"Port 61h Bit-4 Emulation\" selectedOption=\"Enabled\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"0\">Disabled</Option>\n              <Option value=\"1\">Enabled</Option>\n            </AvailableOptions>\n            <DefaultOption>Enabled</DefaultOption>\n            <Help><![CDATA[Emulation of Port 61h bit-4 toggling in SMM]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"PCIe PLL SSC\" selectedOption=\"Enabled\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"255\">Enabled</Option>\n              <Option value=\"0\">Disabled</Option>\n            </AvailableOptions>\n            <DefaultOption>Enabled</DefaultOption>\n            <Help><![CDATA[Enables/Disables PCIe PLL SSC.]]></Help>\n          </Information>\n        </Setting>\n      </Menu>\n    </Menu>\n    <Menu name=\"Super IO Configuration\">\n      <Information>\n        <Help><![CDATA[System Super IO Chip Parameters.]]></Help>\n      </Information>\n      <Subtitle>Super IO Configuration</Subtitle>\n      <Subtitle></Subtitle>\n      <Text>Super IO Chip(AST2500)</Text>\n      <Menu name=\"Serial Port 1 Configuration\">\n        <Information>\n          <Help><![CDATA[Set Parameters of Serial Port 1 (COMA)]]></Help>\n        </Information>\n        <Subtitle>Serial Port 1 Configuration</Subtitle>\n        <Subtitle></Subtitle>\n        <Setting name=\"Serial Port\" order=\"1\" checkedStatus=\"Checked\" type=\"CheckBox\">\n          <!--Checked/Unchecked-->\n          <Information>\n            <DefaultStatus>Checked</DefaultStatus>\n            <Help><![CDATA[Enable or Disable Serial Port (COM)]]></Help>\n          </Information>\n        </Setting>\n        <Text>Device Settings(IO=3F8h; IRQ=4;)</Text>\n        <!--Valid if:  ( Serial Port$1 != 0 ) -->\n        <Subtitle></Subtitle>\n        <Setting name=\"Change Settings\" order=\"1\" selectedOption=\"Auto\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"0\">Auto</Option>\n              <Option value=\"1\">IO=3F8h; IRQ=4;</Option>\n              <Option value=\"3\">IO=2F8h; IRQ=4;</Option>\n              <Option value=\"4\">IO=3E8h; IRQ=4;</Option>\n              <Option value=\"5\">IO=2E8h; IRQ=4;</Option>\n            </AvailableOptions>\n            <DefaultOption>Auto</DefaultOption>\n            <Help><![CDATA[Select an optimal settings for Super IO Device]]></Help>\n            <WorkIf><![CDATA[ ( Serial Port$1 != 0 ) ]]></WorkIf>\n          </Information>\n        </Setting>\n      </Menu>\n      <Menu name=\"Serial Port 2 Configuration\">\n        <Information>\n          <Help><![CDATA[Set Parameters of Serial Port 2 (COMB)]]></Help>\n        </Information>\n        <Subtitle>Serial Port 2 Configuration</Subtitle>\n        <Subtitle></Subtitle>\n        <Setting name=\"Serial Port\" order=\"2\" checkedStatus=\"Checked\" type=\"CheckBox\">\n          <!--Checked/Unchecked-->\n          <Information>\n            <DefaultStatus>Checked</DefaultStatus>\n            <Help><![CDATA[Enable or Disable Serial Port (COM)]]></Help>\n          </Information>\n        </Setting>\n        <Text>Device Settings(IO=2F8h; IRQ=3;)</Text>\n        <!--Valid if:  ( Serial Port$2 != 0 ) -->\n        <Subtitle></Subtitle>\n        <Setting name=\"Change Settings\" order=\"2\" selectedOption=\"Auto\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"0\">Auto</Option>\n              <Option value=\"1\">IO=2F8h; IRQ=3;</Option>\n              <Option value=\"3\">IO=3F8h; IRQ=3;</Option>\n              <Option value=\"4\">IO=3E8h; IRQ=3;</Option>\n              <Option value=\"5\">IO=2E8h; IRQ=3;</Option>\n            </AvailableOptions>\n            <DefaultOption>Auto</DefaultOption>\n            <Help><![CDATA[Select an optimal settings for Super IO Device]]></Help>\n            <WorkIf><![CDATA[ ( Serial Port$2 != 0 ) ]]></WorkIf>\n          </Information>\n        </Setting>\n      </Menu>\n    </Menu>\n    <Menu name=\"Serial Port Console Redirection\">\n      <Information>\n        <Help><![CDATA[Serial Port Console Redirection]]></Help>\n      </Information>\n      <Subtitle></Subtitle>\n      <Subtitle>COM1</Subtitle>\n      <Setting name=\"Console Redirection\" order=\"1\" checkedStatus=\"Unchecked\" type=\"CheckBox\">\n        <!--Checked/Unchecked-->\n        <Information>\n          <DefaultStatus>Unchecked</DefaultStatus>\n          <Help><![CDATA[Console Redirection Enable or Disable.]]></Help>\n        </Information>\n      </Setting>\n      <Menu name=\"Console Redirection Settings\" order=\"1\">\n        <Information>\n          <Help><![CDATA[The settings specify how the host computer and the remote computer (which the user is using) will exchange data. Both computers should have the same or compatible settings.]]></Help>\n          <WorkIf><![CDATA[ ( Console Redirection$1 != 0 ) ]]></WorkIf>\n        </Information>\n        <Subtitle>COM1</Subtitle>\n        <Subtitle>Console Redirection Settings</Subtitle>\n        <Subtitle></Subtitle>\n        <Setting name=\"Terminal Type\" order=\"1\" selectedOption=\"VT100+\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"0\">VT100</Option>\n              <Option value=\"1\">VT100+</Option>\n              <Option value=\"2\">VT-UTF8</Option>\n              <Option value=\"3\">ANSI</Option>\n            </AvailableOptions>\n            <DefaultOption>VT100+</DefaultOption>\n            <Help><![CDATA[Emulation: ANSI: Extended ASCII char set. VT100: ASCII char set. VT100+: Extends VT100 to support color, function keys, etc. VT-UTF8: Uses UTF8 encoding to map Unicode chars onto 1 or more bytes.]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"Bits per second\" order=\"1\" selectedOption=\"115200\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"3\">9600</Option>\n              <Option value=\"4\">19200</Option>\n              <Option value=\"5\">38400</Option>\n              <Option value=\"6\">57600</Option>\n              <Option value=\"7\">115200</Option>\n            </AvailableOptions>\n            <DefaultOption>115200</DefaultOption>\n            <Help><![CDATA[Selects serial port transmission speed. The speed must be matched on the other side. Long or noisy lines may require lower speeds.]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"Data Bits\" order=\"1\" selectedOption=\"8\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"7\">7</Option>\n              <Option value=\"8\">8</Option>\n            </AvailableOptions>\n            <DefaultOption>8</DefaultOption>\n            <Help><![CDATA[Data Bits]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"Parity\" order=\"1\" selectedOption=\"None\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"1\">None</Option>\n              <Option value=\"2\">Even</Option>\n              <Option value=\"3\">Odd</Option>\n              <Option value=\"4\">Mark</Option>\n              <Option value=\"5\">Space</Option>\n            </AvailableOptions>\n            <DefaultOption>None</DefaultOption>\n            <Help><![CDATA[A parity bit can be sent with the data bits to detect some transmission errors. Even: parity bit is 0 if the num of 1's in the data bits is even. Odd: parity bit is 0 if num of 1's in the data bits is odd.  Mark: parity bit is always 1. Space: Parity bit is always 0. Mark and Space Parity do not allow for error detection. They can be used as an additional data bit.]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"Stop Bits\" order=\"1\" selectedOption=\"1\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"1\">1</Option>\n              <Option value=\"3\">2</Option>\n            </AvailableOptions>\n            <DefaultOption>1</DefaultOption>\n            <Help><![CDATA[Stop bits indicate the end of a serial data packet. (A start bit indicates the beginning). The standard setting is 1 stop bit. Communication with slow devices may require more than 1 stop bit.]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"Flow Control\" order=\"1\" selectedOption=\"None\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"0\">None</Option>\n              <Option value=\"1\">Hardware RTS/CTS</Option>\n            </AvailableOptions>\n            <DefaultOption>None</DefaultOption>\n            <Help><![CDATA[Flow control can prevent data loss from buffer overflow. When sending data, if the receiving buffers are full, a 'stop' signal can be sent to stop the data flow. Once the buffers are empty, a 'start' signal can be sent to re-start the flow. Hardware flow control uses two wires to send start/stop signals.]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"VT-UTF8 Combo Key Support\" order=\"1\" checkedStatus=\"Checked\" type=\"CheckBox\">\n          <!--Checked/Unchecked-->\n          <Information>\n            <DefaultStatus>Checked</DefaultStatus>\n            <Help><![CDATA[Enable VT-UTF8 Combination Key Support for ANSI/VT100 terminals]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"Recorder Mode\" order=\"1\" checkedStatus=\"Unchecked\" type=\"CheckBox\">\n          <!--Checked/Unchecked-->\n          <Information>\n            <DefaultStatus>Unchecked</DefaultStatus>\n            <Help><![CDATA[With this mode enabled only text will be sent. This is to capture Terminal data.]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"Resolution 100x31\" order=\"1\" checkedStatus=\"Checked\" type=\"CheckBox\">\n          <!--Checked/Unchecked-->\n          <Information>\n            <DefaultStatus>Checked</DefaultStatus>\n            <Help><![CDATA[Enables or disables extended terminal resolution]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"Putty KeyPad\" order=\"1\" selectedOption=\"VT100\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"1\">VT100</Option>\n              <Option value=\"2\">LINUX</Option>\n              <Option value=\"4\">XTERMR6</Option>\n              <Option value=\"8\">SCO</Option>\n              <Option value=\"16\">ESCN</Option>\n              <Option value=\"32\">VT400</Option>\n            </AvailableOptions>\n            <DefaultOption>VT100</DefaultOption>\n            <Help><![CDATA[Select FunctionKey and KeyPad on Putty.]]></Help>\n          </Information>\n        </Setting>\n        <Subtitle></Subtitle>\n      </Menu>\n      <Subtitle></Subtitle>\n      <Subtitle>SOL</Subtitle>\n      <Setting name=\"Console Redirection\" order=\"2\" checkedStatus=\"Checked\" type=\"CheckBox\">\n        <!--Checked/Unchecked-->\n        <Information>\n          <DefaultStatus>Checked</DefaultStatus>\n          <Help><![CDATA[Console Redirection Enable or Disable.]]></Help>\n        </Information>\n      </Setting>\n      <Menu name=\"Console Redirection Settings\" order=\"2\">\n        <Information>\n          <Help><![CDATA[The settings specify how the host computer and the remote computer (which the user is using) will exchange data. Both computers should have the same or compatible settings.]]></Help>\n          <WorkIf><![CDATA[ ( Console Redirection$2 != 0 ) ]]></WorkIf>\n        </Information>\n        <Subtitle>SOL</Subtitle>\n        <Subtitle>Console Redirection Settings</Subtitle>\n        <Subtitle></Subtitle>\n        <Setting name=\"Terminal Type\" order=\"2\" selectedOption=\"VT100+\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"0\">VT100</Option>\n              <Option value=\"1\">VT100+</Option>\n              <Option value=\"2\">VT-UTF8</Option>\n              <Option value=\"3\">ANSI</Option>\n            </AvailableOptions>\n            <DefaultOption>VT100+</DefaultOption>\n            <Help><![CDATA[Emulation: ANSI: Extended ASCII char set. VT100: ASCII char set. VT100+: Extends VT100 to support color, function keys, etc. VT-UTF8: Uses UTF8 encoding to map Unicode chars onto 1 or more bytes.]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"Bits per second\" order=\"2\" selectedOption=\"115200\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"3\">9600</Option>\n              <Option value=\"4\">19200</Option>\n              <Option value=\"5\">38400</Option>\n              <Option value=\"6\">57600</Option>\n              <Option value=\"7\">115200</Option>\n            </AvailableOptions>\n            <DefaultOption>115200</DefaultOption>\n            <Help><![CDATA[Selects serial port transmission speed. The speed must be matched on the other side. Long or noisy lines may require lower speeds.]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"Data Bits\" order=\"2\" selectedOption=\"8\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"7\">7</Option>\n              <Option value=\"8\">8</Option>\n            </AvailableOptions>\n            <DefaultOption>8</DefaultOption>\n            <Help><![CDATA[Data Bits]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"Parity\" order=\"2\" selectedOption=\"None\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"1\">None</Option>\n              <Option value=\"2\">Even</Option>\n              <Option value=\"3\">Odd</Option>\n              <Option value=\"4\">Mark</Option>\n              <Option value=\"5\">Space</Option>\n            </AvailableOptions>\n            <DefaultOption>None</DefaultOption>\n            <Help><![CDATA[A parity bit can be sent with the data bits to detect some transmission errors. Even: parity bit is 0 if the num of 1's in the data bits is even. Odd: parity bit is 0 if num of 1's in the data bits is odd.  Mark: parity bit is always 1. Space: Parity bit is always 0. Mark and Space Parity do not allow for error detection. They can be used as an additional data bit.]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"Stop Bits\" order=\"2\" selectedOption=\"1\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"1\">1</Option>\n              <Option value=\"3\">2</Option>\n            </AvailableOptions>\n            <DefaultOption>1</DefaultOption>\n            <Help><![CDATA[Stop bits indicate the end of a serial data packet. (A start bit indicates the beginning). The standard setting is 1 stop bit. Communication with slow devices may require more than 1 stop bit.]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"Flow Control\" order=\"2\" selectedOption=\"None\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"0\">None</Option>\n              <Option value=\"1\">Hardware RTS/CTS</Option>\n            </AvailableOptions>\n            <DefaultOption>None</DefaultOption>\n            <Help><![CDATA[Flow control can prevent data loss from buffer overflow. When sending data, if the receiving buffers are full, a 'stop' signal can be sent to stop the data flow. Once the buffers are empty, a 'start' signal can be sent to re-start the flow. Hardware flow control uses two wires to send start/stop signals.]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"VT-UTF8 Combo Key Support\" order=\"2\" checkedStatus=\"Checked\" type=\"CheckBox\">\n          <!--Checked/Unchecked-->\n          <Information>\n            <DefaultStatus>Checked</DefaultStatus>\n            <Help><![CDATA[Enable VT-UTF8 Combination Key Support for ANSI/VT100 terminals]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"Recorder Mode\" order=\"2\" checkedStatus=\"Unchecked\" type=\"CheckBox\">\n          <!--Checked/Unchecked-->\n          <Information>\n            <DefaultStatus>Unchecked</DefaultStatus>\n            <Help><![CDATA[With this mode enabled only text will be sent. This is to capture Terminal data.]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"Resolution 100x31\" order=\"2\" checkedStatus=\"Checked\" type=\"CheckBox\">\n          <!--Checked/Unchecked-->\n          <Information>\n            <DefaultStatus>Checked</DefaultStatus>\n            <Help><![CDATA[Enables or disables extended terminal resolution]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"Putty KeyPad\" order=\"2\" selectedOption=\"VT100\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"1\">VT100</Option>\n              <Option value=\"2\">LINUX</Option>\n              <Option value=\"4\">XTERMR6</Option>\n              <Option value=\"8\">SCO</Option>\n              <Option value=\"16\">ESCN</Option>\n              <Option value=\"32\">VT400</Option>\n            </AvailableOptions>\n            <DefaultOption>VT100</DefaultOption>\n            <Help><![CDATA[Select FunctionKey and KeyPad on Putty.]]></Help>\n          </Information>\n        </Setting>\n        <Subtitle></Subtitle>\n      </Menu>\n      <Subtitle></Subtitle>\n      <Subtitle>Legacy Console Redirection</Subtitle>\n      <Menu name=\"Legacy Console Redirection Settings\">\n        <Information>\n          <Help><![CDATA[Legacy Console Redirection Settings]]></Help>\n        </Information>\n        <Subtitle>Legacy Console Redirection Settings</Subtitle>\n        <Subtitle></Subtitle>\n        <Setting name=\"Redirection COM Port\" selectedOption=\"COM1\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"0\">COM1</Option>\n              <Option value=\"1\">SOL</Option>\n            </AvailableOptions>\n            <DefaultOption>COM1</DefaultOption>\n            <Help><![CDATA[Select a COM port to display redirection of Legacy OS and Legacy OPROM Messages]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"Legacy OS Redirection Resolution\" selectedOption=\"80x25\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"0\">80x24</Option>\n              <Option value=\"1\">80x25</Option>\n            </AvailableOptions>\n            <DefaultOption>80x25</DefaultOption>\n            <Help><![CDATA[On Legacy OS, the Number of Rows and Columns supported redirection]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"Redirection After BIOS POST\" selectedOption=\"Always Enable\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"0\">Always Enable</Option>\n              <Option value=\"1\">BootLoader</Option>\n            </AvailableOptions>\n            <DefaultOption>Always Enable</DefaultOption>\n            <Help><![CDATA[When Bootloader is selected, then Legacy Console Redirection is disabled before booting to legacy OS. When Always Enable is selected, then Legacy Console Redirection is enabled for legacy OS. Default setting for this option is set to Always Enable.]]></Help>\n          </Information>\n        </Setting>\n      </Menu>\n      <Subtitle></Subtitle>\n      <Subtitle>Serial Port for Out-of-Band Management/</Subtitle>\n      <Subtitle>Windows Emergency Management Services (EMS)</Subtitle>\n      <Setting name=\"Console Redirection\" order=\"3\" checkedStatus=\"Unchecked\" type=\"CheckBox\">\n        <!--Checked/Unchecked-->\n        <Information>\n          <DefaultStatus>Unchecked</DefaultStatus>\n          <Help><![CDATA[Console Redirection Enable or Disable.]]></Help>\n        </Information>\n      </Setting>\n      <Menu name=\"Console Redirection Settings\" order=\"3\">\n        <Information>\n          <Help><![CDATA[The settings specify how the host computer and the remote computer (which the user is using) will exchange data. Both computers should have the same or compatible settings.]]></Help>\n          <WorkIf><![CDATA[ ( Console Redirection$3 != 0 ) ]]></WorkIf>\n        </Information>\n        <Setting name=\"Out-of-Band Mgmt Port\" selectedOption=\"COM1\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"0\">COM1</Option>\n              <Option value=\"1\">SOL</Option>\n            </AvailableOptions>\n            <DefaultOption>COM1</DefaultOption>\n            <Help><![CDATA[Microsoft Windows Emergency Management Services (EMS) allows for remote management of a Windows Server OS through a serial port.]]></Help>\n          </Information>\n        </Setting>\n        <Setting name=\"Terminal Type\" order=\"3\" selectedOption=\"VT-UTF8\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"0\">VT100</Option>\n              <Option value=\"1\">VT100+</Option>\n              <Option value=\"2\">VT-UTF8</Option>\n              <Option value=\"3\">ANSI</Option>\n            </AvailableOptions>\n            <DefaultOption>VT-UTF8</DefaultOption>\n            <Help><![CDATA[VT-UTF8 is the preferred terminal type for out-of-band management. The next best choice is VT100+ and then VT100. See above, in Console Redirection Settings page, for more Help with Terminal Type/Emulation.]]></Help>\n            <WorkIf><![CDATA[ ( Console Redirection$3 != 0 ) ]]></WorkIf>\n          </Information>\n        </Setting>\n        <Setting name=\"Bits per second\" order=\"3\" selectedOption=\"115200\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"3\">9600</Option>\n              <Option value=\"4\">19200</Option>\n              <Option value=\"6\">57600</Option>\n              <Option value=\"7\">115200</Option>\n            </AvailableOptions>\n            <DefaultOption>115200</DefaultOption>\n            <Help><![CDATA[Selects serial port transmission speed. The speed must be matched on the other side. Long or noisy lines may require lower speeds.]]></Help>\n            <WorkIf><![CDATA[ ( Console Redirection$3 != 0 ) ]]></WorkIf>\n          </Information>\n        </Setting>\n        <Setting name=\"Flow Control\" order=\"3\" selectedOption=\"None\" type=\"Option\">\n          <Information>\n            <AvailableOptions>\n              <Option value=\"0\">None</Option>\n              <Option value=\"1\">Hardware RTS/CTS</Option>\n              <Option value=\"2\">Software Xon/Xoff</Option>\n            </AvailableOptions>\n            <DefaultOption>None</DefaultOption>\n            <Help><![CDATA[Flow control can prevent data loss from buffer overflow. When sending data, if the receiving buffers are full, a 'stop' signal can be sent to stop the data flow. Once the buffers are empty, a 'start' signal can be sent to re-start the flow. Hardware flow control uses two wires to send start/stop signals.]]></Help>\n            <WorkIf><![CDATA[ ( Console Redirection$3 != 0 ) ]]></WorkIf>\n          </Information>\n        </Setting>\n        <Text>Data Bits(8)</Text>\n        <!--Valid if:  ( Console Redirection$3 != 0 ) -->\n        <Text>Parity(None)</Text>\n        <!--Valid if:  ( Console Redirection$3 != 0 ) -->\n        <Text>Stop Bits(1)</Text>\n        <!--Valid if:  ( Console Redirection$3 != 0 ) -->\n      </Menu>\n    </Menu>\n    <Menu name=\"SATA And RSTe Configuration\">\n      <Information>\n        <Help><![CDATA[SATA Device Options Settings]]></Help>\n      </Information>\n      <Subtitle>SATA And RSTe Configuration</Subtitle>\n      <Subtitle></Subtitle>\n      <Setting name=\"SATA Controller(s)\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"1\">Enabled</Option>\n            <Option value=\"0\">Disabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Enable/Disable SATA Device.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"SATA Mode Selection\" selectedOption=\"AHCI\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">AHCI</Option>\n            <Option value=\"1\">RAID</Option>\n          </AvailableOptions>\n          <DefaultOption>AHCI</DefaultOption>\n          <Help><![CDATA[Determines how SATA controller(s) operate.]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"SATA Interrupt Selection\" selectedOption=\"MSI\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">MSI-x</Option>\n            <Option value=\"1\">MSI</Option>\n            <Option value=\"2\">Legacy</Option>\n          </AvailableOptions>\n          <DefaultOption>MSI</DefaultOption>\n          <Help><![CDATA[Select which interrupt will be available to OS. This option only takes effect if SATA controller is in RAID mode]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) and ( SATA Mode Selection == 1 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"Aggressive LPM Support\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[Enable PCH to aggressively enter link power state.]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"Storage Option ROM/UEFI Driver\" selectedOption=\"Legacy\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Do not launch</Option>\n            <Option value=\"1\">UEFI</Option>\n            <Option value=\"2\">Legacy</Option>\n          </AvailableOptions>\n          <DefaultOption>Legacy</DefaultOption>\n          <Help><![CDATA[Controls the execution of UEFI and Legacy Storage OpROM]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Text>Serial ATA Port 0(Micron_5300_MT (480.1GB))</Text>\n      <!--Valid if:  ( SATA Controller(s) != 0 ) -->\n      <Text>  Software Preserve(SUPPORTED)</Text>\n      <!--Valid if:  ( SATA Controller(s) != 0 ) -->\n      <Setting name=\"  Port 0 Hot Plug\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Designates this port as Hot Pluggable.]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  Port 0 Spin Up Device\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[If enabled for any of ports Staggerred Spin Up will be performed and only the drives which have this option enabled will spin up at boot. Otherwise all drives spin up at boot.]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  Port 0 SATA Device Type\" selectedOption=\"Hard Disk Drive\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Hard Disk Drive</Option>\n            <Option value=\"1\">Solid State Drive</Option>\n          </AvailableOptions>\n          <DefaultOption>Hard Disk Drive</DefaultOption>\n          <Help><![CDATA[Identify the SATA port is connected to Solid State Drive or Hard Disk Drive]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Text>Serial ATA Port 1(Micron_5300_MT (480.1GB))</Text>\n      <!--Valid if:  ( SATA Controller(s) != 0 ) -->\n      <Text>  Software Preserve(SUPPORTED)</Text>\n      <!--Valid if:  ( SATA Controller(s) != 0 ) -->\n      <Setting name=\"  Port 1 Hot Plug\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Designates this port as Hot Pluggable.]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  Port 1 Spin Up Device\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[If enabled for any of ports Staggerred Spin Up will be performed and only the drives which have this option enabled will spin up at boot. Otherwise all drives spin up at boot.]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  Port 1 SATA Device Type\" selectedOption=\"Hard Disk Drive\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Hard Disk Drive</Option>\n            <Option value=\"1\">Solid State Drive</Option>\n          </AvailableOptions>\n          <DefaultOption>Hard Disk Drive</DefaultOption>\n          <Help><![CDATA[Identify the SATA port is connected to Solid State Drive or Hard Disk Drive]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Text>Serial ATA Port 2(Empty)</Text>\n      <!--Valid if:  ( SATA Controller(s) != 0 ) -->\n      <Text>  Software Preserve(Unknown)</Text>\n      <!--Valid if:  ( SATA Controller(s) != 0 ) -->\n      <Setting name=\"  Port 2 Hot Plug\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Designates this port as Hot Pluggable.]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  Port 2 Spin Up Device\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[If enabled for any of ports Staggerred Spin Up will be performed and only the drives which have this option enabled will spin up at boot. Otherwise all drives spin up at boot.]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  Port 2 SATA Device Type\" selectedOption=\"Hard Disk Drive\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Hard Disk Drive</Option>\n            <Option value=\"1\">Solid State Drive</Option>\n          </AvailableOptions>\n          <DefaultOption>Hard Disk Drive</DefaultOption>\n          <Help><![CDATA[Identify the SATA port is connected to Solid State Drive or Hard Disk Drive]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Text>Serial ATA Port 3(Empty)</Text>\n      <!--Valid if:  ( SATA Controller(s) != 0 ) -->\n      <Text>  Software Preserve(Unknown)</Text>\n      <!--Valid if:  ( SATA Controller(s) != 0 ) -->\n      <Setting name=\"  Port 3 Hot Plug\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Designates this port as Hot Pluggable.]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  Port 3 Spin Up Device\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[If enabled for any of ports Staggerred Spin Up will be performed and only the drives which have this option enabled will spin up at boot. Otherwise all drives spin up at boot.]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  Port 3 SATA Device Type\" selectedOption=\"Hard Disk Drive\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Hard Disk Drive</Option>\n            <Option value=\"1\">Solid State Drive</Option>\n          </AvailableOptions>\n          <DefaultOption>Hard Disk Drive</DefaultOption>\n          <Help><![CDATA[Identify the SATA port is connected to Solid State Drive or Hard Disk Drive]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Text>Serial ATA Port 4(Empty)</Text>\n      <!--Valid if:  ( SATA Controller(s) != 0 ) -->\n      <Text>  Software Preserve(Unknown)</Text>\n      <!--Valid if:  ( SATA Controller(s) != 0 ) -->\n      <Setting name=\"  Port 4 Hot Plug\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Designates this port as Hot Pluggable.]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  Port 4 Spin Up Device\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[If enabled for any of ports Staggerred Spin Up will be performed and only the drives which have this option enabled will spin up at boot. Otherwise all drives spin up at boot.]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  Port 4 SATA Device Type\" selectedOption=\"Hard Disk Drive\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Hard Disk Drive</Option>\n            <Option value=\"1\">Solid State Drive</Option>\n          </AvailableOptions>\n          <DefaultOption>Hard Disk Drive</DefaultOption>\n          <Help><![CDATA[Identify the SATA port is connected to Solid State Drive or Hard Disk Drive]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Text>Serial ATA Port 5(Empty)</Text>\n      <!--Valid if:  ( SATA Controller(s) != 0 ) -->\n      <Text>  Software Preserve(Unknown)</Text>\n      <!--Valid if:  ( SATA Controller(s) != 0 ) -->\n      <Setting name=\"  Port 5 Hot Plug\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Designates this port as Hot Pluggable.]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  Port 5 Spin Up Device\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[If enabled for any of ports Staggerred Spin Up will be performed and only the drives which have this option enabled will spin up at boot. Otherwise all drives spin up at boot.]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  Port 5 SATA Device Type\" selectedOption=\"Hard Disk Drive\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Hard Disk Drive</Option>\n            <Option value=\"1\">Solid State Drive</Option>\n          </AvailableOptions>\n          <DefaultOption>Hard Disk Drive</DefaultOption>\n          <Help><![CDATA[Identify the SATA port is connected to Solid State Drive or Hard Disk Drive]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Text>Serial ATA Port 6(Empty)</Text>\n      <!--Valid if:  ( SATA Controller(s) != 0 ) -->\n      <Text>  Software Preserve(Unknown)</Text>\n      <!--Valid if:  ( SATA Controller(s) != 0 ) -->\n      <Setting name=\"  Port 6 Hot Plug\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Designates this port as Hot Pluggable.]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  Port 6 Spin Up Device\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[If enabled for any of ports Staggerred Spin Up will be performed and only the drives which have this option enabled will spin up at boot. Otherwise all drives spin up at boot.]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  Port 6 SATA Device Type\" selectedOption=\"Hard Disk Drive\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Hard Disk Drive</Option>\n            <Option value=\"1\">Solid State Drive</Option>\n          </AvailableOptions>\n          <DefaultOption>Hard Disk Drive</DefaultOption>\n          <Help><![CDATA[Identify the SATA port is connected to Solid State Drive or Hard Disk Drive]]></Help>\n          <WorkIf><![CDATA[ ( SATA Controller(s) != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n    </Menu>\n    <Menu name=\"PCH-FW Configuration\">\n      <Information>\n        <Help><![CDATA[Advanced Mode]]></Help>\n      </Information>\n      <Subtitle>PCH-FW Configuration</Subtitle>\n      <Text>Operational Firmware Version(5.1.4.700)</Text>\n      <Text>Backup Firmware Version(N/A)</Text>\n      <Text>Recovery Firmware Version(5.1.4.700)</Text>\n      <Text>ME Firmware Features(SiEn\r\nNM\r\nPECIProxy\r\nICC\r\nMeStorageServices\r\nBootGuard\r\nPmBusProxy\r\nHSIO\r\nPCHDebug\r\nPowerThermalUtility\r\nPCHThermalSensorInit\r\nDeepSx\r\nDirectMeUpdate\r\nTelemetryHub\r\n)</Text>\n      <Text>ME Firmware Status #1(0x00000255)</Text>\n      <Text>ME Firmware Status #2(0x89118027)</Text>\n      <Text>  Current State(Operational)</Text>\n      <Text>  Error Code(No Error)</Text>\n    </Menu>\n    <Menu name=\"ACPI Settings\">\n      <Information>\n        <Help><![CDATA[]]></Help>\n      </Information>\n      <Setting name=\"WHEA Support\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Enable or disable Windows Hardware Error Architecture.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"High Precision Event Timer\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Enable or Disable the High Precision Event Timer.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Native PCIE Enable\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Bit - PCIe Native * control\r\n 0 - ~ Hot Plug\r\n 1 - SHPC Native Hot Plug control\r\n 2 - ~ Power Management Events\r\n 3 - PCIe Advanced Error Reporting control\r\n 4 - PCIe Capability Structure control\r\n 5 - Latency Tolerance Reporting control]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Native ASPM\" selectedOption=\"Auto\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"2\">Auto</Option>\n            <Option value=\"1\">Enabled</Option>\n            <Option value=\"0\">Disabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Auto</DefaultOption>\n          <Help><![CDATA[Enabled - OS Controlled ASPM, Disabled - BIOS Controlled ASPM]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"PCI AER Support\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[Enable/Disable ACPI OS to natively manage PCI Advanced Error Reporting.]]></Help>\n        </Information>\n      </Setting>\n    </Menu>\n    <Menu name=\"USB Configuration\">\n      <Information>\n        <Help><![CDATA[USB Configuration Parameters]]></Help>\n      </Information>\n      <Subtitle>USB Configuration</Subtitle>\n      <Subtitle></Subtitle>\n      <Text>USB Module Version(23)</Text>\n      <Subtitle></Subtitle>\n      <Text>USB Controllers:()</Text>\n      <Subtitle>      1 XHCI</Subtitle>\n      <Text>USB Devices:()</Text>\n      <Subtitle>      1 Keyboard, 1 Mouse, 1 Hub</Subtitle>\n      <Subtitle></Subtitle>\n      <Setting name=\"Legacy USB Support\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Enabled</Option>\n            <Option value=\"1\">Disabled</Option>\n            <Option value=\"2\">Auto</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Enables Legacy USB support. AUTO option disables legacy support if no USB devices are connected. DISABLE option will keep USB devices available only for EFI applications.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"XHCI Hand-off\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"1\">Enabled</Option>\n            <Option value=\"0\">Disabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[This is a workaround for OSes without XHCI hand-off support. The XHCI ownership change should be claimed by XHCI driver.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"USB Mass Storage Driver Support\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Enable/Disable USB Mass Storage Driver Support.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Port 60/64 Emulation\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Enables I/O port 60h/64h emulation support. This should be enabled for the complete USB keyboard legacy support for non-USB aware OSes.]]></Help>\n        </Information>\n      </Setting>\n    </Menu>\n    <Menu name=\"PCIe/PCI/PnP Configuration\">\n      <Information>\n        <Help><![CDATA[PCI Hot-Plug Settings]]></Help>\n      </Information>\n      <Subtitle>Option ROM execution</Subtitle>\n      <Setting name=\"PCI PERR/SERR Support\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[PCI PERR/SERR enable/disable.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Above 4GB MMIO BIOS assignment\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"1\">Enabled</Option>\n            <Option value=\"0\">Disabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[Enable/Disable above 4GB MemoryMappedIO BIOS assignment\r\n\r\nThis is enabled automatically when Aperture Size is set to 2048MB.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"BME DMA Mitigation\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[Re-enable Bus Master Attribute disabled during Pci enumeration for PCI Bridges after SMM Locked ]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"VGA Priority\" selectedOption=\"Onboard\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"1\">Onboard</Option>\n            <Option value=\"2\">Offboard</Option>\n          </AvailableOptions>\n          <DefaultOption>Onboard</DefaultOption>\n          <Help><![CDATA[Select active Video type]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Onboard Video Option ROM\" selectedOption=\"Legacy\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"1\">Legacy</Option>\n            <Option value=\"2\">EFI</Option>\n            <Option value=\"0\">Disabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Legacy</DefaultOption>\n          <Help><![CDATA[Select which onboard video firmware type to be loaded.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"NVMe Firmware Source\" selectedOption=\"Vendor Defined Firmware\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Vendor Defined Firmware</Option>\n            <Option value=\"1\">AMI Native Support</Option>\n          </AvailableOptions>\n          <DefaultOption>Vendor Defined Firmware</DefaultOption>\n          <Help><![CDATA[AMI Native FW Support or Device Vendor Defined FW Support]]></Help>\n        </Information>\n      </Setting>\n      <Subtitle></Subtitle>\n      <Subtitle>PCIe/PCI/PnP Configuration</Subtitle>\n      <Setting name=\"RSC-RR1U-E16 PCI-E 3.0 X16 OPROM\" selectedOption=\"Legacy\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Legacy</Option>\n            <Option value=\"2\">EFI</Option>\n          </AvailableOptions>\n          <DefaultOption>Legacy</DefaultOption>\n          <Help><![CDATA[Enable or Disable RSC-RR1U-E16 OPROM option.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"M.2-P_1 OPROM\" selectedOption=\"Legacy\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Legacy</Option>\n            <Option value=\"2\">EFI</Option>\n          </AvailableOptions>\n          <DefaultOption>Legacy</DefaultOption>\n          <Help><![CDATA[Enables or disables M.2-P_1 OPROM option.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"M.2-H_2 OPROM\" selectedOption=\"Legacy\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Legacy</Option>\n            <Option value=\"2\">EFI</Option>\n          </AvailableOptions>\n          <DefaultOption>Legacy</DefaultOption>\n          <Help><![CDATA[Enables or disables M.2-H_2 OPROM option.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Onboard LAN Option ROM Type\" selectedOption=\"Legacy\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Legacy</Option>\n            <Option value=\"1\">EFI</Option>\n          </AvailableOptions>\n          <DefaultOption>Legacy</DefaultOption>\n          <Help><![CDATA[Select which firmware type to be loaded for onboard LANs]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Onboard LAN1 Option ROM\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">PXE</Option>\n            <Option value=\"2\">iSCSI</Option>\n          </AvailableOptions>\n          <DefaultOption>PXE</DefaultOption>\n          <Help><![CDATA[Select which firmware function to be loaded for onboard LAN1.]]></Help>\n          <WorkIf><![CDATA[ ( Onboard LAN Option ROM Type != 1 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"Onboard LAN2 Option ROM\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">PXE</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[Select which firmware function to be loaded for onboard LAN2]]></Help>\n          <WorkIf><![CDATA[ ( Onboard LAN Option ROM Type != 1 ) and ( Onboard LAN1 Option ROM <= 1 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"Network Stack\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Enable/Disable UEFI Network Stack]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"IPv4 PXE Support\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Enable/Disable IPv4 PXE boot support. If disabled, IPv4 PXE boot support will not be available.]]></Help>\n          <WorkIf><![CDATA[ ( Network Stack != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"IPv4 HTTP Support\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[Enable/Disable IPv4 HTTP boot support. If disabled, IPv4 HTTP boot support will not be available.]]></Help>\n          <WorkIf><![CDATA[ ( Network Stack != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"IPv6 PXE Support\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Enable/Disable IPv6 PXE boot support. If disabled, IPv6 PXE boot support will not be available.]]></Help>\n          <WorkIf><![CDATA[ ( Network Stack != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"IPv6 HTTP Support\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[Enable/Disable IPv6 HTTP boot support. If disabled, IPv6 HTTP boot support will not be available.]]></Help>\n          <WorkIf><![CDATA[ ( Network Stack != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"IPSEC Certificate\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[Support to Enable/Disable IPSEC certificate for Ikev.]]></Help>\n          <WorkIf><![CDATA[ ( Network Stack != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"PXE boot wait time\" numericValue=\"0\" type=\"Numeric\">\n        <Information>\n          <MaxValue>5</MaxValue>\n          <MinValue>0</MinValue>\n          <StepSize>1</StepSize>\n          <DefaultValue>0</DefaultValue>\n          <Help><![CDATA[Wait time in seconds to press ESC key to abort the PXE boot. Use either +/- or numeric keys to set the value.]]></Help>\n          <WorkIf><![CDATA[ ( Network Stack != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"Media detect count\" numericValue=\"1\" type=\"Numeric\">\n        <Information>\n          <MaxValue>50</MaxValue>\n          <MinValue>1</MinValue>\n          <StepSize>1</StepSize>\n          <DefaultValue>1</DefaultValue>\n          <Help><![CDATA[Number of times the presence of media will be checked. Use either +/- or numeric keys to set the value.]]></Help>\n          <WorkIf><![CDATA[ ( Network Stack != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n    </Menu>\n    <Menu name=\"Network Configuration\">\n      <Information>\n        <Help><![CDATA[Network Configuration Settings]]></Help>\n        <WorkIf><![CDATA[ ( Onboard LAN Option ROM Type != 0 ) ]]></WorkIf>\n      </Information>\n    </Menu>\n    <Menu name=\"Trusted Computing\">\n      <Information>\n        <Help><![CDATA[Trusted Computing Settings]]></Help>\n      </Information>\n      <Subtitle>  TPM20 Device Found</Subtitle>\n      <Text>  Firmware Version:(7.62)</Text>\n      <Text>  Vendor:(IFX)</Text>\n      <Subtitle></Subtitle>\n      <Setting name=\"  Security Device Support\" selectedOption=\"Enable\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disable</Option>\n            <Option value=\"1\">Enable</Option>\n          </AvailableOptions>\n          <DefaultOption>Enable</DefaultOption>\n          <Help><![CDATA[Enables or Disables BIOS support for security device. O.S. will not show Security Device. TCG EFI protocol and INT1A interface will not be available.]]></Help>\n        </Information>\n      </Setting>\n      <Text>  Active PCR banks(SHA-1,SHA256)</Text>\n      <!--Valid if:  (   Security Device Support != 0 ) -->\n      <Text>  Available PCR banks(SHA-1,SHA256)</Text>\n      <!--Valid if:  (   Security Device Support != 0 ) -->\n      <Subtitle></Subtitle>\n      <Setting name=\"  SHA-1 PCR Bank\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Enable or Disable SHA-1 PCR Bank]]></Help>\n          <WorkIf><![CDATA[ (   TPM2.0 UEFI Spec Version != 1 ) and (   Security Device Support != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  SHA256 PCR Bank\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"2\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Enable or Disable SHA256 PCR Bank]]></Help>\n          <WorkIf><![CDATA[ (   TPM2.0 UEFI Spec Version != 1 ) and (   Security Device Support != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Subtitle></Subtitle>\n      <Setting name=\"  Pending Operation\" selectedOption=\"None\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">None</Option>\n            <Option value=\"1\">TPM Clear</Option>\n          </AvailableOptions>\n          <DefaultOption>None</DefaultOption>\n          <Help><![CDATA[Schedule an Operation for the Security Device. NOTE: Your Computer will reboot during restart in order to change State of Security Device.]]></Help>\n          <WorkIf><![CDATA[ (   Security Device Support != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  Platform Hierarchy\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Enable or Disable Platform Hierarchy]]></Help>\n          <WorkIf><![CDATA[ (   Security Device Support != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  Storage Hierarchy\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Enable or Disable Storage Hierarchy]]></Help>\n          <WorkIf><![CDATA[ (   Security Device Support != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  Endorsement Hierarchy\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Enable or Disable Endorsement Hierarchy]]></Help>\n          <WorkIf><![CDATA[ (   Security Device Support != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  TPM2.0 UEFI Spec Version\" selectedOption=\"TCG_2\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"1\">TCG_1_2</Option>\n            <Option value=\"2\">TCG_2</Option>\n          </AvailableOptions>\n          <DefaultOption>TCG_2</DefaultOption>\n          <Help><![CDATA[Select the TCG2 Spec Version Support,\r\n\r\n\rTCG_1_2: the Compatible mode for Win8/Win10,\r\n\r\n\rTCG_2: Support new TCG2 protocol and event format for Win10 or later]]></Help>\n          <WorkIf><![CDATA[ (   Security Device Support != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  Physical Presence Spec Version\" selectedOption=\"1.3\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">1.2</Option>\n            <Option value=\"1\">1.3</Option>\n          </AvailableOptions>\n          <DefaultOption>1.3</DefaultOption>\n          <Help><![CDATA[  Select to Tell O.S. to support PPI Spec Version 1.2 or 1.3. Note some HCK tests might not support 1.3.]]></Help>\n          <WorkIf><![CDATA[ (   Security Device Support != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  PH Randomization\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Enables or Disables Platform Hierarchy randomization. DO NOT ENABLE THIS QUESTION IN PRODUCTION PLATFORMS. THIS IS FOR DEVELOPMENT TESTING. OVERRIDE ChangePlatformAuth ELINK for production platforms supporting TXT.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"  Device Select\" selectedOption=\"Auto\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">TPM 1.2</Option>\n            <Option value=\"1\">TPM 2.0</Option>\n            <Option value=\"2\">Auto</Option>\n          </AvailableOptions>\n          <DefaultOption>Auto</DefaultOption>\n          <Help><![CDATA[TPM 1.2 will restrict support to TPM 1.2 devices, TPM 2.0 will restrict support to TPM 2.0 devices, Auto will support both with the default set to TPM 2.0 devices if not found, TPM 1.2 devices will be enumerated ]]></Help>\n          <WorkIf><![CDATA[ (   Security Device Support != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"  Supermicro BIOS-Based TPM Provision Support\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[Notice!!! Enabling this setup option will lock your TPM for production platform. You will not be able to delete the NV indexes.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"  TXT Support\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[Enables utilization of additional hardware capabilities provided by Intel (R) Trusted Execution Technology.\r\n\r\nChanges require a full power cycle to take effect.]]></Help>\n          <WorkIf><![CDATA[ ( ( ( ( Intel (VMX) Virtualization Technology != 0 ) && ( VT-d != 0 ) ) && ( Active Processor Cores == 0 ) ) && ( Hyper-Threading != 0 ) ) ]]></WorkIf>\n        </Information>\n      </Setting>\n    </Menu>\n    <Menu name=\"HTTP BOOT Configuration\">\n      <Information>\n        <Help><![CDATA[HTTP BOOT Settings]]></Help>\n      </Information>\n      <Subtitle>HTTP BOOT Configuration</Subtitle>\n      <Setting name=\"HTTP Boot One Time\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[After create Http Boot Option, it will auto boot into HttpBoot in first time]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Input the description\" type=\"String\">\n        <Information>\n          <MinSize>0</MinSize>\n          <MaxSize>75</MaxSize>\n          <DefaultString></DefaultString>\n          <Help><![CDATA[]]></Help>\n          <AllowingMultipleLine>False</AllowingMultipleLine>\n        </Information>\n        <StringValue><![CDATA[]]></StringValue>\n      </Setting>\n      <Setting name=\"Boot URI\" type=\"String\">\n        <Information>\n          <MinSize>0</MinSize>\n          <MaxSize>80</MaxSize>\n          <DefaultString></DefaultString>\n          <Help><![CDATA[A new Boot Option will be created according to this Boot URI.]]></Help>\n          <AllowingMultipleLine>False</AllowingMultipleLine>\n        </Information>\n        <StringValue><![CDATA[]]></StringValue>\n      </Setting>\n    </Menu>\n    <Subtitle></Subtitle>\n  </Menu>\n  <Menu name=\"Event Logs\">\n    <Information />\n    <Menu name=\"Change SMBIOS Event Log Settings\">\n      <Information>\n        <Help><![CDATA[Press <Enter> to change the SMBIOS Event Log configuration.]]></Help>\n      </Information>\n      <Subtitle>Enabling/Disabling Options</Subtitle>\n      <Setting name=\"SMBIOS Event Log\" selectedOption=\"Enabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Enabled</DefaultOption>\n          <Help><![CDATA[Change this to enable or disable all features of SMBIOS Event Logging during boot.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Memory Corrected Error Enabling\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[Enable/Disable the runtime event for memory correctable errors.]]></Help>\n        </Information>\n      </Setting>\n      <Subtitle></Subtitle>\n      <Subtitle>Erasing Settings</Subtitle>\n      <Setting name=\"Erase Event Log\" selectedOption=\"No\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">No</Option>\n            <Option value=\"1\">Yes, Next reset</Option>\n            <Option value=\"2\">Yes, Every reset</Option>\n          </AvailableOptions>\n          <DefaultOption>No</DefaultOption>\n          <Help><![CDATA[Choose options for erasing SMBIOS Event Log.  Erasing is done prior to any logging activation during reset.]]></Help>\n          <WorkIf><![CDATA[ ( SMBIOS Event Log != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"When Log is Full\" selectedOption=\"Do Nothing\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Do Nothing</Option>\n            <Option value=\"1\">Erase Immediately</Option>\n          </AvailableOptions>\n          <DefaultOption>Do Nothing</DefaultOption>\n          <Help><![CDATA[Choose options for reactions to a full SMBIOS Event Log.]]></Help>\n          <WorkIf><![CDATA[ ( SMBIOS Event Log != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Subtitle></Subtitle>\n      <Subtitle>SMBIOS Event Log Standard Settings</Subtitle>\n      <Setting name=\"Log System Boot Event\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"1\">Enabled</Option>\n            <Option value=\"0\">Disabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[Choose option to enable/disable logging of System boot event]]></Help>\n          <WorkIf><![CDATA[ ( SMBIOS Event Log != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"MECI\" numericValue=\"1\" type=\"Numeric\">\n        <Information>\n          <MaxValue>255</MaxValue>\n          <MinValue>1</MinValue>\n          <StepSize>1</StepSize>\n          <DefaultValue>1</DefaultValue>\n          <Help><![CDATA[Mutiple Event Count Increment:  The number of occurrences of a duplicate event that must pass before the multiple-event counter of log entry is updated.The value ranges from 1 to 255.]]></Help>\n          <WorkIf><![CDATA[ ( SMBIOS Event Log != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n      <Setting name=\"METW\" numericValue=\"60\" type=\"Numeric\">\n        <Information>\n          <MaxValue>99</MaxValue>\n          <MinValue>0</MinValue>\n          <StepSize>1</StepSize>\n          <DefaultValue>60</DefaultValue>\n          <Help><![CDATA[Mutiple Event Time Window:  The number of minutes which must pass between duplicate log entries which utilize a multiple-event counter. The value ranges from 0 to 99 minutes.]]></Help>\n          <WorkIf><![CDATA[ ( SMBIOS Event Log != 0 ) ]]></WorkIf>\n        </Information>\n      </Setting>\n    </Menu>\n    <Menu name=\"View SMBIOS Event Log\">\n      <Information>\n        <Help><![CDATA[Press <Enter> to view the SMBIOS Event Log records.]]></Help>\n      </Information>\n      <Subtitle>DATE     TIME      ERROR CODE  SEVERITY</Subtitle>\n      <Subtitle></Subtitle>\n    </Menu>\n    <Subtitle></Subtitle>\n  </Menu>\n  <Menu name=\"Security\">\n    <Information />\n    <Subtitle>Password Description</Subtitle>\n    <Subtitle></Subtitle>\n    <Subtitle>If the Administrator / User password is set, then this only limits access to Setup and is asked for when entering Setup. Please set Administrator password first in order for setting User password, if clear Administrator password, User password will be cleared as well. </Subtitle>\n    <Subtitle>The password length must be in the following range:</Subtitle>\n    <Text>Minimum length(3)</Text>\n    <Text>Maximum length(20)</Text>\n    <Subtitle></Subtitle>\n    <Setting name=\"Password Check\" selectedOption=\"Setup\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Setup</Option>\n          <Option value=\"1\">Always</Option>\n        </AvailableOptions>\n        <DefaultOption>Setup</DefaultOption>\n        <Help><![CDATA[Setup: Check password while invoking setup. Always: Check password while invoking setup as well as on each boot.]]></Help>\n      </Information>\n    </Setting>\n    <Setting name=\"Hard Drive Security Frozen\" selectedOption=\"Disabled\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"1\">Enabled</Option>\n          <Option value=\"0\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>Disabled</DefaultOption>\n        <Help><![CDATA[Enabled/Disabled Freeze Lock Security Feature]]></Help>\n      </Information>\n    </Setting>\n    <Subtitle></Subtitle>\n    <Menu name=\"Supermicro Security Erase Configuration\">\n      <Information>\n        <Help><![CDATA[]]></Help>\n      </Information>\n      <Text>Security Module Version(1.00)</Text>\n      <Text>HDD Name(Micron_5300_MTFDDAK480TDT)</Text>\n      <Text>HDD Serial Number(20422B887014)</Text>\n      <Text>Security Mode(SAT3 Supported)</Text>\n      <Text>Estimated Time(2 Minutes)</Text>\n      <Text>HDD User Pwd Status(NOT INSTALLED)</Text>\n      <Setting name=\"Security Function\" order=\"1\" selectedOption=\"Disable\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disable</Option>\n            <Option value=\"1\">Set Password</Option>\n            <Option value=\"2\">Security Erase - Password</Option>\n            <Option value=\"4\">Security Erase - Without Password</Option>\n          </AvailableOptions>\n          <DefaultOption>Disable</DefaultOption>\n          <Help><![CDATA[Password: When SATA user password was set, device and password can be erased by SATA user password.\r\nWithout Password: System will set a default password '111111111' for SATA user password, and erase device with '111111111'.\r\nIf SATA user password is set, system will return fail status.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Password\" order=\"1\" type=\"String\">\n        <Information>\n          <MinSize>0</MinSize>\n          <MaxSize>32</MaxSize>\n          <DefaultString></DefaultString>\n          <Help><![CDATA[Supermicro HDD Security function]]></Help>\n          <AllowingMultipleLine>False</AllowingMultipleLine>\n        </Information>\n        <StringValue><![CDATA[]]></StringValue>\n      </Setting>\n      <Subtitle></Subtitle>\n      <Text>HDD Name(Micron_5300_MTFDDAK480TDT)</Text>\n      <Text>HDD Serial Number(20422B8885C5)</Text>\n      <Text>Security Mode(SAT3 Supported)</Text>\n      <Text>Estimated Time(2 Minutes)</Text>\n      <Text>HDD User Pwd Status(NOT INSTALLED)</Text>\n      <Setting name=\"Security Function\" order=\"2\" selectedOption=\"Disable\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disable</Option>\n            <Option value=\"1\">Set Password</Option>\n            <Option value=\"2\">Security Erase - Password</Option>\n            <Option value=\"4\">Security Erase - Without Password</Option>\n          </AvailableOptions>\n          <DefaultOption>Disable</DefaultOption>\n          <Help><![CDATA[Password: When SATA user password was set, device and password can be erased by SATA user password.\r\nWithout Password: System will set a default password '111111111' for SATA user password, and erase device with '111111111'.\r\nIf SATA user password is set, system will return fail status.]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Password\" order=\"2\" type=\"String\">\n        <Information>\n          <MinSize>0</MinSize>\n          <MaxSize>32</MaxSize>\n          <DefaultString></DefaultString>\n          <Help><![CDATA[Supermicro HDD Security function]]></Help>\n          <AllowingMultipleLine>False</AllowingMultipleLine>\n        </Information>\n        <StringValue><![CDATA[]]></StringValue>\n      </Setting>\n      <Subtitle></Subtitle>\n    </Menu>\n    <Subtitle></Subtitle>\n    <Setting name=\"Administrator Password\" type=\"Password\">\n      <Information>\n        <Help><![CDATA[Set Administrator Password]]></Help>\n        <MinSize>3</MinSize>\n        <MaxSize>20</MaxSize>\n        <HasPassword>False</HasPassword>\n      </Information>\n      <NewPassword><![CDATA[]]></NewPassword>\n      <ConfirmNewPassword><![CDATA[]]></ConfirmNewPassword>\n    </Setting>\n    <Setting name=\"User Password\" type=\"Password\">\n      <Information>\n        <Help><![CDATA[Set User Password]]></Help>\n        <WorkIf><![CDATA[ ( Administrator Password != 0 ) ]]></WorkIf>\n        <MinSize>3</MinSize>\n        <MaxSize>20</MaxSize>\n        <HasPassword>False</HasPassword>\n      </Information>\n      <NewPassword><![CDATA[]]></NewPassword>\n      <ConfirmNewPassword><![CDATA[]]></ConfirmNewPassword>\n    </Setting>\n    <Subtitle></Subtitle>\n    <Subtitle></Subtitle>\n    <Subtitle></Subtitle>\n    <Menu name=\"SMC Secure Boot Configuration\">\n      <Information>\n        <Help><![CDATA[SecureBoot Option]]></Help>\n      </Information>\n      <Setting name=\"Secure Boot\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Enabled</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[Secure Boot feature is Active if Secure Boot is Enabled,\r\nPlatform Key(PK) is enrolled and the System is in User mode.\r\nThe mode change requires platform reset]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Reset Keys Type\" selectedOption=\"Disabled\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Disabled</Option>\n            <Option value=\"1\">Reset all keys to default</Option>\n            <Option value=\"2\">Delete all keys</Option>\n            <Option value=\"3\">Delete PK key</Option>\n          </AvailableOptions>\n          <DefaultOption>Disabled</DefaultOption>\n          <Help><![CDATA[Reset Keys Type]]></Help>\n        </Information>\n      </Setting>\n      <Text>System Mode(Setup)</Text>\n      <Text>Secure Boot(Not Active)</Text>\n      <Setting name=\"Secure Boot Mode\" selectedOption=\"Setup\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">Setup</Option>\n            <Option value=\"2\">Audit</Option>\n          </AvailableOptions>\n          <DefaultOption>Setup</DefaultOption>\n          <Help><![CDATA[Secure Boot Mode]]></Help>\n        </Information>\n      </Setting>\n    </Menu>\n    <Subtitle>HDD Security Configuration:</Subtitle>\n    <Menu name=\"P0:Micron_5300_MTFDDAK480TDT\">\n      <Information>\n        <Help><![CDATA[HDD Security Configuration for selected drive]]></Help>\n      </Information>\n      <Subtitle>HDD Password Description :</Subtitle>\n      <Subtitle></Subtitle>\n      <Subtitle>Allows Access to Set, Modify and Clear</Subtitle>\n      <Subtitle></Subtitle>\n      <Subtitle>HDD PASSWORD CONFIGURATION:</Subtitle>\n      <Subtitle></Subtitle>\n      <Text>Security Supported :(Yes)</Text>\n      <Text>Security Supported :(No)</Text>\n      <Text>Security Enabled   :(No)</Text>\n      <Text>Security Locked    :(Yes)</Text>\n      <Text>Security Locked    :(No)</Text>\n      <Text>Security Frozen    :(No)</Text>\n      <Text>HDD User Pwd Status:(INSTALLED)</Text>\n      <Text>HDD User Pwd Status:(NOT INSTALLED)</Text>\n      <Text>HDD Master Pwd Status :(NOT INSTALLED)</Text>\n      <Subtitle></Subtitle>\n      <Setting name=\"Set User Password\" order=\"1\" type=\"Password\">\n        <Information>\n          <Help><![CDATA[Set HDD User Password. \r\n*** Advisable to Power Cycle System after Setting Hard Disk Passwords ***.\r\nDiscard or Save changes option in setup does not have any impact on HDD when password is set or removed. If the 'Set HDD User Password' option is hidden, do power cycle to enable the option again]]></Help>\n          <MinSize>0</MinSize>\n          <MaxSize>32</MaxSize>\n          <HasPassword>False</HasPassword>\n        </Information>\n        <PasswordValue><![CDATA[]]></PasswordValue>\n      </Setting>\n      <Subtitle></Subtitle>\n      <Subtitle></Subtitle>\n    </Menu>\n    <Menu name=\"P1:Micron_5300_MTFDDAK480TDT\">\n      <Information>\n        <Help><![CDATA[HDD Security Configuration for selected drive]]></Help>\n      </Information>\n      <Subtitle>HDD Password Description :</Subtitle>\n      <Subtitle></Subtitle>\n      <Subtitle>Allows Access to Set, Modify and Clear</Subtitle>\n      <Subtitle></Subtitle>\n      <Subtitle>HDD PASSWORD CONFIGURATION:</Subtitle>\n      <Subtitle></Subtitle>\n      <Text>Security Supported :(No)</Text>\n      <Text>Security Enabled   :(Yes)</Text>\n      <Text>Security Locked    :(No)</Text>\n      <Text>Security Frozen    :(Yes)</Text>\n      <Text>Security Frozen    :(No)</Text>\n      <Text>HDD User Pwd Status:(NOT INSTALLED)</Text>\n      <Text>HDD Master Pwd Status :(INSTALLED)</Text>\n      <Text>HDD Master Pwd Status :(NOT INSTALLED)</Text>\n      <Subtitle></Subtitle>\n      <Setting name=\"Set User Password\" order=\"2\" type=\"Password\">\n        <Information>\n          <Help><![CDATA[Set HDD User Password. \r\n*** Advisable to Power Cycle System after Setting Hard Disk Passwords ***.\r\nDiscard or Save changes option in setup does not have any impact on HDD when password is set or removed. If the 'Set HDD User Password' option is hidden, do power cycle to enable the option again]]></Help>\n          <MinSize>0</MinSize>\n          <MaxSize>32</MaxSize>\n          <HasPassword>False</HasPassword>\n        </Information>\n        <PasswordValue><![CDATA[]]></PasswordValue>\n      </Setting>\n      <Subtitle></Subtitle>\n      <Subtitle></Subtitle>\n    </Menu>\n    <Subtitle></Subtitle>\n    <Subtitle></Subtitle>\n  </Menu>\n  <Menu name=\"Boot\">\n    <Information />\n    <Subtitle>Boot Configuration</Subtitle>\n    <Setting name=\"Setup Prompt Timeout\" numericValue=\"1\" type=\"Numeric\">\n      <Information>\n        <MaxValue>65535</MaxValue>\n        <MinValue>1</MinValue>\n        <StepSize>1</StepSize>\n        <DefaultValue>1</DefaultValue>\n        <Help><![CDATA[Number of seconds to wait for setup activation key. 65535(0xFFFF) means indefinite waiting.]]></Help>\n      </Information>\n    </Setting>\n    <Subtitle></Subtitle>\n    <Setting name=\"Boot mode select\" selectedOption=\"LEGACY\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">LEGACY</Option>\n          <Option value=\"1\">UEFI</Option>\n          <Option value=\"2\">DUAL</Option>\n        </AvailableOptions>\n        <DefaultOption>DUAL</DefaultOption>\n        <Help><![CDATA[Select boot mode LEGACY/UEFI]]></Help>\n      </Information>\n    </Setting>\n    <Subtitle></Subtitle>\n    <Subtitle>FIXED BOOT ORDER Priorities</Subtitle>\n    <Setting name=\"UEFI Boot Option #1\" selectedOption=\"UEFI Hard Disk\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">UEFI Hard Disk</Option>\n          <Option value=\"1\">UEFI CD/DVD</Option>\n          <Option value=\"2\">UEFI USB Hard Disk</Option>\n          <Option value=\"3\">UEFI USB CD/DVD</Option>\n          <Option value=\"4\">UEFI USB Key</Option>\n          <Option value=\"5\">UEFI USB Floppy</Option>\n          <Option value=\"6\">UEFI USB Lan</Option>\n          <Option value=\"7\">UEFI Network</Option>\n          <Option value=\"8\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"9\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>UEFI Hard Disk</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 2, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"UEFI Boot Option #2\" selectedOption=\"UEFI CD/DVD\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">UEFI Hard Disk</Option>\n          <Option value=\"1\">UEFI CD/DVD</Option>\n          <Option value=\"2\">UEFI USB Hard Disk</Option>\n          <Option value=\"3\">UEFI USB CD/DVD</Option>\n          <Option value=\"4\">UEFI USB Key</Option>\n          <Option value=\"5\">UEFI USB Floppy</Option>\n          <Option value=\"6\">UEFI USB Lan</Option>\n          <Option value=\"7\">UEFI Network</Option>\n          <Option value=\"8\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"9\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>UEFI CD/DVD</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 2, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"UEFI Boot Option #3\" selectedOption=\"UEFI USB Hard Disk\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">UEFI Hard Disk</Option>\n          <Option value=\"1\">UEFI CD/DVD</Option>\n          <Option value=\"2\">UEFI USB Hard Disk</Option>\n          <Option value=\"3\">UEFI USB CD/DVD</Option>\n          <Option value=\"4\">UEFI USB Key</Option>\n          <Option value=\"5\">UEFI USB Floppy</Option>\n          <Option value=\"6\">UEFI USB Lan</Option>\n          <Option value=\"7\">UEFI Network</Option>\n          <Option value=\"8\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"9\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>UEFI USB Hard Disk</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 2, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"UEFI Boot Option #4\" selectedOption=\"UEFI USB CD/DVD\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">UEFI Hard Disk</Option>\n          <Option value=\"1\">UEFI CD/DVD</Option>\n          <Option value=\"2\">UEFI USB Hard Disk</Option>\n          <Option value=\"3\">UEFI USB CD/DVD</Option>\n          <Option value=\"4\">UEFI USB Key</Option>\n          <Option value=\"5\">UEFI USB Floppy</Option>\n          <Option value=\"6\">UEFI USB Lan</Option>\n          <Option value=\"7\">UEFI Network</Option>\n          <Option value=\"8\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"9\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>UEFI USB CD/DVD</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 2, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"UEFI Boot Option #5\" selectedOption=\"UEFI USB Key\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">UEFI Hard Disk</Option>\n          <Option value=\"1\">UEFI CD/DVD</Option>\n          <Option value=\"2\">UEFI USB Hard Disk</Option>\n          <Option value=\"3\">UEFI USB CD/DVD</Option>\n          <Option value=\"4\">UEFI USB Key</Option>\n          <Option value=\"5\">UEFI USB Floppy</Option>\n          <Option value=\"6\">UEFI USB Lan</Option>\n          <Option value=\"7\">UEFI Network</Option>\n          <Option value=\"8\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"9\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>UEFI USB Key</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 2, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"UEFI Boot Option #6\" selectedOption=\"UEFI USB Floppy\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">UEFI Hard Disk</Option>\n          <Option value=\"1\">UEFI CD/DVD</Option>\n          <Option value=\"2\">UEFI USB Hard Disk</Option>\n          <Option value=\"3\">UEFI USB CD/DVD</Option>\n          <Option value=\"4\">UEFI USB Key</Option>\n          <Option value=\"5\">UEFI USB Floppy</Option>\n          <Option value=\"6\">UEFI USB Lan</Option>\n          <Option value=\"7\">UEFI Network</Option>\n          <Option value=\"8\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"9\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>UEFI USB Floppy</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 2, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"UEFI Boot Option #7\" selectedOption=\"UEFI USB Lan\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">UEFI Hard Disk</Option>\n          <Option value=\"1\">UEFI CD/DVD</Option>\n          <Option value=\"2\">UEFI USB Hard Disk</Option>\n          <Option value=\"3\">UEFI USB CD/DVD</Option>\n          <Option value=\"4\">UEFI USB Key</Option>\n          <Option value=\"5\">UEFI USB Floppy</Option>\n          <Option value=\"6\">UEFI USB Lan</Option>\n          <Option value=\"7\">UEFI Network</Option>\n          <Option value=\"8\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"9\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>UEFI USB Lan</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 2, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"UEFI Boot Option #8\" selectedOption=\"UEFI Network\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">UEFI Hard Disk</Option>\n          <Option value=\"1\">UEFI CD/DVD</Option>\n          <Option value=\"2\">UEFI USB Hard Disk</Option>\n          <Option value=\"3\">UEFI USB CD/DVD</Option>\n          <Option value=\"4\">UEFI USB Key</Option>\n          <Option value=\"5\">UEFI USB Floppy</Option>\n          <Option value=\"6\">UEFI USB Lan</Option>\n          <Option value=\"7\">UEFI Network</Option>\n          <Option value=\"8\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"9\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>UEFI Network</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 2, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"UEFI Boot Option #9\" selectedOption=\"UEFI AP:UEFI: Built-in EFI Shell\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">UEFI Hard Disk</Option>\n          <Option value=\"1\">UEFI CD/DVD</Option>\n          <Option value=\"2\">UEFI USB Hard Disk</Option>\n          <Option value=\"3\">UEFI USB CD/DVD</Option>\n          <Option value=\"4\">UEFI USB Key</Option>\n          <Option value=\"5\">UEFI USB Floppy</Option>\n          <Option value=\"6\">UEFI USB Lan</Option>\n          <Option value=\"7\">UEFI Network</Option>\n          <Option value=\"8\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"9\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>UEFI AP:UEFI: Built-in EFI Shell</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 2, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"Legacy Boot Option #1\" selectedOption=\"Hard Disk: Micron_5300_MTFDDAK480TDT\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>Hard Disk: Micron_5300_MTFDDAK480TDT</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 1, 2, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"Legacy Boot Option #2\" selectedOption=\"Network:FlexBoot v3.5.901 (PCI 01:00.0)\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>CD/DVD</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 1, 2, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"Legacy Boot Option #3\" selectedOption=\"Disabled\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>USB Hard Disk</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 1, 2, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"Legacy Boot Option #4\" selectedOption=\"Disabled\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>USB CD/DVD</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 1, 2, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"Legacy Boot Option #5\" selectedOption=\"Disabled\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>USB Key</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 1, 2, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"Legacy Boot Option #6\" selectedOption=\"Disabled\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>USB Floppy</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 1, 2, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"Legacy Boot Option #7\" selectedOption=\"Disabled\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>USB Lan</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 1, 2, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"Legacy Boot Option #8\" selectedOption=\"Disabled\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>Network:FlexBoot v3.5.901 (PCI 01:00.0)</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 1, 2, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"DUAL Boot Option #1\" selectedOption=\"Hard Disk: Micron_5300_MTFDDAK480TDT\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">UEFI Hard Disk</Option>\n          <Option value=\"9\">UEFI CD/DVD</Option>\n          <Option value=\"10\">UEFI USB Hard Disk</Option>\n          <Option value=\"11\">UEFI USB CD/DVD</Option>\n          <Option value=\"12\">UEFI USB Key</Option>\n          <Option value=\"13\">UEFI USB Floppy</Option>\n          <Option value=\"14\">UEFI USB Lan</Option>\n          <Option value=\"15\">UEFI Network</Option>\n          <Option value=\"16\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"17\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>Hard Disk: Micron_5300_MTFDDAK480TDT</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 1, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"DUAL Boot Option #2\" selectedOption=\"CD/DVD\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">UEFI Hard Disk</Option>\n          <Option value=\"9\">UEFI CD/DVD</Option>\n          <Option value=\"10\">UEFI USB Hard Disk</Option>\n          <Option value=\"11\">UEFI USB CD/DVD</Option>\n          <Option value=\"12\">UEFI USB Key</Option>\n          <Option value=\"13\">UEFI USB Floppy</Option>\n          <Option value=\"14\">UEFI USB Lan</Option>\n          <Option value=\"15\">UEFI Network</Option>\n          <Option value=\"16\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"17\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>CD/DVD</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 1, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"DUAL Boot Option #3\" selectedOption=\"USB Hard Disk\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">UEFI Hard Disk</Option>\n          <Option value=\"9\">UEFI CD/DVD</Option>\n          <Option value=\"10\">UEFI USB Hard Disk</Option>\n          <Option value=\"11\">UEFI USB CD/DVD</Option>\n          <Option value=\"12\">UEFI USB Key</Option>\n          <Option value=\"13\">UEFI USB Floppy</Option>\n          <Option value=\"14\">UEFI USB Lan</Option>\n          <Option value=\"15\">UEFI Network</Option>\n          <Option value=\"16\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"17\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>USB Hard Disk</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 1, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"DUAL Boot Option #4\" selectedOption=\"USB CD/DVD\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">UEFI Hard Disk</Option>\n          <Option value=\"9\">UEFI CD/DVD</Option>\n          <Option value=\"10\">UEFI USB Hard Disk</Option>\n          <Option value=\"11\">UEFI USB CD/DVD</Option>\n          <Option value=\"12\">UEFI USB Key</Option>\n          <Option value=\"13\">UEFI USB Floppy</Option>\n          <Option value=\"14\">UEFI USB Lan</Option>\n          <Option value=\"15\">UEFI Network</Option>\n          <Option value=\"16\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"17\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>USB CD/DVD</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 1, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"DUAL Boot Option #5\" selectedOption=\"USB Key\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">UEFI Hard Disk</Option>\n          <Option value=\"9\">UEFI CD/DVD</Option>\n          <Option value=\"10\">UEFI USB Hard Disk</Option>\n          <Option value=\"11\">UEFI USB CD/DVD</Option>\n          <Option value=\"12\">UEFI USB Key</Option>\n          <Option value=\"13\">UEFI USB Floppy</Option>\n          <Option value=\"14\">UEFI USB Lan</Option>\n          <Option value=\"15\">UEFI Network</Option>\n          <Option value=\"16\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"17\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>USB Key</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 1, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"DUAL Boot Option #6\" selectedOption=\"USB Floppy\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">UEFI Hard Disk</Option>\n          <Option value=\"9\">UEFI CD/DVD</Option>\n          <Option value=\"10\">UEFI USB Hard Disk</Option>\n          <Option value=\"11\">UEFI USB CD/DVD</Option>\n          <Option value=\"12\">UEFI USB Key</Option>\n          <Option value=\"13\">UEFI USB Floppy</Option>\n          <Option value=\"14\">UEFI USB Lan</Option>\n          <Option value=\"15\">UEFI Network</Option>\n          <Option value=\"16\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"17\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>USB Floppy</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 1, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"DUAL Boot Option #7\" selectedOption=\"USB Lan\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">UEFI Hard Disk</Option>\n          <Option value=\"9\">UEFI CD/DVD</Option>\n          <Option value=\"10\">UEFI USB Hard Disk</Option>\n          <Option value=\"11\">UEFI USB CD/DVD</Option>\n          <Option value=\"12\">UEFI USB Key</Option>\n          <Option value=\"13\">UEFI USB Floppy</Option>\n          <Option value=\"14\">UEFI USB Lan</Option>\n          <Option value=\"15\">UEFI Network</Option>\n          <Option value=\"16\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"17\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>USB Lan</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 1, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"DUAL Boot Option #8\" selectedOption=\"Network:FlexBoot v3.5.901 (PCI 01:00.0)\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">UEFI Hard Disk</Option>\n          <Option value=\"9\">UEFI CD/DVD</Option>\n          <Option value=\"10\">UEFI USB Hard Disk</Option>\n          <Option value=\"11\">UEFI USB CD/DVD</Option>\n          <Option value=\"12\">UEFI USB Key</Option>\n          <Option value=\"13\">UEFI USB Floppy</Option>\n          <Option value=\"14\">UEFI USB Lan</Option>\n          <Option value=\"15\">UEFI Network</Option>\n          <Option value=\"16\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"17\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>Network:FlexBoot v3.5.901 (PCI 01:00.0)</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 1, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"DUAL Boot Option #9\" selectedOption=\"UEFI Hard Disk\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">UEFI Hard Disk</Option>\n          <Option value=\"9\">UEFI CD/DVD</Option>\n          <Option value=\"10\">UEFI USB Hard Disk</Option>\n          <Option value=\"11\">UEFI USB CD/DVD</Option>\n          <Option value=\"12\">UEFI USB Key</Option>\n          <Option value=\"13\">UEFI USB Floppy</Option>\n          <Option value=\"14\">UEFI USB Lan</Option>\n          <Option value=\"15\">UEFI Network</Option>\n          <Option value=\"16\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"17\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>UEFI Hard Disk</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 1, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"DUAL Boot Option #10\" selectedOption=\"UEFI CD/DVD\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">UEFI Hard Disk</Option>\n          <Option value=\"9\">UEFI CD/DVD</Option>\n          <Option value=\"10\">UEFI USB Hard Disk</Option>\n          <Option value=\"11\">UEFI USB CD/DVD</Option>\n          <Option value=\"12\">UEFI USB Key</Option>\n          <Option value=\"13\">UEFI USB Floppy</Option>\n          <Option value=\"14\">UEFI USB Lan</Option>\n          <Option value=\"15\">UEFI Network</Option>\n          <Option value=\"16\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"17\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>UEFI CD/DVD</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 1, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"DUAL Boot Option #11\" selectedOption=\"UEFI USB Hard Disk\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">UEFI Hard Disk</Option>\n          <Option value=\"9\">UEFI CD/DVD</Option>\n          <Option value=\"10\">UEFI USB Hard Disk</Option>\n          <Option value=\"11\">UEFI USB CD/DVD</Option>\n          <Option value=\"12\">UEFI USB Key</Option>\n          <Option value=\"13\">UEFI USB Floppy</Option>\n          <Option value=\"14\">UEFI USB Lan</Option>\n          <Option value=\"15\">UEFI Network</Option>\n          <Option value=\"16\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"17\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>UEFI USB Hard Disk</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 1, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"DUAL Boot Option #12\" selectedOption=\"UEFI USB CD/DVD\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">UEFI Hard Disk</Option>\n          <Option value=\"9\">UEFI CD/DVD</Option>\n          <Option value=\"10\">UEFI USB Hard Disk</Option>\n          <Option value=\"11\">UEFI USB CD/DVD</Option>\n          <Option value=\"12\">UEFI USB Key</Option>\n          <Option value=\"13\">UEFI USB Floppy</Option>\n          <Option value=\"14\">UEFI USB Lan</Option>\n          <Option value=\"15\">UEFI Network</Option>\n          <Option value=\"16\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"17\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>UEFI USB CD/DVD</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 1, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"DUAL Boot Option #13\" selectedOption=\"UEFI USB Key\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">UEFI Hard Disk</Option>\n          <Option value=\"9\">UEFI CD/DVD</Option>\n          <Option value=\"10\">UEFI USB Hard Disk</Option>\n          <Option value=\"11\">UEFI USB CD/DVD</Option>\n          <Option value=\"12\">UEFI USB Key</Option>\n          <Option value=\"13\">UEFI USB Floppy</Option>\n          <Option value=\"14\">UEFI USB Lan</Option>\n          <Option value=\"15\">UEFI Network</Option>\n          <Option value=\"16\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"17\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>UEFI USB Key</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 1, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"DUAL Boot Option #14\" selectedOption=\"UEFI USB Floppy\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">UEFI Hard Disk</Option>\n          <Option value=\"9\">UEFI CD/DVD</Option>\n          <Option value=\"10\">UEFI USB Hard Disk</Option>\n          <Option value=\"11\">UEFI USB CD/DVD</Option>\n          <Option value=\"12\">UEFI USB Key</Option>\n          <Option value=\"13\">UEFI USB Floppy</Option>\n          <Option value=\"14\">UEFI USB Lan</Option>\n          <Option value=\"15\">UEFI Network</Option>\n          <Option value=\"16\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"17\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>UEFI USB Floppy</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 1, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"DUAL Boot Option #15\" selectedOption=\"UEFI USB Lan\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">UEFI Hard Disk</Option>\n          <Option value=\"9\">UEFI CD/DVD</Option>\n          <Option value=\"10\">UEFI USB Hard Disk</Option>\n          <Option value=\"11\">UEFI USB CD/DVD</Option>\n          <Option value=\"12\">UEFI USB Key</Option>\n          <Option value=\"13\">UEFI USB Floppy</Option>\n          <Option value=\"14\">UEFI USB Lan</Option>\n          <Option value=\"15\">UEFI Network</Option>\n          <Option value=\"16\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"17\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>UEFI USB Lan</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 1, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"DUAL Boot Option #16\" selectedOption=\"UEFI Network\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">UEFI Hard Disk</Option>\n          <Option value=\"9\">UEFI CD/DVD</Option>\n          <Option value=\"10\">UEFI USB Hard Disk</Option>\n          <Option value=\"11\">UEFI USB CD/DVD</Option>\n          <Option value=\"12\">UEFI USB Key</Option>\n          <Option value=\"13\">UEFI USB Floppy</Option>\n          <Option value=\"14\">UEFI USB Lan</Option>\n          <Option value=\"15\">UEFI Network</Option>\n          <Option value=\"16\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"17\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>UEFI Network</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 1, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Setting name=\"DUAL Boot Option #17\" selectedOption=\"UEFI AP:UEFI: Built-in EFI Shell\" type=\"Option\">\n      <Information>\n        <AvailableOptions>\n          <Option value=\"0\">Hard Disk: Micron_5300_MTFDDAK480TDT</Option>\n          <Option value=\"1\">CD/DVD</Option>\n          <Option value=\"2\">USB Hard Disk</Option>\n          <Option value=\"3\">USB CD/DVD</Option>\n          <Option value=\"4\">USB Key</Option>\n          <Option value=\"5\">USB Floppy</Option>\n          <Option value=\"6\">USB Lan</Option>\n          <Option value=\"7\">Network:FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n          <Option value=\"8\">UEFI Hard Disk</Option>\n          <Option value=\"9\">UEFI CD/DVD</Option>\n          <Option value=\"10\">UEFI USB Hard Disk</Option>\n          <Option value=\"11\">UEFI USB CD/DVD</Option>\n          <Option value=\"12\">UEFI USB Key</Option>\n          <Option value=\"13\">UEFI USB Floppy</Option>\n          <Option value=\"14\">UEFI USB Lan</Option>\n          <Option value=\"15\">UEFI Network</Option>\n          <Option value=\"16\">UEFI AP:UEFI: Built-in EFI Shell</Option>\n          <Option value=\"17\">Disabled</Option>\n        </AvailableOptions>\n        <DefaultOption>UEFI AP:UEFI: Built-in EFI Shell</DefaultOption>\n        <Help><![CDATA[Sets the system boot order]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0, 1, 3, 4, 5 ] ) ]]></WorkIf>\n      </Information>\n    </Setting>\n    <Subtitle></Subtitle>\n    <Subtitle></Subtitle>\n    <Subtitle></Subtitle>\n    <Menu name=\"UEFI Application Boot Priorities\">\n      <Information>\n        <Help><![CDATA[Specifies the Boot Device Priority sequence from available UEFI Application.]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 0 ] ) ]]></WorkIf>\n      </Information>\n      <Setting name=\"Boot Option #1\" order=\"1\" selectedOption=\"UEFI: Built-in EFI Shell\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">UEFI: Built-in EFI Shell</Option>\n            <Option value=\"18\">Disabled</Option>\n          </AvailableOptions>\n          <DefaultOption>UEFI: Built-in EFI Shell</DefaultOption>\n          <Help><![CDATA[Sets the system boot order]]></Help>\n        </Information>\n      </Setting>\n    </Menu>\n    <Menu name=\"Hard Disk Drive BBS Priorities\">\n      <Information>\n        <Help><![CDATA[Specifies the Boot Device Priority sequence from available Hard Disk Drives.]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 1 ] ) ]]></WorkIf>\n      </Information>\n      <Setting name=\"Boot Option #1\" order=\"2\" selectedOption=\"P0: Micron_5300_MTFDDAK480TDT(SATA,Port:0)\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">P0: Micron_5300_MTFDDAK480TDT(SATA,Port:0)</Option>\n            <Option value=\"1\">P1: Micron_5300_MTFDDAK480TDT(SATA,Port:1)</Option>\n            <Option value=\"18\">Disabled</Option>\n          </AvailableOptions>\n          <DefaultOption>P0: Micron_5300_MTFDDAK480TDT(SATA,Port:0)</DefaultOption>\n          <Help><![CDATA[Sets the system boot order]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Boot Option #2\" order=\"1\" selectedOption=\"P1: Micron_5300_MTFDDAK480TDT(SATA,Port:1)\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">P0: Micron_5300_MTFDDAK480TDT(SATA,Port:0)</Option>\n            <Option value=\"1\">P1: Micron_5300_MTFDDAK480TDT(SATA,Port:1)</Option>\n            <Option value=\"18\">Disabled</Option>\n          </AvailableOptions>\n          <DefaultOption>P1: Micron_5300_MTFDDAK480TDT(SATA,Port:1)</DefaultOption>\n          <Help><![CDATA[Sets the system boot order]]></Help>\n        </Information>\n      </Setting>\n    </Menu>\n    <Menu name=\"NETWORK Drive BBS Priorities\">\n      <Information>\n        <Help><![CDATA[Specifies the Boot Device Priority sequence from available NETWORK Drives.]]></Help>\n        <WorkIf><![CDATA[ ( Boot mode select is not in [ 1 ] ) ]]></WorkIf>\n      </Information>\n      <Setting name=\"Boot Option #1\" order=\"3\" selectedOption=\"FlexBoot v3.5.901 (PCI 01:00.0)\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n            <Option value=\"1\">FlexBoot v3.5.901 (PCI 01:00.1)</Option>\n            <Option value=\"18\">Disabled</Option>\n          </AvailableOptions>\n          <DefaultOption>FlexBoot v3.5.901 (PCI 01:00.0)</DefaultOption>\n          <Help><![CDATA[Sets the system boot order]]></Help>\n        </Information>\n      </Setting>\n      <Setting name=\"Boot Option #2\" order=\"2\" selectedOption=\"FlexBoot v3.5.901 (PCI 01:00.1)\" type=\"Option\">\n        <Information>\n          <AvailableOptions>\n            <Option value=\"0\">FlexBoot v3.5.901 (PCI 01:00.0)</Option>\n            <Option value=\"1\">FlexBoot v3.5.901 (PCI 01:00.1)</Option>\n            <Option value=\"18\">Disabled</Option>\n          </AvailableOptions>\n          <DefaultOption>FlexBoot v3.5.901 (PCI 01:00.1)</DefaultOption>\n          <Help><![CDATA[Sets the system boot order]]></Help>\n        </Information>\n      </Setting>\n    </Menu>\n  </Menu>\n</BiosCfg>\n\n"
  },
  {
    "path": "fixtures/internal/sum/SetBiosConfiguration",
    "content": "Supermicro Update Manager (for UEFI BIOS) 2.14.0 (2024/02/15) (ARM64)\nCopyright(C) 2013-2024 Super Micro Computer, Inc. All rights reserved.\n.................\u001b[0m\n\u001b[0m\u001b[1m\u001b[1m\u001b[1mStatus:\u001b[0m\u001b[0m \u001b[0mThe managed system 10.145.129.168 is rebooting.\n\n.............................\u001b[0mDone\n....\n.................\u001b[0m\n\u001b[0m\n\n\u001b[1m\u001b[1m\u001b[1mNote:\u001b[0m\u001b[0m \u001b[0mNo BIOS setting has been changed.\n\n\u001b[0m\u001b[1m\u001b[1m\u001b[1mStatus:\u001b[0m\u001b[0m \u001b[0mThe BIOS configuration is updated for 10.145.129.168\n\n\u001b[0m\u001b[0m\u001b[0;33m\u001b[0;33m\u001b[0;33mWARNING\u001b[0m\u001b[0m: \u001b[0mWithout option --post_complete, please manually confirm the managed system is POST complete before executing next action.\n\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/bmc-toolbox/bmclib/v2\n\ngo 1.21\n\nrequire (\n\tdario.cat/mergo v1.0.1\n\tgithub.com/Jeffail/gabs/v2 v2.7.0\n\tgithub.com/bmc-toolbox/common v0.0.0-20250112191656-b6de52e8303d\n\tgithub.com/bombsimon/logrusr/v2 v2.0.1\n\tgithub.com/ghodss/yaml v1.0.0\n\tgithub.com/go-logr/logr v1.4.2\n\tgithub.com/go-logr/zerologr v1.2.3\n\tgithub.com/google/go-cmp v0.6.0\n\tgithub.com/hashicorp/go-multierror v1.1.1\n\tgithub.com/jacobweinstock/iamt v0.0.0-20230502042727-d7cdbe67d9ef\n\tgithub.com/jacobweinstock/registrar v0.4.7\n\tgithub.com/pkg/errors v0.9.1\n\tgithub.com/rs/zerolog v1.33.0\n\tgithub.com/sirupsen/logrus v1.9.3\n\tgithub.com/stmcginnis/gofish v0.21.6\n\tgithub.com/stretchr/testify v1.10.0\n\tgo.opentelemetry.io/otel v1.29.0\n\tgo.opentelemetry.io/otel/trace v1.29.0\n\tgo.uber.org/goleak v1.3.0\n\tgolang.org/x/net v0.33.0\n\tgopkg.in/go-playground/assert.v1 v1.2.1\n)\n\nrequire (\n\tgithub.com/VictorLowther/simplexml v0.0.0-20180716164440-0bff93621230 // indirect\n\tgithub.com/VictorLowther/soap v0.0.0-20150314151524-8e36fca84b22 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/hashicorp/errwrap v1.1.0 // indirect\n\tgithub.com/mattn/go-colorable v0.1.13 // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/satori/go.uuid v1.2.0 // indirect\n\tgolang.org/x/sys v0.28.0 // indirect\n\tgolang.org/x/text v0.21.0 // indirect\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=\ndario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=\ngithub.com/Jeffail/gabs/v2 v2.7.0 h1:Y2edYaTcE8ZpRsR2AtmPu5xQdFDIthFG0jYhu5PY8kg=\ngithub.com/Jeffail/gabs/v2 v2.7.0/go.mod h1:dp5ocw1FvBBQYssgHsG7I1WYsiLRtkUaB1FEtSwvNUw=\ngithub.com/VictorLowther/simplexml v0.0.0-20180716164440-0bff93621230 h1:t95Grn2mOPfb3+kPDWsNnj4dlNcxnvuR72IjY8eYjfQ=\ngithub.com/VictorLowther/simplexml v0.0.0-20180716164440-0bff93621230/go.mod h1:t2EzW1qybnPDQ3LR/GgeF0GOzHUXT5IVMLP2gkW1cmc=\ngithub.com/VictorLowther/soap v0.0.0-20150314151524-8e36fca84b22 h1:a0MBqYm44o0NcthLKCljZHe1mxlN6oahCQHHThnSwB4=\ngithub.com/VictorLowther/soap v0.0.0-20150314151524-8e36fca84b22/go.mod h1:/B7V22rcz4860iDqstGvia/2+IYWXf3/JdQCVd/1D2A=\ngithub.com/bmc-toolbox/common v0.0.0-20250112191656-b6de52e8303d h1:5c0jhS9jNLm1t3GVEESsWv+p6recFRLGW90zp8HDIDs=\ngithub.com/bmc-toolbox/common v0.0.0-20250112191656-b6de52e8303d/go.mod h1:Cdnkm+edb6C0pVkyCrwh3JTXAe0iUF9diDG/DztPI9I=\ngithub.com/bombsimon/logrusr/v2 v2.0.1 h1:1VgxVNQMCvjirZIYaT9JYn6sAVGVEcNtRE0y4mvaOAM=\ngithub.com/bombsimon/logrusr/v2 v2.0.1/go.mod h1:ByVAX+vHdLGAfdroiMg6q0zgq2FODY2lc5YJvzmOJio=\ngithub.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/go-logr/logr v1.0.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=\ngithub.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/zerologr v1.2.3 h1:up5N9vcH9Xck3jJkXzgyOxozT14R47IyDODz8LM1KSs=\ngithub.com/go-logr/zerologr v1.2.3/go.mod h1:BxwGo7y5zgSHYR1BjbnHPyF/5ZjVKfKxAZANVu6E8Ho=\ngithub.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=\ngithub.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=\ngithub.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=\ngithub.com/jacobweinstock/iamt v0.0.0-20230502042727-d7cdbe67d9ef h1:G4k02HGmBUfJFSNu3gfKJ+ki+B3qutKsYzYndkqqKc4=\ngithub.com/jacobweinstock/iamt v0.0.0-20230502042727-d7cdbe67d9ef/go.mod h1:FgmiLTU6cJewV4Xgrq6m5o8CUlTQOJtqzaFLGA0mG+E=\ngithub.com/jacobweinstock/registrar v0.4.7 h1:s4dOExccgD+Pc7rJC+f3Mc3D+NXHcXUaOibtcEsPxOc=\ngithub.com/jacobweinstock/registrar v0.4.7/go.mod h1:PWmkdGFG5/ZdCqgMo7pvB3pXABOLHc5l8oQ0sgmBNDU=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=\ngithub.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=\ngithub.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=\ngithub.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=\ngithub.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=\ngithub.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=\ngithub.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=\ngithub.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/stmcginnis/gofish v0.21.6 h1:jK3TGD6VANaAHKHypVNfD6io2nPrU+6eF8X4qARsTlY=\ngithub.com/stmcginnis/gofish v0.21.6/go.mod h1:PzF5i8ecRG9A2ol8XT64npKUunyraJ+7t0kYMpQAtqU=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=\ngithub.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngo.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=\ngo.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=\ngo.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=\ngo.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngolang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=\ngolang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=\ngolang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210608053332-aa57babbf139/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=\ngolang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=\ngolang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=\ngolang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=\ngopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "internal/executor/errors.go",
    "content": "package executor\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n)\n\nvar (\n\tErrNoCommandOutput             = errors.New(\"command returned no output\")\n\tErrVersionStrExpectedSemver    = errors.New(\"expected version string to follow semver format\")\n\tErrFakeExecutorInvalidArgs     = errors.New(\"invalid number of args passed to fake executor\")\n\tErrRepositoryBaseURL           = errors.New(\"repository base URL undefined, ensure UpdateOptions.BaseURL OR UPDATE_BASE_URL env var is set\")\n\tErrNoUpdatesApplicable         = errors.New(\"no updates applicable\")\n\tErrDmiDecodeRun                = errors.New(\"error running dmidecode\")\n\tErrComponentListExpected       = errors.New(\"expected a list of components to apply updates\")\n\tErrDeviceInventory             = errors.New(\"failed to collect device inventory\")\n\tErrUnsupportedDiskVendor       = errors.New(\"unsupported disk vendor\")\n\tErrNoUpdateHandlerForComponent = errors.New(\"component slug has no update handler declared\")\n\tErrBinNotExecutable            = errors.New(\"bin has no executable bit set\")\n\tErrBinLstat                    = errors.New(\"failed to run lstat on bin\")\n\tErrBinLookupPath               = errors.New(\"failed to lookup bin path\")\n)\n\n// ExecError is returned when the command exits with an error or a non zero exit status\ntype ExecError struct {\n\tCmd      string\n\tStderr   string\n\tStdout   string\n\tExitCode int\n}\n\n// Error implements the error interface\nfunc (u *ExecError) Error() string {\n\treturn fmt.Sprintf(\"cmd %s exited with error: %s\\n\\t exitCode: %d\\n\\t stdout: %s\", u.Cmd, u.Stderr, u.ExitCode, u.Stdout)\n}\n\nfunc newExecError(cmd string, r *Result) *ExecError {\n\treturn &ExecError{\n\t\tCmd:      cmd,\n\t\tStderr:   string(r.Stderr),\n\t\tStdout:   string(r.Stdout),\n\t\tExitCode: r.ExitCode,\n\t}\n}\n"
  },
  {
    "path": "internal/executor/executor.go",
    "content": "package executor\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n)\n\n// Executor interface lets us implement dummy executors for tests\ntype Executor interface {\n\tExecWithContext(context.Context) (*Result, error)\n\tSetArgs([]string)\n\tSetEnv([]string)\n\tGetCmd() string\n\tCheckExecutable() error\n\tSetStdout([]byte)\n}\n\nfunc NewExecutor(cmd string) Executor {\n\treturn &Execute{Cmd: cmd, CheckBin: true}\n}\n\n// An execute instace\ntype Execute struct {\n\tCmd      string\n\tArgs     []string\n\tEnv      []string\n\tStdin    io.Reader\n\tCheckBin bool\n\tQuiet    bool\n}\n\n// The result of a command execution\ntype Result struct {\n\tStdout   []byte\n\tStderr   []byte\n\tExitCode int\n}\n\n// GetCmd returns the command with args as a string\nfunc (e *Execute) GetCmd() string {\n\tcmd := []string{e.Cmd}\n\tcmd = append(cmd, e.Args...)\n\n\treturn strings.Join(cmd, \" \")\n}\n\n// SetArgs sets the command args\nfunc (e *Execute) SetArgs(a []string) {\n\te.Args = a\n}\n\n// SetEnv sets the env variables\nfunc (e *Execute) SetEnv(env []string) {\n\te.Env = env\n}\n\n// SetStdout doesn't do much, is around for tests\nfunc (e *Execute) SetStdout(_ []byte) {\n}\n\n// ExecWithContext executes the command and returns the Result object\nfunc (e *Execute) ExecWithContext(ctx context.Context) (result *Result, err error) {\n\tif e.CheckBin {\n\t\terr = e.CheckExecutable()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tcmd := exec.CommandContext(ctx, e.Cmd, e.Args...)\n\tcmd.Env = append(cmd.Env, e.Env...)\n\tcmd.Stdin = e.Stdin\n\n\tvar stdoutBuf, stderrBuf bytes.Buffer\n\tif !e.Quiet {\n\t\tcmd.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf)\n\t\tcmd.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)\n\t} else {\n\t\tcmd.Stderr = &stderrBuf\n\t\tcmd.Stdout = &stdoutBuf\n\t}\n\n\tif err := cmd.Run(); err != nil {\n\t\tresult = &Result{stdoutBuf.Bytes(), stderrBuf.Bytes(), cmd.ProcessState.ExitCode()}\n\t\treturn result, newExecError(e.GetCmd(), result)\n\t}\n\n\tresult = &Result{stdoutBuf.Bytes(), stderrBuf.Bytes(), cmd.ProcessState.ExitCode()}\n\n\treturn result, nil\n}\n\n// CheckExecutable determines if the set Cmd value exists as a file and is an executable.\nfunc (e *Execute) CheckExecutable() error {\n\tvar path string\n\n\tif strings.Contains(e.Cmd, \"/\") {\n\t\tpath = e.Cmd\n\t} else {\n\t\tvar err error\n\t\tpath, err = exec.LookPath(e.Cmd)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(ErrBinLookupPath, err.Error())\n\t\t}\n\n\t\te.Cmd = path\n\t}\n\n\tfileInfo, err := os.Lstat(path)\n\tif err != nil {\n\t\treturn errors.Wrap(ErrBinLstat, err.Error())\n\t}\n\n\t// bit mask 0111 indicates atleast one of owner, group, others has an executable bit set\n\tif fileInfo.Mode()&0o111 == 0 {\n\t\treturn ErrBinNotExecutable\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/executor/executor_test.go",
    "content": "package executor\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc Test_Stdin(t *testing.T) {\n\te := new(Execute)\n\te.Cmd = \"grep\"\n\te.Args = []string{\"hello\"}\n\te.Stdin = bytes.NewReader([]byte(\"hello\"))\n\n\tresult, err := e.ExecWithContext(context.Background())\n\tif err != nil {\n\t\tfmt.Println(err.Error())\n\t}\n\n\tassert.Equal(t, []byte(\"hello\\n\"), result.Stdout)\n}\n\ntype checkBinTester struct {\n\tcreateFile  bool\n\tfilePath    string\n\texpectedErr error\n\tfileMode    uint\n\ttestName    string\n}\n\nfunc initCheckBinTests() []checkBinTester {\n\treturn []checkBinTester{\n\t\t{\n\t\t\tfalse,\n\t\t\t\"f\",\n\t\t\tErrBinLookupPath,\n\t\t\t0,\n\t\t\t\"bin path lookup err test\",\n\t\t},\n\t\t{\n\t\t\tfalse,\n\t\t\t\"/tmp/f\",\n\t\t\tErrBinLstat,\n\t\t\t0,\n\t\t\t\"bin exists err test\",\n\t\t},\n\t\t{\n\t\t\ttrue,\n\t\t\t\"/tmp/f\",\n\t\t\tErrBinNotExecutable,\n\t\t\t0o666,\n\t\t\t\"bin exists with no executable bit test\",\n\t\t},\n\t\t{\n\t\t\ttrue,\n\t\t\t\"/tmp/j\",\n\t\t\tnil,\n\t\t\t0o667,\n\t\t\t\"bin with executable bit returns no error\",\n\t\t},\n\t\t{\n\t\t\ttrue,\n\t\t\t\"/tmp/k\",\n\t\t\tnil,\n\t\t\t0o700,\n\t\t\t\"bin with owner executable bit returns no error\",\n\t\t},\n\t\t{\n\t\t\ttrue,\n\t\t\t\"/tmp/l\",\n\t\t\tnil,\n\t\t\t0o070,\n\t\t\t\"bin with group executable bit returns no error\",\n\t\t},\n\t\t{\n\t\t\ttrue,\n\t\t\t\"/tmp/m\",\n\t\t\tnil,\n\t\t\t0o007,\n\t\t\t\"bin with other executable bit returns no error\",\n\t\t},\n\t}\n}\n\nfunc Test_CheckExecutable(t *testing.T) {\n\ttests := initCheckBinTests()\n\tfor _, c := range tests {\n\t\tif c.createFile {\n\t\t\tf, err := os.Create(c.filePath)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\n\t\t\t// nolint:gocritic // test code\n\t\t\tdefer os.Remove(c.filePath)\n\n\t\t\tif c.fileMode != 0 {\n\t\t\t\terr = f.Chmod(fs.FileMode(c.fileMode))\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\te := new(Execute)\n\t\te.Cmd = c.filePath\n\t\terr := e.CheckExecutable()\n\t\tassert.Equal(t, c.expectedErr, errors.Cause(err), c.testName)\n\t}\n}\n"
  },
  {
    "path": "internal/executor/fake_executor.go",
    "content": "package executor\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"strings\"\n)\n\n// FakeExecute implements the utils.Executor interface\n// to enable testing\ntype FakeExecute struct {\n\tCmd      string\n\tArgs     []string\n\tEnv      []string\n\tCheckBin bool\n\tStdin    io.Reader\n\tStdout   []byte // Set this for the dummy data to be returned\n\tStderr   []byte // Set this for the dummy data to be returned\n\tQuiet    bool\n\tExitCode int\n}\n\nfunc NewFakeExecutor(cmd string) Executor {\n\treturn &FakeExecute{Cmd: cmd, CheckBin: false}\n}\n\n// nolint:gocyclo // TODO: break this method up and move into each $util_test.go\n// FakeExecute method returns whatever you want it to return\n// Set e.Stdout and e.Stderr to data to be returned\nfunc (e *FakeExecute) ExecWithContext(_ context.Context) (*Result, error) {\n\t// switch e.Cmd {\n\t// case \"ipmicfg\":\n\t// \tif e.Args[0] == \"-summary\" {\n\t// \t\tbuf := new(bytes.Buffer)\n\n\t// \t\t_, err := buf.ReadFrom(e.Stdin)\n\t// \t\tif err != nil {\n\t// \t\t\treturn nil, err\n\t// \t\t}\n\n\t// \t\te.Stdout = buf.Bytes()\n\t// \t}\n\t// }\n\n\treturn &Result{Stdout: e.Stdout, Stderr: e.Stderr, ExitCode: 0}, nil\n}\n\n// CheckExecutable implements the Executor interface\nfunc (e *FakeExecute) CheckExecutable() error {\n\treturn nil\n}\n\n// CmdPath returns the absolute path to the executable\n// this means the caller should not have disabled CheckBin.\nfunc (e *FakeExecute) CmdPath() string {\n\treturn e.Cmd\n}\n\nfunc (e *FakeExecute) SetArgs(a []string) {\n\te.Args = a\n}\n\nfunc (e *FakeExecute) SetEnv(env []string) {\n\te.Env = env\n}\n\nfunc (e *FakeExecute) SetQuiet() {\n\te.Quiet = true\n}\n\nfunc (e *FakeExecute) SetVerbose() {\n\te.Quiet = false\n}\n\nfunc (e *FakeExecute) SetStdout(b []byte) {\n\te.Stdout = b\n}\n\nfunc (e *FakeExecute) SetStderr(b []byte) {\n\te.Stderr = b\n}\n\nfunc (e *FakeExecute) SetStdin(r io.Reader) {\n\te.Stdin = r\n}\n\nfunc (e *FakeExecute) DisableBinCheck() {\n\te.CheckBin = false\n}\n\nfunc (e *FakeExecute) SetExitCode(i int) {\n\te.ExitCode = i\n}\n\nfunc (e *FakeExecute) GetCmd() string {\n\tcmd := []string{e.Cmd}\n\tcmd = append(cmd, e.Args...)\n\n\treturn strings.Join(cmd, \" \")\n}\n"
  },
  {
    "path": "internal/helper/helper.go",
    "content": "package helper\n\nimport (\n\t\"regexp\"\n\t\"runtime\"\n)\n\nvar basename = regexp.MustCompile(`^.+\\.(.*$)`)\n\n// WhosCalling returns the current caller of the functions\nfunc WhosCalling() string {\n\tpc, _, _, ok := runtime.Caller(1)\n\tif ok {\n\t\treturn basename.ReplaceAllString(runtime.FuncForPC(pc).Name(), \"${1}\")\n\t}\n\treturn \"unknown\"\n}\n"
  },
  {
    "path": "internal/helper/helper_test.go",
    "content": "package helper\n\nimport \"testing\"\n\nfunc TestWhosCalling(t *testing.T) {\n\texpectedAnswer := \"TestWhosCalling\"\n\n\tanswer := WhosCalling()\n\n\tif answer != expectedAnswer {\n\t\tt.Errorf(\"Expected answer %v: found %v\", expectedAnswer, answer)\n\t}\n}\n"
  },
  {
    "path": "internal/httpclient/httpclient.go",
    "content": "package httpclient\n\nimport (\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/cookiejar\"\n\t\"strings\"\n\t\"time\"\n\n\t\"golang.org/x/net/publicsuffix\"\n)\n\n// SecureTLS disables InsecureSkipVerify and adds a cert pool to an HTTP client's\n// TLS config\nfunc SecureTLS(c *http.Client, rootCAs *x509.CertPool) {\n\tif c == nil {\n\t\treturn\n\t}\n\ttp := DefaultTransport()\n\tif c.Transport != nil {\n\t\tif assertedTransport, ok := c.Transport.(*http.Transport); ok {\n\t\t\ttp = assertedTransport\n\t\t}\n\t\t// otherwise, we overwrite the transport\n\t}\n\ttp.TLSClientConfig.InsecureSkipVerify = false\n\ttp.TLSClientConfig.RootCAs = rootCAs\n\tc.Transport = tp\n}\n\n// DefaultTransport sets an HTTP Transport\nfunc DefaultTransport() *http.Transport {\n\treturn &http.Transport{\n\t\tTLSClientConfig:   &tls.Config{InsecureSkipVerify: true},\n\t\tDisableKeepAlives: true,\n\t\tDial: (&net.Dialer{\n\t\t\tTimeout:   120 * time.Second,\n\t\t\tKeepAlive: 120 * time.Second,\n\t\t}).Dial,\n\t\tTLSHandshakeTimeout:   120 * time.Second,\n\t\tResponseHeaderTimeout: 120 * time.Second,\n\t}\n}\n\n// SecureTLSOption disables InsecureSkipVerify and adds a cert pool to an HTTP client's\n// TLS config\nfunc SecureTLSOption(rootCAs *x509.CertPool) func(*http.Client) {\n\treturn func(c *http.Client) {\n\t\tSecureTLS(c, rootCAs)\n\t}\n}\n\n// Build builds a client session with our default parameters\nfunc Build(opts ...func(*http.Client)) *http.Client {\n\t// we ignore the error here because cookiejar.New always returns nil\n\tjar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})\n\n\tclient := &http.Client{\n\t\tTimeout:   time.Second * 120,\n\t\tTransport: DefaultTransport(),\n\t\tJar:       jar,\n\t}\n\n\tfor _, opt := range opts {\n\t\tif opt != nil {\n\t\t\topt(client)\n\t\t}\n\t}\n\n\treturn client\n}\n\n// StandardizeProcessorName makes the processor name standard across vendors\nfunc StandardizeProcessorName(name string) string {\n\treturn strings.ToLower(strings.TrimSuffix(strings.TrimSpace(strings.Split(name, \"@\")[0]), \" 0\"))\n}\n"
  },
  {
    "path": "internal/httpclient/httpclient_test.go",
    "content": "package httpclient\n\nimport (\n\t\"crypto/x509\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"testing\"\n)\n\nfunc CertPoolFromCert(cert *x509.Certificate) *x509.CertPool {\n\tcertPool := x509.NewCertPool()\n\tcertPool.AddCert(cert)\n\treturn certPool\n}\n\nfunc TestBuildWithOptions(t *testing.T) {\n\tcases := []struct {\n\t\tname         string\n\t\tsecureClient bool\n\t\twithCertPool func(cert *x509.Certificate) *x509.CertPool\n\t\twantErr      bool\n\t}{\n\t\t{\n\t\t\t\"Default not secure, no error\",\n\t\t\tfalse,\n\t\t\tfunc(_ *x509.Certificate) *x509.CertPool { return nil },\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"Default secure, want an error\",\n\t\t\ttrue,\n\t\t\tfunc(_ *x509.Certificate) *x509.CertPool { return nil },\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"Default secure, no error\",\n\t\t\ttrue,\n\t\t\tCertPoolFromCert,\n\t\t\tfalse,\n\t\t},\n\t}\n\n\tserver := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tfmt.Fprintln(w, `{\"hello\": \"client\"}`)\n\t}))\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\topts := []func(*http.Client){}\n\t\t\tif tc.secureClient {\n\t\t\t\topts = append(opts, SecureTLSOption(tc.withCertPool(server.Certificate())))\n\t\t\t}\n\t\t\tclient := Build(opts...)\n\t\t\treq, _ := http.NewRequest(http.MethodGet, server.URL, nil)\n\n\t\t\t_, err := client.Do(req)\n\t\t\tif tc.wantErr {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Fatal(\"Missing expected error\")\n\t\t\t\t}\n\n\t\t\t\t// Different versions of Go return different error messages so we just\n\t\t\t\t// check that its a *url.Error{}\n\t\t\t\tif _, ok := err.(*url.Error); !ok {\n\t\t\t\t\tt.Fatalf(\"Missing expected error: got %T: '%s'\", err, err)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Got unexpected error %s\", err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/ipmi/ipmi.go",
    "content": "package ipmi\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"github.com/go-logr/logr\"\n\t\"github.com/pkg/errors\"\n)\n\n// Ipmi holds the date for an ipmi connection\ntype Ipmi struct {\n\tUsername    string\n\tPassword    string\n\tHost        string\n\tipmitool    string\n\tcipherSuite string\n\tlog         logr.Logger\n}\n\n// Option for setting optional Ipmi values\ntype Option func(*Ipmi)\n\nfunc WithIpmitoolPath(path string) Option {\n\treturn func(i *Ipmi) {\n\t\ti.ipmitool = path\n\t}\n}\n\nfunc WithCipherSuite(cipherSuite string) Option {\n\treturn func(i *Ipmi) {\n\t\ti.cipherSuite = cipherSuite\n\t}\n}\n\nfunc WithLogger(log logr.Logger) Option {\n\treturn func(i *Ipmi) {\n\t\ti.log = log\n\t}\n}\n\n// New returns a new ipmi instance\nfunc New(username string, password string, host string, opts ...Option) (ipmi *Ipmi, err error) {\n\tipmi = &Ipmi{\n\t\tUsername: username,\n\t\tPassword: password,\n\t\tHost:     host,\n\t\tlog:      logr.Discard(),\n\t}\n\n\tfor _, opt := range opts {\n\t\topt(ipmi)\n\t}\n\n\tif ipmi.ipmitool == \"\" {\n\t\tipmi.ipmitool, err = exec.LookPath(\"ipmitool\")\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\tif _, err = os.Stat(ipmi.ipmitool); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn ipmi, err\n}\n\nfunc (i *Ipmi) run(ctx context.Context, command []string) (output string, err error) {\n\tvar out []byte\n\tvar ipmiCiphers = []string{\"3\", \"17\"}\n\tipmiArgs := []string{\"-I\", \"lanplus\", \"-U\", i.Username, \"-E\", \"-N\", \"5\"}\n\n\tif strings.Contains(i.Host, \":\") {\n\t\thost, port, err := net.SplitHostPort(i.Host)\n\t\tif err == nil {\n\t\t\tipmiArgs = append(ipmiArgs, \"-H\", host, \"-p\", port)\n\t\t}\n\t} else {\n\t\tipmiArgs = append(ipmiArgs, \"-H\", i.Host)\n\t}\n\n\tif i.cipherSuite != \"\" {\n\t\tipmiCiphers = []string{i.cipherSuite}\n\t}\n\tfor _, cipherString := range ipmiCiphers {\n\t\tipmiCmd := append(ipmiArgs, \"-C\", cipherString)\n\t\ti.log.V(3).Info(\"ipmitool options\", \"opts\", formatOptions(ipmiCmd))\n\t\tipmiCmd = append(ipmiCmd, command...)\n\t\tcmd := exec.CommandContext(ctx, i.ipmitool, ipmiCmd...)\n\t\tcmd.Env = []string{fmt.Sprintf(\"IPMITOOL_PASSWORD=%s\", i.Password)}\n\t\tout, err = cmd.CombinedOutput()\n\t\tif err == nil || ctx.Err() != nil {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif ctx.Err() == context.DeadlineExceeded {\n\t\treturn string(out), ctx.Err()\n\t}\n\n\treturn string(out), errors.Wrap(err, strings.TrimSpace(string(out)))\n}\n\ntype cmdOpt struct {\n\tOpt string `json:\"opt\"`\n\tVal string `json:\"val\"`\n}\n\nfunc formatOptions(opts []string) []cmdOpt {\n\tresult := []cmdOpt{}\n\tfor _, opt := range opts {\n\t\tif strings.HasPrefix(opt, \"-\") {\n\t\t\to := cmdOpt{Opt: opt}\n\t\t\tif opt == \"-E\" {\n\t\t\t\to.Val = \"-E\"\n\t\t\t}\n\t\t\tresult = append(result, o)\n\t\t} else {\n\t\t\tresult[len(result)-1].Val = opt\n\t\t}\n\t}\n\n\treturn result\n}\n\n// PowerCycle reboots the machine via bmc\nfunc (i *Ipmi) PowerCycle(ctx context.Context) (status bool, err error) {\n\toutput, err := i.run(ctx, []string{\"chassis\", \"power\", \"cycle\"})\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"%v: %v\", err, output)\n\t}\n\n\tif strings.HasPrefix(output, \"Chassis Power Control: Cycle\") {\n\t\treturn true, err\n\t}\n\treturn false, fmt.Errorf(\"%v: %v\", err, output)\n}\n\n// ForceRestart does the chassis power cycle even if the chassis is turned off.\n// From the RedFish spec (https://www.dmtf.org/sites/default/files/standards/documents/DSP2046_2018.1.pdf):\n//\n//\tPerform an immediate (non-graceful) shutdown, followed by a restart.\nfunc (i *Ipmi) ForceRestart(ctx context.Context) (status bool, err error) {\n\toutput, err := i.run(ctx, []string{\"chassis\", \"power\", \"status\"})\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"%v: %v\", err, output)\n\t}\n\n\tcommand := \"on\"\n\treply := \"Up/On\"\n\tif strings.HasPrefix(output, \"Chassis Power is on\") {\n\t\tcommand = \"cycle\"\n\t\treply = \"Cycle\"\n\t} else if !strings.HasPrefix(output, \"Chassis Power is off\") {\n\t\treturn false, fmt.Errorf(\"%v: %v\", err, output)\n\t}\n\n\toutput, err = i.run(ctx, []string{\"chassis\", \"power\", command})\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"%v: %v\", err, output)\n\t}\n\n\tif strings.HasPrefix(output, \"Chassis Power Control: \"+reply) {\n\t\treturn true, err\n\t}\n\treturn false, fmt.Errorf(\"%v: %v\", err, output)\n}\n\n// PowerReset reboots the machine via bmc\nfunc (i *Ipmi) PowerReset(ctx context.Context) (status bool, err error) {\n\toutput, err := i.run(ctx, []string{\"chassis\", \"power\", \"reset\"})\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"%v: %v\", err, output)\n\t}\n\n\tif !strings.HasPrefix(output, \"Chassis Power Control: Reset\") {\n\t\treturn false, fmt.Errorf(\"%v: %v\", err, output)\n\t}\n\treturn true, err\n}\n\n// PowerCycleBmc reboots the bmc we are connected to\nfunc (i *Ipmi) PowerCycleBmc(ctx context.Context) (status bool, err error) {\n\toutput, err := i.run(ctx, []string{\"mc\", \"reset\", \"cold\"})\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"%v: %v\", err, output)\n\t}\n\n\tif strings.HasPrefix(output, \"Sent cold reset command to MC\") {\n\t\treturn true, err\n\t}\n\treturn false, fmt.Errorf(\"%v: %v\", err, output)\n}\n\n// PowerResetBmc reboots the bmc we are connected to\nfunc (i *Ipmi) PowerResetBmc(ctx context.Context, resetType string) (ok bool, err error) {\n\toutput, err := i.run(ctx, []string{\"mc\", \"reset\", strings.ToLower(resetType)})\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"%v: %v\", err, output)\n\t}\n\n\tif strings.HasPrefix(output, fmt.Sprintf(\"Sent %v reset command to MC\", strings.ToLower(resetType))) {\n\t\treturn true, err\n\t}\n\treturn false, fmt.Errorf(\"%v: %v\", err, output)\n}\n\n// PowerOn power on the machine via bmc\nfunc (i *Ipmi) PowerOn(ctx context.Context) (status bool, err error) {\n\ts, err := i.IsOn(ctx)\n\tif err != nil {\n\t\treturn false, errors.Wrap(err, \"error checking power state\")\n\t}\n\tif s {\n\t\treturn true, nil\n\t}\n\n\toutput, err := i.run(ctx, []string{\"chassis\", \"power\", \"on\"})\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"%v: %v\", err, output)\n\t}\n\n\tif strings.HasPrefix(output, \"Chassis Power Control: Up/On\") {\n\t\treturn true, nil\n\t}\n\treturn false, fmt.Errorf(\"stderr/stdout: %v\", output)\n}\n\n// PowerOnForce power on the machine via bmc even when the machine is already on (Thanks HP!)\nfunc (i *Ipmi) PowerOnForce(ctx context.Context) (status bool, err error) {\n\toutput, err := i.run(ctx, []string{\"chassis\", \"power\", \"on\"})\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"%v: %v\", err, output)\n\t}\n\n\tif strings.HasPrefix(output, \"Chassis Power Control: Up/On\") {\n\t\treturn true, err\n\t}\n\treturn false, fmt.Errorf(\"%v: %v\", err, output)\n}\n\n// PowerOff power off the machine via bmc\nfunc (i *Ipmi) PowerOff(ctx context.Context) (status bool, err error) {\n\tif on, err := i.IsOn(ctx); err == nil && !on {\n\t\treturn true, nil\n\t}\n\toutput, err := i.run(ctx, []string{\"chassis\", \"power\", \"off\"})\n\tif strings.Contains(output, \"Chassis Power Control: Down/Off\") {\n\t\treturn true, err\n\t}\n\treturn false, fmt.Errorf(\"%v: %v\", err, output)\n}\n\n// PowerSoft power off the machine via bmc\nfunc (i *Ipmi) PowerSoft(ctx context.Context) (status bool, err error) {\n\ton, _ := i.IsOn(ctx)\n\tif !on {\n\t\treturn true, nil\n\t}\n\n\toutput, err := i.run(ctx, []string{\"chassis\", \"power\", \"soft\"})\n\tif !strings.Contains(output, \"Chassis Power Control: Soft\") {\n\t\treturn false, fmt.Errorf(\"%v: %v\", err, output)\n\t}\n\treturn true, err\n}\n\n// PxeOnceEfi makes the machine to boot via pxe once using EFI\nfunc (i *Ipmi) PxeOnceEfi(ctx context.Context) (status bool, err error) {\n\toutput, err := i.run(ctx, []string{\"chassis\", \"bootdev\", \"pxe\", \"options=efiboot\"})\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"%v: %v\", err, output)\n\t}\n\n\tif strings.Contains(output, \"Set Boot Device to pxe\") {\n\t\treturn true, err\n\t}\n\treturn false, fmt.Errorf(\"%v: %v\", err, output)\n}\n\n// BootDeviceSet sets the next boot device with options\nfunc (i *Ipmi) BootDeviceSet(ctx context.Context, bootDevice string, setPersistent, efiBoot bool) (ok bool, err error) {\n\tvar atLeastOneOptionSelected bool\n\tipmiCmd := []string{\"chassis\", \"bootdev\", strings.ToLower(bootDevice)}\n\tvar opts []string\n\tif setPersistent {\n\t\topts = append(opts, \"persistent\")\n\t\tatLeastOneOptionSelected = true\n\t}\n\tif efiBoot {\n\t\topts = append(opts, \"efiboot\")\n\t\tatLeastOneOptionSelected = true\n\t}\n\tif atLeastOneOptionSelected {\n\t\toptsJoined := strings.Join(opts, \",\")\n\t\toptsFull := fmt.Sprintf(\"options=%v\", optsJoined)\n\t\tipmiCmd = append(ipmiCmd, optsFull)\n\t}\n\n\toutput, err := i.run(ctx, ipmiCmd)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"%v: %v\", err, output)\n\t}\n\n\tif strings.Contains(output, fmt.Sprintf(\"Set Boot Device to %v\", strings.ToLower(bootDevice))) {\n\t\treturn true, err\n\t}\n\treturn false, fmt.Errorf(\"%v: %v\", err, output)\n}\n\n// PxeOnceMbr makes the machine to boot via pxe once using MBR\nfunc (i *Ipmi) PxeOnceMbr(ctx context.Context) (status bool, err error) {\n\toutput, err := i.run(ctx, []string{\"chassis\", \"bootdev\", \"pxe\"})\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"%v: %v\", err, output)\n\t}\n\n\tif strings.Contains(output, \"Set Boot Device to pxe\") {\n\t\treturn true, err\n\t}\n\treturn false, fmt.Errorf(\"%v: %v\", err, output)\n}\n\n// PxeOnce makes the machine to boot via pxe once using MBR\nfunc (i *Ipmi) PxeOnce(ctx context.Context) (status bool, err error) {\n\treturn i.PxeOnceMbr(ctx)\n}\n\n// IsOn tells if a machine is currently powered on\nfunc (i *Ipmi) IsOn(ctx context.Context) (status bool, err error) {\n\toutput, err := i.run(ctx, []string{\"chassis\", \"power\", \"status\"})\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"%v: %v\", err, output)\n\t}\n\n\tif strings.Contains(output, \"Chassis Power is on\") {\n\t\treturn true, err\n\t}\n\treturn false, err\n}\n\n// PowerState returns the current power state of the machine\nfunc (i *Ipmi) PowerState(ctx context.Context) (state string, err error) {\n\treturn i.run(ctx, []string{\"chassis\", \"power\", \"status\"})\n}\n\n// ReadUsers list all BMC users\nfunc (i *Ipmi) ReadUsers(ctx context.Context) (users []map[string]string, err error) {\n\toutput, err := i.run(ctx, []string{\"user\", \"list\"})\n\tif err != nil {\n\t\treturn users, errors.Wrap(err, \"error getting user list\")\n\t}\n\n\theader := map[int]string{}\n\tfirstLine := true\n\tscanner := bufio.NewScanner(strings.NewReader(output))\n\tfor scanner.Scan() {\n\t\tline := strings.Fields(scanner.Text())\n\t\tif firstLine {\n\t\t\tfirstLine = false\n\t\t\tfor x := 0; x < 5; x++ {\n\t\t\t\theader[x] = line[x]\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tentry := map[string]string{}\n\t\tif line[1] != \"true\" {\n\t\t\tfor x := 0; x < 5; x++ {\n\t\t\t\tentry[header[x]] = line[x]\n\t\t\t}\n\t\t\tusers = append(users, entry)\n\t\t}\n\t}\n\n\treturn users, err\n}\n\n// ClearSystemEventLog clears the system event log\nfunc (i *Ipmi) ClearSystemEventLog(ctx context.Context) (err error) {\n\t_, err = i.run(ctx, []string{\"sel\", \"clear\"})\n\treturn err\n}\n\n// GetSystemEventLog returns the system event log entries in ID, Timestamp, Description, Message format\nfunc (i *Ipmi) GetSystemEventLog(ctx context.Context) (entries [][]string, err error) {\n\toutput, err := i.GetSystemEventLogRaw(ctx)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"error getting system event log\")\n\t}\n\n\tentries = parseSystemEventLog(output)\n\n\treturn entries, nil\n}\n\n// parseSystemEventLogRaw parses the raw output of the system event log. Helper\n// function for GetSystemEventLog to make testing the parser easier.\nfunc parseSystemEventLog(raw string) (entries [][]string) {\n\tscanner := bufio.NewScanner(strings.NewReader(raw))\n\tfor scanner.Scan() {\n\t\tline := strings.Split(scanner.Text(), \"|\")\n\t\tif len(line) < 6 {\n\t\t\tcontinue\n\t\t}\n\t\tif line[0] == \"ID\" {\n\t\t\tcontinue\n\t\t}\n\t\tfor i := range line {\n\t\t\tline[i] = strings.TrimSpace(line[i])\n\t\t}\n\t\t// ID, Timestamp (date time), Description, Message (message : assertion)\n\t\tentries = append(entries, []string{line[0], fmt.Sprintf(\"%s %s\", line[1], line[2]), line[3], fmt.Sprintf(\"%s : %s\", line[4], line[5])})\n\t}\n\n\treturn entries\n}\n\n// GetSystemEventLogRaw returns the raw SEL output\nfunc (i *Ipmi) GetSystemEventLogRaw(ctx context.Context) (eventlog string, err error) {\n\toutput, err := i.run(ctx, []string{\"sel\", \"list\"})\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"error getting system event log\")\n\t}\n\n\treturn output, nil\n}\n\nfunc (i *Ipmi) DeactivateSOL(ctx context.Context) (err error) {\n\tout, err := i.run(ctx, []string{\"sol\", \"deactivate\"})\n\t// Don't treat this as a failure (we just want to ensure there\n\t// isn't an active SOL session left open)\n\tif strings.TrimSpace(out) == \"Info: SOL payload already de-activated\" {\n\t\terr = nil\n\t}\n\treturn err\n}\n\n// SendPowerDiag tells the BMC to issue an NMI to the device\nfunc (i *Ipmi) SendPowerDiag(ctx context.Context) error {\n\t_, err := i.run(ctx, []string{\"chassis\", \"power\", \"diag\"})\n\tif err != nil {\n\t\terr = errors.Wrap(err, \"failed sending power diag\")\n\t}\n\n\treturn err\n}\n"
  },
  {
    "path": "internal/redfishwrapper/bios.go",
    "content": "package redfishwrapper\n\nimport (\n\t\"context\"\n\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/stmcginnis/gofish/schemas\"\n)\n\nfunc (c *Client) GetBiosConfiguration(ctx context.Context) (biosConfig map[string]string, err error) {\n\tsys, err := c.System()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbiosConfig = make(map[string]string)\n\tif !c.compatibleOdataID(sys.ODataID, knownSystemsOdataIDs) {\n\t\treturn biosConfig, nil\n\t}\n\n\tbios, err := sys.Bios()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif bios == nil {\n\t\treturn nil, bmclibErrs.ErrNoBiosAttributes\n\t}\n\n\tfor attr := range bios.Attributes {\n\t\tbiosConfig[attr] = bios.Attributes.String(attr)\n\t}\n\n\treturn biosConfig, nil\n}\n\nfunc (c *Client) SetBiosConfiguration(ctx context.Context, biosConfig map[string]string) (err error) {\n\tsys, err := c.System()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tsettingsAttributes := make(schemas.SettingsAttributes)\n\n\tfor attr, value := range biosConfig {\n\t\tsettingsAttributes[attr] = value\n\t}\n\n\tif !c.compatibleOdataID(sys.ODataID, knownSystemsOdataIDs) {\n\t\treturn nil\n\t}\n\n\tbios, err := sys.Bios()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// TODO(jwb) We should handle passing different apply times here\n\treturn bios.UpdateBiosAttributesApplyAt(settingsAttributes, schemas.OnResetSettingsApplyTime)\n}\n\nfunc (c *Client) ResetBiosConfiguration(ctx context.Context) (err error) {\n\tsys, err := c.System()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !c.compatibleOdataID(sys.ODataID, knownSystemsOdataIDs) {\n\t\treturn nil\n\t}\n\n\tbios, err := sys.Bios()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = bios.ResetBios()\n\treturn err\n}\n"
  },
  {
    "path": "internal/redfishwrapper/bios_test.go",
    "content": "package redfishwrapper\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc biosConfigFromFixture(t *testing.T) map[string]string {\n\tt.Helper()\n\n\tfixturePath := fixturesDir + \"/dell/bios.json\"\n\tfh, err := os.Open(fixturePath)\n\tif err != nil {\n\t\tt.Fatalf(\"%s, failed to open fixture: %s\", err.Error(), fixturePath)\n\t}\n\n\tdefer fh.Close()\n\n\tb, err := io.ReadAll(fh)\n\tif err != nil {\n\t\tt.Fatalf(\"%s, failed to read fixture: %s\", err.Error(), fixturePath)\n\t}\n\n\tvar bios map[string]any\n\terr = json.Unmarshal([]byte(b), &bios)\n\tif err != nil {\n\t\tt.Fatalf(\"%s, failed to unmarshal fixture: %s\", err.Error(), fixturePath)\n\t}\n\n\texpectedBiosConfig := make(map[string]string)\n\tfor k, v := range bios[\"Attributes\"].(map[string]any) {\n\t\texpectedBiosConfig[k] = fmt.Sprintf(\"%v\", v)\n\t}\n\n\treturn expectedBiosConfig\n}\n\nfunc TestGetBiosConfiguration(t *testing.T) {\n\ttests := []struct {\n\t\ttestName           string\n\t\thfunc              map[string]func(http.ResponseWriter, *http.Request)\n\t\texpectedBiosConfig map[string]string\n\t}{\n\t\t{\n\t\t\t\"GetBiosConfiguration\",\n\t\t\tmap[string]func(http.ResponseWriter, *http.Request){\n\t\t\t\t\"/redfish/v1/\":                               endpointFunc(t, \"/dell/serviceroot.json\"),\n\t\t\t\t\"/redfish/v1/Systems\":                        endpointFunc(t, \"/dell/systems.json\"),\n\t\t\t\t\"/redfish/v1/Systems/System.Embedded.1\":      endpointFunc(t, \"/dell/system.embedded.1.json\"),\n\t\t\t\t\"/redfish/v1/Systems/System.Embedded.1/Bios\": endpointFunc(t, \"/dell/bios.json\"),\n\t\t\t},\n\t\t\tbiosConfigFromFixture(t),\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\tmux := http.NewServeMux()\n\t\t\thandleFunc := tc.hfunc\n\t\t\tfor endpoint, handler := range handleFunc {\n\t\t\t\tmux.HandleFunc(endpoint, handler)\n\t\t\t}\n\n\t\t\tserver := httptest.NewTLSServer(mux)\n\t\t\tdefer server.Close()\n\n\t\t\tparsedURL, err := url.Parse(server.URL)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tctx := context.Background()\n\t\t\tclient := NewClient(parsedURL.Hostname(), parsedURL.Port(), \"\", \"\", WithBasicAuthEnabled(true))\n\n\t\t\terr = client.Open(ctx)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tbiosConfig, err := client.GetBiosConfiguration(ctx)\n\t\t\tassert.Nil(t, err)\n\t\t\tassert.Equal(t, tc.expectedBiosConfig, biosConfig)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/redfishwrapper/boot_device.go",
    "content": "package redfishwrapper\n\nimport (\n\t\"context\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/bmc\"\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/stmcginnis/gofish/schemas\"\n)\n\ntype bootDeviceMapping struct {\n\tBootDeviceType bmc.BootDeviceType\n\tRedFishTarget  schemas.BootSource\n}\n\nvar bootDeviceTypeMappings = []bootDeviceMapping{\n\t{\n\t\tBootDeviceType: bmc.BootDeviceTypeBIOS,\n\t\tRedFishTarget:  schemas.BiosSetupBootSource,\n\t},\n\t{\n\t\tBootDeviceType: bmc.BootDeviceTypeCDROM,\n\t\tRedFishTarget:  schemas.CdBootSource,\n\t},\n\t{\n\t\tBootDeviceType: bmc.BootDeviceTypeDiag,\n\t\tRedFishTarget:  schemas.DiagsBootSource,\n\t},\n\t{\n\t\tBootDeviceType: bmc.BootDeviceTypeFloppy,\n\t\tRedFishTarget:  schemas.FloppyBootSource,\n\t},\n\t{\n\t\tBootDeviceType: bmc.BootDeviceTypeDisk,\n\t\tRedFishTarget:  schemas.HddBootSource,\n\t},\n\t{\n\t\tBootDeviceType: bmc.BootDeviceTypeNone,\n\t\tRedFishTarget:  schemas.NoneBootSource,\n\t},\n\t{\n\t\tBootDeviceType: bmc.BootDeviceTypePXE,\n\t\tRedFishTarget:  schemas.PxeBootSource,\n\t},\n\t{\n\t\tBootDeviceType: bmc.BootDeviceTypeRemoteDrive,\n\t\tRedFishTarget:  schemas.RemoteDriveBootSource,\n\t},\n\t{\n\t\tBootDeviceType: bmc.BootDeviceTypeSDCard,\n\t\tRedFishTarget:  schemas.SDCardBootSource,\n\t},\n\t{\n\t\tBootDeviceType: bmc.BootDeviceTypeUSB,\n\t\tRedFishTarget:  schemas.UsbBootSource,\n\t},\n\t{\n\t\tBootDeviceType: bmc.BootDeviceTypeUtil,\n\t\tRedFishTarget:  schemas.UtilitiesBootSource,\n\t},\n\t{\n\t\tBootDeviceType: bmc.BootDeviceUefiHTTP,\n\t\tRedFishTarget:  schemas.UefiHTTPBootSource,\n\t},\n}\n\n// bootDeviceStringToTarget gets the RedFish BootSource that corresponds to the given device string,\n// or an error if the device is not a RedFish BootSource.\nfunc bootDeviceStringToTarget(device string) (schemas.BootSource, error) {\n\tfor _, bootDevice := range bootDeviceTypeMappings {\n\t\tif string(bootDevice.BootDeviceType) == device {\n\t\t\treturn bootDevice.RedFishTarget, nil\n\t\t}\n\t}\n\treturn \"\", errors.New(\"invalid boot device\")\n}\n\n// bootTargetToBootDeviceType converts the redfish boot target to a bmc.BootDeviceType.\n// if the target is unknown or unsupported, then an error is returned.\nfunc bootTargetToBootDeviceType(target schemas.BootSource) (bmc.BootDeviceType, error) {\n\tfor _, bootDevice := range bootDeviceTypeMappings {\n\t\tif bootDevice.RedFishTarget == target {\n\t\t\treturn bootDevice.BootDeviceType, nil\n\t\t}\n\t}\n\treturn \"\", errors.New(\"invalid boot device\")\n}\n\n// SystemBootDeviceSet set the boot device for the system.\nfunc (c *Client) SystemBootDeviceSet(_ context.Context, bootDevice string, setPersistent, efiBoot bool) (ok bool, err error) {\n\tif err := c.SessionActive(); err != nil {\n\t\treturn false, errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error())\n\t}\n\n\tsystem, err := c.System()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tboot := system.Boot\n\n\tboot.BootSourceOverrideTarget, err = bootDeviceStringToTarget(bootDevice)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif setPersistent {\n\t\tboot.BootSourceOverrideEnabled = schemas.ContinuousBootSourceOverrideEnabled\n\t} else {\n\t\tboot.BootSourceOverrideEnabled = schemas.OnceBootSourceOverrideEnabled\n\t}\n\n\tif efiBoot {\n\t\tboot.BootSourceOverrideMode = schemas.UEFIBootSourceOverrideMode\n\t} else {\n\t\tboot.BootSourceOverrideMode = schemas.LegacyBootSourceOverrideMode\n\t}\n\n\tif err = system.SetBoot(&boot); err != nil {\n\t\t// Some redfish implementations don't like all the fields we're setting so we\n\t\t// try again here with a minimal set of fields. This has shown to work with the\n\t\t// Redfish implementation on HP DL160 Gen10.\n\t\tsecondTry := schemas.Boot{}\n\t\tsecondTry.BootSourceOverrideTarget = boot.BootSourceOverrideTarget\n\t\tsecondTry.BootSourceOverrideEnabled = boot.BootSourceOverrideEnabled\n\t\tif err = system.SetBoot(&secondTry); err != nil {\n\t\t\treturn false, err\n\t\t}\n\t}\n\n\treturn true, nil\n}\n\n// GetBootDeviceOverride returns the current boot override settings\nfunc (c *Client) GetBootDeviceOverride(_ context.Context) (override bmc.BootDeviceOverride, err error) {\n\tif err := c.SessionActive(); err != nil {\n\t\treturn override, errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error())\n\t}\n\n\tsystem, err := c.System()\n\tif err != nil {\n\t\treturn override, err\n\t}\n\n\tboot := system.Boot\n\tbootDevice, err := bootTargetToBootDeviceType(boot.BootSourceOverrideTarget)\n\tif err != nil {\n\t\treturn override, err\n\t}\n\n\toverride = bmc.BootDeviceOverride{\n\t\tIsPersistent: boot.BootSourceOverrideEnabled == schemas.ContinuousBootSourceOverrideEnabled,\n\t\tIsEFIBoot:    boot.BootSourceOverrideMode == schemas.UEFIBootSourceOverrideMode,\n\t\tDevice:       bootDevice,\n\t}\n\n\treturn override, nil\n}\n"
  },
  {
    "path": "internal/redfishwrapper/client.go",
    "content": "package redfishwrapper\n\nimport (\n\t\"context\"\n\t\"crypto/x509\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/bmc-toolbox/bmclib/v2/internal/httpclient\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/stmcginnis/gofish\"\n\t\"github.com/stmcginnis/gofish/schemas\"\n)\n\nvar (\n\tErrManagerID = errors.New(\"error identifying Manager Odata ID\")\n\tErrBIOSID    = errors.New(\"error identifying System BIOS Odata ID\")\n)\n\n// Client is a redfishwrapper client which wraps the gofish client.\ntype Client struct {\n\thost                  string\n\tport                  string\n\tuser                  string\n\tpass                  string\n\tsystemName            string\n\tbasicAuth             bool\n\tdisableEtagMatch      bool\n\tversionsNotCompatible []string // a slice of redfish versions to ignore as incompatible\n\tclient                *gofish.APIClient\n\thttpClient            *http.Client\n\thttpClientSetupFuncs  []func(*http.Client)\n\tlogger                logr.Logger\n}\n\n// Option is a function applied to a *Conn\ntype Option func(*Client)\n\n// WithHTTPClient returns an option that sets an HTTP client for the connecion\nfunc WithHTTPClient(cli *http.Client) Option {\n\treturn func(c *Client) {\n\t\tc.httpClient = cli\n\t}\n}\n\n// WithSecureTLS returns an option that enables secure TLS with an optional cert pool.\nfunc WithSecureTLS(rootCAs *x509.CertPool) Option {\n\treturn func(c *Client) {\n\t\tc.httpClientSetupFuncs = append(c.httpClientSetupFuncs, httpclient.SecureTLSOption(rootCAs))\n\t}\n}\n\n// WithVersionsNotCompatible returns an option that sets the redfish versions to ignore as incompatible.\n//\n// The version string value must match the value returned by\n// curl -k  \"https://10.247.133.39/redfish/v1\" | jq .RedfishVersion\nfunc WithVersionsNotCompatible(versions []string) Option {\n\treturn func(c *Client) {\n\t\tc.versionsNotCompatible = append(c.versionsNotCompatible, versions...)\n\t}\n}\n\n// WithBasicAuthEnabled sets Basic Auth on the Gofish driver.\nfunc WithBasicAuthEnabled(e bool) Option {\n\treturn func(c *Client) {\n\t\tc.basicAuth = e\n\t}\n}\n\n// WithEtagMatchDisabled disables the If-Match Etag header from being included by the Gofish driver.\n//\n// As of the current implementation this disables the header for POST/PATCH requests to the System entity endpoints.\nfunc WithEtagMatchDisabled(d bool) Option {\n\treturn func(c *Client) {\n\t\tc.disableEtagMatch = d\n\t}\n}\n\n// WithLogger sets the logger on the redfish wrapper client\nfunc WithLogger(l *logr.Logger) Option {\n\treturn func(c *Client) {\n\t\tc.logger = *l\n\t}\n}\n\nfunc WithSystemName(name string) Option {\n\treturn func(c *Client) {\n\t\tc.systemName = name\n\t}\n}\n\n// NewClient returns a redfishwrapper client\nfunc NewClient(host, port, user, pass string, opts ...Option) *Client {\n\tif !strings.HasPrefix(host, \"https://\") && !strings.HasPrefix(host, \"http://\") {\n\t\thost = \"https://\" + host\n\t}\n\n\tclient := &Client{\n\t\thost:                  host,\n\t\tport:                  port,\n\t\tuser:                  user,\n\t\tpass:                  pass,\n\t\tlogger:                logr.Discard(),\n\t\tversionsNotCompatible: []string{},\n\t}\n\n\tfor _, opt := range opts {\n\t\topt(client)\n\t}\n\n\treturn client\n}\n\n// Open sets up a new redfish session.\nfunc (c *Client) Open(ctx context.Context) error {\n\tendpoint := c.host\n\tif c.port != \"\" {\n\t\tendpoint = c.host + \":\" + c.port\n\t}\n\n\tconfig := gofish.ClientConfig{\n\t\tEndpoint:   endpoint,\n\t\tUsername:   c.user,\n\t\tPassword:   c.pass,\n\t\tInsecure:   true,\n\t\tHTTPClient: c.httpClient,\n\t\tBasicAuth:  c.basicAuth,\n\t}\n\n\tif config.HTTPClient == nil {\n\t\tconfig.HTTPClient = httpclient.Build(c.httpClientSetupFuncs...)\n\t} else {\n\t\tfor _, setupFunc := range c.httpClientSetupFuncs {\n\t\t\tsetupFunc(config.HTTPClient)\n\t\t}\n\t}\n\n\tdebug := os.Getenv(\"DEBUG_BMCLIB\")\n\tif debug == \"true\" {\n\t\tconfig.DumpWriter = os.Stdout\n\t}\n\n\tif tm := getTimeout(ctx); tm != 0 {\n\t\tconfig.HTTPClient.Timeout = tm\n\t}\n\tvar err error\n\tc.client, err = gofish.Connect(config)\n\n\treturn err\n}\n\nfunc getTimeout(ctx context.Context) time.Duration {\n\tdeadline, ok := ctx.Deadline()\n\tif !ok {\n\t\treturn 0\n\t}\n\n\treturn time.Until(deadline)\n}\n\n// Close closes the redfish session.\nfunc (c *Client) Close(ctx context.Context) error {\n\tif c.client == nil || c.client.Service == nil {\n\t\treturn nil\n\t}\n\n\tc.client.Logout()\n\n\treturn nil\n}\n\n// SessionActive returns an error if a redfish session is not active.\nfunc (c *Client) SessionActive() error {\n\tif c.client == nil || c.client.Service == nil {\n\t\treturn bmclibErrs.ErrNotAuthenticated\n\t}\n\n\t// With basic auth enabled theres no session to be checked.\n\tif c.basicAuth {\n\t\treturn nil\n\t}\n\n\t_, err := c.client.GetSession()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Overrides the HTTP client timeout\nfunc (c *Client) SetHttpClientTimeout(t time.Duration) {\n\tc.client.HTTPClient.Timeout = t\n}\n\n// retrieve the current HTTP client timeout\nfunc (c *Client) HttpClientTimeout() time.Duration {\n\treturn c.client.HTTPClient.Timeout\n}\n\n// RunRawRequestWithHeaders wraps the gofish client method RunRawRequestWithHeaders\nfunc (c *Client) RunRawRequestWithHeaders(method, url string, payloadBuffer io.ReadSeeker, contentType string, customHeaders map[string]string) (*http.Response, error) {\n\tif err := c.SessionActive(); err != nil {\n\t\treturn nil, errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error())\n\t}\n\n\treturn c.client.RunRawRequestWithHeaders(method, url, payloadBuffer, contentType, customHeaders)\n}\n\nfunc (c *Client) Delete(url string) (*http.Response, error) {\n\treturn c.client.Delete(url)\n}\n\nfunc (c *Client) Get(url string) (*http.Response, error) {\n\treturn c.client.Get(url)\n}\n\n// VersionCompatible compares the redfish version reported by the BMC with the blacklist if specified.\nfunc (c *Client) VersionCompatible() bool {\n\tif len(c.versionsNotCompatible) == 0 {\n\t\treturn true\n\t}\n\n\tif err := c.SessionActive(); err != nil {\n\t\treturn false\n\t}\n\n\treturn !slices.Contains(c.versionsNotCompatible, c.client.Service.RedfishVersion)\n}\n\n// redfishVersionMeetsOrExceeds compares this connection's redfish version to what is provided\n// as a requirement. We rely on the stated structure of the version string as described in the\n// Protocol Version (section 6.6) of the Redfish spec. If an implementation's version string is\n// non-conforming this function returns false.\nfunc redfishVersionMeetsOrExceeds(version string, major, minor, patch int) bool {\n\tif version == \"\" {\n\t\treturn false\n\t}\n\n\tparts := strings.Split(version, \".\")\n\tif len(parts) != 3 {\n\t\treturn false\n\t}\n\n\tvar rfVer []int64\n\tfor _, part := range parts {\n\t\tver, err := strconv.ParseInt(part, 10, 32)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\trfVer = append(rfVer, ver)\n\t}\n\n\tif rfVer[0] < int64(major) {\n\t\treturn false\n\t}\n\n\tif rfVer[1] < int64(minor) {\n\t\treturn false\n\t}\n\n\treturn rfVer[2] >= int64(patch)\n}\n\nfunc (c *Client) GetBootProgress() ([]*schemas.BootProgress, error) {\n\t// The redfish standard adopts the BootProgress object in 1.13.0. Earlier versions of redfish return\n\t// json NULL, which gofish turns into a zero-value object of BootProgress. We gate this on the RedfishVersion\n\t// to avoid the complexity of interpreting whether a given value is legitimate.\n\tif !redfishVersionMeetsOrExceeds(c.client.Service.RedfishVersion, 1, 13, 0) {\n\t\treturn nil, fmt.Errorf(\"%w: %s\", bmclibErrs.ErrRedfishVersionIncompatible, c.client.Service.RedfishVersion)\n\t}\n\n\tsystems, err := c.client.Service.Systems()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"retrieving redfish systems collection: %w\", err)\n\t}\n\n\tbps := []*schemas.BootProgress{}\n\tfor _, sys := range systems {\n\t\tbps = append(bps, &sys.BootProgress)\n\t}\n\n\treturn bps, nil\n}\n\nfunc (c *Client) PostWithHeaders(ctx context.Context, url string, payload interface{}, headers map[string]string) (*http.Response, error) {\n\treturn c.client.PostWithHeaders(url, payload, headers)\n}\n\nfunc (c *Client) PatchWithHeaders(ctx context.Context, url string, payload interface{}, headers map[string]string) (*http.Response, error) {\n\treturn c.client.PatchWithHeaders(url, payload, headers)\n}\n\nfunc (c *Client) Tasks(ctx context.Context) ([]*schemas.Task, error) {\n\tts, err := c.client.Service.Tasks()\n\tif err != nil {\n\t\treturn []*schemas.Task{}, err\n\t}\n\n\treturn ts.Tasks()\n}\n\nfunc (c *Client) ManagerOdataID(ctx context.Context) (string, error) {\n\tmanagers, err := c.client.Service.Managers()\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(ErrManagerID, err.Error())\n\t}\n\n\tfor _, m := range managers {\n\t\tif m.ID != \"\" {\n\t\t\treturn m.ODataID, nil\n\t\t}\n\t}\n\n\treturn \"\", ErrManagerID\n}\n\nfunc (c *Client) SystemsBIOSOdataID(ctx context.Context) (string, error) {\n\tsystems, err := c.client.Service.Systems()\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(ErrBIOSID, err.Error())\n\t}\n\n\tfor _, s := range systems {\n\t\tbios, err := s.Bios()\n\t\tif err != nil {\n\t\t\treturn \"\", errors.Wrap(ErrBIOSID, err.Error())\n\t\t}\n\n\t\tif bios == nil {\n\t\t\treturn \"\", ErrBIOSID\n\t\t}\n\n\t\tif bios.ID != \"\" {\n\t\t\treturn bios.ODataID, nil\n\t\t}\n\t}\n\n\treturn \"\", ErrBIOSID\n}\n\n// DeviceVendorModel returns the device manufacturer and model attributes\nfunc (c *Client) DeviceVendorModel(ctx context.Context) (vendor, model string, err error) {\n\tsystems, err := c.client.Service.Systems()\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\n\tfor _, sys := range systems {\n\t\treturn sys.Manufacturer, sys.Model, nil\n\t}\n\n\treturn vendor, model, bmclibErrs.ErrSystemVendorModel\n}\n"
  },
  {
    "path": "internal/redfishwrapper/client_test.go",
    "content": "package redfishwrapper\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"testing\"\n\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/stmcginnis/gofish/schemas\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestWithVersionsNotCompatible(t *testing.T) {\n\thost := \"127.0.0.1\"\n\tuser := \"ADMIN\"\n\tpass := \"ADMIN\"\n\n\ttests := []struct {\n\t\tname     string\n\t\tversions []string\n\t}{\n\t\t{\n\t\t\t\"no versions\",\n\t\t\t[]string{},\n\t\t},\n\t\t{\n\t\t\t\"with versions\",\n\t\t\t[]string{\"1.2.3\", \"4.5.6\"},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tclient := NewClient(host, \"\", user, pass, WithVersionsNotCompatible(tt.versions))\n\t\t\tassert.Equal(t, tt.versions, client.versionsNotCompatible)\n\t\t})\n\t}\n}\n\nfunc TestWithBasicAuthEnabled(t *testing.T) {\n\thost := \"127.0.0.1\"\n\tuser := \"ADMIN\"\n\tpass := \"ADMIN\"\n\n\ttests := []struct {\n\t\tname    string\n\t\tenabled bool\n\t}{\n\t\t{\n\t\t\t\"disabled\",\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"enabled\",\n\t\t\ttrue,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tclient := NewClient(host, \"\", user, pass, WithBasicAuthEnabled(tt.enabled))\n\t\t\tassert.Equal(t, tt.enabled, client.basicAuth)\n\t\t})\n\t}\n}\n\nfunc TestWithEtagMatchDisabled(t *testing.T) {\n\thost := \"127.0.0.1\"\n\tuser := \"ADMIN\"\n\tpass := \"ADMIN\"\n\n\ttests := []struct {\n\t\tname     string\n\t\tdisabled bool\n\t}{\n\t\t{\n\t\t\t\"disabled\",\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"enabled\",\n\t\t\tfalse,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tclient := NewClient(host, \"\", user, pass, WithEtagMatchDisabled(tt.disabled))\n\t\t\tassert.Equal(t, tt.disabled, client.disableEtagMatch)\n\t\t})\n\t}\n}\n\nconst (\n\tfixturesDir = \"./fixtures\"\n)\n\nfunc TestManagerOdataID(t *testing.T) {\n\ttests := map[string]struct {\n\t\thfunc  map[string]func(http.ResponseWriter, *http.Request)\n\t\texpect string\n\t\terr    error\n\t}{\n\t\t\"happy case\": {\n\t\t\thfunc: map[string]func(http.ResponseWriter, *http.Request){\n\t\t\t\t// service root\n\t\t\t\t\"/redfish/v1/\":           endpointFunc(t, \"serviceroot.json\"),\n\t\t\t\t\"/redfish/v1/Systems\":    endpointFunc(t, \"systems.json\"),\n\t\t\t\t\"/redfish/v1/Managers\":   endpointFunc(t, \"managers.json\"),\n\t\t\t\t\"/redfish/v1/Managers/1\": endpointFunc(t, \"managers_1.json\"),\n\t\t\t},\n\t\t\texpect: \"/redfish/v1/Managers/1\",\n\t\t\terr:    nil,\n\t\t},\n\t\t\"failure case\": {\n\t\t\thfunc: map[string]func(http.ResponseWriter, *http.Request){\n\t\t\t\t\"/redfish/v1/\": endpointFunc(t, \"/serviceroot_no_manager.json\"),\n\t\t\t},\n\t\t\texpect: \"\",\n\t\t\terr:    ErrManagerID,\n\t\t},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tmux := http.NewServeMux()\n\t\t\thandleFunc := tc.hfunc\n\t\t\tfor endpoint, handler := range handleFunc {\n\t\t\t\tmux.HandleFunc(endpoint, handler)\n\t\t\t}\n\n\t\t\tserver := httptest.NewTLSServer(mux)\n\t\t\tdefer server.Close()\n\n\t\t\tparsedURL, err := url.Parse(server.URL)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tctx := context.Background()\n\n\t\t\t//os.Setenv(\"DEBUG_BMCLIB\", \"true\")\n\t\t\tclient := NewClient(parsedURL.Hostname(), parsedURL.Port(), \"\", \"\")\n\n\t\t\terr = client.Open(ctx)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tgot, err := client.ManagerOdataID(ctx)\n\t\t\tif err != nil {\n\t\t\t\tassert.Equal(t, tc.err, err)\n\t\t\t}\n\n\t\t\tassert.Equal(t, tc.expect, got)\n\n\t\t\tclient.Close(context.Background())\n\t\t})\n\t}\n}\n\nfunc TestSystemsBIOSOdataID(t *testing.T) {\n\ttests := map[string]struct {\n\t\thfunc  map[string]func(http.ResponseWriter, *http.Request)\n\t\texpect string\n\t\terr    error\n\t}{\n\t\t\"happy case\": {\n\t\t\thfunc: map[string]func(http.ResponseWriter, *http.Request){\n\t\t\t\t// service root\n\t\t\t\t\"/redfish/v1/\":               endpointFunc(t, \"serviceroot.json\"),\n\t\t\t\t\"/redfish/v1/Systems\":        endpointFunc(t, \"systems.json\"),\n\t\t\t\t\"/redfish/v1/Systems/1\":      endpointFunc(t, \"systems_1.json\"),\n\t\t\t\t\"/redfish/v1/Systems/1/Bios\": endpointFunc(t, \"systems_bios.json\"),\n\t\t\t},\n\t\t\texpect: \"/redfish/v1/Systems/1/Bios\",\n\t\t\terr:    nil,\n\t\t},\n\t\t\"failure case\": {\n\t\t\thfunc: map[string]func(http.ResponseWriter, *http.Request){\n\t\t\t\t\"/redfish/v1/\": endpointFunc(t, \"serviceroot.json\"),\n\t\t\t},\n\t\t\texpect: \"\",\n\t\t\terr:    ErrBIOSID,\n\t\t},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tmux := http.NewServeMux()\n\t\t\thandleFunc := tc.hfunc\n\t\t\tfor endpoint, handler := range handleFunc {\n\t\t\t\tmux.HandleFunc(endpoint, handler)\n\t\t\t}\n\n\t\t\tserver := httptest.NewTLSServer(mux)\n\t\t\tdefer server.Close()\n\n\t\t\tparsedURL, err := url.Parse(server.URL)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tctx := context.Background()\n\n\t\t\tclient := NewClient(parsedURL.Hostname(), parsedURL.Port(), \"\", \"\")\n\n\t\t\terr = client.Open(ctx)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tgot, err := client.SystemsBIOSOdataID(ctx)\n\t\t\tif err != nil {\n\t\t\t\tassert.Equal(t, tc.err, err)\n\t\t\t}\n\n\t\t\tassert.Equal(t, tc.expect, got)\n\n\t\t\tclient.Close(context.Background())\n\t\t})\n\t}\n}\n\nfunc TestRedfishVersionMeetsOrExceeds(t *testing.T) {\n\tt.Parallel()\n\n\ttestCases := []struct {\n\t\tname    string\n\t\tversion string\n\t\texp     bool\n\t}{\n\t\t{\n\t\t\t\"empty string\",\n\t\t\t\"\",\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"short string\",\n\t\t\t\"1.2\",\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"bogus component\",\n\t\t\t\"1.asdf.2\",\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"major too low\",\n\t\t\t\"0.3.4\",\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"minor too low\",\n\t\t\t\"1.1.3\",\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"patch too low\",\n\t\t\t\"1.2.2\",\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"meets\",\n\t\t\t\"1.2.3\",\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"exceeds\",\n\t\t\t\"1.2.4\",\n\t\t\ttrue,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tgot := redfishVersionMeetsOrExceeds(tc.version, 1, 2, 3)\n\t\tassert.Equal(t, tc.exp, got, \"testcase %s\", tc.name)\n\t}\n}\n\nfunc TestGetBootProgress(t *testing.T) {\n\ttests := map[string]struct {\n\t\thfunc  map[string]func(http.ResponseWriter, *http.Request)\n\t\texpect []*schemas.BootProgress\n\t\terr    error\n\t}{\n\t\t\"happy case\": {\n\t\t\thfunc: map[string]func(http.ResponseWriter, *http.Request){\n\t\t\t\t// service root\n\t\t\t\t\"/redfish/v1/\":          endpointFunc(t, \"smc_1.14.0_serviceroot.json\"),\n\t\t\t\t\"/redfish/v1/Systems\":   endpointFunc(t, \"smc_1.14.0_systems.json\"),\n\t\t\t\t\"/redfish/v1/Systems/1\": endpointFunc(t, \"smc_1.14.0_systems_1.json\"),\n\t\t\t},\n\t\t\texpect: []*schemas.BootProgress{\n\t\t\t\t&schemas.BootProgress{\n\t\t\t\t\tLastState: schemas.SystemHardwareInitializationCompleteBootProgressTypes,\n\t\t\t\t},\n\t\t\t},\n\t\t\terr: nil,\n\t\t},\n\t\t\"insufficient redfish version\": {\n\t\t\thfunc: map[string]func(http.ResponseWriter, *http.Request){\n\t\t\t\t\"/redfish/v1/\": endpointFunc(t, \"smc_1.9.0_serviceroot.json\"),\n\t\t\t},\n\t\t\texpect: nil,\n\t\t\terr:    bmclibErrs.ErrRedfishVersionIncompatible,\n\t\t},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tmux := http.NewServeMux()\n\t\t\thandleFunc := tc.hfunc\n\t\t\tfor endpoint, handler := range handleFunc {\n\t\t\t\tmux.HandleFunc(endpoint, handler)\n\t\t\t}\n\n\t\t\tserver := httptest.NewTLSServer(mux)\n\t\t\tdefer server.Close()\n\n\t\t\tparsedURL, err := url.Parse(server.URL)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tclient := NewClient(parsedURL.Hostname(), parsedURL.Port(), \"\", \"\")\n\n\t\t\terr = client.Open(context.TODO())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer client.Close(context.TODO())\n\n\t\t\tgot, err := client.GetBootProgress()\n\t\t\tif err != nil {\n\t\t\t\tassert.ErrorIs(t, err, tc.err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tassert.ElementsMatch(t, tc.expect, got)\n\t\t})\n\t}\n\n}\n"
  },
  {
    "path": "internal/redfishwrapper/firmware.go",
    "content": "package redfishwrapper\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"net/textproto\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/stmcginnis/gofish/schemas\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/constants\"\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n)\n\ntype installMethod string\n\nconst (\n\tunstructuredHttpPush installMethod = \"unstructuredHttpPush\"\n\tmultipartHttpUpload  installMethod = \"multipartUpload\"\n)\n\nvar (\n\t// the URI for starting a firmware update via StartUpdate is defined in the Redfish Resource and\n\t// Schema Guide (2024.1)\n\tstartUpdateURI = \"/redfish/v1/UpdateService/Actions/UpdateService.StartUpdate\"\n)\n\nvar (\n\terrMultiPartPayload   = errors.New(\"error preparing multipart payload\")\n\terrUpdateParams       = errors.New(\"error in redfish UpdateParameters payload\")\n\terrTaskIdFromRespBody = errors.New(\"failed to identify firmware install taskID from response body\")\n)\n\ntype RedfishUpdateServiceParameters struct {\n\tTargets            []string                     `json:\"Targets\"`\n\tOperationApplyTime constants.OperationApplyTime `json:\"@Redfish.OperationApplyTime\"`\n\tOem                json.RawMessage              `json:\"Oem\"`\n}\n\n// FirmwareUpload uploads and initiates the firmware install process\nfunc (c *Client) FirmwareUpload(ctx context.Context, updateFile *os.File, params *RedfishUpdateServiceParameters) (taskID string, err error) {\n\tparameters, err := json.Marshal(params)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(errUpdateParams, err.Error())\n\t}\n\n\tinstallMethod, installURI, err := c.firmwareInstallMethodURI()\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(bmclibErrs.ErrFirmwareUpload, err.Error())\n\t}\n\n\t// override the gofish HTTP client timeout,\n\t// since the context timeout is set at Open() and is at a lower value than required for this operation.\n\t//\n\t// record the http client timeout to be restored when this method returns\n\thttpClientTimeout := c.HttpClientTimeout()\n\tdefer func() {\n\t\tc.SetHttpClientTimeout(httpClientTimeout)\n\t}()\n\n\tctxDeadline, _ := ctx.Deadline()\n\tc.SetHttpClientTimeout(time.Until(ctxDeadline))\n\n\tvar resp *http.Response\n\n\tswitch installMethod {\n\tcase multipartHttpUpload:\n\t\tvar uploadErr error\n\t\tresp, uploadErr = c.multipartHTTPUpload(installURI, updateFile, parameters)\n\t\tif uploadErr != nil {\n\t\t\treturn \"\", errors.Wrap(bmclibErrs.ErrFirmwareUpload, uploadErr.Error())\n\t\t}\n\n\tcase unstructuredHttpPush:\n\t\tvar uploadErr error\n\t\tresp, uploadErr = c.unstructuredHttpUpload(installURI, updateFile)\n\t\tif uploadErr != nil {\n\t\t\treturn \"\", errors.Wrap(bmclibErrs.ErrFirmwareUpload, uploadErr.Error())\n\t\t}\n\n\tdefault:\n\t\treturn \"\", errors.Wrap(bmclibErrs.ErrFirmwareUpload, \"unsupported install method: \"+string(installMethod))\n\t}\n\n\tresponse, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(bmclibErrs.ErrFirmwareUpload, err.Error())\n\t}\n\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusAccepted {\n\t\treturn \"\", errors.Wrap(\n\t\t\tbmclibErrs.ErrFirmwareUpload,\n\t\t\t\"unexpected status code returned: \"+resp.Status,\n\t\t)\n\t}\n\n\t// The response contains a location header pointing to the task URI\n\t// Location: /redfish/v1/TaskService/Tasks/JID_467696020275\n\tvar location = resp.Header.Get(\"Location\")\n\tif strings.Contains(location, \"/TaskService/Tasks/\") {\n\t\treturn taskIDFromLocationHeader(location)\n\t}\n\n\trfTask := &schemas.Task{}\n\tif err := rfTask.UnmarshalJSON(response); err != nil {\n\t\t// we got invalid JSON\n\t\treturn \"\", fmt.Errorf(\"unmarshaling redfish response: %w\", err)\n\t}\n\t// it's possible to get well-formed JSON that isn't a Task (thanks SMC). Test that we have something sensible.\n\tif strings.Contains(rfTask.ODataType, \"Task\") {\n\t\treturn rfTask.ID, nil\n\t}\n\n\treturn taskIDFromResponseBody(response)\n}\n\n// StartUpdateForUploadedFirmware starts an update for a firmware file previously uploaded and returns the taskID\nfunc (c *Client) StartUpdateForUploadedFirmware(ctx context.Context) (taskID string, err error) {\n\terrStartUpdate := errors.New(\"error in starting update for uploaded firmware\")\n\tupdateService, err := c.client.Service.UpdateService()\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"error querying redfish update service\")\n\t}\n\n\t// Start update the hard way. We do this to get back the task object from the response body so that\n\t// we can parse the task id out of it.\n\tresp, err := updateService.GetClient().PostWithHeaders(startUpdateURI, nil, nil)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"error querying redfish start update endpoint\")\n\t}\n\n\tresponse, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"error reading redfish start update response body\")\n\t}\n\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusAccepted {\n\t\treturn \"\", errors.Wrap(errStartUpdate, \"unexpected status code returned: \"+resp.Status)\n\t}\n\n\tvar location = resp.Header.Get(\"Location\")\n\tif strings.Contains(location, \"/TaskService/Tasks/\") {\n\t\treturn taskIDFromLocationHeader(location)\n\t}\n\n\trfTask := &schemas.Task{}\n\tif err := rfTask.UnmarshalJSON(response); err != nil {\n\t\t// we got invalid JSON\n\t\treturn \"\", fmt.Errorf(\"unmarshaling redfish response: %w\", err)\n\t}\n\tif strings.Contains(rfTask.ODataType, \"Task\") {\n\t\treturn rfTask.ID, nil\n\t}\n\n\treturn taskIDFromResponseBody(response)\n}\n\ntype TaskAccepted struct {\n\tAccepted struct {\n\t\tCode                string `json:\"code\"`\n\t\tMessage             string `json:\"Message\"`\n\t\tMessageExtendedInfo []struct {\n\t\t\tMessageID         string   `json:\"MessageId\"`\n\t\t\tSeverity          string   `json:\"Severity\"`\n\t\t\tResolution        string   `json:\"Resolution\"`\n\t\t\tMessage           string   `json:\"Message\"`\n\t\t\tMessageArgs       []string `json:\"MessageArgs\"`\n\t\t\tRelatedProperties []string `json:\"RelatedProperties\"`\n\t\t} `json:\"@Message.ExtendedInfo\"`\n\t} `json:\"Accepted\"`\n}\n\nfunc taskIDFromResponseBody(resp []byte) (taskID string, err error) {\n\ta := &TaskAccepted{}\n\tif err = json.Unmarshal(resp, a); err != nil {\n\t\treturn \"\", errors.Wrap(errTaskIdFromRespBody, err.Error())\n\t}\n\n\tvar taskURI string\n\n\tfor _, info := range a.Accepted.MessageExtendedInfo {\n\t\tfor _, msg := range info.MessageArgs {\n\t\t\tif !strings.Contains(msg, \"/TaskService/Tasks/\") {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttaskURI = msg\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif taskURI == \"\" {\n\t\treturn \"\", errors.Wrap(errTaskIdFromRespBody, \"TaskService/Tasks/<id> URI not identified\")\n\t}\n\n\ttokens := strings.Split(taskURI, \"/\")\n\tif len(tokens) == 0 {\n\t\treturn \"\", errors.Wrap(errTaskIdFromRespBody, \"invalid/unsupported task URI: \"+taskURI)\n\t}\n\n\treturn tokens[len(tokens)-1], nil\n}\n\nfunc taskIDFromLocationHeader(uri string) (taskID string, err error) {\n\turi = strings.TrimSuffix(uri, \"/\")\n\n\tswitch {\n\t// OpenBMC returns /redfish/v1/TaskService/Tasks/12/Monitor\n\tcase strings.Contains(uri, \"/Tasks/\") && strings.HasSuffix(uri, \"/Monitor\"):\n\t\ttaskIDPart := strings.Split(uri, \"/Tasks/\")[1]\n\t\ttaskID := strings.TrimSuffix(taskIDPart, \"/Monitor\")\n\t\treturn taskID, nil\n\n\tcase strings.Contains(uri, \"Tasks/\"):\n\t\ttaskIDPart := strings.Split(uri, \"/Tasks/\")[1]\n\t\treturn taskIDPart, nil\n\n\tdefault:\n\t\treturn \"\", errors.Wrap(bmclibErrs.ErrTaskNotFound, \"failed to parse taskID from uri: \"+uri)\n\t}\n}\n\ntype multipartPayload struct {\n\tupdateParameters []byte\n\tupdateFile       *os.File\n}\n\nfunc (c *Client) multipartHTTPUpload(url string, update *os.File, params []byte) (*http.Response, error) {\n\tif url == \"\" {\n\t\treturn nil, fmt.Errorf(\"unable to execute request, no target provided\")\n\t}\n\n\t// payload ordered in the format it ends up in the multipart form\n\tpayload := &multipartPayload{\n\t\tupdateParameters: params,\n\t\tupdateFile:       update,\n\t}\n\n\treturn c.runRequestWithMultipartPayload(url, payload)\n}\n\nfunc (c *Client) unstructuredHttpUpload(url string, update io.Reader) (*http.Response, error) {\n\tif url == \"\" {\n\t\treturn nil, fmt.Errorf(\"unable to execute request, no target provided\")\n\t}\n\n\t// TODO: transform this to read the update so that we don't hold the data in memory\n\tb, _ := io.ReadAll(update)\n\tpayloadReadSeeker := bytes.NewReader(b)\n\n\treturn c.RunRawRequestWithHeaders(http.MethodPost, url, payloadReadSeeker, \"application/octet-stream\", nil)\n\n}\n\n// firmwareUpdateMethodURI returns the updateMethod and URI\nfunc (c *Client) firmwareInstallMethodURI() (method installMethod, updateURI string, err error) {\n\tupdateService, err := c.UpdateService()\n\tif err != nil {\n\t\treturn \"\", \"\", errors.Wrap(bmclibErrs.ErrRedfishUpdateService, err.Error())\n\t}\n\n\t// update service disabled\n\tif !updateService.ServiceEnabled {\n\t\treturn \"\", \"\", errors.Wrap(bmclibErrs.ErrRedfishUpdateService, \"service disabled\")\n\t}\n\n\tswitch {\n\tcase updateService.MultipartHTTPPushURI != \"\":\n\t\treturn multipartHttpUpload, updateService.MultipartHTTPPushURI, nil\n\tcase updateService.HTTPPushURI != \"\": //nolint:staticcheck\n\t\treturn unstructuredHttpPush, updateService.HTTPPushURI, nil //nolint:staticcheck\n\t}\n\n\treturn \"\", \"\", errors.Wrap(bmclibErrs.ErrRedfishUpdateService, \"unsupported update method\")\n}\n\n// sets up the UpdateParameters MIMEHeader for the multipart form\n// the Go multipart writer CreateFormField does not currently let us set Content-Type on a MIME Header\n// https://cs.opensource.google/go/go/+/refs/tags/go1.17.8:src/mime/multipart/writer.go;l=151\nfunc updateParametersFormField(fieldName string, writer *multipart.Writer) (io.Writer, error) {\n\tif fieldName != \"UpdateParameters\" {\n\t\treturn nil, errors.Wrap(errUpdateParams, \"expected field not found to create multipart form\")\n\t}\n\n\th := make(textproto.MIMEHeader)\n\th.Set(\"Content-Disposition\", `form-data; name=\"UpdateParameters\"`)\n\th.Set(\"Content-Type\", \"application/json\")\n\n\treturn writer.CreatePart(h)\n}\n\n// pipeReaderFakeSeeker wraps the io.PipeReader and implements the io.Seeker interface\n// to meet the API requirements for the Gofish client https://github.com/stmcginnis/gofish/blob/46b1b33645ed1802727dc4df28f5d3c3da722b15/client.go#L434\n//\n// The Gofish method linked does not currently perform seeks and so a PR will be suggested\n// to change the method signature to accept an io.Reader instead.\ntype pipeReaderFakeSeeker struct {\n\t*io.PipeReader\n}\n\n// Seek impelements the io.Seeker interface only to panic if called\nfunc (p pipeReaderFakeSeeker) Seek(offset int64, whence int) (int64, error) {\n\treturn 0, errors.New(\"Seek() not implemented for fake pipe reader seeker.\")\n}\n\n// multipartPayloadSize prepares a temporary multipart form to determine the form size\n//\n// It creates a temporary form without reading in the update file payload and returns\n// sizeOf(form) + sizeOf(update file)\nfunc multipartPayloadSize(payload *multipartPayload) (int64, *bytes.Buffer, error) {\n\tbody := &bytes.Buffer{}\n\tform := multipart.NewWriter(body)\n\n\t// Add UpdateParameters field part\n\tpart, err := updateParametersFormField(\"UpdateParameters\", form)\n\tif err != nil {\n\t\treturn 0, body, err\n\t}\n\n\tif _, err = io.Copy(part, bytes.NewReader(payload.updateParameters)); err != nil {\n\t\treturn 0, body, err\n\t}\n\n\t// Add updateFile form\n\t_, err = form.CreateFormFile(\"UpdateFile\", filepath.Base(payload.updateFile.Name()))\n\tif err != nil {\n\t\treturn 0, body, err\n\t}\n\n\t// determine update file size\n\tfinfo, err := payload.updateFile.Stat()\n\tif err != nil {\n\t\treturn 0, body, err\n\t}\n\n\t// add terminating boundary to multipart form\n\terr = form.Close()\n\tif err != nil {\n\t\treturn 0, body, err\n\t}\n\n\treturn int64(body.Len()) + finfo.Size(), body, nil\n}\n\n// runRequestWithMultipartPayload is a copy of https://github.com/stmcginnis/gofish/blob/main/client.go#L349\n// with a change to add the UpdateParameters multipart form field with a json content type header\n// the resulting form ends up in this format\n//\n// Content-Length: 416\n// Content-Type: multipart/form-data; boundary=--------------------\n// ----1771f60800cb2801\n\n// --------------------------1771f60800cb2801\n// Content-Disposition: form-data; name=\"UpdateParameters\"\n// Content-Type: application/json\n\n// {\"Targets\": [], \"@Redfish.OperationApplyTime\": \"OnReset\", \"Oem\":\n//  {}}\n// --------------------------1771f60800cb2801\n// Content-Disposition: form-data; name=\"UpdateFile\"; filename=\"dum\n// myfile\"\n// Content-Type: application/octet-stream\n\n// hey.\n// --------------------------1771f60800cb2801--\nfunc (c *Client) runRequestWithMultipartPayload(url string, payload *multipartPayload) (*http.Response, error) {\n\tif url == \"\" {\n\t\treturn nil, fmt.Errorf(\"unable to execute request, no target provided\")\n\t}\n\n\t// A content-length header is passed in to indicate the payload size\n\t//\n\t// The Content-length is set explicitly since the payload is an io.Reader,\n\t// https://github.com/golang/go/blob/ddad9b618cce0ed91d66f0470ddb3e12cfd7eeac/src/net/http/request.go#L861\n\t//\n\t// Without the content-length header the http client will set the Transfer-Encoding to 'chunked'\n\t// and that does not work for some BMCs (iDracs).\n\tcontentLength, _, err := multipartPayloadSize(payload)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"error determining multipart payload size\")\n\t}\n\n\theaders := map[string]string{\n\t\t\"Content-Length\": strconv.FormatInt(contentLength, 10),\n\t}\n\n\t// setup pipe\n\tpipeReader, pipeWriter := io.Pipe()\n\tdefer pipeReader.Close()\n\n\t// initiate a mulitpart writer\n\tform := multipart.NewWriter(pipeWriter)\n\n\t// go routine blocks on the io.Copy until the http request is made\n\tgo func() {\n\t\tvar err error\n\t\tdefer func() {\n\t\t\tif err != nil {\n\t\t\t\tc.logger.Error(err, \"multipart upload error occurred\")\n\t\t\t}\n\t\t}()\n\n\t\tdefer pipeWriter.Close()\n\n\t\t// Add UpdateParameters part\n\t\tparametersPart, err := updateParametersFormField(\"UpdateParameters\", form)\n\t\tif err != nil {\n\t\t\tc.logger.Error(errMultiPartPayload, err.Error()+\": UpdateParameters part copy error\")\n\n\t\t\treturn\n\t\t}\n\n\t\tif _, err = io.Copy(parametersPart, bytes.NewReader(payload.updateParameters)); err != nil {\n\t\t\tc.logger.Error(errMultiPartPayload, err.Error()+\": UpdateParameters part copy error\")\n\n\t\t\treturn\n\t\t}\n\n\t\t// Add UpdateFile part\n\t\tupdateFilePart, err := form.CreateFormFile(\"UpdateFile\", filepath.Base(payload.updateFile.Name()))\n\t\tif err != nil {\n\t\t\tc.logger.Error(errMultiPartPayload, err.Error()+\": UpdateFile part create error\")\n\n\t\t\treturn\n\t\t}\n\n\t\tif _, err = io.Copy(updateFilePart, payload.updateFile); err != nil {\n\t\t\tc.logger.Error(errMultiPartPayload, err.Error()+\": UpdateFile part copy error\")\n\n\t\t\treturn\n\t\t}\n\n\t\t// add terminating boundary to multipart form\n\t\tform.Close()\n\t}()\n\n\t// pipeReader wrapped as a io.ReadSeeker to satisfy the gofish method signature\n\treader := pipeReaderFakeSeeker{pipeReader}\n\n\treturn c.RunRawRequestWithHeaders(http.MethodPost, url, reader, form.FormDataContentType(), headers)\n}\n"
  },
  {
    "path": "internal/redfishwrapper/firmware_test.go",
    "content": "package redfishwrapper\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"log\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"go.uber.org/goleak\"\n)\n\nfunc TestRunRequestWithMultipartPayload(t *testing.T) {\n\tdefer goleak.VerifyNone(t)\n\n\t// init things\n\ttmpdir := t.TempDir()\n\tbinPath := filepath.Join(tmpdir, \"test.bin\")\n\terr := os.WriteFile(binPath, []byte(`HELLOWORLD`), 0600)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tupdateFile, err := os.Open(binPath)\n\tif err != nil {\n\t\tt.Fatalf(\"%s -> %s\", err.Error(), binPath)\n\t}\n\n\tdefer updateFile.Close()\n\tdefer os.Remove(binPath)\n\n\tmultipartEndpoint := func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.Method != \"POST\" {\n\t\t\tw.WriteHeader(http.StatusNotFound)\n\t\t}\n\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\n\t\t// payload size\n\t\texpectedContentLength := \"476\"\n\n\t\texpected := []string{\n\t\t\t`Content-Disposition: form-data; name=\"UpdateParameters\"`,\n\t\t\t`Content-Type: application/json`,\n\t\t\t`{\"Targets\":[],\"@Redfish.OperationApplyTime\":\"OnReset\",\"Oem\":{}}`,\n\t\t\t`Content-Disposition: form-data; name=\"UpdateFile\"; filename=\"test.bin\"`,\n\t\t\t`Content-Type: application/octet-stream`,\n\t\t\t`HELLOWORLD`,\n\t\t}\n\n\t\tfor _, want := range expected {\n\t\t\tassert.Contains(t, string(body), want, \"expected value in payload\")\n\t\t}\n\n\t\tassert.Equal(t, expectedContentLength, r.Header.Get(\"Content-Length\"))\n\n\t\tw.Header().Add(\"Location\", \"/redfish/v1/TaskService/Tasks/JID_467696020275\")\n\t\tw.WriteHeader(http.StatusAccepted)\n\t}\n\n\ttests := map[string]struct {\n\t\thfunc     map[string]func(http.ResponseWriter, *http.Request)\n\t\tupdateURI string\n\t\tpayload   *multipartPayload\n\t\terr       error\n\t}{\n\t\t\"happy case - multipart push\": {\n\t\t\thfunc: map[string]func(http.ResponseWriter, *http.Request){\n\t\t\t\t\"/redfish/v1/\": endpointFunc(t, \"serviceroot.json\"),\n\t\t\t\t\"/redfish/v1/UpdateService/MultipartUpload\": multipartEndpoint,\n\t\t\t},\n\t\t\tupdateURI: \"/redfish/v1/UpdateService/MultipartUpload\",\n\t\t\tpayload: &multipartPayload{\n\t\t\t\tupdateParameters: []byte(`{\"Targets\":[],\"@Redfish.OperationApplyTime\":\"OnReset\",\"Oem\":{}}`),\n\t\t\t\tupdateFile:       updateFile,\n\t\t\t},\n\t\t\terr: nil,\n\t\t},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tmux := http.NewServeMux()\n\t\t\thandleFunc := tc.hfunc\n\t\t\tfor endpoint, handler := range handleFunc {\n\t\t\t\tmux.HandleFunc(endpoint, handler)\n\t\t\t}\n\n\t\t\tserver := httptest.NewTLSServer(mux)\n\t\t\tdefer server.Close()\n\n\t\t\tparsedURL, err := url.Parse(server.URL)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tctx := context.Background()\n\n\t\t\tclient := NewClient(parsedURL.Hostname(), parsedURL.Port(), \"\", \"\", WithBasicAuthEnabled(true))\n\n\t\t\terr = client.Open(ctx)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\t_, err = client.runRequestWithMultipartPayload(tc.updateURI, tc.payload)\n\t\t\tif tc.err != nil {\n\t\t\t\tassert.ErrorContains(t, err, tc.err.Error())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tassert.Nil(t, err)\n\t\t\tclient.Close(context.Background())\n\t\t})\n\t}\n}\n\nfunc TestFirmwareInstallMethodURI(t *testing.T) {\n\ttests := map[string]struct {\n\t\thfunc               map[string]func(http.ResponseWriter, *http.Request)\n\t\texpectInstallMethod installMethod\n\t\texpectUpdateURI     string\n\t\terr                 error\n\t}{\n\t\t\"happy case - multipart push\": {\n\t\t\thfunc: map[string]func(http.ResponseWriter, *http.Request){\n\t\t\t\t\"/redfish/v1/\":              endpointFunc(t, \"serviceroot.json\"),\n\t\t\t\t\"/redfish/v1/Systems\":       endpointFunc(t, \"systems.json\"),\n\t\t\t\t\"/redfish/v1/Managers\":      endpointFunc(t, \"managers.json\"),\n\t\t\t\t\"/redfish/v1/Managers/1\":    endpointFunc(t, \"managers_1.json\"),\n\t\t\t\t\"/redfish/v1/UpdateService\": endpointFunc(t, \"updateservice_with_multipart.json\"),\n\t\t\t},\n\t\t\texpectInstallMethod: multipartHttpUpload,\n\t\t\texpectUpdateURI:     \"/redfish/v1/UpdateService/MultipartUpload\",\n\t\t\terr:                 nil,\n\t\t},\n\t\t\"happy case - unstructured http push\": {\n\t\t\thfunc: map[string]func(http.ResponseWriter, *http.Request){\n\t\t\t\t\"/redfish/v1/\":              endpointFunc(t, \"serviceroot.json\"),\n\t\t\t\t\"/redfish/v1/Systems\":       endpointFunc(t, \"systems.json\"),\n\t\t\t\t\"/redfish/v1/Managers\":      endpointFunc(t, \"managers.json\"),\n\t\t\t\t\"/redfish/v1/Managers/1\":    endpointFunc(t, \"managers_1.json\"),\n\t\t\t\t\"/redfish/v1/UpdateService\": endpointFunc(t, \"updateservice_with_httppushuri.json\"),\n\t\t\t},\n\t\t\texpectInstallMethod: unstructuredHttpPush,\n\t\t\texpectUpdateURI:     \"/redfish/v1/UpdateService/update\",\n\t\t\terr:                 nil,\n\t\t},\n\t\t\"failure case - service disabled\": {\n\t\t\thfunc: map[string]func(http.ResponseWriter, *http.Request){\n\t\t\t\t\"/redfish/v1/\":              endpointFunc(t, \"serviceroot.json\"),\n\t\t\t\t\"/redfish/v1/Systems\":       endpointFunc(t, \"systems.json\"),\n\t\t\t\t\"/redfish/v1/Managers\":      endpointFunc(t, \"managers.json\"),\n\t\t\t\t\"/redfish/v1/Managers/1\":    endpointFunc(t, \"managers_1.json\"),\n\t\t\t\t\"/redfish/v1/UpdateService\": endpointFunc(t, \"updateservice_disabled.json\"),\n\t\t\t},\n\t\t\texpectInstallMethod: \"\",\n\t\t\texpectUpdateURI:     \"\",\n\t\t\terr:                 bmclibErrs.ErrRedfishUpdateService,\n\t\t},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tmux := http.NewServeMux()\n\t\t\thandleFunc := tc.hfunc\n\t\t\tfor endpoint, handler := range handleFunc {\n\t\t\t\tmux.HandleFunc(endpoint, handler)\n\t\t\t}\n\n\t\t\tserver := httptest.NewTLSServer(mux)\n\t\t\tdefer server.Close()\n\n\t\t\tparsedURL, err := url.Parse(server.URL)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tctx := context.Background()\n\n\t\t\tclient := NewClient(parsedURL.Hostname(), parsedURL.Port(), \"\", \"\", WithBasicAuthEnabled(true))\n\n\t\t\terr = client.Open(ctx)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tgotMethod, gotURI, err := client.firmwareInstallMethodURI()\n\t\t\tif tc.err != nil {\n\t\t\t\tassert.ErrorContains(t, err, tc.err.Error())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tassert.Nil(t, err)\n\t\t\tassert.Equal(t, tc.expectInstallMethod, gotMethod)\n\t\t\tassert.Equal(t, tc.expectUpdateURI, gotURI)\n\n\t\t\tclient.Close(context.Background())\n\t\t})\n\t}\n}\n\nfunc TestTaskIDFromResponseBody(t *testing.T) {\n\ttestCases := []struct {\n\t\tname        string\n\t\tbody        []byte\n\t\texpectedID  string\n\t\texpectedErr error\n\t}{\n\t\t{\n\t\t\tname:        \"happy case\",\n\t\t\tbody:        mustReadFile(t, \"updateservice_ok_response.json\"),\n\t\t\texpectedID:  \"1234\",\n\t\t\texpectedErr: nil,\n\t\t},\n\t\t{\n\t\t\tname:        \"failure case\",\n\t\t\tbody:        mustReadFile(t, \"updateservice_unexpected_response.json\"),\n\t\t\texpectedID:  \"\",\n\t\t\texpectedErr: errTaskIdFromRespBody,\n\t\t},\n\t\t{\n\t\t\tname:        \"failure case - invalid json\",\n\t\t\tbody:        []byte(`<html><head>crappy bmc is crappy<head/></html>`),\n\t\t\texpectedID:  \"\",\n\t\t\texpectedErr: errTaskIdFromRespBody,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttaskID, err := taskIDFromResponseBody(tc.body)\n\t\t\tif tc.expectedErr != nil {\n\t\t\t\tassert.ErrorContains(t, err, tc.expectedErr.Error())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tassert.Nil(t, err)\n\t\t\tassert.Equal(t, tc.expectedID, taskID)\n\t\t})\n\t}\n}\n\nfunc TestTaskIDFromLocationHeader(t *testing.T) {\n\ttestCases := []struct {\n\t\tname        string\n\t\turi         string\n\t\texpectedID  string\n\t\texpectedErr error\n\t}{\n\t\t{\n\t\t\tname:        \"task URI with JID\",\n\t\t\turi:         \"http://foo/redfish/v1/TaskService/Tasks/JID_12345\",\n\t\t\texpectedID:  \"JID_12345\",\n\t\t\texpectedErr: nil,\n\t\t},\n\t\t{\n\t\t\tname:        \"task URI with ID\",\n\t\t\turi:         \"http://foo/redfish/v1/TaskService/Tasks/1234\",\n\t\t\texpectedID:  \"1234\",\n\t\t\texpectedErr: nil,\n\t\t},\n\t\t{\n\t\t\tname:        \"task URI with Monitor suffix\",\n\t\t\turi:         \"/redfish/v1/TaskService/Tasks/12/Monitor\",\n\t\t\texpectedID:  \"12\",\n\t\t\texpectedErr: nil,\n\t\t},\n\t\t{\n\t\t\tname:        \"trailing slash removed\",\n\t\t\turi:         \"http://foo/redfish/v1/TaskService/Tasks/1/\",\n\t\t\texpectedID:  \"1\",\n\t\t\texpectedErr: nil,\n\t\t},\n\t\t{\n\t\t\tname:        \"invalid task URI - no task ID\",\n\t\t\turi:         \"http://foo/redfish/v1/TaskService/Tasks/\",\n\t\t\texpectedID:  \"\",\n\t\t\texpectedErr: bmclibErrs.ErrTaskNotFound,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttaskID, err := taskIDFromLocationHeader(tc.uri)\n\t\t\tif tc.expectedErr != nil {\n\t\t\t\tassert.ErrorContains(t, err, tc.expectedErr.Error())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tassert.Nil(t, err)\n\t\t\tassert.Equal(t, tc.expectedID, taskID)\n\t\t})\n\t}\n}\n\nfunc TestUpdateParametersFormField(t *testing.T) {\n\ttestCases := []struct {\n\t\tname        string\n\t\tfieldName   string\n\t\texpectedErr error\n\t}{\n\t\t{\n\t\t\tname:        \"happy case\",\n\t\t\tfieldName:   \"UpdateParameters\",\n\t\t\texpectedErr: nil,\n\t\t},\n\t\t{\n\t\t\tname:        \"failure case\",\n\t\t\tfieldName:   \"InvalidField\",\n\t\t\texpectedErr: errUpdateParams,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tbuf := new(bytes.Buffer)\n\t\t\twriter := multipart.NewWriter(buf)\n\n\t\t\toutput, err := updateParametersFormField(tc.fieldName, writer)\n\t\t\tif tc.expectedErr != nil {\n\t\t\t\tassert.ErrorContains(t, err, tc.expectedErr.Error())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tassert.NoError(t, err)\n\t\t\tassert.Contains(t, buf.String(), `Content-Disposition: form-data; name=\"UpdateParameters`)\n\t\t\tassert.Contains(t, buf.String(), `Content-Type: application/json`)\n\t\t\tassert.NotNil(t, output)\n\n\t\t\t// Validate the created multipart form content\n\t\t\terr = writer.Close()\n\t\t\tassert.NoError(t, err)\n\n\t\t})\n\t}\n}\n\nfunc TestMultipartPayloadSize(t *testing.T) {\n\tupdateParameters, err := json.Marshal(struct {\n\t\tTargets            []string `json:\"Targets\"`\n\t\tRedfishOpApplyTime string   `json:\"@Redfish.OperationApplyTime\"`\n\t\tOem                struct{} `json:\"Oem\"`\n\t}{\n\t\t[]string{},\n\t\t\"foobar\",\n\t\tstruct{}{},\n\t})\n\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttmpdir := t.TempDir()\n\tbinPath := filepath.Join(tmpdir, \"test.bin\")\n\terr = os.WriteFile(binPath, []byte(`HELLOWORLD`), 0600)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttestfileFH, err := os.Open(binPath)\n\tif err != nil {\n\t\tt.Fatalf(\"%s -> %s\", err.Error(), binPath)\n\t}\n\n\ttestCases := []struct {\n\t\ttestName     string\n\t\tpayload      *multipartPayload\n\t\texpectedSize int64\n\t\terrorMsg     string\n\t}{\n\t\t{\n\t\t\t\"content length as expected\",\n\t\t\t&multipartPayload{\n\t\t\t\tupdateParameters: updateParameters,\n\t\t\t\tupdateFile:       testfileFH,\n\t\t\t},\n\t\t\t475,\n\t\t\t\"\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\tgotSize, _, err := multipartPayloadSize(tc.payload)\n\t\t\tif tc.errorMsg != \"\" {\n\t\t\t\tassert.Contains(t, err.Error(), tc.errorMsg)\n\t\t\t}\n\n\t\t\tassert.Nil(t, err)\n\t\t\tassert.Equal(t, tc.expectedSize, gotSize)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/redfishwrapper/fixtures/dell/bios.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#Bios.Bios\",\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Bios\",\n    \"@odata.type\": \"#Bios.v1_1_0.Bios\",\n    \"Id\": \"Bios\",\n    \"Name\": \"BIOS Configuration Current Settings\",\n    \"Description\": \"BIOS Configuration Current Settings\",\n    \"AttributeRegistry\": \"BiosAttributeRegistry.v1_0_3\",\n    \"Attributes\": {\n        \"SystemModelName\": \"PowerEdge R6515\",\n        \"SystemBiosVersion\": \"2.2.4\",\n        \"SystemServiceTag\": \"4PN08J3\",\n        \"SystemManufacturer\": \"Dell Inc.\",\n        \"SysMfrContactInfo\": \"www.dell.com\",\n        \"SystemCpldVersion\": \"1.0.7\",\n        \"UefiComplianceVersion\": \"2.7\",\n        \"AgesaVersion\": \"RomePI-SP3 1.0.0.A\",\n        \"SmuVersion\": \"0.36.109.0\",\n        \"DxioVersion\": \"36.637\",\n        \"ProcCoreSpeed\": \"2.80 GHz\",\n        \"Proc1Id\": \"17-31-0\",\n        \"Proc1Brand\": \"AMD EPYC 7402P 24-Core Processor               \",\n        \"Proc1L2Cache\": \"24x512 KB\",\n        \"Proc1L3Cache\": \"128 MB\",\n        \"Proc1Microcode\": \"0x830104D\",\n        \"SataPortAModel\": \"Not Enumerated\",\n        \"SataPortADriveType\": \"Not Enumerated\",\n        \"SataPortACapacity\": \"N/A\",\n        \"SataPortBModel\": \"Not Enumerated\",\n        \"SataPortBDriveType\": \"Not Enumerated\",\n        \"SataPortBCapacity\": \"N/A\",\n        \"SataPortCModel\": \"Not Enumerated\",\n        \"SataPortCDriveType\": \"Not Enumerated\",\n        \"SataPortCCapacity\": \"N/A\",\n        \"SataPortDModel\": \"Not Enumerated\",\n        \"SataPortDDriveType\": \"Not Enumerated\",\n        \"SataPortDCapacity\": \"N/A\",\n        \"SetBootOrderEn\": \"NIC.Slot.3-1-1,HardDisk.List.1-1\",\n        \"SetBootOrderDis\": \"\",\n        \"SetBootOrderFqdd1\": \"\",\n        \"SetBootOrderFqdd2\": \"\",\n        \"SetBootOrderFqdd3\": \"\",\n        \"SetBootOrderFqdd4\": \"\",\n        \"SetBootOrderFqdd5\": \"\",\n        \"SetBootOrderFqdd6\": \"\",\n        \"SetBootOrderFqdd7\": \"\",\n        \"SetBootOrderFqdd8\": \"\",\n        \"SetBootOrderFqdd9\": \"\",\n        \"SetBootOrderFqdd10\": \"\",\n        \"SetBootOrderFqdd11\": \"\",\n        \"SetBootOrderFqdd12\": \"\",\n        \"SetBootOrderFqdd13\": \"\",\n        \"SetBootOrderFqdd14\": \"\",\n        \"SetBootOrderFqdd15\": \"\",\n        \"SetBootOrderFqdd16\": \"\",\n        \"SetLegacyHddOrderFqdd1\": \"\",\n        \"SetLegacyHddOrderFqdd2\": \"\",\n        \"SetLegacyHddOrderFqdd3\": \"\",\n        \"SetLegacyHddOrderFqdd4\": \"\",\n        \"SetLegacyHddOrderFqdd5\": \"\",\n        \"SetLegacyHddOrderFqdd6\": \"\",\n        \"SetLegacyHddOrderFqdd7\": \"\",\n        \"SetLegacyHddOrderFqdd8\": \"\",\n        \"SetLegacyHddOrderFqdd9\": \"\",\n        \"SetLegacyHddOrderFqdd10\": \"\",\n        \"SetLegacyHddOrderFqdd11\": \"\",\n        \"SetLegacyHddOrderFqdd12\": \"\",\n        \"SetLegacyHddOrderFqdd13\": \"\",\n        \"SetLegacyHddOrderFqdd14\": \"\",\n        \"SetLegacyHddOrderFqdd15\": \"\",\n        \"SetLegacyHddOrderFqdd16\": \"\",\n        \"CurrentEmbVideoState\": \"Enabled\",\n        \"AesNi\": \"Enabled\",\n        \"TpmInfo\": \"Type: 2.0  NTC\",\n        \"TpmFirmware\": \"1.3.2.8\",\n        \"SysMemSize\": \"64 GB\",\n        \"SysMemType\": \"ECC DDR4\",\n        \"SysMemSpeed\": \"3200 MT/s\",\n        \"SysMemVolt\": \"1.20 V\",\n        \"VideoMem\": \"16 MB\",\n        \"AssetTag\": \"\",\n        \"SHA256SystemPassword\": \"\",\n        \"SHA256SystemPasswordSalt\": \"\",\n        \"SHA256SetupPassword\": \"\",\n        \"SHA256SetupPasswordSalt\": \"\",\n        \"CpuMinSevAsid\": 1,\n        \"Proc1NumCores\": 24,\n        \"ControlledTurboMinusBin\": 0,\n        \"AcPwrRcvryUserDelay\": 60,\n        \"LogicalProc\": \"Enabled\",\n        \"ProcVirtualization\": \"Enabled\",\n        \"IommuSupport\": \"Enabled\",\n        \"L1StreamHwPrefetcher\": \"Enabled\",\n        \"L2StreamHwPrefetcher\": \"Enabled\",\n        \"MadtCoreEnumeration\": \"Linear\",\n        \"NumaNodesPerSocket\": \"1\",\n        \"CcxAsNumaDomain\": \"Disabled\",\n        \"TransparentSme\": \"Disabled\",\n        \"ProcX2Apic\": \"Enabled\",\n        \"ProcCcds\": \"All\",\n        \"CcdCores\": \"All\",\n        \"ControlledTurbo\": \"Disabled\",\n        \"OptimizerMode\": \"Auto\",\n        \"EmbSata\": \"Off\",\n        \"SecurityFreezeLock\": \"Disabled\",\n        \"WriteCache\": \"Disabled\",\n        \"SataPortA\": \"Auto\",\n        \"SataPortB\": \"Auto\",\n        \"SataPortC\": \"Auto\",\n        \"SataPortD\": \"Auto\",\n        \"NvmeMode\": \"NonRaid\",\n        \"BiosNvmeDriver\": \"DellQualifiedDrives\",\n        \"BootMode\": \"Bios\",\n        \"BootSeqRetry\": \"Enabled\",\n        \"HddFailover\": \"Enabled\",\n        \"GenericUsbBoot\": \"Disabled\",\n        \"HddPlaceholder\": \"Disabled\",\n        \"SysPrepClean\": \"None\",\n        \"OneTimeBootMode\": \"Disabled\",\n        \"OneTimeBootSeqDev\": \"NIC.Slot.3-1-1\",\n        \"OneTimeHddSeqDev\": \"AHCI.Slot.2-1\",\n        \"UsbPorts\": \"AllOn\",\n        \"InternalUsb\": \"On\",\n        \"UsbManagedPort\": \"On\",\n        \"IntegratedRaid\": \"Enabled\",\n        \"EmbNic1Nic2\": \"DisabledOs\",\n        \"EmbVideo\": \"Enabled\",\n        \"PciePreferredIoBus\": \"Disabled\",\n        \"PcieEnhancedPreferredIo\": \"Disabled\",\n        \"SriovGlobalEnable\": \"Disabled\",\n        \"OsWatchdogTimer\": \"Disabled\",\n        \"MmioLimit\": \"8TB\",\n        \"DellAutoDiscovery\": \"PlatformDefault\",\n        \"Slot2Bif\": \"x16\",\n        \"Slot3Bif\": \"x16\",\n        \"Slot1\": \"Enabled\",\n        \"Slot2\": \"Enabled\",\n        \"Slot3\": \"Enabled\",\n        \"SerialComm\": \"OnConRedirCom1\",\n        \"SerialPortAddress\": \"Serial1Com1Serial2Com2\",\n        \"ExtSerialConnector\": \"Serial1\",\n        \"FailSafeBaud\": \"115200\",\n        \"ConTermType\": \"Vt100Vt220\",\n        \"RedirAfterBoot\": \"Enabled\",\n        \"SysProfile\": \"PerfPerWattOptimizedOs\",\n        \"ProcPwrPerf\": \"OsDbpm\",\n        \"MemFrequency\": \"MaxPerf\",\n        \"ProcTurboMode\": \"Enabled\",\n        \"ProcCStates\": \"Enabled\",\n        \"WriteDataCrc\": \"Disabled\",\n        \"MemPatrolScrub\": \"Standard\",\n        \"MemRefreshRate\": \"1x\",\n        \"WorkloadProfile\": \"NotAvailable\",\n        \"PcieAspmL1\": \"Enabled\",\n        \"DeterminismSlider\": \"PowerDeterminism\",\n        \"EfficiencyOptimizedMode\": \"Disabled\",\n        \"ApbDis\": \"Disabled\",\n        \"PasswordStatus\": \"Unlocked\",\n        \"TpmSecurity\": \"On\",\n        \"Tpm2Hierarchy\": \"Enabled\",\n        \"PwrButton\": \"Enabled\",\n        \"AcPwrRcvry\": \"Last\",\n        \"AcPwrRcvryDelay\": \"Immediate\",\n        \"UefiVariableAccess\": \"Standard\",\n        \"SecureBoot\": \"Disabled\",\n        \"SecureBootPolicy\": \"Standard\",\n        \"SecureBootMode\": \"DeployedMode\",\n        \"AuthorizeDeviceFirmware\": \"Disabled\",\n        \"TpmPpiBypassProvision\": \"Disabled\",\n        \"TpmPpiBypassClear\": \"Disabled\",\n        \"Tpm2Algorithm\": \"SHA1\",\n        \"RedundantOsLocation\": \"None\",\n        \"RedundantOsState\": \"Visible\",\n        \"RedundantOsBoot\": \"Disabled\",\n        \"MemTest\": \"Disabled\",\n        \"DramRefreshDelay\": \"Minimum\",\n        \"MemOpMode\": \"OptimizerMode\",\n        \"MemoryInterleaving\": \"Auto\",\n        \"CorrEccSmi\": \"Enabled\",\n        \"OppSrefEn\": \"Disabled\",\n        \"CECriticalSEL\": \"Enabled\",\n        \"DimmSlot00\": \"Enabled\",\n        \"DimmSlot01\": \"Enabled\",\n        \"DimmSlot02\": \"Enabled\",\n        \"DimmSlot03\": \"Enabled\",\n        \"DimmSlot04\": \"Enabled\",\n        \"DimmSlot05\": \"Enabled\",\n        \"DimmSlot06\": \"Enabled\",\n        \"DimmSlot07\": \"Enabled\",\n        \"DimmSlot08\": \"Enabled\",\n        \"DimmSlot09\": \"Enabled\",\n        \"DimmSlot10\": \"Enabled\",\n        \"DimmSlot11\": \"Enabled\",\n        \"DimmSlot12\": \"Enabled\",\n        \"DimmSlot13\": \"Enabled\",\n        \"DimmSlot14\": \"Enabled\",\n        \"DimmSlot15\": \"Enabled\",\n        \"NumLock\": \"On\",\n        \"ErrPrompt\": \"Enabled\",\n        \"ForceInt10\": \"Disabled\",\n        \"DellWyseP25BIOSAccess\": \"Enabled\",\n        \"PowerCycleRequest\": \"None\",\n        \"SysPassword\": null,\n        \"SetupPassword\": null\n    },\n    \"Actions\": {\n        \"#Bios.ChangePassword\": {\n            \"target\": \"/redfish/v1/Systems/System.Embedded.1/Bios/Actions/Bios.ChangePassword\"\n        },\n        \"#Bios.ResetBios\": {\n            \"target\": \"/redfish/v1/Systems/System.Embedded.1/Bios/Actions/Bios.ResetBios\"\n        },\n        \"Oem\": {\n            \"#DellBios.RunBIOSLiveScanning\": {\n                \"target\": \"/redfish/v1/Systems/System.Embedded.1/Bios/Actions/Oem/DellBios.RunBIOSLiveScanning\"\n            }\n        }\n    },\n    \"@Redfish.Settings\": {\n        \"@odata.context\": \"/redfish/v1/$metadata#Settings.Settings\",\n        \"@odata.type\": \"#Settings.v1_3_0.Settings\",\n        \"SettingsObject\": {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Bios/Settings\"\n        },\n        \"SupportedApplyTimes\": [\n            \"OnReset\",\n            \"AtMaintenanceWindowStart\",\n            \"InMaintenanceWindowOnReset\"\n        ]\n    },\n    \"Links\": {\n        \"SoftwareImages\": [\n            {\n                \"@odata.id\": \"/redfish/v1/UpdateService/FirmwareInventory/Previous-159-2.3.6__BIOS.Setup.1-1\"\n            },\n            {\n                \"@odata.id\": \"/redfish/v1/UpdateService/FirmwareInventory/Installed-159-2.2.4__BIOS.Setup.1-1\"\n            },\n            {\n                \"@odata.id\": \"/redfish/v1/UpdateService/FirmwareInventory/Current-159-2.2.4__BIOS.Setup.1-1\"\n            }\n        ],\n        \"SoftwareImages@odata.count\": 3,\n        \"ActiveSoftwareImage\": {\n            \"@odata.id\": \"/redfish/v1/UpdateService/FirmwareInventory/Installed-159-2.2.4__BIOS.Setup.1-1\"\n        }\n    }\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/dell/manager.idrac.embedded.1.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#Manager.Manager\",\n    \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1\",\n    \"@odata.type\": \"#Manager.v1_20_0.Manager\",\n    \"Id\": \"iDRAC.Embedded.1\",\n    \"Name\": \"Manager\",\n    \"Description\": \"BMC\",\n    \"ManagerType\": \"BMC\",\n    \"FirmwareVersion\": \"1.20.50.52\",\n    \"Model\": \"17G Monolithic\",\n    \"PowerState\": \"On\",\n    \"Status\": {\n        \"State\": \"Enabled\",\n        \"Health\": \"OK\"\n    },\n    \"GraphicalConsole\": {\n        \"ServiceEnabled\": true,\n        \"MaxConcurrentSessions\": 6,\n        \"ConnectTypesSupported\": [\n            \"KVMIP\"\n        ]\n    },\n    \"CommandShell\": {\n        \"ServiceEnabled\": true,\n        \"MaxConcurrentSessions\": 5,\n        \"ConnectTypesSupported\": [\n            \"SSH\",\n            \"IPMI\"\n        ]\n    },\n    \"LogServices\": {\n        \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices\"\n    },\n    \"EthernetInterfaces\": {\n        \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/EthernetInterfaces\"\n    },\n    \"NetworkProtocol\": {\n        \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/NetworkProtocol\"\n    },\n    \"Links\": {\n        \"ManagerForServers@odata.count\": 1,\n        \"ManagerForServers\": [\n            {\n                \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1\"\n            }\n        ],\n        \"ManagerForChassis@odata.count\": 1,\n        \"ManagerForChassis\": [\n            {\n                \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1\"\n            }\n        ],\n        \"ManagerInChassis\": {\n            \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1\"\n        }\n    },\n    \"Actions\": {\n        \"#Manager.Reset\": {\n            \"target\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Actions/Manager.Reset\",\n            \"ResetType@Redfish.AllowableValues\": [\n                \"GracefulRestart\"\n            ]\n        }\n    }\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/dell/managers.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#ManagerCollection.ManagerCollection\",\n    \"@odata.id\": \"/redfish/v1/Managers\",\n    \"@odata.type\": \"#ManagerCollection.ManagerCollection\",\n    \"Description\": \"BMC Manager Collection\",\n    \"Members\": [\n        {\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1\"\n        }\n    ],\n    \"Members@odata.count\": 1,\n    \"Name\": \"Manager Collection\"\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/dell/serviceroot.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#ServiceRoot.ServiceRoot\",\n    \"@odata.id\": \"/redfish/v1\",\n    \"@odata.type\": \"#ServiceRoot.v1_6_0.ServiceRoot\",\n    \"AccountService\": {\n        \"@odata.id\": \"/redfish/v1/AccountService\"\n    },\n    \"CertificateService\": {\n        \"@odata.id\": \"/redfish/v1/CertificateService\"\n    },\n    \"Chassis\": {\n        \"@odata.id\": \"/redfish/v1/Chassis\"\n    },\n    \"Description\": \"Root Service\",\n    \"EventService\": {\n        \"@odata.id\": \"/redfish/v1/EventService\"\n    },\n    \"Fabrics\": {\n        \"@odata.id\": \"/redfish/v1/Fabrics\"\n    },\n    \"Id\": \"RootService\",\n    \"JobService\": {\n        \"@odata.id\": \"/redfish/v1/JobService\"\n    },\n    \"JsonSchemas\": {\n        \"@odata.id\": \"/redfish/v1/JsonSchemas\"\n    },\n    \"Links\": {\n        \"Sessions\": {\n            \"@odata.id\": \"/redfish/v1/SessionService/Sessions\"\n        }\n    },\n    \"Managers\": {\n        \"@odata.id\": \"/redfish/v1/Managers\"\n    },\n    \"Name\": \"Root Service\",\n    \"Oem\": {\n        \"Dell\": {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellServiceRoot.DellServiceRoot\",\n            \"@odata.type\": \"#DellServiceRoot.v1_0_0.DellServiceRoot\",\n            \"IsBranded\": 0,\n            \"ManagerMACAddress\": \"d0:8e:79:bb:3e:ea\",\n            \"ServiceTag\": \"FOOBAR\"\n        }\n    },\n    \"Product\": \"Integrated Dell Remote Access Controller\",\n    \"ProtocolFeaturesSupported\": {\n        \"ExcerptQuery\": false,\n        \"ExpandQuery\": {\n            \"ExpandAll\": true,\n            \"Levels\": true,\n            \"Links\": true,\n            \"MaxLevels\": 1,\n            \"NoLinks\": true\n        },\n        \"FilterQuery\": true,\n        \"OnlyMemberQuery\": true,\n        \"SelectQuery\": true\n    },\n    \"RedfishVersion\": \"1.9.0\",\n    \"Registries\": {\n        \"@odata.id\": \"/redfish/v1/Registries\"\n    },\n    \"SessionService\": {\n        \"@odata.id\": \"/redfish/v1/SessionService\"\n    },\n    \"Systems\": {\n        \"@odata.id\": \"/redfish/v1/Systems\"\n    },\n    \"Tasks\": {\n        \"@odata.id\": \"/redfish/v1/TaskService\"\n    },\n    \"TelemetryService\": {\n        \"@odata.id\": \"/redfish/v1/TelemetryService\"\n    },\n    \"UpdateService\": {\n        \"@odata.id\": \"/redfish/v1/UpdateService\"\n    },\n    \"Vendor\": \"Dell\"\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/dell/system.embedded.1.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#ComputerSystem.ComputerSystem\",\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1\",\n    \"@odata.type\": \"#ComputerSystem.v1_10_0.ComputerSystem\",\n    \"Actions\": {\n        \"#ComputerSystem.Reset\": {\n            \"target\": \"/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset\",\n            \"ResetType@Redfish.AllowableValues\": [\n                \"On\",\n                \"ForceOff\",\n                \"ForceRestart\",\n                \"GracefulRestart\",\n                \"GracefulShutdown\",\n                \"PushPowerButton\",\n                \"Nmi\",\n                \"PowerCycle\"\n            ]\n        }\n    },\n    \"AssetTag\": \"\",\n    \"Bios\": {\n        \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Bios\"\n    },\n    \"BiosVersion\": \"2.2.4\",\n    \"Boot\": {\n        \"BootOptions\": {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/BootOptions\"\n        },\n        \"Certificates\": {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Boot/Certificates\"\n        },\n        \"BootOrder\": [\n            \"NIC.Slot.3-1-1\",\n            \"HardDisk.List.1-1\"\n        ],\n        \"BootOrder@odata.count\": 2,\n        \"BootSourceOverrideEnabled\": \"Disabled\",\n        \"BootSourceOverrideMode\": \"Legacy\",\n        \"BootSourceOverrideTarget\": \"None\",\n        \"UefiTargetBootSourceOverride\": null,\n        \"BootSourceOverrideTarget@Redfish.AllowableValues\": [\n            \"None\",\n            \"Pxe\",\n            \"Floppy\",\n            \"Cd\",\n            \"Hdd\",\n            \"BiosSetup\",\n            \"Utilities\",\n            \"UefiTarget\",\n            \"SDCard\",\n            \"UefiHttp\"\n        ]\n    },\n    \"Description\": \"Computer System which represents a machine (physical or virtual) and the local resources such as memory, cpu and other devices that can be accessed from that machine.\",\n    \"EthernetInterfaces\": {\n        \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/EthernetInterfaces\"\n    },\n    \"HostName\": \"\",\n    \"HostWatchdogTimer\": {\n        \"FunctionEnabled\": false,\n        \"Status\": {\n            \"State\": \"Disabled\"\n        },\n        \"TimeoutAction\": \"None\"\n    },\n    \"HostingRoles\": [],\n    \"HostingRoles@odata.count\": 0,\n    \"Id\": \"System.Embedded.1\",\n    \"IndicatorLED\": \"Lit\",\n    \"Links\": {\n        \"Chassis\": [\n            {\n                \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1\"\n            }\n        ],\n        \"Chassis@odata.count\": 1,\n        \"CooledBy\": [\n            {\n                \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/0\"\n            },\n            {\n                \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/1\"\n            },\n            {\n                \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/2\"\n            },\n            {\n                \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/3\"\n            },\n            {\n                \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/4\"\n            },\n            {\n                \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/5\"\n            },\n            {\n                \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/6\"\n            },\n            {\n                \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/7\"\n            },\n            {\n                \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/8\"\n            },\n            {\n                \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/9\"\n            },\n            {\n                \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/10\"\n            },\n            {\n                \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/11\"\n            }\n        ],\n        \"CooledBy@odata.count\": 12,\n        \"ManagedBy\": [\n            {\n                \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1\"\n            }\n        ],\n        \"ManagedBy@odata.count\": 1,\n        \"Oem\": {\n            \"Dell\": {\n                \"@odata.type\": \"#DellOem.v1_1_0.DellOemLinks\",\n                \"BootOrder\": {\n                    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellBootSources\"\n                },\n                \"DellBootSources\": {\n                    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellBootSources\"\n                },\n                \"DellSoftwareInstallationService\": {\n                    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellSoftwareInstallationService\"\n                },\n                \"DellVideoCollection\": {\n                    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellVideo\"\n                },\n                \"DellChassisCollection\": {\n                    \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Oem/Dell/DellChassis\"\n                },\n                \"DellPresenceAndStatusSensorCollection\": {\n                    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellPresenceAndStatusSensors\"\n                },\n                \"DellSensorCollection\": {\n                    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellSensors\"\n                },\n                \"DellRollupStatusCollection\": {\n                    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRollupStatus\"\n                },\n                \"DellPSNumericSensorCollection\": {\n                    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellPSNumericSensors\"\n                },\n                \"DellVideoNetworkCollection\": {\n                    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellVideoNetwork\"\n                },\n                \"DellOSDeploymentService\": {\n                    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellOSDeploymentService\"\n                },\n                \"DellMetricService\": {\n                    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellMetricService\"\n                },\n                \"DellGPUSensorCollection\": {\n                    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellGPUSensors\"\n                },\n                \"DellRaidService\": {\n                    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService\"\n                },\n                \"DellNumericSensorCollection\": {\n                    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellNumericSensors\"\n                },\n                \"DellBIOSService\": {\n                    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellBIOSService\"\n                },\n                \"DellSlotCollection\": {\n                    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellSlots\"\n                }\n            }\n        },\n        \"PoweredBy\": [\n            {\n                \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Power#/PowerSupplies/0\"\n            },\n            {\n                \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Power#/PowerSupplies/1\"\n            }\n        ],\n        \"PoweredBy@odata.count\": 2\n    },\n    \"Manufacturer\": \"Dell Inc.\",\n    \"Memory\": {\n        \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Memory\"\n    },\n    \"MemorySummary\": {\n        \"MemoryMirroring\": \"System\",\n        \"Status\": {\n            \"Health\": \"OK\",\n            \"HealthRollup\": \"OK\",\n            \"State\": \"Enabled\"\n        },\n        \"TotalSystemMemoryGiB\": 64\n    },\n    \"Model\": \"PowerEdge R6515\",\n    \"Name\": \"System\",\n    \"NetworkInterfaces\": {\n        \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/NetworkInterfaces\"\n    },\n    \"Oem\": {\n        \"Dell\": {\n            \"@odata.type\": \"#DellOem.v1_1_0.DellOemResources\",\n            \"DellSystem\": {\n                \"BIOSReleaseDate\": \"04/12/2021\",\n                \"BaseBoardChassisSlot\": \"NA\",\n                \"BatteryRollupStatus\": \"OK\",\n                \"BladeGeometry\": \"NotApplicable\",\n                \"CMCIP\": null,\n                \"CPURollupStatus\": \"OK\",\n                \"ChassisModel\": \"\",\n                \"ChassisName\": \"Main System Chassis\",\n                \"ChassisServiceTag\": \"FOOXD53\",\n                \"ChassisSystemHeightUnit\": 1,\n                \"CurrentRollupStatus\": \"OK\",\n                \"EstimatedExhaustTemperatureCelsius\": 255,\n                \"EstimatedSystemAirflowCFM\": 255,\n                \"ExpressServiceCode\": \"33944916423\",\n                \"FanRollupStatus\": \"OK\",\n                \"Id\": \"System.Embedded.1\",\n                \"IDSDMRollupStatus\": null,\n                \"IntrusionRollupStatus\": \"OK\",\n                \"IsOEMBranded\": \"False\",\n                \"LastSystemInventoryTime\": \"2022-04-01T17:01:30+00:00\",\n                \"LastUpdateTime\": \"2022-01-14T15:14:30+00:00\",\n                \"LicensingRollupStatus\": \"OK\",\n                \"MaxCPUSockets\": 1,\n                \"MaxDIMMSlots\": 16,\n                \"MaxPCIeSlots\": 5,\n                \"MemoryOperationMode\": \"OptimizerMode\",\n                \"Name\": \"DellSystem\",\n                \"NodeID\": \"FOOXD53\",\n                \"PSRollupStatus\": \"OK\",\n                \"PlatformGUID\": \"3335444f-c0c6-5880-4410-004c4c4c4544\",\n                \"PopulatedDIMMSlots\": 8,\n                \"PopulatedPCIeSlots\": 2,\n                \"PowerCapEnabledState\": \"Disabled\",\n                \"SDCardRollupStatus\": null,\n                \"SELRollupStatus\": \"OK\",\n                \"ServerAllocationWatts\": null,\n                \"StorageRollupStatus\": \"OK\",\n                \"SysMemErrorMethodology\": \"Multi-bitECC\",\n                \"SysMemFailOverState\": \"NotInUse\",\n                \"SysMemLocation\": \"SystemBoardOrMotherboard\",\n                \"SysMemPrimaryStatus\": \"OK\",\n                \"SystemGeneration\": \"15G Monolithic\",\n                \"SystemID\": 2300,\n                \"SystemRevision\": \"I\",\n                \"TempRollupStatus\": \"OK\",\n                \"TempStatisticsRollupStatus\": \"OK\",\n                \"UUID\": \"4c4c4544-004c-4410-8058-c6c04f443533\",\n                \"VoltRollupStatus\": \"OK\",\n                \"smbiosGUID\": \"44454c4c-4c00-1044-8058-c6c04f443533\",\n                \"@odata.context\": \"/redfish/v1/$metadata#DellSystem.DellSystem\",\n                \"@odata.type\": \"#DellSystem.v1_2_0.DellSystem\",\n                \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellSystem/System.Embedded.1\"\n            }\n        }\n    },\n    \"PCIeDevices\": [\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/195-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/72-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-2\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-3\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-7\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-1\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-4\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-8\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/65-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/194-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/4-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-1\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-2\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-4\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-3\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-8\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-7\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-2\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-3\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-7\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-1\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-4\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-8\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/1-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-2\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-3\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-20\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-7\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-1\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-4\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-8\"\n        }\n    ],\n    \"PCIeDevices@odata.count\": 32,\n    \"PCIeFunctions\": [\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/195-0/PCIeFunctions/195-0-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/72-0/PCIeFunctions/72-0-3\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-2/PCIeFunctions/128-2-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-3/PCIeFunctions/128-3-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-7/PCIeFunctions/128-7-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-1/PCIeFunctions/128-1-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-4/PCIeFunctions/128-4-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-8/PCIeFunctions/128-8-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/65-0/PCIeFunctions/65-0-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/65-0/PCIeFunctions/65-0-1\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/194-0/PCIeFunctions/194-0-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/4-0/PCIeFunctions/4-0-3\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-1/PCIeFunctions/192-1-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-2/PCIeFunctions/192-2-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-4/PCIeFunctions/192-4-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-0/PCIeFunctions/192-0-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-3/PCIeFunctions/192-3-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-8/PCIeFunctions/192-8-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-7/PCIeFunctions/192-7-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-2/PCIeFunctions/64-2-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-3/PCIeFunctions/64-3-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-7/PCIeFunctions/64-7-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-1/PCIeFunctions/64-1-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-4/PCIeFunctions/64-4-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-8/PCIeFunctions/64-8-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/1-0/PCIeFunctions/1-0-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-2/PCIeFunctions/0-2-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-3/PCIeFunctions/0-3-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-20/PCIeFunctions/0-20-3\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-7/PCIeFunctions/0-7-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-20/PCIeFunctions/0-20-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-1/PCIeFunctions/0-1-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-4/PCIeFunctions/0-4-0\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-8/PCIeFunctions/0-8-0\"\n        }\n    ],\n    \"PCIeFunctions@odata.count\": 34,\n    \"PartNumber\": \"FOOCNNA00\",\n    \"PowerState\": \"On\",\n    \"ProcessorSummary\": {\n        \"Count\": 1,\n        \"LogicalProcessorCount\": 48,\n        \"Model\": \"AMD EPYC 7402P 24-Core Processor\",\n        \"Status\": {\n            \"Health\": \"OK\",\n            \"HealthRollup\": \"OK\",\n            \"State\": \"Enabled\"\n        }\n    },\n    \"Processors\": {\n        \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Processors\"\n    },\n    \"SKU\": \"FOOXD53\",\n    \"SecureBoot\": {\n        \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/SecureBoot\"\n    },\n    \"SerialNumber\": \"CNCMU0005400IJ\",\n    \"SimpleStorage\": {\n        \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/SimpleStorage\"\n    },\n    \"Status\": {\n        \"Health\": \"OK\",\n        \"HealthRollup\": \"OK\",\n        \"State\": \"Enabled\"\n    },\n    \"Storage\": {\n        \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Storage\"\n    },\n    \"SystemType\": \"Physical\",\n    \"TrustedModules\": [\n        {\n            \"FirmwareVersion\": \"1.3.1.0\",\n            \"InterfaceType\": \"TPM2_0\",\n            \"Status\": {\n                \"State\": \"Enabled\"\n            }\n        }\n    ],\n    \"TrustedModules@odata.count\": 1,\n    \"UUID\": \"4c4c4544-004c-4410-8058-c6c04f443533\"\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/dell/system.embedded.1.virtualmedia.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#ComputerSystem.ComputerSystem\",\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1\",\n    \"@odata.type\": \"#ComputerSystem.v1_22_0.ComputerSystem\",\n    \"Id\": \"System.Embedded.1\",\n    \"Name\": \"System\",\n    \"Description\": \"Computer System which represents a machine (physical or virtual) and the local resources such as memory, cpu and other devices that can be accessed from that machine.\",\n    \"Manufacturer\": \"Dell Inc.\",\n    \"Model\": \"PowerEdge R660\",\n    \"PowerState\": \"Off\",\n    \"Status\": {\n        \"Health\": \"OK\",\n        \"HealthRollup\": \"OK\",\n        \"State\": \"Enabled\"\n    },\n    \"Boot\": {\n        \"BootSourceOverrideEnabled\": \"Disabled\",\n        \"BootSourceOverrideMode\": \"Legacy\",\n        \"BootSourceOverrideTarget\": \"None\"\n    },\n    \"VirtualMedia\": {\n        \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/VirtualMedia\"\n    },\n    \"Links\": {\n        \"ManagedBy\": [\n            {\n                \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1\"\n            }\n        ],\n        \"ManagedBy@odata.count\": 1,\n        \"Chassis\": [\n            {\n                \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1\"\n            }\n        ],\n        \"Chassis@odata.count\": 1\n    }\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/dell/systems.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#ComputerSystemCollection.ComputerSystemCollection\",\n    \"@odata.id\": \"/redfish/v1/Systems\",\n    \"@odata.type\": \"#ComputerSystemCollection.ComputerSystemCollection\",\n    \"Description\": \"Collection of Computer Systems\",\n    \"Members\": [\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1\"\n        }\n    ],\n    \"Members@odata.count\": 1,\n    \"Name\": \"Computer System Collection\"\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/dell/virtualmedia_1.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#VirtualMedia.VirtualMedia\",\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/VirtualMedia/1\",\n    \"@odata.type\": \"#VirtualMedia.v1_6_4.VirtualMedia\",\n    \"@odata.etag\": \"W/\\\"gen-2\\\"\",\n    \"Id\": \"1\",\n    \"Name\": \"VirtualMedia Instance 1\",\n    \"Description\": \"iDRAC Virtual Media Instance\",\n    \"Image\": null,\n    \"ImageName\": null,\n    \"Inserted\": false,\n    \"ConnectedVia\": \"NotConnected\",\n    \"WriteProtected\": null,\n    \"TransferMethod\": null,\n    \"TransferProtocolType\": null,\n    \"VerifyCertificate\": false,\n    \"MediaTypes\": [\n        \"CD\",\n        \"DVD\",\n        \"USBStick\"\n    ],\n    \"MediaTypes@odata.count\": 3,\n    \"Actions\": {\n        \"#VirtualMedia.InsertMedia\": {\n            \"target\": \"/redfish/v1/Systems/System.Embedded.1/VirtualMedia/1/Actions/VirtualMedia.InsertMedia\",\n            \"TransferProtocolType@Redfish.AllowableValues\": [\n                \"CIFS\",\n                \"HTTP\",\n                \"HTTPS\",\n                \"NFS\"\n            ],\n            \"TransferMethod@Redfish.AllowableValues\": [\n                \"Stream\"\n            ]\n        },\n        \"#VirtualMedia.EjectMedia\": {\n            \"target\": \"/redfish/v1/Systems/System.Embedded.1/VirtualMedia/1/Actions/VirtualMedia.EjectMedia\"\n        }\n    },\n    \"Certificates\": {\n        \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/VirtualMedia/1/Certificates\"\n    }\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/dell/virtualmedia_2.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#VirtualMedia.VirtualMedia\",\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/VirtualMedia/2\",\n    \"@odata.type\": \"#VirtualMedia.v1_6_4.VirtualMedia\",\n    \"@odata.etag\": \"W/\\\"gen-2\\\"\",\n    \"Id\": \"2\",\n    \"Name\": \"VirtualMedia Instance 2\",\n    \"Description\": \"iDRAC Virtual Media Instance\",\n    \"Image\": null,\n    \"ImageName\": null,\n    \"Inserted\": false,\n    \"ConnectedVia\": \"NotConnected\",\n    \"WriteProtected\": null,\n    \"TransferMethod\": null,\n    \"TransferProtocolType\": null,\n    \"VerifyCertificate\": false,\n    \"MediaTypes\": [\n        \"CD\",\n        \"DVD\",\n        \"USBStick\"\n    ],\n    \"MediaTypes@odata.count\": 3,\n    \"Actions\": {\n        \"#VirtualMedia.InsertMedia\": {\n            \"target\": \"/redfish/v1/Systems/System.Embedded.1/VirtualMedia/2/Actions/VirtualMedia.InsertMedia\",\n            \"TransferProtocolType@Redfish.AllowableValues\": [\n                \"CIFS\",\n                \"HTTP\",\n                \"HTTPS\",\n                \"NFS\"\n            ],\n            \"TransferMethod@Redfish.AllowableValues\": [\n                \"Stream\"\n            ]\n        },\n        \"#VirtualMedia.EjectMedia\": {\n            \"target\": \"/redfish/v1/Systems/System.Embedded.1/VirtualMedia/2/Actions/VirtualMedia.EjectMedia\"\n        }\n    },\n    \"Certificates\": {\n        \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/VirtualMedia/2/Certificates\"\n    }\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/dell/virtualmedia_collection.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#VirtualMediaCollection.VirtualMediaCollection\",\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/VirtualMedia\",\n    \"@odata.type\": \"#VirtualMediaCollection.VirtualMediaCollection\",\n    \"@odata.etag\": \"W/\\\"gen-1\\\"\",\n    \"Description\": \"A collection of VirtualMediaCollection\",\n    \"Members\": [\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/VirtualMedia/2\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/VirtualMedia/1\"\n        }\n    ],\n    \"Members@odata.count\": 2,\n    \"Name\": \"Virtual Media Services\"\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/managers.json",
    "content": "{\n    \"@odata.type\": \"#ManagerCollection.ManagerCollection\",\n    \"@odata.id\": \"/redfish/v1/Managers\",\n    \"Name\": \"Manager Collection\",\n    \"Description\": \"Manager Collection\",\n    \"Members@odata.count\": 1,\n    \"Members\": [\n        {\n            \"@odata.id\": \"/redfish/v1/Managers/1\"\n        }\n    ]\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/managers_1.json",
    "content": "{\n    \"@odata.type\": \"#Manager.v1_7_0.Manager\",\n    \"@odata.id\": \"/redfish/v1/Managers/1\",\n    \"Id\": \"1\",\n    \"Name\": \"Manager\",\n    \"Description\": \"BMC\",\n    \"ManagerType\": \"BMC\",\n    \"UUID\": \"00000000-0000-0000-0000-3CECEFCEFEDA\",\n    \"Model\": \"ASPEED\",\n    \"FirmwareVersion\": \"01.13.04\",\n    \"DateTime\": \"2023-11-06T14:16:52Z\",\n    \"DateTimeLocalOffset\": \"+00:00\",\n    \"Status\": {\n        \"State\": \"Enabled\",\n        \"Health\": \"OK\"\n    },\n    \"GraphicalConsole\": {\n        \"ServiceEnabled\": true,\n        \"MaxConcurrentSessions\": 4,\n        \"ConnectTypesSupported\": [\n            \"KVMIP\"\n        ]\n    },\n    \"SerialConsole\": {\n        \"ServiceEnabled\": true,\n        \"MaxConcurrentSessions\": 1,\n        \"ConnectTypesSupported\": [\n            \"SSH\",\n            \"IPMI\"\n        ]\n    },\n    \"CommandShell\": {\n        \"ServiceEnabled\": true,\n        \"MaxConcurrentSessions\": 0,\n        \"ConnectTypesSupported\": [\n            \"SSH\"\n        ]\n    },\n    \"NetworkProtocol\": {\n        \"@odata.id\": \"/redfish/v1/Managers/1/NetworkProtocol\"\n    },\n    \"EthernetInterfaces\": {\n        \"@odata.id\": \"/redfish/v1/Managers/1/EthernetInterfaces\"\n    },\n    \"SerialInterfaces\": {\n        \"@odata.id\": \"/redfish/v1/Managers/1/SerialInterfaces\"\n    },\n    \"LogServices\": {\n        \"@odata.id\": \"/redfish/v1/Managers/1/LogServices\"\n    },\n    \"VirtualMedia\": {\n        \"@odata.id\": \"/redfish/v1/Managers/1/VirtualMedia\"\n    },\n    \"HostInterfaces\": {\n        \"@odata.id\": \"/redfish/v1/Managers/1/HostInterfaces\"\n    },\n    \"LldpService\": {\n        \"@odata.id\": \"/redfish/v1/Managers/1/LldpService\"\n    },\n    \"Links\": {\n        \"ManagerForServers@odata.count\": 1,\n        \"ManagerForServers\": [\n            {\n                \"@odata.id\": \"/redfish/v1/Systems/1\"\n            }\n        ],\n        \"ManagerForChassis@odata.count\": 1,\n        \"ManagerForChassis\": [\n            {\n                \"@odata.id\": \"/redfish/v1/Chassis/1\"\n            }\n        ],\n        \"ManagerInChassis\": {\n            \"@odata.id\": \"/redfish/v1/Chassis/1/\"\n        },\n        \"ActiveSoftwareImage\": {\n            \"@odata.id\": \"/redfish/v1/UpdateService/FirmwareInventory/BMC\"\n        },\n        \"SoftwareImages@odata.count\": 1,\n        \"SoftwareImages\": [\n            {\n                \"@odata.id\": \"/redfish/v1/UpdateService/FirmwareInventory/BMC\"\n            }\n        ],\n        \"Oem\": {}\n    },\n    \"Actions\": {\n        \"#Manager.Reset\": {\n            \"target\": \"/redfish/v1/Managers/1/Actions/Manager.Reset\"\n        }\n    }\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/serviceroot.json",
    "content": "{\n    \"@odata.type\": \"#ServiceRoot.v1_5_2.ServiceRoot\",\n    \"@odata.id\": \"/redfish/v1\",\n    \"Id\": \"ServiceRoot\",\n    \"Name\": \"Root Service\",\n    \"RedfishVersion\": \"1.9.0\",\n    \"UUID\": \"00000000-0000-0000-0000-3CECEFCEFEDA\",\n    \"Systems\": {\n        \"@odata.id\": \"/redfish/v1/Systems\"\n    },\n    \"Chassis\": {\n        \"@odata.id\": \"/redfish/v1/Chassis\"\n    },\n    \"Managers\": {\n        \"@odata.id\": \"/redfish/v1/Managers\"\n    },\n    \"Tasks\": {\n        \"@odata.id\": \"/redfish/v1/TaskService\"\n    },\n    \"SessionService\": {\n        \"@odata.id\": \"/redfish/v1/SessionService\"\n    },\n    \"AccountService\": {\n        \"@odata.id\": \"/redfish/v1/AccountService\"\n    },\n    \"EventService\": {\n        \"@odata.id\": \"/redfish/v1/EventService\"\n    },\n    \"UpdateService\": {\n        \"@odata.id\": \"/redfish/v1/UpdateService\"\n    },\n    \"CertificateService\": {\n        \"@odata.id\": \"/redfish/v1/CertificateService\"\n    },\n    \"Registries\": {\n        \"@odata.id\": \"/redfish/v1/Registries\"\n    },\n    \"JsonSchemas\": {\n        \"@odata.id\": \"/redfish/v1/JsonSchemas\"\n    },\n    \"TelemetryService\": {\n        \"@odata.id\": \"/redfish/v1/TelemetryService\"\n    },\n    \"Links\": {\n        \"Sessions\": {\n            \"@odata.id\": \"/redfish/v1/SessionService/Sessions\"\n        }\n    },\n    \"ProtocolFeaturesSupported\": {\n        \"FilterQuery\": true,\n        \"SelectQuery\": true,\n        \"ExcerptQuery\": false,\n        \"OnlyMemberQuery\": false,\n        \"ExpandQuery\": {\n            \"Links\": true,\n            \"NoLinks\": true,\n            \"ExpandAll\": true,\n            \"Levels\": true,\n            \"MaxLevels\": 2\n        }\n    }\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/serviceroot_no_manager.json",
    "content": "{\n    \"@odata.type\": \"#ServiceRoot.v1_5_2.ServiceRoot\",\n    \"@odata.id\": \"/redfish/v1\",\n    \"Id\": \"ServiceRoot\",\n    \"Name\": \"Root Service\",\n    \"RedfishVersion\": \"1.9.0\",\n    \"UUID\": \"00000000-0000-0000-0000-3CECEFCEFEDA\",\n    \"Systems\": {\n        \"@odata.id\": \"/redfish/v1/Systems\"\n    },\n    \"Chassis\": {\n        \"@odata.id\": \"/redfish/v1/Chassis\"\n    },\n    \"Tasks\": {\n        \"@odata.id\": \"/redfish/v1/TaskService\"\n    },\n    \"SessionService\": {\n        \"@odata.id\": \"/redfish/v1/SessionService\"\n    },\n    \"AccountService\": {\n        \"@odata.id\": \"/redfish/v1/AccountService\"\n    },\n    \"EventService\": {\n        \"@odata.id\": \"/redfish/v1/EventService\"\n    },\n    \"UpdateService\": {\n        \"@odata.id\": \"/redfish/v1/UpdateService\"\n    },\n    \"CertificateService\": {\n        \"@odata.id\": \"/redfish/v1/CertificateService\"\n    },\n    \"Registries\": {\n        \"@odata.id\": \"/redfish/v1/Registries\"\n    },\n    \"JsonSchemas\": {\n        \"@odata.id\": \"/redfish/v1/JsonSchemas\"\n    },\n    \"TelemetryService\": {\n        \"@odata.id\": \"/redfish/v1/TelemetryService\"\n    },\n    \"Links\": {\n        \"Sessions\": {\n            \"@odata.id\": \"/redfish/v1/SessionService/Sessions\"\n        }\n    },\n    \"ProtocolFeaturesSupported\": {\n        \"FilterQuery\": true,\n        \"SelectQuery\": true,\n        \"ExcerptQuery\": false,\n        \"OnlyMemberQuery\": false,\n        \"ExpandQuery\": {\n            \"Links\": true,\n            \"NoLinks\": true,\n            \"ExpandAll\": true,\n            \"Levels\": true,\n            \"MaxLevels\": 2\n        }\n    }\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/smc_1.14.0_serviceroot.json",
    "content": "{\"@odata.type\":\"#ServiceRoot.v1_14_0.ServiceRoot\",\"@odata.id\":\"/redfish/v1\",\"Id\":\"ServiceRoot\",\"Name\":\"Root Service\",\"RedfishVersion\":\"1.14.0\",\"UUID\":\"00000000-0000-0000-0000-3CECEFC84895\",\"Vendor\":\"Supermicro\",\"Systems\":{\"@odata.id\":\"/redfish/v1/Systems\"},\"Chassis\":{\"@odata.id\":\"/redfish/v1/Chassis\"},\"Managers\":{\"@odata.id\":\"/redfish/v1/Managers\"},\"Tasks\":{\"@odata.id\":\"/redfish/v1/TaskService\"},\"SessionService\":{\"@odata.id\":\"/redfish/v1/SessionService\"},\"AccountService\":{\"@odata.id\":\"/redfish/v1/AccountService\"},\"EventService\":{\"@odata.id\":\"/redfish/v1/EventService\"},\"UpdateService\":{\"@odata.id\":\"/redfish/v1/UpdateService\"},\"CertificateService\":{\"@odata.id\":\"/redfish/v1/CertificateService\"},\"Registries\":{\"@odata.id\":\"/redfish/v1/Registries\"},\"JsonSchemas\":{\"@odata.id\":\"/redfish/v1/JsonSchemas\"},\"TelemetryService\":{\"@odata.id\":\"/redfish/v1/TelemetryService\"},\"Product\":null,\"ServiceIdentification\":\"S482931X2814218\",\"Links\":{\"Sessions\":{\"@odata.id\":\"/redfish/v1/SessionService/Sessions\"}},\"Oem\":{\"Supermicro\":{\"DumpService\":{\"@odata.id\":\"/redfish/v1/Oem/Supermicro/DumpService\"}}},\"ProtocolFeaturesSupported\":{\"FilterQuery\":true,\"SelectQuery\":true,\"ExcerptQuery\":false,\"OnlyMemberQuery\":false,\"DeepOperations\":{\"DeepPATCH\":false,\"DeepPOST\":false,\"MaxLevels\":1},\"ExpandQuery\":{\"Links\":true,\"NoLinks\":true,\"ExpandAll\":true,\"Levels\":true,\"MaxLevels\":2}},\"@odata.etag\":\"\\\"a3ee7c2898ae386781519de584c4dacd\\\"\"}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/smc_1.14.0_systems.json",
    "content": "{\"@odata.type\":\"#ComputerSystemCollection.ComputerSystemCollection\",\"@odata.id\":\"/redfish/v1/Systems\",\"Name\":\"Computer System Collection\",\"Description\":\"Computer System Collection\",\"Members@odata.count\":1,\"Members\":[{\"@odata.id\":\"/redfish/v1/Systems/1\"}],\"@odata.etag\":\"\\\"e310554bb25b657853dd0b5f36f07991\\\"\"}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/smc_1.14.0_systems_1.json",
    "content": "{\"@odata.type\":\"#ComputerSystem.v1_16_0.ComputerSystem\",\"@odata.id\":\"/redfish/v1/Systems/1\",\"Id\":\"1\",\"Name\":\"System\",\"Description\":\"Description of server\",\"Status\":{\"State\":\"Enabled\",\"Health\":\"Critical\"},\"SerialNumber\":\"S482931X2814218\",\"PartNumber\":\"SYS-510T-MR-EI018\",\"AssetTag\":null,\"IndicatorLED\":\"Off\",\"LocationIndicatorActive\":false,\"SystemType\":\"Physical\",\"BiosVersion\":\"2.0\",\"Manufacturer\":\"Supermicro\",\"Model\":\"SYS-510T-MR-EI018\",\"SKU\":\"To be filled by O.E.M.\",\"UUID\":\"B11CC600-6D10-11EC-8000-3CECEFC846F8\",\"ProcessorSummary\":{\"Count\":1,\"Model\":\"Intel(R) Xeon(R) processor\",\"Status\":{\"State\":\"Enabled\",\"Health\":\"OK\",\"HealthRollup\":\"OK\"},\"Metrics\":{\"@odata.id\":\"/redfish/v1/Systems/1/ProcessorSummary/ProcessorMetrics\"}},\"MemorySummary\":{\"TotalSystemMemoryGiB\":64,\"MemoryMirroring\":\"System\",\"Status\":{\"State\":\"Enabled\",\"Health\":\"OK\",\"HealthRollup\":\"OK\"},\"Metrics\":{\"@odata.id\":\"/redfish/v1/Systems/1/MemorySummary/MemoryMetrics\"}},\"PowerState\":\"On\",\"PowerOnDelaySeconds\":3,\"PowerOnDelaySeconds@Redfish.AllowableNumbers\":[\"3:254:1\"],\"PowerOffDelaySeconds\":3,\"PowerOffDelaySeconds@Redfish.AllowableNumbers\":[\"3:254:1\"],\"PowerCycleDelaySeconds\":5,\"PowerCycleDelaySeconds@Redfish.AllowableNumbers\":[\"5:254:1\"],\"Boot\":{\"AutomaticRetryConfig\":\"Disabled\",\"BootSourceOverrideEnabled\":\"Continuous\",\"BootSourceOverrideMode\":\"UEFI\",\"BootSourceOverrideTarget\":\"Hdd\",\"BootSourceOverrideTarget@Redfish.AllowableValues\":[\"None\",\"Pxe\",\"Floppy\",\"Cd\",\"Usb\",\"Hdd\",\"BiosSetup\",\"UsbCd\",\"UefiBootNext\",\"UefiHttp\"],\"BootOptions\":{\"@odata.id\":\"/redfish/v1/Systems/1/BootOptions\"},\"BootNext\":null,\"BootOrder\":[\"Boot0003\",\"Boot0004\",\"Boot0005\",\"Boot0006\",\"Boot0007\",\"Boot0008\",\"Boot0009\",\"Boot000A\",\"Boot000B\",\"Boot0002\"]},\"GraphicalConsole\":{\"ServiceEnabled\":true,\"Port\":5900,\"MaxConcurrentSessions\":4,\"ConnectTypesSupported\":[\"KVMIP\"]},\"SerialConsole\":{\"MaxConcurrentSessions\":1,\"SSH\":{\"ServiceEnabled\":true,\"Port\":22,\"SharedWithManagerCLI\":true,\"ConsoleEntryCommand\":\"cd system1/sol1; start\",\"HotKeySequenceDisplay\":\"press <Enter>, <Esc>, and then <T> to terminate session\"},\"IPMI\":{\"HotKeySequenceDisplay\":\"Press ~.  - terminate connection\",\"ServiceEnabled\":true,\"Port\":623}},\"VirtualMediaConfig\":{\"ServiceEnabled\":true,\"Port\":623},\"BootProgress\":{\"OemLastState\":null,\"LastState\":\"SystemHardwareInitializationComplete\"},\"Processors\":{\"@odata.id\":\"/redfish/v1/Systems/1/Processors\"},\"Memory\":{\"@odata.id\":\"/redfish/v1/Systems/1/Memory\"},\"EthernetInterfaces\":{\"@odata.id\":\"/redfish/v1/Systems/1/EthernetInterfaces\"},\"NetworkInterfaces\":{\"@odata.id\":\"/redfish/v1/Systems/1/NetworkInterfaces\"},\"Storage\":{\"@odata.id\":\"/redfish/v1/Systems/1/Storage\"},\"LogServices\":{\"@odata.id\":\"/redfish/v1/Systems/1/LogServices\"},\"SecureBoot\":{\"@odata.id\":\"/redfish/v1/Systems/1/SecureBoot\"},\"Bios\":{\"@odata.id\":\"/redfish/v1/Systems/1/Bios\"},\"VirtualMedia\":{\"@odata.id\":\"/redfish/v1/Managers/1/VirtualMedia\"},\"Links\":{\"Chassis\":[{\"@odata.id\":\"/redfish/v1/Chassis/1\"}],\"ManagedBy\":[{\"@odata.id\":\"/redfish/v1/Managers/1\"}],\"PoweredBy\":[{\"@odata.id\":\"/redfish/v1/Chassis/1/PowerSubsystem/PowerSupplies/1\"},{\"@odata.id\":\"/redfish/v1/Chassis/1/PowerSubsystem/PowerSupplies/2\"}]},\"Actions\":{\"Oem\":{},\"#ComputerSystem.Reset\":{\"target\":\"/redfish/v1/Systems/1/Actions/ComputerSystem.Reset\",\"@Redfish.ActionInfo\":\"/redfish/v1/Systems/1/ResetActionInfo\",\"ResetType@Redfish.AllowableValues\":[\"On\",\"ForceOff\",\"GracefulShutdownGracefulRestart\",\"ForceRestart\",\"Nmi\",\"ForceOn\"]}},\"Oem\":{\"Supermicro\":{\"@odata.type\":\"#SmcSystemExtensions.v1_0_0.System\",\"NodeManager\":{\"@odata.id\":\"/redfish/v1/Systems/1/Oem/Supermicro/NodeManager\"}}},\"@odata.etag\":\"\\\"27ffd39c216000b3013c84008394dffd\\\"\"}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/smc_1.9.0_serviceroot.json",
    "content": "{\"@odata.type\":\"#ServiceRoot.v1_5_2.ServiceRoot\",\"@odata.id\":\"/redfish/v1\",\"Id\":\"ServiceRoot\",\"Name\":\"Root Service\",\"RedfishVersion\":\"1.9.0\",\"UUID\":\"00000000-0000-0000-0000-3CECEFC8484F\",\"Systems\":{\"@odata.id\":\"/redfish/v1/Systems\"},\"Chassis\":{\"@odata.id\":\"/redfish/v1/Chassis\"},\"Managers\":{\"@odata.id\":\"/redfish/v1/Managers\"},\"Tasks\":{\"@odata.id\":\"/redfish/v1/TaskService\"},\"SessionService\":{\"@odata.id\":\"/redfish/v1/SessionService\"},\"AccountService\":{\"@odata.id\":\"/redfish/v1/AccountService\"},\"EventService\":{\"@odata.id\":\"/redfish/v1/EventService\"},\"UpdateService\":{\"@odata.id\":\"/redfish/v1/UpdateService\"},\"CertificateService\":{\"@odata.id\":\"/redfish/v1/CertificateService\"},\"Registries\":{\"@odata.id\":\"/redfish/v1/Registries\"},\"JsonSchemas\":{\"@odata.id\":\"/redfish/v1/JsonSchemas\"},\"TelemetryService\":{\"@odata.id\":\"/redfish/v1/TelemetryService\"},\"Links\":{\"Sessions\":{\"@odata.id\":\"/redfish/v1/SessionService/Sessions\"}},\"Oem\":{\"Supermicro\":{\"DumpService\":{\"@odata.id\":\"/redfish/v1/Oem/Supermicro/DumpService\"}}},\"ProtocolFeaturesSupported\":{\"FilterQuery\":true,\"SelectQuery\":true,\"ExcerptQuery\":false,\"OnlyMemberQuery\":false,\"ExpandQuery\":{\"Links\":true,\"NoLinks\":true,\"ExpandAll\":true,\"Levels\":true,\"MaxLevels\":2}}}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/systems.json",
    "content": "{\n    \"@odata.type\": \"#ComputerSystemCollection.ComputerSystemCollection\",\n    \"@odata.id\": \"/redfish/v1/Systems\",\n    \"Name\": \"Computer System Collection\",\n    \"Description\": \"Computer System Collection\",\n    \"Members@odata.count\": 1,\n    \"Members\": [\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/1\"\n        }\n    ]\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/systems_1.json",
    "content": "{\n    \"@odata.type\": \"#ComputerSystem.v1_8_0.ComputerSystem\",\n    \"@odata.id\": \"/redfish/v1/Systems/1\",\n    \"Id\": \"1\",\n    \"Name\": \"System\",\n    \"Description\": \"Description of server\",\n    \"Status\": {\n        \"State\": \"Enabled\",\n        \"Health\": \"Critical\"\n    },\n    \"SerialNumber\": \"FOOBAR\",\n    \"PartNumber\": \"SYS-510T-MR1-EI018\",\n    \"SystemType\": \"Physical\",\n    \"BiosVersion\": \"1.6\",\n    \"Manufacturer\": \"Supermicro\",\n    \"Model\": \"SYS-510T-MR1-EI018\",\n    \"SKU\": \"To be filled by O.E.M.\",\n    \"UUID\": \"0032331A-24D7-EC11-8000-3CECEFCEFEDA\",\n    \"ProcessorSummary\": {\n        \"Count\": 1,\n        \"Model\": \"Intel(R) Xeon(R) processor\",\n        \"Status\": {\n            \"State\": \"Enabled\",\n            \"Health\": \"OK\",\n            \"HealthRollup\": \"OK\"\n        },\n        \"Metrics\": {\n            \"@odata.id\": \"/redfish/v1/Systems/1/ProcessorSummary/ProcessorMetrics\"\n        }\n    },\n    \"MemorySummary\": {\n        \"TotalSystemMemoryGiB\": 64,\n        \"MemoryMirroring\": \"System\",\n        \"Status\": {\n            \"State\": \"Enabled\",\n            \"Health\": \"OK\",\n            \"HealthRollup\": \"OK\"\n        },\n        \"Metrics\": {\n            \"@odata.id\": \"/redfish/v1/Systems/1/MemorySummary/MemoryMetrics\"\n        }\n    },\n    \"IndicatorLED\": \"Off\",\n    \"PowerState\": \"On\",\n    \"Boot\": {\n        \"BootSourceOverrideEnabled\": \"Once\",\n        \"BootSourceOverrideMode\": \"UEFI\",\n        \"BootSourceOverrideTarget\": \"Hdd\",\n        \"BootSourceOverrideTarget@Redfish.AllowableValues\": [\n            \"None\",\n            \"Pxe\",\n            \"Floppy\",\n            \"Cd\",\n            \"Usb\",\n            \"Hdd\",\n            \"BiosSetup\",\n            \"UsbCd\",\n            \"UefiBootNext\",\n            \"UefiHttp\"\n        ],\n        \"BootOptions\": {\n            \"@odata.id\": \"/redfish/v1/Systems/1/BootOptions\"\n        },\n        \"BootNext\": \"\",\n        \"BootOrder\": [\n            \"Boot0003\",\n            \"Boot0006\",\n            \"Boot0005\"\n        ]\n    },\n    \"Processors\": {\n        \"@odata.id\": \"/redfish/v1/Systems/1/Processors\"\n    },\n    \"Memory\": {\n        \"@odata.id\": \"/redfish/v1/Systems/1/Memory\"\n    },\n    \"EthernetInterfaces\": {\n        \"@odata.id\": \"/redfish/v1/Systems/1/EthernetInterfaces\"\n    },\n    \"NetworkInterfaces\": {\n        \"@odata.id\": \"/redfish/v1/Systems/1/NetworkInterfaces\"\n    },\n    \"SimpleStorage\": {\n        \"@odata.id\": \"/redfish/v1/Systems/1/SimpleStorage\"\n    },\n    \"Storage\": {\n        \"@odata.id\": \"/redfish/v1/Systems/1/Storage\"\n    },\n    \"LogServices\": {\n        \"@odata.id\": \"/redfish/v1/Systems/1/LogServices\"\n    },\n    \"SecureBoot\": {\n        \"@odata.id\": \"/redfish/v1/Systems/1/SecureBoot\"\n    },\n    \"Bios\": {\n        \"@odata.id\": \"/redfish/v1/Systems/1/Bios\"\n    },\n    \"Links\": {\n        \"Chassis\": [\n            {\n                \"@odata.id\": \"/redfish/v1/Chassis/1\"\n            }\n        ],\n        \"ManagedBy\": [\n            {\n                \"@odata.id\": \"/redfish/v1/Managers/1\"\n            }\n        ]\n    },\n    \"Actions\": {\n        \"#ComputerSystem.Reset\": {\n            \"target\": \"/redfish/v1/Systems/1/Actions/ComputerSystem.Reset\",\n            \"@Redfish.ActionInfo\": \"/redfish/v1/Systems/1/ResetActionInfo\"\n        }\n    }\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/systems_1_no_bios.json",
    "content": "{\n    \"@odata.type\": \"#ComputerSystem.v1_8_0.ComputerSystem\",\n    \"@odata.id\": \"/redfish/v1/Systems/1\",\n    \"Id\": \"1\",\n    \"Name\": \"System\",\n    \"Description\": \"Description of server\",\n    \"Status\": {\n        \"State\": \"Enabled\",\n        \"Health\": \"Critical\"\n    },\n    \"SerialNumber\": \"FOOBAR\",\n    \"PartNumber\": \"SYS-510T-MR1-EI018\",\n    \"SystemType\": \"Physical\",\n    \"BiosVersion\": \"1.6\",\n    \"Manufacturer\": \"Supermicro\",\n    \"Model\": \"SYS-510T-MR1-EI018\",\n    \"SKU\": \"To be filled by O.E.M.\",\n    \"UUID\": \"0032331A-24D7-EC11-8000-3CECEFCEFEDA\",\n    \"ProcessorSummary\": {\n        \"Count\": 1,\n        \"Model\": \"Intel(R) Xeon(R) processor\",\n        \"Status\": {\n            \"State\": \"Enabled\",\n            \"Health\": \"OK\",\n            \"HealthRollup\": \"OK\"\n        },\n        \"Metrics\": {\n            \"@odata.id\": \"/redfish/v1/Systems/1/ProcessorSummary/ProcessorMetrics\"\n        }\n    },\n    \"MemorySummary\": {\n        \"TotalSystemMemoryGiB\": 64,\n        \"MemoryMirroring\": \"System\",\n        \"Status\": {\n            \"State\": \"Enabled\",\n            \"Health\": \"OK\",\n            \"HealthRollup\": \"OK\"\n        },\n        \"Metrics\": {\n            \"@odata.id\": \"/redfish/v1/Systems/1/MemorySummary/MemoryMetrics\"\n        }\n    },\n    \"IndicatorLED\": \"Off\",\n    \"PowerState\": \"On\",\n    \"Boot\": {\n        \"BootSourceOverrideEnabled\": \"Once\",\n        \"BootSourceOverrideMode\": \"UEFI\",\n        \"BootSourceOverrideTarget\": \"Hdd\",\n        \"BootSourceOverrideTarget@Redfish.AllowableValues\": [\n            \"None\",\n            \"Pxe\",\n            \"Floppy\",\n            \"Cd\",\n            \"Usb\",\n            \"Hdd\",\n            \"BiosSetup\",\n            \"UsbCd\",\n            \"UefiBootNext\",\n            \"UefiHttp\"\n        ],\n        \"BootOptions\": {\n            \"@odata.id\": \"/redfish/v1/Systems/1/BootOptions\"\n        },\n        \"BootNext\": \"\",\n        \"BootOrder\": [\n            \"Boot0003\",\n            \"Boot0006\",\n            \"Boot0005\"\n        ]\n    },\n    \"Processors\": {\n        \"@odata.id\": \"/redfish/v1/Systems/1/Processors\"\n    },\n    \"Memory\": {\n        \"@odata.id\": \"/redfish/v1/Systems/1/Memory\"\n    },\n    \"EthernetInterfaces\": {\n        \"@odata.id\": \"/redfish/v1/Systems/1/EthernetInterfaces\"\n    },\n    \"NetworkInterfaces\": {\n        \"@odata.id\": \"/redfish/v1/Systems/1/NetworkInterfaces\"\n    },\n    \"SimpleStorage\": {\n        \"@odata.id\": \"/redfish/v1/Systems/1/SimpleStorage\"\n    },\n    \"Storage\": {\n        \"@odata.id\": \"/redfish/v1/Systems/1/Storage\"\n    },\n    \"LogServices\": {\n        \"@odata.id\": \"/redfish/v1/Systems/1/LogServices\"\n    },\n    \"SecureBoot\": {\n        \"@odata.id\": \"/redfish/v1/Systems/1/SecureBoot\"\n    },\n    \"Bios\": {\n        \"@odata.id\": \"/redfish/v1/Systems/1/Bios\"\n    },\n    \"Links\": {\n        \"Chassis\": [\n            {\n                \"@odata.id\": \"/redfish/v1/Chassis/1\"\n            }\n        ],\n        \"ManagedBy\": [\n            {\n                \"@odata.id\": \"/redfish/v1/Managers/1\"\n            }\n        ]\n    },\n    \"Actions\": {\n        \"#ComputerSystem.Reset\": {\n            \"target\": \"/redfish/v1/Systems/1/Actions/ComputerSystem.Reset\",\n            \"@Redfish.ActionInfo\": \"/redfish/v1/Systems/1/ResetActionInfo\"\n        }\n    }\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/systems_bios.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#Bios.Bios\",\n    \"@odata.id\": \"/redfish/v1/Systems/1/Bios\",\n    \"@odata.type\": \"#Bios.v1_1_1.Bios\",\n    \"Id\": \"Bios\",\n    \"Name\": \"BIOS Configuration Current Settings\",\n    \"Description\": \"BIOS Configuration Current Settings\",\n    \"AttributeRegistry\": \"BiosAttributeRegistry.v1_0_3\",\n    \"Attributes\": {\n        \"SmuVersion\": \"0.36.113.0\",\n        \"DxioVersion\": \"36.637\",\n        \"ProcCoreSpeed\": \"2.80 GHz\",\n        \"Proc1Id\": \"17-31-0\",\n        \"Proc1Brand\": \"AMD EPYC 7402P 24-Core Processor               \",\n        \"Proc1L2Cache\": \"24x512 KB\",\n        \"Proc1L3Cache\": \"128 MB\",\n        \"Proc1Microcode\": \"0x8301052\"\n    }\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/tasks/tasks_1_completed.json",
    "content": "{\n    \"@odata.type\": \"#Task.v1_4_3.Task\",\n    \"@odata.id\": \"/redfish/v1/TaskService/Tasks/1\",\n    \"Id\": \"1\",\n    \"Name\": \"BIOS Verify\",\n    \"TaskState\": \"Completed\",\n    \"StartTime\": \"2023-11-06T12:04:16+00:00\",\n    \"EndTime\": \"2023-11-06T12:05:31+00:00\",\n    \"PercentComplete\": 100,\n    \"HidePayload\": true,\n    \"TaskMonitor\": \"/redfish/v1/TaskMonitor/fa37JncCHryDsbzayy4cBWDxS22Jjzh\",\n    \"TaskStatus\": \"OK\",\n    \"Messages\": [\n        {\n            \"MessageId\": \"\",\n            \"RelatedProperties\": [\n                \"\"\n            ],\n            \"Message\": \"\",\n            \"MessageArgs\": [\n                \"\"\n            ],\n            \"Severity\": \"\"\n        }\n    ],\n    \"Oem\": {}\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/tasks/tasks_1_failed.json",
    "content": "{\n    \"@odata.type\": \"#Task.v1_4_3.Task\",\n    \"@odata.id\": \"/redfish/v1/TaskService/Tasks/1\",\n    \"Id\": \"1\",\n    \"Name\": \"BIOS Verify\",\n    \"TaskState\": \"Failed\",\n    \"StartTime\": \"2023-11-06T12:04:16+00:00\",\n    \"EndTime\": \"2023-11-06T12:05:31+00:00\",\n    \"PercentComplete\": 100,\n    \"HidePayload\": true,\n    \"TaskMonitor\": \"/redfish/v1/TaskMonitor/fa37JncCHryDsbzayy4cBWDxS22Jjzh\",\n    \"TaskStatus\": \"OK\",\n    \"Messages\": [\n        {\n            \"MessageId\": \"\",\n            \"RelatedProperties\": [\n                \"\"\n            ],\n            \"Message\": \"\",\n            \"MessageArgs\": [\n                \"\"\n            ],\n            \"Severity\": \"\"\n        }\n    ],\n    \"Oem\": {}\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/tasks/tasks_1_pending.json",
    "content": "{\n    \"@odata.type\": \"#Task.v1_4_3.Task\",\n    \"@odata.id\": \"/redfish/v1/TaskService/Tasks/1\",\n    \"Id\": \"1\",\n    \"Name\": \"BIOS Verify\",\n    \"TaskState\": \"Pending\",\n    \"StartTime\": \"2023-11-06T12:04:16+00:00\",\n    \"EndTime\": \"2023-11-06T12:05:31+00:00\",\n    \"PercentComplete\": 100,\n    \"HidePayload\": true,\n    \"TaskMonitor\": \"/redfish/v1/TaskMonitor/fa37JncCHryDsbzayy4cBWDxS22Jjzh\",\n    \"TaskStatus\": \"OK\",\n    \"Messages\": [\n        {\n            \"MessageId\": \"\",\n            \"RelatedProperties\": [\n                \"\"\n            ],\n            \"Message\": \"\",\n            \"MessageArgs\": [\n                \"\"\n            ],\n            \"Severity\": \"\"\n        }\n    ],\n    \"Oem\": {}\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/tasks/tasks_1_running.json",
    "content": "{\n    \"@odata.type\": \"#Task.v1_4_3.Task\",\n    \"@odata.id\": \"/redfish/v1/TaskService/Tasks/1\",\n    \"Id\": \"1\",\n    \"Name\": \"BIOS Verify\",\n    \"TaskState\": \"Running\",\n    \"StartTime\": \"2023-11-06T12:04:16+00:00\",\n    \"EndTime\": \"2023-11-06T12:05:31+00:00\",\n    \"PercentComplete\": 100,\n    \"HidePayload\": true,\n    \"TaskMonitor\": \"/redfish/v1/TaskMonitor/fa37JncCHryDsbzayy4cBWDxS22Jjzh\",\n    \"TaskStatus\": \"OK\",\n    \"Messages\": [\n        {\n            \"MessageId\": \"\",\n            \"RelatedProperties\": [\n                \"\"\n            ],\n            \"Message\": \"\",\n            \"MessageArgs\": [\n                \"\"\n            ],\n            \"Severity\": \"\"\n        }\n    ],\n    \"Oem\": {}\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/tasks/tasks_1_scheduled.json",
    "content": "{\n    \"@odata.type\": \"#Task.v1_4_3.Task\",\n    \"@odata.id\": \"/redfish/v1/TaskService/Tasks/1\",\n    \"Id\": \"1\",\n    \"Name\": \"BIOS Verify\",\n    \"TaskState\": \"Scheduled\",\n    \"StartTime\": \"2023-11-06T12:04:16+00:00\",\n    \"EndTime\": \"2023-11-06T12:05:31+00:00\",\n    \"PercentComplete\": 100,\n    \"HidePayload\": true,\n    \"TaskMonitor\": \"/redfish/v1/TaskMonitor/fa37JncCHryDsbzayy4cBWDxS22Jjzh\",\n    \"TaskStatus\": \"OK\",\n    \"Messages\": [\n        {\n            \"MessageId\": \"\",\n            \"RelatedProperties\": [\n                \"\"\n            ],\n            \"Message\": \"\",\n            \"MessageArgs\": [\n                \"\"\n            ],\n            \"Severity\": \"\"\n        }\n    ],\n    \"Oem\": {}\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/tasks/tasks_1_starting.json",
    "content": "{\n    \"@odata.type\": \"#Task.v1_4_3.Task\",\n    \"@odata.id\": \"/redfish/v1/TaskService/Tasks/1\",\n    \"Id\": \"1\",\n    \"Name\": \"BIOS Verify\",\n    \"TaskState\": \"Starting\",\n    \"StartTime\": \"2023-11-06T12:04:16+00:00\",\n    \"EndTime\": \"2023-11-06T12:05:31+00:00\",\n    \"PercentComplete\": 100,\n    \"HidePayload\": true,\n    \"TaskMonitor\": \"/redfish/v1/TaskMonitor/fa37JncCHryDsbzayy4cBWDxS22Jjzh\",\n    \"TaskStatus\": \"OK\",\n    \"Messages\": [\n        {\n            \"MessageId\": \"\",\n            \"RelatedProperties\": [\n                \"\"\n            ],\n            \"Message\": \"\",\n            \"MessageArgs\": [\n                \"\"\n            ],\n            \"Severity\": \"\"\n        }\n    ],\n    \"Oem\": {}\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/tasks/tasks_1_unknown.json",
    "content": "{\n    \"@odata.type\": \"#Task.v1_4_3.Task\",\n    \"@odata.id\": \"/redfish/v1/TaskService/Tasks/1\",\n    \"Id\": \"1\",\n    \"Name\": \"BIOS Verify\",\n    \"TaskState\": \"foobared\",\n    \"StartTime\": \"2023-11-06T12:04:16+00:00\",\n    \"EndTime\": \"2023-11-06T12:05:31+00:00\",\n    \"PercentComplete\": 100,\n    \"HidePayload\": true,\n    \"TaskMonitor\": \"/redfish/v1/TaskMonitor/fa37JncCHryDsbzayy4cBWDxS22Jjzh\",\n    \"TaskStatus\": \"OK\",\n    \"Messages\": [\n        {\n            \"MessageId\": \"\",\n            \"RelatedProperties\": [\n                \"\"\n            ],\n            \"Message\": \"\",\n            \"MessageArgs\": [\n                \"\"\n            ],\n            \"Severity\": \"\"\n        }\n    ],\n    \"Oem\": {}\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/tasks/tasks_2.json",
    "content": "{\n    \"@odata.type\": \"#Task.v1_4_3.Task\",\n    \"@odata.id\": \"/redfish/v1/TaskService/Tasks/2\",\n    \"Id\": \"2\",\n    \"Name\": \"BIOS Update\",\n    \"TaskState\": \"Completed\",\n    \"StartTime\": \"2023-11-06T12:05:47+00:00\",\n    \"EndTime\": \"2023-11-06T12:12:37+00:00\",\n    \"PercentComplete\": 100,\n    \"HidePayload\": true,\n    \"TaskMonitor\": \"/redfish/v1/TaskMonitor/MaiRrV41mtzxlYvKWrO72tK0LK0e1zL\",\n    \"TaskStatus\": \"OK\",\n    \"Messages\": [\n        {\n            \"MessageId\": \"\",\n            \"RelatedProperties\": [\n                \"\"\n            ],\n            \"Message\": \"\",\n            \"MessageArgs\": [\n                \"\"\n            ],\n            \"Severity\": \"\"\n        }\n    ],\n    \"Oem\": {}\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/tasks.json",
    "content": "{\n    \"@odata.type\": \"#TaskCollection.TaskCollection\",\n    \"@odata.id\": \"/redfish/v1/TaskService/Tasks\",\n    \"Id\": \"Tasks\",\n    \"Name\": \"Task Collection\",\n    \"Members@odata.count\": 2,\n    \"Members\": [\n        {\n            \"@odata.id\": \"/redfish/v1/TaskService/Tasks/1\"\n        },\n        {\n            \"@odata.id\": \"/redfish/v1/TaskService/Tasks/2\"\n        }\n    ]\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/taskservice.json",
    "content": "{\n    \"@odata.type\": \"#TaskService.v1_1_3.TaskService\",\n    \"@odata.id\": \"/redfish/v1/TaskService\",\n    \"Id\": \"TaskService\",\n    \"Name\": \"Tasks Service\",\n    \"DateTime\": \"2023-11-07T10:17:09Z\",\n    \"CompletedTaskOverWritePolicy\": \"Oldest\",\n    \"LifeCycleEventOnTaskStateChange\": false,\n    \"Status\": {\n        \"State\": \"Enabled\",\n        \"Health\": \"OK\"\n    },\n    \"ServiceEnabled\": true,\n    \"Tasks\": {\n        \"@odata.id\": \"/redfish/v1/TaskService/Tasks\"\n    },\n    \"Oem\": {}\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/updateservice_disabled.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#UpdateService.UpdateService\",\n    \"@odata.id\": \"/redfish/v1/UpdateService\",\n    \"@odata.type\": \"#UpdateService.v1_8_0.UpdateService\",\n    \"Actions\": {\n        \"#UpdateService.SimpleUpdate\": {\n            \"@Redfish.OperationApplyTimeSupport\": {\n                \"@odata.type\": \"#Settings.v1_3_0.OperationApplyTimeSupport\",\n                \"SupportedValues\": [\n                    \"Immediate\",\n                    \"OnReset\"\n                ]\n            },\n            \"TransferProtocol@Redfish.AllowableValues\": [\n                \"HTTP\",\n                \"NFS\",\n                \"CIFS\",\n                \"TFTP\",\n                \"HTTPS\"\n            ],\n            \"target\": \"/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate\"\n        }\n    },\n    \"Description\": \"Represents the properties for the Update Service\",\n    \"FirmwareInventory\": {\n        \"@odata.id\": \"/redfish/v1/UpdateService/FirmwareInventory\"\n    },\n    \"HttpPushUri\": \"/redfish/v1/UpdateService/FirmwareInventory\",\n    \"Id\": \"UpdateService\",\n    \"MaxImageSizeBytes\": null,\n    \"MultipartHttpPushUri\": \"/redfish/v1/UpdateService/MultipartUpload\",\n    \"Name\": \"Update Service\",\n    \"ServiceEnabled\": false,\n    \"SoftwareInventory\": {\n        \"@odata.id\": \"/redfish/v1/UpdateService/SoftwareInventory\"\n    },\n    \"Status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n    }\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/updateservice_ok_response.json",
    "content": "{\n    \"Accepted\": {\n        \"code\": \"Base.v1_10_3.Accepted\",\n        \"Message\": \"Successfully Accepted Request. Please see the location header and ExtendedInfo for more information.\",\n        \"@Message.ExtendedInfo\": [\n            {\n                \"MessageId\": \"SMC.1.0.OemSimpleupdateAcceptedMessage\",\n                \"Severity\": \"Ok\",\n                \"Resolution\": \"No resolution was required.\",\n                \"Message\": \"Please also check Task Resource /redfish/v1/TaskService/Tasks/1 to see more information.\",\n                \"MessageArgs\": [\n                    \"/redfish/v1/TaskService/Tasks/1234\"\n                ],\n                \"RelatedProperties\": [\n                    \"BiosVerifyAccepted\"\n                ]\n            }\n        ]\n    }\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/updateservice_unexpected_response.json",
    "content": "{\n    \"Accepted\": {\n        \"code\": \"Base.v1_10_3.Accepted\",\n        \"Message\": \"Successfully Accepted Request. Please see the location header and ExtendedInfo for more information.\",\n        \"@Message.ExtendedInfo\": [\n            {\n                \"MessageId\": \"SMC.1.0.OemSimpleupdateAcceptedMessage\",\n                \"Severity\": \"Ok\",\n                \"Resolution\": \"No resolution was required.\",\n                \"RelatedProperties\": [\n                    \"BiosVerifyAccepted\"\n                ]\n            }\n        ]\n    }\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/updateservice_with_httppushuri.json",
    "content": "{\n    \"@odata.id\": \"/redfish/v1/UpdateService\",\n    \"@odata.type\": \"#UpdateService.v1_5_0.UpdateService\",\n    \"Description\": \"Service for Software Update\",\n    \"FirmwareInventory\": {\n        \"@odata.id\": \"/redfish/v1/UpdateService/FirmwareInventory\"\n    },\n    \"HttpPushUri\": \"/redfish/v1/UpdateService/update\",\n    \"HttpPushUriOptions\": {\n        \"HttpPushUriApplyTime\": {\n            \"ApplyTime\": \"OnReset\"\n        }\n    },\n    \"Id\": \"UpdateService\",\n    \"MaxImageSizeBytes\": 35651584,\n    \"Name\": \"Update Service\",\n    \"ServiceEnabled\": true\n}"
  },
  {
    "path": "internal/redfishwrapper/fixtures/updateservice_with_multipart.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#UpdateService.UpdateService\",\n    \"@odata.id\": \"/redfish/v1/UpdateService\",\n    \"@odata.type\": \"#UpdateService.v1_8_0.UpdateService\",\n    \"Actions\": {\n        \"#UpdateService.SimpleUpdate\": {\n            \"@Redfish.OperationApplyTimeSupport\": {\n                \"@odata.type\": \"#Settings.v1_3_0.OperationApplyTimeSupport\",\n                \"SupportedValues\": [\n                    \"Immediate\",\n                    \"OnReset\"\n                ]\n            },\n            \"TransferProtocol@Redfish.AllowableValues\": [\n                \"HTTP\",\n                \"NFS\",\n                \"CIFS\",\n                \"TFTP\",\n                \"HTTPS\"\n            ],\n            \"target\": \"/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate\"\n        }\n    },\n    \"Description\": \"Represents the properties for the Update Service\",\n    \"FirmwareInventory\": {\n        \"@odata.id\": \"/redfish/v1/UpdateService/FirmwareInventory\"\n    },\n    \"HttpPushUri\": \"/redfish/v1/UpdateService/FirmwareInventory\",\n    \"Id\": \"UpdateService\",\n    \"MaxImageSizeBytes\": null,\n    \"MultipartHttpPushUri\": \"/redfish/v1/UpdateService/MultipartUpload\",\n    \"Name\": \"Update Service\",\n    \"ServiceEnabled\": true,\n    \"SoftwareInventory\": {\n        \"@odata.id\": \"/redfish/v1/UpdateService/SoftwareInventory\"\n    },\n    \"Status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n    }\n}"
  },
  {
    "path": "internal/redfishwrapper/inventory.go",
    "content": "package redfishwrapper\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/bmc-toolbox/common\"\n\t\"github.com/stmcginnis/gofish/schemas\"\n)\n\nvar (\n\t// Supported Chassis Odata IDs\n\tKnownChassisOdataIDs = []string{\n\t\t// Dells\n\t\t\"/redfish/v1/Chassis/Enclosure.Internal.0-1\",\n\t\t\"/redfish/v1/Chassis/System.Embedded.1\",\n\t\t\"/redfish/v1/Chassis/Enclosure.Internal.0-1:NonRAID.Integrated.1-1\",\n\t\t// Supermicro\n\t\t\"/redfish/v1/Chassis/1\",\n\t\t// MegaRAC/ARockRack\n\t\t\"/redfish/v1/Chassis/Self\",\n\t\t// OpenBMC on ASRock\n\t\t\"/redfish/v1/Chassis/ASRock_ROMED8HM3\",\n\t}\n\n\t// Supported System Odata IDs\n\tknownSystemsOdataIDs = []string{\n\t\t// Dells\n\t\t\"/redfish/v1/Systems/System.Embedded.1\",\n\t\t\"/redfish/v1/Systems/System.Embedded.1/Bios\",\n\t\t// Supermicros\n\t\t\"/redfish/v1/Systems/1\",\n\t\t// MegaRAC/ARockRack\n\t\t\"/redfish/v1/Systems/Self\",\n\t\t// OpenBMC on ASRock\n\t\t\"/redfish/v1/Systems/system\",\n\t}\n\n\t// Supported Manager Odata IDs (BMCs)\n\tmanagerOdataIDs = []string{\n\t\t// Dells\n\t\t\"/redfish/v1/Managers/iDRAC.Embedded.1\",\n\t\t// Supermicros\n\t\t\"/redfish/v1/Managers/1\",\n\t\t// MegaRAC/ARockRack\n\t\t\"/redfish/v1/Managers/Self\",\n\t\t// OpenBMC on ASRock\n\t\t\"/redfish/v1/Managers/bmc\",\n\t}\n)\n\n// TODO: consider removing this\nfunc (c *Client) compatibleOdataID(OdataID string, knownOdataIDs []string) bool {\n\tfor _, url := range knownOdataIDs {\n\t\tif url == OdataID {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc (c *Client) Inventory(ctx context.Context, failOnError bool) (device *common.Device, err error) {\n\tupdateService, err := c.UpdateService()\n\tif err != nil && failOnError {\n\t\treturn nil, errors.Wrap(bmclibErrs.ErrRedfishSoftwareInventory, err.Error())\n\t}\n\n\tsoftwareInventory := []*schemas.SoftwareInventory{}\n\n\tif updateService != nil {\n\t\t// nolint\n\t\tsoftwareInventory, err = updateService.FirmwareInventory()\n\t\tif err != nil && failOnError {\n\t\t\treturn nil, errors.Wrap(bmclibErrs.ErrRedfishSoftwareInventory, err.Error())\n\t\t}\n\t}\n\n\t// initialize device to be populated with inventory\n\tnewDevice := common.NewDevice()\n\tdevice = &newDevice\n\n\t// populate device Chassis components attributes\n\terr = c.chassisAttributes(ctx, device, failOnError, softwareInventory)\n\tif err != nil && failOnError {\n\t\treturn nil, err\n\t}\n\n\t// populate device System components attributes\n\terr = c.systemAttributes(device, failOnError, softwareInventory)\n\tif err != nil && failOnError {\n\t\treturn nil, err\n\t}\n\n\t// populate device BMC component attributes\n\terr = c.bmcAttributes(ctx, device, softwareInventory)\n\tif err != nil && failOnError {\n\t\treturn nil, err\n\t}\n\n\treturn device, nil\n}\n\n// DeviceVendorModel returns the device vendor and model attributes\n\n// bmcAttributes collects BMC component attributes\nfunc (c *Client) bmcAttributes(ctx context.Context, device *common.Device, softwareInventory []*schemas.SoftwareInventory) (err error) {\n\tmanagers, err := c.Managers(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar compatible int\n\tfor _, manager := range managers {\n\t\tif managers == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif !c.compatibleOdataID(manager.ODataID, managerOdataIDs) {\n\t\t\tcontinue\n\t\t}\n\n\t\tcompatible++\n\n\t\tif manager.ManagerType != \"BMC\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tdevice.BMC = &common.BMC{\n\t\t\tCommon: common.Common{\n\t\t\t\tDescription: manager.Description,\n\t\t\t\tVendor:      device.Vendor,\n\t\t\t\tModel:       device.Model,\n\t\t\t\tStatus: &common.Status{\n\t\t\t\t\tHealth: string(manager.Status.Health),\n\t\t\t\t\tState:  string(manager.Status.State),\n\t\t\t\t},\n\t\t\t\tFirmware: &common.Firmware{\n\t\t\t\t\tInstalled: manager.FirmwareVersion,\n\t\t\t\t},\n\t\t\t},\n\n\t\t\tID: manager.ID,\n\t\t}\n\n\t\t// include additional firmware attributes from redfish firmware inventory\n\t\tc.firmwareAttributes(\"\", device.BMC.ID, device.BMC.Firmware, softwareInventory)\n\t}\n\n\tif compatible == 0 {\n\t\treturn bmclibErrs.ErrRedfishManagerOdataID\n\t}\n\n\treturn nil\n}\n\n// chassisAttributes populates the device chassis attributes\nfunc (c *Client) chassisAttributes(ctx context.Context, device *common.Device, failOnError bool, softwareInventory []*schemas.SoftwareInventory) (err error) {\n\tchassis, err := c.Chassis(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcompatible := 0\n\tfor _, ch := range chassis {\n\t\tif !c.compatibleOdataID(ch.ODataID, KnownChassisOdataIDs) {\n\t\t\tcontinue\n\t\t}\n\n\t\tcompatible++\n\n\t\terr = c.collectEnclosure(ch, device, softwareInventory)\n\t\tif err != nil && failOnError {\n\t\t\treturn err\n\t\t}\n\n\t\terr = c.collectPSUs(ch, device, softwareInventory)\n\t\tif err != nil && failOnError {\n\t\t\treturn err\n\t\t}\n\n\t}\n\n\terr = c.collectCPLDs(device, softwareInventory)\n\tif err != nil && failOnError {\n\t\treturn err\n\t}\n\n\tif compatible == 0 {\n\t\treturn bmclibErrs.ErrRedfishChassisOdataID\n\t}\n\n\treturn nil\n\n}\n\nfunc (c *Client) systemAttributes(device *common.Device, failOnError bool, softwareInventory []*schemas.SoftwareInventory) (err error) {\n\tsys, err := c.System()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !c.compatibleOdataID(sys.ODataID, knownSystemsOdataIDs) {\n\t\treturn bmclibErrs.ErrRedfishSystemOdataID\n\t}\n\n\tif sys.Manufacturer != \"\" && sys.Model != \"\" && sys.SerialNumber != \"\" {\n\t\tdevice.Vendor = sys.Manufacturer\n\t\tdevice.Model = sys.Model\n\t\tdevice.Serial = sys.SerialNumber\n\t}\n\n\ttype collectorFuncs []func(\n\t\tsys *schemas.ComputerSystem,\n\t\tdevice *common.Device,\n\t\tsoftwareInventory []*schemas.SoftwareInventory,\n\t) error\n\n\t// slice of collector methods\n\tfuncs := collectorFuncs{\n\t\tc.collectCPUs,\n\t\tc.collectDIMMs,\n\t\tc.collectDrives,\n\t\tc.collectBIOS,\n\t\tc.collectNICs,\n\t\tc.collectTPMs,\n\t\tc.collectStorageControllers,\n\t}\n\n\t// execute collector methods\n\tfor _, f := range funcs {\n\t\terr := f(sys, device, softwareInventory)\n\t\tif err != nil && failOnError {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// firmwareInventory looks up the redfish inventory for objects that\n// match - 1. slug, 2. id\n// and returns the intalled or previous firmware for objects that matched\n//\n// slug - the component slug constant\n// id - the component ID\n// previous - when true returns previously installed firmware, else returns the current\nfunc (c *Client) firmwareAttributes(slug, id string, firmwareObj *common.Firmware, softwareInventory []*schemas.SoftwareInventory) {\n\tif len(softwareInventory) == 0 {\n\t\treturn\n\t}\n\n\tif id == \"\" {\n\t\tid = slug\n\t}\n\n\tfor _, inv := range softwareInventory {\n\t\t// include previously installed firmware attributes\n\t\tif strings.HasPrefix(inv.ID, \"Previous\") {\n\t\t\tif strings.Contains(inv.ID, id) || strings.EqualFold(slug, inv.Name) {\n\n\t\t\t\tif firmwareObj == nil {\n\t\t\t\t\tfirmwareObj = &common.Firmware{}\n\t\t\t\t}\n\n\t\t\t\tif firmwareObj.Installed == inv.Version {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tfirmwareObj.Previous = append(firmwareObj.Previous, &common.Firmware{\n\t\t\t\t\tInstalled:  inv.Version,\n\t\t\t\t\tSoftwareID: inv.SoftwareID,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\t// update firmwareObj with installed firmware attributes\n\t\tif strings.HasPrefix(inv.ID, \"Installed\") {\n\t\t\tif strings.Contains(inv.ID, id) || strings.EqualFold(slug, inv.Name) {\n\n\t\t\t\tif firmwareObj == nil {\n\t\t\t\t\tfirmwareObj = &common.Firmware{}\n\t\t\t\t}\n\n\t\t\t\tif firmwareObj.Installed == \"\" || firmwareObj.Installed != inv.Version {\n\t\t\t\t\tfirmwareObj.Installed = inv.Version\n\t\t\t\t}\n\n\t\t\t\tfirmwareObj.Metadata = map[string]string{\"name\": inv.Name}\n\t\t\t\tfirmwareObj.SoftwareID = inv.SoftwareID\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "internal/redfishwrapper/inventory_collect.go",
    "content": "package redfishwrapper\n\nimport (\n\t\"math\"\n\t\"strings\"\n\n\t\"github.com/bmc-toolbox/common\"\n\t\"github.com/stmcginnis/gofish\"\n\t\"github.com/stmcginnis/gofish/schemas\"\n)\n\n// defines various inventory collection helper methods\n\n// collectEnclosure collects Enclosure information\nfunc (c *Client) collectEnclosure(ch *schemas.Chassis, device *common.Device, softwareInventory []*schemas.SoftwareInventory) (err error) {\n\te := &common.Enclosure{\n\t\tCommon: common.Common{\n\t\t\tDescription: ch.Description,\n\t\t\tVendor:      common.FormatVendorName(ch.Manufacturer),\n\t\t\tModel:       ch.Model,\n\t\t\tSerial:      ch.SerialNumber,\n\t\t\tStatus: &common.Status{\n\t\t\t\tHealth: string(ch.Status.Health),\n\t\t\t\tState:  string(ch.Status.State),\n\t\t\t},\n\t\t\tFirmware: &common.Firmware{},\n\t\t},\n\n\t\tID:          ch.ID,\n\t\tChassisType: string(ch.ChassisType),\n\t}\n\n\tif e.Model == \"\" && ch.PartNumber != \"\" {\n\t\te.Model = ch.PartNumber\n\t}\n\n\t// include additional firmware attributes from redfish firmware inventory\n\tc.firmwareAttributes(common.SlugEnclosure, e.ID, e.Firmware, softwareInventory)\n\n\tdevice.Enclosures = append(device.Enclosures, e)\n\n\treturn nil\n}\n\n// collectPSUs collects Power Supply Unit component information\nfunc (c *Client) collectPSUs(ch *schemas.Chassis, device *common.Device, softwareInventory []*schemas.SoftwareInventory) (err error) {\n\tpower, err := ch.Power()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif power == nil {\n\t\treturn nil\n\t}\n\n\tfor _, psu := range power.PowerSupplies {\n\t\tp := &common.PSU{\n\t\t\tCommon: common.Common{\n\t\t\t\tDescription: psu.Name,\n\t\t\t\tVendor:      common.FormatVendorName(psu.Manufacturer),\n\t\t\t\tModel:       psu.Model,\n\t\t\t\tSerial:      psu.SerialNumber,\n\n\t\t\t\tStatus: &common.Status{\n\t\t\t\t\tHealth: string(psu.Status.Health),\n\t\t\t\t\tState:  string(psu.Status.State),\n\t\t\t\t},\n\t\t\t\tFirmware: &common.Firmware{\n\t\t\t\t\tInstalled: psu.FirmwareVersion,\n\t\t\t\t},\n\t\t\t},\n\n\t\t\tID:                 psu.ID,\n\t\t\tPowerCapacityWatts: int64(gofish.Deref(psu.PowerCapacityWatts)),\n\t\t}\n\n\t\t// include additional firmware attributes from redfish firmware inventory\n\t\tc.firmwareAttributes(common.SlugPSU, psu.ID, p.Firmware, softwareInventory)\n\n\t\tdevice.PSUs = append(device.PSUs, p)\n\n\t}\n\treturn nil\n}\n\n// collectTPMs collects Trusted Platform Module component information\nfunc (c *Client) collectTPMs(sys *schemas.ComputerSystem, device *common.Device, softwareInventory []*schemas.SoftwareInventory) (err error) {\n\tfor _, module := range sys.TrustedModules { //nolint:staticcheck\n\t\ttpm := &common.TPM{\n\t\t\tCommon: common.Common{\n\t\t\t\tFirmware: &common.Firmware{\n\t\t\t\t\tInstalled: module.FirmwareVersion,\n\t\t\t\t},\n\t\t\t\tStatus: &common.Status{\n\t\t\t\t\tState:  string(module.Status.State),\n\t\t\t\t\tHealth: string(module.Status.Health),\n\t\t\t\t},\n\t\t\t},\n\n\t\t\tInterfaceType: string(module.InterfaceType),\n\t\t}\n\n\t\t// include additional firmware attributes from redfish firmware inventory\n\t\tc.firmwareAttributes(common.SlugTPM, \"TPM\", tpm.Firmware, softwareInventory)\n\n\t\tdevice.TPMs = append(device.TPMs, tpm)\n\t}\n\n\treturn nil\n}\n\n// collectNICs collects network interface component information\nfunc (c *Client) collectNICs(sys *schemas.ComputerSystem, device *common.Device, softwareInventory []*schemas.SoftwareInventory) (err error) {\n\tif sys == nil || device == nil {\n\t\treturn nil\n\t}\n\n\t// collect network interface information\n\tnics, err := sys.NetworkInterfaces()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// collect network ethernet interface information, these attributes are not available in NetworkAdapter, NetworkInterfaces\n\tethernetInterfaces, err := sys.EthernetInterfaces()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, nic := range nics {\n\t\t// collect network interface adaptor information\n\t\tadapter, err := nic.NetworkAdapter()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif adapter == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tn := &common.NIC{\n\t\t\tCommon: common.Common{\n\t\t\t\tVendor:      common.FormatVendorName(adapter.Manufacturer),\n\t\t\t\tModel:       adapter.Model,\n\t\t\t\tSerial:      adapter.SerialNumber,\n\t\t\t\tProductName: adapter.PartNumber,\n\t\t\t\tStatus: &common.Status{\n\t\t\t\t\tState:  string(nic.Status.State),\n\t\t\t\t\tHealth: string(nic.Status.Health),\n\t\t\t\t},\n\t\t\t},\n\n\t\t\tID: nic.ID, // \"Id\": \"NIC.Slot.3\",\n\t\t}\n\n\t\tports, err := adapter.NetworkPorts()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tportFirmwareVersion := getFirmwareVersionFromController(adapter.Controllers, len(ports))\n\n\t\tfor _, networkPort := range ports {\n\n\t\t\t// populate network ports general data\n\t\t\tnicPort := &common.NICPort{}\n\t\t\tc.collectNetworkPortInfo(nicPort, adapter, networkPort, portFirmwareVersion, softwareInventory)\n\n\t\t\tif networkPort.ActiveLinkTechnology == schemas.EthernetLinkNetworkTechnology {\n\t\t\t\t// ethernet specific data\n\t\t\t\tc.collectEthernetInfo(nicPort, ethernetInterfaces)\n\t\t\t}\n\t\t\tn.NICPorts = append(n.NICPorts, nicPort)\n\t\t}\n\n\t\t// include additional firmware attributes from redfish firmware inventory\n\t\tc.firmwareAttributes(common.SlugNIC, n.ID, n.Firmware, softwareInventory)\n\t\tif len(portFirmwareVersion) > 0 {\n\t\t\tif n.Firmware == nil {\n\t\t\t\tn.Firmware = &common.Firmware{}\n\t\t\t}\n\t\t\tn.Firmware.Installed = portFirmwareVersion\n\t\t}\n\n\t\tdevice.NICs = append(device.NICs, n)\n\t}\n\n\treturn nil\n}\n\nfunc (c *Client) collectNetworkPortInfo(\n\tnicPort *common.NICPort,\n\tadapter *schemas.NetworkAdapter,\n\tnetworkPort *schemas.NetworkPort,\n\tfirmware string,\n\tsoftwareInventory []*schemas.SoftwareInventory,\n) {\n\n\tif adapter != nil {\n\t\tnicPort.Vendor = adapter.Manufacturer\n\t\tnicPort.Model = adapter.Model\n\t}\n\n\tif networkPort != nil {\n\n\t\tnicPort.Description = networkPort.Description\n\t\tnicPort.PCIVendorID = networkPort.VendorID\n\t\tnicPort.Status = &common.Status{\n\t\t\tHealth: string(networkPort.Status.Health),\n\t\t\tState:  string(networkPort.Status.State),\n\t\t}\n\t\tnicPort.ID = networkPort.ID\n\t\tnicPort.PhysicalID = networkPort.PhysicalPortNumber\n\t\tnicPort.LinkStatus = string(networkPort.LinkStatus)\n\t\tnicPort.ActiveLinkTechnology = string(networkPort.ActiveLinkTechnology)\n\n\t\tif networkPort.CurrentLinkSpeedMbps != nil {\n\t\t\tnicPort.SpeedBits = int64(gofish.Deref(networkPort.CurrentLinkSpeedMbps)) * int64(math.Pow10(6))\n\t\t}\n\n\t\tif len(networkPort.AssociatedNetworkAddresses) > 0 {\n\t\t\tfor _, macAddress := range networkPort.AssociatedNetworkAddresses {\n\t\t\t\tif len(macAddress) > 0 && macAddress != \"00:00:00:00:00:00\" {\n\t\t\t\t\tnicPort.MacAddress = macAddress // first valid value only\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tc.firmwareAttributes(common.SlugNIC, networkPort.ID, nicPort.Firmware, softwareInventory)\n\t}\n\tif len(firmware) > 0 {\n\t\tif nicPort.Firmware == nil {\n\t\t\tnicPort.Firmware = &common.Firmware{}\n\t\t}\n\t\tnicPort.Firmware.Installed = firmware\n\t}\n}\n\nfunc (c *Client) collectEthernetInfo(nicPort *common.NICPort, ethernetInterfaces []*schemas.EthernetInterface) {\n\tif nicPort == nil {\n\t\treturn\n\t}\n\n\t// populate mac address et al. from matching ethernet interface\n\tfor _, ethInterface := range ethernetInterfaces {\n\t\t// the ethernet interface includes the port, position number and function NIC.Slot.3-1-1\n\t\tif !strings.HasPrefix(ethInterface.ID, nicPort.ID) {\n\t\t\tcontinue\n\t\t}\n\n\t\t// override values only if needed\n\t\tif len(ethInterface.Description) > 0 {\n\t\t\tnicPort.Description = ethInterface.Description\n\t\t}\n\t\tif len(ethInterface.Status.Health) > 0 {\n\t\t\tif nicPort.Status == nil {\n\t\t\t\tnicPort.Status = &common.Status{}\n\t\t\t}\n\t\t\tnicPort.Status.Health = string(ethInterface.Status.Health)\n\t\t}\n\t\tif len(ethInterface.Status.State) > 0 {\n\t\t\tif nicPort.Status == nil {\n\t\t\t\tnicPort.Status = &common.Status{}\n\t\t\t}\n\t\t\tnicPort.Status.State = string(ethInterface.Status.State)\n\t\t}\n\t\tnicPort.ID = ethInterface.ID // override ID\n\t\tif ethInterface.SpeedMbps != nil {\n\t\t\tnicPort.SpeedBits = int64(gofish.Deref(ethInterface.SpeedMbps)) * int64(math.Pow10(6))\n\t\t}\n\n\t\tnicPort.AutoNeg = ethInterface.AutoNeg\n\t\tnicPort.MTUSize = gofish.Deref(ethInterface.MTUSize)\n\n\t\t// always override mac address\n\t\tnicPort.MacAddress = ethInterface.MACAddress\n\t\tbreak // stop at first match\n\t}\n}\n\nfunc getFirmwareVersionFromController(controllers []schemas.Controllers, portCount int) string {\n\tfor _, controller := range controllers {\n\t\tif gofish.Deref(controller.ControllerCapabilities.NetworkPortCount) == portCount {\n\t\t\treturn controller.FirmwarePackageVersion\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc (c *Client) collectBIOS(sys *schemas.ComputerSystem, device *common.Device, softwareInventory []*schemas.SoftwareInventory) (err error) {\n\tdevice.BIOS = &common.BIOS{\n\t\tCommon: common.Common{\n\t\t\tFirmware: &common.Firmware{\n\t\t\t\tInstalled: sys.BiosVersion,\n\t\t\t},\n\t\t},\n\t}\n\n\tbios, err := sys.Bios()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif bios != nil {\n\t\tdevice.BIOS.Description = bios.Description\n\t}\n\n\t// include additional firmware attributes from redfish firmware inventory\n\tc.firmwareAttributes(common.SlugBIOS, \"BIOS\", device.BIOS.Firmware, softwareInventory)\n\n\treturn nil\n}\n\n// collectDrives collects drive component information\nfunc (c *Client) collectDrives(sys *schemas.ComputerSystem, device *common.Device, softwareInventory []*schemas.SoftwareInventory) (err error) {\n\tstorage, err := sys.Storage()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, member := range storage {\n\t\tif member.DrivesCount == 0 {\n\t\t\tcontinue\n\t\t}\n\n\t\tdrives, err := member.Drives()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, drive := range drives {\n\t\t\td := &common.Drive{\n\t\t\t\tCommon: common.Common{\n\t\t\t\t\tProductName: drive.Model,\n\t\t\t\t\tDescription: drive.Description,\n\t\t\t\t\tSerial:      drive.SerialNumber,\n\t\t\t\t\tVendor:      common.FormatVendorName(drive.Manufacturer),\n\t\t\t\t\tModel:       drive.Model,\n\t\t\t\t\tFirmware: &common.Firmware{\n\t\t\t\t\t\tInstalled: drive.Revision,\n\t\t\t\t\t},\n\t\t\t\t\tStatus: &common.Status{\n\t\t\t\t\t\tHealth: string(drive.Status.Health),\n\t\t\t\t\t\tState:  string(drive.Status.State),\n\t\t\t\t\t},\n\t\t\t\t},\n\n\t\t\t\tID:                  drive.ID,\n\t\t\t\tType:                string(drive.MediaType),\n\t\t\t\tStorageController:   member.ID,\n\t\t\t\tProtocol:            string(drive.Protocol),\n\t\t\t\tCapacityBytes:       int64(gofish.Deref(drive.CapacityBytes)),\n\t\t\t\tCapableSpeedGbps:    int64(gofish.Deref(drive.CapableSpeedGbs)),\n\t\t\t\tNegotiatedSpeedGbps: int64(gofish.Deref(drive.NegotiatedSpeedGbs)),\n\t\t\t\tBlockSizeBytes:      int64(gofish.Deref(drive.BlockSizeBytes)),\n\t\t\t}\n\n\t\t\t// include additional firmware attributes from redfish firmware inventory\n\t\t\tc.firmwareAttributes(\"Disk\", drive.ID, d.Firmware, softwareInventory)\n\n\t\t\tdevice.Drives = append(device.Drives, d)\n\n\t\t}\n\n\t}\n\n\treturn nil\n}\n\n// collectStorageControllers populates the device with Storage controller component attributes\nfunc (c *Client) collectStorageControllers(sys *schemas.ComputerSystem, device *common.Device, softwareInventory []*schemas.SoftwareInventory) (err error) {\n\tstorage, err := sys.Storage()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, member := range storage {\n\t\tfor _, controller := range member.StorageControllers { //nolint:staticcheck\n\n\t\t\tcs := &common.StorageController{\n\t\t\t\tCommon: common.Common{\n\t\t\t\t\tDescription: controller.Name,\n\t\t\t\t\tVendor:      common.FormatVendorName(controller.Manufacturer),\n\t\t\t\t\tModel:       controller.PartNumber,\n\t\t\t\t\tSerial:      controller.SerialNumber,\n\t\t\t\t\tStatus: &common.Status{\n\t\t\t\t\t\tHealth: string(controller.Status.Health),\n\t\t\t\t\t\tState:  string(controller.Status.State),\n\t\t\t\t\t},\n\t\t\t\t\tFirmware: &common.Firmware{\n\t\t\t\t\t\tInstalled: controller.FirmwareVersion,\n\t\t\t\t\t},\n\t\t\t\t},\n\n\t\t\t\tID:        controller.ID,\n\t\t\t\tSpeedGbps: int64(gofish.Deref(controller.SpeedGbps)),\n\t\t\t}\n\n\t\t\t// In some cases the storage controller model number is present in the Name field\n\t\t\tif strings.TrimSpace(cs.Model) == \"\" && strings.TrimSpace(controller.Name) != \"\" {\n\t\t\t\tcs.Model = controller.Name\n\t\t\t}\n\n\t\t\t// include additional firmware attributes from redfish firmware inventory\n\t\t\tc.firmwareAttributes(cs.Description, cs.ID, cs.Firmware, softwareInventory)\n\n\t\t\tdevice.StorageControllers = append(device.StorageControllers, cs)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// collectCPUs populates the device with CPU component attributes\nfunc (c *Client) collectCPUs(sys *schemas.ComputerSystem, device *common.Device, _ []*schemas.SoftwareInventory) (err error) {\n\tprocs, err := sys.Processors()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, proc := range procs {\n\t\tif proc.ProcessorType != \"CPU\" {\n\t\t\t// TODO: handle this case\n\t\t\tcontinue\n\t\t}\n\n\t\tdevice.CPUs = append(device.CPUs, &common.CPU{\n\t\t\tCommon: common.Common{\n\t\t\t\tDescription: proc.Description,\n\t\t\t\tVendor:      common.FormatVendorName(proc.Manufacturer),\n\t\t\t\tModel:       proc.Model,\n\t\t\t\tSerial:      \"\",\n\t\t\t\tStatus: &common.Status{\n\t\t\t\t\tHealth: string(proc.Status.Health),\n\t\t\t\t\tState:  string(proc.Status.State),\n\t\t\t\t},\n\t\t\t\tFirmware: &common.Firmware{\n\t\t\t\t\tInstalled: proc.ProcessorID.MicrocodeInfo,\n\t\t\t\t},\n\t\t\t},\n\t\t\tID:           proc.ID,\n\t\t\tArchitecture: string(proc.ProcessorArchitecture),\n\t\t\tSlot:         proc.Socket,\n\t\t\tClockSpeedHz: int64(gofish.Deref(proc.MaxSpeedMHz) * 1000 * 1000),\n\t\t\tCores:        gofish.Deref(proc.TotalCores),\n\t\t\tThreads:      gofish.Deref(proc.TotalThreads),\n\t\t})\n\t}\n\n\treturn nil\n}\n\n// collectDIMMs populates the device with memory component attributes\nfunc (c *Client) collectDIMMs(sys *schemas.ComputerSystem, device *common.Device, softwareInventory []*schemas.SoftwareInventory) (err error) {\n\tdimms, err := sys.Memory()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, dimm := range dimms {\n\t\tdevice.Memory = append(device.Memory, &common.Memory{\n\t\t\tCommon: common.Common{\n\t\t\t\tDescription: dimm.Description,\n\t\t\t\tVendor:      common.FormatVendorName(dimm.Manufacturer),\n\t\t\t\tModel:       \"\",\n\t\t\t\tSerial:      dimm.SerialNumber,\n\t\t\t\tStatus: &common.Status{\n\t\t\t\t\tHealth: string(dimm.Status.Health),\n\t\t\t\t\tState:  string(dimm.Status.State),\n\t\t\t\t},\n\t\t\t},\n\n\t\t\tSlot:         dimm.ID,\n\t\t\tType:         string(dimm.MemoryType),\n\t\t\tSizeBytes:    int64(gofish.Deref(dimm.VolatileSizeMiB) * 1024 * 1024),\n\t\t\tFormFactor:   \"\",\n\t\t\tPartNumber:   dimm.PartNumber,\n\t\t\tClockSpeedHz: int64(gofish.Deref(dimm.OperatingSpeedMhz) * 1000 * 1000),\n\t\t})\n\t}\n\n\treturn nil\n}\n\n// collecCPLDs populates the device with CPLD component attributes\nfunc (c *Client) collectCPLDs(device *common.Device, softwareInventory []*schemas.SoftwareInventory) (err error) {\n\n\tcpld := &common.CPLD{\n\t\tCommon: common.Common{\n\t\t\tVendor:   common.FormatVendorName(device.Vendor),\n\t\t\tModel:    device.Model,\n\t\t\tFirmware: &common.Firmware{Metadata: make(map[string]string)},\n\t\t},\n\t}\n\n\tc.firmwareAttributes(common.SlugCPLD, \"\", cpld.Firmware, softwareInventory)\n\tname, exists := cpld.Firmware.Metadata[\"name\"]\n\tif exists {\n\t\tcpld.Description = name\n\t}\n\n\tdevice.CPLDs = []*common.CPLD{cpld}\n\n\treturn nil\n}\n"
  },
  {
    "path": "internal/redfishwrapper/inventory_collect_test.go",
    "content": "package redfishwrapper\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/bmc-toolbox/common\"\n\t\"github.com/stmcginnis/gofish\"\n\t\"github.com/stmcginnis/gofish/schemas\"\n)\n\nfunc TestInventoryCollectNetworkPortInfo(t *testing.T) {\n\ttestAdapter := &schemas.NetworkAdapter{\n\t\tManufacturer: \"Acme\",\n\t\tModel:        \"Anvil 3000\",\n\t}\n\ttestNetworkPort := &schemas.NetworkPort{\n\t\tEntity:                     schemas.Entity{ID: \"NetworkPort-1\"},\n\t\tVendorID:                   \"Vendor-ID\",\n\t\tPhysicalPortNumber:         \"10\",\n\t\tLinkStatus:                 \"Up\",\n\t\tActiveLinkTechnology:       \"Ethernet\",\n\t\tCurrentLinkSpeedMbps:       gofish.ToRef(1000),\n\t\tAssociatedNetworkAddresses: []string{\"98:E7:43:00:01:0A\"},\n\t}\n\ttestFirmwareVersion := \"1.2.3\"\n\n\twNicPortOnlyAdapter := &common.NICPort{Common: common.Common{Vendor: testAdapter.Manufacturer, Model: testAdapter.Model}}\n\twNicPortOnlyNPort := &common.NICPort{\n\t\tCommon: common.Common{\n\t\t\tDescription: testNetworkPort.Description,\n\t\t\tPCIVendorID: testNetworkPort.VendorID,\n\t\t\tStatus: &common.Status{\n\t\t\t\tHealth: string(testNetworkPort.Status.Health),\n\t\t\t\tState:  string(testNetworkPort.Status.State),\n\t\t\t},\n\t\t},\n\t\tID:                   testNetworkPort.ID,\n\t\tPhysicalID:           testNetworkPort.PhysicalPortNumber,\n\t\tLinkStatus:           string(testNetworkPort.LinkStatus),\n\t\tActiveLinkTechnology: string(testNetworkPort.ActiveLinkTechnology),\n\t\tSpeedBits:            1000000000,\n\t\tMacAddress:           testNetworkPort.AssociatedNetworkAddresses[0],\n\t}\n\twNicPortOnlyFirmware := &common.NICPort{Common: common.Common{Firmware: &common.Firmware{Installed: testFirmwareVersion}}}\n\twNicPortFull := &common.NICPort{\n\t\tCommon: common.Common{\n\t\t\tDescription: testNetworkPort.Description,\n\t\t\tVendor:      testAdapter.Manufacturer,\n\t\t\tModel:       testAdapter.Model,\n\t\t\tPCIVendorID: testNetworkPort.VendorID,\n\t\t\tFirmware:    &common.Firmware{Installed: testFirmwareVersion},\n\t\t\tStatus: &common.Status{\n\t\t\t\tHealth: string(testNetworkPort.Status.Health),\n\t\t\t\tState:  string(testNetworkPort.Status.State),\n\t\t\t},\n\t\t},\n\t\tID:                   testNetworkPort.ID,\n\t\tPhysicalID:           testNetworkPort.PhysicalPortNumber,\n\t\tLinkStatus:           string(testNetworkPort.LinkStatus),\n\t\tActiveLinkTechnology: string(testNetworkPort.ActiveLinkTechnology),\n\t\tSpeedBits:            1000000000,\n\t\tMacAddress:           testNetworkPort.AssociatedNetworkAddresses[0],\n\t}\n\n\ttests := []struct {\n\t\tname          string\n\t\tnicPort       *common.NICPort\n\t\tadapter       *schemas.NetworkAdapter\n\t\tnetworkPort   *schemas.NetworkPort\n\t\tfirmware      string\n\t\twantedNicPort *common.NICPort\n\t}{\n\t\t{name: \"nil\"},\n\t\t{name: \"empty\", nicPort: &common.NICPort{}, wantedNicPort: &common.NICPort{}},\n\t\t{\n\t\t\tname:          \"only adapter\",\n\t\t\tnicPort:       &common.NICPort{},\n\t\t\tadapter:       testAdapter,\n\t\t\twantedNicPort: wNicPortOnlyAdapter,\n\t\t},\n\t\t{\n\t\t\tname:          \"only network port\",\n\t\t\tnicPort:       &common.NICPort{},\n\t\t\tnetworkPort:   testNetworkPort,\n\t\t\twantedNicPort: wNicPortOnlyNPort,\n\t\t},\n\t\t{\n\t\t\tname:          \"only firmware\",\n\t\t\tnicPort:       &common.NICPort{},\n\t\t\tfirmware:      testFirmwareVersion,\n\t\t\twantedNicPort: wNicPortOnlyFirmware,\n\t\t},\n\t\t{\n\t\t\tname:          \"full\",\n\t\t\tnicPort:       &common.NICPort{},\n\t\t\tadapter:       testAdapter,\n\t\t\tnetworkPort:   testNetworkPort,\n\t\t\tfirmware:      testFirmwareVersion,\n\t\t\twantedNicPort: wNicPortFull,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tc := Client{}\n\t\t\tc.collectNetworkPortInfo(tt.nicPort, tt.adapter, tt.networkPort, tt.firmware, []*schemas.SoftwareInventory{})\n\t\t\tif !reflect.DeepEqual(tt.nicPort, tt.wantedNicPort) {\n\t\t\t\tt.Errorf(\"collectNetworkPortInfo() gotNicPort = %v, want %v\", tt.nicPort, tt.wantedNicPort)\n\t\t\t}\n\t\t})\n\t}\n\n}\n\nfunc TestInventoryCollectEthernetInfo(t *testing.T) {\n\ttestNicPortID := \"test NIC port ID\"\n\ttestEthernetID := \"test NIC port ID ethernet\"\n\ttestNicPort := &common.NICPort{\n\t\tID: testNicPortID,\n\t}\n\ttestUnmatchingEthList := []*schemas.EthernetInterface{\n\t\t{Entity: schemas.Entity{ID: \"other ID\"}},\n\t\t{Entity: schemas.Entity{ID: \"another one\"}},\n\t}\n\ttestMatchingEth := &schemas.EthernetInterface{\n\t\tEntity: schemas.Entity{ID: testEthernetID},\n\t\tStatus: schemas.Status{\n\t\t\tHealth: \"OK\",\n\t\t\tState:  \"Enabled\",\n\t\t},\n\t\tSpeedMbps:  gofish.ToRef(10000),\n\t\tAutoNeg:    true,\n\t\tMTUSize:    gofish.ToRef(1500),\n\t\tMACAddress: \"f6:a9:26:e3:e6:32\",\n\t}\n\ttestMatchingEthList := append(testUnmatchingEthList, testMatchingEth)\n\n\twNicPortFull := &common.NICPort{\n\t\tCommon: common.Common{\n\t\t\tDescription: testMatchingEth.Description,\n\t\t\tStatus: &common.Status{\n\t\t\t\tHealth: string(testMatchingEth.Status.Health),\n\t\t\t\tState:  string(testMatchingEth.Status.State),\n\t\t\t},\n\t\t},\n\t\tID:         testMatchingEth.ID,\n\t\tSpeedBits:  10000000000,\n\t\tAutoNeg:    testMatchingEth.AutoNeg,\n\t\tMTUSize:    gofish.Deref(testMatchingEth.MTUSize),\n\t\tMacAddress: testMatchingEth.MACAddress,\n\t}\n\n\ttests := []struct {\n\t\tname               string\n\t\tnicPort            *common.NICPort\n\t\tethernetInterfaces []*schemas.EthernetInterface\n\t\twantedNicPort      *common.NICPort\n\t}{\n\t\t{name: \"nil\"},\n\t\t{name: \"empty\", nicPort: testNicPort, wantedNicPort: testNicPort},\n\t\t{name: \"empty ethernet list\", nicPort: testNicPort, ethernetInterfaces: []*schemas.EthernetInterface{}, wantedNicPort: testNicPort},\n\t\t{name: \"unmatching ethernet list\", nicPort: testNicPort, ethernetInterfaces: testUnmatchingEthList, wantedNicPort: testNicPort},\n\t\t{\n\t\t\tname:               \"full\",\n\t\t\tnicPort:            testNicPort,\n\t\t\tethernetInterfaces: testMatchingEthList,\n\t\t\twantedNicPort:      wNicPortFull},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tc := Client{}\n\t\t\tc.collectEthernetInfo(tt.nicPort, tt.ethernetInterfaces)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/redfishwrapper/main_test.go",
    "content": "package redfishwrapper\n\nimport (\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"testing\"\n)\n\nfunc mustReadFile(t *testing.T, filename string) []byte {\n\tt.Helper()\n\n\tfixture := fixturesDir + \"/\" + filename\n\tfh, err := os.Open(fixture)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tdefer fh.Close()\n\n\tb, err := io.ReadAll(fh)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\treturn b\n}\n\nvar endpointFunc = func(t *testing.T, file string) http.HandlerFunc {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tif file == \"404\" {\n\t\t\tw.WriteHeader(http.StatusNotFound)\n\t\t}\n\n\t\t// expect either GET or Delete methods\n\t\tif r.Method != http.MethodGet && r.Method != http.MethodDelete {\n\t\t\tw.WriteHeader(http.StatusNotFound)\n\t\t\treturn\n\t\t}\n\n\t\t_, _ = w.Write(mustReadFile(t, file))\n\t}\n}\n"
  },
  {
    "path": "internal/redfishwrapper/power.go",
    "content": "package redfishwrapper\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/stmcginnis/gofish/schemas\"\n)\n\n// PowerSet sets the power state of a server\nfunc (c *Client) PowerSet(ctx context.Context, state string) (ok bool, err error) {\n\t// TODO: create consts for the state values\n\tswitch strings.ToLower(state) {\n\tcase \"on\":\n\t\treturn c.SystemPowerOn(ctx)\n\tcase \"off\":\n\t\treturn c.SystemForceOff(ctx)\n\tcase \"soft\":\n\t\treturn c.SystemPowerOff(ctx)\n\tcase \"reset\":\n\t\treturn c.SystemReset(ctx)\n\tcase \"cycle\":\n\t\treturn c.SystemPowerCycle(ctx)\n\tdefault:\n\t\treturn false, errors.New(\"unknown power action\")\n\t}\n}\n\n// BMCReset powercycles the BMC.\nfunc (c *Client) BMCReset(ctx context.Context, resetType string) (ok bool, err error) {\n\tif err := c.SessionActive(); err != nil {\n\t\treturn false, errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error())\n\t}\n\n\tmanager, err := c.Manager(ctx)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif _, err = manager.Reset(schemas.ResetType(resetType)); err != nil {\n\t\treturn false, err\n\t}\n\n\treturn true, nil\n}\n\n// SystemPowerOn powers on the system.\nfunc (c *Client) SystemPowerOn(ctx context.Context) (ok bool, err error) {\n\tif err := c.SessionActive(); err != nil {\n\t\treturn false, errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error())\n\t}\n\n\tsystem, err := c.System()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif system.PowerState == schemas.OnPowerState {\n\t\treturn true, nil\n\t}\n\n\tsystem.DisableEtagMatch(c.disableEtagMatch)\n\tif _, err = system.Reset(schemas.OnResetType); err != nil {\n\t\treturn false, err\n\t}\n\n\treturn true, nil\n}\n\n// SystemPowerOff powers off the system.\nfunc (c *Client) SystemPowerOff(ctx context.Context) (ok bool, err error) {\n\tif err := c.SessionActive(); err != nil {\n\t\treturn false, errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error())\n\t}\n\n\tsystem, err := c.System()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif system.PowerState == schemas.OffPowerState {\n\t\treturn true, nil\n\t}\n\n\tsystem.DisableEtagMatch(c.disableEtagMatch)\n\n\tif _, err = system.Reset(schemas.GracefulShutdownResetType); err != nil {\n\t\treturn false, err\n\t}\n\n\treturn false, nil\n}\n\n// SystemReset power cycles the system.\nfunc (c *Client) SystemReset(ctx context.Context) (ok bool, err error) {\n\tif err := c.SessionActive(); err != nil {\n\t\treturn false, errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error())\n\t}\n\n\tsystem, err := c.System()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tsystem.DisableEtagMatch(c.disableEtagMatch)\n\tif _, err = system.Reset(schemas.PowerCycleResetType); err != nil {\n\t\t_, _ = c.SystemPowerOff(ctx)\n\n\t\tfor wait := 1; wait < 10; wait++ {\n\t\t\tstatus, _ := c.SystemPowerStatus(ctx)\n\t\t\tif status == \"off\" {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t}\n\n\t\treturn c.SystemPowerOn(ctx)\n\t}\n\n\treturn true, nil\n}\n\n// SystemPowerCycle power cycles the system.\nfunc (c *Client) SystemPowerCycle(ctx context.Context) (ok bool, err error) {\n\tif err := c.SessionActive(); err != nil {\n\t\treturn false, errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error())\n\t}\n\n\tsystem, err := c.System()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif system.PowerState == schemas.OffPowerState {\n\t\treturn false, fmt.Errorf(\"power cycle failed: Command not supported in present state: %v\", system.PowerState)\n\t}\n\n\tsystem.DisableEtagMatch(c.disableEtagMatch)\n\n\tif _, err = system.Reset(schemas.ForceRestartResetType); err != nil {\n\t\treturn false, errors.WithMessage(err, \"power cycle failed\")\n\t}\n\n\treturn true, nil\n}\n\n// SystemPowerStatus returns the system power state.\nfunc (c *Client) SystemPowerStatus(ctx context.Context) (result string, err error) {\n\tif err := c.SessionActive(); err != nil {\n\t\treturn result, errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error())\n\t}\n\n\tsystem, err := c.System()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn string(system.PowerState), nil\n}\n\n// SystemForceOff powers off the system, without waiting for the OS to shutdown.\nfunc (c *Client) SystemForceOff(ctx context.Context) (ok bool, err error) {\n\tif err := c.SessionActive(); err != nil {\n\t\treturn false, errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error())\n\t}\n\n\tsystem, err := c.System()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif system.PowerState == schemas.OffPowerState {\n\t\treturn true, nil\n\t}\n\n\tsystem.DisableEtagMatch(c.disableEtagMatch)\n\n\tif _, err = system.Reset(schemas.ForceOffResetType); err != nil {\n\t\treturn false, err\n\t}\n\n\treturn true, nil\n}\n\n// SendNMI tells the BMC to issue an NMI to the device\nfunc (c *Client) SendNMI(_ context.Context) error {\n\tif err := c.SessionActive(); err != nil {\n\t\treturn errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error())\n\t}\n\n\tsystem, err := c.System()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = system.Reset(schemas.NmiResetType)\n\treturn err\n}\n"
  },
  {
    "path": "internal/redfishwrapper/sel.go",
    "content": "package redfishwrapper\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/stmcginnis/gofish/schemas\"\n)\n\n// ClearSystemEventLog clears all of the LogServices logs\nfunc (c *Client) ClearSystemEventLog(ctx context.Context) (err error) {\n\tif err := c.SessionActive(); err != nil {\n\t\treturn errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error())\n\t}\n\n\tchassis, err := c.client.Service.Chassis()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, c := range chassis {\n\t\tlogServices, err := c.LogServices()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tfor _, logService := range logServices {\n\t\t\t_, err = logService.ClearLog(\"\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// GetSystemEventLog returns the SystemEventLogEntries\nfunc (c *Client) GetSystemEventLog(ctx context.Context) (entries [][]string, err error) {\n\tif err := c.SessionActive(); err != nil {\n\t\treturn nil, errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error())\n\t}\n\n\tmanagers, err := c.client.Service.Managers()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, m := range managers {\n\t\tlogServices, err := m.LogServices()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tfor _, logService := range logServices {\n\t\t\tlentries, err := logService.Entries()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\tfor _, entry := range lentries {\n\t\t\t\tentries = append(entries, []string{\n\t\t\t\t\tentry.ID,\n\t\t\t\t\tentry.Created,\n\t\t\t\t\tentry.Description,\n\t\t\t\t\tentry.Message,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\treturn entries, nil\n}\n\n// GetSystemEventLogRaw returns the raw SEL\nfunc (c *Client) GetSystemEventLogRaw(ctx context.Context) (eventlog string, err error) {\n\tvar allEntries []*schemas.LogEntry\n\n\tif err := c.SessionActive(); err != nil {\n\t\treturn \"\", errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error())\n\t}\n\n\tmanagers, err := c.client.Service.Managers()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tfor _, m := range managers {\n\t\tlogServices, err := m.LogServices()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tfor _, logService := range logServices {\n\t\t\tlentries, err := logService.Entries()\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\n\t\t\tallEntries = append(allEntries, lentries...)\n\t\t}\n\t}\n\n\trawEntries, err := json.Marshal(allEntries)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn string(rawEntries), nil\n}\n"
  },
  {
    "path": "internal/redfishwrapper/system.go",
    "content": "package redfishwrapper\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/stmcginnis/gofish/schemas\"\n)\n\n// The methods here should be a thin wrapper so as to only guard the client from authentication failures.\n\n// AccountService gets the Redfish AccountService.d\nfunc (c *Client) AccountService() (*schemas.AccountService, error) {\n\tif err := c.SessionActive(); err != nil {\n\t\treturn nil, errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error())\n\t}\n\n\treturn c.client.Service.AccountService()\n}\n\n// UpdateService gets the update service instance.\nfunc (c *Client) UpdateService() (*schemas.UpdateService, error) {\n\tif err := c.SessionActive(); err != nil {\n\t\treturn nil, errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error())\n\t}\n\n\treturn c.client.Service.UpdateService()\n}\n\n// System gets the system matching c.systemName or when c.systemName is not set\n// and there is only one system it returns that system.\nfunc (c *Client) System() (*schemas.ComputerSystem, error) {\n\tif err := c.SessionActive(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tsystems, err := c.client.Service.Systems()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// If no system name is set and there is only one system, return it.\n\t// This is to handle backwards compatibility where we didn't require passing\n\t// a system name to the client.\n\tif c.systemName == \"\" && len(systems) == 1 && systems[0] != nil {\n\t\treturn systems[0], nil\n\t}\n\n\treturn c.matchingSystem(systems)\n}\n\n// Manager gets the manager instances of this service. It matches the manager\n// to the system name if one is set in the client. If no system name is set\n// and there is only one manager it returns that manager. Otherwise it returns\n// an error.\nfunc (c *Client) Manager(ctx context.Context) (*schemas.Manager, error) {\n\tif err := c.SessionActive(); err != nil {\n\t\treturn nil, errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error())\n\t}\n\n\tms, err := c.client.Service.Managers()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// If no system name is set and there is only one manager, return it.\n\t// This is to handle backwards compatibility where we didn't require passing\n\t// a system name to the client.\n\tif c.systemName == \"\" && len(ms) == 1 && ms[0] != nil {\n\t\treturn ms[0], nil\n\t}\n\n\tfor _, m := range ms {\n\t\tsys, err := m.ManagerForServers()\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, err := c.matchingSystem(sys); err == nil {\n\t\t\treturn m, nil\n\t\t}\n\t}\n\n\treturn nil, fmt.Errorf(\"no matching redfish manager found for system: %s\", c.systemName)\n}\n\nfunc (c *Client) Managers(ctx context.Context) ([]*schemas.Manager, error) {\n\tif err := c.SessionActive(); err != nil {\n\t\treturn nil, errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error())\n\t}\n\n\treturn c.client.Service.Managers()\n}\n\n// Chassis gets the chassis instances managed by this service.\nfunc (c *Client) Chassis(ctx context.Context) ([]*schemas.Chassis, error) {\n\tif err := c.SessionActive(); err != nil {\n\t\treturn nil, errors.Wrap(bmclibErrs.ErrNotAuthenticated, err.Error())\n\t}\n\n\treturn c.client.Service.Chassis()\n}\n\nfunc (c *Client) matchingSystem(systems []*schemas.ComputerSystem) (*schemas.ComputerSystem, error) {\n\tfor _, s := range systems {\n\t\tif s != nil && s.Name == c.systemName {\n\t\t\treturn s, nil\n\t\t}\n\t}\n\n\treturn nil, fmt.Errorf(\"no matching redfish system found for system: %s\", c.systemName)\n}\n"
  },
  {
    "path": "internal/redfishwrapper/system_test.go",
    "content": "package redfishwrapper\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stmcginnis/gofish/schemas\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestMatchingSystem(t *testing.T) {\n\ttests := map[string]struct {\n\t\tclient       *Client\n\t\tsystems      []*schemas.ComputerSystem\n\t\texpectErr    bool\n\t\texpectSystem *schemas.ComputerSystem\n\t}{\n\t\t\"finds matching system by name\": {\n\t\t\tclient: &Client{\n\t\t\t\tsystemName: \"System1\",\n\t\t\t},\n\t\t\tsystems: []*schemas.ComputerSystem{\n\t\t\t\t{\n\t\t\t\t\tEntity: schemas.Entity{\n\t\t\t\t\t\tName: \"System1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tEntity: schemas.Entity{\n\t\t\t\t\t\tName: \"System2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectErr: false,\n\t\t\texpectSystem: &schemas.ComputerSystem{\n\t\t\t\tEntity: schemas.Entity{\n\t\t\t\t\tName: \"System1\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t\"no matching system found\": {\n\t\t\tclient: &Client{\n\t\t\t\tsystemName: \"NonExistent\",\n\t\t\t},\n\t\t\tsystems: []*schemas.ComputerSystem{\n\t\t\t\t{\n\t\t\t\t\tEntity: schemas.Entity{\n\t\t\t\t\t\tName: \"System1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectErr:    true,\n\t\t\texpectSystem: nil,\n\t\t},\n\t\t\"empty systems list\": {\n\t\t\tclient: &Client{\n\t\t\t\tsystemName: \"System1\",\n\t\t\t},\n\t\t\tsystems:      []*schemas.ComputerSystem{},\n\t\t\texpectErr:    true,\n\t\t\texpectSystem: nil,\n\t\t},\n\t\t\"system name empty\": {\n\t\t\tclient: &Client{\n\t\t\t\tsystemName: \"\",\n\t\t\t},\n\t\t\tsystems: []*schemas.ComputerSystem{\n\t\t\t\t{\n\t\t\t\t\tEntity: schemas.Entity{\n\t\t\t\t\t\tName: \"System1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tEntity: schemas.Entity{\n\t\t\t\t\t\tName: \"System2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectErr:    true,\n\t\t\texpectSystem: nil,\n\t\t},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tresult, err := tc.client.matchingSystem(tc.systems)\n\t\t\tif tc.expectErr {\n\t\t\t\tassert.Error(t, err)\n\t\t\t} else {\n\t\t\t\tassert.NoError(t, err)\n\t\t\t\tassert.Equal(t, tc.expectSystem, result)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/redfishwrapper/task.go",
    "content": "package redfishwrapper\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/constants\"\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/stmcginnis/gofish/schemas\"\n)\n\nvar (\n\terrUnexpectedTaskState = errors.New(\"unexpected task state\")\n)\n\nfunc (c *Client) Task(ctx context.Context, taskID string) (*schemas.Task, error) {\n\ttasks, err := c.Tasks(ctx)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"error querying redfish tasks\")\n\t}\n\n\tfor _, t := range tasks {\n\t\tif t.ID != taskID {\n\t\t\tcontinue\n\t\t}\n\n\t\treturn t, nil\n\t}\n\n\treturn nil, bmclibErrs.ErrTaskNotFound\n}\n\nfunc (c *Client) TaskStatus(ctx context.Context, taskID string) (constants.TaskState, string, error) {\n\ttask, err := c.Task(ctx, taskID)\n\tif err != nil {\n\t\treturn \"\", \"\", errors.Wrap(err, \"error querying redfish for taskID: \"+taskID)\n\t}\n\n\ttaskInfo := fmt.Sprintf(\n\t\t\"id: %s, state: %s, status: %s\",\n\t\ttask.ID,\n\t\ttask.TaskState,\n\t\ttask.TaskStatus,\n\t)\n\n\t// task message include information that help debug a cause of failure\n\tif msgs := c.taskMessagesAsString(task.Messages); msgs != \"\" {\n\t\ttaskInfo += \", messages: \" + msgs\n\t}\n\n\ts := c.ConvertTaskState(string(task.TaskState))\n\treturn s, taskInfo, nil\n}\n\nfunc (c *Client) taskMessagesAsString(messages []schemas.Message) string {\n\tif len(messages) == 0 {\n\t\treturn \"\"\n\t}\n\n\tvar found []string\n\tfor _, m := range messages {\n\t\tif m.Message == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tfound = append(found, m.Message)\n\t}\n\n\treturn strings.Join(found, \",\")\n}\n\nfunc (c *Client) ConvertTaskState(state string) constants.TaskState {\n\tswitch strings.ToLower(state) {\n\tcase \"starting\", \"downloading\", \"downloaded\", \"scheduling\":\n\t\treturn constants.Initializing\n\tcase \"running\", \"stopping\", \"cancelling\":\n\t\treturn constants.Running\n\tcase \"pending\", \"new\":\n\t\treturn constants.Queued\n\tcase \"scheduled\":\n\t\treturn constants.PowerCycleHost\n\tcase \"interrupted\", \"killed\", \"exception\", \"cancelled\", \"suspended\", \"failed\":\n\t\treturn constants.Failed\n\tcase \"completed\":\n\t\treturn constants.Complete\n\tdefault:\n\t\treturn constants.Unknown\n\t}\n}\n\nfunc (c *Client) TaskStateActive(state constants.TaskState) (bool, error) {\n\tswitch state {\n\tcase constants.Initializing, constants.Running, constants.Queued:\n\t\treturn true, nil\n\tcase constants.Complete, constants.Failed:\n\t\treturn false, nil\n\tdefault:\n\t\treturn false, errors.Wrap(errUnexpectedTaskState, string(state))\n\t}\n}\n"
  },
  {
    "path": "internal/redfishwrapper/task_test.go",
    "content": "package redfishwrapper\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"testing\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/constants\"\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestConvertTaskState(t *testing.T) {\n\ttestCases := []struct {\n\t\ttestName string\n\t\tstate    string\n\t\texpected constants.TaskState\n\t}{\n\t\t{\"starting state\", \"starting\", constants.Initializing},\n\t\t{\"downloading state\", \"downloading\", constants.Initializing},\n\t\t{\"downloaded state\", \"downloaded\", constants.Initializing},\n\t\t{\"scheduling state\", \"scheduling\", constants.Initializing},\n\t\t{\"running state\", \"running\", constants.Running},\n\t\t{\"stopping state\", \"stopping\", constants.Running},\n\t\t{\"cancelling state\", \"cancelling\", constants.Running},\n\t\t{\"pending state\", \"pending\", constants.Queued},\n\t\t{\"new state\", \"new\", constants.Queued},\n\t\t{\"scheduled state\", \"scheduled\", constants.PowerCycleHost},\n\t\t{\"interrupted state\", \"interrupted\", constants.Failed},\n\t\t{\"killed state\", \"killed\", constants.Failed},\n\t\t{\"exception state\", \"exception\", constants.Failed},\n\t\t{\"cancelled state\", \"cancelled\", constants.Failed},\n\t\t{\"suspended state\", \"suspended\", constants.Failed},\n\t\t{\"failed state\", \"failed\", constants.Failed},\n\t\t{\"completed state\", \"completed\", constants.Complete},\n\t\t{\"unknown state\", \"unknown_state\", constants.Unknown},\n\t}\n\n\tclient := Client{}\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\tresult := client.ConvertTaskState(tc.state)\n\t\t\tassert.Equal(t, tc.expected, result)\n\t\t})\n\t}\n}\n\nfunc TestTaskStateActive(t *testing.T) {\n\ttestCases := []struct {\n\t\ttestName  string\n\t\ttaskState constants.TaskState\n\t\texpected  bool\n\t\terr       error\n\t}{\n\t\t{\"active initializing\", constants.Initializing, true, nil},\n\t\t{\"active running\", constants.Running, true, nil},\n\t\t{\"active queued\", constants.Queued, true, nil},\n\t\t{\"inactive complete\", constants.Complete, false, nil},\n\t\t{\"inactive failed\", constants.Failed, false, nil},\n\t\t{\"unknown state\", \"foobar\", false, errUnexpectedTaskState},\n\t}\n\n\tclient := &Client{}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\tactive, err := client.TaskStateActive(tc.taskState)\n\n\t\t\tif tc.err != nil {\n\t\t\t\tassert.ErrorIs(t, err, tc.err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tassert.Equal(t, tc.expected, active)\n\t\t})\n\t}\n}\n\nfunc TestTaskStatus(t *testing.T) {\n\ttype hmap map[string]func(http.ResponseWriter, *http.Request)\n\twithHandler := func(s string, f func(http.ResponseWriter, *http.Request)) hmap {\n\t\treturn hmap{\n\t\t\t\"/redfish/v1/\":                  endpointFunc(t, \"serviceroot.json\"),\n\t\t\t\"/redfish/v1/Systems\":           endpointFunc(t, \"systems.json\"),\n\t\t\t\"/redfish/v1/TaskService\":       endpointFunc(t, \"taskservice.json\"),\n\t\t\t\"/redfish/v1/TaskService/Tasks\": endpointFunc(t, \"tasks.json\"),\n\t\t\t//\t\t\t\"/redfish/v1/TaskService/Tasks/1\": endpointFunc(t, \"tasks_1.json\"),\n\t\t\t//\t\t\t\"/redfish/v1/TaskService/Tasks/2\": endpointFunc(t, \"tasks_2.json\"),\n\t\t\ts: f,\n\t\t}\n\t}\n\n\ttests := map[string]struct {\n\t\thmap           hmap\n\t\texpectedState  constants.TaskState\n\t\texpectedStatus string\n\t\texpectedErr    error\n\t}{\n\t\t\"task in Initializing state\": {\n\t\t\thmap: withHandler(\n\t\t\t\t\"/redfish/v1/TaskService/Tasks/1\",\n\t\t\t\tendpointFunc(t, \"tasks/tasks_1_starting.json\"),\n\t\t\t),\n\t\t\texpectedState:  constants.Initializing,\n\t\t\texpectedStatus: \"id: 1, state: Starting, status: OK\",\n\t\t\texpectedErr:    nil,\n\t\t},\n\t\t\"task in Running state\": {\n\t\t\thmap: withHandler(\n\t\t\t\t\"/redfish/v1/TaskService/Tasks/1\",\n\t\t\t\tendpointFunc(t, \"tasks/tasks_1_running.json\"),\n\t\t\t),\n\t\t\texpectedState:  constants.Running,\n\t\t\texpectedStatus: \"id: 1, state: Running, status: OK\",\n\t\t\texpectedErr:    nil,\n\t\t},\n\t\t\"task in Queued state\": {\n\t\t\thmap: withHandler(\n\t\t\t\t\"/redfish/v1/TaskService/Tasks/1\",\n\t\t\t\tendpointFunc(t, \"tasks/tasks_1_pending.json\"),\n\t\t\t),\n\t\t\texpectedState:  constants.Queued,\n\t\t\texpectedStatus: \"id: 1, state: Pending, status: OK\",\n\t\t\texpectedErr:    nil,\n\t\t},\n\t\t\"task in PowerCycleHost state\": {\n\t\t\thmap: withHandler(\n\t\t\t\t\"/redfish/v1/TaskService/Tasks/1\",\n\t\t\t\tendpointFunc(t, \"tasks/tasks_1_scheduled.json\"),\n\t\t\t),\n\t\t\texpectedState:  constants.PowerCycleHost,\n\t\t\texpectedStatus: \"id: 1, state: Scheduled, status: OK\",\n\t\t\texpectedErr:    nil,\n\t\t},\n\t\t\"task in Failed state\": {\n\t\t\thmap: withHandler(\n\t\t\t\t\"/redfish/v1/TaskService/Tasks/1\",\n\t\t\t\tendpointFunc(t, \"tasks/tasks_1_failed.json\"),\n\t\t\t),\n\t\t\texpectedState:  constants.Failed,\n\t\t\texpectedStatus: \"id: 1, state: Failed, status: OK\",\n\t\t\texpectedErr:    nil,\n\t\t},\n\t\t\"task in Complete state\": {\n\t\t\thmap: withHandler(\n\t\t\t\t\"/redfish/v1/TaskService/Tasks/1\",\n\t\t\t\tendpointFunc(t, \"tasks/tasks_1_completed.json\"),\n\t\t\t),\n\t\t\texpectedState:  constants.Complete,\n\t\t\texpectedStatus: \"id: 1, state: Completed, status: OK\",\n\t\t\texpectedErr:    nil,\n\t\t},\n\t\t\"unknown task state\": {\n\t\t\thmap: withHandler(\n\t\t\t\t\"/redfish/v1/TaskService/Tasks/1\",\n\t\t\t\tendpointFunc(t, \"tasks/tasks_1_unknown.json\"),\n\t\t\t),\n\t\t\texpectedState:  constants.Unknown,\n\t\t\texpectedStatus: \"id: 1, state: foobared, status: OK\",\n\t\t\texpectedErr:    nil,\n\t\t},\n\t\t\"failure case - no task found\": {\n\t\t\thmap: hmap{\n\t\t\t\t\"/redfish/v1/\":                  endpointFunc(t, \"serviceroot.json\"),\n\t\t\t\t\"/redfish/v1/Systems\":           endpointFunc(t, \"systems.json\"),\n\t\t\t\t\"/redfish/v1/TaskService\":       endpointFunc(t, \"taskservice.json\"),\n\t\t\t\t\"/redfish/v1/TaskService/Tasks\": endpointFunc(t, \"tasks.json\"),\n\t\t\t},\n\t\t\texpectedState:  \"\",\n\t\t\texpectedStatus: \"\",\n\t\t\texpectedErr:    bmclibErrs.ErrTaskNotFound,\n\t\t},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tmux := http.NewServeMux()\n\n\t\t\tfor endpoint, handler := range tc.hmap {\n\t\t\t\tmux.HandleFunc(endpoint, handler)\n\t\t\t}\n\n\t\t\tserver := httptest.NewTLSServer(mux)\n\t\t\tdefer server.Close()\n\n\t\t\tparsedURL, err := url.Parse(server.URL)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tctx := context.Background()\n\n\t\t\tclient := NewClient(parsedURL.Hostname(), parsedURL.Port(), \"\", \"\", WithBasicAuthEnabled(true))\n\n\t\t\terr = client.Open(ctx)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tstate, status, err := client.TaskStatus(ctx, \"1\")\n\t\t\tif tc.expectedErr != nil {\n\t\t\t\tassert.ErrorContains(t, err, tc.expectedErr.Error())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tassert.Nil(t, err)\n\t\t\tassert.Equal(t, tc.expectedState, state)\n\t\t\tassert.Equal(t, tc.expectedStatus, status)\n\n\t\t\tclient.Close(context.Background())\n\t\t})\n\t}\n}\n\nfunc TestTask(t *testing.T) {\n\ttype hmap map[string]func(http.ResponseWriter, *http.Request)\n\thandlers := func() hmap {\n\t\treturn hmap{\n\t\t\t\"/redfish/v1/\":                    endpointFunc(t, \"serviceroot.json\"),\n\t\t\t\"/redfish/v1/Systems\":             endpointFunc(t, \"systems.json\"),\n\t\t\t\"/redfish/v1/TaskService\":         endpointFunc(t, \"taskservice.json\"),\n\t\t\t\"/redfish/v1/TaskService/Tasks\":   endpointFunc(t, \"tasks.json\"),\n\t\t\t\"/redfish/v1/TaskService/Tasks/1\": endpointFunc(t, \"/tasks/tasks_1_completed.json\"),\n\t\t\t\"/redfish/v1/TaskService/Tasks/2\": endpointFunc(t, \"/tasks/tasks_2.json\"),\n\t\t}\n\t}\n\n\ttests := map[string]struct {\n\t\thandlers         hmap\n\t\ttaskID           string\n\t\texpectTaskStatus string\n\t\texpectTaskState  string\n\t\terr              error\n\t}{\n\t\t\"happy case - task 1\": {\n\t\t\thandlers:         handlers(),\n\t\t\ttaskID:           \"1\",\n\t\t\texpectTaskStatus: \"OK\",\n\t\t\texpectTaskState:  \"Completed\",\n\t\t\terr:              nil,\n\t\t},\n\t\t\"happy case - task 2\": {\n\t\t\thandlers:         handlers(),\n\t\t\ttaskID:           \"2\",\n\t\t\texpectTaskStatus: \"OK\",\n\t\t\texpectTaskState:  \"Completed\",\n\t\t\terr:              nil,\n\t\t},\n\t\t\"failure case - no task found\": {\n\t\t\thandlers: handlers(),\n\t\t\ttaskID:   \"3\",\n\t\t\terr:      bmclibErrs.ErrTaskNotFound,\n\t\t},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tmux := http.NewServeMux()\n\n\t\t\tfor endpoint, handler := range tc.handlers {\n\t\t\t\tmux.HandleFunc(endpoint, handler)\n\t\t\t}\n\n\t\t\tserver := httptest.NewTLSServer(mux)\n\t\t\tdefer server.Close()\n\n\t\t\tparsedURL, err := url.Parse(server.URL)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tctx := context.Background()\n\n\t\t\t//os.Setenv(\"DEBUG_BMCLIB\", \"true\")\n\t\t\tclient := NewClient(parsedURL.Hostname(), parsedURL.Port(), \"\", \"\", WithBasicAuthEnabled(true))\n\n\t\t\terr = client.Open(ctx)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tgot, err := client.Task(ctx, tc.taskID)\n\t\t\tif tc.err != nil {\n\t\t\t\tassert.ErrorContains(t, err, tc.err.Error())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tassert.Nil(t, err)\n\t\t\tassert.NotNil(t, got)\n\t\t\tassert.Equal(t, tc.expectTaskStatus, string(got.TaskStatus))\n\t\t\tassert.Equal(t, tc.expectTaskState, string(got.TaskState))\n\n\t\t\tclient.Close(context.Background())\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "internal/redfishwrapper/virtual_media.go",
    "content": "package redfishwrapper\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"slices\"\n\n\t\"github.com/stmcginnis/gofish\"\n\t\"github.com/stmcginnis/gofish/schemas\"\n)\n\n// getVirtualMedia retrieves virtual media resources by first checking the\n// Redfish Manager path and falling back to the System path if none are found.\n//\n// Some BMC implementations (e.g., Dell iDRAC) expose VirtualMedia under the\n// System resource (/redfish/v1/Systems/{SystemId}/VirtualMedia) rather than\n// the Manager resource (/redfish/v1/Managers/{ManagerId}/VirtualMedia).\n// Both locations are valid per the Redfish specification.\nfunc (c *Client) getVirtualMedia(ctx context.Context) ([]*schemas.VirtualMedia, error) {\n\t// Try Manager path first (standard Redfish location).\n\tm, err := c.Manager(ctx)\n\tif err == nil {\n\t\tvm, err := m.VirtualMedia()\n\t\tif err == nil && len(vm) > 0 {\n\t\t\treturn vm, nil\n\t\t}\n\t}\n\n\t// Fallback to System path (Dell iDRAC and other implementations that\n\t// expose VirtualMedia under ComputerSystem per Redfish spec v1.12.0+).\n\tsys, err := c.System()\n\tif err == nil {\n\t\tvm, err := sys.VirtualMedia()\n\t\tif err == nil && len(vm) > 0 {\n\t\t\treturn vm, nil\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"no virtual media found at Manager or System resource paths\")\n}\n\n// SetVirtualMedia sets virtual media on the system. If mediaURL is empty,\n// matching media may be ejected. When multiple matching virtual media slots\n// exist, each slot is tried in order until one succeeds.\nfunc (c *Client) SetVirtualMedia(ctx context.Context, kind string, mediaURL string) (bool, error) {\n\tvar mediaKind schemas.VirtualMediaType\n\n\tswitch kind {\n\tcase \"CD\":\n\t\tmediaKind = schemas.CDVirtualMediaType\n\tcase \"Floppy\":\n\t\tmediaKind = schemas.FloppyVirtualMediaType\n\tcase \"USBStick\":\n\t\tmediaKind = schemas.USBStickVirtualMediaType\n\tcase \"DVD\":\n\t\tmediaKind = schemas.DVDVirtualMediaType\n\tdefault:\n\t\treturn false, errors.New(\"invalid media type\")\n\t}\n\n\tvirtualMedia, err := c.getVirtualMedia(ctx)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tsupportedMediaTypes := []string{}\n\tvar slotErrors []error\n\n\tfor _, vm := range virtualMedia {\n\t\tif !slices.Contains(vm.MediaTypes, mediaKind) {\n\t\t\tfor _, mt := range vm.MediaTypes {\n\t\t\t\tsupportedMediaTypes = append(supportedMediaTypes, string(mt))\n\t\t\t}\n\n\t\t\tcontinue\n\t\t}\n\n\t\tif mediaURL == \"\" {\n\t\t\t// Only ejecting the media was requested.\n\t\t\tif *vm.Inserted && vm.SupportsMediaEject {\n\t\t\t\tif _, err := vm.EjectMedia(); err != nil {\n\t\t\t\t\tslotErrors = append(slotErrors, fmt.Errorf(\"%s: eject: %w\", vm.ODataID, err))\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn true, nil\n\t\t}\n\n\t\t// Ejecting the media before inserting new media makes the success rate of inserting the new media higher.\n\t\tif *vm.Inserted && vm.SupportsMediaEject {\n\t\t\tif _, err := vm.EjectMedia(); err != nil {\n\t\t\t\tslotErrors = append(slotErrors, fmt.Errorf(\"%s: eject before insert: %w\", vm.ODataID, err))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\tif !vm.SupportsMediaInsert {\n\t\t\tslotErrors = append(slotErrors, fmt.Errorf(\"%s: does not support insert\", vm.ODataID))\n\t\t\tcontinue\n\t\t}\n\n\t\tinsertMedia := schemas.VirtualMediaInsertMediaParameters{\n\t\t\tImage:          mediaURL,\n\t\t\tInserted:       gofish.ToRef(true),\n\t\t\tWriteProtected: gofish.ToRef(true),\n\t\t}\n\t\tif _, err := vm.InsertMedia(&insertMedia); err != nil {\n\t\t\t// Some BMCs (e.g., Supermicro X11SDV-4C-TLN2F) don't support the\n\t\t\t// Inserted and WriteProtected properties, so retry without them.\n\t\t\tinsertMedia = schemas.VirtualMediaInsertMediaParameters{Image: mediaURL}\n\t\t\tif _, err := vm.InsertMedia(&insertMedia); err != nil {\n\t\t\t\tslotErrors = append(slotErrors, fmt.Errorf(\"%s: insert: %w\", vm.ODataID, err))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\n\t\treturn true, nil\n\t}\n\n\tif len(slotErrors) > 0 {\n\t\treturn false, fmt.Errorf(\"all matching virtual media slots failed: %w\", errors.Join(slotErrors...))\n\t}\n\n\treturn false, fmt.Errorf(\"not a supported media type: %s. supported media types: %v\", kind, supportedMediaTypes)\n}\n\nfunc (c *Client) InsertedVirtualMedia(ctx context.Context) ([]string, error) {\n\tvirtualMedia, err := c.getVirtualMedia(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar inserted []string\n\n\tfor _, media := range virtualMedia {\n\t\tif *media.Inserted {\n\t\t\tinserted = append(inserted, media.ID)\n\t\t}\n\t}\n\n\treturn inserted, nil\n}\n"
  },
  {
    "path": "internal/redfishwrapper/virtual_media_test.go",
    "content": "package redfishwrapper\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestGetVirtualMedia(t *testing.T) {\n\ttests := map[string]struct {\n\t\thfunc       map[string]func(http.ResponseWriter, *http.Request)\n\t\tbasicAuth   bool\n\t\texpectCount int\n\t\texpectErr   string\n\t}{\n\t\t\"manager path has virtual media\": {\n\t\t\t// Standard case: VirtualMedia is found under Manager (e.g., HP iLO, Supermicro)\n\t\t\thfunc: map[string]func(http.ResponseWriter, *http.Request){\n\t\t\t\t\"/redfish/v1/\":                        endpointFunc(t, \"serviceroot.json\"),\n\t\t\t\t\"/redfish/v1/Managers\":                endpointFunc(t, \"managers.json\"),\n\t\t\t\t\"/redfish/v1/Managers/1\":              endpointFunc(t, \"managers_1.json\"),\n\t\t\t\t\"/redfish/v1/Managers/1/VirtualMedia\": endpointFunc(t, \"404\"),\n\t\t\t\t\"/redfish/v1/Systems\":                 endpointFunc(t, \"systems.json\"),\n\t\t\t\t\"/redfish/v1/Systems/1\":               endpointFunc(t, \"systems_1.json\"),\n\t\t\t},\n\t\t\t// managers_1.json has a VirtualMedia link, but our mock returns 404 for the collection.\n\t\t\t// This means Manager path returns 0 items, so fallback to System path.\n\t\t\t// systems_1.json doesn't have VirtualMedia link, so both fail.\n\t\t\texpectCount: 0,\n\t\t\texpectErr:   \"no virtual media found\",\n\t\t},\n\t\t\"dell idrac - system path has virtual media\": {\n\t\t\t// Dell iDRAC case: Manager has no VirtualMedia, System has VirtualMedia\n\t\t\thfunc: map[string]func(http.ResponseWriter, *http.Request){\n\t\t\t\t\"/redfish/v1/\":                                         endpointFunc(t, \"dell/serviceroot.json\"),\n\t\t\t\t\"/redfish/v1/Managers\":                                 endpointFunc(t, \"dell/managers.json\"),\n\t\t\t\t\"/redfish/v1/Managers/iDRAC.Embedded.1\":                endpointFunc(t, \"dell/manager.idrac.embedded.1.json\"),\n\t\t\t\t\"/redfish/v1/Systems\":                                  endpointFunc(t, \"dell/systems.json\"),\n\t\t\t\t\"/redfish/v1/Systems/System.Embedded.1\":                endpointFunc(t, \"dell/system.embedded.1.virtualmedia.json\"),\n\t\t\t\t\"/redfish/v1/Systems/System.Embedded.1/VirtualMedia\":   endpointFunc(t, \"dell/virtualmedia_collection.json\"),\n\t\t\t\t\"/redfish/v1/Systems/System.Embedded.1/VirtualMedia/1\": endpointFunc(t, \"dell/virtualmedia_1.json\"),\n\t\t\t\t\"/redfish/v1/Systems/System.Embedded.1/VirtualMedia/2\": endpointFunc(t, \"dell/virtualmedia_2.json\"),\n\t\t\t},\n\t\t\tbasicAuth:   true,\n\t\t\texpectCount: 2,\n\t\t\texpectErr:   \"\",\n\t\t},\n\t\t\"no virtual media anywhere\": {\n\t\t\t// Neither Manager nor System exposes VirtualMedia\n\t\t\thfunc: map[string]func(http.ResponseWriter, *http.Request){\n\t\t\t\t\"/redfish/v1/\":                          endpointFunc(t, \"dell/serviceroot.json\"),\n\t\t\t\t\"/redfish/v1/Managers\":                  endpointFunc(t, \"dell/managers.json\"),\n\t\t\t\t\"/redfish/v1/Managers/iDRAC.Embedded.1\": endpointFunc(t, \"dell/manager.idrac.embedded.1.json\"),\n\t\t\t\t\"/redfish/v1/Systems\":                   endpointFunc(t, \"dell/systems.json\"),\n\t\t\t\t\"/redfish/v1/Systems/System.Embedded.1\": endpointFunc(t, \"dell/system.embedded.1.json\"),\n\t\t\t},\n\t\t\tbasicAuth:   true,\n\t\t\texpectCount: 0,\n\t\t\texpectErr:   \"no virtual media found\",\n\t\t},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tmux := http.NewServeMux()\n\t\t\tfor endpoint, handler := range tc.hfunc {\n\t\t\t\tmux.HandleFunc(endpoint, handler)\n\t\t\t}\n\n\t\t\tserver := httptest.NewTLSServer(mux)\n\t\t\tdefer server.Close()\n\n\t\t\tparsedURL, err := url.Parse(server.URL)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tctx := context.Background()\n\n\t\t\topts := []Option{}\n\t\t\tif tc.basicAuth {\n\t\t\t\topts = append(opts, WithBasicAuthEnabled(true))\n\t\t\t}\n\n\t\t\tclient := NewClient(parsedURL.Hostname(), parsedURL.Port(), \"\", \"\", opts...)\n\n\t\t\terr = client.Open(ctx)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tdefer client.Close(ctx)\n\n\t\t\tvm, err := client.getVirtualMedia(ctx)\n\t\t\tif tc.expectErr != \"\" {\n\t\t\t\tassert.Error(t, err)\n\t\t\t\tassert.Contains(t, err.Error(), tc.expectErr)\n\t\t\t\tassert.Nil(t, vm)\n\t\t\t} else {\n\t\t\t\tassert.NoError(t, err)\n\t\t\t\tassert.Len(t, vm, tc.expectCount)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSetVirtualMedia_DellSystemPath(t *testing.T) {\n\t// Test that SetVirtualMedia works with Dell iDRAC where VirtualMedia\n\t// is only available under the System resource path.\n\t// We test ejection (empty mediaURL) which only requires GET operations\n\t// and validates the full System path fallback flow.\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/redfish/v1/\", endpointFunc(t, \"dell/serviceroot.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Managers\", endpointFunc(t, \"dell/managers.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Managers/iDRAC.Embedded.1\", endpointFunc(t, \"dell/manager.idrac.embedded.1.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Systems\", endpointFunc(t, \"dell/systems.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Systems/System.Embedded.1\", endpointFunc(t, \"dell/system.embedded.1.virtualmedia.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Systems/System.Embedded.1/VirtualMedia\", endpointFunc(t, \"dell/virtualmedia_collection.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Systems/System.Embedded.1/VirtualMedia/1\", endpointFunc(t, \"dell/virtualmedia_1.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Systems/System.Embedded.1/VirtualMedia/2\", endpointFunc(t, \"dell/virtualmedia_2.json\"))\n\n\tserver := httptest.NewTLSServer(mux)\n\tdefer server.Close()\n\n\tparsedURL, err := url.Parse(server.URL)\n\trequire.NoError(t, err)\n\n\tctx := context.Background()\n\n\tclient := NewClient(parsedURL.Hostname(), parsedURL.Port(), \"\", \"\", WithBasicAuthEnabled(true))\n\n\terr = client.Open(ctx)\n\trequire.NoError(t, err)\n\n\tdefer client.Close(ctx)\n\n\t// Test ejecting CD media via System path (empty URL = eject).\n\t// VirtualMedia fixtures have Inserted: false, so eject is a no-op success.\n\tok, err := client.SetVirtualMedia(ctx, \"CD\", \"\")\n\tassert.NoError(t, err)\n\tassert.True(t, ok)\n}\n\nfunc TestInsertedVirtualMedia_DellSystemPath(t *testing.T) {\n\t// Test that InsertedVirtualMedia works when VirtualMedia is only\n\t// available under the System resource path (Dell iDRAC).\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/redfish/v1/\", endpointFunc(t, \"dell/serviceroot.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Managers\", endpointFunc(t, \"dell/managers.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Managers/iDRAC.Embedded.1\", endpointFunc(t, \"dell/manager.idrac.embedded.1.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Systems\", endpointFunc(t, \"dell/systems.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Systems/System.Embedded.1\", endpointFunc(t, \"dell/system.embedded.1.virtualmedia.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Systems/System.Embedded.1/VirtualMedia\", endpointFunc(t, \"dell/virtualmedia_collection.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Systems/System.Embedded.1/VirtualMedia/1\", endpointFunc(t, \"dell/virtualmedia_1.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Systems/System.Embedded.1/VirtualMedia/2\", endpointFunc(t, \"dell/virtualmedia_2.json\"))\n\n\tserver := httptest.NewTLSServer(mux)\n\tdefer server.Close()\n\n\tparsedURL, err := url.Parse(server.URL)\n\trequire.NoError(t, err)\n\n\tctx := context.Background()\n\n\tclient := NewClient(parsedURL.Hostname(), parsedURL.Port(), \"\", \"\", WithBasicAuthEnabled(true))\n\n\terr = client.Open(ctx)\n\trequire.NoError(t, err)\n\n\tdefer client.Close(ctx)\n\n\t// Both VirtualMedia instances have Inserted: false, so should return empty\n\tinserted, err := client.InsertedVirtualMedia(ctx)\n\tassert.NoError(t, err)\n\tassert.Empty(t, inserted)\n}\n\nfunc TestSetVirtualMedia_SlotFallback(t *testing.T) {\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/redfish/v1/\", endpointFunc(t, \"dell/serviceroot.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Managers\", endpointFunc(t, \"dell/managers.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Managers/iDRAC.Embedded.1\", endpointFunc(t, \"dell/manager.idrac.embedded.1.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Systems\", endpointFunc(t, \"dell/systems.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Systems/System.Embedded.1\", endpointFunc(t, \"dell/system.embedded.1.virtualmedia.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Systems/System.Embedded.1/VirtualMedia\", endpointFunc(t, \"dell/virtualmedia_collection.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Systems/System.Embedded.1/VirtualMedia/1\", endpointFunc(t, \"dell/virtualmedia_1.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Systems/System.Embedded.1/VirtualMedia/2\", endpointFunc(t, \"dell/virtualmedia_2.json\"))\n\t// VirtualMedia/2 (first in collection) rejects InsertMedia with 500.\n\tmux.HandleFunc(\"/redfish/v1/Systems/System.Embedded.1/VirtualMedia/2/Actions/VirtualMedia.InsertMedia\",\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t},\n\t)\n\t// VirtualMedia/1 (second in collection) accepts InsertMedia.\n\tmux.HandleFunc(\"/redfish/v1/Systems/System.Embedded.1/VirtualMedia/1/Actions/VirtualMedia.InsertMedia\",\n\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.WriteHeader(http.StatusNoContent)\n\t\t},\n\t)\n\n\tserver := httptest.NewTLSServer(mux)\n\tdefer server.Close()\n\n\tparsedURL, err := url.Parse(server.URL)\n\trequire.NoError(t, err)\n\n\tctx := context.Background()\n\n\tclient := NewClient(parsedURL.Hostname(), parsedURL.Port(), \"\", \"\", WithBasicAuthEnabled(true))\n\n\terr = client.Open(ctx)\n\trequire.NoError(t, err)\n\n\tdefer client.Close(ctx)\n\n\tok, err := client.SetVirtualMedia(ctx, \"CD\", \"http://example.com/boot.iso\")\n\tassert.NoError(t, err)\n\tassert.True(t, ok)\n}\n\nfunc TestSetVirtualMedia_InvalidMediaType(t *testing.T) {\n\t// Test that invalid media type returns an error before any Redfish calls\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/redfish/v1/\", endpointFunc(t, \"serviceroot.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Managers\", endpointFunc(t, \"managers.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Managers/1\", endpointFunc(t, \"managers_1.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Systems\", endpointFunc(t, \"systems.json\"))\n\tmux.HandleFunc(\"/redfish/v1/Systems/1\", endpointFunc(t, \"systems_1.json\"))\n\n\tserver := httptest.NewTLSServer(mux)\n\tdefer server.Close()\n\n\tparsedURL, err := url.Parse(server.URL)\n\trequire.NoError(t, err)\n\n\tctx := context.Background()\n\n\tclient := NewClient(parsedURL.Hostname(), parsedURL.Port(), \"\", \"\")\n\n\terr = client.Open(ctx)\n\trequire.NoError(t, err)\n\n\tdefer client.Close(ctx)\n\n\tok, err := client.SetVirtualMedia(ctx, \"InvalidType\", \"http://example.com/boot.iso\")\n\tassert.Error(t, err)\n\tassert.False(t, ok)\n\tassert.Contains(t, err.Error(), \"invalid media type\")\n}\n"
  },
  {
    "path": "internal/sum/sum.go",
    "content": "package sum\n\n// SUM is Supermicro Update Manager\n// https://www.supermicro.com/en/solutions/management-software/supermicro-update-manager\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\tex \"github.com/bmc-toolbox/bmclib/v2/internal/executor\"\n\n\t\"github.com/bmc-toolbox/common\"\n\t\"github.com/bmc-toolbox/common/config\"\n\t\"github.com/go-logr/logr\"\n)\n\n// Sum is a sum command executor object\ntype Sum struct {\n\tExecutor ex.Executor\n\tSumPath  string\n\tLog      logr.Logger\n\tHost     string\n\tUsername string\n\tPassword string\n}\n\n// Option for setting optional Client values\ntype Option func(*Sum)\n\nfunc WithSumPath(sumPath string) Option {\n\treturn func(c *Sum) {\n\t\tc.SumPath = sumPath\n\t}\n}\n\nfunc WithLogger(log logr.Logger) Option {\n\treturn func(c *Sum) {\n\t\tc.Log = log\n\t}\n}\n\nfunc New(host, user, pass string, opts ...Option) (*Sum, error) {\n\tsum := &Sum{\n\t\tHost:     host,\n\t\tUsername: user,\n\t\tPassword: pass,\n\t\tLog:      logr.Discard(),\n\t}\n\n\tfor _, opt := range opts {\n\t\topt(sum)\n\t}\n\n\tvar err error\n\n\tif sum.SumPath == \"\" {\n\t\tsum.SumPath, err = exec.LookPath(\"sum\")\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\tif _, err = os.Stat(sum.SumPath); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\te := ex.NewExecutor(sum.SumPath)\n\te.SetEnv([]string{\"LC_ALL=C.UTF-8\"})\n\tsum.Executor = e\n\n\treturn sum, nil\n}\n\n// Open a connection to a BMC\nfunc (c *Sum) Open(ctx context.Context) (err error) {\n\treturn nil\n}\n\n// Close a connection to a BMC\nfunc (c *Sum) Close(ctx context.Context) (err error) {\n\treturn nil\n}\n\nfunc (s *Sum) run(ctx context.Context, command string, additionalArgs ...string) (output string, err error) {\n\t// TODO(splaspood) use a tmp file here (as sum supports) to read the password\n\tsumArgs := []string{\"-i\", s.Host, \"-u\", s.Username, \"-p\", s.Password, \"-c\", command}\n\tsumArgs = append(sumArgs, additionalArgs...)\n\n\ts.Log.V(9).WithValues(\n\t\t\"sumArgs\",\n\t\tsumArgs,\n\t).Info(\"Calling sum\")\n\n\ts.Executor.SetArgs(sumArgs)\n\n\tresult, err := s.Executor.ExecWithContext(ctx)\n\tif err != nil {\n\t\treturn string(result.Stderr), err\n\t}\n\n\treturn string(result.Stdout), err\n}\n\nfunc (s *Sum) GetCurrentBiosCfg(ctx context.Context) (output string, err error) {\n\treturn s.run(ctx, \"GetCurrentBiosCfg\")\n}\n\nfunc (s *Sum) LoadDefaultBiosCfg(ctx context.Context) (err error) {\n\t_, err = s.run(ctx, \"LoadDefaultBiosCfg\")\n\treturn err\n}\n\nfunc (s *Sum) ChangeBiosCfg(ctx context.Context, cfgFile string, reboot bool) (err error) {\n\targs := []string{\"--file\", cfgFile}\n\n\tif reboot {\n\t\targs = append(args, \"--reboot\")\n\t}\n\n\t_, err = s.run(ctx, \"ChangeBiosCfg\", args...)\n\n\treturn err\n}\n\n// GetBiosConfiguration return bios configuration\nfunc (s *Sum) GetBiosConfiguration(ctx context.Context) (biosConfig map[string]string, err error) {\n\tbiosText, err := s.GetCurrentBiosCfg(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// We need to call vcm here to take the XML returned by SUM and convert it into a simple map\n\tvcm, err := config.NewVendorConfigManager(\"xml\", common.VendorSupermicro, map[string]string{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = vcm.Unmarshal(biosText)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbiosConfig, err = vcm.StandardConfig()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn biosConfig, nil\n}\n\n// SetBiosConfiguration set bios configuration\nfunc (s *Sum) SetBiosConfiguration(ctx context.Context, biosConfig map[string]string) (err error) {\n\tvcm, err := config.NewVendorConfigManager(\"xml\", common.VendorSupermicro, map[string]string{})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor k, v := range biosConfig {\n\t\tswitch {\n\t\tcase k == \"boot_mode\":\n\t\t\tif err = vcm.BootMode(v); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase k == \"boot_order\":\n\t\t\tif err = vcm.BootOrder(v); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase k == \"intel_sgx\":\n\t\t\tif err = vcm.IntelSGX(v); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase k == \"secure_boot\":\n\t\t\tswitch v {\n\t\t\tcase \"Enabled\":\n\t\t\t\tif err = vcm.SecureBoot(true); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\tcase \"Disabled\":\n\t\t\t\tif err = vcm.SecureBoot(false); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\tcase k == \"tpm\":\n\t\t\tswitch v {\n\t\t\tcase \"Enabled\":\n\t\t\t\tif err = vcm.TPM(true); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\tcase \"Disabled\":\n\t\t\t\tif err = vcm.TPM(false); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\tcase k == \"smt\":\n\t\t\tswitch v {\n\t\t\tcase \"Enabled\":\n\t\t\t\tif err = vcm.SMT(true); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\tcase \"Disabled\":\n\t\t\t\tif err = vcm.SMT(false); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\tcase k == \"sr_iov\":\n\t\t\tswitch v {\n\t\t\tcase \"Enabled\":\n\t\t\t\tif err = vcm.SRIOV(true); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\tcase \"Disabled\":\n\t\t\t\tif err = vcm.SRIOV(false); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\tcase strings.HasPrefix(k, \"raw:\"):\n\t\t\t// k = raw:Menu1,SubMenu1,SubMenuMenu1,SettingName\n\t\t\tpathStr := strings.TrimPrefix(k, \"raw:\")\n\t\t\tpath := strings.Split(pathStr, \",\")\n\t\t\tname := path[len(path)-1]\n\t\t\tpath = path[:len(path)-1]\n\n\t\t\tvcm.Raw(name, v, path)\n\t\t}\n\t}\n\n\txmlData, err := vcm.Marshal()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn s.SetBiosConfigurationFromFile(ctx, xmlData)\n}\n\nfunc (s *Sum) SetBiosConfigurationFromFile(ctx context.Context, cfg string) (err error) {\n\t// Open tmp file to hold cfg\n\tinputConfigTmpFile, err := os.CreateTemp(\"\", \"bmclib\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer os.Remove(inputConfigTmpFile.Name())\n\n\t_, err = inputConfigTmpFile.WriteString(cfg)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = inputConfigTmpFile.Close()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn s.ChangeBiosCfg(ctx, inputConfigTmpFile.Name(), true)\n}\n\n// ResetBiosConfiguration reset bios configuration\nfunc (s *Sum) ResetBiosConfiguration(ctx context.Context) (err error) {\n\treturn s.LoadDefaultBiosCfg(ctx)\n}\n"
  },
  {
    "path": "internal/sum/sum_test.go",
    "content": "package sum\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"testing\"\n\n\tex \"github.com/bmc-toolbox/bmclib/v2/internal/executor\"\n)\n\nfunc newFakeSum(t *testing.T, fixtureName string) *Sum {\n\te := &Sum{\n\t\tExecutor: ex.NewFakeExecutor(\"sum\"),\n\t}\n\n\tb, err := os.ReadFile(\"../../fixtures/internal/sum/\" + fixtureName)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\te.Executor.SetStdout(b)\n\n\treturn e\n}\n\nfunc TestExec_Run(t *testing.T) {\n\t// Create a new instance of Sum\n\texec := newFakeSum(t, \"GetBIOSInfo\")\n\n\t// Create a new context\n\tctx := context.Background()\n\n\t// Call the run function\n\t_, err := exec.run(ctx, \"GetBIOSInfo\")\n\n\t// Check the output and error\n\tif err != nil {\n\t\tt.Errorf(\"Expected no error, got: %v\", err)\n\t}\n}\n\nfunc TestExec_SetBiosConfiguration(t *testing.T) {\n\t// Create a new context\n\tctx := context.Background()\n\n\t// Define the BIOS configuration\n\tbiosConfig := map[string]string{\n\t\t\"boot_mode\":   \"UEFI\",\n\t\t\"boot_order\":  \"UEFI\",\n\t\t\"intel_sgx\":   \"Enabled\",\n\t\t\"secure_boot\": \"Enabled\",\n\t\t\"tpm\":         \"Enabled\",\n\t\t\"smt\":         \"Disabled\",\n\t\t\"sr_iov\":      \"Enabled\",\n\t\t\"raw:Menu1,SubMenu1,SubMenuMenu1,SettingName\": \"Value\",\n\t}\n\n\texec := newFakeSum(t, \"SetBiosConfiguration\")\n\n\t// Call the SetBiosConfiguration function\n\terr := exec.SetBiosConfiguration(ctx, biosConfig)\n\n\t// Check for any errors\n\tif err != nil {\n\t\tt.Errorf(\"Expected no error, got: %v\", err)\n\t}\n\n\t// Additional assertions can be added to verify the behavior of the function\n}\n\nfunc TestExec_GetBiosConfiguration(t *testing.T) {\n\t// Create a new context\n\tctx := context.Background()\n\n\texec := newFakeSum(t, \"GetBiosConfiguration\")\n\n\t// Call the SetBiosConfiguration function\n\tbiosConfig, err := exec.GetBiosConfiguration(ctx)\n\n\t// Check for any errors\n\tif err != nil {\n\t\tt.Errorf(\"Expected no error, got: %v\", err)\n\t}\n\n\t// Confirm boot_mode exists\n\t_, ok := biosConfig[\"boot_mode\"]\n\tif !ok {\n\t\tt.Fail()\n\t}\n}\n"
  },
  {
    "path": "internal/utils.go",
    "content": "package internal\n\nimport (\n\t\"unicode\"\n)\n\n// IsntLetterOrNumber check if the give rune is not a letter nor a number\nfunc IsntLetterOrNumber(c rune) bool {\n\treturn !unicode.IsLetter(c) && !unicode.IsNumber(c)\n}\n\nfunc IsRoleValid(role string) bool {\n\treturn role == \"admin\" || role == \"user\" || role == \"operator\"\n}\n\nfunc StringInSlice(str string, sl []string) bool {\n\tfor _, s := range sl {\n\t\tif str == s {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "lint.mk",
    "content": "# BEGIN: lint-install github.com/bmc-toolbox/bmclib/v2\n# http://github.com/tinkerbell/lint-install\n\n.PHONY: lint\nlint: _lint\n\nLINT_ARCH := $(shell uname -m)\nLINT_OS := $(shell uname)\nLINT_OS_LOWER := $(shell echo $(LINT_OS) | tr '[:upper:]' '[:lower:]')\nLINT_ROOT := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))\n\n# shellcheck and hadolint lack arm64 native binaries: rely on x86-64 emulation\nifeq ($(LINT_OS),Darwin)\n\tifeq ($(LINT_ARCH),arm64)\n\t\tLINT_ARCH=x86_64\n\tendif\nendif\n\nLINTERS :=\nFIXERS :=\n\nGOLANGCI_LINT_CONFIG := $(LINT_ROOT)/.golangci.yml\nGOLANGCI_LINT_VERSION ?= v1.61.0\nGOLANGCI_LINT_BIN := $(LINT_ROOT)/out/linters/golangci-lint-$(GOLANGCI_LINT_VERSION)-$(LINT_ARCH)\n$(GOLANGCI_LINT_BIN):\n\tmkdir -p $(LINT_ROOT)/out/linters\n\trm -rf $(LINT_ROOT)/out/linters/golangci-lint-*\n\tcurl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(LINT_ROOT)/out/linters $(GOLANGCI_LINT_VERSION)\n\tmv $(LINT_ROOT)/out/linters/golangci-lint $@\n\nLINTERS += golangci-lint-lint\ngolangci-lint-lint: $(GOLANGCI_LINT_BIN)\n\tfind . -name go.mod -execdir \"$(GOLANGCI_LINT_BIN)\" run -c \"$(GOLANGCI_LINT_CONFIG)\" \\;\n\nFIXERS += golangci-lint-fix\ngolangci-lint-fix: $(GOLANGCI_LINT_BIN)\n\tfind . -name go.mod -execdir \"$(GOLANGCI_LINT_BIN)\" run -c \"$(GOLANGCI_LINT_CONFIG)\" --fix \\;\n\n.PHONY: _lint $(LINTERS)\n_lint: $(LINTERS)\n\n.PHONY: fix $(FIXERS)\nfix: $(FIXERS)\n\n# END: lint-install github.com/bmc-toolbox/bmclib/v2\n"
  },
  {
    "path": "logging/logging.go",
    "content": "package logging\n\nimport (\n\t\"os\"\n\n\t\"github.com/bombsimon/logrusr/v2\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/go-logr/zerologr\"\n\t\"github.com/rs/zerolog\"\n\t\"github.com/sirupsen/logrus\"\n)\n\n// DefaultLogger if no client logger is defined\nfunc DefaultLogger() logr.Logger {\n\tlogrusLog := logrus.New()\n\tlogrusLog.SetFormatter(&logrus.JSONFormatter{})\n\tlogrusLog.SetOutput(os.Stdout)\n\n\tswitch os.Getenv(\"BMCLIB_LOG_LEVEL\") {\n\tcase \"debug\":\n\t\tlogrusLog.SetLevel(logrus.DebugLevel)\n\tcase \"trace\":\n\t\tlogrusLog.SetLevel(logrus.TraceLevel)\n\t\tlogrusLog.SetReportCaller(true)\n\tdefault:\n\t\tlogrusLog.SetLevel(logrus.InfoLevel)\n\t}\n\n\treturn logrusr.New(logrusLog)\n}\n\n// ZeroLogger is a logr.Logger implementation that uses zerolog.\n// This logger handles nested structs better than the logrus implementation.\nfunc ZeroLogger(level string) logr.Logger {\n\tzl := zerolog.New(os.Stdout)\n\tzl = zl.With().Caller().Timestamp().Logger()\n\tvar l zerolog.Level\n\tswitch level {\n\tcase \"debug\":\n\t\tl = zerolog.DebugLevel\n\tdefault:\n\t\tl = zerolog.InfoLevel\n\t}\n\tzl = zl.Level(l)\n\n\treturn zerologr.New(&zl)\n}\n"
  },
  {
    "path": "option.go",
    "content": "package bmclib\n\nimport (\n\t\"context\"\n\t\"crypto/x509\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/internal/httpclient\"\n\t\"github.com/bmc-toolbox/bmclib/v2/providers/homeassistant\"\n\t\"github.com/bmc-toolbox/bmclib/v2/providers/rpc\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/jacobweinstock/registrar\"\n\toteltrace \"go.opentelemetry.io/otel/trace\"\n)\n\n// Option for setting optional Client values\ntype Option func(*Client)\n\n// WithLogger sets the logger\nfunc WithLogger(logger logr.Logger) Option {\n\treturn func(args *Client) { args.Logger = logger }\n}\n\n// WithRegistry sets the Registry\nfunc WithRegistry(registry *registrar.Registry) Option {\n\treturn func(args *Client) { args.Registry = registry }\n}\n\n// WithSecureTLS enforces trusted TLS connections, with an optional CA certificate pool.\n// Using this option with an nil pool uses the system CAs.\nfunc WithSecureTLS(rootCAs *x509.CertPool) Option {\n\treturn func(args *Client) {\n\t\targs.httpClientSetupFuncs = append(args.httpClientSetupFuncs, httpclient.SecureTLSOption(rootCAs))\n\t}\n}\n\n// WithHTTPClient sets an http client\nfunc WithHTTPClient(c *http.Client) Option {\n\treturn func(args *Client) {\n\t\targs.httpClient = c\n\t}\n}\n\n// WithPerProviderTimeout sets the timeout when interacting with a BMC.\n// This timeout value is applied per provider.\n// When not defined and a context with a timeout is passed to a method, the default timeout\n// will be the context timeout duration divided by the number of providers in the registry,\n// meaning, the len(Client.Registry.Drivers).\n// If this per provider timeout is not defined and no context timeout is defined,\n// the defaultConnectTimeout is used.\nfunc WithPerProviderTimeout(timeout time.Duration) Option {\n\treturn func(args *Client) {\n\t\targs.perProviderTimeout = func(context.Context) time.Duration { return timeout }\n\t}\n}\n\nfunc WithIpmitoolCipherSuite(cipherSuite string) Option {\n\treturn func(args *Client) {\n\t\targs.providerConfig.ipmitool.CipherSuite = cipherSuite\n\t}\n}\n\nfunc WithIpmitoolPort(port string) Option {\n\treturn func(args *Client) {\n\t\targs.providerConfig.ipmitool.Port = port\n\t}\n}\n\nfunc WithIpmitoolPath(path string) Option {\n\treturn func(args *Client) {\n\t\targs.providerConfig.ipmitool.IpmitoolPath = path\n\t}\n}\n\nfunc WithAsrockrackHTTPClient(httpClient *http.Client) Option {\n\treturn func(args *Client) {\n\t\targs.providerConfig.asrock.HttpClient = httpClient\n\t}\n}\n\nfunc WithAsrockrackPort(port string) Option {\n\treturn func(args *Client) {\n\t\targs.providerConfig.asrock.Port = port\n\t}\n}\n\nfunc WithRedfishHTTPClient(httpClient *http.Client) Option {\n\treturn func(args *Client) {\n\t\targs.providerConfig.gofish.HttpClient = httpClient\n\t}\n}\n\nfunc WithRedfishPort(port string) Option {\n\treturn func(args *Client) {\n\t\targs.providerConfig.gofish.Port = port\n\t}\n}\n\n// WithRedfishVersionsNotCompatible sets the list of incompatible redfish versions.\n//\n// With this option set, The bmclib.Registry.FilterForCompatible(ctx) method will not proceed on\n// devices with the given redfish version(s).\nfunc WithRedfishVersionsNotCompatible(versions []string) Option {\n\treturn func(args *Client) {\n\t\targs.providerConfig.gofish.VersionsNotCompatible = append(args.providerConfig.gofish.VersionsNotCompatible, versions...)\n\t}\n}\n\nfunc WithRedfishUseBasicAuth(useBasicAuth bool) Option {\n\treturn func(args *Client) {\n\t\targs.providerConfig.gofish.UseBasicAuth = useBasicAuth\n\t}\n}\n\nfunc WithRedfishEtagMatchDisabled(d bool) Option {\n\treturn func(args *Client) {\n\t\targs.providerConfig.gofish.DisableEtagMatch = d\n\t}\n}\n\nfunc WithRedfishSystemName(name string) Option {\n\treturn func(args *Client) {\n\t\targs.providerConfig.gofish.SystemName = name\n\t}\n}\n\nfunc WithIntelAMTHostScheme(hostScheme string) Option {\n\treturn func(args *Client) {\n\t\targs.providerConfig.intelamt.HostScheme = hostScheme\n\t}\n}\n\nfunc WithIntelAMTPort(port uint32) Option {\n\treturn func(args *Client) {\n\t\targs.providerConfig.intelamt.Port = port\n\t}\n}\n\n// WithDellRedfishVersionsNotCompatible sets the list of incompatible redfish versions.\n//\n// With this option set, The bmclib.Registry.FilterForCompatible(ctx) method will not proceed on\n// devices with the given redfish version(s).\nfunc WithDellRedfishVersionsNotCompatible(versions []string) Option {\n\treturn func(args *Client) {\n\t\targs.providerConfig.dell.VersionsNotCompatible = append(args.providerConfig.dell.VersionsNotCompatible, versions...)\n\t}\n}\n\nfunc WithDellRedfishUseBasicAuth(useBasicAuth bool) Option {\n\treturn func(args *Client) {\n\t\targs.providerConfig.dell.UseBasicAuth = useBasicAuth\n\t}\n}\n\nfunc WithRPCOpt(opt rpc.Provider) Option {\n\treturn func(args *Client) {\n\t\targs.providerConfig.rpc = opt\n\t}\n}\n\nfunc WithHomeAssistantOpt(opt homeassistant.Config) Option {\n\treturn func(args *Client) {\n\t\targs.providerConfig.homeassistant = opt\n\t}\n}\n\n// WithTracerProvider specifies a tracer provider to use for creating a tracer.\n// If none is specified a noop tracerprovider is used.\nfunc WithTracerProvider(provider oteltrace.TracerProvider) Option {\n\treturn func(args *Client) {\n\t\tif provider != nil {\n\t\t\targs.traceprovider = provider\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "providers/asrockrack/asrockrack.go",
    "content": "package asrockrack\n\nimport (\n\t\"context\"\n\t\"crypto/x509\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/constants\"\n\t\"github.com/bmc-toolbox/bmclib/v2/internal/httpclient\"\n\t\"github.com/bmc-toolbox/bmclib/v2/providers\"\n\t\"github.com/bmc-toolbox/common\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/jacobweinstock/registrar\"\n\t\"github.com/pkg/errors\"\n)\n\nconst (\n\t// ProviderName for the provider implementation\n\tProviderName = \"asrockrack\"\n\t// ProviderProtocol for the provider implementation\n\tProviderProtocol = \"vendorapi\"\n\n\tE3C256D4ID_NL = \"E3C256D4ID-NL\"\n\tE3C246D4ID_NL = \"E3C246D4ID-NL\"\n\tE3C246D4I_NL  = \"E3C246D4I-NL\"\n)\n\nvar (\n\t// Features implemented by asrockrack https\n\tFeatures = registrar.Features{\n\t\tproviders.FeaturePostCodeRead,\n\t\tproviders.FeatureBmcReset,\n\t\tproviders.FeatureUserCreate,\n\t\tproviders.FeatureUserUpdate,\n\t\tproviders.FeatureFirmwareUpload,\n\t\tproviders.FeatureFirmwareInstallUploaded,\n\t\tproviders.FeatureFirmwareTaskStatus,\n\t\tproviders.FeatureFirmwareInstallSteps,\n\t\tproviders.FeatureInventoryRead,\n\t\tproviders.FeaturePowerSet,\n\t\tproviders.FeaturePowerState,\n\t}\n)\n\n// ASRockRack holds the status and properties of a connection to a asrockrack bmc\ntype ASRockRack struct {\n\tip                   string\n\tusername             string\n\tpassword             string\n\tdeviceModel          string\n\tloginSession         *loginSession\n\thttpClient           *http.Client\n\tresetRequired        bool // Indicates if the BMC requires a reset\n\tskipLogout           bool // A Close() / httpsLogout() request is ignored if the BMC was just flashed - since the sessions are terminated either way\n\tlog                  logr.Logger\n\thttpClientSetupFuncs []func(*http.Client)\n}\n\ntype Config struct {\n\tPort       string\n\tHttpClient *http.Client\n}\n\n// ASRockOption is a type that can configure an *ASRockRack\ntype ASRockOption func(*ASRockRack)\n\n// WithSecureTLS enforces trusted TLS connections, with an optional CA certificate pool.\n// Using this option with an nil pool uses the system CAs.\nfunc WithSecureTLS(rootCAs *x509.CertPool) ASRockOption {\n\treturn func(r *ASRockRack) {\n\t\tr.httpClientSetupFuncs = append(r.httpClientSetupFuncs, httpclient.SecureTLSOption(rootCAs))\n\t}\n}\n\n// WithHTTPClient sets an HTTP client on the ASRockRack\nfunc WithHTTPClient(c *http.Client) ASRockOption {\n\treturn func(ar *ASRockRack) {\n\t\tar.httpClient = c\n\t}\n}\n\n// New returns a new ASRockRack instance ready to be used\nfunc New(ip string, username string, password string, log logr.Logger) *ASRockRack {\n\treturn NewWithOptions(ip, username, password, log)\n}\n\n// NewWithOptions returns a new ASRockRack instance with options ready to be used\nfunc NewWithOptions(ip string, username string, password string, log logr.Logger, opts ...ASRockOption) *ASRockRack {\n\tr := &ASRockRack{\n\t\tip:           ip,\n\t\tusername:     username,\n\t\tpassword:     password,\n\t\tlog:          log,\n\t\tloginSession: &loginSession{},\n\t}\n\tfor _, opt := range opts {\n\t\topt(r)\n\t}\n\tif r.httpClient == nil {\n\t\tr.httpClient = httpclient.Build(r.httpClientSetupFuncs...)\n\t} else {\n\t\tfor _, setupFunc := range r.httpClientSetupFuncs {\n\t\t\tsetupFunc(r.httpClient)\n\t\t}\n\t}\n\treturn r\n}\n\nfunc (a *ASRockRack) Name() string {\n\treturn ProviderName\n}\n\n// Open a connection to a BMC, implements the Opener interface\nfunc (a *ASRockRack) Open(ctx context.Context) (err error) {\n\tif err := a.httpsLogin(ctx); err != nil {\n\t\treturn err\n\t}\n\n\treturn a.supported(ctx)\n}\n\nfunc (a *ASRockRack) supported(ctx context.Context) error {\n\tsupported := []string{\n\t\tE3C256D4ID_NL,\n\t\tE3C246D4ID_NL,\n\t\tE3C246D4I_NL,\n\t}\n\n\tif a.deviceModel == \"\" {\n\t\tdevice := common.NewDevice()\n\t\tdevice.Metadata = map[string]string{}\n\n\t\terr := a.fruAttributes(ctx, &device)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, \"failed to identify device model\")\n\t\t}\n\n\t\tif device.Model == \"\" {\n\t\t\treturn errors.Wrap(err, \"failed to identify device model - empty model attribute\")\n\t\t}\n\n\t\ta.deviceModel = device.Model\n\t}\n\n\tfor _, s := range supported {\n\t\tif strings.EqualFold(a.deviceModel, s) {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\treturn fmt.Errorf(\"device model not supported: %s\", a.deviceModel)\n}\n\n// Close a connection to a BMC, implements the Closer interface\nfunc (a *ASRockRack) Close(ctx context.Context) (err error) {\n\tif a.skipLogout {\n\t\treturn nil\n\t}\n\n\treturn a.httpsLogout(ctx)\n}\n\n// CheckCredentials verify whether the credentials are valid or not\nfunc (a *ASRockRack) CheckCredentials(ctx context.Context) (err error) {\n\treturn a.httpsLogin(ctx)\n}\n\nfunc (a *ASRockRack) PostCode(ctx context.Context) (status string, code int, err error) {\n\tpostInfo, err := a.postCodeInfo(ctx)\n\tif err != nil {\n\t\treturn status, code, err\n\t}\n\n\tcode = postInfo.PostData\n\tstatus, exists := knownPOSTCodes[code]\n\tif !exists {\n\t\tstatus = constants.POSTCodeUnknown\n\t}\n\n\treturn status, code, nil\n}\n"
  },
  {
    "path": "providers/asrockrack/asrockrack_test.go",
    "content": "package asrockrack\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gopkg.in/go-playground/assert.v1\"\n)\n\nfunc TestHttpLogin(t *testing.T) {\n\terr := aClient.httpsLogin(context.TODO())\n\tif err != nil {\n\t\tt.Errorf(\"login: %s\", err.Error())\n\t}\n\n\tassert.Equal(t, \"l5L29IP7\", aClient.loginSession.CSRFToken)\n}\n\nfunc TestClose(t *testing.T) {\n\terr := aClient.httpsLogin(context.TODO())\n\tif err != nil {\n\t\tt.Errorf(\"login setup: %s\", err.Error())\n\t}\n\n\terr = aClient.httpsLogout(context.TODO())\n\tif err != nil {\n\t\tt.Errorf(\"logout: %s\", err.Error())\n\t}\n}\n\nfunc TestFirwmwareUpdateBMC(t *testing.T) {\n\terr := aClient.httpsLogin(context.TODO())\n\tif err != nil {\n\t\tt.Errorf(\"login: %s\", err.Error())\n\t}\n\n\tupgradeFile := \"/tmp/dummy-E3C246D4I-NL_L0.01.00.ima\"\n\t_, err = os.Create(upgradeFile)\n\tif err != nil {\n\t\tt.Errorf(\"create file: %s\", err.Error())\n\t}\n\n\tfh, err := os.Open(upgradeFile)\n\tif err != nil {\n\t\tt.Errorf(\"file open: %s\", err.Error())\n\t}\n\n\tdefer fh.Close()\n\tctx, cancel := context.WithTimeout(context.TODO(), time.Minute*15)\n\tdefer cancel()\n\n\terr = aClient.firmwareUploadBMC(ctx, fh)\n\tif err != nil {\n\t\tt.Errorf(\"upload: %s\", err.Error())\n\t}\n}\n"
  },
  {
    "path": "providers/asrockrack/firmware.go",
    "content": "package asrockrack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/constants\"\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/bmc-toolbox/bmclib/v2/internal\"\n\t\"github.com/bmc-toolbox/common\"\n)\n\nconst (\n\tversionStrError    = -1\n\tversionStrMatch    = 0\n\tversionStrMismatch = 1\n\tversionStrEmpty    = 2\n)\n\n// bmc client interface implementations methods\nfunc (a *ASRockRack) FirmwareInstallSteps(ctx context.Context, component string) ([]constants.FirmwareInstallStep, error) {\n\tif err := a.supported(ctx); err != nil {\n\t\treturn nil, bmclibErrs.NewErrUnsupportedHardware(err.Error())\n\t}\n\n\tswitch strings.ToUpper(component) {\n\tcase common.SlugBMC:\n\t\treturn []constants.FirmwareInstallStep{\n\t\t\tconstants.FirmwareInstallStepUpload,\n\t\t\tconstants.FirmwareInstallStepInstallUploaded,\n\t\t\tconstants.FirmwareInstallStepInstallStatus,\n\t\t\tconstants.FirmwareInstallStepResetBMCPostInstall,\n\t\t\tconstants.FirmwareInstallStepResetBMCOnInstallFailure,\n\t\t}, nil\n\t}\n\n\treturn nil, errors.Wrap(bmclibErrs.ErrFirmwareUpload, \"component unsupported: \"+component)\n}\n\nfunc (a *ASRockRack) FirmwareUpload(ctx context.Context, component string, file *os.File) (taskID string, err error) {\n\tswitch strings.ToUpper(component) {\n\tcase common.SlugBIOS:\n\t\treturn \"\", a.firmwareUploadBIOS(ctx, file)\n\tcase common.SlugBMC:\n\t\treturn \"\", a.firmwareUploadBMC(ctx, file)\n\t}\n\n\treturn \"\", errors.Wrap(bmclibErrs.ErrFirmwareUpload, \"component unsupported: \"+component)\n\n}\n\nfunc (a *ASRockRack) firmwareUploadBMC(ctx context.Context, file *os.File) error {\n\t//\t// expect atleast 5 minutes left in the deadline to proceed with the upload\n\td, _ := ctx.Deadline()\n\tif time.Until(d) < 5*time.Minute {\n\t\treturn errors.New(\"remaining context deadline insufficient to perform update: \" + time.Until(d).String())\n\t}\n\n\t// Beware: this locks some capabilities, e.g. the access to fruAttributes\n\ta.log.V(2).WithValues(\"step\", \"1/4\").Info(\"set device to flash mode, takes a minute...\")\n\terr := a.setFlashMode(ctx)\n\tif err != nil {\n\t\treturn errors.Wrap(\n\t\t\tbmclibErrs.ErrFirmwareUpload,\n\t\t\t\"failed in step 1/3 - set device to flash mode: \"+err.Error(),\n\t\t)\n\t}\n\n\tvar fwEndpoint string\n\tswitch a.deviceModel {\n\t// E3C256D4ID-NL calls a different endpoint for firmware upload\n\tcase \"E3C256D4ID-NL\":\n\t\tfwEndpoint = \"api/maintenance/firmware/firmware\"\n\tdefault:\n\t\tfwEndpoint = \"api/maintenance/firmware\"\n\t}\n\n\ta.log.V(2).WithValues(\"step\", \"2/4\").Info(\"upload BMC firmware image to \" + fwEndpoint)\n\terr = a.uploadFirmware(ctx, fwEndpoint, file)\n\tif err != nil {\n\t\treturn errors.Wrap(\n\t\t\tbmclibErrs.ErrFirmwareUpload,\n\t\t\t\"failed in step 2/3 - upload BMC firmware image: \"+err.Error(),\n\t\t)\n\t}\n\n\ta.log.V(2).WithValues(\"step\", \"3/4\").Info(\"verify uploaded BMC firmware\")\n\terr = a.verifyUploadedFirmware(ctx)\n\tif err != nil {\n\t\treturn errors.Wrap(\n\t\t\tbmclibErrs.ErrFirmwareUpload,\n\t\t\t\"failed in step 3/3 - verify uploaded BMC firmware: \"+err.Error(),\n\t\t)\n\t}\n\n\treturn nil\n}\n\nfunc (a *ASRockRack) firmwareUploadBIOS(ctx context.Context, file *os.File) error {\n\ta.log.V(2).WithValues(\"step\", \"1/3\").Info(\"upload BIOS firmware image\")\n\terr := a.uploadFirmware(ctx, \"api/asrr/maintenance/BIOS/firmware\", file)\n\tif err != nil {\n\t\treturn errors.Wrap(\n\t\t\tbmclibErrs.ErrFirmwareUpload,\n\t\t\t\"failed in step 1/3 - upload BIOS firmware image: \"+err.Error(),\n\t\t)\n\t}\n\n\ta.log.V(2).WithValues(\"step\", \"2/3\").Info(\"set BIOS preserve flash configuration\")\n\terr = a.biosUpgradeConfiguration(ctx)\n\tif err != nil {\n\t\treturn errors.Wrap(\n\t\t\tbmclibErrs.ErrFirmwareUpload,\n\t\t\t\"failed in step 2/3 - set flash configuration: \"+err.Error(),\n\t\t)\n\t}\n\n\t// 3. run upgrade\n\ta.log.V(2).WithValues(\"step\", \"3/3\").Info(\"proceed with BIOS firmware install\")\n\terr = a.upgradeBIOS(ctx)\n\tif err != nil {\n\t\treturn errors.Wrap(\n\t\t\tbmclibErrs.ErrFirmwareUpload,\n\t\t\t\"failed in step 3/3 - proceed with BIOS firmware install: \"+err.Error(),\n\t\t)\n\t}\n\n\treturn nil\n}\n\nfunc (a *ASRockRack) FirmwareInstallUploaded(ctx context.Context, component, uploadTaskID string) (installTaskID string, err error) {\n\tswitch strings.ToUpper(component) {\n\tcase common.SlugBIOS:\n\t\treturn \"\", a.firmwareInstallUploadedBIOS(ctx)\n\tcase common.SlugBMC:\n\t\treturn \"\", a.firmwareInstallUploadedBMC(ctx)\n\t}\n\n\treturn \"\", errors.Wrap(bmclibErrs.ErrFirmwareInstall, \"component unsupported: \"+component)\n}\n\n// firmwareInstallUploadedBIOS uploads and installs firmware for the BMC component\nfunc (a *ASRockRack) firmwareInstallUploadedBIOS(ctx context.Context) error {\n\t// 4. Run the upgrade - preserving current config\n\ta.log.V(2).WithValues(\"step\", \"install\").Info(\"proceed with BIOS firmware install, preserve current configuration\")\n\terr := a.upgradeBIOS(ctx)\n\tif err != nil {\n\t\treturn errors.Wrap(\n\t\t\tbmclibErrs.ErrFirmwareInstallUploaded,\n\t\t\t\"failed in step 4/4 - proceed with BMC firmware install: \"+err.Error(),\n\t\t)\n\t}\n\n\treturn nil\n}\n\n// firmwareInstallUploadedBMC uploads and installs firmware for the BMC component\nfunc (a *ASRockRack) firmwareInstallUploadedBMC(ctx context.Context) error {\n\t// 4. Run the upgrade - preserving current config\n\ta.log.V(2).WithValues(\"step\", \"install\").Info(\"proceed with BMC firmware install, preserve current configuration\")\n\terr := a.upgradeBMC(ctx)\n\tif err != nil {\n\t\treturn errors.Wrap(\n\t\t\tbmclibErrs.ErrFirmwareInstallUploaded,\n\t\t\t\"failed in step 4/4 - proceed with BMC firmware install\"+err.Error(),\n\t\t)\n\t}\n\n\treturn nil\n}\n\n// FirmwareTaskStatus returns the status of a firmware related task queued on the BMC.\nfunc (a *ASRockRack) FirmwareTaskStatus(ctx context.Context, kind constants.FirmwareInstallStep, component, taskID, installVersion string) (state constants.TaskState, status string, err error) {\n\tcomponent = strings.ToUpper(component)\n\tswitch component {\n\tcase common.SlugBIOS, common.SlugBMC:\n\t\treturn a.firmwareUpdateStatus(ctx, component, installVersion)\n\tdefault:\n\t\treturn \"\", \"\", errors.Wrap(bmclibErrs.ErrFirmwareInstallStatus, \"component unsupported: \"+component)\n\t}\n}\n\n// firmwareUpdateBIOSStatus returns the BIOS firmware install status\nfunc (a *ASRockRack) firmwareUpdateStatus(ctx context.Context, component string, installVersion string) (state constants.TaskState, status string, err error) {\n\tvar endpoint string\n\tcomponent = strings.ToUpper(component)\n\tswitch component {\n\tcase common.SlugBIOS:\n\t\tendpoint = \"api/asrr/maintenance/BIOS/flash-progress\"\n\tcase common.SlugBMC:\n\t\tendpoint = \"api/maintenance/firmware/flash-progress\"\n\tdefault:\n\t\treturn \"\", \"\", errors.Wrap(bmclibErrs.ErrFirmwareInstallStatus, \"component unsupported: \"+component)\n\t}\n\n\t// 1. query the flash progress endpoint\n\t//\n\t// once an update completes/fails this endpoint will return 500\n\tprogress, err := a.flashProgress(ctx, endpoint)\n\tif err != nil {\n\t\ta.log.V(3).Error(err, \"bmc query for install progress returned error: \")\n\t}\n\n\tif progress != nil {\n\t\tstatus = fmt.Sprintf(\"action: %s, progress: %s\", progress.Action, progress.Progress)\n\n\t\tswitch progress.State {\n\t\tcase 0:\n\t\t\treturn constants.Running, status, nil\n\t\tcase 1: // \"Flashing To be done\"\n\t\t\treturn constants.Queued, status, nil\n\t\tcase 2:\n\t\t\treturn constants.Complete, status, nil\n\t\tdefault:\n\t\t\ta.log.V(3).WithValues(\"state\", progress.State).Info(\"warn\", \"bmc returned unknown flash progress state\")\n\t\t}\n\t}\n\n\t// 2. query the firmware info endpoint to determine the update status\n\t//\n\t// at this point the flash-progress endpoint isn't returning useful information\n\tvar installStatus int\n\n\tinstallStatus, err = a.versionInstalled(ctx, component, installVersion)\n\tif err != nil {\n\t\treturn \"\", \"\", errors.Wrap(bmclibErrs.ErrFirmwareInstallStatus, err.Error())\n\t}\n\n\tswitch installStatus {\n\tcase versionStrMatch:\n\t\tif progress == nil {\n\t\t\t// TODO: we should pass the force parameter to firmwareUpdateStatus,\n\t\t\t// so that we can know if we expect a version change or not\n\t\t\ta.log.V(3).Info(\"Nil progress + no version change -> unknown\")\n\t\t\treturn constants.Unknown, status, nil\n\t\t}\n\n\t\treturn constants.Complete, status, nil\n\tcase versionStrEmpty:\n\t\treturn constants.Unknown, status, nil\n\tcase versionStrMismatch:\n\t\treturn constants.Running, status, nil\n\t}\n\n\treturn constants.Unknown, status, nil\n}\n\n// versionInstalled returns int values on the status of the firmware version install\n//\n// - 0 indicates the given version parameter matches the version installed\n// - 1 indicates the given version parameter does not match the version installed\n// - 2 the version parameter returned from the BMC is empty (which means the BMC needs a reset)\nfunc (a *ASRockRack) versionInstalled(ctx context.Context, component, version string) (status int, err error) {\n\tcomponent = strings.ToUpper(component)\n\tif !internal.StringInSlice(component, []string{common.SlugBIOS, common.SlugBMC}) {\n\t\treturn versionStrError, errors.Wrap(bmclibErrs.ErrFirmwareInstall, \"component unsupported: \"+component)\n\t}\n\n\tfwInfo, err := a.firmwareInfo(ctx)\n\tif err != nil {\n\t\terr = errors.Wrap(err, \"error querying for firmware info: \")\n\t\ta.log.V(3).Info(\"warn\", err.Error())\n\t\treturn versionStrError, err\n\t}\n\n\tvar installed string\n\n\tswitch component {\n\tcase common.SlugBIOS:\n\t\tinstalled = fwInfo.BIOSVersion\n\tcase common.SlugBMC:\n\t\tinstalled = fwInfo.BMCVersion\n\t}\n\n\t// version match\n\tif strings.EqualFold(installed, version) {\n\t\treturn versionStrMatch, nil\n\t}\n\n\t// fwinfo returned an empty string for firmware revision\n\t// this indicates the BMC is out of sync with the firmware versions installed\n\tif strings.TrimSpace(installed) == \"\" {\n\t\treturn versionStrEmpty, nil\n\t}\n\n\treturn 1, nil\n}\n"
  },
  {
    "path": "providers/asrockrack/firmware_update.md",
    "content": "### BMC\n Flashing a BMC firmware seems to be a multi step process\n\n\n 1. PUT /api/maintenance/flash\n       no payload (seems to set the device to be in flash mode or such)\n       200 OK - takes about a minute to return\n\n 2. POST /api/maintenance/firmware\n      Content-Type: multipart/form-data\n      ------WebKitFormBoundaryESKCgdjyLnqUPHBK\n\t\tContent-Disposition: form-data; name=\"fwimage\"; filename=\"E3C246D4I-NL_L0.01.00.ima\"\n\t\tContent-Type: application/octet-stream\n     ------WebKitFormBoundaryESKCgdjyLnqUPHBK--\n .   response - '{\"cc\": 0}' - successful upload\n\n 3. GET /api/maintenance/firmware/verification\n       500 - Bad firmware payload -> invoke reset\n       200 - OK\n           [ { \"id\": 1, \"current_image_name\": \"ast2500e\", \"current_image_version1\": \"0.01.00\", \"current_image_version2\": \"\", \"new_image_version\": \"0.03.00\", \"section_status\": 0, \"verification_status\": 5 } ]\n \n 4. If verificaion fails OR firmware update progress is at 100% done - invoke reset\n \n      GET /api/maintenance/reset\n       200 OK\n \n 5. PUT /api/maintenance/firmware/upgrade\n      payload {\"preserve_config\":1,\"preserve_network\":0,\"preserve_user\":0,\"flash_status\":1}\n      200 OK\n      response - same as payload\n\n 6. GET https://10.230.148.171/api/maintenance/firmware/flash-progress\n     { \"id\": 1, \"action\": \"Flashing...\", \"progress\": \"12% done         \", \"state\": 0 }\n     { \"id\": 1, \"action\": \"Flashing...\", \"progress\": \"100% done\", \"state\": 0 }\n\n\n### BIOS\n\n1. POST api/asrr/maintenance/BIOS/firmware\n   multipart payload: \n\n------WebKitFormBoundaryBet48KCtZK4gBlQz\nContent-Disposition: form-data; name=\"fwimage\"; filename=\"E6D4INL2.07B\"\nContent-Type: application/octet-stream\n\n\n------WebKitFormBoundaryBet48KCtZK4gBlQz--\n\n\n2. POST api/asrr/maintenance/BIOS/configuration\n   payload {\"action\":\"2\"}\n   200 OK\n     {\"response\": 1}\n\n3. POST api/asrr/maintenance/BIOS/upgrade\n   payload {action: 3}\n   200 oK\n\n4.  GET api/asrr/maintenance/BIOS/flash-progress\n\n\n"
  },
  {
    "path": "providers/asrockrack/fixtures/E3C246D4I-NL/sensors.json",
    "content": "[\n    {\n        \"id\": 1,\n        \"sensor_number\": 1,\n        \"name\": \"3VSB\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 112.000000,\n        \"type\": \"voltage\",\n        \"type_number\": 2,\n        \"reading\": 3.360000,\n        \"sensor_state\": 1,\n        \"discrete_state\": 0,\n        \"settable_readable_threshMask\": 13878,\n        \"lower_non_recoverable_threshold\": 2.820000,\n        \"lower_critical_threshold\": 2.970000,\n        \"lower_non_critical_threshold\": 0.000000,\n        \"higher_non_critical_threshold\": 0.000000,\n        \"higher_critical_threshold\": 3.630000,\n        \"higher_non_recoverable_threshold\": 3.780000,\n        \"accessible\": 0,\n        \"unit\": \"V\"\n    },\n    {\n        \"id\": 2,\n        \"sensor_number\": 2,\n        \"name\": \"5VSB\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 101.000000,\n        \"type\": \"voltage\",\n        \"type_number\": 2,\n        \"reading\": 5.050000,\n        \"sensor_state\": 1,\n        \"discrete_state\": 0,\n        \"settable_readable_threshMask\": 13878,\n        \"lower_non_recoverable_threshold\": 4.250000,\n        \"lower_critical_threshold\": 4.500000,\n        \"lower_non_critical_threshold\": 0.000000,\n        \"higher_non_critical_threshold\": 0.000000,\n        \"higher_critical_threshold\": 5.500000,\n        \"higher_non_recoverable_threshold\": 5.750000,\n        \"accessible\": 0,\n        \"unit\": \"V\"\n    },\n    {\n        \"id\": 3,\n        \"sensor_number\": 3,\n        \"name\": \"VCORE\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 64.000000,\n        \"type\": \"voltage\",\n        \"type_number\": 2,\n        \"reading\": 0.640000,\n        \"sensor_state\": 1,\n        \"discrete_state\": 0,\n        \"settable_readable_threshMask\": 12336,\n        \"lower_non_recoverable_threshold\": 0.000000,\n        \"lower_critical_threshold\": 0.000000,\n        \"lower_non_critical_threshold\": 0.000000,\n        \"higher_non_critical_threshold\": 0.000000,\n        \"higher_critical_threshold\": 1.890000,\n        \"higher_non_recoverable_threshold\": 1.980000,\n        \"accessible\": 0,\n        \"unit\": \"V\"\n    },\n    {\n        \"id\": 4,\n        \"sensor_number\": 4,\n        \"name\": \"VCCSA\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 105.000000,\n        \"type\": \"voltage\",\n        \"type_number\": 2,\n        \"reading\": 1.050000,\n        \"sensor_state\": 1,\n        \"discrete_state\": 0,\n        \"settable_readable_threshMask\": 13878,\n        \"lower_non_recoverable_threshold\": 0.890000,\n        \"lower_critical_threshold\": 0.950000,\n        \"lower_non_critical_threshold\": 0.000000,\n        \"higher_non_critical_threshold\": 0.000000,\n        \"higher_critical_threshold\": 1.160000,\n        \"higher_non_recoverable_threshold\": 1.210000,\n        \"accessible\": 0,\n        \"unit\": \"V\"\n    },\n    {\n        \"id\": 5,\n        \"sensor_number\": 5,\n        \"name\": \"VCCM\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 120.000000,\n        \"type\": \"voltage\",\n        \"type_number\": 2,\n        \"reading\": 1.200000,\n        \"sensor_state\": 1,\n        \"discrete_state\": 0,\n        \"settable_readable_threshMask\": 13878,\n        \"lower_non_recoverable_threshold\": 1.020000,\n        \"lower_critical_threshold\": 1.080000,\n        \"lower_non_critical_threshold\": 0.000000,\n        \"higher_non_critical_threshold\": 0.000000,\n        \"higher_critical_threshold\": 1.320000,\n        \"higher_non_recoverable_threshold\": 1.380000,\n        \"accessible\": 0,\n        \"unit\": \"V\"\n    },\n    {\n        \"id\": 6,\n        \"sensor_number\": 6,\n        \"name\": \"1.05V_PCH\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 105.000000,\n        \"type\": \"voltage\",\n        \"type_number\": 2,\n        \"reading\": 1.050000,\n        \"sensor_state\": 1,\n        \"discrete_state\": 0,\n        \"settable_readable_threshMask\": 13878,\n        \"lower_non_recoverable_threshold\": 0.890000,\n        \"lower_critical_threshold\": 0.950000,\n        \"lower_non_critical_threshold\": 0.000000,\n        \"higher_non_critical_threshold\": 0.000000,\n        \"higher_critical_threshold\": 1.160000,\n        \"higher_non_recoverable_threshold\": 1.210000,\n        \"accessible\": 0,\n        \"unit\": \"V\"\n    },\n    {\n        \"id\": 7,\n        \"sensor_number\": 7,\n        \"name\": \"VCCIO\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 95.000000,\n        \"type\": \"voltage\",\n        \"type_number\": 2,\n        \"reading\": 0.950000,\n        \"sensor_state\": 1,\n        \"discrete_state\": 0,\n        \"settable_readable_threshMask\": 13878,\n        \"lower_non_recoverable_threshold\": 0.810000,\n        \"lower_critical_threshold\": 0.860000,\n        \"lower_non_critical_threshold\": 0.000000,\n        \"higher_non_critical_threshold\": 0.000000,\n        \"higher_critical_threshold\": 1.050000,\n        \"higher_non_recoverable_threshold\": 1.090000,\n        \"accessible\": 0,\n        \"unit\": \"V\"\n    },\n    {\n        \"id\": 8,\n        \"sensor_number\": 9,\n        \"name\": \"VPPM\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 125.000000,\n        \"type\": \"voltage\",\n        \"type_number\": 2,\n        \"reading\": 2.500000,\n        \"sensor_state\": 1,\n        \"discrete_state\": 0,\n        \"settable_readable_threshMask\": 13878,\n        \"lower_non_recoverable_threshold\": 2.200000,\n        \"lower_critical_threshold\": 2.320000,\n        \"lower_non_critical_threshold\": 0.000000,\n        \"higher_non_critical_threshold\": 0.000000,\n        \"higher_critical_threshold\": 2.840000,\n        \"higher_non_recoverable_threshold\": 2.960000,\n        \"accessible\": 0,\n        \"unit\": \"V\"\n    },\n    {\n        \"id\": 9,\n        \"sensor_number\": 12,\n        \"name\": \"BAT\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 96.000000,\n        \"type\": \"voltage\",\n        \"type_number\": 2,\n        \"reading\": 2.880000,\n        \"sensor_state\": 1,\n        \"discrete_state\": 0,\n        \"settable_readable_threshMask\": 13878,\n        \"lower_non_recoverable_threshold\": 2.550000,\n        \"lower_critical_threshold\": 2.700000,\n        \"lower_non_critical_threshold\": 0.000000,\n        \"higher_non_critical_threshold\": 0.000000,\n        \"higher_critical_threshold\": 3.300000,\n        \"higher_non_recoverable_threshold\": 3.450000,\n        \"accessible\": 0,\n        \"unit\": \"V\"\n    },\n    {\n        \"id\": 10,\n        \"sensor_number\": 13,\n        \"name\": \"3V\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 111.000000,\n        \"type\": \"voltage\",\n        \"type_number\": 2,\n        \"reading\": 3.330000,\n        \"sensor_state\": 1,\n        \"discrete_state\": 0,\n        \"settable_readable_threshMask\": 13878,\n        \"lower_non_recoverable_threshold\": 2.820000,\n        \"lower_critical_threshold\": 2.970000,\n        \"lower_non_critical_threshold\": 0.000000,\n        \"higher_non_critical_threshold\": 0.000000,\n        \"higher_critical_threshold\": 3.630000,\n        \"higher_non_recoverable_threshold\": 3.780000,\n        \"accessible\": 0,\n        \"unit\": \"V\"\n    },\n    {\n        \"id\": 11,\n        \"sensor_number\": 14,\n        \"name\": \"5V\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 101.000000,\n        \"type\": \"voltage\",\n        \"type_number\": 2,\n        \"reading\": 5.050000,\n        \"sensor_state\": 1,\n        \"discrete_state\": 0,\n        \"settable_readable_threshMask\": 13878,\n        \"lower_non_recoverable_threshold\": 4.250000,\n        \"lower_critical_threshold\": 4.500000,\n        \"lower_non_critical_threshold\": 0.000000,\n        \"higher_non_critical_threshold\": 0.000000,\n        \"higher_critical_threshold\": 5.500000,\n        \"higher_non_recoverable_threshold\": 5.750000,\n        \"accessible\": 0,\n        \"unit\": \"V\"\n    },\n    {\n        \"id\": 12,\n        \"sensor_number\": 15,\n        \"name\": \"12V\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 122.000000,\n        \"type\": \"voltage\",\n        \"type_number\": 2,\n        \"reading\": 12.200000,\n        \"sensor_state\": 1,\n        \"discrete_state\": 0,\n        \"settable_readable_threshMask\": 13878,\n        \"lower_non_recoverable_threshold\": 10.200000,\n        \"lower_critical_threshold\": 10.800000,\n        \"lower_non_critical_threshold\": 0.000000,\n        \"higher_non_critical_threshold\": 0.000000,\n        \"higher_critical_threshold\": 13.200000,\n        \"higher_non_recoverable_threshold\": 13.800000,\n        \"accessible\": 0,\n        \"unit\": \"V\"\n    },\n    {\n        \"id\": 13,\n        \"sensor_number\": 48,\n        \"name\": \"MB Temp\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 30.000000,\n        \"type\": \"temperature\",\n        \"type_number\": 1,\n        \"reading\": 30.000000,\n        \"sensor_state\": 1,\n        \"discrete_state\": 0,\n        \"settable_readable_threshMask\": 6168,\n        \"lower_non_recoverable_threshold\": 0.000000,\n        \"lower_critical_threshold\": 0.000000,\n        \"lower_non_critical_threshold\": 0.000000,\n        \"higher_non_critical_threshold\": 54.000000,\n        \"higher_critical_threshold\": 55.000000,\n        \"higher_non_recoverable_threshold\": 0.000000,\n        \"accessible\": 0,\n        \"unit\": \"°C\"\n    },\n    {\n        \"id\": 14,\n        \"sensor_number\": 50,\n        \"name\": \"TR1 Temp\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 0.000000,\n        \"type\": \"temperature\",\n        \"type_number\": 1,\n        \"reading\": 0.000000,\n        \"sensor_state\": 1,\n        \"discrete_state\": 0,\n        \"settable_readable_threshMask\": 2056,\n        \"lower_non_recoverable_threshold\": 0.000000,\n        \"lower_critical_threshold\": 0.000000,\n        \"lower_non_critical_threshold\": 0.000000,\n        \"higher_non_critical_threshold\": 65.000000,\n        \"higher_critical_threshold\": 0.000000,\n        \"higher_non_recoverable_threshold\": 0.000000,\n        \"accessible\": 213,\n        \"unit\": \"°C\"\n    },\n    {\n        \"id\": 15,\n        \"sensor_number\": 51,\n        \"name\": \"CPU Temp\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 28.000000,\n        \"type\": \"temperature\",\n        \"type_number\": 1,\n        \"reading\": 28.000000,\n        \"sensor_state\": 1,\n        \"discrete_state\": 0,\n        \"settable_readable_threshMask\": 6168,\n        \"lower_non_recoverable_threshold\": 0.000000,\n        \"lower_critical_threshold\": 0.000000,\n        \"lower_non_critical_threshold\": 0.000000,\n        \"higher_non_critical_threshold\": 99.000000,\n        \"higher_critical_threshold\": 100.000000,\n        \"higher_non_recoverable_threshold\": 0.000000,\n        \"accessible\": 0,\n        \"unit\": \"°C\"\n    },\n    {\n        \"id\": 16,\n        \"sensor_number\": 53,\n        \"name\": \"PCH Temp\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 36.000000,\n        \"type\": \"temperature\",\n        \"type_number\": 1,\n        \"reading\": 36.000000,\n        \"sensor_state\": 1,\n        \"discrete_state\": 0,\n        \"settable_readable_threshMask\": 6168,\n        \"lower_non_recoverable_threshold\": 0.000000,\n        \"lower_critical_threshold\": 0.000000,\n        \"lower_non_critical_threshold\": 0.000000,\n        \"higher_non_critical_threshold\": 99.000000,\n        \"higher_critical_threshold\": 100.000000,\n        \"higher_non_recoverable_threshold\": 0.000000,\n        \"accessible\": 0,\n        \"unit\": \"°C\"\n    },\n    {\n        \"id\": 17,\n        \"sensor_number\": 96,\n        \"name\": \"IPB FAN1\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 26.000000,\n        \"type\": \"fan\",\n        \"type_number\": 4,\n        \"reading\": 5200.000000,\n        \"sensor_state\": 1,\n        \"discrete_state\": 0,\n        \"settable_readable_threshMask\": 257,\n        \"lower_non_recoverable_threshold\": 0.000000,\n        \"lower_critical_threshold\": 0.000000,\n        \"lower_non_critical_threshold\": 200.000000,\n        \"higher_non_critical_threshold\": 0.000000,\n        \"higher_critical_threshold\": 0.000000,\n        \"higher_non_recoverable_threshold\": 0.000000,\n        \"accessible\": 0,\n        \"unit\": \"RPM\"\n    },\n    {\n        \"id\": 18,\n        \"sensor_number\": 97,\n        \"name\": \"IPB FAN2\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 26.000000,\n        \"type\": \"fan\",\n        \"type_number\": 4,\n        \"reading\": 5200.000000,\n        \"sensor_state\": 1,\n        \"discrete_state\": 0,\n        \"settable_readable_threshMask\": 257,\n        \"lower_non_recoverable_threshold\": 0.000000,\n        \"lower_critical_threshold\": 0.000000,\n        \"lower_non_critical_threshold\": 200.000000,\n        \"higher_non_critical_threshold\": 0.000000,\n        \"higher_critical_threshold\": 0.000000,\n        \"higher_non_recoverable_threshold\": 0.000000,\n        \"accessible\": 0,\n        \"unit\": \"RPM\"\n    },\n    {\n        \"id\": 19,\n        \"sensor_number\": 98,\n        \"name\": \"IPB FAN3\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 26.000000,\n        \"type\": \"fan\",\n        \"type_number\": 4,\n        \"reading\": 5200.000000,\n        \"sensor_state\": 1,\n        \"discrete_state\": 0,\n        \"settable_readable_threshMask\": 257,\n        \"lower_non_recoverable_threshold\": 0.000000,\n        \"lower_critical_threshold\": 0.000000,\n        \"lower_non_critical_threshold\": 200.000000,\n        \"higher_non_critical_threshold\": 0.000000,\n        \"higher_critical_threshold\": 0.000000,\n        \"higher_non_recoverable_threshold\": 0.000000,\n        \"accessible\": 0,\n        \"unit\": \"RPM\"\n    },\n    {\n        \"id\": 20,\n        \"sensor_number\": 99,\n        \"name\": \"IPB FAN4\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 26.000000,\n        \"type\": \"fan\",\n        \"type_number\": 4,\n        \"reading\": 5200.000000,\n        \"sensor_state\": 1,\n        \"discrete_state\": 0,\n        \"settable_readable_threshMask\": 257,\n        \"lower_non_recoverable_threshold\": 0.000000,\n        \"lower_critical_threshold\": 0.000000,\n        \"lower_non_critical_threshold\": 200.000000,\n        \"higher_non_critical_threshold\": 0.000000,\n        \"higher_critical_threshold\": 0.000000,\n        \"higher_non_recoverable_threshold\": 0.000000,\n        \"accessible\": 0,\n        \"unit\": \"RPM\"\n    },\n    {\n        \"id\": 21,\n        \"sensor_number\": 100,\n        \"name\": \"IPB FAN5\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 26.000000,\n        \"type\": \"fan\",\n        \"type_number\": 4,\n        \"reading\": 5200.000000,\n        \"sensor_state\": 1,\n        \"discrete_state\": 0,\n        \"settable_readable_threshMask\": 257,\n        \"lower_non_recoverable_threshold\": 0.000000,\n        \"lower_critical_threshold\": 0.000000,\n        \"lower_non_critical_threshold\": 200.000000,\n        \"higher_non_critical_threshold\": 0.000000,\n        \"higher_critical_threshold\": 0.000000,\n        \"higher_non_recoverable_threshold\": 0.000000,\n        \"accessible\": 0,\n        \"unit\": \"RPM\"\n    },\n    {\n        \"id\": 22,\n        \"sensor_number\": 101,\n        \"name\": \"IPB FAN6\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 26.000000,\n        \"type\": \"fan\",\n        \"type_number\": 4,\n        \"reading\": 5200.000000,\n        \"sensor_state\": 1,\n        \"discrete_state\": 0,\n        \"settable_readable_threshMask\": 257,\n        \"lower_non_recoverable_threshold\": 0.000000,\n        \"lower_critical_threshold\": 0.000000,\n        \"lower_non_critical_threshold\": 200.000000,\n        \"higher_non_critical_threshold\": 0.000000,\n        \"higher_critical_threshold\": 0.000000,\n        \"higher_non_recoverable_threshold\": 0.000000,\n        \"accessible\": 0,\n        \"unit\": \"RPM\"\n    },\n    {\n        \"id\": 23,\n        \"sensor_number\": 102,\n        \"name\": \"IPB FAN7\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 26.000000,\n        \"type\": \"fan\",\n        \"type_number\": 4,\n        \"reading\": 5200.000000,\n        \"sensor_state\": 1,\n        \"discrete_state\": 0,\n        \"settable_readable_threshMask\": 257,\n        \"lower_non_recoverable_threshold\": 0.000000,\n        \"lower_critical_threshold\": 0.000000,\n        \"lower_non_critical_threshold\": 200.000000,\n        \"higher_non_critical_threshold\": 0.000000,\n        \"higher_critical_threshold\": 0.000000,\n        \"higher_non_recoverable_threshold\": 0.000000,\n        \"accessible\": 0,\n        \"unit\": \"RPM\"\n    },\n    {\n        \"id\": 24,\n        \"sensor_number\": 103,\n        \"name\": \"IPB FAN8\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 26.000000,\n        \"type\": \"fan\",\n        \"type_number\": 4,\n        \"reading\": 5200.000000,\n        \"sensor_state\": 1,\n        \"discrete_state\": 0,\n        \"settable_readable_threshMask\": 257,\n        \"lower_non_recoverable_threshold\": 0.000000,\n        \"lower_critical_threshold\": 0.000000,\n        \"lower_non_critical_threshold\": 200.000000,\n        \"higher_non_critical_threshold\": 0.000000,\n        \"higher_critical_threshold\": 0.000000,\n        \"higher_non_recoverable_threshold\": 0.000000,\n        \"accessible\": 0,\n        \"unit\": \"RPM\"\n    },\n    {\n        \"id\": 25,\n        \"sensor_number\": 145,\n        \"name\": \"CPU_PROCHOT\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 0.000000,\n        \"type\": \"processor\",\n        \"type_number\": 7,\n        \"reading\": 32768.000000,\n        \"sensor_state\": 0,\n        \"discrete_state\": 3,\n        \"settable_readable_threshMask\": 0,\n        \"lower_non_recoverable_threshold\": 0.000000,\n        \"lower_critical_threshold\": 0.000000,\n        \"lower_non_critical_threshold\": 0.000000,\n        \"higher_non_critical_threshold\": 0.000000,\n        \"higher_critical_threshold\": 0.000000,\n        \"higher_non_recoverable_threshold\": 0.000000,\n        \"accessible\": 0,\n        \"unit\": \"unknown\"\n    },\n    {\n        \"id\": 26,\n        \"sensor_number\": 147,\n        \"name\": \"CPU_THERMTRIP\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 0.000000,\n        \"type\": \"processor\",\n        \"type_number\": 7,\n        \"reading\": 32768.000000,\n        \"sensor_state\": 0,\n        \"discrete_state\": 111,\n        \"settable_readable_threshMask\": 0,\n        \"lower_non_recoverable_threshold\": 0.000000,\n        \"lower_critical_threshold\": 0.000000,\n        \"lower_non_critical_threshold\": 0.000000,\n        \"higher_non_critical_threshold\": 0.000000,\n        \"higher_critical_threshold\": 0.000000,\n        \"higher_non_recoverable_threshold\": 0.000000,\n        \"accessible\": 0,\n        \"unit\": \"unknown\"\n    },\n    {\n        \"id\": 27,\n        \"sensor_number\": 153,\n        \"name\": \"CPU_CATERR\",\n        \"owner_id\": 32,\n        \"owner_lun\": 0,\n        \"raw_reading\": 0.000000,\n        \"type\": \"processor\",\n        \"type_number\": 7,\n        \"reading\": 32768.000000,\n        \"sensor_state\": 0,\n        \"discrete_state\": 3,\n        \"settable_readable_threshMask\": 0,\n        \"lower_non_recoverable_threshold\": 0.000000,\n        \"lower_critical_threshold\": 0.000000,\n        \"lower_non_critical_threshold\": 0.000000,\n        \"higher_non_critical_threshold\": 0.000000,\n        \"higher_critical_threshold\": 0.000000,\n        \"higher_non_recoverable_threshold\": 0.000000,\n        \"accessible\": 0,\n        \"unit\": \"unknown\"\n    }\n]"
  },
  {
    "path": "providers/asrockrack/helpers.go",
    "content": "package asrockrack\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"os\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/constants\"\n\tbrrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/bmc-toolbox/common\"\n)\n\n// API session setup response payload\ntype loginSession struct {\n\tCSRFToken         string `json:\"csrftoken,omitempty\"`\n\tPrivilege         int    `json:\"privilege,omitempty\"`\n\tRACSessionID      int    `json:\"racsession_id,omitempty\"`\n\tExtendedPrivilege int    `json:\"extendedpriv,omitempty\"`\n}\n\n// Firmware info endpoint response payload\ntype firmwareInfo struct {\n\tBMCVersion       string `json:\"BMC_fw_version\"`\n\tBIOSVersion      string `json:\"BIOS_fw_version\"`\n\tMEVersion        string `json:\"ME_fw_version\"`\n\tMicrocodeVersion string `json:\"Micro_Code_version\"`\n\tCPLDVersion      string `json:\"CPLD_version\"`\n\tCMVersion        string `json:\"CM_version\"`\n\tBPBVersion       string `json:\"BPB_version\"`\n\tNodeID           string `json:\"Node_id\"`\n}\n\ntype biosPOSTCode struct {\n\tPostStatus int `json:\"poststatus\"`\n\tPostData   int `json:\"postdata\"`\n}\n\n// component is part of a payload returned by the inventory info endpoint\ntype component struct {\n\tDeviceID                int    `json:\"device_id\"`\n\tDeviceName              string `json:\"device_name\"`\n\tDeviceType              string `json:\"device_type\"`\n\tProductManufacturerName string `json:\"product_manufacturer_name\"`\n\tProductName             string `json:\"product_name\"`\n\tProductPartNumber       string `json:\"product_part_number\"`\n\tProductVersion          string `json:\"product_version\"`\n\tProductSerialNumber     string `json:\"product_serial_number\"`\n\tProductAssetTag         string `json:\"product_asset_tag\"`\n\tProductExtra            string `json:\"product_extra\"`\n}\n\n// fru is part of a payload returned by the fru info endpoint\ntype fru struct {\n\tComponent      string\n\tVersion        int    `json:\"version\"`\n\tLength         int    `json:\"length\"`\n\tLanguage       int    `json:\"language\"`\n\tManufacturer   string `json:\"manufacturer\"`\n\tProductName    string `json:\"product_name\"`\n\tPartNumber     string `json:\"part_number\"`\n\tProductVersion string `json:\"product_version\"`\n\tSerialNumber   string `json:\"serial_number\"`\n\tAssetTag       string `json:\"asset_tag\"`\n\tFruFileID      string `json:\"fru_file_id\"`\n\tType           string `json:\"type\"`\n\tCustomFields   string `json:\"custom_fields\"`\n}\n\n// sensor is part of the payload returned by the sensors endpoint\ntype sensor struct {\n\tID                            int     `json:\"id\"`\n\tSensorNumber                  int     `json:\"sensor_number\"`\n\tName                          string  `json:\"name\"`\n\tOwnerID                       int     `json:\"owner_id\"`\n\tOwnerLun                      int     `json:\"owner_lun\"`\n\tRawReading                    float64 `json:\"raw_reading\"`\n\tType                          string  `json:\"type\"`\n\tTypeNumber                    int     `json:\"type_number\"`\n\tReading                       float64 `json:\"reading\"`\n\tSensorState                   int     `json:\"sensor_state\"`\n\tDiscreteState                 int     `json:\"discrete_state\"`\n\tSettableReadableThreshMask    int     `json:\"settable_readable_threshMask\"`\n\tLowerNonRecoverableThreshold  float64 `json:\"lower_non_recoverable_threshold\"`\n\tLowerCriticalThreshold        float64 `json:\"lower_critical_threshold\"`\n\tLowerNonCriticalThreshold     float64 `json:\"lower_non_critical_threshold\"`\n\tHigherNonCriticalThreshold    float64 `json:\"higher_non_critical_threshold\"`\n\tHigherCriticalThreshold       float64 `json:\"higher_critical_threshold\"`\n\tHigherNonRecoverableThreshold float64 `json:\"higher_non_recoverable_threshold\"`\n\tAccessible                    int     `json:\"accessible\"`\n\tUnit                          string  `json:\"unit\"`\n}\n\n// Payload to preseve config when updating the BMC firmware\ntype preserveConfig struct {\n\tFlashStatus     int `json:\"flash_status\"` // 1 = full firmware flash, 2 = section based flash, 3 - version compare flash\n\tPreserveConfig  int `json:\"preserve_config\"`\n\tPreserveNetwork int `json:\"preserve_network\"`\n\tPreserveUser    int `json:\"preserve_user\"`\n}\n\n// Firmware flash progress\n// { \"id\": 1, \"action\": \"Flashing...\", \"progress\": \"12% done         \", \"state\": 0 }\n// { \"id\": 1, \"action\": \"Flashing...\", \"progress\": \"100% done\", \"state\": 0 }\ntype upgradeProgress struct {\n\tID       int    `json:\"id,omitempty\"`\n\tAction   string `json:\"action,omitempty\"`\n\tProgress string `json:\"progress,omitempty\"`\n\tState    int    `json:\"state,omitempty\"`\n}\n\n// Chassis status struct\ntype chassisStatus struct {\n\tPowerStatus int `json:\"power_status\"`\n\tLEDStatus   int `json:\"led_status\"`\n}\n\n// BIOS upgrade commands\n// 2 == configure\n// 3 == apply upgrade\ntype biosUpdateAction struct {\n\tAction int `json:\"action\"`\n}\n\nvar (\n\tknownPOSTCodes = map[int]string{\n\t\t160: constants.POSTStateOS,\n\t\t2:   constants.POSTStateBootINIT, // no differentiation between BIOS init and PXE boot\n\t\t144: constants.POSTStateUEFI,\n\t\t154: constants.POSTStateUEFI,\n\t\t178: constants.POSTStateUEFI,\n\t}\n)\n\nfunc (a *ASRockRack) listUsers(ctx context.Context) ([]*UserAccount, error) {\n\tresp, statusCode, err := a.queryHTTPS(ctx, \"api/settings/users\", \"GET\", nil, nil, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif statusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"non 200 response: %d\", statusCode)\n\t}\n\n\taccounts := []*UserAccount{}\n\n\terr = json.Unmarshal(resp, &accounts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn accounts, nil\n}\n\nfunc (a *ASRockRack) createUpdateUser(ctx context.Context, account *UserAccount) error {\n\tendpoint := \"api/settings/users/\" + fmt.Sprintf(\"%d\", account.ID)\n\n\tpayload, err := json.Marshal(account)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\theaders := map[string]string{\"Content-Type\": \"application/json\"}\n\t_, statusCode, err := a.queryHTTPS(ctx, endpoint, \"PUT\", bytes.NewReader(payload), headers, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif statusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"non 200 response: %d\", statusCode)\n\t}\n\n\treturn nil\n}\n\n// 1 Set BMC to flash mode and prepare flash area\n//\n// with the BMC set in flash mode, no new logins are accepted\n// and only a few endpoints can be queried with the existing session\n// one of the few being the install progress/flash status endpoint.\nfunc (a *ASRockRack) setFlashMode(ctx context.Context) error {\n\tdevice := common.NewDevice()\n\tdevice.Metadata = map[string]string{}\n\t_ = a.fruAttributes(ctx, &device)\n\n\tpConfig := &preserveConfig{}\n\t// preserve config is needed by e3c256d4i\n\tswitch device.Model {\n\tcase E3C256D4ID_NL:\n\t\tpConfig = &preserveConfig{PreserveConfig: 1}\n\t}\n\n\tpayload, err := json.Marshal(pConfig)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\theaders := map[string]string{\"Content-Type\": \"application/json\"}\n\t_, statusCode, err := a.queryHTTPS(ctx, \"api/maintenance/flash\", \"PUT\", bytes.NewReader(payload), headers, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif statusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"non 200 response: %d\", statusCode)\n\t}\n\n\ta.resetRequired = true\n\n\treturn nil\n}\n\nfunc multipartSize(fieldname, filename string) int64 {\n\tbody := &bytes.Buffer{}\n\tform := multipart.NewWriter(body)\n\t_, _ = form.CreateFormFile(fieldname, filename)\n\t_ = form.Close()\n\treturn int64(body.Len())\n}\n\n// 2 Upload the firmware file\nfunc (a *ASRockRack) uploadFirmware(ctx context.Context, endpoint string, file *os.File) error {\n\tvar size int64\n\tfinfo, err := file.Stat()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to determine file size: %w\", err)\n\t}\n\n\tsize = finfo.Size()\n\n\tfieldName, fileName := \"fwimage\", \"image\"\n\tcontentLength := multipartSize(fieldName, fileName) + size\n\n\t// Before reading the file, rewind to the beginning\n\t_, _ = file.Seek(0, 0)\n\n\t// setup pipe\n\tpipeReader, pipeWriter := io.Pipe()\n\tdefer pipeReader.Close()\n\n\t// initiate a mulitpart writer\n\tform := multipart.NewWriter(pipeWriter)\n\n\terrCh := make(chan error, 1)\n\tgo func() {\n\t\tdefer pipeWriter.Close()\n\n\t\t// create form part\n\t\tpart, err := form.CreateFormFile(fieldName, fileName)\n\t\tif err != nil {\n\t\t\terrCh <- err\n\t\t\treturn\n\t\t}\n\n\t\t// copy from source into form part writer\n\t\t_, err = io.Copy(part, file)\n\t\tif err != nil {\n\t\t\terrCh <- err\n\t\t\treturn\n\t\t}\n\n\t\t// add terminating boundary to multipart form\n\t\terrCh <- form.Close()\n\t}()\n\n\t// multi-part content type\n\theaders := map[string]string{\n\t\t\"Content-Type\": form.FormDataContentType(),\n\t}\n\n\t// POST payload\n\t_, statusCode, err := a.queryHTTPS(ctx, endpoint, \"POST\", pipeReader, headers, contentLength)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif statusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"non 200 response: %d\", statusCode)\n\t}\n\n\treturn nil\n}\n\n// 3. Verify uploaded firmware file - to be invoked after uploadFirmware()\nfunc (a *ASRockRack) verifyUploadedFirmware(ctx context.Context) error {\n\t_, statusCode, err := a.queryHTTPS(ctx, \"api/maintenance/firmware/verification\", \"GET\", nil, nil, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif statusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"non 200 response: %d\", statusCode)\n\t}\n\n\treturn nil\n}\n\n// 4. Start firmware flashing process - to be invoked after verifyUploadedFirmware\nfunc (a *ASRockRack) upgradeBMC(ctx context.Context) error {\n\tendpoint := \"api/maintenance/firmware/upgrade\"\n\n\t// preserve all configuration during upgrade, full flash\n\tpConfig := &preserveConfig{FlashStatus: 1, PreserveConfig: 1, PreserveNetwork: 1, PreserveUser: 1}\n\tpayload, err := json.Marshal(pConfig)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\theaders := map[string]string{\"Content-Type\": \"application/json\"}\n\t_, statusCode, err := a.queryHTTPS(ctx, endpoint, \"PUT\", bytes.NewReader(payload), headers, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif statusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"non 200 response: %d\", statusCode)\n\t}\n\n\treturn nil\n}\n\n// 5. firmware flash progress\nfunc (a *ASRockRack) flashProgress(ctx context.Context, endpoint string) (*upgradeProgress, error) {\n\tresp, statusCode, err := a.queryHTTPS(ctx, endpoint, \"GET\", nil, nil, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif statusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"non 200 response: %d\", statusCode)\n\t}\n\n\tp := &upgradeProgress{}\n\terr = json.Unmarshal(resp, p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn p, nil\n}\n\n// Query firmware information from the BMC\nfunc (a *ASRockRack) firmwareInfo(ctx context.Context) (*firmwareInfo, error) {\n\tresp, statusCode, err := a.queryHTTPS(ctx, \"api/asrr/fw-info\", \"GET\", nil, nil, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif statusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"non 200 response: %d\", statusCode)\n\t}\n\n\tf := &firmwareInfo{}\n\terr = json.Unmarshal(resp, f)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn f, nil\n}\n\n// Query BIOS/UEFI POST code information from the BMC\nfunc (a *ASRockRack) postCodeInfo(ctx context.Context) (*biosPOSTCode, error) {\n\tresp, statusCode, err := a.queryHTTPS(ctx, \"/api/asrr/getbioscode\", \"GET\", nil, nil, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif statusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"non 200 response: %d\", statusCode)\n\t}\n\n\tb := &biosPOSTCode{}\n\terr = json.Unmarshal(resp, b)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn b, nil\n}\n\n// Query the inventory info endpoint\nfunc (a *ASRockRack) inventoryInfoE3C246D41D(ctx context.Context) ([]*component, error) {\n\tresp, statusCode, err := a.queryHTTPS(ctx, \"api/asrr/inventory_info\", \"GET\", nil, nil, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif statusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"non 200 response: %d\", statusCode)\n\t}\n\n\tcomponents := []*component{}\n\terr = json.Unmarshal(resp, &components)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn components, nil\n}\n\n// Query the fru info endpoint\nfunc (a *ASRockRack) fruInfo(ctx context.Context) ([]*fru, error) {\n\tresp, statusCode, err := a.queryHTTPS(ctx, \"api/fru\", \"GET\", nil, nil, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif statusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"non 200 response: %d\", statusCode)\n\t}\n\n\tdata := []map[string]*fru{}\n\terr = json.Unmarshal(resp, &data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(data) == 0 {\n\t\treturn nil, fmt.Errorf(\"no FRU data returned\")\n\t}\n\n\tfrus := []*fru{}\n\tfor key, f := range data[0] {\n\t\tswitch key {\n\t\tcase \"chassis\", \"board\", \"product\":\n\t\t\tfrus = append(frus, &fru{\n\t\t\t\tComponent:      key,\n\t\t\t\tVersion:        f.Version,\n\t\t\t\tLength:         f.Length,\n\t\t\t\tLanguage:       f.Language,\n\t\t\t\tManufacturer:   f.Manufacturer,\n\t\t\t\tProductName:    f.ProductName,\n\t\t\t\tPartNumber:     f.PartNumber,\n\t\t\t\tProductVersion: f.ProductVersion,\n\t\t\t\tSerialNumber:   f.SerialNumber,\n\t\t\t\tAssetTag:       f.SerialNumber,\n\t\t\t\tFruFileID:      f.FruFileID,\n\t\t\t\tCustomFields:   f.CustomFields,\n\t\t\t\tType:           f.Type,\n\t\t\t})\n\t\t}\n\t}\n\n\treturn frus, nil\n}\n\n// Query the sensors  endpoint\nfunc (a *ASRockRack) sensors(ctx context.Context) ([]*sensor, error) {\n\tresp, statusCode, err := a.queryHTTPS(ctx, \"api/sensors\", \"GET\", nil, nil, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif statusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"non 200 response: %d\", statusCode)\n\t}\n\n\tsensors := []*sensor{}\n\terr = json.Unmarshal(resp, &sensors)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn sensors, nil\n}\n\n// Set the BIOS upgrade configuration\n//   - preserve current configuration\nfunc (a *ASRockRack) biosUpgradeConfiguration(ctx context.Context) error {\n\tendpoint := \"api/asrr/maintenance/BIOS/configuration\"\n\n\t// Preserve existing configuration?\n\tp := biosUpdateAction{Action: 2}\n\tpayload, err := json.Marshal(p)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\theaders := map[string]string{\"Content-Type\": \"application/json\"}\n\tresp, statusCode, err := a.queryHTTPS(ctx, endpoint, \"POST\", bytes.NewReader(payload), headers, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif statusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"non 200 response: %d\", statusCode)\n\t}\n\n\tf := &firmwareInfo{}\n\treturn json.Unmarshal(resp, f)\n}\n\n// Run BIOS upgrade\nfunc (a *ASRockRack) upgradeBIOS(ctx context.Context) error {\n\tendpoint := \"api/asrr/maintenance/BIOS/upgrade\"\n\n\t// Run upgrade\n\tp := biosUpdateAction{Action: 3}\n\tpayload, err := json.Marshal(p)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\theaders := map[string]string{\"Content-Type\": \"application/json\"}\n\tresp, statusCode, err := a.queryHTTPS(ctx, endpoint, \"POST\", bytes.NewReader(payload), headers, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif statusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"non 200 response: %d\", statusCode)\n\t}\n\n\tf := &firmwareInfo{}\n\treturn json.Unmarshal(resp, f)\n}\n\n// Returns the chassis status object which includes the power state\nfunc (a *ASRockRack) chassisStatusInfo(ctx context.Context) (*chassisStatus, error) {\n\tresp, statusCode, err := a.queryHTTPS(ctx, \"/api/chassis-status\", \"GET\", nil, nil, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif statusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"non 200 response: %d\", statusCode)\n\t}\n\n\tchassisStatus := chassisStatus{}\n\terr = json.Unmarshal(resp, &chassisStatus)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &chassisStatus, nil\n}\n\n// Aquires a session id cookie and a csrf token\nfunc (a *ASRockRack) httpsLogin(ctx context.Context) error {\n\turlEndpoint := \"api/session\"\n\n\t// login payload\n\tpayload := []byte(\n\t\tfmt.Sprintf(\"username=%s&password=%s&certlogin=0\",\n\t\t\ta.username,\n\t\t\ta.password,\n\t\t),\n\t)\n\n\theaders := map[string]string{\"Content-Type\": \"application/x-www-form-urlencoded\"}\n\n\tresp, statusCode, err := a.queryHTTPS(ctx, urlEndpoint, \"POST\", bytes.NewReader(payload), headers, 0)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"logging in: %w\", err)\n\t}\n\n\tif statusCode == 401 {\n\t\treturn brrs.ErrLoginFailed\n\t}\n\n\t// Unmarshal login session\n\terr = json.Unmarshal(resp, a.loginSession)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unmarshalling response payload: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// Close ends the BMC session\nfunc (a *ASRockRack) httpsLogout(ctx context.Context) error {\n\t_, statusCode, err := a.queryHTTPS(ctx, \"api/session\", \"DELETE\", nil, nil, 0)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"logging out: %w\", err)\n\t}\n\n\tif statusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"non 200 response at https logout: %d\", statusCode)\n\t}\n\n\treturn nil\n}\n\n// queryHTTPS run the HTTPS query passing in the required headers\n// the / suffix should be excluded from the URLendpoint\n// returns - response body, http status code, error if any\nfunc (a *ASRockRack) queryHTTPS(ctx context.Context, endpoint, method string, payload io.Reader, headers map[string]string, contentLength int64) ([]byte, int, error) {\n\tvar body []byte\n\tvar err error\n\tvar req *http.Request\n\n\tURL := fmt.Sprintf(\"https://%s/%s\", a.ip, endpoint)\n\treq, err = http.NewRequestWithContext(ctx, method, URL, payload)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\t// add headers\n\treq.Header.Add(\"X-CSRFTOKEN\", a.loginSession.CSRFToken)\n\tfor k, v := range headers {\n\t\treq.Header.Add(k, v)\n\t}\n\n\t// Content-Length headers are ignored, unless defined in this manner\n\t// https://go.googlesource.com/go/+/go1.16/src/net/http/request.go#161\n\t// https://go.googlesource.com/go/+/go1.16/src/net/http/request.go#88\n\tif contentLength > 0 {\n\t\treq.ContentLength = contentLength\n\t}\n\n\t// debug dump request\n\tif os.Getenv(constants.EnvEnableDebug) == \"true\" {\n\t\treqDump, _ := httputil.DumpRequestOut(req, true)\n\t\ta.log.V(3).Info(\"trace\", \"url\", URL, \"requestDump\", string(reqDump))\n\t}\n\n\tresp, err := a.httpClient.Do(req)\n\tif err != nil {\n\t\treturn body, 0, err\n\t}\n\n\t// debug dump response\n\tif os.Getenv(constants.EnvEnableDebug) == \"true\" {\n\t\trespDump, _ := httputil.DumpResponse(resp, true)\n\t\ta.log.V(3).Info(\"trace\", \"responseDump\", string(respDump))\n\t}\n\n\tbody, err = io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn body, 0, err\n\t}\n\n\tdefer resp.Body.Close()\n\n\treturn body, resp.StatusCode, nil\n}\n"
  },
  {
    "path": "providers/asrockrack/helpers_test.go",
    "content": "package asrockrack\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"gopkg.in/go-playground/assert.v1\"\n)\n\nfunc Test_FirmwareInfo(t *testing.T) {\n\texpected := firmwareInfo{\n\t\tBMCVersion:       \"0.01.00\",\n\t\tBIOSVersion:      \"L2.07B\",\n\t\tMEVersion:        \"5.1.3.78\",\n\t\tMicrocodeVersion: \"000000ca\",\n\t\tCPLDVersion:      \"N/A\",\n\t\tCMVersion:        \"0.13.01\",\n\t\tBPBVersion:       \"0.0.002.0\",\n\t\tNodeID:           \"2\",\n\t}\n\n\terr := aClient.httpsLogin(context.TODO())\n\tif err != nil {\n\t\tt.Errorf(\"login: %s\", err.Error())\n\t}\n\n\tfwInfo, err := aClient.firmwareInfo(context.TODO())\n\tif err != nil {\n\t\tt.Errorf(\"firmwareInfo: %s\", err.Error())\n\t}\n\n\tassert.Equal(t, expected, fwInfo)\n}\n\nfunc TestInventoryInfo(t *testing.T) {\n\terr := aClient.httpsLogin(context.TODO())\n\tif err != nil {\n\t\tt.Errorf(\"login: %s\", err.Error())\n\t}\n\n\tinventory, err := aClient.inventoryInfoE3C246D41D(context.TODO())\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\n\tassert.Equal(t, 6, len(inventory))\n\tassert.Equal(t, \"CPU\", inventory[0].DeviceType)\n\tassert.Equal(t, \"Storage device\", inventory[5].DeviceType)\n}\nfunc Test_fruInfo(t *testing.T) {\n\terr := aClient.httpsLogin(context.TODO())\n\tif err != nil {\n\t\tt.Errorf(\"login: %s\", err.Error())\n\t}\n\n\tfrus, err := aClient.fruInfo(context.TODO())\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\n\tassert.Equal(t, 3, len(frus))\n}\n\nfunc Test_sensors(t *testing.T) {\n\terr := aClient.httpsLogin(context.TODO())\n\tif err != nil {\n\t\tt.Errorf(\"login: %s\", err.Error())\n\t}\n\n\tsensors, err := aClient.sensors(context.TODO())\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\n\tassert.Equal(t, 27, len(sensors))\n}\n\nfunc Test_biosPOSTCode(t *testing.T) {\n\texpected := biosPOSTCode{\n\t\tPostStatus: 1,\n\t\tPostData:   160,\n\t}\n\n\terr := aClient.httpsLogin(context.TODO())\n\tif err != nil {\n\t\tt.Errorf(\"login: %s\", err.Error())\n\t}\n\n\tinfo, err := aClient.postCodeInfo(context.TODO())\n\tif err != nil {\n\t\tt.Error(err.Error())\n\t}\n\n\tassert.Equal(t, expected, info)\n}\n\nfunc Test_chassisStatus(t *testing.T) {\n\texpected := chassisStatus{\n\t\tPowerStatus: 1,\n\t\tLEDStatus:   0,\n\t}\n\n\terr := aClient.httpsLogin(context.TODO())\n\tif err != nil {\n\t\tt.Errorf(\"login: %s\", err.Error())\n\t}\n\n\tinfo, err := aClient.chassisStatusInfo(context.TODO())\n\tif err != nil {\n\t\tt.Error(err.Error())\n\t}\n\n\tassert.Equal(t, expected, info)\n}\n"
  },
  {
    "path": "providers/asrockrack/inventory.go",
    "content": "package asrockrack\n\nimport (\n\t\"context\"\n\n\t\"github.com/bmc-toolbox/common\"\n)\n\n// Inventory returns hardware and firmware inventory\nfunc (a *ASRockRack) Inventory(ctx context.Context) (device *common.Device, err error) {\n\t// initialize device to be populated with inventory\n\tnewDevice := common.NewDevice()\n\tdevice = &newDevice\n\tdevice.Status = &common.Status{}\n\n\tdevice.Metadata = map[string]string{}\n\n\t// populate device BMC, BIOS component attributes\n\terr = a.fruAttributes(ctx, device)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// populate device System components attributes\n\terr = a.systemAttributes(ctx, device)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// populate device health based on sensor readings\n\t//\n\t// sensor data collection can fail for a myriad of reasons\n\t// we log the error and keep going\n\terr = a.systemHealth(ctx, device)\n\tif err != nil {\n\t\ta.log.V(2).Error(err, \"sensor data collection error\", \"deviceModel\", a.deviceModel)\n\t}\n\n\treturn device, nil\n}\n\n// systemHealth collects system health information based on the sensors data\nfunc (a *ASRockRack) systemHealth(ctx context.Context, device *common.Device) error {\n\tsensors, err := a.sensors(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tok := true\n\tdevice.Status.Health = \"OK\"\n\tfor _, sensor := range sensors {\n\t\tswitch sensor.Name {\n\t\tcase \"CPU_CATERR\", \"CPU_THERMTRIP\", \"CPU_PROCHOT\":\n\t\t\tif sensor.SensorState != 0 {\n\t\t\t\tok = false\n\t\t\t\tdevice.Status.State = sensor.Name\n\t\t\t\tbreak\n\t\t\t}\n\t\tdefault:\n\t\t\tif sensor.SensorState != 1 {\n\t\t\t\tok = false\n\t\t\t\tdevice.Status.State = sensor.Name\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tif !ok {\n\t\tdevice.Status.Health = \"CRITICAL\"\n\t}\n\n\t// we don't want to fail inventory collection hence ignore POST code collection error\n\tdevice.Status.PostCodeStatus, device.Status.PostCode, _ = a.PostCode(ctx)\n\n\treturn nil\n}\n\n// fruAttributes collects chassis information\nfunc (a *ASRockRack) fruAttributes(ctx context.Context, device *common.Device) error {\n\tcomponents, err := a.fruInfo(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, component := range components {\n\t\tswitch component.Component {\n\t\tcase \"board\":\n\t\t\tdevice.Vendor = component.Manufacturer\n\t\t\tdevice.Model = component.ProductName\n\t\t\tdevice.Serial = component.SerialNumber\n\t\tcase \"chassis\":\n\t\t\tdevice.Enclosures = append(device.Enclosures, &common.Enclosure{\n\t\t\t\tCommon: common.Common{\n\t\t\t\t\tSerial:      component.SerialNumber,\n\t\t\t\t\tDescription: component.Type,\n\t\t\t\t},\n\t\t\t})\n\t\tcase \"product\":\n\t\t\tdevice.Metadata[\"product.manufacturer\"] = component.Manufacturer\n\t\t\tdevice.Metadata[\"product.name\"] = component.ProductName\n\t\t\tdevice.Metadata[\"product.part_number\"] = component.PartNumber\n\t\t\tdevice.Metadata[\"product.version\"] = component.ProductVersion\n\t\t\tdevice.Metadata[\"product.serialnumber\"] = component.SerialNumber\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// systemAttributes collects system component attributes\nfunc (a *ASRockRack) systemAttributes(ctx context.Context, device *common.Device) error {\n\tfwInfo, err := a.firmwareInfo(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdevice.BIOS = &common.BIOS{\n\t\tCommon: common.Common{\n\t\t\tVendor:   device.Vendor,\n\t\t\tModel:    device.Model,\n\t\t\tFirmware: &common.Firmware{Installed: fwInfo.BIOSVersion},\n\t\t},\n\t}\n\n\tdevice.BMC = &common.BMC{\n\t\tCommon: common.Common{\n\t\t\tVendor:   device.Vendor,\n\t\t\tModel:    device.Model,\n\t\t\tFirmware: &common.Firmware{Installed: fwInfo.BMCVersion},\n\t\t},\n\t}\n\n\tif fwInfo.CPLDVersion != \"N/A\" {\n\t\tdevice.CPLDs = append(device.CPLDs, &common.CPLD{\n\t\t\tCommon: common.Common{\n\t\t\t\tVendor:   device.Vendor,\n\t\t\t\tModel:    device.Model,\n\t\t\t\tFirmware: &common.Firmware{Installed: fwInfo.CPLDVersion},\n\t\t\t},\n\t\t})\n\t}\n\n\tdevice.Metadata[\"node_id\"] = fwInfo.NodeID\n\n\tswitch device.Model {\n\tcase E3C246D4ID_NL, E3C246D4I_NL:\n\t\treturn a.componentAttributesE3C246(ctx, fwInfo, device)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc (a *ASRockRack) componentAttributesE3C246(ctx context.Context, fwInfo *firmwareInfo, device *common.Device) error {\n\t// TODO: implement newer device inventory\n\tcomponents, err := a.inventoryInfoE3C246D41D(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, component := range components {\n\t\tswitch component.DeviceType {\n\t\tcase \"CPU\":\n\t\t\tdevice.CPUs = append(device.CPUs,\n\t\t\t\t&common.CPU{\n\t\t\t\t\tCommon: common.Common{\n\t\t\t\t\t\tVendor: component.ProductManufacturerName,\n\t\t\t\t\t\tModel:  component.ProductName,\n\t\t\t\t\t\tFirmware: &common.Firmware{\n\t\t\t\t\t\t\tInstalled: fwInfo.MicrocodeVersion,\n\t\t\t\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\t\t\t\t\"Intel_ME_version\": fwInfo.MEVersion,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t)\n\t\tcase \"Memory\":\n\t\t\tdevice.Memory = append(device.Memory,\n\t\t\t\t&common.Memory{\n\t\t\t\t\tCommon: common.Common{\n\t\t\t\t\t\tVendor:      component.ProductManufacturerName,\n\t\t\t\t\t\tSerial:      component.ProductSerialNumber,\n\t\t\t\t\t\tDescription: component.ProductExtra,\n\t\t\t\t\t},\n\n\t\t\t\t\tPartNumber: component.ProductPartNumber,\n\t\t\t\t\tType:       component.DeviceName,\n\t\t\t\t},\n\t\t\t)\n\n\t\tcase \"Storage device\":\n\t\t\tvar vendor string\n\n\t\t\tif component.ProductManufacturerName == \"N/A\" &&\n\t\t\t\tcomponent.ProductPartNumber != \"N/A\" {\n\t\t\t\tvendor = common.FormatVendorName(component.ProductPartNumber)\n\t\t\t}\n\n\t\t\tdevice.Drives = append(device.Drives,\n\t\t\t\t&common.Drive{\n\t\t\t\t\tCommon: common.Common{\n\t\t\t\t\t\tVendor:      vendor,\n\t\t\t\t\t\tSerial:      component.ProductSerialNumber,\n\t\t\t\t\t\tProductName: component.ProductPartNumber,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t)\n\t\t}\n\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "providers/asrockrack/inventory_test.go",
    "content": "package asrockrack\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestGetInventory(t *testing.T) {\n\tdevice, err := aClient.Inventory(context.TODO())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\taClient.deviceModel = E3C246D4I_NL\n\tassert.NotNil(t, device)\n\tassert.Equal(t, \"ASRockRack\", device.Vendor)\n\tassert.Equal(t, E3C246D4I_NL, device.Model)\n\n\tassert.Equal(t, \"L2.07B\", device.BIOS.Firmware.Installed)\n\tassert.Equal(t, \"0.01.00\", device.BMC.Firmware.Installed)\n\tassert.Equal(t, \"000000ca\", device.CPUs[0].Firmware.Installed)\n\tassert.Equal(t, \"Intel(R) Xeon(R) E-2278G CPU @ 3.40GHz\", device.CPUs[0].Model)\n\tassert.Equal(t, 2, len(device.Memory))\n\tassert.Equal(t, 2, len(device.Drives))\n\tassert.Equal(t, \"OK\", device.Status.Health)\n}\n"
  },
  {
    "path": "providers/asrockrack/mock_test.go",
    "content": "package asrockrack\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/bombsimon/logrusr/v2\"\n\t\"github.com/sirupsen/logrus\"\n)\n\nvar (\n\tloginPayload           = []byte(`username=foo&password=bar&certlogin=0`)\n\tloginResponse          = []byte(`{ \"ok\": 0, \"privilege\": 4, \"extendedpriv\": 259, \"racsession_id\": 10, \"remote_addr\": \"136.144.50.145\", \"server_name\": \"10.230.148.171\", \"server_addr\": \"10.230.148.171\", \"HTTPSEnabled\": 1, \"CSRFToken\": \"l5L29IP7\" }`)\n\tfwinfoResponse         = []byte(`{ \"BMC_fw_version\": \"0.01.00\", \"BIOS_fw_version\": \"L2.07B\", \"ME_fw_version\": \"5.1.3.78\", \"Micro_Code_version\": \"000000ca\", \"CPLD_version\": \"N\\/A\", \"CM_version\": \"0.13.01\", \"BPB_version\": \"0.0.002.0\", \"Node_id\": \"2\" }`)\n\tfwUploadResponse       = []byte(`{\"cc\": 0}`)\n\tfwVerificationResponse = []byte(`[ { \"id\": 1, \"current_image_name\": \"ast2500e\", \"current_image_version1\": \"0.01.00\", \"current_image_version2\": \"\", \"new_image_version\": \"0.03.00\", \"section_status\": 0, \"verification_status\": 5 } ]`)\n\tfwUpgradeProgress      = []byte(`{ \"id\": 1, \"action\": \"Flashing...\", \"progress\": \"__PERCENT__% done         \", \"state\": __STATE__ }`)\n\tusersPayload           = []byte(`[ { \"id\": 1, \"name\": \"anonymous\", \"access\": 0, \"kvm\": 1, \"vmedia\": 1, \"snmp\": 0, \"prev_snmp\": 0, \"network_privilege\": \"administrator\", \"fixed_user_count\": 2, \"snmp_access\": \"\", \"OEMProprietary_level_Privilege\": 1, \"privilege_limit_serial\": \"none\", \"snmp_authentication_protocol\": \"\", \"snmp_privacy_protocol\": \"\", \"email_id\": \"\", \"email_format\": \"ami_format\", \"ssh_key\": \"Not Available\", \"creation_time\": 4802 }, { \"id\": 2, \"name\": \"admin\", \"access\": 1, \"kvm\": 1, \"vmedia\": 1, \"snmp\": 0, \"prev_snmp\": 0, \"network_privilege\": \"administrator\", \"fixed_user_count\": 2, \"snmp_access\": \"\", \"OEMProprietary_level_Privilege\": 1, \"privilege_limit_serial\": \"none\", \"snmp_authentication_protocol\": \"\", \"snmp_privacy_protocol\": \"\", \"email_id\": \"\", \"email_format\": \"ami_format\", \"ssh_key\": \"Not Available\", \"creation_time\": 188 }, { \"id\": 3, \"name\": \"foo\", \"access\": 1, \"kvm\": 1, \"vmedia\": 1, \"snmp\": 0, \"prev_snmp\": 0, \"network_privilege\": \"administrator\", \"fixed_user_count\": 2, \"snmp_access\": \"\", \"OEMProprietary_level_Privilege\": 1, \"privilege_limit_serial\": \"none\", \"snmp_authentication_protocol\": \"\", \"snmp_privacy_protocol\": \"\", \"email_id\": \"\", \"email_format\": \"ami_format\", \"ssh_key\": \"Not Available\", \"creation_time\": 4802 }, { \"id\": 4, \"name\": \"\", \"access\": 0, \"kvm\": 0, \"vmedia\": 0, \"snmp\": 0, \"prev_snmp\": 0, \"network_privilege\": \"\", \"fixed_user_count\": 2, \"snmp_access\": \"\", \"OEMProprietary_level_Privilege\": 1, \"privilege_limit_serial\": \"\", \"snmp_authentication_protocol\": \"\", \"snmp_privacy_protocol\": \"\", \"email_id\": \"\", \"email_format\": \"\", \"ssh_key\": \"Not Available\", \"creation_time\": 0 }, { \"id\": 5, \"name\": \"\", \"access\": 0, \"kvm\": 0, \"vmedia\": 0, \"snmp\": 0, \"prev_snmp\": 0, \"network_privilege\": \"\", \"fixed_user_count\": 2, \"snmp_access\": \"\", \"OEMProprietary_level_Privilege\": 1, \"privilege_limit_serial\": \"\", \"snmp_authentication_protocol\": \"\", \"snmp_privacy_protocol\": \"\", \"email_id\": \"\", \"email_format\": \"\", \"ssh_key\": \"Not Available\", \"creation_time\": 0 }, { \"id\": 6, \"name\": \"\", \"access\": 0, \"kvm\": 0, \"vmedia\": 0, \"snmp\": 0, \"prev_snmp\": 0, \"network_privilege\": \"\", \"fixed_user_count\": 2, \"snmp_access\": \"\", \"OEMProprietary_level_Privilege\": 1, \"privilege_limit_serial\": \"\", \"snmp_authentication_protocol\": \"\", \"snmp_privacy_protocol\": \"\", \"email_id\": \"\", \"email_format\": \"\", \"ssh_key\": \"Not Available\", \"creation_time\": 0 }, { \"id\": 7, \"name\": \"\", \"access\": 0, \"kvm\": 0, \"vmedia\": 0, \"snmp\": 0, \"prev_snmp\": 0, \"network_privilege\": \"\", \"fixed_user_count\": 2, \"snmp_access\": \"\", \"OEMProprietary_level_Privilege\": 1, \"privilege_limit_serial\": \"\", \"snmp_authentication_protocol\": \"\", \"snmp_privacy_protocol\": \"\", \"email_id\": \"\", \"email_format\": \"\", \"ssh_key\": \"Not Available\", \"creation_time\": 0 }, { \"id\": 8, \"name\": \"\", \"access\": 0, \"kvm\": 0, \"vmedia\": 0, \"snmp\": 0, \"prev_snmp\": 0, \"network_privilege\": \"\", \"fixed_user_count\": 2, \"snmp_access\": \"\", \"OEMProprietary_level_Privilege\": 1, \"privilege_limit_serial\": \"\", \"snmp_authentication_protocol\": \"\", \"snmp_privacy_protocol\": \"\", \"email_id\": \"\", \"email_format\": \"\", \"ssh_key\": \"Not Available\", \"creation_time\": 0 }, { \"id\": 9, \"name\": \"\", \"access\": 0, \"kvm\": 0, \"vmedia\": 0, \"snmp\": 0, \"prev_snmp\": 0, \"network_privilege\": \"\", \"fixed_user_count\": 2, \"snmp_access\": \"\", \"OEMProprietary_level_Privilege\": 1, \"privilege_limit_serial\": \"\", \"snmp_authentication_protocol\": \"\", \"snmp_privacy_protocol\": \"\", \"email_id\": \"\", \"email_format\": \"\", \"ssh_key\": \"Not Available\", \"creation_time\": 0 }, { \"id\": 10, \"name\": \"\", \"access\": 0, \"kvm\": 0, \"vmedia\": 0, \"snmp\": 0, \"prev_snmp\": 0, \"network_privilege\": \"\", \"fixed_user_count\": 2, \"snmp_access\": \"\", \"OEMProprietary_level_Privilege\": 1, \"privilege_limit_serial\": \"\", \"snmp_authentication_protocol\": \"\", \"snmp_privacy_protocol\": \"\", \"email_id\": \"\", \"email_format\": \"\", \"ssh_key\": \"Not Available\", \"creation_time\": 0 } ]`)\n\tinventoryinfoResponse  = []byte(`[ { \"device_id\": 1, \"device_name\": \"CPU1\", \"device_type\": \"CPU\", \"product_manufacturer_name\": \"Intel(R) Corporation\", \"product_name\": \"Intel(R) Xeon(R) E-2278G CPU @ 3.40GHz\", \"product_part_number\": \"N\\/A\", \"product_version\": \"N\\/A\", \"product_serial_number\": \"N\\/A\", \"product_asset_tag\": \"N\\/A\", \"product_extra\": \"N\\/A\" }, { \"device_id\": 5, \"device_name\": \"DDR4_A1\", \"device_type\": \"Memory\", \"product_manufacturer_name\": \"Micron\", \"product_name\": \"SODIMM\", \"product_part_number\": \"18ASF2G72HZ-2G6E1   \", \"product_version\": \"N\\/A\", \"product_serial_number\": \"2724B52D\", \"product_asset_tag\": \"N\\/A\", \"product_extra\": \"2666 MT\\/s  16GB\" }, { \"device_id\": 7, \"device_name\": \"DDR4_B1\", \"device_type\": \"Memory\", \"product_manufacturer_name\": \"Micron\", \"product_name\": \"SODIMM\", \"product_part_number\": \"18ASF2G72HZ-2G6E1   \", \"product_version\": \"N\\/A\", \"product_serial_number\": \"2724B58A\", \"product_asset_tag\": \"N\\/A\", \"product_extra\": \"2666 MT\\/s  16GB\" }, { \"device_id\": 37, \"device_name\": \"PCIe card 1\", \"device_type\": \"PCIe & OCP Card\", \"product_manufacturer_name\": \"8086(Intel Corporation)\", \"product_name\": \"020000(Ethernet controller)\", \"product_part_number\": \"1572\", \"product_version\": \"N\\/A\", \"product_serial_number\": \"N\\/A\", \"product_asset_tag\": \"PCIE7\", \"product_extra\": \"N\\/A\" }, { \"device_id\": 105, \"device_name\": \"Storage \", \"device_type\": \"Storage device\", \"product_manufacturer_name\": \"N\\/A\", \"product_name\": \"N\\/A\", \"product_part_number\": \"INTEL SSDSC2KB480G8\", \"product_version\": \"N\\/A\", \"product_serial_number\": \"PHYF001303ED480BGN\", \"product_asset_tag\": \"SATA_4\", \"product_extra\": \"N\\/A\" }, { \"device_id\": 106, \"device_name\": \"Storage \", \"device_type\": \"Storage device\", \"product_manufacturer_name\": \"N\\/A\", \"product_name\": \"N\\/A\", \"product_part_number\": \"INTEL SSDSC2KB480G8\", \"product_version\": \"N\\/A\", \"product_serial_number\": \"BTYF01940L38480BGN\", \"product_asset_tag\": \"SATA_5\", \"product_extra\": \"N\\/A\" } ]`)\n\tfruinfoResponse        = []byte(`[ { \"device\": { \"id\": 0, \"name\": \"BMC_FRU\" }, \"common_header\": { \"version\": 1, \"internal_use_area_start_offset\": 0, \"chassis_info_area_start_offset\": 1, \"board_info_area_start_offset\": 4, \"product_info_area_start_offset\": 11, \"multi_record_area_start_offset\": 0 }, \"chassis\": { \"version\": 1, \"length\": 3, \"type\": \"Main Server Chassis\", \"part_number\": \"\", \"serial_number\": \"K61206147700263\", \"custom_fields\": \"\" }, \"board\": { \"version\": 1, \"length\": 7, \"language\": 0, \"date\": \"Mon Jul 20 06:04:00 2020\\\\n\", \"manufacturer\": \"ASRockRack\", \"product_name\": \"E3C246D4I-NL\", \"serial_number\": \"197965920000514\", \"part_number\": \"\", \"fru_file_id\": \"\", \"custom_fields\": \"\" }, \"product\": { \"version\": 1, \"length\": 7, \"language\": 0, \"manufacturer\": \"Packet\", \"product_name\": \"c3.small.x86\", \"part_number\": \"Open19\", \"product_version\": \"R1.00\", \"serial_number\": \"D6S0R8000736\", \"asset_tag\": \"\", \"fru_file_id\": \"\", \"custom_fields\": \"\" } } ]`)\n\tbiosPOSTCodeResponse   = []byte(`{ \"poststatus\": 1, \"postdata\": 160 }`)\n\tchassisStatusResponse  = []byte(`{ \"power_status\": 1, \"led_status\": 0 }`)\n\n\t// TODO: implement under rw mutex\n\thttpRequestTestVar *http.Request\n)\n\n// setup test BMC\nvar server *httptest.Server\nvar bmcURL *url.URL\nvar fwUpgradeState *testFwUpgradeState\n\ntype testFwUpgradeState struct {\n\tFlashModeSet     bool\n\tFirmwareUploaded bool\n\tFirmwareVerified bool\n\tUpgradeInitiated bool\n\tUpgradePercent   int\n\tResetDone        bool\n}\n\n// the bmc lib client\nvar aClient *ASRockRack\n\nfunc TestMain(m *testing.M) {\n\t// setup mock server\n\tserver = mockASRockBMC()\n\tbmcURL, _ = url.Parse(server.URL)\n\n\tl := logrus.New()\n\tl.Level = logrus.DebugLevel\n\t// setup bmc client\n\ttLog := logrusr.New(l)\n\taClient = New(bmcURL.Host, \"foo\", \"bar\", tLog)\n\n\t// firmware update test state\n\tfwUpgradeState = &testFwUpgradeState{}\n\tos.Exit(m.Run())\n}\n\n// ///////////// mock bmc service ///////////////////////////\nfunc mockASRockBMC() *httptest.Server {\n\thandler := http.NewServeMux()\n\thandler.HandleFunc(\"/\", index)\n\thandler.HandleFunc(\"/api/session\", session)\n\thandler.HandleFunc(\"/api/asrr/fw-info\", fwinfo)\n\thandler.HandleFunc(\"/api/fru\", fruinfo)\n\thandler.HandleFunc(\"/api/asrr/inventory_info\", inventoryinfo)\n\thandler.HandleFunc(\"/api/sensors\", sensorsinfo)\n\thandler.HandleFunc(\"/api/asrr/getbioscode\", biosPOSTCodeinfo)\n\thandler.HandleFunc(\"/api/chassis-status\", chassisStatusInfo)\n\n\t// fw update endpoints - in order of invocation\n\thandler.HandleFunc(\"/api/maintenance/flash\", bmcFirmwareUpgrade)\n\thandler.HandleFunc(\"/api/maintenance/firmware\", bmcFirmwareUpgrade)\n\thandler.HandleFunc(\"/api/maintenance/firmware/firmware\", bmcFirmwareUpgrade)\n\thandler.HandleFunc(\"/api/maintenance/firmware/verification\", bmcFirmwareUpgrade)\n\thandler.HandleFunc(\"/api/maintenance/firmware/upgrade\", bmcFirmwareUpgrade)\n\thandler.HandleFunc(\"/api/maintenance/firmware/flash-progress\", bmcFirmwareUpgrade)\n\thandler.HandleFunc(\"/api/maintenance/reset\", bmcFirmwareUpgrade)\n\thandler.HandleFunc(\"/api/asrr/maintenance/BIOS/firmware\", biosFirmwareUpgrade)\n\n\t// user accounts endpoints\n\thandler.HandleFunc(\"/api/settings/users\", userAccountList)\n\thandler.HandleFunc(\"/api/settings/users/3\", userAccountList)\n\treturn httptest.NewTLSServer(handler)\n}\n\nfunc index(w http.ResponseWriter, r *http.Request) {\n\tswitch r.Method {\n\tcase \"GET\":\n\t\t_, _ = w.Write([]byte(`ASRockRack`))\n\t}\n}\n\nfunc userAccountList(w http.ResponseWriter, r *http.Request) {\n\tswitch r.Method {\n\tcase \"GET\":\n\t\tif os.Getenv(\"TEST_FAIL_QUERY\") != \"\" {\n\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t} else {\n\t\t\t_, _ = w.Write(usersPayload)\n\t\t}\n\tcase \"PUT\":\n\t\thttpRequestTestVar = r\n\t}\n}\n\nfunc biosFirmwareUpgrade(w http.ResponseWriter, r *http.Request) {\n\tswitch r.Method {\n\tcase \"POST\":\n\t\tswitch r.RequestURI {\n\t\tcase \"/api/asrr/maintenance/BIOS/firmware\":\n\n\t\t\t// validate content type\n\t\t\tif !strings.Contains(r.Header.Get(\"Content-Type\"), \"multipart/form-data\") {\n\t\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\t}\n\n\t\t\t// parse multipart form\n\t\t\terr := r.ParseMultipartForm(100)\n\t\t\tif err != nil {\n\t\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc bmcFirmwareUpgrade(w http.ResponseWriter, r *http.Request) {\n\tswitch r.Method {\n\tcase \"GET\":\n\t\tswitch r.RequestURI {\n\t\t// 3. bmc verifies uploaded firmware image\n\t\tcase \"/api/maintenance/firmware/verification\":\n\t\t\tif !fwUpgradeState.FirmwareUploaded {\n\t\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\t}\n\t\t\tfwUpgradeState.FirmwareVerified = true\n\t\t\t_, _ = w.Write(fwVerificationResponse)\n\t\t// 5. flash progress\n\t\tcase \"/api/maintenance/firmware/flash-progress\":\n\t\t\tif !fwUpgradeState.UpgradeInitiated {\n\t\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\t}\n\n\t\t\tresp := fwUpgradeProgress\n\t\t\tif fwUpgradeState.UpgradePercent >= 100 {\n\t\t\t\tfwUpgradeState.UpgradePercent = 100\n\t\t\t\t// state: 2  indicates firmware flash complete\n\t\t\t\tresp = bytes.Replace(resp, []byte(\"__STATE__\"), []byte(strconv.Itoa(2)), 1)\n\t\t\t} else {\n\t\t\t\t// state: 0 indicates firmware flash in progress\n\t\t\t\tresp = bytes.Replace(resp, []byte(\"__STATE__\"), []byte(strconv.Itoa(0)), 1)\n\t\t\t\tfwUpgradeState.UpgradePercent += 50\n\t\t\t}\n\n\t\t\tresp = bytes.Replace(resp, []byte(\"__PERCENT__\"), []byte(strconv.Itoa(fwUpgradeState.UpgradePercent)), 1)\n\t\t\t_, _ = w.Write(resp)\n\t\t}\n\tcase \"PUT\":\n\n\t\tswitch r.RequestURI {\n\t\t// 1. set device to flash mode\n\t\tcase \"/api/maintenance/flash\":\n\t\t\tfwUpgradeState.FlashModeSet = true\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t// 4. run the upgrade\n\t\tcase \"/api/maintenance/firmware/upgrade\":\n\t\t\tif !fwUpgradeState.FirmwareVerified {\n\t\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\t}\n\n\t\t\tif r.Header.Get(\"Content-Type\") != \"application/json\" {\n\t\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\t}\n\n\t\t\tp := &preserveConfig{}\n\t\t\terr := json.NewDecoder(r.Body).Decode(&p)\n\t\t\tif err != nil {\n\t\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// config should be preserved\n\t\t\tif p.PreserveConfig != 1 {\n\t\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\t}\n\n\t\t\t// full firmware flash\n\t\t\tif p.FlashStatus != 1 {\n\t\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\t}\n\n\t\t\tfwUpgradeState.UpgradeInitiated = true\n\t\t\t// respond with request body\n\t\t\tb := new(bytes.Buffer)\n\t\t\t_, _ = b.ReadFrom(r.Body)\n\t\t\t_, _ = w.Write(b.Bytes())\n\t\t}\n\tcase \"POST\":\n\t\tswitch r.RequestURI {\n\t\tcase \"/api/maintenance/reset\":\n\t\t\tw.WriteHeader(http.StatusOK)\n\n\t\t// 2. upload firmware\n\t\tcase \"/api/maintenance/firmware\", \"/api/maintenance/firmware/firmware\":\n\n\t\t\t// validate flash mode set\n\t\t\tif !fwUpgradeState.FlashModeSet {\n\t\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\t}\n\n\t\t\t// validate content type\n\t\t\tif !strings.Contains(r.Header.Get(\"Content-Type\"), \"multipart/form-data\") {\n\t\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\t}\n\n\t\t\t// parse multipart form\n\t\t\terr := r.ParseMultipartForm(100)\n\t\t\tif err != nil {\n\t\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\t}\n\n\t\t\tfwUpgradeState.FirmwareUploaded = true\n\t\t\t_, _ = w.Write(fwUploadResponse)\n\t\t}\n\tdefault:\n\t\tw.WriteHeader(http.StatusBadRequest)\n\t}\n}\n\nfunc fwinfo(w http.ResponseWriter, r *http.Request) {\n\tswitch r.Method {\n\tcase \"GET\":\n\t\t_, _ = w.Write(fwinfoResponse)\n\t}\n}\n\nfunc fruinfo(w http.ResponseWriter, r *http.Request) {\n\tswitch r.Method {\n\tcase \"GET\":\n\t\t_, _ = w.Write(fruinfoResponse)\n\t}\n}\n\nfunc inventoryinfo(w http.ResponseWriter, r *http.Request) {\n\tswitch r.Method {\n\tcase \"GET\":\n\t\t_, _ = w.Write(inventoryinfoResponse)\n\t}\n}\n\nfunc sensorsinfo(w http.ResponseWriter, r *http.Request) {\n\tswitch r.Method {\n\tcase \"GET\":\n\t\tfh, err := os.Open(\"./fixtures/E3C246D4I-NL/sensors.json\")\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\n\t\tb, err := io.ReadAll(fh)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\t_, _ = w.Write(b)\n\t}\n}\n\nfunc biosPOSTCodeinfo(w http.ResponseWriter, r *http.Request) {\n\tswitch r.Method {\n\tcase \"GET\":\n\t\t_, _ = w.Write(biosPOSTCodeResponse)\n\t}\n}\n\nfunc chassisStatusInfo(w http.ResponseWriter, r *http.Request) {\n\tswitch r.Method {\n\tcase \"GET\":\n\t\t_, _ = w.Write(chassisStatusResponse)\n\t}\n}\n\nfunc session(w http.ResponseWriter, r *http.Request) {\n\tswitch r.Method {\n\tcase \"POST\":\n\t\t// login to BMC\n\t\tb, _ := io.ReadAll(r.Body)\n\t\tif string(b) == string(loginPayload) {\n\t\t\t// login request needs to be of the right content-typ\n\t\t\tif r.Header.Get(\"Content-Type\") != \"application/x-www-form-urlencoded\" {\n\t\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t\t}\n\n\t\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\t\thttp.SetCookie(w, &http.Cookie{Name: \"QSESSIONID\", Value: \"94ed00f482249dd77arIcp6eBBJaik\", Path: \"/\"})\n\t\t\t_, _ = w.Write(loginResponse)\n\t\t} else {\n\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t}\n\tcase \"DELETE\":\n\t\tif r.Header.Get(\"X-Csrftoken\") != \"l5L29IP7\" {\n\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "providers/asrockrack/power.go",
    "content": "package asrockrack\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/pkg/errors\"\n)\n\ntype power struct {\n\tCommand int `json:\"power_command\"`\n}\n\n// PowerStateGet gets the power state of a machine\nfunc (a *ASRockRack) PowerStateGet(ctx context.Context) (state string, err error) {\n\tinfo, err := a.chassisStatusInfo(ctx)\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"401\") {\n\t\t\t// during a BMC update, only the flash-progress endpoint can be queried\n\t\t\t// and so we cannot determine server power status\n\t\t\t// we don't return an error here because we don't want the bmclib client to retry another provider.\n\t\t\tprogress, err := a.flashProgress(ctx, \"/api/maintenance/firmware/flash-progress\")\n\t\t\tif err == nil && progress.Action != \"\" {\n\t\t\t\ta.log.V(2).WithValues(\n\t\t\t\t\t\"action\", progress.Action,\n\t\t\t\t\t\"progress\", progress.Progress,\n\t\t\t\t\t\"state\", progress.State,\n\t\t\t\t).Info(\"bmc in flash mode, power status cannot be determined\")\n\n\t\t\t\treturn \"\", errors.Wrap(\n\t\t\t\t\tbmclibErrs.ErrBMCUpdating,\n\t\t\t\t\tfmt.Sprintf(\n\t\t\t\t\t\t\"action: %s, progress: %s, state: %d\",\n\t\t\t\t\t\tprogress.Action,\n\t\t\t\t\t\tprogress.Progress,\n\t\t\t\t\t\tprogress.State,\n\t\t\t\t\t),\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\n\t\treturn \"\", errors.Wrap(bmclibErrs.ErrPowerStatusRead, err.Error())\n\t}\n\n\tswitch info.PowerStatus {\n\tcase 0:\n\t\treturn \"Off\", nil\n\tcase 1:\n\t\treturn \"On\", nil\n\tdefault:\n\t\treturn \"\", errors.Wrap(\n\t\t\tbmclibErrs.ErrPowerStatusRead,\n\t\t\tfmt.Errorf(\"unknown status: %d\", info.PowerStatus).Error(),\n\t\t)\n\t}\n}\n\n// PowerSet sets the hardware power state of a machine\nfunc (a *ASRockRack) PowerSet(ctx context.Context, state string) (ok bool, err error) {\n\tswitch strings.ToLower(state) {\n\tcase \"on\":\n\t\treturn a.powerAction(ctx, 1)\n\tcase \"off\":\n\t\treturn a.powerAction(ctx, 0)\n\tcase \"soft\":\n\t\treturn a.powerAction(ctx, 5)\n\tcase \"reset\":\n\t\treturn a.powerAction(ctx, 3)\n\tcase \"cycle\":\n\t\treturn a.powerAction(ctx, 2)\n\tdefault:\n\t\treturn false, errors.New(\"requested power state unknown: \" + state)\n\t}\n}\n\nfunc (a *ASRockRack) powerAction(ctx context.Context, action int) (ok bool, err error) {\n\tendpoint := \"/api/actions/power\"\n\n\tp := power{Command: action}\n\tpayload, err := json.Marshal(p)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\theaders := map[string]string{\"Content-Type\": \"application/json\"}\n\t_, statusCode, err := a.queryHTTPS(\n\t\tctx,\n\t\tendpoint,\n\t\t\"POST\",\n\t\tbytes.NewReader(payload),\n\t\theaders,\n\t\t0,\n\t)\n\tif err != nil {\n\t\treturn false, errors.Wrap(bmclibErrs.ErrPowerStatusSet, err.Error())\n\t}\n\n\tif statusCode != http.StatusOK {\n\t\treturn false, errors.Wrap(\n\t\t\tbmclibErrs.ErrNon200Response,\n\t\t\tfmt.Errorf(\"%d\", statusCode).Error(),\n\t\t)\n\t}\n\n\treturn true, nil\n}\n\n// BmcReset will reset the BMC - ASRR BMCs only support a cold reset.\nfunc (a *ASRockRack) BmcReset(ctx context.Context, resetType string) (ok bool, err error) {\n\terr = a.resetBMC(ctx)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treturn true, nil\n}\n\n// 4. reset BMC - performs a cold reset\nfunc (a *ASRockRack) resetBMC(ctx context.Context) error {\n\tendpoint := \"api/maintenance/reset\"\n\n\t_, statusCode, err := a.queryHTTPS(ctx, endpoint, \"POST\", nil, nil, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// The E3C256D4ID BMC returns a 500 status error on the BMC reset request\n\tif statusCode != http.StatusOK && statusCode != http.StatusInternalServerError {\n\t\treturn fmt.Errorf(\"non 200 response: %d\", statusCode)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "providers/asrockrack/user.go",
    "content": "package asrockrack\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/bmc-toolbox/bmclib/v2/internal\"\n)\n\nvar (\n\t// TODO: standardize these across Redfish, IPMI, Vendor GUI\n\tvalidRoles = []string{\"Administrator\", \"Operator\", \"User\"}\n)\n\n// UserAccount is a ASRR BMC user account struct\ntype UserAccount struct {\n\tID                           int    `json:\"id\"`\n\tName                         string `json:\"name\"`\n\tAccess                       int    `json:\"access\"`\n\tAccessByChannel              string `json:\"accessByChannel,omitempty\"`\n\tKvm                          int    `json:\"kvm\"`\n\tVmedia                       int    `json:\"vmedia\"`\n\tNetworkPrivilege             string `json:\"network_privilege\"`\n\tFixedUserCount               int    `json:\"fixed_user_count\"`\n\tOEMProprietaryLevelPrivilege int    `json:\"OEMProprietary_level_Privilege\"`\n\tPrivilege                    string `json:\"privilege,omitempty\"`\n\tPrivilegeByChannel           string `json:\"privilegeByChannel,omitempty\"`\n\tPrivilegeLimitSerial         string `json:\"privilege_limit_serial\"`\n\tSSHKey                       string `json:\"ssh_key\"`\n\tCreationTime                 int    `json:\"creation_time\"`\n\tChangepassword               int    `json:\"changepassword\"`\n\tUserOperation                int    `json:\"UserOperation\"`\n\tPassword                     string `json:\"password\"`\n\tConfirmPassword              string `json:\"confirm_password\"`\n\tPasswordSize                 string `json:\"password_size\"`\n\tPrevSNMP                     int    `json:\"prev_snmp\"`\n\tSNMP                         int    `json:\"snmp\"`\n\tSNMPAccess                   string `json:\"snmp_access\"`\n\tSNMPAuthenticationProtocol   string `json:\"snmp_authentication_protocol\"`\n\tEmailFormat                  string `json:\"email_format\"`\n\tEmailID                      string `json:\"email_id\"`\n}\n\n// UserRead returns a list of enabled user accounts\nfunc (a *ASRockRack) UserRead(ctx context.Context) (users []map[string]string, err error) {\n\terr = a.Open(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\taccounts, err := a.listUsers(ctx)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(bmclibErrs.ErrRetrievingUserAccounts, err.Error())\n\t}\n\n\tusers = make([]map[string]string, 0)\n\tfor _, account := range accounts {\n\t\tif account.Access == 1 {\n\t\t\tuser := map[string]string{\n\t\t\t\t\"ID\":     fmt.Sprintf(\"%d\", account.ID),\n\t\t\t\t\"Name\":   account.Name,\n\t\t\t\t\"RoleID\": account.NetworkPrivilege,\n\t\t\t}\n\t\t\tusers = append(users, user)\n\t\t}\n\t}\n\n\treturn users, nil\n}\n\n// UserCreate adds a new user account\nfunc (a *ASRockRack) UserCreate(ctx context.Context, user, pass, role string) (ok bool, err error) {\n\tif !internal.StringInSlice(role, validRoles) {\n\t\treturn false, bmclibErrs.ErrInvalidUserRole\n\t}\n\n\tif user == \"\" || pass == \"\" || role == \"\" {\n\t\treturn false, bmclibErrs.ErrUserParamsRequired\n\t}\n\n\t// fetch current list of accounts\n\taccounts, err := a.listUsers(ctx)\n\tif err != nil {\n\t\treturn false, errors.Wrap(bmclibErrs.ErrRetrievingUserAccounts, err.Error())\n\t}\n\n\t// identify account slot not in use\n\tfor _, account := range accounts {\n\t\t// ASRR BMCs have a reserved slot 1 for a disabled Anonymous, no idea why.\n\t\tif account.ID == 1 {\n\t\t\tcontinue\n\t\t}\n\n\t\taccount := account\n\t\tif account.Name == user {\n\t\t\treturn false, errors.Wrap(bmclibErrs.ErrUserAccountExists, user)\n\t\t}\n\n\t\tif account.Access == 0 && account.Name == \"\" {\n\t\t\tnewAccount := newUserAccount(account.ID, user, pass, strings.ToLower(role))\n\t\t\terr := a.createUpdateUser(ctx, newAccount)\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t\treturn true, nil\n\t\t}\n\t}\n\n\treturn false, bmclibErrs.ErrNoUserSlotsAvailable\n}\n\n//\n\n// UserUpdate updates a user password and role\nfunc (a *ASRockRack) UserUpdate(ctx context.Context, user, pass, role string) (ok bool, err error) {\n\tif !internal.StringInSlice(role, validRoles) {\n\t\treturn false, bmclibErrs.ErrInvalidUserRole\n\t}\n\n\tif user == \"\" || pass == \"\" || role == \"\" {\n\t\treturn false, bmclibErrs.ErrUserParamsRequired\n\t}\n\n\taccounts, err := a.listUsers(ctx)\n\tif err != nil {\n\t\treturn false, errors.Wrap(bmclibErrs.ErrRetrievingUserAccounts, err.Error())\n\t}\n\n\trole = strings.ToLower(role)\n\n\t// identify account slot not in use\n\tfor _, account := range accounts {\n\t\taccount := account\n\t\tif account.Name == user {\n\t\t\tuser := newUserAccount(account.ID, user, pass, role)\n\n\t\t\tuser.AccessByChannel = account.AccessByChannel\n\t\t\tuser.PrivilegeByChannel = account.PrivilegeByChannel\n\t\t\tuser.Privilege = role\n\n\t\t\tif role == \"administrator\" {\n\t\t\t\tuser.PrivilegeLimitSerial = \"none\"\n\t\t\t\tuser.UserOperation = 1\n\t\t\t\tuser.CreationTime = 6000 // doesn't mean anything.\n\t\t\t}\n\n\t\t\terr := a.createUpdateUser(ctx, user)\n\t\t\tif err != nil {\n\t\t\t\treturn false, errors.Wrap(bmclibErrs.ErrUserAccountUpdate, err.Error())\n\t\t\t}\n\n\t\t\treturn true, nil\n\t\t}\n\t}\n\n\treturn ok, errors.Wrap(bmclibErrs.ErrUserAccountNotFound, user)\n}\n\n// newUserAccount returns a user account object populated with the given attributes and certain defaults\n//\n// note: the role parameter must be validated before being passed to this constructor\nfunc newUserAccount(id int, user, pass, role string) *UserAccount {\n\treturn &UserAccount{\n\t\tID:                           id,\n\t\tName:                         user,\n\t\tAccess:                       1, // Access enabled\n\t\tKvm:                          1,\n\t\tVmedia:                       1,\n\t\tNetworkPrivilege:             role,\n\t\tFixedUserCount:               2, // No idea what this is about\n\t\tOEMProprietaryLevelPrivilege: 1,\n\t\tPrivilegeLimitSerial:         role,\n\t\tSSHKey:                       \"Not Available\",\n\t\tCreationTime:                 0,\n\t\tChangepassword:               1,\n\t\tUserOperation:                0,\n\t\tPassword:                     pass,\n\t\tConfirmPassword:              pass,\n\t\tPasswordSize:                 \"bytes_16\", // bytes_20 for larger passwords\n\t\tEmailFormat:                  \"AMI-Format\",\n\t}\n}\n"
  },
  {
    "path": "providers/asrockrack/user_test.go",
    "content": "package asrockrack\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/http\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n)\n\n// NOTE: user accounts are defined in mock_test.go as JSON payload in the userPayload var\n\ntype testCase struct {\n\tuser  string\n\tpass  string\n\trole  string\n\tok    bool\n\terr   error\n\ttName string\n}\n\nvar (\n\t// common set of test cases\n\ttestCases = []testCase{\n\n\t\t{\n\t\t\t\"foo\",\n\t\t\t\"baz\",\n\t\t\t\"\",\n\t\t\tfalse,\n\t\t\tbmclibErrs.ErrInvalidUserRole,\n\t\t\t\"role not defined\",\n\t\t},\n\t\t{\n\t\t\t\"foo\",\n\t\t\t\"\",\n\t\t\t\"Administrator\",\n\t\t\tfalse,\n\t\t\tbmclibErrs.ErrUserParamsRequired,\n\t\t\t\"param not defined\",\n\t\t},\n\t}\n)\n\nfunc Test_UserRead(t *testing.T) {\n\texpected := []map[string]string{\n\t\t{\n\t\t\t\"RoleID\": \"administrator\",\n\t\t\t\"ID\":     \"2\",\n\t\t\t\"Name\":   \"admin\",\n\t\t},\n\t\t{\n\t\t\t\"ID\":     \"3\",\n\t\t\t\"Name\":   \"foo\",\n\t\t\t\"RoleID\": \"administrator\",\n\t\t},\n\t}\n\n\terr := aClient.httpsLogin(context.TODO())\n\tif err != nil {\n\t\tt.Errorf(\"login: %s\", err.Error())\n\t}\n\n\tusers, err := aClient.UserRead(context.TODO())\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tassert.Equal(t, expected, users)\n\n\tfor _, tt := range testCases {\n\t\tok, err := aClient.UserCreate(context.TODO(), tt.user, tt.pass, tt.role)\n\t\tassert.Equal(t, errors.Is(err, tt.err), true, tt.tName)\n\t\tassert.Equal(t, tt.ok, ok, tt.tName)\n\t}\n\n\t// test account retrieval failure error\n\tos.Setenv(\"TEST_FAIL_QUERY\", \"womp womp\")\n\tdefer os.Unsetenv(\"TEST_FAIL_QUERY\")\n\n\t_, err = aClient.UserRead(context.TODO())\n\tassert.Equal(t, errors.Is(err, bmclibErrs.ErrRetrievingUserAccounts), true)\n}\n\nfunc Test_UserCreate(t *testing.T) {\n\n\ttests := testCases\n\ttests = append(tests,\n\t\t[]testCase{{\n\t\t\t\"root\",\n\t\t\t\"calvin\",\n\t\t\t\"Administrator\",\n\t\t\ttrue,\n\t\t\tnil,\n\t\t\t\"user account is created\",\n\t\t},\n\t\t\t{\n\t\t\t\t\"admin\",\n\t\t\t\t\"foo\",\n\t\t\t\t\"Administrator\",\n\t\t\t\tfalse,\n\t\t\t\tbmclibErrs.ErrUserAccountExists,\n\t\t\t\t\"account already exists\",\n\t\t\t},\n\t\t}...,\n\t)\n\n\terr := aClient.httpsLogin(context.TODO())\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tfor _, tt := range tests {\n\t\tok, err := aClient.UserCreate(context.TODO(), tt.user, tt.pass, tt.role)\n\t\tassert.Equal(t, errors.Is(err, tt.err), true, tt.tName)\n\t\tassert.Equal(t, tt.ok, ok, tt.tName)\n\t}\n}\n\nfunc Test_UserUpdate(t *testing.T) {\n\ttests := testCases\n\ttests = append(tests,\n\t\t[]testCase{\n\t\t\t{\n\t\t\t\t\"admin\",\n\t\t\t\t\"calvin\",\n\t\t\t\t\"Administrator\",\n\t\t\t\ttrue,\n\t\t\t\tnil,\n\t\t\t\t\"user account is updated\",\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"badmin\",\n\t\t\t\t\"calvin\",\n\t\t\t\t\"Administrator\",\n\t\t\t\tfalse,\n\t\t\t\tbmclibErrs.ErrUserAccountNotFound,\n\t\t\t\t\"user account not present\",\n\t\t\t},\n\t\t}...,\n\t)\n\n\terr := aClient.httpsLogin(context.TODO())\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tfor _, tt := range tests {\n\t\tok, err := aClient.UserUpdate(context.TODO(), tt.user, tt.pass, tt.role)\n\t\tassert.Equal(t, errors.Is(err, tt.err), true, tt.tName)\n\t\tassert.Equal(t, tt.ok, ok, tt.tName)\n\t}\n}\n\nfunc Test_createUser(t *testing.T) {\n\terr := aClient.httpsLogin(context.TODO())\n\tif err != nil {\n\t\tt.Errorf(\"login: %s\", err.Error())\n\t}\n\n\taccount := &UserAccount{\n\t\tID:                           3,\n\t\tName:                         \"foobar\",\n\t\tAccess:                       1,\n\t\tKvm:                          1,\n\t\tVmedia:                       1,\n\t\tNetworkPrivilege:             \"administrator\",\n\t\tFixedUserCount:               2,\n\t\tOEMProprietaryLevelPrivilege: 1,\n\t\tPrivilegeLimitSerial:         \"none\",\n\t\tSSHKey:                       \"Not Available\",\n\t\tCreationTime:                 4802,\n\t\tChangepassword:               0,\n\t\tUserOperation:                0,\n\t\tPassword:                     \"\",\n\t\tConfirmPassword:              \"\",\n\t\tPasswordSize:                 \"\",\n\t}\n\n\terr = aClient.createUpdateUser(context.TODO(), account)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tassert.Equal(t, \"/api/settings/users/3\", httpRequestTestVar.URL.String())\n\tassert.Equal(t, http.MethodPut, httpRequestTestVar.Method)\n\tvar contentType string\n\tfor k, v := range httpRequestTestVar.Header {\n\t\tif k == \"Content-Type\" {\n\t\t\tcontentType = v[0]\n\t\t}\n\t}\n\n\tassert.Equal(t, \"application/json\", contentType)\n\n}\n\nfunc Test_userAccounts(t *testing.T) {\n\terr := aClient.httpsLogin(context.TODO())\n\tif err != nil {\n\t\tt.Errorf(\"login: %s\", err.Error())\n\t}\n\n\taccount0 := &UserAccount{\n\t\tID:                           1,\n\t\tName:                         \"anonymous\",\n\t\tAccess:                       0,\n\t\tKvm:                          1,\n\t\tVmedia:                       1,\n\t\tNetworkPrivilege:             \"administrator\",\n\t\tFixedUserCount:               2,\n\t\tOEMProprietaryLevelPrivilege: 1,\n\t\tPrivilegeLimitSerial:         \"none\",\n\t\tSSHKey:                       \"Not Available\",\n\t\tCreationTime:                 4802,\n\t\tChangepassword:               0,\n\t\tUserOperation:                0,\n\t\tPassword:                     \"\",\n\t\tConfirmPassword:              \"\",\n\t\tPasswordSize:                 \"\",\n\t\tEmailFormat:                  \"ami_format\",\n\t}\n\n\taccounts, err := aClient.listUsers(context.TODO())\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tassert.Equal(t, 10, len(accounts))\n\tassert.Equal(t, account0, accounts[0])\n}\n"
  },
  {
    "path": "providers/dell/firmware.go",
    "content": "package dell\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/constants\"\n\tbmcliberrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\trfw \"github.com/bmc-toolbox/bmclib/v2/internal/redfishwrapper\"\n\t\"github.com/bmc-toolbox/common\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/stmcginnis/gofish/schemas\"\n)\n\n// bmc client interface implementations methods\nfunc (c *Conn) FirmwareInstallSteps(ctx context.Context, component string) ([]constants.FirmwareInstallStep, error) {\n\tif err := c.deviceSupported(ctx); err != nil {\n\t\treturn nil, bmcliberrs.NewErrUnsupportedHardware(err.Error())\n\t}\n\n\treturn []constants.FirmwareInstallStep{\n\t\tconstants.FirmwareInstallStepUploadInitiateInstall,\n\t\tconstants.FirmwareInstallStepInstallStatus,\n\t}, nil\n}\n\nfunc (c *Conn) FirmwareInstallUploadAndInitiate(ctx context.Context, component string, file *os.File) (taskID string, err error) {\n\tif err := c.deviceSupported(ctx); err != nil {\n\t\treturn \"\", bmcliberrs.NewErrUnsupportedHardware(err.Error())\n\t}\n\n\t//\t// expect atleast 5 minutes left in the deadline to proceed with the upload\n\td, _ := ctx.Deadline()\n\tif time.Until(d) < 10*time.Minute {\n\t\treturn \"\", errors.New(\"remaining context deadline insufficient to perform update: \" + time.Until(d).String())\n\t}\n\n\t// list current tasks on BMC\n\ttasks, err := c.redfishwrapper.Tasks(ctx)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"error listing bmc redfish tasks\")\n\t}\n\n\t// validate a new firmware install task can be queued\n\tif err := c.checkQueueability(component, tasks); err != nil {\n\t\treturn \"\", errors.Wrap(bmcliberrs.ErrFirmwareInstall, err.Error())\n\t}\n\n\tparams := &rfw.RedfishUpdateServiceParameters{\n\t\tTargets:            []string{},\n\t\tOperationApplyTime: constants.OnReset,\n\t\tOem:                []byte(`{}`),\n\t}\n\n\treturn c.redfishwrapper.FirmwareUpload(ctx, file, params)\n}\n\n// checkQueueability returns an error if an existing firmware task is in progress for the given component\nfunc (c *Conn) checkQueueability(component string, tasks []*schemas.Task) error {\n\terrTaskActive := errors.New(\"A firmware job was found active for component: \" + component)\n\n\t// Redfish on the Idrac names firmware install tasks in this manner.\n\ttaskNameMap := map[string]string{\n\t\tcommon.SlugBIOS:              \"Firmware Update: BIOS\",\n\t\tcommon.SlugBMC:               \"Firmware Update: iDRAC with Lifecycle Controller\",\n\t\tcommon.SlugNIC:               \"Firmware Update: Network\",\n\t\tcommon.SlugDrive:             \"Firmware Update: Serial ATA\",\n\t\tcommon.SlugStorageController: \"Firmware Update: SAS RAID\",\n\t}\n\n\tfor _, t := range tasks {\n\t\tif t.Name == taskNameMap[strings.ToUpper(component)] {\n\t\t\t// taskInfo returned in error if any.\n\t\t\ttaskInfo := fmt.Sprintf(\"id: %s, state: %s, status: %s\", t.ID, t.TaskState, t.TaskStatus)\n\n\t\t\t// convert redfish task state to bmclib state\n\t\t\tconvstate := c.redfishwrapper.ConvertTaskState(string(t.TaskState))\n\t\t\t// check if task is active based on converted state\n\t\t\tactive, err := c.redfishwrapper.TaskStateActive(convstate)\n\t\t\tif err != nil {\n\t\t\t\treturn errors.Wrap(err, taskInfo)\n\t\t\t}\n\n\t\t\tif active {\n\t\t\t\treturn errors.Wrap(errTaskActive, taskInfo)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// FirmwareTaskStatus returns the status of a firmware related task queued on the BMC.\nfunc (c *Conn) FirmwareTaskStatus(ctx context.Context, kind constants.FirmwareInstallStep, component, taskID, installVersion string) (state constants.TaskState, status string, err error) {\n\tif err := c.deviceSupported(ctx); err != nil {\n\t\treturn \"\", \"\", bmcliberrs.NewErrUnsupportedHardware(err.Error())\n\t}\n\n\t// Dell jobs are turned into Redfish tasks on the idrac\n\t// once the Redfish task completes successfully, the Redfish task is purged,\n\t// and the dell Job stays around.\n\ttask, err := c.redfishwrapper.Task(ctx, taskID)\n\tif err != nil {\n\t\tif errors.Is(err, bmcliberrs.ErrTaskNotFound) {\n\t\t\treturn c.statusFromJob(taskID)\n\t\t}\n\n\t\treturn \"\", \"\", err\n\t}\n\n\treturn c.statusFromTaskOem(taskID, task.OEM)\n}\n\nfunc (c *Conn) statusFromJob(taskID string) (constants.TaskState, string, error) {\n\tjob, err := c.job(taskID)\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\n\ts := strings.ToLower(job.JobState)\n\tstate := c.redfishwrapper.ConvertTaskState(s)\n\n\tstatus := fmt.Sprintf(\n\t\t\"id: %s, state: %s, status: %s, progress: %d%%\",\n\t\ttaskID,\n\t\tjob.JobState,\n\t\tjob.Message,\n\t\tjob.PercentComplete,\n\t)\n\n\treturn state, status, nil\n}\n\nfunc (c *Conn) statusFromTaskOem(taskID string, oem json.RawMessage) (constants.TaskState, string, error) {\n\tdata, err := convFirmwareTaskOem(oem)\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\n\ts := strings.ToLower(data.Dell.JobState)\n\tstate := c.redfishwrapper.ConvertTaskState(s)\n\n\tstatus := fmt.Sprintf(\n\t\t\"id: %s, state: %s, status: %s, progress: %d%%\",\n\t\ttaskID,\n\t\tdata.Dell.JobState,\n\t\tdata.Dell.Message,\n\t\tdata.Dell.PercentComplete,\n\t)\n\n\treturn state, status, nil\n}\n\nfunc (c *Conn) job(jobID string) (*Dell, error) {\n\terrLookup := errors.New(\"error querying dell job: \" + jobID)\n\n\tendpoint := \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/\" + jobID\n\tresp, err := c.redfishwrapper.Get(endpoint)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(errLookup, err.Error())\n\t}\n\n\tif resp.StatusCode != 200 {\n\t\treturn nil, errors.Wrap(errLookup, \"unexpected status code: \"+resp.Status)\n\t}\n\n\tdefer resp.Body.Close()\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(errLookup, err.Error())\n\t}\n\n\tdell := &Dell{}\n\terr = json.Unmarshal(body, &dell)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(errLookup, err.Error())\n\t}\n\n\treturn dell, nil\n}\n\ntype oem struct {\n\tDell `json:\"Dell\"`\n}\n\ntype Dell struct {\n\tOdataType         string        `json:\"@odata.type\"`\n\tCompletionTime    interface{}   `json:\"CompletionTime\"`\n\tDescription       string        `json:\"Description\"`\n\tEndTime           string        `json:\"EndTime\"`\n\tID                string        `json:\"Id\"`\n\tJobState          string        `json:\"JobState\"`\n\tJobType           string        `json:\"JobType\"`\n\tMessage           string        `json:\"Message\"`\n\tMessageArgs       []interface{} `json:\"MessageArgs\"`\n\tMessageID         string        `json:\"MessageId\"`\n\tName              string        `json:\"Name\"`\n\tPercentComplete   int           `json:\"PercentComplete\"`\n\tStartTime         string        `json:\"StartTime\"`\n\tTargetSettingsURI interface{}   `json:\"TargetSettingsURI\"`\n}\n\nfunc convFirmwareTaskOem(oemdata json.RawMessage) (oem, error) {\n\toem := oem{}\n\n\terrTaskOem := errors.New(\"error in Task Oem data: \" + string(oemdata))\n\n\tif len(oemdata) == 0 || string(oemdata) == `{}` {\n\t\treturn oem, errors.Wrap(errTaskOem, \"empty oem data\")\n\t}\n\n\tif err := json.Unmarshal(oemdata, &oem); err != nil {\n\t\treturn oem, errors.Wrap(errTaskOem, \"failed to unmarshal: \"+err.Error())\n\t}\n\n\tif oem.Dell.Description == \"\" || oem.Dell.JobState == \"\" {\n\t\treturn oem, errors.Wrap(errTaskOem, \"invalid oem data\")\n\t}\n\n\tif oem.Dell.JobType != \"FirmwareUpdate\" {\n\t\treturn oem, errors.Wrap(errTaskOem, \"unexpected job type: \"+oem.Dell.JobType)\n\t}\n\n\treturn oem, nil\n}\n"
  },
  {
    "path": "providers/dell/firmware_test.go",
    "content": "package dell\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestConvFirmwareTaskOem(t *testing.T) {\n\ttestCases := []struct {\n\t\tname        string\n\t\toemdata     []byte\n\t\texpectedJob oem\n\t\texpectedErr string\n\t}{\n\t\t{\n\t\t\tname: \"Valid OEM data\",\n\t\t\toemdata: []byte(`{\n\t\t\t\t\"Dell\": {\n\t\t\t\t\t\"@odata.type\": \"#DellJob.v1_4_0.DellJob\",\n\t\t\t\t\t\"CompletionTime\": null,\n\t\t\t\t\t\"Description\": \"Job Instance\",\n\t\t\t\t\t\"EndTime\": \"TIME_NA\",\n\t\t\t\t\t\"Id\": \"JID_005950769310\",\n\t\t\t\t\t\"JobState\": \"Scheduled\",\n\t\t\t\t\t\"JobType\": \"FirmwareUpdate\",\n\t\t\t\t\t\"Message\": \"Task successfully scheduled.\",\n\t\t\t\t\t\"MessageArgs\": [],\n\t\t\t\t\t\"MessageId\": \"IDRAC.2.8.JCP001\",\n\t\t\t\t\t\"Name\": \"Firmware Update: BIOS\",\n\t\t\t\t\t\"PercentComplete\": 0,\n\t\t\t\t\t\"StartTime\": \"TIME_NOW\",\n\t\t\t\t\t\"TargetSettingsURI\": null\n\t\t\t\t}\n\t\t\t}`),\n\t\t\texpectedJob: oem{\n\t\t\t\tDell{\n\t\t\t\t\tOdataType:         \"#DellJob.v1_4_0.DellJob\",\n\t\t\t\t\tCompletionTime:    nil,\n\t\t\t\t\tDescription:       \"Job Instance\",\n\t\t\t\t\tEndTime:           \"TIME_NA\",\n\t\t\t\t\tID:                \"JID_005950769310\",\n\t\t\t\t\tJobState:          \"Scheduled\",\n\t\t\t\t\tJobType:           \"FirmwareUpdate\",\n\t\t\t\t\tMessage:           \"Task successfully scheduled.\",\n\t\t\t\t\tMessageArgs:       []interface{}{},\n\t\t\t\t\tMessageID:         \"IDRAC.2.8.JCP001\",\n\t\t\t\t\tName:              \"Firmware Update: BIOS\",\n\t\t\t\t\tPercentComplete:   0,\n\t\t\t\t\tStartTime:         \"TIME_NOW\",\n\t\t\t\t\tTargetSettingsURI: nil,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedErr: \"\",\n\t\t},\n\t\t{\n\t\t\tname:        \"Empty OEM data\",\n\t\t\toemdata:     []byte(`{}`),\n\t\t\texpectedJob: oem{},\n\t\t\texpectedErr: \"empty oem data\",\n\t\t},\n\t\t{\n\t\t\tname:        \"Invalid OEM data\",\n\t\t\toemdata:     []byte(`{\"InvalidKey\": \"InvalidValue\"}`),\n\t\t\texpectedJob: oem{},\n\t\t\texpectedErr: \"invalid oem data\",\n\t\t},\n\t\t{\n\t\t\tname: \"Unexpected job type\",\n\t\t\toemdata: []byte(`{\n\t\t\t\t\t\"Dell\": {\n\t\t\t\t\t\t\"JobType\": \"InvalidJobType\",\n\t\t\t\t\t\t\"Description\": \"Job Instance\",\n\t\t\t\t\t\t\"JobState\": \"Scheduled\"\n\t\t\t\t\t}\n\t\t\t\t}`),\n\t\t\texpectedJob: oem{},\n\t\t\texpectedErr: \"unexpected job type\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tjob, err := convFirmwareTaskOem(tc.oemdata)\n\t\t\tif tc.expectedErr == \"\" {\n\t\t\t\tassert.NoError(t, err)\n\t\t\t\tassert.Equal(t, tc.expectedJob, job)\n\t\t\t} else {\n\t\t\t\tassert.Error(t, err)\n\t\t\t\tassert.Contains(t, err.Error(), tc.expectedErr)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "providers/dell/fixtures/serviceroot.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#ServiceRoot.ServiceRoot\",\n    \"@odata.id\": \"/redfish/v1\",\n    \"@odata.type\": \"#ServiceRoot.v1_6_0.ServiceRoot\",\n    \"AccountService\": {\n        \"@odata.id\": \"/redfish/v1/AccountService\"\n    },\n    \"CertificateService\": {\n        \"@odata.id\": \"/redfish/v1/CertificateService\"\n    },\n    \"Chassis\": {\n        \"@odata.id\": \"/redfish/v1/Chassis\"\n    },\n    \"Description\": \"Root Service\",\n    \"EventService\": {\n        \"@odata.id\": \"/redfish/v1/EventService\"\n    },\n    \"Fabrics\": {\n        \"@odata.id\": \"/redfish/v1/Fabrics\"\n    },\n    \"Id\": \"RootService\",\n    \"JobService\": {\n        \"@odata.id\": \"/redfish/v1/JobService\"\n    },\n    \"JsonSchemas\": {\n        \"@odata.id\": \"/redfish/v1/JsonSchemas\"\n    },\n    \"Links\": {\n        \"Sessions\": {\n            \"@odata.id\": \"/redfish/v1/SessionService/Sessions\"\n        }\n    },\n    \"Managers\": {\n        \"@odata.id\": \"/redfish/v1/Managers\"\n    },\n    \"Name\": \"Root Service\",\n    \"Oem\": {\n        \"Dell\": {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellServiceRoot.DellServiceRoot\",\n            \"@odata.type\": \"#DellServiceRoot.v1_0_0.DellServiceRoot\",\n            \"IsBranded\": 0,\n            \"ManagerMACAddress\": \"d0:8e:79:bb:3e:ea\",\n            \"ServiceTag\": \"FOOBAR\"\n        }\n    },\n    \"Product\": \"Integrated Dell Remote Access Controller\",\n    \"ProtocolFeaturesSupported\": {\n        \"ExcerptQuery\": false,\n        \"ExpandQuery\": {\n            \"ExpandAll\": true,\n            \"Levels\": true,\n            \"Links\": true,\n            \"MaxLevels\": 1,\n            \"NoLinks\": true\n        },\n        \"FilterQuery\": true,\n        \"OnlyMemberQuery\": true,\n        \"SelectQuery\": true\n    },\n    \"RedfishVersion\": \"1.9.0\",\n    \"Registries\": {\n        \"@odata.id\": \"/redfish/v1/Registries\"\n    },\n    \"SessionService\": {\n        \"@odata.id\": \"/redfish/v1/SessionService\"\n    },\n    \"Systems\": {\n        \"@odata.id\": \"/redfish/v1/Systems\"\n    },\n    \"Tasks\": {\n        \"@odata.id\": \"/redfish/v1/TaskService\"\n    },\n    \"TelemetryService\": {\n        \"@odata.id\": \"/redfish/v1/TelemetryService\"\n    },\n    \"UpdateService\": {\n        \"@odata.id\": \"/redfish/v1/UpdateService\"\n    },\n    \"Vendor\": \"Dell\"\n}"
  },
  {
    "path": "providers/dell/fixtures/systems.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#ComputerSystemCollection.ComputerSystemCollection\",\n    \"@odata.id\": \"/redfish/v1/Systems\",\n    \"@odata.type\": \"#ComputerSystemCollection.ComputerSystemCollection\",\n    \"Description\": \"Collection of Computer Systems\",\n    \"Members\": [\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1\"\n        }\n    ],\n    \"Members@odata.count\": 1,\n    \"Name\": \"Computer System Collection\"\n}"
  },
  {
    "path": "providers/dell/fixtures/systems_embedded.1.json",
    "content": "{\n  \"@odata.context\": \"/redfish/v1/$metadata#ComputerSystem.ComputerSystem\",\n  \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1\",\n  \"@odata.type\": \"#ComputerSystem.v1_12_0.ComputerSystem\",\n  \"Actions\": {\n    \"#ComputerSystem.Reset\": {\n      \"target\": \"/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset\",\n      \"ResetType@Redfish.AllowableValues\": [\n        \"On\",\n        \"ForceOff\",\n        \"ForceRestart\",\n        \"GracefulRestart\",\n        \"GracefulShutdown\",\n        \"PushPowerButton\",\n        \"Nmi\",\n        \"PowerCycle\"\n      ]\n    }\n  },\n  \"AssetTag\": \"\",\n  \"Bios\": {\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Bios\"\n  },\n  \"BiosVersion\": \"2.6.6\",\n  \"Boot\": {\n    \"BootOptions\": {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/BootOptions\"\n    },\n    \"Certificates\": {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Boot/Certificates\"\n    },\n    \"BootOrder\": [\n      \"HardDisk.List.1-1\",\n      \"NIC.Slot.3-1-1\"\n    ],\n    \"BootOrder@odata.count\": 2,\n    \"BootSourceOverrideEnabled\": \"Disabled\",\n    \"BootSourceOverrideMode\": \"Legacy\",\n    \"BootSourceOverrideTarget\": \"None\",\n    \"UefiTargetBootSourceOverride\": null,\n    \"BootSourceOverrideTarget@Redfish.AllowableValues\": [\n      \"None\",\n      \"Pxe\",\n      \"Floppy\",\n      \"Cd\",\n      \"Hdd\",\n      \"BiosSetup\",\n      \"Utilities\",\n      \"UefiTarget\",\n      \"SDCard\",\n      \"UefiHttp\"\n    ]\n  },\n  \"Description\": \"Computer System which represents a machine (physical or virtual) and the local resources such as memory, cpu and other devices that can be accessed from that machine.\",\n  \"EthernetInterfaces\": {\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/EthernetInterfaces\"\n  },\n  \"HostName\": \"foobaz\",\n  \"HostWatchdogTimer\": {\n    \"FunctionEnabled\": false,\n    \"Status\": {\n      \"State\": \"Disabled\"\n    },\n    \"TimeoutAction\": \"None\"\n  },\n  \"HostingRoles\": [],\n  \"HostingRoles@odata.count\": 0,\n  \"Id\": \"System.Embedded.1\",\n  \"IndicatorLED\": \"Lit\",\n  \"Links\": {\n    \"Chassis\": [\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1\"\n      }\n    ],\n    \"Chassis@odata.count\": 1,\n    \"CooledBy\": [\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/0\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/1\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/2\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/3\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/4\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/5\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/6\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/7\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/8\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/9\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/10\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/11\"\n      }\n    ],\n    \"CooledBy@odata.count\": 12,\n    \"ManagedBy\": [\n      {\n        \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1\"\n      }\n    ],\n    \"ManagedBy@odata.count\": 1,\n    \"Oem\": {\n      \"Dell\": {\n        \"@odata.type\": \"#DellOem.v1_2_0.DellOemLinks\",\n        \"BootOrder\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellBootSources\"\n        },\n        \"DellBootSources\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellBootSources\"\n        },\n        \"DellSoftwareInstallationService\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellSoftwareInstallationService\"\n        },\n        \"DellVideoCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellVideo\"\n        },\n        \"DellChassisCollection\": {\n          \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Oem/Dell/DellChassis\"\n        },\n        \"DellPresenceAndStatusSensorCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellPresenceAndStatusSensors\"\n        },\n        \"DellSensorCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellSensors\"\n        },\n        \"DellRollupStatusCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRollupStatus\"\n        },\n        \"DellPSNumericSensorCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellPSNumericSensors\"\n        },\n        \"DellVideoNetworkCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellVideoNetwork\"\n        },\n        \"DellOSDeploymentService\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellOSDeploymentService\"\n        },\n        \"DellMetricService\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellMetricService\"\n        },\n        \"DellGPUSensorCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellGPUSensors\"\n        },\n        \"DellRaidService\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService\"\n        },\n        \"DellNumericSensorCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellNumericSensors\"\n        },\n        \"DellBIOSService\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellBIOSService\"\n        },\n        \"DellSlotCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellSlots\"\n        }\n      }\n    },\n    \"PoweredBy\": [\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Power#/PowerSupplies/0\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Power#/PowerSupplies/1\"\n      }\n    ],\n    \"PoweredBy@odata.count\": 2\n  },\n  \"Manufacturer\": \"Dell Inc.\",\n  \"Memory\": {\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Memory\"\n  },\n  \"MemorySummary\": {\n    \"MemoryMirroring\": \"System\",\n    \"Status\": {\n      \"Health\": \"OK\",\n      \"HealthRollup\": \"OK\",\n      \"State\": \"Enabled\"\n    },\n    \"TotalSystemMemoryGiB\": 256\n  },\n  \"Model\": \"PowerEdge R6515\",\n  \"Name\": \"System\",\n  \"NetworkInterfaces\": {\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/NetworkInterfaces\"\n  },\n  \"Oem\": {\n    \"Dell\": {\n      \"@odata.type\": \"#DellOem.v1_2_0.DellOemResources\",\n      \"DellSystem\": {\n        \"BIOSReleaseDate\": \"01/13/2022\",\n        \"BaseBoardChassisSlot\": \"NA\",\n        \"BatteryRollupStatus\": \"OK\",\n        \"BladeGeometry\": \"NotApplicable\",\n        \"CMCIP\": null,\n        \"CPURollupStatus\": \"OK\",\n        \"ChassisModel\": \"\",\n        \"ChassisName\": \"Main System Chassis\",\n        \"ChassisServiceTag\": \"FOOBAR\",\n        \"ChassisSystemHeightUnit\": 1,\n        \"CurrentRollupStatus\": \"OK\",\n        \"EstimatedExhaustTemperatureCelsius\": 255,\n        \"EstimatedSystemAirflowCFM\": 255,\n        \"ExpressServiceCode\": \"1234567819\",\n        \"FanRollupStatus\": \"OK\",\n        \"Id\": \"System.Embedded.1\",\n        \"IDSDMRollupStatus\": null,\n        \"IntrusionRollupStatus\": \"OK\",\n        \"IsOEMBranded\": \"False\",\n        \"LastSystemInventoryTime\": \"2023-04-28T04:00:49+00:00\",\n        \"LastUpdateTime\": \"2022-10-11T21:35:12+00:00\",\n        \"LicensingRollupStatus\": \"OK\",\n        \"ManagedSystemSize\": \"1 U\",\n        \"MaxCPUSockets\": 1,\n        \"MaxDIMMSlots\": 16,\n        \"MaxPCIeSlots\": 5,\n        \"MemoryOperationMode\": \"OptimizerMode\",\n        \"Name\": \"DellSystem\",\n        \"NodeID\": \"FOOBAR\",\n        \"PSRollupStatus\": \"OK\",\n        \"PlatformGUID\": \"33435a4f-c0c6-4780-5210-00304c4c4544\",\n        \"PopulatedDIMMSlots\": 8,\n        \"PopulatedPCIeSlots\": 2,\n        \"PowerCapEnabledState\": \"Disabled\",\n        \"SDCardRollupStatus\": null,\n        \"SELRollupStatus\": \"OK\",\n        \"ServerAllocationWatts\": null,\n        \"StorageRollupStatus\": \"OK\",\n        \"SysMemErrorMethodology\": \"Multi-bitECC\",\n        \"SysMemFailOverState\": \"NotInUse\",\n        \"SysMemLocation\": \"SystemBoardOrMotherboard\",\n        \"SysMemPrimaryStatus\": \"OK\",\n        \"SystemGeneration\": \"15G Monolithic\",\n        \"SystemID\": 2300,\n        \"SystemRevision\": \"I\",\n        \"TempRollupStatus\": \"OK\",\n        \"TempStatisticsRollupStatus\": \"OK\",\n        \"UUID\": \"4c4c4544-0030-5210-8047-c6c04f5a4333\",\n        \"VoltRollupStatus\": \"OK\",\n        \"smbiosGUID\": \"44454c4c-3000-1052-8047-c6c04f5a4333\",\n        \"@odata.context\": \"/redfish/v1/$metadata#DellSystem.DellSystem\",\n        \"@odata.type\": \"#DellSystem.v1_3_0.DellSystem\",\n        \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellSystem/System.Embedded.1\"\n      }\n    }\n  },\n  \"PCIeDevices\": [\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-3\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-2\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-4\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-7\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-1\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-20\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-8\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/70-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/65-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/69-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-2\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-1\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/194-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/4-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-3\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-4\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-8\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-7\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/1-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-3\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-2\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/72-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-4\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-7\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-1\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-8\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-3\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-2\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-4\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-7\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-1\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-8\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/195-0\"\n    }\n  ],\n  \"PCIeDevices@odata.count\": 34,\n  \"PCIeFunctions\": [\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-3/PCIeFunctions/0-3-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-2/PCIeFunctions/0-2-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-4/PCIeFunctions/0-4-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-7/PCIeFunctions/0-7-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-1/PCIeFunctions/0-1-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-20/PCIeFunctions/0-20-3\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-8/PCIeFunctions/0-8-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-20/PCIeFunctions/0-20-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/70-0/PCIeFunctions/70-0-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/65-0/PCIeFunctions/65-0-1\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/65-0/PCIeFunctions/65-0-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/69-0/PCIeFunctions/69-0-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-2/PCIeFunctions/192-2-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-1/PCIeFunctions/192-1-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/194-0/PCIeFunctions/194-0-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/4-0/PCIeFunctions/4-0-3\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-3/PCIeFunctions/192-3-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-4/PCIeFunctions/192-4-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-8/PCIeFunctions/192-8-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-0/PCIeFunctions/192-0-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-7/PCIeFunctions/192-7-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/1-0/PCIeFunctions/1-0-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-3/PCIeFunctions/128-3-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-2/PCIeFunctions/128-2-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/72-0/PCIeFunctions/72-0-3\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-4/PCIeFunctions/128-4-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-7/PCIeFunctions/128-7-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-1/PCIeFunctions/128-1-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-8/PCIeFunctions/128-8-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-3/PCIeFunctions/64-3-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-2/PCIeFunctions/64-2-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-4/PCIeFunctions/64-4-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-7/PCIeFunctions/64-7-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-1/PCIeFunctions/64-1-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-8/PCIeFunctions/64-8-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/195-0/PCIeFunctions/195-0-0\"\n    }\n  ],\n  \"PCIeFunctions@odata.count\": 36,\n  \"PartNumber\": \"07PXPYA01\",\n  \"PowerState\": \"On\",\n  \"ProcessorSummary\": {\n    \"Count\": 1,\n    \"LogicalProcessorCount\": 32,\n    \"Model\": \"AMD EPYC 7502P 32-Core Processor\",\n    \"Status\": {\n      \"Health\": \"OK\",\n      \"HealthRollup\": \"OK\",\n      \"State\": \"Enabled\"\n    }\n  },\n  \"Processors\": {\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Processors\"\n  },\n  \"SKU\": \"FOOBAR\",\n  \"SecureBoot\": {\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/SecureBoot\"\n  },\n  \"SerialNumber\": \"FOOBAR123\",\n  \"SimpleStorage\": {\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/SimpleStorage\"\n  },\n  \"Status\": {\n    \"Health\": \"OK\",\n    \"HealthRollup\": \"OK\",\n    \"State\": \"Enabled\"\n  },\n  \"Storage\": {\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Storage\"\n  },\n  \"SystemType\": \"Physical\",\n  \"TrustedModules\": [\n    {\n      \"FirmwareVersion\": \"1.3.2.8\",\n      \"InterfaceType\": \"TPM2_0\",\n      \"Status\": {\n        \"State\": \"Enabled\"\n      }\n    }\n  ],\n  \"TrustedModules@odata.count\": 1,\n  \"UUID\": \"4c4c4544-0030-5210-8047-c6c04f5a4333\"\n}"
  },
  {
    "path": "providers/dell/fixtures/systems_embedded_no_manufacturer.1.json",
    "content": "{\n  \"@odata.context\": \"/redfish/v1/$metadata#ComputerSystem.ComputerSystem\",\n  \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1\",\n  \"@odata.type\": \"#ComputerSystem.v1_12_0.ComputerSystem\",\n  \"Actions\": {\n    \"#ComputerSystem.Reset\": {\n      \"target\": \"/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset\",\n      \"ResetType@Redfish.AllowableValues\": [\n        \"On\",\n        \"ForceOff\",\n        \"ForceRestart\",\n        \"GracefulRestart\",\n        \"GracefulShutdown\",\n        \"PushPowerButton\",\n        \"Nmi\",\n        \"PowerCycle\"\n      ]\n    }\n  },\n  \"AssetTag\": \"\",\n  \"Bios\": {\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Bios\"\n  },\n  \"BiosVersion\": \"2.6.6\",\n  \"Boot\": {\n    \"BootOptions\": {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/BootOptions\"\n    },\n    \"Certificates\": {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Boot/Certificates\"\n    },\n    \"BootOrder\": [\n      \"HardDisk.List.1-1\",\n      \"NIC.Slot.3-1-1\"\n    ],\n    \"BootOrder@odata.count\": 2,\n    \"BootSourceOverrideEnabled\": \"Disabled\",\n    \"BootSourceOverrideMode\": \"Legacy\",\n    \"BootSourceOverrideTarget\": \"None\",\n    \"UefiTargetBootSourceOverride\": null,\n    \"BootSourceOverrideTarget@Redfish.AllowableValues\": [\n      \"None\",\n      \"Pxe\",\n      \"Floppy\",\n      \"Cd\",\n      \"Hdd\",\n      \"BiosSetup\",\n      \"Utilities\",\n      \"UefiTarget\",\n      \"SDCard\",\n      \"UefiHttp\"\n    ]\n  },\n  \"Description\": \"Computer System which represents a machine (physical or virtual) and the local resources such as memory, cpu and other devices that can be accessed from that machine.\",\n  \"EthernetInterfaces\": {\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/EthernetInterfaces\"\n  },\n  \"HostName\": \"foobaz\",\n  \"HostWatchdogTimer\": {\n    \"FunctionEnabled\": false,\n    \"Status\": {\n      \"State\": \"Disabled\"\n    },\n    \"TimeoutAction\": \"None\"\n  },\n  \"HostingRoles\": [],\n  \"HostingRoles@odata.count\": 0,\n  \"Id\": \"System.Embedded.1\",\n  \"IndicatorLED\": \"Lit\",\n  \"Links\": {\n    \"Chassis\": [\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1\"\n      }\n    ],\n    \"Chassis@odata.count\": 1,\n    \"CooledBy\": [\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/0\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/1\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/2\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/3\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/4\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/5\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/6\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/7\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/8\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/9\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/10\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/11\"\n      }\n    ],\n    \"CooledBy@odata.count\": 12,\n    \"ManagedBy\": [\n      {\n        \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1\"\n      }\n    ],\n    \"ManagedBy@odata.count\": 1,\n    \"Oem\": {\n      \"Dell\": {\n        \"@odata.type\": \"#DellOem.v1_2_0.DellOemLinks\",\n        \"BootOrder\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellBootSources\"\n        },\n        \"DellBootSources\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellBootSources\"\n        },\n        \"DellSoftwareInstallationService\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellSoftwareInstallationService\"\n        },\n        \"DellVideoCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellVideo\"\n        },\n        \"DellChassisCollection\": {\n          \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Oem/Dell/DellChassis\"\n        },\n        \"DellPresenceAndStatusSensorCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellPresenceAndStatusSensors\"\n        },\n        \"DellSensorCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellSensors\"\n        },\n        \"DellRollupStatusCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRollupStatus\"\n        },\n        \"DellPSNumericSensorCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellPSNumericSensors\"\n        },\n        \"DellVideoNetworkCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellVideoNetwork\"\n        },\n        \"DellOSDeploymentService\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellOSDeploymentService\"\n        },\n        \"DellMetricService\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellMetricService\"\n        },\n        \"DellGPUSensorCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellGPUSensors\"\n        },\n        \"DellRaidService\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService\"\n        },\n        \"DellNumericSensorCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellNumericSensors\"\n        },\n        \"DellBIOSService\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellBIOSService\"\n        },\n        \"DellSlotCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellSlots\"\n        }\n      }\n    },\n    \"PoweredBy\": [\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Power#/PowerSupplies/0\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Power#/PowerSupplies/1\"\n      }\n    ],\n    \"PoweredBy@odata.count\": 2\n  },\n  \"Manufacturer\": \"\",\n  \"Memory\": {\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Memory\"\n  },\n  \"MemorySummary\": {\n    \"MemoryMirroring\": \"System\",\n    \"Status\": {\n      \"Health\": \"OK\",\n      \"HealthRollup\": \"OK\",\n      \"State\": \"Enabled\"\n    },\n    \"TotalSystemMemoryGiB\": 256\n  },\n  \"Model\": \"PowerEdge R6515\",\n  \"Name\": \"System\",\n  \"NetworkInterfaces\": {\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/NetworkInterfaces\"\n  },\n  \"Oem\": {\n    \"Dell\": {\n      \"@odata.type\": \"#DellOem.v1_2_0.DellOemResources\",\n      \"DellSystem\": {\n        \"BIOSReleaseDate\": \"01/13/2022\",\n        \"BaseBoardChassisSlot\": \"NA\",\n        \"BatteryRollupStatus\": \"OK\",\n        \"BladeGeometry\": \"NotApplicable\",\n        \"CMCIP\": null,\n        \"CPURollupStatus\": \"OK\",\n        \"ChassisModel\": \"\",\n        \"ChassisName\": \"Main System Chassis\",\n        \"ChassisServiceTag\": \"FOOBAR\",\n        \"ChassisSystemHeightUnit\": 1,\n        \"CurrentRollupStatus\": \"OK\",\n        \"EstimatedExhaustTemperatureCelsius\": 255,\n        \"EstimatedSystemAirflowCFM\": 255,\n        \"ExpressServiceCode\": \"1234567819\",\n        \"FanRollupStatus\": \"OK\",\n        \"Id\": \"System.Embedded.1\",\n        \"IDSDMRollupStatus\": null,\n        \"IntrusionRollupStatus\": \"OK\",\n        \"IsOEMBranded\": \"False\",\n        \"LastSystemInventoryTime\": \"2023-04-28T04:00:49+00:00\",\n        \"LastUpdateTime\": \"2022-10-11T21:35:12+00:00\",\n        \"LicensingRollupStatus\": \"OK\",\n        \"ManagedSystemSize\": \"1 U\",\n        \"MaxCPUSockets\": 1,\n        \"MaxDIMMSlots\": 16,\n        \"MaxPCIeSlots\": 5,\n        \"MemoryOperationMode\": \"OptimizerMode\",\n        \"Name\": \"DellSystem\",\n        \"NodeID\": \"FOOBAR\",\n        \"PSRollupStatus\": \"OK\",\n        \"PlatformGUID\": \"33435a4f-c0c6-4780-5210-00304c4c4544\",\n        \"PopulatedDIMMSlots\": 8,\n        \"PopulatedPCIeSlots\": 2,\n        \"PowerCapEnabledState\": \"Disabled\",\n        \"SDCardRollupStatus\": null,\n        \"SELRollupStatus\": \"OK\",\n        \"ServerAllocationWatts\": null,\n        \"StorageRollupStatus\": \"OK\",\n        \"SysMemErrorMethodology\": \"Multi-bitECC\",\n        \"SysMemFailOverState\": \"NotInUse\",\n        \"SysMemLocation\": \"SystemBoardOrMotherboard\",\n        \"SysMemPrimaryStatus\": \"OK\",\n        \"SystemGeneration\": \"15G Monolithic\",\n        \"SystemID\": 2300,\n        \"SystemRevision\": \"I\",\n        \"TempRollupStatus\": \"OK\",\n        \"TempStatisticsRollupStatus\": \"OK\",\n        \"UUID\": \"4c4c4544-0030-5210-8047-c6c04f5a4333\",\n        \"VoltRollupStatus\": \"OK\",\n        \"smbiosGUID\": \"44454c4c-3000-1052-8047-c6c04f5a4333\",\n        \"@odata.context\": \"/redfish/v1/$metadata#DellSystem.DellSystem\",\n        \"@odata.type\": \"#DellSystem.v1_3_0.DellSystem\",\n        \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellSystem/System.Embedded.1\"\n      }\n    }\n  },\n  \"PCIeDevices\": [\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-3\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-2\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-4\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-7\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-1\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-20\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-8\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/70-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/65-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/69-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-2\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-1\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/194-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/4-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-3\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-4\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-8\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-7\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/1-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-3\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-2\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/72-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-4\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-7\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-1\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-8\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-3\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-2\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-4\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-7\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-1\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-8\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/195-0\"\n    }\n  ],\n  \"PCIeDevices@odata.count\": 34,\n  \"PCIeFunctions\": [\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-3/PCIeFunctions/0-3-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-2/PCIeFunctions/0-2-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-4/PCIeFunctions/0-4-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-7/PCIeFunctions/0-7-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-1/PCIeFunctions/0-1-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-20/PCIeFunctions/0-20-3\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-8/PCIeFunctions/0-8-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-20/PCIeFunctions/0-20-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/70-0/PCIeFunctions/70-0-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/65-0/PCIeFunctions/65-0-1\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/65-0/PCIeFunctions/65-0-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/69-0/PCIeFunctions/69-0-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-2/PCIeFunctions/192-2-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-1/PCIeFunctions/192-1-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/194-0/PCIeFunctions/194-0-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/4-0/PCIeFunctions/4-0-3\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-3/PCIeFunctions/192-3-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-4/PCIeFunctions/192-4-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-8/PCIeFunctions/192-8-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-0/PCIeFunctions/192-0-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-7/PCIeFunctions/192-7-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/1-0/PCIeFunctions/1-0-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-3/PCIeFunctions/128-3-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-2/PCIeFunctions/128-2-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/72-0/PCIeFunctions/72-0-3\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-4/PCIeFunctions/128-4-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-7/PCIeFunctions/128-7-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-1/PCIeFunctions/128-1-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-8/PCIeFunctions/128-8-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-3/PCIeFunctions/64-3-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-2/PCIeFunctions/64-2-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-4/PCIeFunctions/64-4-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-7/PCIeFunctions/64-7-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-1/PCIeFunctions/64-1-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-8/PCIeFunctions/64-8-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/195-0/PCIeFunctions/195-0-0\"\n    }\n  ],\n  \"PCIeFunctions@odata.count\": 36,\n  \"PartNumber\": \"07PXPYA01\",\n  \"PowerState\": \"On\",\n  \"ProcessorSummary\": {\n    \"Count\": 1,\n    \"LogicalProcessorCount\": 32,\n    \"Model\": \"AMD EPYC 7502P 32-Core Processor\",\n    \"Status\": {\n      \"Health\": \"OK\",\n      \"HealthRollup\": \"OK\",\n      \"State\": \"Enabled\"\n    }\n  },\n  \"Processors\": {\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Processors\"\n  },\n  \"SKU\": \"FOOBAR\",\n  \"SecureBoot\": {\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/SecureBoot\"\n  },\n  \"SerialNumber\": \"FOOBAR123\",\n  \"SimpleStorage\": {\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/SimpleStorage\"\n  },\n  \"Status\": {\n    \"Health\": \"OK\",\n    \"HealthRollup\": \"OK\",\n    \"State\": \"Enabled\"\n  },\n  \"Storage\": {\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Storage\"\n  },\n  \"SystemType\": \"Physical\",\n  \"TrustedModules\": [\n    {\n      \"FirmwareVersion\": \"1.3.2.8\",\n      \"InterfaceType\": \"TPM2_0\",\n      \"Status\": {\n        \"State\": \"Enabled\"\n      }\n    }\n  ],\n  \"TrustedModules@odata.count\": 1,\n  \"UUID\": \"4c4c4544-0030-5210-8047-c6c04f5a4333\"\n}"
  },
  {
    "path": "providers/dell/fixtures/systems_embedded_not_dell.1.json",
    "content": "{\n  \"@odata.context\": \"/redfish/v1/$metadata#ComputerSystem.ComputerSystem\",\n  \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1\",\n  \"@odata.type\": \"#ComputerSystem.v1_12_0.ComputerSystem\",\n  \"Actions\": {\n    \"#ComputerSystem.Reset\": {\n      \"target\": \"/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset\",\n      \"ResetType@Redfish.AllowableValues\": [\n        \"On\",\n        \"ForceOff\",\n        \"ForceRestart\",\n        \"GracefulRestart\",\n        \"GracefulShutdown\",\n        \"PushPowerButton\",\n        \"Nmi\",\n        \"PowerCycle\"\n      ]\n    }\n  },\n  \"AssetTag\": \"\",\n  \"Bios\": {\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Bios\"\n  },\n  \"BiosVersion\": \"2.6.6\",\n  \"Boot\": {\n    \"BootOptions\": {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/BootOptions\"\n    },\n    \"Certificates\": {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Boot/Certificates\"\n    },\n    \"BootOrder\": [\n      \"HardDisk.List.1-1\",\n      \"NIC.Slot.3-1-1\"\n    ],\n    \"BootOrder@odata.count\": 2,\n    \"BootSourceOverrideEnabled\": \"Disabled\",\n    \"BootSourceOverrideMode\": \"Legacy\",\n    \"BootSourceOverrideTarget\": \"None\",\n    \"UefiTargetBootSourceOverride\": null,\n    \"BootSourceOverrideTarget@Redfish.AllowableValues\": [\n      \"None\",\n      \"Pxe\",\n      \"Floppy\",\n      \"Cd\",\n      \"Hdd\",\n      \"BiosSetup\",\n      \"Utilities\",\n      \"UefiTarget\",\n      \"SDCard\",\n      \"UefiHttp\"\n    ]\n  },\n  \"Description\": \"Computer System which represents a machine (physical or virtual) and the local resources such as memory, cpu and other devices that can be accessed from that machine.\",\n  \"EthernetInterfaces\": {\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/EthernetInterfaces\"\n  },\n  \"HostName\": \"foobaz\",\n  \"HostWatchdogTimer\": {\n    \"FunctionEnabled\": false,\n    \"Status\": {\n      \"State\": \"Disabled\"\n    },\n    \"TimeoutAction\": \"None\"\n  },\n  \"HostingRoles\": [],\n  \"HostingRoles@odata.count\": 0,\n  \"Id\": \"System.Embedded.1\",\n  \"IndicatorLED\": \"Lit\",\n  \"Links\": {\n    \"Chassis\": [\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1\"\n      }\n    ],\n    \"Chassis@odata.count\": 1,\n    \"CooledBy\": [\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/0\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/1\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/2\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/3\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/4\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/5\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/6\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/7\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/8\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/9\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/10\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Thermal#/Fans/11\"\n      }\n    ],\n    \"CooledBy@odata.count\": 12,\n    \"ManagedBy\": [\n      {\n        \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1\"\n      }\n    ],\n    \"ManagedBy@odata.count\": 1,\n    \"Oem\": {\n      \"Dell\": {\n        \"@odata.type\": \"#DellOem.v1_2_0.DellOemLinks\",\n        \"BootOrder\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellBootSources\"\n        },\n        \"DellBootSources\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellBootSources\"\n        },\n        \"DellSoftwareInstallationService\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellSoftwareInstallationService\"\n        },\n        \"DellVideoCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellVideo\"\n        },\n        \"DellChassisCollection\": {\n          \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Oem/Dell/DellChassis\"\n        },\n        \"DellPresenceAndStatusSensorCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellPresenceAndStatusSensors\"\n        },\n        \"DellSensorCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellSensors\"\n        },\n        \"DellRollupStatusCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRollupStatus\"\n        },\n        \"DellPSNumericSensorCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellPSNumericSensors\"\n        },\n        \"DellVideoNetworkCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellVideoNetwork\"\n        },\n        \"DellOSDeploymentService\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellOSDeploymentService\"\n        },\n        \"DellMetricService\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellMetricService\"\n        },\n        \"DellGPUSensorCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellGPUSensors\"\n        },\n        \"DellRaidService\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellRaidService\"\n        },\n        \"DellNumericSensorCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellNumericSensors\"\n        },\n        \"DellBIOSService\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellBIOSService\"\n        },\n        \"DellSlotCollection\": {\n          \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellSlots\"\n        }\n      }\n    },\n    \"PoweredBy\": [\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Power#/PowerSupplies/0\"\n      },\n      {\n        \"@odata.id\": \"/redfish/v1/Chassis/System.Embedded.1/Power#/PowerSupplies/1\"\n      }\n    ],\n    \"PoweredBy@odata.count\": 2\n  },\n  \"Manufacturer\": \"bmclib\",\n  \"Memory\": {\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Memory\"\n  },\n  \"MemorySummary\": {\n    \"MemoryMirroring\": \"System\",\n    \"Status\": {\n      \"Health\": \"OK\",\n      \"HealthRollup\": \"OK\",\n      \"State\": \"Enabled\"\n    },\n    \"TotalSystemMemoryGiB\": 256\n  },\n  \"Model\": \"PowerEdge R6515\",\n  \"Name\": \"System\",\n  \"NetworkInterfaces\": {\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/NetworkInterfaces\"\n  },\n  \"Oem\": {\n    \"Dell\": {\n      \"@odata.type\": \"#DellOem.v1_2_0.DellOemResources\",\n      \"DellSystem\": {\n        \"BIOSReleaseDate\": \"01/13/2022\",\n        \"BaseBoardChassisSlot\": \"NA\",\n        \"BatteryRollupStatus\": \"OK\",\n        \"BladeGeometry\": \"NotApplicable\",\n        \"CMCIP\": null,\n        \"CPURollupStatus\": \"OK\",\n        \"ChassisModel\": \"\",\n        \"ChassisName\": \"Main System Chassis\",\n        \"ChassisServiceTag\": \"FOOBAR\",\n        \"ChassisSystemHeightUnit\": 1,\n        \"CurrentRollupStatus\": \"OK\",\n        \"EstimatedExhaustTemperatureCelsius\": 255,\n        \"EstimatedSystemAirflowCFM\": 255,\n        \"ExpressServiceCode\": \"1234567819\",\n        \"FanRollupStatus\": \"OK\",\n        \"Id\": \"System.Embedded.1\",\n        \"IDSDMRollupStatus\": null,\n        \"IntrusionRollupStatus\": \"OK\",\n        \"IsOEMBranded\": \"False\",\n        \"LastSystemInventoryTime\": \"2023-04-28T04:00:49+00:00\",\n        \"LastUpdateTime\": \"2022-10-11T21:35:12+00:00\",\n        \"LicensingRollupStatus\": \"OK\",\n        \"ManagedSystemSize\": \"1 U\",\n        \"MaxCPUSockets\": 1,\n        \"MaxDIMMSlots\": 16,\n        \"MaxPCIeSlots\": 5,\n        \"MemoryOperationMode\": \"OptimizerMode\",\n        \"Name\": \"DellSystem\",\n        \"NodeID\": \"FOOBAR\",\n        \"PSRollupStatus\": \"OK\",\n        \"PlatformGUID\": \"33435a4f-c0c6-4780-5210-00304c4c4544\",\n        \"PopulatedDIMMSlots\": 8,\n        \"PopulatedPCIeSlots\": 2,\n        \"PowerCapEnabledState\": \"Disabled\",\n        \"SDCardRollupStatus\": null,\n        \"SELRollupStatus\": \"OK\",\n        \"ServerAllocationWatts\": null,\n        \"StorageRollupStatus\": \"OK\",\n        \"SysMemErrorMethodology\": \"Multi-bitECC\",\n        \"SysMemFailOverState\": \"NotInUse\",\n        \"SysMemLocation\": \"SystemBoardOrMotherboard\",\n        \"SysMemPrimaryStatus\": \"OK\",\n        \"SystemGeneration\": \"15G Monolithic\",\n        \"SystemID\": 2300,\n        \"SystemRevision\": \"I\",\n        \"TempRollupStatus\": \"OK\",\n        \"TempStatisticsRollupStatus\": \"OK\",\n        \"UUID\": \"4c4c4544-0030-5210-8047-c6c04f5a4333\",\n        \"VoltRollupStatus\": \"OK\",\n        \"smbiosGUID\": \"44454c4c-3000-1052-8047-c6c04f5a4333\",\n        \"@odata.context\": \"/redfish/v1/$metadata#DellSystem.DellSystem\",\n        \"@odata.type\": \"#DellSystem.v1_3_0.DellSystem\",\n        \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Oem/Dell/DellSystem/System.Embedded.1\"\n      }\n    }\n  },\n  \"PCIeDevices\": [\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-3\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-2\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-4\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-7\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-1\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-20\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-8\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/70-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/65-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/69-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-2\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-1\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/194-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/4-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-3\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-4\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-8\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-7\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/1-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-3\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-2\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/72-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-4\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-7\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-1\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-8\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-3\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-2\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-4\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-7\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-1\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-8\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/195-0\"\n    }\n  ],\n  \"PCIeDevices@odata.count\": 34,\n  \"PCIeFunctions\": [\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-3/PCIeFunctions/0-3-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-2/PCIeFunctions/0-2-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-4/PCIeFunctions/0-4-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-7/PCIeFunctions/0-7-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-1/PCIeFunctions/0-1-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-20/PCIeFunctions/0-20-3\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-8/PCIeFunctions/0-8-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/0-20/PCIeFunctions/0-20-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/70-0/PCIeFunctions/70-0-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/65-0/PCIeFunctions/65-0-1\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/65-0/PCIeFunctions/65-0-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/69-0/PCIeFunctions/69-0-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-2/PCIeFunctions/192-2-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-1/PCIeFunctions/192-1-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/194-0/PCIeFunctions/194-0-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/4-0/PCIeFunctions/4-0-3\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-3/PCIeFunctions/192-3-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-4/PCIeFunctions/192-4-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-8/PCIeFunctions/192-8-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-0/PCIeFunctions/192-0-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/192-7/PCIeFunctions/192-7-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/1-0/PCIeFunctions/1-0-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-3/PCIeFunctions/128-3-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-2/PCIeFunctions/128-2-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/72-0/PCIeFunctions/72-0-3\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-4/PCIeFunctions/128-4-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-7/PCIeFunctions/128-7-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-1/PCIeFunctions/128-1-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/128-8/PCIeFunctions/128-8-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-3/PCIeFunctions/64-3-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-2/PCIeFunctions/64-2-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-4/PCIeFunctions/64-4-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-7/PCIeFunctions/64-7-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-1/PCIeFunctions/64-1-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/64-8/PCIeFunctions/64-8-0\"\n    },\n    {\n      \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/PCIeDevices/195-0/PCIeFunctions/195-0-0\"\n    }\n  ],\n  \"PCIeFunctions@odata.count\": 36,\n  \"PartNumber\": \"07PXPYA01\",\n  \"PowerState\": \"On\",\n  \"ProcessorSummary\": {\n    \"Count\": 1,\n    \"LogicalProcessorCount\": 32,\n    \"Model\": \"AMD EPYC 7502P 32-Core Processor\",\n    \"Status\": {\n      \"Health\": \"OK\",\n      \"HealthRollup\": \"OK\",\n      \"State\": \"Enabled\"\n    }\n  },\n  \"Processors\": {\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Processors\"\n  },\n  \"SKU\": \"FOOBAR\",\n  \"SecureBoot\": {\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/SecureBoot\"\n  },\n  \"SerialNumber\": \"FOOBAR123\",\n  \"SimpleStorage\": {\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/SimpleStorage\"\n  },\n  \"Status\": {\n    \"Health\": \"OK\",\n    \"HealthRollup\": \"OK\",\n    \"State\": \"Enabled\"\n  },\n  \"Storage\": {\n    \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1/Storage\"\n  },\n  \"SystemType\": \"Physical\",\n  \"TrustedModules\": [\n    {\n      \"FirmwareVersion\": \"1.3.2.8\",\n      \"InterfaceType\": \"TPM2_0\",\n      \"Status\": {\n        \"State\": \"Enabled\"\n      }\n    }\n  ],\n  \"TrustedModules@odata.count\": 1,\n  \"UUID\": \"4c4c4544-0030-5210-8047-c6c04f5a4333\"\n}"
  },
  {
    "path": "providers/dell/idrac.go",
    "content": "package dell\n\nimport (\n\t\"context\"\n\t\"crypto/x509\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/internal/httpclient\"\n\t\"github.com/bmc-toolbox/bmclib/v2/internal/redfishwrapper\"\n\t\"github.com/bmc-toolbox/bmclib/v2/providers\"\n\t\"github.com/bmc-toolbox/common\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/jacobweinstock/registrar\"\n\t\"github.com/pkg/errors\"\n\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n)\n\nconst (\n\t// ProviderName for the provider Dell implementation\n\tProviderName = \"dell\"\n\t// ProviderProtocol for the provider Dell implementation\n\tProviderProtocol = \"redfish\"\n\n\tredfishV1Prefix           = \"/redfish/v1\"\n\tscreenshotEndpoint        = \"/Dell/Managers/iDRAC.Embedded.1/DellLCService/Actions/DellLCService.ExportServerScreenShot\"\n\tmanagerAttributesEndpoint = \"/Managers/iDRAC.Embedded.1/Attributes\"\n)\n\nvar (\n\t// Features implemented by dell redfish\n\tFeatures = registrar.Features{\n\t\tproviders.FeatureScreenshot,\n\t\tproviders.FeaturePowerState,\n\t\tproviders.FeaturePowerSet,\n\t\tproviders.FeatureFirmwareInstallSteps,\n\t\tproviders.FeatureFirmwareUploadInitiateInstall,\n\t\tproviders.FeatureFirmwareTaskStatus,\n\t\tproviders.FeatureInventoryRead,\n\t\tproviders.FeatureBmcReset,\n\t\tproviders.FeatureGetBiosConfiguration,\n\t\tproviders.FeatureSetBiosConfiguration,\n\t\tproviders.FeatureResetBiosConfiguration,\n\t}\n\n\terrManufacturerUnknown = errors.New(\"error identifying device manufacturer\")\n)\n\ntype Config struct {\n\tHttpClient            *http.Client\n\tPort                  string\n\tVersionsNotCompatible []string\n\tRootCAs               *x509.CertPool\n\tUseBasicAuth          bool\n}\n\n// Option for setting optional Client values\ntype Option func(*Config)\n\nfunc WithHttpClient(httpClient *http.Client) Option {\n\treturn func(c *Config) {\n\t\tc.HttpClient = httpClient\n\t}\n}\n\nfunc WithPort(port string) Option {\n\treturn func(c *Config) {\n\t\tc.Port = port\n\t}\n}\n\nfunc WithVersionsNotCompatible(versionsNotCompatible []string) Option {\n\treturn func(c *Config) {\n\t\tc.VersionsNotCompatible = versionsNotCompatible\n\t}\n}\n\nfunc WithRootCAs(rootCAs *x509.CertPool) Option {\n\treturn func(c *Config) {\n\t\tc.RootCAs = rootCAs\n\t}\n}\n\nfunc WithUseBasicAuth(useBasicAuth bool) Option {\n\treturn func(c *Config) {\n\t\tc.UseBasicAuth = useBasicAuth\n\t}\n}\n\n// Conn details for redfish client\ntype Conn struct {\n\tredfishwrapper *redfishwrapper.Client\n\tLog            logr.Logger\n}\n\n// New returns connection with a redfish client initialized\nfunc New(host, user, pass string, log logr.Logger, opts ...Option) *Conn {\n\tdefaultConfig := &Config{\n\t\tHttpClient:            httpclient.Build(),\n\t\tPort:                  \"443\",\n\t\tVersionsNotCompatible: []string{},\n\t}\n\n\tfor _, opt := range opts {\n\t\topt(defaultConfig)\n\t}\n\n\trfOpts := []redfishwrapper.Option{\n\t\tredfishwrapper.WithHTTPClient(defaultConfig.HttpClient),\n\t\tredfishwrapper.WithVersionsNotCompatible(defaultConfig.VersionsNotCompatible),\n\t\tredfishwrapper.WithBasicAuthEnabled(defaultConfig.UseBasicAuth),\n\t}\n\n\tif defaultConfig.RootCAs != nil {\n\t\trfOpts = append(rfOpts, redfishwrapper.WithSecureTLS(defaultConfig.RootCAs))\n\t}\n\n\treturn &Conn{\n\t\tLog:            log,\n\t\tredfishwrapper: redfishwrapper.NewClient(host, defaultConfig.Port, user, pass, rfOpts...),\n\t}\n}\n\n// Open a connection to a BMC via redfish\nfunc (c *Conn) Open(ctx context.Context) (err error) {\n\tif err := c.redfishwrapper.Open(ctx); err != nil {\n\t\treturn err\n\t}\n\n\t// because this uses the redfish interface and the redfish interface\n\t// is available across various BMC vendors, we verify the device we're connected to is dell.\n\tif err := c.deviceSupported(ctx); err != nil {\n\t\tif er := c.redfishwrapper.Close(ctx); er != nil {\n\t\t\treturn fmt.Errorf(\"%v: %w\", err, er)\n\t\t}\n\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *Conn) deviceSupported(ctx context.Context) error {\n\tmanufacturer, err := c.deviceManufacturer(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tm := strings.ToLower(manufacturer)\n\tif !strings.Contains(m, common.VendorDell) {\n\t\treturn errors.Wrap(bmclibErrs.ErrIncompatibleProvider, m)\n\t}\n\n\treturn nil\n}\n\n// Close a connection to a BMC via redfish\nfunc (c *Conn) Close(ctx context.Context) error {\n\treturn c.redfishwrapper.Close(ctx)\n}\n\n// Name returns the client provider name.\nfunc (c *Conn) Name() string {\n\treturn ProviderName\n}\n\n// Compatible tests whether a BMC is compatible with the gofish provider\nfunc (c *Conn) Compatible(ctx context.Context) bool {\n\terr := c.Open(ctx)\n\tif err != nil {\n\t\tc.Log.V(2).WithValues(\n\t\t\t\"provider\",\n\t\t\tc.Name(),\n\t\t).Info(\"warn\", bmclibErrs.ErrCompatibilityCheck.Error(), err.Error())\n\n\t\treturn false\n\t}\n\tdefer c.Close(ctx)\n\n\tif !c.redfishwrapper.VersionCompatible() {\n\t\tc.Log.V(2).WithValues(\n\t\t\t\"provider\",\n\t\t\tc.Name(),\n\t\t).Info(\"info\", bmclibErrs.ErrCompatibilityCheck.Error(), \"incompatible redfish version\")\n\n\t\treturn false\n\t}\n\n\t_, err = c.PowerStateGet(ctx)\n\tif err != nil {\n\t\tc.Log.V(2).WithValues(\n\t\t\t\"provider\",\n\t\t\tc.Name(),\n\t\t).Info(\"warn\", bmclibErrs.ErrCompatibilityCheck.Error(), err.Error())\n\t}\n\n\treturn err == nil\n}\n\n// PowerStateGet gets the power state of a BMC machine\nfunc (c *Conn) PowerStateGet(ctx context.Context) (state string, err error) {\n\treturn c.redfishwrapper.SystemPowerStatus(ctx)\n}\n\n// PowerSet sets the power state of a server\nfunc (c *Conn) PowerSet(ctx context.Context, state string) (ok bool, err error) {\n\treturn c.redfishwrapper.PowerSet(ctx, state)\n}\n\n// Inventory collects hardware inventory and install firmware information\nfunc (c *Conn) Inventory(ctx context.Context) (device *common.Device, err error) {\n\treturn c.redfishwrapper.Inventory(ctx, false)\n}\n\n// BmcReset power cycles the BMC\nfunc (c *Conn) BmcReset(ctx context.Context, resetType string) (ok bool, err error) {\n\treturn c.redfishwrapper.BMCReset(ctx, resetType)\n}\n\n// GetBiosConfiguration returns the BIOS configuration settings via the BMC\nfunc (c *Conn) GetBiosConfiguration(ctx context.Context) (biosConfig map[string]string, err error) {\n\treturn c.redfishwrapper.GetBiosConfiguration(ctx)\n}\n\n// SetBiosConfiguration sets the BIOS configuration settings via the BMC\nfunc (c *Conn) SetBiosConfiguration(ctx context.Context, biosConfig map[string]string) (err error) {\n\treturn c.redfishwrapper.SetBiosConfiguration(ctx, biosConfig)\n}\n\n// ResetBiosConfiguration resets the BIOS configuration settings back to 'factory defaults' via the BMC\nfunc (c *Conn) ResetBiosConfiguration(ctx context.Context) (err error) {\n\treturn c.redfishwrapper.ResetBiosConfiguration(ctx)\n}\n\n// SendNMI tells the BMC to issue an NMI to the device\nfunc (c *Conn) SendNMI(ctx context.Context) error {\n\treturn c.redfishwrapper.SendNMI(ctx)\n}\n\n// deviceManufacturer returns the device manufacturer and model attributes\nfunc (c *Conn) deviceManufacturer(ctx context.Context) (vendor string, err error) {\n\tsys, err := c.redfishwrapper.System()\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(errManufacturerUnknown, err.Error())\n\t}\n\n\tif sys.Manufacturer != \"\" {\n\t\treturn sys.Manufacturer, nil\n\t}\n\n\treturn \"\", errManufacturerUnknown\n}\n\nfunc (c *Conn) Screenshot(ctx context.Context) (image []byte, fileType string, err error) {\n\tfileType = \"png\"\n\n\tresp, err := c.redfishwrapper.PostWithHeaders(\n\t\tctx,\n\t\tredfishV1Prefix+screenshotEndpoint,\n\t\t// other FileType parameters are LastCrashScreenshot, Preview\n\t\tjson.RawMessage(`{\"FileType\":\"ServerScreenShot\"}`),\n\t\tmap[string]string{\"Content-Type\": \"application/json\"},\n\t)\n\tif err != nil {\n\t\treturn nil, \"\", errors.Wrap(bmclibErrs.ErrScreenshot, err.Error())\n\t}\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, \"\", errors.Wrap(bmclibErrs.ErrScreenshot, err.Error())\n\t}\n\n\tif resp.StatusCode != 200 {\n\t\treturn nil, \"\", errors.Wrap(bmclibErrs.ErrScreenshot, resp.Status)\n\t}\n\n\tdata := &struct {\n\t\tB64encoded string `json:\"ServerScreenshotFile\"`\n\t}{}\n\tif err := json.Unmarshal(body, &data); err != nil {\n\t\treturn nil, \"\", errors.Wrap(bmclibErrs.ErrScreenshot, err.Error())\n\t}\n\n\tif data.B64encoded == \"\" {\n\t\treturn nil, \"\", errors.Wrap(bmclibErrs.ErrScreenshot, \"no screencapture data in response\")\n\t}\n\n\timage, err = base64.StdEncoding.DecodeString(data.B64encoded)\n\tif err != nil {\n\t\treturn nil, \"\", errors.Wrap(bmclibErrs.ErrScreenshot, err.Error())\n\t}\n\n\treturn image, fileType, nil\n}\n"
  },
  {
    "path": "providers/dell/idrac_test.go",
    "content": "package dell\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"os\"\n\t\"testing\"\n\n\tberrors \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nconst (\n\tfixturesDir = \"./fixtures\"\n)\n\nvar endpointFunc = func(file string) http.HandlerFunc {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\t// expect either GET or Delete methods\n\t\tif r.Method != http.MethodGet && r.Method != http.MethodDelete {\n\t\t\tw.WriteHeader(http.StatusNotFound)\n\t\t}\n\n\t\tfixture := fixturesDir + file\n\t\tfh, err := os.Open(fixture)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\n\t\tdefer fh.Close()\n\n\t\tb, err := io.ReadAll(fh)\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\n\t\t_, _ = w.Write(b)\n\t}\n}\n\nfunc Test_Screenshot(t *testing.T) {\n\t// byte slice instead of a real image\n\timg := []byte(`foobar`)\n\n\t// endpoint to handler funcs\n\ttype handlerFuncMap map[string]func(http.ResponseWriter, *http.Request)\n\n\ttestcases := []struct {\n\t\tname           string\n\t\timgbytes       []byte\n\t\thandlerFuncMap handlerFuncMap\n\t}{\n\t\t{\n\t\t\t\"happy path\",\n\t\t\t[]byte(`foobar`),\n\t\t\thandlerFuncMap{\n\t\t\t\t// service root\n\t\t\t\t\"/redfish/v1/\":                          endpointFunc(\"/serviceroot.json\"),\n\t\t\t\t\"/redfish/v1/Systems\":                   endpointFunc(\"/systems.json\"),\n\t\t\t\t\"/redfish/v1/Systems/System.Embedded.1\": endpointFunc(\"/systems_embedded.1.json\"),\n\t\t\t\t// screenshot endpoint\n\t\t\t\tredfishV1Prefix + screenshotEndpoint: func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t\tassert.Equal(t, r.Method, http.MethodPost)\n\n\t\t\t\t\tassert.Equal(t, r.Header.Get(\"Content-Type\"), \"application/json\")\n\n\t\t\t\t\tb, err := io.ReadAll(r.Body)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Fatal(err)\n\t\t\t\t\t}\n\t\t\t\t\tassert.Equal(t, []byte(`{\"FileType\":\"ServerScreenShot\"}`), b)\n\n\t\t\t\t\tencoded := base64.RawStdEncoding.EncodeToString(img)\n\t\t\t\t\trespFmtStr := `{\"@Message.ExtendedInfo\":[{\"Message\":\"Successfully Completed Request\",\"MessageArgs\":[],\"MessageArgs@odata.count\":0,\"MessageId\":\"Base.1.8.Success\",\"RelatedProperties\":[],\"RelatedProperties@odata.count\":0,\"Resolution\":\"None\",\"Severity\":\"OK\"},{\"Message\":\"The Export Server Screen Shot operation successfully exported the server screen shot file.\",\"MessageArgs\":[],\"MessageArgs@odata.count\":0,\"MessageId\":\"IDRAC.2.5.LC080\",\"RelatedProperties\":[],\"RelatedProperties@odata.count\":0,\"Resolution\":\"Download the encoded Base64 format server screen shot file, decode the Base64 file and then save it as a *.png file.\",\"Severity\":\"Informational\"}],\"ServerScreenshotFile\":\"%s\"}`\n\n\t\t\t\t\t_, _ = w.Write([]byte(fmt.Sprintf(respFmtStr, encoded)))\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmux := http.NewServeMux()\n\n\t\t\tfor endpoint, handler := range tc.handlerFuncMap {\n\t\t\t\tmux.HandleFunc(endpoint, handler)\n\t\t\t}\n\n\t\t\tserver := httptest.NewTLSServer(mux)\n\t\t\tdefer server.Close()\n\n\t\t\tparsedURL, err := url.Parse(server.URL)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\t//os.Setenv(\"DEBUG_BMCLIB\", \"true\")\n\t\t\tclient := New(parsedURL.Hostname(), \"\", \"\", logr.Discard(), WithPort(parsedURL.Port()), WithUseBasicAuth(true))\n\n\t\t\terr = client.Open(context.TODO())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\timg, fileType, err := client.Screenshot(context.TODO())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tassert.Equal(t, tc.imgbytes, img)\n\t\t\tassert.Equal(t, \"png\", fileType)\n\t\t})\n\t}\n}\n\nfunc TestOpenErrors(t *testing.T) {\n\ttests := map[string]struct {\n\t\tfns map[string]func(http.ResponseWriter, *http.Request)\n\t\terr error\n\t}{\n\t\t\"not dell manufacturer\": {\n\t\t\tfns: map[string]func(http.ResponseWriter, *http.Request){\n\t\t\t\t// service root\n\t\t\t\t\"/redfish/v1/\":                          endpointFunc(\"/serviceroot.json\"),\n\t\t\t\t\"/redfish/v1/Systems\":                   endpointFunc(\"/systems.json\"),\n\t\t\t\t\"/redfish/v1/Systems/System.Embedded.1\": endpointFunc(\"/systems_embedded_not_dell.1.json\"),\n\t\t\t},\n\t\t\terr: berrors.ErrIncompatibleProvider,\n\t\t},\n\t\t\"manufacturer failure\": {\n\t\t\tfns: map[string]func(http.ResponseWriter, *http.Request){\n\t\t\t\t// service root\n\t\t\t\t\"/redfish/v1/\":                          endpointFunc(\"/serviceroot.json\"),\n\t\t\t\t\"/redfish/v1/Systems\":                   endpointFunc(\"/systems.json\"),\n\t\t\t\t\"/redfish/v1/Systems/System.Embedded.1\": endpointFunc(\"/systems_embedded_no_manufacturer.1.json\"),\n\t\t\t},\n\t\t\terr: errManufacturerUnknown,\n\t\t},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tmux := http.NewServeMux()\n\t\t\thandleFunc := tc.fns\n\t\t\tfor endpoint, handler := range handleFunc {\n\t\t\t\tmux.HandleFunc(endpoint, handler)\n\t\t\t}\n\t\t\tserver := httptest.NewTLSServer(mux)\n\t\t\tdefer server.Close()\n\n\t\t\tparsedURL, err := url.Parse(server.URL)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tclient := New(parsedURL.Hostname(), \"\", \"\", logr.Discard(), WithPort(parsedURL.Port()), WithUseBasicAuth(true))\n\n\t\t\terr = client.Open(context.TODO())\n\t\t\tif !errors.Is(err, tc.err) {\n\t\t\t\tt.Fatalf(\"expected %v, got %v\", tc.err, err)\n\t\t\t}\n\t\t\tclient.Close(context.Background())\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "providers/homeassistant/homeassistant.go",
    "content": "package homeassistant\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"time\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/internal/httpclient\"\n\t\"github.com/bmc-toolbox/bmclib/v2/providers\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/jacobweinstock/registrar\"\n)\n\nconst (\n\t// ProviderName for the HomeAssistant implementation.\n\tProviderName = \"homeassistant\"\n\t// ProviderProtocol for the HomeAssistant implementation.\n\tProviderProtocol = \"http\"\n)\n\n// Features implemented by the HomeAssistant provider.\nvar Features = registrar.Features{\n\tproviders.FeaturePowerSet,\n\tproviders.FeaturePowerState,\n\tproviders.FeatureBootDeviceSet, // no-op\n}\n\ntype Config struct {\n\tApiUrl                     string\n\tApiToken                   string\n\tSwitchEntityID             string\n\tPowerOperationDelaySeconds uint32\n\tHTTPClient                 *http.Client\n\tLogger                     logr.Logger\n}\n\ntype EntityStateResponse struct {\n\tEntityID     string\n\tFriendlyName string\n\tState        string\n}\n\n// New returns a new Config containing all the defaults for the HomeAssistant provider.\nfunc New(apiUrl string, apiToken string) *Config {\n\treturn &Config{\n\t\tApiUrl:     apiUrl,\n\t\tApiToken:   apiToken,\n\t\tHTTPClient: httpclient.Build(),\n\t\tLogger:     logr.Discard(),\n\t}\n}\n\n// Name returns the name of this HomeAssistant provider.\n// Implements bmc.Provider interface\nfunc (p *Config) Name() string {\n\treturn ProviderName\n}\n\n// Open a connection to Home Assistant, and validate the entity referenced exists.\nfunc (p *Config) Open(ctx context.Context) error {\n\tp.Logger.Info(\"homeassistant provider opened\")\n\n\tentityState, err := p.haGetEntityState(ctx, p.SwitchEntityID)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get Home Assistant entity state: %w\", err)\n\t}\n\n\tp.Logger.Info(\"Home Assistant entity state\", \"entity\", p.SwitchEntityID, \"entityState\", entityState)\n\n\treturn nil\n}\n\nfunc (p *Config) haGetEntityState(ctx context.Context, haEntityId string) (EntityStateResponse, error) {\n\tstateUrl, err := url.JoinPath(p.ApiUrl, \"api\", \"states\", haEntityId)\n\tif err != nil {\n\t\treturn EntityStateResponse{}, err\n\t}\n\tp.Logger.Info(\"Testing connection to Home Assistant API\", \"url\", stateUrl)\n\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", stateUrl, nil)\n\tif err != nil {\n\t\treturn EntityStateResponse{}, err\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+p.ApiToken)\n\treq.Header.Set(\"Accept-Encoding\", \"application/json\")\n\n\tresp, err := p.HTTPClient.Do(req)\n\tif err != nil {\n\t\treturn EntityStateResponse{}, err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn EntityStateResponse{}, fmt.Errorf(\"failed to connect to Home Assistant API, status code: %d\", resp.StatusCode)\n\t}\n\tif resp.ContentLength < 0 {\n\t\treturn EntityStateResponse{}, fmt.Errorf(\"invalid content length in response: %d\", resp.ContentLength)\n\t}\n\trespBuf := new(bytes.Buffer)\n\tif _, err := io.CopyN(respBuf, resp.Body, resp.ContentLength); err != nil {\n\t\treturn EntityStateResponse{}, fmt.Errorf(\"failed to read response body: %w\", err)\n\t}\n\tp.Logger.Info(\"Successfully connected to Home Assistant API\", \"entity\", haEntityId, \"statusCode\", resp.StatusCode, \"respBuf\", respBuf)\n\n\t// Deserialize into a temp struct\n\tstateResponse := struct {\n\t\tState      string            `json:\"state\"`\n\t\tEntityID   string            `json:\"entity_id\"`\n\t\tAttributes map[string]string `json:\"attributes\"`\n\t}{}\n\tif err := json.Unmarshal(respBuf.Bytes(), &stateResponse); err != nil {\n\t\treturn EntityStateResponse{}, fmt.Errorf(\"failed to unmarshal response body: %w\", err)\n\t}\n\n\t// Ensure we have Attributes[\"friendly_name\"] field\n\tif _, ok := stateResponse.Attributes[\"friendly_name\"]; !ok {\n\t\treturn EntityStateResponse{}, fmt.Errorf(\"missing friendly_name attribute in response\")\n\t}\n\tfinalResponse := EntityStateResponse{\n\t\tEntityID:     stateResponse.EntityID,\n\t\tFriendlyName: stateResponse.Attributes[\"friendly_name\"],\n\t\tState:        stateResponse.State,\n\t}\n\treturn finalResponse, nil\n}\n\n// Close a connection to the HomeAssistant consumer.\nfunc (p *Config) Close(_ context.Context) (err error) {\n\treturn nil\n}\n\n// PowerStateGet gets the power state of a BMC machine.\nfunc (p *Config) PowerStateGet(ctx context.Context) (state string, err error) {\n\tentityState, err := p.haGetEntityState(ctx, p.SwitchEntityID)\n\tif err != nil {\n\t\treturn \"unknown\", fmt.Errorf(\"failed to get Home Assistant entity state: %w\", err)\n\t}\n\tp.Logger.Info(\"Home Assistant PowerStateGet\", \"entity\", p.SwitchEntityID, \"entityState\", entityState)\n\treturn entityState.State, nil\n}\n\n// PowerSet sets the power state of a BMC machine.\nfunc (p *Config) PowerSet(ctx context.Context, state string) (ok bool, err error) {\n\t// Send a POST request to the Home Assistant API to toggle the switch entity\n\n\tvar service string\n\tif state == \"on\" {\n\t\tservice = \"turn_on\"\n\t} else if state == \"off\" {\n\t\tservice = \"turn_off\"\n\t} else {\n\t\treturn false, fmt.Errorf(\"invalid power state: %s\", state)\n\t}\n\n\tserviceUrl, err := url.JoinPath(p.ApiUrl, \"api\", \"services\", \"switch\", service)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tp.Logger.Info(\"Setting Home Assistant entity power state\", \"url\", serviceUrl, \"entity\", p.SwitchEntityID, \"desiredState\", state)\n\treqBodyMap := map[string]interface{}{\n\t\t\"entity_id\": p.SwitchEntityID,\n\t}\n\treqBodyBytes, err := json.Marshal(reqBodyMap)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to marshal request body: %w\", err)\n\t}\n\n\treq, err := http.NewRequestWithContext(ctx, \"POST\", serviceUrl, bytes.NewBuffer(reqBodyBytes))\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+p.ApiToken)\n\treq.Header.Set(\"Accept-Encoding\", \"application/json\")\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tresp, err := p.HTTPClient.Do(req)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusAccepted {\n\t\treturn false, fmt.Errorf(\"failed to set power state, status code: %d\", resp.StatusCode)\n\t}\n\n\tp.Logger.Info(\"Successfully set Home Assistant entity power state\", \"entity\", p.SwitchEntityID, \"desiredState\", state)\n\n\t// Sleep for the configured delay to allow the power operation to take effect\n\tif p.PowerOperationDelaySeconds > 0 {\n\t\tp.Logger.Info(\"Waiting for power operation delay\", \"seconds\", p.PowerOperationDelaySeconds)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn false, ctx.Err()\n\t\tcase <-time.After(time.Duration(p.PowerOperationDelaySeconds) * time.Second):\n\t\t}\n\t\tp.Logger.Info(\"Power operation delay complete\")\n\t} else {\n\t\tp.Logger.Info(\"No power operation delay configured, proceeding immediately\")\n\t}\n\n\treturn true, nil\n}\n\n// BootDeviceSet is a no-op here.\nfunc (p *Config) BootDeviceSet(ctx context.Context, bootDevice string, setPersistent, efiBoot bool) (ok bool, err error) {\n\t// fully no-op for now; in the future, some other switch could touch some GPIO which could work with a custom bootloader\n\tp.Logger.Info(\"BootDeviceSet is not implemented for Home Assistant provider; no operation performed\", \"bootDevice\", bootDevice, \"setPersistent\", setPersistent, \"efiBoot\", efiBoot)\n\treturn true, nil\n}\n"
  },
  {
    "path": "providers/intelamt/intelamt.go",
    "content": "package intelamt\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/providers\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/jacobweinstock/iamt\"\n\t\"github.com/jacobweinstock/registrar\"\n)\n\nconst (\n\t// ProviderName for the provider AMT implementation\n\tProviderName = \"IntelAMT\"\n\t// ProviderProtocol for the provider AMT implementation\n\tProviderProtocol = \"AMT\"\n)\n\nvar (\n\t// Features implemented by the AMT provider\n\tFeatures = registrar.Features{\n\t\tproviders.FeaturePowerSet,\n\t\tproviders.FeaturePowerState,\n\t\tproviders.FeatureBootDeviceSet,\n\t}\n)\n\n// iamtClient interface allows us to mock the client for testing\ntype iamtClient interface {\n\tClose(context.Context) error\n\tIsPoweredOn(context.Context) (bool, error)\n\tOpen(context.Context) error\n\tPowerCycle(context.Context) error\n\tPowerOff(context.Context) error\n\tPowerOn(context.Context) error\n\tSetPXE(context.Context) error\n}\n\n// Conn is a connection to a BMC via Intel AMT\ntype Conn struct {\n\tclient iamtClient\n}\n\n// Option for setting optional Client values\ntype Option func(*Config)\n\nfunc WithPort(port uint32) Option {\n\treturn func(c *Config) {\n\t\tc.Port = port\n\t}\n}\n\nfunc WithHostScheme(hostScheme string) Option {\n\treturn func(c *Config) {\n\t\tc.HostScheme = hostScheme\n\t}\n}\n\nfunc WithLogger(logger logr.Logger) Option {\n\treturn func(c *Config) {\n\t\tc.Logger = logger\n\t}\n}\n\ntype Config struct {\n\t// HostScheme should be either \"http\" or \"https\".\n\tHostScheme string\n\t// Port is the port number to connect to.\n\tPort   uint32\n\tLogger logr.Logger\n}\n\n// New creates a new AMT connection\nfunc New(host string, user string, pass string, opts ...Option) *Conn {\n\tdefaultClient := &Config{\n\t\tHostScheme: \"http\",\n\t\tPort:       16992,\n\t\tLogger:     logr.Discard(),\n\t}\n\tfor _, opt := range opts {\n\t\topt(defaultClient)\n\t}\n\n\tiopts := []iamt.Option{\n\t\tiamt.WithLogger(defaultClient.Logger),\n\t\tiamt.WithPort(defaultClient.Port),\n\t\tiamt.WithScheme(defaultClient.HostScheme),\n\t}\n\treturn &Conn{\n\t\tclient: iamt.NewClient(host, user, pass, iopts...),\n\t}\n}\n\n// Name of the provider\nfunc (c *Conn) Name() string {\n\treturn ProviderName\n}\n\n// Open a connection to the BMC via Intel AMT.\nfunc (c *Conn) Open(ctx context.Context) (err error) {\n\treturn c.client.Open(ctx)\n}\n\n// Close a connection to a BMC\nfunc (c *Conn) Close(ctx context.Context) (err error) {\n\treturn c.client.Close(ctx)\n}\n\n// Compatible tests whether a BMC is compatible with the ipmitool provider\nfunc (c *Conn) Compatible(ctx context.Context) bool {\n\tif err := c.client.Open(ctx); err != nil {\n\t\treturn false\n\t}\n\n\tif _, err := c.client.IsPoweredOn(ctx); err != nil {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\n// BootDeviceSet sets the next boot device with options\nfunc (c *Conn) BootDeviceSet(ctx context.Context, bootDevice string, setPersistent, efiBoot bool) (ok bool, err error) {\n\tif strings.ToLower(bootDevice) != \"pxe\" {\n\t\treturn false, errors.New(\"only pxe boot device is supported for AMT provider\")\n\t}\n\tif err := c.client.SetPXE(ctx); err != nil {\n\t\treturn false, err\n\t}\n\n\treturn true, nil\n}\n\n// PowerStateGet gets the power state of a BMC machine\nfunc (c *Conn) PowerStateGet(ctx context.Context) (state string, err error) {\n\ton, err := c.client.IsPoweredOn(ctx)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif on {\n\t\treturn \"on\", nil\n\t}\n\n\treturn \"off\", nil\n}\n\n// PowerSet sets the power state of a BMC machine\nfunc (c *Conn) PowerSet(ctx context.Context, state string) (ok bool, err error) {\n\ton, _ := c.client.IsPoweredOn(ctx)\n\n\tswitch strings.ToLower(state) {\n\tcase \"on\":\n\t\tif on {\n\t\t\treturn true, nil\n\t\t}\n\t\tif err := c.client.PowerOn(ctx); err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tok = true\n\tcase \"off\":\n\t\tif !on {\n\t\t\treturn true, nil\n\t\t}\n\t\tif err := c.client.PowerOff(ctx); err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tok = true\n\tcase \"cycle\":\n\t\tif err := c.client.PowerCycle(ctx); err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tok = true\n\tdefault:\n\t\terr = errors.New(\"requested state type unknown\")\n\t}\n\n\treturn ok, err\n}\n"
  },
  {
    "path": "providers/intelamt/intelamt_test.go",
    "content": "package intelamt\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/go-logr/logr\"\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n)\n\ntype mock struct {\n\terrSetPXE      error\n\terrIsPoweredOn error\n\tpoweredON      bool\n\terrPowerOn     error\n\terrPowerOff    error\n\terrPowerCycle  error\n\terrOpen        error\n}\n\nfunc (m *mock) Open(ctx context.Context) error {\n\treturn m.errOpen\n}\n\nfunc (m *mock) Close(ctx context.Context) error {\n\treturn nil\n}\n\nfunc (m *mock) IsPoweredOn(ctx context.Context) (bool, error) {\n\tif m.errIsPoweredOn != nil {\n\t\treturn false, m.errIsPoweredOn\n\t}\n\treturn m.poweredON, nil\n}\n\nfunc (m *mock) PowerOn(ctx context.Context) error {\n\treturn m.errPowerOn\n}\n\nfunc (m *mock) PowerOff(ctx context.Context) error {\n\treturn m.errPowerOff\n}\n\nfunc (m *mock) PowerCycle(ctx context.Context) error {\n\treturn m.errPowerCycle\n}\n\nfunc (m *mock) SetPXE(ctx context.Context) error {\n\treturn m.errSetPXE\n}\n\nfunc TestClose(t *testing.T) {\n\tconn := &Conn{client: &mock{}}\n\tif err := conn.Close(context.Background()); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestName(t *testing.T) {\n\tconn := &Conn{client: &mock{}}\n\tif diff := cmp.Diff(conn.Name(), ProviderName); diff != \"\" {\n\t\tt.Fatal(diff)\n\t}\n}\n\nfunc TestBootDeviceSet(t *testing.T) {\n\ttests := map[string]struct {\n\t\twant     bool\n\t\terr      error\n\t\tfailCall bool\n\t\tdevice   string\n\t}{\n\t\t\"success\":                   {want: true, device: \"pxe\"},\n\t\t\"invalid boot device\":       {want: false, err: errors.New(\"only pxe boot device is supported for AMT provider\"), device: \"invalid\"},\n\t\t\"failed to set boot device\": {want: false, failCall: true, err: errors.New(\"\"), device: \"pxe\"},\n\t}\n\n\tfor name, tt := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tm := &mock{}\n\t\t\tif tt.failCall {\n\t\t\t\tm = &mock{errSetPXE: tt.err}\n\t\t\t}\n\t\t\tconn := &Conn{client: m}\n\t\t\tctx := context.Background()\n\t\t\tif err := conn.Open(ctx); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer conn.Close(ctx)\n\t\t\tgot, err := conn.BootDeviceSet(ctx, tt.device, false, false)\n\t\t\tif err != nil && tt.err == nil {\n\t\t\t\tt.Fatalf(\"expected nil error, got: %v\", err)\n\t\t\t}\n\t\t\tif diff := cmp.Diff(got, tt.want); diff != \"\" {\n\t\t\t\tt.Fatal(diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPowerStateGet(t *testing.T) {\n\ttests := map[string]struct {\n\t\twant string\n\t\terr  error\n\t}{\n\t\t\"power on\":                  {want: \"on\"},\n\t\t\"power off\":                 {want: \"off\"},\n\t\t\"invalid power state\":       {want: \"\", err: errors.New(\"invalid power state: invalid\")},\n\t\t\"failed to set power state\": {want: \"\", err: errors.New(\"\")},\n\t}\n\n\tfor name, tt := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tvar state bool\n\t\t\tswitch tt.want {\n\t\t\tcase \"on\":\n\t\t\t\tstate = true\n\t\t\tcase \"off\":\n\t\t\t\tstate = false\n\t\t\tdefault:\n\t\t\t}\n\t\t\tm := &mock{poweredON: state, errIsPoweredOn: tt.err}\n\t\t\tconn := &Conn{client: m}\n\t\t\tctx := context.Background()\n\t\t\tif err := conn.Open(ctx); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer conn.Close(ctx)\n\t\t\tgot, err := conn.PowerStateGet(ctx)\n\t\t\tif err != nil && tt.err == nil {\n\t\t\t\tt.Fatalf(\"expected nil error, got: %v\", err)\n\t\t\t}\n\t\t\tif diff := cmp.Diff(got, tt.want); diff != \"\" {\n\t\t\t\tt.Fatal(diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPowerSet(t *testing.T) {\n\ttests := map[string]struct {\n\t\twant      bool\n\t\terr       error\n\t\tpoweredOn bool\n\t\twantState string\n\t}{\n\t\t\"power on success\":     {want: true, wantState: \"on\"},\n\t\t\"power on success 2\":   {want: true, wantState: \"on\", poweredOn: true},\n\t\t\"power on failed\":      {want: false, wantState: \"on\", err: errors.New(\"failed to power on\")},\n\t\t\"power off success\":    {want: true, wantState: \"off\"},\n\t\t\"power off success 2\":  {want: true, wantState: \"off\", poweredOn: true},\n\t\t\"power off failed\":     {want: false, poweredOn: true, wantState: \"off\", err: errors.New(\"failed to power off\")},\n\t\t\"power cycle success\":  {want: true, wantState: \"cycle\"},\n\t\t\"power cycle failed\":   {want: false, wantState: \"cycle\", err: errors.New(\"failed to power cycle\")},\n\t\t\"power cycle failed 2\": {want: false, wantState: \"cycle\", poweredOn: false, err: errors.New(\"failed to power cycle\")},\n\t\t\"invalid power state\":  {want: false, wantState: \"unknown\", err: errors.New(\"requested state type unknown\")},\n\t}\n\n\tfor name, tt := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tm := &mock{}\n\t\t\tswitch name {\n\t\t\tcase \"power on failed\":\n\t\t\t\tm.errPowerOn = tt.err\n\t\t\tcase \"power off failed\":\n\t\t\t\tm.errPowerOff = tt.err\n\t\t\tcase \"power cycle failed\":\n\t\t\t\tm.errPowerCycle = tt.err\n\t\t\tcase \"power cycle failed 2\":\n\t\t\t\tm.errPowerCycle = tt.err\n\t\t\t\tm.errPowerOn = tt.err\n\t\t\tdefault:\n\t\t\t}\n\t\t\tm.poweredON = tt.poweredOn\n\t\t\tconn := &Conn{client: m}\n\t\t\tctx := context.Background()\n\t\t\tif err := conn.Open(ctx); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tdefer conn.Close(ctx)\n\t\t\tgot, err := conn.PowerSet(ctx, tt.wantState)\n\t\t\tif err != nil && tt.err == nil {\n\t\t\t\tt.Fatalf(\"expected nil error, got: %v\", err)\n\t\t\t}\n\t\t\tif diff := cmp.Diff(got, tt.want); diff != \"\" {\n\t\t\t\tt.Fatal(diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCompatible(t *testing.T) {\n\ttests := map[string]struct {\n\t\twant       bool\n\t\tfailOnOpen bool\n\t}{\n\t\t\"success\":         {want: true},\n\t\t\"failed on open\":  {want: false, failOnOpen: true},\n\t\t\"failed on power\": {want: false},\n\t}\n\n\tfor name, tt := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tm := &mock{}\n\t\t\tif !tt.want {\n\t\t\t\tif tt.failOnOpen {\n\t\t\t\t\tm.errOpen = errors.New(\"failed to open\")\n\t\t\t\t} else {\n\t\t\t\t\tm.errIsPoweredOn = errors.New(\"failed to power on\")\n\t\t\t\t}\n\t\t\t}\n\t\t\tconn := &Conn{client: m}\n\t\t\tctx := context.Background()\n\t\t\tdefer conn.Close(ctx)\n\t\t\tgot := conn.Compatible(ctx)\n\t\t\tif diff := cmp.Diff(got, tt.want); diff != \"\" {\n\t\t\t\tt.Fatal(diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNew(t *testing.T) {\n\twantClient := &mock{}\n\twant := &Conn{client: wantClient}\n\tgot := New(\"localhost\", \"admin\", \"pass\")\n\tt.Log(got == nil)\n\tc := Conn{}\n\tl := logr.Logger{}\n\tif diff := cmp.Diff(got, want, cmpopts.IgnoreUnexported(c, l)); diff != \"\" {\n\t\tt.Fatal(diff)\n\t}\n}\n"
  },
  {
    "path": "providers/ipmitool/ipmitool.go",
    "content": "package ipmitool\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"strings\"\n\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/bmc-toolbox/bmclib/v2/internal/ipmi\"\n\t\"github.com/bmc-toolbox/bmclib/v2/providers\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/jacobweinstock/registrar\"\n)\n\nconst (\n\t// ProviderName for the provider implementation\n\tProviderName = \"ipmitool\"\n\t// ProviderProtocol for the provider implementation\n\tProviderProtocol = \"ipmi\"\n)\n\nvar (\n\t// Features implemented by ipmitool\n\tFeatures = registrar.Features{\n\t\tproviders.FeaturePowerSet,\n\t\tproviders.FeaturePowerState,\n\t\tproviders.FeatureUserRead,\n\t\tproviders.FeatureBmcReset,\n\t\tproviders.FeatureBootDeviceSet,\n\t\tproviders.FeatureClearSystemEventLog,\n\t\tproviders.FeatureGetSystemEventLog,\n\t\tproviders.FeatureGetSystemEventLogRaw,\n\t\tproviders.FeatureDeactivateSOL,\n\t}\n)\n\n// Conn for Ipmitool connection details\ntype Conn struct {\n\tipmitool *ipmi.Ipmi\n\tlog      logr.Logger\n}\n\ntype Config struct {\n\tCipherSuite  string\n\tIpmitoolPath string\n\tLog          logr.Logger\n\tPort         string\n}\n\n// Option for setting optional Client values\ntype Option func(*Config)\n\nfunc WithLogger(log logr.Logger) Option {\n\treturn func(c *Config) {\n\t\tc.Log = log\n\t}\n}\n\nfunc WithPort(port string) Option {\n\treturn func(c *Config) {\n\t\tc.Port = port\n\t}\n}\n\nfunc WithCipherSuite(cipherSuite string) Option {\n\treturn func(c *Config) {\n\t\tc.CipherSuite = cipherSuite\n\t}\n}\n\nfunc WithIpmitoolPath(ipmitoolPath string) Option {\n\treturn func(c *Config) {\n\t\tc.IpmitoolPath = ipmitoolPath\n\t}\n}\n\nfunc New(host, user, pass string, opts ...Option) (*Conn, error) {\n\tdefaultConfig := &Config{\n\t\tPort: \"623\",\n\t\tLog:  logr.Discard(),\n\t}\n\n\tfor _, opt := range opts {\n\t\topt(defaultConfig)\n\t}\n\n\tiopts := []ipmi.Option{\n\t\tipmi.WithIpmitoolPath(defaultConfig.IpmitoolPath),\n\t\tipmi.WithCipherSuite(defaultConfig.CipherSuite),\n\t\tipmi.WithLogger(defaultConfig.Log),\n\t}\n\tipt, err := ipmi.New(user, pass, host+\":\"+defaultConfig.Port, iopts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &Conn{ipmitool: ipt, log: defaultConfig.Log}, nil\n}\n\n// Open a connection to a BMC\nfunc (c *Conn) Open(ctx context.Context) (err error) {\n\t_, err = c.ipmitool.PowerState(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Close a connection to a BMC\nfunc (c *Conn) Close(ctx context.Context) (err error) {\n\treturn nil\n}\n\n// Compatible tests whether a BMC is compatible with the ipmitool provider\nfunc (c *Conn) Compatible(ctx context.Context) bool {\n\terr := c.Open(ctx)\n\tif err != nil {\n\t\tc.log.V(2).WithValues(\n\t\t\t\"provider\",\n\t\t\tc.Name(),\n\t\t).Info(\"warn\", bmclibErrs.ErrCompatibilityCheck.Error(), err.Error())\n\n\t\treturn false\n\t}\n\tdefer c.Close(ctx)\n\n\t_, err = c.ipmitool.PowerState(ctx)\n\tif err != nil {\n\t\tc.log.V(2).WithValues(\n\t\t\t\"provider\",\n\t\t\tc.Name(),\n\t\t).Info(\"warn\", bmclibErrs.ErrCompatibilityCheck.Error(), err.Error())\n\t}\n\n\treturn err == nil\n}\n\nfunc (c *Conn) Name() string {\n\treturn ProviderName\n}\n\n// BootDeviceSet sets the next boot device with options\nfunc (c *Conn) BootDeviceSet(ctx context.Context, bootDevice string, setPersistent, efiBoot bool) (ok bool, err error) {\n\treturn c.ipmitool.BootDeviceSet(ctx, bootDevice, setPersistent, efiBoot)\n}\n\n// BmcReset will reset a BMC\nfunc (c *Conn) BmcReset(ctx context.Context, resetType string) (ok bool, err error) {\n\treturn c.ipmitool.PowerResetBmc(ctx, resetType)\n}\n\n// DeactivateSOL will deactivate active SOL sessions\nfunc (c *Conn) DeactivateSOL(ctx context.Context) (err error) {\n\treturn c.ipmitool.DeactivateSOL(ctx)\n}\n\n// UserRead list all users\nfunc (c *Conn) UserRead(ctx context.Context) (users []map[string]string, err error) {\n\treturn c.ipmitool.ReadUsers(ctx)\n}\n\n// PowerStateGet gets the power state of a BMC machine\nfunc (c *Conn) PowerStateGet(ctx context.Context) (state string, err error) {\n\treturn c.ipmitool.PowerState(ctx)\n}\n\n// PowerSet sets the power state of a BMC machine\nfunc (c *Conn) PowerSet(ctx context.Context, state string) (ok bool, err error) {\n\tswitch strings.ToLower(state) {\n\tcase \"on\":\n\t\ton, errOn := c.ipmitool.IsOn(ctx)\n\t\tif errOn != nil || !on {\n\t\t\tok, err = c.ipmitool.PowerOn(ctx)\n\t\t} else {\n\t\t\tok = true\n\t\t}\n\tcase \"off\":\n\t\tok, err = c.ipmitool.PowerOff(ctx)\n\tcase \"soft\":\n\t\tok, err = c.ipmitool.PowerSoft(ctx)\n\tcase \"reset\":\n\t\tok, err = c.ipmitool.PowerReset(ctx)\n\tcase \"cycle\":\n\t\tok, err = c.ipmitool.PowerCycle(ctx)\n\tdefault:\n\t\terr = errors.New(\"requested state type unknown\")\n\t}\n\n\treturn ok, err\n}\n\nfunc (c *Conn) ClearSystemEventLog(ctx context.Context) (err error) {\n\treturn c.ipmitool.ClearSystemEventLog(ctx)\n}\n\nfunc (c *Conn) GetSystemEventLog(ctx context.Context) (entries [][]string, err error) {\n\treturn c.ipmitool.GetSystemEventLog(ctx)\n}\n\nfunc (c *Conn) GetSystemEventLogRaw(ctx context.Context) (eventlog string, err error) {\n\treturn c.ipmitool.GetSystemEventLogRaw(ctx)\n}\n\n// SendNMI tells the BMC to issue an NMI to the device\nfunc (c *Conn) SendNMI(ctx context.Context) error {\n\treturn c.ipmitool.SendPowerDiag(ctx)\n}\n"
  },
  {
    "path": "providers/ipmitool/ipmitool_test.go",
    "content": "package ipmitool\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/logging\"\n)\n\nfunc TestMain(m *testing.M) {\n\tvar tempDir string\n\t_, err := exec.LookPath(\"ipmitool\")\n\tif err != nil {\n\t\ttempDir, err = os.MkdirTemp(\"/tmp\", \"\")\n\t\tif err != nil {\n\t\t\tos.Exit(2)\n\t\t}\n\t\tpath := os.Getenv(\"PATH\") + \":\" + tempDir\n\t\tos.Setenv(\"PATH\", path)\n\t\tfmt.Println(os.Getenv(\"PATH\"))\n\t\tf := filepath.Join(tempDir, \"ipmitool\")\n\t\terr = os.WriteFile(f, []byte{}, 0755)\n\t\tif err != nil {\n\t\t\tos.RemoveAll(tempDir)\n\t\t\tos.Exit(3)\n\t\t}\n\t}\n\n\tcode := m.Run()\n\tos.RemoveAll(tempDir)\n\tos.Exit(code)\n}\n\nfunc TestPowerState(t *testing.T) {\n\tt.Skip(\"need real ipmi server\")\n\tuser := \"ADMIN\"\n\tpass := \"ADMIN\"\n\thost := \"127.0.0.1\"\n\tport := \"623\"\n\tc, err := New(host, user, pass, WithPort(port), WithLogger(logging.DefaultLogger()))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tstate, err := c.PowerStateGet(context.Background())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Log(state)\n\tt.Fatal()\n}\n\nfunc TestPowerSet1(t *testing.T) {\n\tt.Skip(\"need real ipmi server\")\n\tuser := \"ADMIN\"\n\tpass := \"ADMIN\"\n\thost := \"127.0.0.1\"\n\tport := \"623\"\n\tc, err := New(host, user, pass, WithPort(port), WithLogger(logging.DefaultLogger()))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tstate, err := c.PowerSet(context.Background(), \"soft\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Log(state)\n\tt.Fatal()\n}\n\nfunc TestBootDeviceSet2(t *testing.T) {\n\tt.Skip(\"need real ipmi server\")\n\thost := \"127.0.0.1\"\n\tport := \"623\"\n\tuser := \"ADMIN\"\n\tpass := \"ADMIN\"\n\ti, err := New(host, user, pass, WithPort(port), WithLogger(logging.DefaultLogger()))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tstate, err := i.BootDeviceSet(context.Background(), \"disk\", false, false)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Log(state)\n\tt.Fatal()\n}\n\nfunc TestBMCReset(t *testing.T) {\n\tt.Skip(\"need real ipmi server\")\n\thost := \"127.0.0.1\"\n\tport := \"623\"\n\tuser := \"ADMIN\"\n\tpass := \"ADMIN\"\n\ti, err := New(host, user, pass, WithPort(port), WithLogger(logging.DefaultLogger()))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tstate, err := i.BmcReset(context.Background(), \"warm\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Log(state)\n\tt.Fatal()\n}\n\nfunc TestDeactivateSOL(t *testing.T) {\n\tt.Skip(\"need real ipmi server\")\n\thost := \"127.0.0.1\"\n\tport := \"623\"\n\tuser := \"ADMIN\"\n\tpass := \"ADMIN\"\n\ti, err := New(host, user, pass, WithPort(port), WithLogger(logging.DefaultLogger()))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\terr = i.DeactivateSOL(context.Background())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Log(err != nil)\n\tt.Fatal()\n}\n\nfunc TestSystemEventLogClear(t *testing.T) {\n\tt.Skip(\"need real ipmi server\")\n\thost := \"127.0.0.1\"\n\tport := \"623\"\n\tuser := \"ADMIN\"\n\tpass := \"ADMIN\"\n\ti, err := New(host, user, pass, WithPort(port), WithLogger(logging.DefaultLogger()))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\terr = i.ClearSystemEventLog(context.Background())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Log(\"System Event Log cleared\")\n\tt.Fatal()\n}\n\nfunc TestSystemEventLogGet(t *testing.T) {\n\tt.Skip(\"need real ipmi server\")\n\thost := \"127.0.0.1\"\n\tport := \"623\"\n\tuser := \"ADMIN\"\n\tpass := \"ADMIN\"\n\ti, err := New(host, user, pass, WithPort(port), WithLogger(logging.DefaultLogger()))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tentries, err := i.GetSystemEventLog(context.Background())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Log(entries)\n\tt.Fatal()\n}\n\nfunc TestSystemEventLogGetRaw(t *testing.T) {\n\tt.Skip(\"need real ipmi server\")\n\thost := \"127.0.0.1\"\n\tport := \"623\"\n\tuser := \"ADMIN\"\n\tpass := \"ADMIN\"\n\ti, err := New(host, user, pass, WithPort(port), WithLogger(logging.DefaultLogger()))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\teventlog, err := i.GetSystemEventLogRaw(context.Background())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Log(eventlog)\n\tt.Fatal()\n}\n\nfunc TestSendNMI(t *testing.T) {\n\tt.Skip(\"need real ipmi server\")\n\thost := \"127.0.0.1\"\n\tport := \"623\"\n\tuser := \"ADMIN\"\n\tpass := \"ADMIN\"\n\ti, err := New(host, user, pass, WithPort(port), WithLogger(logging.DefaultLogger()))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\terr = i.SendNMI(context.Background())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Log(\"NMI sent\")\n\tt.Fatal()\n}\n"
  },
  {
    "path": "providers/openbmc/firmware.go",
    "content": "package openbmc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/constants\"\n\t\"github.com/bmc-toolbox/common\"\n\n\tbmcliberrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\trfw \"github.com/bmc-toolbox/bmclib/v2/internal/redfishwrapper\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/stmcginnis/gofish/schemas\"\n)\n\n// bmc client interface implementations methods\nfunc (c *Conn) FirmwareInstallSteps(ctx context.Context, component string) ([]constants.FirmwareInstallStep, error) {\n\tif err := c.deviceSupported(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch strings.ToUpper(component) {\n\tcase common.SlugBIOS:\n\t\treturn []constants.FirmwareInstallStep{\n\t\t\tconstants.FirmwareInstallStepPowerOffHost,\n\t\t\tconstants.FirmwareInstallStepUploadInitiateInstall,\n\t\t\tconstants.FirmwareInstallStepInstallStatus,\n\t\t}, nil\n\tcase common.SlugBMC:\n\t\treturn []constants.FirmwareInstallStep{\n\t\t\tconstants.FirmwareInstallStepUploadInitiateInstall,\n\t\t\tconstants.FirmwareInstallStepInstallStatus,\n\t\t}, nil\n\tdefault:\n\t\treturn nil, errors.New(\"component firmware install not supported: \" + component)\n\t}\n}\n\nfunc (c *Conn) FirmwareInstallUploadAndInitiate(ctx context.Context, component string, file *os.File) (taskID string, err error) {\n\tif err := c.deviceSupported(ctx); err != nil {\n\t\treturn \"\", errNotOpenBMCDevice\n\t}\n\n\t//\t// expect atleast 5 minutes left in the deadline to proceed with the upload\n\td, _ := ctx.Deadline()\n\tif time.Until(d) < 10*time.Minute {\n\t\treturn \"\", errors.New(\"remaining context deadline insufficient to perform update: \" + time.Until(d).String())\n\t}\n\n\t// list current tasks on BMC\n\ttasks, err := c.redfishwrapper.Tasks(ctx)\n\tif err != nil {\n\t\treturn \"\", errors.Wrap(err, \"error listing bmc redfish tasks\")\n\t}\n\n\t// validate a new firmware install task can be queued\n\tif err := c.checkQueueability(component, tasks); err != nil {\n\t\treturn \"\", errors.Wrap(bmcliberrs.ErrFirmwareInstall, err.Error())\n\t}\n\n\tparams := &rfw.RedfishUpdateServiceParameters{\n\t\tTargets:            []string{},\n\t\tOperationApplyTime: constants.OnReset,\n\t\tOem:                []byte(`{}`),\n\t}\n\n\treturn c.redfishwrapper.FirmwareUpload(ctx, file, params)\n}\n\n// returns an error when a bmc firmware install is active\nfunc (c *Conn) checkQueueability(component string, tasks []*schemas.Task) error {\n\terrTaskActive := errors.New(\"A firmware job was found active for component: \" + component)\n\n\tfor _, t := range tasks {\n\t\t// taskInfo returned in error if any.\n\t\ttaskInfo := fmt.Sprintf(\"id: %s, state: %s, status: %s\", t.ID, t.TaskState, t.TaskStatus)\n\n\t\t// convert redfish task state to bmclib state\n\t\tconvstate := c.redfishwrapper.ConvertTaskState(string(t.TaskState))\n\t\t// check if task is active based on converted state\n\t\tactive, err := c.redfishwrapper.TaskStateActive(convstate)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(err, taskInfo)\n\t\t}\n\n\t\tif active {\n\t\t\treturn errors.Wrap(errTaskActive, taskInfo)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// FirmwareTaskStatus returns the status of a firmware related task queued on the BMC.\nfunc (c *Conn) FirmwareTaskStatus(ctx context.Context, kind constants.FirmwareInstallStep, component, taskID, installVersion string) (state constants.TaskState, status string, err error) {\n\treturn c.redfishwrapper.TaskStatus(ctx, taskID)\n}\n"
  },
  {
    "path": "providers/openbmc/openbmc.go",
    "content": "package openbmc\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/x509\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/internal/httpclient\"\n\t\"github.com/bmc-toolbox/bmclib/v2/internal/redfishwrapper\"\n\t\"github.com/bmc-toolbox/bmclib/v2/providers\"\n\t\"github.com/bmc-toolbox/common\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/jacobweinstock/registrar\"\n\t\"github.com/pkg/errors\"\n)\n\nconst (\n\t// ProviderName for the OpenBMC provider implementation\n\tProviderName = \"openbmc\"\n\t// ProviderProtocol for the OpenBMC provider implementation\n\tProviderProtocol = \"redfish\"\n)\n\nvar (\n\t// Features implemented by dell redfish\n\tFeatures = registrar.Features{\n\t\tproviders.FeaturePowerState,\n\t\tproviders.FeaturePowerSet,\n\t\tproviders.FeatureBmcReset,\n\t\tproviders.FeatureFirmwareInstallSteps,\n\t\tproviders.FeatureFirmwareUploadInitiateInstall,\n\t\tproviders.FeatureFirmwareTaskStatus,\n\t\tproviders.FeatureInventoryRead,\n\t}\n\n\terrNotOpenBMCDevice = errors.New(\"not an OpenBMC device\")\n)\n\ntype Config struct {\n\tHttpClient            *http.Client\n\tPort                  string\n\tVersionsNotCompatible []string\n\tRootCAs               *x509.CertPool\n\tUseBasicAuth          bool\n}\n\n// Option for setting optional Client values\ntype Option func(*Config)\n\nfunc WithHttpClient(httpClient *http.Client) Option {\n\treturn func(c *Config) {\n\t\tc.HttpClient = httpClient\n\t}\n}\n\nfunc WithPort(port string) Option {\n\treturn func(c *Config) {\n\t\tc.Port = port\n\t}\n}\n\nfunc WithRootCAs(rootCAs *x509.CertPool) Option {\n\treturn func(c *Config) {\n\t\tc.RootCAs = rootCAs\n\t}\n}\n\nfunc WithUseBasicAuth(useBasicAuth bool) Option {\n\treturn func(c *Config) {\n\t\tc.UseBasicAuth = useBasicAuth\n\t}\n}\n\n// Conn details for redfish client\ntype Conn struct {\n\thost           string\n\thttpClient     *http.Client\n\tredfishwrapper *redfishwrapper.Client\n\tLog            logr.Logger\n}\n\n// New returns connection with a redfish client initialized\nfunc New(host, user, pass string, log logr.Logger, opts ...Option) *Conn {\n\tdefaultConfig := &Config{\n\t\tHttpClient:            httpclient.Build(),\n\t\tPort:                  \"443\",\n\t\tVersionsNotCompatible: []string{},\n\t}\n\n\tfor _, opt := range opts {\n\t\topt(defaultConfig)\n\t}\n\n\trfOpts := []redfishwrapper.Option{\n\t\tredfishwrapper.WithHTTPClient(defaultConfig.HttpClient),\n\t\tredfishwrapper.WithBasicAuthEnabled(defaultConfig.UseBasicAuth),\n\t\tredfishwrapper.WithEtagMatchDisabled(true),\n\t}\n\n\tif defaultConfig.RootCAs != nil {\n\t\trfOpts = append(rfOpts, redfishwrapper.WithSecureTLS(defaultConfig.RootCAs))\n\t}\n\n\treturn &Conn{\n\t\thost:           host,\n\t\thttpClient:     defaultConfig.HttpClient,\n\t\tLog:            log,\n\t\tredfishwrapper: redfishwrapper.NewClient(host, defaultConfig.Port, user, pass, rfOpts...),\n\t}\n}\n\n// Open a connection to a BMC via redfish\nfunc (c *Conn) Open(ctx context.Context) (err error) {\n\tif err := c.deviceSupported(ctx); err != nil {\n\t\treturn err\n\t}\n\n\tif err := c.redfishwrapper.Open(ctx); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *Conn) deviceSupported(ctx context.Context) error {\n\tvar host = c.host\n\tif !strings.HasPrefix(host, \"https://\") && !strings.HasPrefix(host, \"http://\") {\n\t\thost = \"https://\" + host\n\t}\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, host, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tresp, err := c.httpClient.Do(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer resp.Body.Close()\n\n\tb, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !bytes.Contains(b, []byte(`OpenBMC`)) {\n\t\treturn errNotOpenBMCDevice\n\t}\n\n\treturn nil\n}\n\n// Close a connection to a BMC via redfish\nfunc (c *Conn) Close(ctx context.Context) error {\n\treturn c.redfishwrapper.Close(ctx)\n}\n\n// Name returns the client provider name.\nfunc (c *Conn) Name() string {\n\treturn ProviderName\n}\n\n// PowerStateGet gets the power state of a BMC machine\nfunc (c *Conn) PowerStateGet(ctx context.Context) (state string, err error) {\n\treturn c.redfishwrapper.SystemPowerStatus(ctx)\n}\n\n// PowerSet sets the power state of a server\nfunc (c *Conn) PowerSet(ctx context.Context, state string) (ok bool, err error) {\n\treturn c.redfishwrapper.PowerSet(ctx, state)\n}\n\n// Inventory collects hardware inventory and install firmware information\nfunc (c *Conn) Inventory(ctx context.Context) (device *common.Device, err error) {\n\treturn c.redfishwrapper.Inventory(ctx, false)\n}\n\n// BmcReset power cycles the BMC\nfunc (c *Conn) BmcReset(ctx context.Context, resetType string) (ok bool, err error) {\n\treturn c.redfishwrapper.BMCReset(ctx, resetType)\n}\n\n// SendNMI tells the BMC to issue an NMI to the device\nfunc (c *Conn) SendNMI(ctx context.Context) error {\n\treturn c.redfishwrapper.SendNMI(ctx)\n}\n"
  },
  {
    "path": "providers/providers.go",
    "content": "package providers\n\nimport \"github.com/jacobweinstock/registrar\"\n\nconst (\n\t// FeaturePowerState represents the powerstate functionality\n\t// an implementation will use these when they have implemented\n\t// the corresponding interface method.\n\tFeaturePowerState registrar.Feature = \"powerstate\"\n\t// FeaturePowerSet means an implementation can set a BMC power state\n\tFeaturePowerSet registrar.Feature = \"powerset\"\n\t// FeatureUserCreate means an implementation can create BMC users\n\tFeatureUserCreate registrar.Feature = \"usercreate\"\n\t// FeatureUserDelete means an implementation can delete BMC users\n\tFeatureUserDelete registrar.Feature = \"userdelete\"\n\t// FeatureUserUpdate means an implementation can update BMC users\n\tFeatureUserUpdate registrar.Feature = \"userupdate\"\n\t// FeatureUserRead means an implementation can read BMC users\n\tFeatureUserRead registrar.Feature = \"userread\"\n\t// FeatureBmcReset means an implementation can warm or cold reset a BMC\n\tFeatureBmcReset registrar.Feature = \"bmcreset\"\n\t// FeatureBootDeviceSet means an implementation the next boot device\n\tFeatureBootDeviceSet registrar.Feature = \"bootdeviceset\"\n\t// FeaturesVirtualMedia means an implementation can manage virtual media devices\n\tFeatureVirtualMedia registrar.Feature = \"virtualmedia\"\n\t// FeatureMountFloppyImage means an implementation uploads a floppy image for mounting as virtual media.\n\t//\n\t// note: This is differs from FeatureVirtualMedia which is limited to accepting a URL to download the image from.\n\tFeatureMountFloppyImage registrar.Feature = \"mountFloppyImage\"\n\t// FeatureUnmountFloppyImage means an implementation removes a floppy image that was previously uploaded.\n\tFeatureUnmountFloppyImage registrar.Feature = \"unmountFloppyImage\"\n\t// FeatureFirmwareInstall means an implementation that initiates the firmware install process\n\t// FeatureFirmwareInstall means an implementation that uploads _and_ initiates the firmware install process\n\tFeatureFirmwareInstall registrar.Feature = \"firmwareinstall\"\n\t// FeatureFirmwareInstallSatus means an implementation that returns the firmware install status\n\tFeatureFirmwareInstallStatus registrar.Feature = \"firmwareinstallstatus\"\n\t// FeatureInventoryRead means an implementation that returns the hardware and firmware inventory\n\tFeatureInventoryRead registrar.Feature = \"inventoryread\"\n\t// FeaturePostCodeRead means an implementation that returns the boot BIOS/UEFI post code status and value\n\tFeaturePostCodeRead registrar.Feature = \"postcoderead\"\n\t// FeatureScreenshot means an implementation that returns a screenshot of the video.\n\tFeatureScreenshot registrar.Feature = \"screenshot\"\n\t// FeatureClearSystemEventLog means an implementation that clears the BMC System Event Log (SEL)\n\tFeatureClearSystemEventLog registrar.Feature = \"clearsystemeventlog\"\n\t// FeatureGetSystemEventLog means an implementation that returns the BMC System Event Log (SEL)\n\tFeatureGetSystemEventLog registrar.Feature = \"getsystemeventlog\"\n\t// FeatureGetSystemEventLogRaw means an implementation that returns the BMC System Event Log (SEL) in raw format\n\tFeatureGetSystemEventLogRaw registrar.Feature = \"getsystemeventlograw\"\n\t// FeatureFirmwareInstallSteps means an implementation returns the steps part of the firmware update process.\n\tFeatureFirmwareInstallSteps registrar.Feature = \"firmwareinstallsteps\"\n\n\t// FeatureFirmwareUpload means an implementation that uploads firmware for installing.\n\tFeatureFirmwareUpload registrar.Feature = \"firmwareupload\"\n\n\t// \tFeatureFirmwareInstallUploaded means an implementation that installs firmware uploaded using the firmwareupload feature.\n\tFeatureFirmwareInstallUploaded registrar.Feature = \"firmwareinstalluploaded\"\n\n\t// FeatureFirmwareTaskStatus identifies an implementaton that can return the status of a firmware upload/install task.\n\tFeatureFirmwareTaskStatus registrar.Feature = \"firmwaretaskstatus\"\n\n\t// FeatureFirmwareUploadInitiateInstall identifies an implementation that uploads firmware _and_ initiates the install process.\n\tFeatureFirmwareUploadInitiateInstall registrar.Feature = \"uploadandinitiateinstall\"\n\n\t// FeatureDeactivateSOL means an implementation that can deactivate active SOL sessions\n\tFeatureDeactivateSOL registrar.Feature = \"deactivatesol\"\n\n\t// FeatureResetBiosConfiguration means an implementation that can reset bios configuration back to 'factory' defaults\n\tFeatureResetBiosConfiguration registrar.Feature = \"resetbiosconfig\"\n\n\t// FeatureSetBiosConfiguration means an implementation that can set bios configuration from an input k/v map\n\tFeatureSetBiosConfiguration registrar.Feature = \"setbiosconfig\"\n\n\t// FeatureSetBiosConfigurationFromFile means an implementation that can set bios configuration from a vendor specific text file\n\tFeatureSetBiosConfigurationFromFile registrar.Feature = \"setbiosconfigfile\"\n\n\t// FeatureGetBiosConfiguration means an implementation that can get bios configuration in a simple k/v map\n\tFeatureGetBiosConfiguration registrar.Feature = \"getbiosconfig\"\n\n\t// FeatureBootProgress indicates that the implementation supports reading the BootProgress from the BMC\n\tFeatureBootProgress registrar.Feature = \"bootprogress\"\n)\n"
  },
  {
    "path": "providers/redfish/fixtures/v1/dell/entries.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#LogEntryCollection.LogEntryCollection\",\n    \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries\",\n    \"@odata.type\": \"#LogEntryCollection.LogEntryCollection\",\n    \"Description\": \"System Event Logs for this manager\",\n    \"Members\": [\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#LogEntry.LogEntry\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/2\",\n            \"@odata.type\": \"#LogEntry.v1_6_1.LogEntry\",\n            \"Created\": \"2023-01-01T00:00:00-00:00\",\n            \"Description\": \"Log Entry 2\",\n            \"EntryCode\": \"Assert\",\n            \"EntryType\": \"SEL\",\n            \"GeneratorId\": \"0x0001\",\n            \"Id\": \"1\",\n            \"Links\": {},\n            \"Message\": \"OEM software event.\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"d000000\",\n            \"Name\": \"Log Entry 2\",\n            \"SensorNumber\": 999,\n            \"SensorType\": null,\n            \"Severity\": \"OK\"\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#LogEntry.LogEntry\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/1\",\n            \"@odata.type\": \"#LogEntry.v1_6_1.LogEntry\",\n            \"Created\": \"2023-01-01T00:00:00-00:00\",\n            \"Description\": \"Log Entry 1\",\n            \"EntryCode\": \"Deassert\",\n            \"EntryType\": \"SEL\",\n            \"GeneratorId\": \"0x0001\",\n            \"Id\": \"1\",\n            \"Links\": {},\n            \"Message\": \"OEM software event.\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"d000000\",\n            \"Name\": \"Log Entry 1\",\n            \"SensorNumber\": 999,\n            \"SensorType\": null,\n            \"Severity\": \"OK\"\n        }\n    ],\n    \"Members@odata.count\": 2,\n    \"Name\": \"Log Entry Collection\"\n}\n"
  },
  {
    "path": "providers/redfish/fixtures/v1/dell/job_delete_ok.json",
    "content": "{\n    \"@Message.ExtendedInfo\": [\n        {\n            \"Message\": \"Successfully Completed Request\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"Base.1.7.Success\",\n            \"RelatedProperties\": [],\n            \"RelatedProperties@odata.count\": 0,\n            \"Resolution\": \"None\",\n            \"Severity\": \"OK\"\n        },\n        {\n            \"Message\": \"The operation successfully completed.\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"IDRAC.2.4.SYS413\",\n            \"RelatedProperties\": [],\n            \"RelatedProperties@odata.count\": 0,\n            \"Resolution\": \"No response action is required.\",\n            \"Severity\": \"Informational\"\n        }\n    ]\n}"
  },
  {
    "path": "providers/redfish/fixtures/v1/dell/jobs.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#DellJobCollection.DellJobCollection\",\n    \"@odata.type\": \"#DellJobCollection.DellJobCollection\",\n    \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs\",\n    \"Description\": \"Collection of Job Instances\",\n    \"Members\": [\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_134386578592\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": \"2021-02-15T19:24:36\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_134386578592\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"ExportConfiguration\",\n            \"Message\": \"Successfully exported Server Configuration Profile\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"SYS043\",\n            \"Name\": \"Export: Server Configuration Profile\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2021-02-15T19:24:17\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_134561935885\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": \"2021-02-16T00:16:54\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_134561935885\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"ExportConfiguration\",\n            \"Message\": \"Successfully exported Server Configuration Profile\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"SYS043\",\n            \"Name\": \"Export: Server Configuration Profile\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2021-02-16T00:16:33\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_134628507565\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": \"2021-02-16T02:07:51\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_134628507565\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"ExportConfiguration\",\n            \"Message\": \"Successfully exported Server Configuration Profile\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"SYS043\",\n            \"Name\": \"Export: Server Configuration Profile\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2021-02-16T02:07:30\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_134734988942\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": \"2021-02-16T05:05:19\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_134734988942\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"ExportConfiguration\",\n            \"Message\": \"Successfully exported Server Configuration Profile\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"SYS043\",\n            \"Name\": \"Export: Server Configuration Profile\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2021-02-16T05:04:58\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_134769926208\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": \"2021-02-16T06:03:33\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_134769926208\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"ExportConfiguration\",\n            \"Message\": \"Successfully exported Server Configuration Profile\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"SYS043\",\n            \"Name\": \"Export: Server Configuration Profile\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2021-02-16T06:03:12\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_135707407125\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": \"2021-02-17T08:05:59\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_135707407125\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"ExportConfiguration\",\n            \"Message\": \"Successfully exported Server Configuration Profile\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"SYS043\",\n            \"Name\": \"Export: Server Configuration Profile\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2021-02-17T08:05:40\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_135729671798\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": \"2021-02-17T08:43:08\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_135729671798\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"ExportConfiguration\",\n            \"Message\": \"Successfully exported Server Configuration Profile\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"SYS043\",\n            \"Name\": \"Export: Server Configuration Profile\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2021-02-17T08:42:47\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_135762432944\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": \"2021-02-17T09:37:42\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_135762432944\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"ExportConfiguration\",\n            \"Message\": \"Successfully exported Server Configuration Profile\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"SYS043\",\n            \"Name\": \"Export: Server Configuration Profile\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2021-02-17T09:37:23\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_135792879555\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": \"2021-02-17T10:28:28\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_135792879555\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"ExportConfiguration\",\n            \"Message\": \"Successfully exported Server Configuration Profile\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"SYS043\",\n            \"Name\": \"Export: Server Configuration Profile\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2021-02-17T10:28:07\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_135855732001\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": \"2021-02-17T12:13:13\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_135855732001\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"ExportConfiguration\",\n            \"Message\": \"Successfully exported Server Configuration Profile\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"SYS043\",\n            \"Name\": \"Export: Server Configuration Profile\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2021-02-17T12:12:53\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_135889086333\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": \"2021-02-17T13:08:46\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_135889086333\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"ExportConfiguration\",\n            \"Message\": \"Successfully exported Server Configuration Profile\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"SYS043\",\n            \"Name\": \"Export: Server Configuration Profile\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2021-02-17T13:08:28\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_155889974199\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": \"2021-03-12T16:43:38\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_155889974199\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"ExportConfiguration\",\n            \"Message\": \"Successfully exported Server Configuration Profile\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"SYS043\",\n            \"Name\": \"Export: Server Configuration Profile\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2021-03-12T16:43:17\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_157492856325\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": \"2021-03-14T14:15:04\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_157492856325\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"ExportConfiguration\",\n            \"Message\": \"Successfully exported Server Configuration Profile\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"SYS043\",\n            \"Name\": \"Export: Server Configuration Profile\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2021-03-14T14:14:45\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_157527722202\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": \"2021-03-14T15:13:12\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_157527722202\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"ExportConfiguration\",\n            \"Message\": \"Successfully exported Server Configuration Profile\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"SYS043\",\n            \"Name\": \"Export: Server Configuration Profile\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2021-03-14T15:12:52\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_157563681404\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": \"2021-03-14T16:13:08\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_157563681404\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"ExportConfiguration\",\n            \"Message\": \"Successfully exported Server Configuration Profile\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"SYS043\",\n            \"Name\": \"Export: Server Configuration Profile\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2021-03-14T16:12:48\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_157882741266\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": \"2021-03-15T01:04:54\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_157882741266\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"ExportConfiguration\",\n            \"Message\": \"Successfully exported Server Configuration Profile\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"SYS043\",\n            \"Name\": \"Export: Server Configuration Profile\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2021-03-15T01:04:34\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_157915627166\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": \"2021-03-15T01:59:43\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_157915627166\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"ExportConfiguration\",\n            \"Message\": \"Successfully exported Server Configuration Profile\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"SYS043\",\n            \"Name\": \"Export: Server Configuration Profile\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2021-03-15T01:59:22\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_158221456256\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": \"2021-03-15T10:29:25\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_158221456256\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"ExportConfiguration\",\n            \"Message\": \"Successfully exported Server Configuration Profile\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"SYS043\",\n            \"Name\": \"Export: Server Configuration Profile\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2021-03-15T10:29:05\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_158337818297\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": \"2021-03-15T13:43:22\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_158337818297\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"ExportConfiguration\",\n            \"Message\": \"Successfully exported Server Configuration Profile\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"SYS043\",\n            \"Name\": \"Export: Server Configuration Profile\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2021-03-15T13:43:01\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_158625018022\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": \"2021-03-15T21:42:02\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_158625018022\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"ExportConfiguration\",\n            \"Message\": \"Successfully exported Server Configuration Profile\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"SYS043\",\n            \"Name\": \"Export: Server Configuration Profile\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2021-03-15T21:41:41\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_291745931343\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": \"2021-08-16T23:30:13\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_291745931343\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"ExportConfiguration\",\n            \"Message\": \"Successfully exported Server Configuration Profile\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"SYS043\",\n            \"Name\": \"Export: Server Configuration Profile\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2021-08-16T23:29:53\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_348594655738\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": \"2021-10-21T18:47:46\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": \"TIME_NA\",\n            \"Id\": \"JID_348594655738\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"BIOSConfiguration\",\n            \"Message\": \"Job completed successfully.\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"PR19\",\n            \"Name\": \"Configure: BIOS.Setup.1-1\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2021-10-21T18:37:45\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_348601794602\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": \"2021-10-21T18:49:59\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_348601794602\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"ExportConfiguration\",\n            \"Message\": \"Successfully exported Server Configuration Profile\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"SYS043\",\n            \"Name\": \"Export: Server Configuration Profile\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2021-10-21T18:49:39\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_421738664260\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": \"2022-01-14T09:24:26\",\n            \"ActualRunningStopTime\": \"2022-01-14T09:24:57\",\n            \"CompletionTime\": \"2022-01-14T09:24:57\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_421738664260\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"ExportConfiguration\",\n            \"Message\": \"Successfully exported Server Configuration Profile\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"SYS043\",\n            \"Name\": \"Export: Server Configuration Profile\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2022-01-14T09:24:26\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_452370412267\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": \"2022-02-18T20:17:21\",\n            \"ActualRunningStopTime\": \"2022-02-18T20:17:53\",\n            \"CompletionTime\": \"2022-02-18T20:17:53\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_452370412267\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"ExportConfiguration\",\n            \"Message\": \"Successfully exported Server Configuration Profile\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"SYS043\",\n            \"Name\": \"Export: Server Configuration Profile\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2022-02-18T20:17:21\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_467762674724\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": null,\n            \"Description\": \"Job Instance\",\n            \"EndTime\": \"TIME_NA\",\n            \"Id\": \"JID_467762674724\",\n            \"JobState\": \"Scheduled\",\n            \"JobType\": \"FirmwareUpdate\",\n            \"Message\": \"Task successfully scheduled.\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"JCP001\",\n            \"Name\": \"Firmware Update: BIOS\",\n            \"PercentComplete\": 0,\n            \"StartTime\": \"2022-03-08T15:51:07\",\n            \"TargetSettingsURI\": null\n        },\n        {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellJob.DellJob\",\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs/JID_467767920358\",\n            \"@odata.type\": \"#DellJob.v1_1_0.DellJob\",\n            \"ActualRunningStartTime\": null,\n            \"ActualRunningStopTime\": null,\n            \"CompletionTime\": \"2022-03-08T16:02:33\",\n            \"Description\": \"Job Instance\",\n            \"EndTime\": null,\n            \"Id\": \"JID_467767920358\",\n            \"JobState\": \"Completed\",\n            \"JobType\": \"FirmwareUpdate\",\n            \"Message\": \"Job completed successfully.\",\n            \"MessageArgs\": [],\n            \"MessageArgs@odata.count\": 0,\n            \"MessageId\": \"RED001\",\n            \"Name\": \"Firmware Update: iDRAC with Lifecycle Controller\",\n            \"PercentComplete\": 100,\n            \"StartTime\": \"2022-03-08T15:59:52\",\n            \"TargetSettingsURI\": null\n        }\n    ],\n    \"Members@odata.count\": 27,\n    \"Name\": \"JobQueue\"\n}"
  },
  {
    "path": "providers/redfish/fixtures/v1/dell/logservices.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#LogServiceCollection.LogServiceCollection\",\n    \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices\",\n    \"@odata.type\": \"#LogServiceCollection.LogServiceCollection\",\n    \"Description\": \"Collection of Log Services for this Manager\",\n    \"Members\": [\n        {\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel\"\n        }\n    ],\n    \"Members@odata.count\": 3,\n    \"Name\": \"Log Service Collection\"\n}\n"
  },
  {
    "path": "providers/redfish/fixtures/v1/dell/logservices.sel.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#LogService.LogService\",\n    \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel\",\n    \"@odata.type\": \"#LogService.v1_1_3.LogService\",\n    \"Actions\": {\n        \"#LogService.ClearLog\": {\n            \"target\": \"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Actions/LogService.ClearLog\"\n        }\n    },\n    \"DateTime\": \"2023-01-01T00:00:00-00:00\",\n    \"DateTimeLocalOffset\": \"00:00\",\n    \"Description\": \"SEL Log Service\",\n    \"Entries\": {\n        \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries\"\n    },\n    \"Id\": \"Sel\",\n    \"LogEntryType\": \"SEL\",\n    \"MaxNumberOfRecords\": 1024,\n    \"Name\": \"SEL Log Service\",\n    \"OverWritePolicy\": \"WrapsWhenFull\",\n    \"ServiceEnabled\": true\n}\n"
  },
  {
    "path": "providers/redfish/fixtures/v1/dell/manager.idrac.embedded.1.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#Manager.Manager\",\n    \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1\",\n    \"@odata.type\": \"#Manager.v1_9_0.Manager\",\n    \"LogServices\": {\n        \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices\"\n    },\n    \"Comment\": \"This isn't part of the schema - this file contains only the bare minimum to make the schema validator happy\"\n}\n"
  },
  {
    "path": "providers/redfish/fixtures/v1/dell/managers.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#ManagerCollection.ManagerCollection\",\n    \"@odata.id\": \"/redfish/v1/Managers\",\n    \"@odata.type\": \"#ManagerCollection.ManagerCollection\",\n    \"Description\": \"BMC\",\n    \"Members\": [\n        {\n            \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1\"\n        }\n    ],\n    \"Members@odata.count\": 1,\n    \"Name\": \"Manager\"\n}\n"
  },
  {
    "path": "providers/redfish/fixtures/v1/dell/selentries/1.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#LogEntry.LogEntry\",\n    \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/1\",\n    \"@odata.type\": \"#LogEntry.v1_6_1.LogEntry\",\n    \"Created\": \"2023-01-01T00:00:00-00:00\",\n    \"Description\": \"Log Entry 1\",\n    \"EntryCode\": \"Deassert\",\n    \"EntryType\": \"SEL\",\n    \"GeneratorId\": \"0x0001\",\n    \"Id\": \"1\",\n    \"Links\": {},\n    \"Message\": \"OEM software event.\",\n    \"MessageArgs\": [],\n    \"MessageArgs@odata.count\": 0,\n    \"MessageId\": \"d000000\",\n    \"Name\": \"Log Entry 1\",\n    \"SensorNumber\": 999,\n    \"SensorType\": null,\n    \"Severity\": \"OK\"\n}\n"
  },
  {
    "path": "providers/redfish/fixtures/v1/dell/selentries/2.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#LogEntry.LogEntry\",\n    \"@odata.id\": \"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/2\",\n    \"@odata.type\": \"#LogEntry.v1_6_1.LogEntry\",\n    \"Created\": \"2023-01-01T00:00:00-00:00\",\n    \"Description\": \"Log Entry 2\",\n    \"EntryCode\": \"Assert\",\n    \"EntryType\": \"SEL\",\n    \"GeneratorId\": \"0x0001\",\n    \"Id\": \"1\",\n    \"Links\": {},\n    \"Message\": \"OEM software event.\",\n    \"MessageArgs\": [],\n    \"MessageArgs@odata.count\": 0,\n    \"MessageId\": \"d000000\",\n    \"Name\": \"Log Entry 2\",\n    \"SensorNumber\": 999,\n    \"SensorType\": null,\n    \"Severity\": \"OK\"\n}\n"
  },
  {
    "path": "providers/redfish/fixtures/v1/serviceroot.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#ServiceRoot.ServiceRoot\",\n    \"@odata.id\": \"/redfish/v1\",\n    \"@odata.type\": \"#ServiceRoot.v1_6_0.ServiceRoot\",\n    \"AccountService\": {\n        \"@odata.id\": \"/redfish/v1/AccountService\"\n    },\n    \"CertificateService\": {\n        \"@odata.id\": \"/redfish/v1/CertificateService\"\n    },\n    \"Chassis\": {\n        \"@odata.id\": \"/redfish/v1/Chassis\"\n    },\n    \"Description\": \"Root Service\",\n    \"EventService\": {\n        \"@odata.id\": \"/redfish/v1/EventService\"\n    },\n    \"Fabrics\": {\n        \"@odata.id\": \"/redfish/v1/Fabrics\"\n    },\n    \"Id\": \"RootService\",\n    \"JobService\": {\n        \"@odata.id\": \"/redfish/v1/JobService\"\n    },\n    \"JsonSchemas\": {\n        \"@odata.id\": \"/redfish/v1/JsonSchemas\"\n    },\n    \"Links\": {\n        \"Sessions\": {\n            \"@odata.id\": \"/redfish/v1/SessionService/Sessions\"\n        }\n    },\n    \"Managers\": {\n        \"@odata.id\": \"/redfish/v1/Managers\"\n    },\n    \"Name\": \"Root Service\",\n    \"Oem\": {\n        \"Dell\": {\n            \"@odata.context\": \"/redfish/v1/$metadata#DellServiceRoot.DellServiceRoot\",\n            \"@odata.type\": \"#DellServiceRoot.v1_0_0.DellServiceRoot\",\n            \"IsBranded\": 0,\n            \"ManagerMACAddress\": \"d0:8e:79:bb:3e:ea\",\n            \"ServiceTag\": \"FOOBAR\"\n        }\n    },\n    \"Product\": \"Integrated Dell Remote Access Controller\",\n    \"ProtocolFeaturesSupported\": {\n        \"ExcerptQuery\": false,\n        \"ExpandQuery\": {\n            \"ExpandAll\": true,\n            \"Levels\": true,\n            \"Links\": true,\n            \"MaxLevels\": 1,\n            \"NoLinks\": true\n        },\n        \"FilterQuery\": true,\n        \"OnlyMemberQuery\": true,\n        \"SelectQuery\": true\n    },\n    \"RedfishVersion\": \"1.9.0\",\n    \"Registries\": {\n        \"@odata.id\": \"/redfish/v1/Registries\"\n    },\n    \"SessionService\": {\n        \"@odata.id\": \"/redfish/v1/SessionService\"\n    },\n    \"Systems\": {\n        \"@odata.id\": \"/redfish/v1/Systems\"\n    },\n    \"Tasks\": {\n        \"@odata.id\": \"/redfish/v1/TaskService\"\n    },\n    \"TelemetryService\": {\n        \"@odata.id\": \"/redfish/v1/TelemetryService\"\n    },\n    \"UpdateService\": {\n        \"@odata.id\": \"/redfish/v1/UpdateService\"\n    },\n    \"Vendor\": \"Dell\"\n}"
  },
  {
    "path": "providers/redfish/fixtures/v1/systems.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#ComputerSystemCollection.ComputerSystemCollection\",\n    \"@odata.id\": \"/redfish/v1/Systems\",\n    \"@odata.type\": \"#ComputerSystemCollection.ComputerSystemCollection\",\n    \"Description\": \"Collection of Computer Systems\",\n    \"Members\": [\n        {\n            \"@odata.id\": \"/redfish/v1/Systems/System.Embedded.1\"\n        }\n    ],\n    \"Members@odata.count\": 1,\n    \"Name\": \"Computer System Collection\"\n}"
  },
  {
    "path": "providers/redfish/fixtures/v1/updateservice.json",
    "content": "{\n    \"@odata.context\": \"/redfish/v1/$metadata#UpdateService.UpdateService\",\n    \"@odata.id\": \"/redfish/v1/UpdateService\",\n    \"@odata.type\": \"#UpdateService.v1_8_0.UpdateService\",\n    \"Actions\": {\n        \"#UpdateService.SimpleUpdate\": {\n            \"@Redfish.OperationApplyTimeSupport\": {\n                \"@odata.type\": \"#Settings.v1_3_0.OperationApplyTimeSupport\",\n                \"SupportedValues\": [\n                    \"Immediate\",\n                    \"OnReset\"\n                ]\n            },\n            \"TransferProtocol@Redfish.AllowableValues\": [\n                \"HTTP\",\n                \"NFS\",\n                \"CIFS\",\n                \"TFTP\",\n                \"HTTPS\"\n            ],\n            \"target\": \"/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate\"\n        },\n        \"Oem\": {\n            \"DellUpdateService.v1_1_0#DellUpdateService.Install\": {\n                \"InstallUpon@Redfish.AllowableValues\": [\n                    \"Now\",\n                    \"NowAndReboot\",\n                    \"NextReboot\"\n                ],\n                \"target\": \"/redfish/v1/UpdateService/Actions/Oem/DellUpdateService.Install\"\n            }\n        }\n    },\n    \"Description\": \"Represents the properties for the Update Service\",\n    \"FirmwareInventory\": {\n        \"@odata.id\": \"/redfish/v1/UpdateService/FirmwareInventory\"\n    },\n    \"HttpPushUri\": \"/redfish/v1/UpdateService/FirmwareInventory\",\n    \"Id\": \"UpdateService\",\n    \"MaxImageSizeBytes\": null,\n    \"MultipartHttpPushUri\": \"/redfish/v1/UpdateService/MultipartUpload\",\n    \"Name\": \"Update Service\",\n    \"ServiceEnabled\": true,\n    \"SoftwareInventory\": {\n        \"@odata.id\": \"/redfish/v1/UpdateService/SoftwareInventory\"\n    },\n    \"Status\": {\n        \"Health\": \"OK\",\n        \"State\": \"Enabled\"\n    }\n}"
  },
  {
    "path": "providers/redfish/main_test.go",
    "content": "package redfish\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/go-logr/logr\"\n)\n\nconst (\n\tfixturesDir = \"./fixtures\"\n)\n\nvar (\n\tmockServer  *httptest.Server\n\tmockBMCHost *url.URL\n\tmockClient  *Conn\n)\n\n// jsonResponse returns the fixture json response for a request URI\nfunc jsonResponse(endpoint string) []byte {\n\tjsonResponsesMap := map[string]string{\n\t\t\"/redfish/v1/Managers\":                                            fixturesDir + \"/v1/dell/managers.json\",\n\t\t\"/redfish/v1/Managers/iDRAC.Embedded.1\":                           fixturesDir + \"/v1/dell/manager.idrac.embedded.1.json\",\n\t\t\"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices\":               fixturesDir + \"/v1/dell/logservices.json\",\n\t\t\"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel\":           fixturesDir + \"/v1/dell/logservices.sel.json\",\n\t\t\"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries\":   fixturesDir + \"/v1/dell/entries.json\",\n\t\t\"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/1\": fixturesDir + \"/v1/dell/selentries/1.json\",\n\t\t\"/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Entries/2\": fixturesDir + \"/v1/dell/selentries/2.json\",\n\n\t\t\"/redfish/v1/\":                          fixturesDir + \"/v1/serviceroot.json\",\n\t\t\"/redfish/v1/UpdateService\":             fixturesDir + \"/v1/updateservice.json\",\n\t\t\"/redfish/v1/Systems\":                   fixturesDir + \"/v1/systems.json\",\n\t}\n\n\tfh, err := os.Open(jsonResponsesMap[endpoint])\n\tif err != nil {\n\t\tlog.Fatalf(\"%s, failed to open fixture: %s for endpoint: %s\", err.Error(), jsonResponsesMap[endpoint], endpoint)\n\t}\n\n\tdefer fh.Close()\n\n\tb, err := io.ReadAll(fh)\n\tif err != nil {\n\t\tlog.Fatalf(\"%s, failed to read fixture: %s for endpoint: %s\", err.Error(), jsonResponsesMap[endpoint], endpoint)\n\t}\n\n\treturn b\n}\n\nfunc TestMain(m *testing.M) {\n\t// setup mock server\n\tmockServer = func() *httptest.Server {\n\t\thandler := http.NewServeMux()\n\t\thandler.HandleFunc(\"/redfish/v1/\", serviceRoot)\n\t\thandler.HandleFunc(\"/redfish/v1/SessionService/Sessions\", sessionService)\n\t\treturn httptest.NewTLSServer(handler)\n\t}()\n\n\tmockBMCHost, _ = url.Parse(mockServer.URL)\n\tmockClient = New(mockBMCHost.Hostname(), \"foo\", \"bar\", logr.Discard(), WithPort(mockBMCHost.Port()))\n\terr := mockClient.Open(context.TODO())\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tos.Exit(m.Run())\n}\n\nfunc serviceRoot(w http.ResponseWriter, r *http.Request) {\n\t// expect either GET or Delete methods\n\tif r.Method != http.MethodGet && r.Method != http.MethodDelete {\n\t\tw.WriteHeader(http.StatusNotFound)\n\t}\n\n\t_, _ = w.Write(jsonResponse(r.RequestURI))\n}\n\nfunc sessionService(w http.ResponseWriter, r *http.Request) {\n\tif r.Method != http.MethodPost {\n\t\tw.WriteHeader(http.StatusNotFound)\n\t}\n\n\t_, err := io.ReadAll(r.Body)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tw.Header().Set(\"X-Auth-Token\", \"hunter2\")\n\tw.Header().Set(\"Location\", r.URL.String())\n\t_, _ = w.Write([]byte(`is cool`))\n}\n"
  },
  {
    "path": "providers/redfish/redfish.go",
    "content": "package redfish\n\nimport (\n\t\"context\"\n\t\"crypto/x509\"\n\t\"net/http\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/internal/httpclient\"\n\t\"github.com/bmc-toolbox/bmclib/v2/internal/redfishwrapper\"\n\t\"github.com/bmc-toolbox/bmclib/v2/providers\"\n\t\"github.com/bmc-toolbox/common\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/jacobweinstock/registrar\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/bmc\"\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n)\n\nconst (\n\t// ProviderName for the provider implementation\n\tProviderName = \"gofish\"\n\t// ProviderProtocol for the provider implementation\n\tProviderProtocol = \"redfish\"\n)\n\nvar (\n\t// Features implemented by gofish\n\tFeatures = registrar.Features{\n\t\tproviders.FeaturePowerSet,\n\t\tproviders.FeaturePowerState,\n\t\tproviders.FeatureUserCreate,\n\t\tproviders.FeatureUserUpdate,\n\t\tproviders.FeatureUserDelete,\n\t\tproviders.FeatureBootDeviceSet,\n\t\tproviders.FeatureVirtualMedia,\n\t\tproviders.FeatureInventoryRead,\n\t\tproviders.FeatureBmcReset,\n\t\tproviders.FeatureClearSystemEventLog,\n\t\tproviders.FeatureGetBiosConfiguration,\n\t\tproviders.FeatureSetBiosConfiguration,\n\t\tproviders.FeatureResetBiosConfiguration,\n\t}\n)\n\n// Conn details for redfish client\ntype Conn struct {\n\tredfishwrapper       *redfishwrapper.Client\n\tfailInventoryOnError bool\n\tLog                  logr.Logger\n}\n\ntype Config struct {\n\tHttpClient *http.Client\n\tPort       string\n\t// VersionsNotCompatible\tis the list of incompatible redfish versions.\n\t//\n\t// With this option set, The bmclib.Registry.FilterForCompatible(ctx) method will not proceed on\n\t// devices with the given redfish version(s).\n\tVersionsNotCompatible []string\n\tRootCAs               *x509.CertPool\n\tUseBasicAuth          bool\n\t// DisableEtagMatch disables the If-Match Etag header from being included by the Gofish driver.\n\tDisableEtagMatch bool\n\tSystemName       string\n}\n\n// Option for setting optional Client values\ntype Option func(*Config)\n\nfunc WithHttpClient(httpClient *http.Client) Option {\n\treturn func(c *Config) {\n\t\tc.HttpClient = httpClient\n\t}\n}\n\nfunc WithPort(port string) Option {\n\treturn func(c *Config) {\n\t\tc.Port = port\n\t}\n}\n\nfunc WithVersionsNotCompatible(versionsNotCompatible []string) Option {\n\treturn func(c *Config) {\n\t\tc.VersionsNotCompatible = versionsNotCompatible\n\t}\n}\n\nfunc WithRootCAs(rootCAs *x509.CertPool) Option {\n\treturn func(c *Config) {\n\t\tc.RootCAs = rootCAs\n\t}\n}\n\nfunc WithUseBasicAuth(useBasicAuth bool) Option {\n\treturn func(c *Config) {\n\t\tc.UseBasicAuth = useBasicAuth\n\t}\n}\n\nfunc WithSystemName(name string) Option {\n\treturn func(c *Config) {\n\t\tc.SystemName = name\n\t}\n}\n\n// WithEtagMatchDisabled disables the If-Match Etag header from being included by the Gofish driver.\n//\n// As of the current implementation this disables the header for POST/PATCH requests to the System entity endpoints.\nfunc WithEtagMatchDisabled(d bool) Option {\n\treturn func(c *Config) {\n\t\tc.DisableEtagMatch = d\n\t}\n}\n\n// New returns connection with a redfish client initialized\nfunc New(host, user, pass string, log logr.Logger, opts ...Option) *Conn {\n\tdefaultConfig := &Config{\n\t\tHttpClient:            httpclient.Build(),\n\t\tPort:                  \"443\",\n\t\tVersionsNotCompatible: []string{},\n\t}\n\tfor _, opt := range opts {\n\t\topt(defaultConfig)\n\t}\n\n\trfOpts := []redfishwrapper.Option{\n\t\tredfishwrapper.WithHTTPClient(defaultConfig.HttpClient),\n\t\tredfishwrapper.WithVersionsNotCompatible(defaultConfig.VersionsNotCompatible),\n\t\tredfishwrapper.WithEtagMatchDisabled(defaultConfig.DisableEtagMatch),\n\t\tredfishwrapper.WithBasicAuthEnabled(defaultConfig.UseBasicAuth),\n\t\tredfishwrapper.WithSystemName(defaultConfig.SystemName),\n\t}\n\n\tif defaultConfig.RootCAs != nil {\n\t\trfOpts = append(rfOpts, redfishwrapper.WithSecureTLS(defaultConfig.RootCAs))\n\t}\n\n\treturn &Conn{\n\t\tLog:                  log,\n\t\tfailInventoryOnError: false,\n\t\tredfishwrapper:       redfishwrapper.NewClient(host, defaultConfig.Port, user, pass, rfOpts...),\n\t}\n}\n\n// Open a connection to a BMC via redfish\nfunc (c *Conn) Open(ctx context.Context) (err error) {\n\treturn c.redfishwrapper.Open(ctx)\n}\n\n// Close a connection to a BMC via redfish\nfunc (c *Conn) Close(ctx context.Context) error {\n\treturn c.redfishwrapper.Close(ctx)\n}\n\n// Name returns the client provider name.\nfunc (c *Conn) Name() string {\n\treturn ProviderName\n}\n\n// Compatible tests whether a BMC is compatible with the gofish provider\nfunc (c *Conn) Compatible(ctx context.Context) bool {\n\terr := c.Open(ctx)\n\tif err != nil {\n\t\tc.Log.V(2).WithValues(\n\t\t\t\"provider\",\n\t\t\tc.Name(),\n\t\t).Info(\"warn\", bmclibErrs.ErrCompatibilityCheck.Error(), err.Error())\n\n\t\treturn false\n\t}\n\tdefer c.Close(ctx)\n\n\tif !c.redfishwrapper.VersionCompatible() {\n\t\tc.Log.V(2).WithValues(\n\t\t\t\"provider\",\n\t\t\tc.Name(),\n\t\t).Info(\"info\", bmclibErrs.ErrCompatibilityCheck.Error(), \"incompatible redfish version\")\n\n\t\treturn false\n\t}\n\n\t_, err = c.PowerStateGet(ctx)\n\tif err != nil {\n\t\tc.Log.V(2).WithValues(\n\t\t\t\"provider\",\n\t\t\tc.Name(),\n\t\t).Info(\"warn\", bmclibErrs.ErrCompatibilityCheck.Error(), err.Error())\n\t}\n\n\treturn err == nil\n}\n\n// BmcReset power cycles the BMC\nfunc (c *Conn) BmcReset(ctx context.Context, resetType string) (ok bool, err error) {\n\treturn c.redfishwrapper.BMCReset(ctx, resetType)\n}\n\n// PowerStateGet gets the power state of a BMC machine\nfunc (c *Conn) PowerStateGet(ctx context.Context) (state string, err error) {\n\treturn c.redfishwrapper.SystemPowerStatus(ctx)\n}\n\n// PowerSet sets the power state of a server\nfunc (c *Conn) PowerSet(ctx context.Context, state string) (ok bool, err error) {\n\treturn c.redfishwrapper.PowerSet(ctx, state)\n}\n\n// BootDeviceSet sets the boot device\nfunc (c *Conn) BootDeviceSet(ctx context.Context, bootDevice string, setPersistent, efiBoot bool) (ok bool, err error) {\n\treturn c.redfishwrapper.SystemBootDeviceSet(ctx, bootDevice, setPersistent, efiBoot)\n}\n\n// BootDeviceOverrideGet gets the boot override device information\nfunc (c *Conn) BootDeviceOverrideGet(ctx context.Context) (bmc.BootDeviceOverride, error) {\n\treturn c.redfishwrapper.GetBootDeviceOverride(ctx)\n}\n\n// SetVirtualMedia sets the virtual media\nfunc (c *Conn) SetVirtualMedia(ctx context.Context, kind string, mediaURL string) (ok bool, err error) {\n\treturn c.redfishwrapper.SetVirtualMedia(ctx, kind, mediaURL)\n}\n\n// Inventory collects hardware inventory and install firmware information\nfunc (c *Conn) Inventory(ctx context.Context) (device *common.Device, err error) {\n\treturn c.redfishwrapper.Inventory(ctx, c.failInventoryOnError)\n}\n\n// GetBiosConfiguration return bios configuration\nfunc (c *Conn) GetBiosConfiguration(ctx context.Context) (biosConfig map[string]string, err error) {\n\treturn c.redfishwrapper.GetBiosConfiguration(ctx)\n}\n\n// SetBiosConfiguration set bios configuration\nfunc (c *Conn) SetBiosConfiguration(ctx context.Context, biosConfig map[string]string) (err error) {\n\treturn c.redfishwrapper.SetBiosConfiguration(ctx, biosConfig)\n}\n\n// ResetBiosConfiguration set bios configuration\nfunc (c *Conn) ResetBiosConfiguration(ctx context.Context) (err error) {\n\treturn c.redfishwrapper.ResetBiosConfiguration(ctx)\n}\n\n// SendNMI tells the BMC to issue an NMI to the device\nfunc (c *Conn) SendNMI(ctx context.Context) error {\n\treturn c.redfishwrapper.SendNMI(ctx)\n}\n"
  },
  {
    "path": "providers/redfish/sel.go",
    "content": "package redfish\n\nimport \"context\"\n\nfunc (c *Conn) ClearSystemEventLog(ctx context.Context) (err error) {\n\treturn c.redfishwrapper.ClearSystemEventLog(ctx)\n}\n\nfunc (c *Conn) GetSystemEventLog(ctx context.Context) (entries [][]string, err error) {\n\treturn c.redfishwrapper.GetSystemEventLog(ctx)\n}\n\nfunc (c *Conn) GetSystemEventLogRaw(ctx context.Context) (eventlog string, err error) {\n\treturn c.redfishwrapper.GetSystemEventLogRaw(ctx)\n}\n"
  },
  {
    "path": "providers/redfish/sel_test.go",
    "content": "package redfish\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\n// Write tests for GetSystemEventLog\nfunc Test_GetSystemEventLog(t *testing.T) {\n\tentries, err := mockClient.GetSystemEventLog(context.TODO())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassert.NotNil(t, entries)\n\tassert.Equal(t, 2, len(entries))\n}\n\n// Write tests for GetSystemEventLogRaw\nfunc Test_GetSystemEventLogRaw(t *testing.T) {\n\teventlog, err := mockClient.GetSystemEventLogRaw(context.Background())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tassert.NotNil(t, eventlog)\n}\n"
  },
  {
    "path": "providers/redfish/user.go",
    "content": "package redfish\n\nimport (\n\t\"context\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/internal\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/stmcginnis/gofish/schemas\"\n)\n\nvar (\n\tErrNoUserSlotsAvailable = errors.New(\"no user account slots available\")\n\tErrUserNotPresent       = errors.New(\"account with username was not found\")\n\tErrUserPassParams       = errors.New(\"user and pass parameters required\")\n\tErrUserExists           = errors.New(\"user exists\")\n\tErrInvalidUserRole      = errors.New(\"invalid user role\")\n\tValidRoles              = []string{\"Administrator\", \"Operator\", \"ReadOnly\", \"None\"}\n)\n\n// UserRead returns a list of enabled user accounts\nfunc (c *Conn) UserRead(ctx context.Context) (users []map[string]string, err error) {\n\tservice, err := c.redfishwrapper.AccountService()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\taccounts, err := service.Accounts()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tusers = make([]map[string]string, 0)\n\n\tfor _, account := range accounts {\n\t\tif account.Enabled {\n\t\t\tuser := map[string]string{\n\t\t\t\t\"ID\":       account.ID,\n\t\t\t\t\"Name\":     account.Name,\n\t\t\t\t\"Username\": account.UserName,\n\t\t\t\t\"RoleID\":   account.RoleID,\n\t\t\t}\n\t\t\tusers = append(users, user)\n\t\t}\n\t}\n\n\treturn users, nil\n}\n\n// UserUpdate updates a user password and role\nfunc (c *Conn) UserUpdate(ctx context.Context, user, pass, role string) (ok bool, err error) {\n\tservice, err := c.redfishwrapper.AccountService()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\taccounts, err := service.Accounts()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tfor _, account := range accounts {\n\t\tif account.UserName == user {\n\t\t\tvar change bool\n\t\t\tif pass != \"\" {\n\t\t\t\taccount.Password = pass\n\t\t\t\tchange = true\n\t\t\t}\n\t\t\tif role != \"\" {\n\t\t\t\taccount.RoleID = role\n\t\t\t\tchange = true\n\t\t\t}\n\n\t\t\tif change {\n\t\t\t\terr := account.Update()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn false, err\n\t\t\t\t}\n\t\t\t\treturn true, nil\n\t\t\t}\n\t\t}\n\t}\n\n\treturn ok, ErrUserNotPresent\n}\n\n// UserCreate adds a new user account\nfunc (c *Conn) UserCreate(ctx context.Context, user, pass, role string) (ok bool, err error) {\n\tif !internal.StringInSlice(role, ValidRoles) {\n\t\treturn false, ErrInvalidUserRole\n\t}\n\n\tif user == \"\" || pass == \"\" {\n\t\treturn false, ErrUserPassParams\n\t}\n\n\tservice, err := c.redfishwrapper.AccountService()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// fetch current list of accounts\n\taccounts, err := service.Accounts()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// identify account slot not in use\n\tfor _, account := range accounts {\n\t\t// Dell iDracs don't want us to create accounts in these slots\n\t\tif internal.StringInSlice(account.ID, []string{\"1\"}) {\n\t\t\tcontinue\n\t\t}\n\n\t\taccount := account\n\t\tif account.UserName == user {\n\t\t\treturn false, errors.Wrap(ErrUserExists, user)\n\t\t}\n\n\t\tif !account.Enabled && account.UserName == \"\" {\n\t\t\taccount.Enabled = true\n\t\t\taccount.UserName = user\n\t\t\taccount.Password = pass\n\t\t\taccount.RoleID = role\n\t\t\taccount.AccountTypes = []schemas.AccountTypes{\"Redfish\", \"OEM\"}\n\n\t\t\terr := account.Update()\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t\treturn true, nil\n\t\t}\n\t}\n\n\treturn false, ErrNoUserSlotsAvailable\n}\n\n// UserDelete deletes a user account\nfunc (c *Conn) UserDelete(ctx context.Context, user string) (ok bool, err error) {\n\tif user == \"\" {\n\t\treturn false, ErrUserPassParams\n\t}\n\n\tservice, err := c.redfishwrapper.AccountService()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// fetch current list of accounts\n\taccounts, err := service.Accounts()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\t// identify account slot\n\tfor _, account := range accounts {\n\t\t// Dell iDracs don't want us to create/delete accounts in these slots\n\t\tif internal.StringInSlice(account.ID, []string{\"1\"}) {\n\t\t\tcontinue\n\t\t}\n\n\t\taccount := account\n\t\tif account.UserName == user {\n\t\t\taccount.Enabled = false\n\t\t\taccount.UserName = \"\"\n\t\t\taccount.Password = \"\"\n\n\t\t\terr := account.Update()\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\n\t\t\treturn true, nil\n\t\t}\n\t}\n\n\treturn false, ErrUserNotPresent\n}\n"
  },
  {
    "path": "providers/rpc/doc.go",
    "content": "/*\nPackage rpc is a provider that defines an HTTP request/response contract for handling BMC interactions.\nIt allows users a simple way to interoperate with an existing/bespoke out-of-band management solution.\n\nThe rpc provider request/response payloads are modeled after JSON-RPC 2.0, but are not JSON-RPC 2.0\ncompliant so as to allow for more flexibility and interoperability with existing systems.\n\nThe rpc provider has options that can be set to include an HMAC signature in the request header.\nIt follows the features found at https://webhooks.fyi/security/hmac, this includes hash algorithms sha256\nand sha512, replay prevention, versioning, and key rotation.\n*/\npackage rpc\n"
  },
  {
    "path": "providers/rpc/experimental.go",
    "content": "package rpc\n\nimport (\n\t\"github.com/Jeffail/gabs/v2\"\n\t\"github.com/ghodss/yaml\"\n)\n\n// embedPayload will embed the RequestPayload into the given JSON object at the dot path notation location (\"object.data\").\nfunc (p *RequestPayload) embedPayload(rawJSON []byte, dotPath string) ([]byte, error) {\n\tif rawJSON == nil {\n\t\treturn rawJSON, nil\n\t}\n\tjdata2, err := yaml.YAMLToJSON(rawJSON)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tg, err := gabs.ParseJSON(jdata2)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif _, err := g.SetP(p, dotPath); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn g.Bytes(), nil\n}\n"
  },
  {
    "path": "providers/rpc/http.go",
    "content": "package rpc\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\n// createRequest\nfunc (p *Provider) createRequest(ctx context.Context, rp RequestPayload) (*http.Request, error) {\n\tvar data []byte\n\tif rj := p.Opts.Experimental.CustomRequestPayload; rj != nil && p.Opts.Experimental.DotPath != \"\" {\n\t\td, err := rp.embedPayload(rj, p.Opts.Experimental.DotPath)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdata = d\n\t} else {\n\t\td, err := json.Marshal(rp)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdata = d\n\t}\n\n\treq, err := http.NewRequestWithContext(ctx, p.Opts.Request.HTTPMethod, p.listenerURL.String(), bytes.NewReader(data))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor k, v := range p.Opts.Request.StaticHeaders {\n\t\treq.Header.Add(k, strings.Join(v, \",\"))\n\t}\n\tif p.Opts.Request.HTTPContentType != \"\" {\n\t\treq.Header.Set(\"Content-Type\", p.Opts.Request.HTTPContentType)\n\t}\n\tif p.Opts.Request.TimestampHeader != \"\" {\n\t\treq.Header.Add(p.Opts.Request.TimestampHeader, time.Now().Format(p.Opts.Request.TimestampFormat))\n\t}\n\n\treturn req, nil\n}\n\nfunc (p *Provider) handleResponse(statusCode int, headers http.Header, body *bytes.Buffer, reqKeysAndValues []any) (ResponsePayload, error) {\n\tkvs := reqKeysAndValues\n\tdefer func() {\n\t\tif !p.LogNotificationsDisabled {\n\t\t\tkvs = append(kvs, responseKVS(statusCode, headers, body)...)\n\t\t\tp.Logger.Info(\"rpc notification details\", kvs...)\n\t\t}\n\t}()\n\n\tres := ResponsePayload{}\n\tif err := json.Unmarshal(body.Bytes(), &res); err != nil {\n\t\tif statusCode != http.StatusOK {\n\t\t\treturn ResponsePayload{}, fmt.Errorf(\"unexpected status code: %d, response error(optional): %v\", statusCode, res.Error)\n\t\t}\n\t\treturn ResponsePayload{}, fmt.Errorf(\"failed to parse response: %w\", err)\n\t}\n\tif statusCode != http.StatusOK {\n\t\treturn ResponsePayload{}, fmt.Errorf(\"unexpected status code: %d, response error(optional): %v\", statusCode, res.Error)\n\t}\n\n\treturn res, nil\n}\n"
  },
  {
    "path": "providers/rpc/http_test.go",
    "content": "package rpc\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n\t\"github.com/google/go-cmp/cmp/cmpopts\"\n)\n\nfunc testRequest(method, reqURL string, body RequestPayload, headers http.Header) *http.Request {\n\tbuf := new(bytes.Buffer)\n\t_ = json.NewEncoder(buf).Encode(body)\n\treq, _ := http.NewRequestWithContext(context.Background(), method, reqURL, buf)\n\treq.Header = headers\n\treturn req\n}\n\nfunc TestRequestKVS(t *testing.T) {\n\ttests := map[string]struct {\n\t\treq      *http.Request\n\t\texpected []interface{}\n\t}{\n\t\t\"success\": {\n\t\t\treq: testRequest(\n\t\t\t\thttp.MethodPost, \"http://example.com\",\n\t\t\t\tRequestPayload{ID: 1, Host: \"127.0.0.1\", Method: \"POST\", Params: nil},\n\t\t\t\thttp.Header{\"Content-Type\": []string{\"application/json\"}},\n\t\t\t),\n\t\t\texpected: []interface{}{\"request\", requestDetails{\n\t\t\t\tBody: RequestPayload{\n\t\t\t\t\tID:     1,\n\t\t\t\t\tHost:   \"127.0.0.1\",\n\t\t\t\t\tMethod: \"POST\",\n\t\t\t\t\tParams: nil,\n\t\t\t\t},\n\t\t\t\tHeaders: http.Header{\"Content-Type\": {\"application/json\"}},\n\t\t\t\tURL:     \"http://example.com\",\n\t\t\t\tMethod:  \"POST\",\n\t\t\t}},\n\t\t},\n\t}\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tbuf := new(bytes.Buffer)\n\t\t\t_, _ = io.Copy(buf, tc.req.Body)\n\t\t\tkvs := requestKVS(tc.req.Method, tc.req.URL.String(), tc.req.Header, buf)\n\t\t\tif diff := cmp.Diff(kvs, tc.expected); diff != \"\" {\n\t\t\t\tt.Fatalf(\"requestKVS() mismatch (-want +got):\\n%s\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestResponseKVS(t *testing.T) {\n\ttests := map[string]struct {\n\t\tresp     *http.Response\n\t\texpected []interface{}\n\t}{\n\t\t\"one\": {\n\t\t\tresp: &http.Response{StatusCode: 200, Header: http.Header{\"Content-Type\": []string{\"application/json\"}}, Body: io.NopCloser(strings.NewReader(`{\"id\":1,\"host\":\"127.0.0.1\"}`))},\n\t\t\texpected: []interface{}{\"response\", responseDetails{\n\t\t\t\tStatusCode: 200,\n\t\t\t\tHeaders:    http.Header{\"Content-Type\": {\"application/json\"}},\n\t\t\t\tBody:       ResponsePayload{ID: 1, Host: \"127.0.0.1\"},\n\t\t\t}},\n\t\t},\n\t}\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tbuf := new(bytes.Buffer)\n\t\t\t_, _ = io.Copy(buf, tc.resp.Body)\n\t\t\tkvs := responseKVS(tc.resp.StatusCode, tc.resp.Header, buf)\n\t\t\tif diff := cmp.Diff(kvs, tc.expected); diff != \"\" {\n\t\t\t\tt.Fatalf(\"requestKVS() mismatch (-want +got):\\n%s\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCreateRequest(t *testing.T) {\n\ttests := map[string]struct {\n\t\tcfg      Provider\n\t\tbody     RequestPayload\n\t\texpected *http.Request\n\t}{\n\t\t\"success\": {\n\t\t\tcfg: Provider{\n\t\t\t\tOpts: Opts{\n\t\t\t\t\tRequest: RequestOpts{\n\t\t\t\t\t\tHTTPMethod:      http.MethodPost,\n\t\t\t\t\t\tHTTPContentType: \"application/json\",\n\t\t\t\t\t\tStaticHeaders:   http.Header{\"X-Test\": []string{\"test\"}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tlistenerURL: &url.URL{Scheme: \"http\", Host: \"example.com\"},\n\t\t\t},\n\t\t\tbody: RequestPayload{ID: 1, Host: \"127.0.0.1\", Method: PowerSetMethod},\n\t\t\texpected: &http.Request{\n\t\t\t\tContentLength: 52,\n\t\t\t\tHost:          \"example.com\",\n\t\t\t\tProto:         \"HTTP/1.1\",\n\t\t\t\tProtoMajor:    1,\n\t\t\t\tProtoMinor:    1,\n\t\t\t\tMethod:        http.MethodPost,\n\t\t\t\tURL:           &url.URL{Scheme: \"http\", Host: \"example.com\"},\n\t\t\t\tHeader:        http.Header{\"X-Test\": {\"test\"}, \"Content-Type\": {\"application/json\"}},\n\t\t\t},\n\t\t},\n\t}\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tdata, _ := json.Marshal(tc.body)\n\t\t\tbody := bytes.NewReader(data)\n\t\t\ttc.expected.Body = io.NopCloser(body)\n\t\t\treq, err := tc.cfg.createRequest(context.Background(), tc.body)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tif diff := cmp.Diff(req, tc.expected, cmpopts.IgnoreUnexported(http.Request{}, bytes.Reader{}), cmpopts.IgnoreFields(http.Request{}, \"GetBody\")); diff != \"\" {\n\t\t\t\tt.Fatalf(\"createRequest() mismatch (-got +want):\\n%s\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestContentSize(t *testing.T) {\n\tprov := New(\"http://127.0.0.1/rpc\", \"127.0.2.1\", Secrets{SHA256: {\"superSecret1\"}})\n\t_ = prov.Open(context.Background())\n\treqPayload := RequestPayload{ID: 1, Host: \"127.0.0.1\", Method: PowerGetMethod}\n\treq, err := prov.createRequest(context.Background(), reqPayload)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif req.ContentLength > maxContentLenAllowed {\n\t\tt.Fatalf(\"unexpected content length: got: %d, want: %v\", req.ContentLength, maxContentLenAllowed)\n\t}\n}\n"
  },
  {
    "path": "providers/rpc/logging.go",
    "content": "package rpc\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"net/http\"\n)\n\ntype requestDetails struct {\n\tBody    RequestPayload `json:\"body\"`\n\tHeaders http.Header    `json:\"headers\"`\n\tURL     string         `json:\"url\"`\n\tMethod  string         `json:\"method\"`\n}\n\ntype responseDetails struct {\n\tStatusCode int             `json:\"statusCode\"`\n\tBody       ResponsePayload `json:\"body\"`\n\tHeaders    http.Header     `json:\"headers\"`\n}\n\n// requestKVS returns a slice of key, value sets. Used for logging.\nfunc requestKVS(method, url string, headers http.Header, body *bytes.Buffer) []interface{} {\n\tvar r requestDetails\n\tif body.Len() > 0 {\n\t\tvar p RequestPayload\n\t\t_ = json.Unmarshal(body.Bytes(), &p)\n\n\t\tr = requestDetails{\n\t\t\tBody:    p,\n\t\t\tHeaders: headers,\n\t\t\tURL:     url,\n\t\t\tMethod:  method,\n\t\t}\n\t}\n\n\treturn []interface{}{\"request\", r}\n}\n\n// responseKVS returns a slice of key, value sets. Used for logging.\nfunc responseKVS(statusCode int, headers http.Header, body *bytes.Buffer) []interface{} {\n\tvar r responseDetails\n\tif body.Len() > 0 {\n\t\tvar p ResponsePayload\n\t\t_ = json.Unmarshal(body.Bytes(), &p)\n\t\tr = responseDetails{\n\t\t\tStatusCode: statusCode,\n\t\t\tBody:       p,\n\t\t\tHeaders:    headers,\n\t\t}\n\t}\n\n\treturn []interface{}{\"response\", r}\n}\n"
  },
  {
    "path": "providers/rpc/payload.go",
    "content": "package rpc\n\nimport \"fmt\"\n\ntype Method string\n\nconst (\n\tBootDeviceMethod   Method = \"setBootDevice\"\n\tPowerSetMethod     Method = \"setPowerState\"\n\tPowerGetMethod     Method = \"getPowerState\"\n\tVirtualMediaMethod Method = \"setVirtualMedia\"\n\tPingMethod         Method = \"ping\"\n)\n\n// RequestPayload is the payload sent to the ConsumerURL.\ntype RequestPayload struct {\n\tID     int64  `json:\"id\"`\n\tHost   string `json:\"host\"`\n\tMethod Method `json:\"method\"`\n\tParams any    `json:\"params,omitempty\"`\n}\n\n// BootDeviceParams are the parameters options used when setting a boot device.\ntype BootDeviceParams struct {\n\tDevice     string `json:\"device\"`\n\tPersistent bool   `json:\"persistent\"`\n\tEFIBoot    bool   `json:\"efiBoot\"`\n}\n\n// PowerSetParams are the parameters options used when setting the power state.\ntype PowerSetParams struct {\n\tState string `json:\"state\"`\n}\n\n// PowerGetParams are the parameters options used when getting the power state.\ntype VirtualMediaParams struct {\n\tMediaURL string `json:\"mediaUrl\"`\n\tKind     string `json:\"kind\"`\n}\n\n// ResponsePayload is the payload received from the ConsumerURL.\n// The Result field is an interface{} so that different methods\n// can define the contract according to their needs.\ntype ResponsePayload struct {\n\t// ID is the ID of the response. It should match the ID of the request but is not enforced.\n\tID     int64          `json:\"id\"`\n\tHost   string         `json:\"host\"`\n\tResult any            `json:\"result,omitempty\"`\n\tError  *ResponseError `json:\"error,omitempty\"`\n}\n\ntype ResponseError struct {\n\tCode    int    `json:\"code\"`\n\tMessage string `json:\"message\"`\n}\n\ntype PowerGetResult string\n\nconst (\n\tPoweredOn  PowerGetResult = \"on\"\n\tPoweredOff PowerGetResult = \"off\"\n)\n\nfunc (p PowerGetResult) String() string {\n\treturn string(p)\n}\n\nfunc (r *ResponseError) String() string {\n\treturn fmt.Sprintf(\"code: %v, message: %v\", r.Code, r.Message)\n}\n"
  },
  {
    "path": "providers/rpc/rpc.go",
    "content": "package rpc\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"hash\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/internal/httpclient\"\n\t\"github.com/bmc-toolbox/bmclib/v2/providers\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/jacobweinstock/registrar\"\n)\n\nconst (\n\t// ProviderName for the RPC implementation.\n\tProviderName = \"rpc\"\n\t// ProviderProtocol for the rpc implementation.\n\tProviderProtocol = \"http\"\n\n\t// defaults\n\ttimestampHeader      = \"X-BMCLIB-Timestamp\"\n\tsignatureHeader      = \"X-BMCLIB-Signature\"\n\tcontentType          = \"application/json\"\n\tmaxContentLenAllowed = 512 << (10 * 1) // 512KB\n\n\t// SHA256 is the SHA256 algorithm.\n\tSHA256 Algorithm = \"sha256\"\n\t// SHA256Short is the short version of the SHA256 algorithm.\n\tSHA256Short Algorithm = \"256\"\n\t// SHA512 is the SHA512 algorithm.\n\tSHA512 Algorithm = \"sha512\"\n\t// SHA512Short is the short version of the SHA512 algorithm.\n\tSHA512Short Algorithm = \"512\"\n)\n\n// Features implemented by the RPC provider.\nvar Features = registrar.Features{\n\tproviders.FeaturePowerSet,\n\tproviders.FeaturePowerState,\n\tproviders.FeatureBootDeviceSet,\n}\n\n// Algorithm is the type for HMAC algorithms.\ntype Algorithm string\n\n// Secrets hold per algorithm slice secrets.\n// These secrets will be used to create HMAC signatures.\ntype Secrets map[Algorithm][]string\n\n// Signatures hold per algorithm slice of signatures.\ntype Signatures map[Algorithm][]string\n\n// Provider defines the configuration for sending rpc notifications.\ntype Provider struct {\n\t// ConsumerURL is the URL where an rpc consumer/listener is running\n\t// and to which we will send and receive all notifications.\n\tConsumerURL string\n\t// Host is the BMC ip address or hostname or identifier.\n\tHost string\n\t// HTTPClient is the http client used for all HTTP calls.\n\tHTTPClient *http.Client\n\t// Logger is the logger to use for logging.\n\tLogger logr.Logger\n\t// LogNotificationsDisabled determines whether responses from rpc consumer/listeners will be logged or not.\n\tLogNotificationsDisabled bool\n\t// Opts are the options for the rpc provider.\n\tOpts Opts\n\n\t// listenerURL is the URL of the rpc consumer/listener.\n\tlistenerURL *url.URL\n}\n\ntype Opts struct {\n\t// Request is the options used to create the rpc HTTP request.\n\tRequest RequestOpts\n\t// Signature is the options used for adding an HMAC signature to an HTTP request.\n\tSignature SignatureOpts\n\t// HMAC is the options used to create a HMAC signature.\n\tHMAC HMACOpts\n\t// Experimental options.\n\tExperimental Experimental\n}\n\ntype RequestOpts struct {\n\t// HTTPContentType is the content type to use for the rpc request notification.\n\tHTTPContentType string\n\t// HTTPMethod is the HTTP method to use for the rpc request notification.\n\tHTTPMethod string\n\t// StaticHeaders are predefined headers that will be added to every request.\n\tStaticHeaders http.Header\n\t// TimestampFormat is the time format for the timestamp header.\n\tTimestampFormat string\n\t// TimestampHeader is the header name that should contain the timestamp. Example: X-BMCLIB-Timestamp\n\tTimestampHeader string\n}\n\ntype SignatureOpts struct {\n\t// HeaderName is the header name that should contain the signature(s). Example: X-BMCLIB-Signature\n\tHeaderName string\n\t// AppendAlgoToHeaderDisabled decides whether to append the algorithm to the signature header or not.\n\t// Example: X-BMCLIB-Signature becomes X-BMCLIB-Signature-256\n\t// When set to true, a header will be added for each algorithm. Example: X-BMCLIB-Signature-256 and X-BMCLIB-Signature-512\n\tAppendAlgoToHeaderDisabled bool\n\t// IncludedPayloadHeaders are headers whose values will be included in the signature payload. Example: given these headers in a request:\n\t// X-My-Header=123,X-Another=456, and IncludedPayloadHeaders := []string{\"X-Another\"}, the value of \"X-Another\" will be included in the signature payload.\n\t// All headers will be deduplicated.\n\tIncludedPayloadHeaders []string\n}\n\ntype HMACOpts struct {\n\t// Hashes is a map of algorithms to a slice of hash.Hash (these are the hashed secrets). The slice is used to support multiple secrets.\n\tHashes map[Algorithm][]hash.Hash\n\t// PrefixSigDisabled determines whether the algorithm will be prefixed to the signature. Example: sha256=abc123\n\tPrefixSigDisabled bool\n\t// Secrets are a map of algorithms to secrets used for signing.\n\tSecrets Secrets\n}\n\ntype Experimental struct {\n\t// CustomRequestPayload must be in json.\n\tCustomRequestPayload []byte\n\t// DotPath is the path to where the bmclib RequestPayload{} will be embedded. For example: object.data.body\n\tDotPath string\n}\n\n// New returns a new Config containing all the defaults for the rpc provider.\nfunc New(consumerURL string, host string, secrets Secrets) *Provider {\n\t// defaults\n\tc := &Provider{\n\t\tHost:        host,\n\t\tConsumerURL: consumerURL,\n\t\tHTTPClient:  httpclient.Build(),\n\t\tLogger:      logr.Discard(),\n\t\tOpts: Opts{\n\t\t\tRequest: RequestOpts{\n\t\t\t\tHTTPContentType: contentType,\n\t\t\t\tHTTPMethod:      http.MethodPost,\n\t\t\t\tTimestampFormat: time.RFC3339,\n\t\t\t\tTimestampHeader: timestampHeader,\n\t\t\t},\n\t\t\tSignature: SignatureOpts{\n\t\t\t\tHeaderName:             signatureHeader,\n\t\t\t\tIncludedPayloadHeaders: []string{},\n\t\t\t},\n\t\t\tHMAC: HMACOpts{\n\t\t\t\tHashes:  map[Algorithm][]hash.Hash{},\n\t\t\t\tSecrets: secrets,\n\t\t\t},\n\t\t\tExperimental: Experimental{},\n\t\t},\n\t}\n\n\tif len(secrets) > 0 {\n\t\tc.Opts.HMAC.Hashes = CreateHashes(secrets)\n\t}\n\n\treturn c\n}\n\n// Name returns the name of this rpc provider.\n// Implements bmc.Provider interface\nfunc (p *Provider) Name() string {\n\treturn ProviderName\n}\n\n// Open a connection to the rpc consumer.\n// For the rpc provider, Open means validating the Config and\n// that communication with the rpc consumer can be established.\nfunc (p *Provider) Open(ctx context.Context) error {\n\t// 1. validate consumerURL is a properly formatted URL.\n\t// 2. validate that we can communicate with the rpc consumer.\n\tu, err := url.Parse(p.ConsumerURL)\n\tif err != nil {\n\t\treturn err\n\t}\n\tp.listenerURL = u\n\n\tif _, err = p.process(ctx, RequestPayload{\n\t\tID:     time.Now().UnixNano(),\n\t\tHost:   p.Host,\n\t\tMethod: PingMethod,\n\t}); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// Close a connection to the rpc consumer.\nfunc (p *Provider) Close(_ context.Context) (err error) {\n\treturn nil\n}\n\n// BootDeviceSet sends a next boot device rpc notification.\nfunc (p *Provider) BootDeviceSet(ctx context.Context, bootDevice string, setPersistent, efiBoot bool) (ok bool, err error) {\n\trp := RequestPayload{\n\t\tID:     time.Now().UnixNano(),\n\t\tHost:   p.Host,\n\t\tMethod: BootDeviceMethod,\n\t\tParams: BootDeviceParams{\n\t\t\tDevice:     bootDevice,\n\t\t\tPersistent: setPersistent,\n\t\t\tEFIBoot:    efiBoot,\n\t\t},\n\t}\n\tresp, err := p.process(ctx, rp)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif resp.Error != nil && resp.Error.Code != 0 {\n\t\treturn false, fmt.Errorf(\"error from rpc consumer: %v\", resp.Error)\n\t}\n\n\treturn true, nil\n}\n\n// PowerSet sets the power state of a BMC machine.\nfunc (p *Provider) PowerSet(ctx context.Context, state string) (ok bool, err error) {\n\trp := RequestPayload{\n\t\tID:     time.Now().UnixNano(),\n\t\tHost:   p.Host,\n\t\tMethod: PowerSetMethod,\n\t\tParams: PowerSetParams{\n\t\t\tState: strings.ToLower(state),\n\t\t},\n\t}\n\tresp, err := p.process(ctx, rp)\n\tif err != nil {\n\t\treturn ok, err\n\t}\n\tif resp.Error != nil && resp.Error.Code != 0 {\n\t\treturn ok, fmt.Errorf(\"error from rpc consumer: %v\", resp.Error)\n\t}\n\n\treturn true, nil\n}\n\n// PowerStateGet gets the power state of a BMC machine.\nfunc (p *Provider) PowerStateGet(ctx context.Context) (state string, err error) {\n\trp := RequestPayload{\n\t\tID:     time.Now().UnixNano(),\n\t\tHost:   p.Host,\n\t\tMethod: PowerGetMethod,\n\t}\n\tresp, err := p.process(ctx, rp)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif resp.Error != nil && resp.Error.Code != 0 {\n\t\treturn \"\", fmt.Errorf(\"error from rpc consumer: %v\", resp.Error)\n\t}\n\n\ts, ok := resp.Result.(string)\n\tif !ok {\n\t\treturn \"\", fmt.Errorf(\"expected result equal to type string, got: %T\", resp.Result)\n\t}\n\n\treturn s, nil\n}\n\n// process is the main function for the roundtrip of rpc calls to the ConsumerURL.\nfunc (p *Provider) process(ctx context.Context, rp RequestPayload) (ResponsePayload, error) {\n\t// 1. create the HTTP request.\n\t// 2. create the signature payload.\n\t// 3. sign the signature payload.\n\t// 4. add signatures to the request as headers.\n\t// 5. request/response round trip.\n\t// 6. handle the response.\n\treq, err := p.createRequest(ctx, rp)\n\tif err != nil {\n\t\treturn ResponsePayload{}, err\n\t}\n\n\t// create the signature payload\n\treqBuf := new(bytes.Buffer)\n\treqBody, err := req.GetBody()\n\tif err != nil {\n\t\treturn ResponsePayload{}, fmt.Errorf(\"failed to get request body: %w\", err)\n\t}\n\tif _, err := io.Copy(reqBuf, reqBody); err != nil {\n\t\treturn ResponsePayload{}, fmt.Errorf(\"failed to read request body: %w\", err)\n\t}\n\n\theadersForSig := http.Header{}\n\tfor _, h := range p.Opts.Signature.IncludedPayloadHeaders {\n\t\tif val := req.Header.Get(h); val != \"\" {\n\t\t\theadersForSig.Add(h, val)\n\t\t}\n\t}\n\tsigPay := createSignaturePayload(reqBuf.Bytes(), headersForSig)\n\n\t// sign the signature payload\n\tsigs, err := sign(sigPay, p.Opts.HMAC.Hashes, p.Opts.HMAC.PrefixSigDisabled)\n\tif err != nil {\n\t\treturn ResponsePayload{}, err\n\t}\n\n\t// add signatures to the request as headers.\n\tfor algo, keys := range sigs {\n\t\tif len(sigs) > 0 {\n\t\t\th := p.Opts.Signature.HeaderName\n\t\t\tif !p.Opts.Signature.AppendAlgoToHeaderDisabled {\n\t\t\t\th = fmt.Sprintf(\"%s-%s\", h, algo.ToShort())\n\t\t\t}\n\t\t\treq.Header.Add(h, strings.Join(keys, \",\"))\n\t\t}\n\t}\n\n\t// request/response round trip.\n\tkvs := requestKVS(req.Method, req.URL.String(), req.Header, reqBuf)\n\tkvs = append(kvs, []interface{}{\"host\", p.Host, \"method\", rp.Method, \"consumerURL\", p.ConsumerURL}...)\n\tif rp.Params != nil {\n\t\tkvs = append(kvs, []interface{}{\"params\", rp.Params}...)\n\t}\n\n\tresp, err := p.HTTPClient.Do(req)\n\tif err != nil {\n\t\tp.Logger.Error(err, \"failed to send rpc notification\", kvs...)\n\t\treturn ResponsePayload{}, err\n\t}\n\tdefer resp.Body.Close()\n\n\t// handle the response\n\tif resp.ContentLength > maxContentLenAllowed || resp.ContentLength < 0 {\n\t\treturn ResponsePayload{}, fmt.Errorf(\"response body is too large: %d bytes, max allowed: %d bytes\", resp.ContentLength, maxContentLenAllowed)\n\t}\n\trespBuf := new(bytes.Buffer)\n\tif _, err := io.CopyN(respBuf, resp.Body, resp.ContentLength); err != nil {\n\t\treturn ResponsePayload{}, fmt.Errorf(\"failed to read response body: %w\", err)\n\t}\n\trespPayload, err := p.handleResponse(resp.StatusCode, resp.Header, respBuf, kvs)\n\tif err != nil {\n\t\treturn ResponsePayload{}, err\n\t}\n\n\treturn respPayload, nil\n}\n\n// Transformer implements the mergo interfaces for merging custom types.\nfunc (p *Provider) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {\n\tswitch typ {\n\tcase reflect.TypeOf(logr.Logger{}):\n\t\treturn func(dst, src reflect.Value) error {\n\t\t\tif dst.CanSet() {\n\t\t\t\tisZero := dst.MethodByName(\"GetSink\")\n\t\t\t\tresult := isZero.Call(nil)\n\t\t\t\tif result[0].IsNil() {\n\t\t\t\t\tdst.Set(src)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "providers/rpc/rpc_test.go",
    "content": "package rpc\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/google/go-cmp/cmp\"\n)\n\nfunc TestOpen(t *testing.T) {\n\ttests := map[string]struct {\n\t\turl       string\n\t\tshouldErr bool\n\t}{\n\t\t\"success\":        {},\n\t\t\"bad url\":        {url: \"%\", shouldErr: true},\n\t\t\"failed request\": {url: \"127.1.1.1\", shouldErr: true},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tsvr := testConsumer{rp: ResponsePayload{}}.testServer()\n\t\t\tdefer svr.Close()\n\n\t\t\tu := svr.URL\n\t\t\tif tc.url != \"\" {\n\t\t\t\tu = tc.url\n\t\t\t}\n\t\t\tc := New(u, \"127.0.1.1\", Secrets{SHA256: []string{\"superSecret1\"}})\n\t\t\tctx, cancel := context.WithCancel(context.Background())\n\t\t\tdefer cancel()\n\t\t\tif err := c.Open(ctx); err != nil && !tc.shouldErr {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tc.Close(ctx)\n\t\t})\n\t}\n}\n\nfunc TestBootDeviceSet(t *testing.T) {\n\ttests := map[string]struct {\n\t\turl       string\n\t\tshouldErr bool\n\t}{\n\t\t\"success\":               {},\n\t\t\"failure from consumer\": {shouldErr: true},\n\t\t\"failed request\":        {url: \"127.1.1.1\", shouldErr: true},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trsp := testConsumer{\n\t\t\t\trp: ResponsePayload{},\n\t\t\t}\n\t\t\tif tc.shouldErr {\n\t\t\t\trsp.rp.Error = &ResponseError{Code: 500, Message: \"failed\"}\n\t\t\t}\n\t\t\tsvr := rsp.testServer()\n\t\t\tdefer svr.Close()\n\n\t\t\tu := svr.URL\n\t\t\tif tc.url != \"\" {\n\t\t\t\tu = tc.url\n\t\t\t}\n\t\t\tc := New(u, \"127.0.1.1\", Secrets{SHA256: {\"superSecret1\"}})\n\t\t\tctx, cancel := context.WithCancel(context.Background())\n\t\t\tdefer cancel()\n\t\t\t_ = c.Open(ctx)\n\t\t\tif _, err := c.BootDeviceSet(ctx, \"pxe\", false, false); err != nil && !tc.shouldErr {\n\t\t\t\tt.Fatal(err)\n\t\t\t} else if err == nil && tc.shouldErr {\n\t\t\t\tt.Fatal(\"expected error, got none\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPowerSet(t *testing.T) {\n\ttests := map[string]struct {\n\t\turl        string\n\t\tpowerState string\n\t\tshouldErr  bool\n\t}{\n\t\t\"success\":               {powerState: \"on\"},\n\t\t\"failed request\":        {powerState: \"on\", url: \"127.1.1.1\", shouldErr: true},\n\t\t\"unknown state\":         {powerState: \"unknown\", shouldErr: true},\n\t\t\"failure from consumer\": {powerState: \"on\", shouldErr: true},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trsp := testConsumer{\n\t\t\t\trp: ResponsePayload{Result: tc.powerState},\n\t\t\t}\n\t\t\tif tc.shouldErr {\n\t\t\t\trsp.rp.Error = &ResponseError{Code: 500, Message: \"failed\"}\n\t\t\t}\n\t\t\tsvr := rsp.testServer()\n\t\t\tdefer svr.Close()\n\n\t\t\tu := svr.URL\n\t\t\tif tc.url != \"\" {\n\t\t\t\tu = tc.url\n\t\t\t}\n\t\t\tc := New(u, \"127.0.1.1\", Secrets{SHA256: {\"superSecret1\"}})\n\t\t\tctx, cancel := context.WithCancel(context.Background())\n\t\t\tdefer cancel()\n\t\t\t_ = c.Open(ctx)\n\n\t\t\t_, err := c.PowerSet(ctx, tc.powerState)\n\t\t\tif err != nil && !tc.shouldErr {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPowerStateGet(t *testing.T) {\n\ttests := map[string]struct {\n\t\tpowerState string\n\t\tshouldErr  bool\n\t\turl        string\n\t}{\n\t\t\"success\":       {powerState: \"on\"},\n\t\t\"unknown state\": {shouldErr: true},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trsp := testConsumer{\n\t\t\t\trp: ResponsePayload{ID: 123, Host: \"127.0.1.1\", Result: tc.powerState},\n\t\t\t}\n\t\t\tif tc.shouldErr {\n\t\t\t\trsp.rp.Error = &ResponseError{Code: 500, Message: \"failed\"}\n\t\t\t}\n\t\t\tsvr := rsp.testServer()\n\t\t\tdefer svr.Close()\n\n\t\t\tu := svr.URL\n\t\t\tif tc.url != \"\" {\n\t\t\t\tu = tc.url\n\t\t\t}\n\t\t\tctx, cancel := context.WithCancel(context.Background())\n\t\t\tdefer cancel()\n\t\t\tc := New(u, \"127.0.1.1\", Secrets{SHA256: {\"superSecret1\"}})\n\t\t\t_ = c.Open(ctx)\n\t\t\tgotState, err := c.PowerStateGet(ctx)\n\t\t\tif err != nil && !tc.shouldErr {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif diff := cmp.Diff(gotState, tc.powerState); diff != \"\" {\n\t\t\t\tt.Fatal(diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestServerErrors(t *testing.T) {\n\ttests := map[string]struct {\n\t\tstatusCode int\n\t\tshouldErr  bool\n\t}{\n\t\t\"bad request\": {statusCode: http.StatusBadRequest, shouldErr: true},\n\t\t\"not found\":   {statusCode: http.StatusNotFound, shouldErr: true},\n\t\t\"internal\":    {statusCode: http.StatusInternalServerError, shouldErr: true},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trsp := testConsumer{\n\t\t\t\trp:         ResponsePayload{Result: \"on\"},\n\t\t\t\tstatusCode: tc.statusCode,\n\t\t\t}\n\t\t\tsvr := rsp.testServer()\n\t\t\tdefer svr.Close()\n\n\t\t\tctx, cancel := context.WithCancel(context.Background())\n\t\t\tdefer cancel()\n\t\t\tc := New(svr.URL, \"127.0.0.1\", Secrets{SHA256: {\"superSecret1\"}})\n\t\t\tif err := c.Open(ctx); err == nil {\n\t\t\t\tt.Fatal(\"expected error, got none\")\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype testConsumer struct {\n\trp         ResponsePayload\n\tstatusCode int\n}\n\nfunc (t testConsumer) testServer() *httptest.Server {\n\treturn httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif t.statusCode != 0 {\n\t\t\tw.WriteHeader(t.statusCode)\n\t\t\treturn\n\t\t}\n\t\tb, _ := json.Marshal(t.rp)\n\t\t_, _ = w.Write(b)\n\t}))\n}\n"
  },
  {
    "path": "providers/rpc/signature.go",
    "content": "package rpc\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"crypto/sha512\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"hash\"\n\t\"net/http\"\n\t\"strings\"\n)\n\ntype Hashes map[Algorithm][]hash.Hash\n\n// createSignaturePayload a signature payload is created by appending header values to the request body.\n// there is no delimiter between the body and the header values and all header values.\nfunc createSignaturePayload(body []byte, h http.Header) []byte {\n\t// add headers to signature payload, no space between values.\n\tfor _, val := range h {\n\t\tbody = append(body, []byte(strings.Join(val, \"\"))...)\n\n\t}\n\n\treturn body\n}\n\n// sign signs the data with all the given hashes and returns the signatures.\nfunc sign(data []byte, h Hashes, prefixSigDisabled bool) (Signatures, error) {\n\tsigs := map[Algorithm][]string{}\n\tfor algo, hshs := range h {\n\t\tfor _, hsh := range hshs {\n\t\t\tif _, err := hsh.Write(data); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tsig := hex.EncodeToString(hsh.Sum(nil))\n\t\t\tif !prefixSigDisabled {\n\t\t\t\tsig = fmt.Sprintf(\"%s=%s\", algo, sig)\n\t\t\t}\n\t\t\tsigs[algo] = append(sigs[algo], sig)\n\t\t\t// reset so Sign can be called multiple times. Otherwise, the next call will append to the previous one.\n\t\t\thsh.Reset()\n\t\t}\n\t}\n\n\treturn sigs, nil\n}\n\n// ToShort returns the short version of an algorithm.\nfunc (a Algorithm) ToShort() Algorithm {\n\tswitch a {\n\tcase SHA256:\n\t\treturn SHA256Short\n\tcase SHA512:\n\t\treturn SHA512Short\n\tdefault:\n\t\treturn a\n\t}\n}\n\n// NewSHA256 returns a map of SHA256 HMACs from the given secrets.\nfunc NewSHA256(secret ...string) Hashes {\n\tvar hsh []hash.Hash\n\tfor _, s := range secret {\n\t\thsh = append(hsh, hmac.New(sha256.New, []byte(s)))\n\t}\n\treturn Hashes{SHA256: hsh}\n}\n\n// NewSHA512 returns a map of SHA512 HMACs from the given secrets.\nfunc NewSHA512(secret ...string) Hashes {\n\tvar hsh []hash.Hash\n\tfor _, s := range secret {\n\t\thsh = append(hsh, hmac.New(sha512.New, []byte(s)))\n\t}\n\treturn Hashes{SHA512: hsh}\n}\n\nfunc mergeHashes(hs ...Hashes) Hashes {\n\tm := Hashes{}\n\tfor _, h := range hs {\n\t\tfor k, v := range h {\n\t\t\tm[k] = append(m[k], v...)\n\t\t}\n\t}\n\treturn m\n}\n\n// CreateHashes creates a new hash for all secrets provided.\nfunc CreateHashes(s Secrets) map[Algorithm][]hash.Hash {\n\th := map[Algorithm][]hash.Hash{}\n\tfor algo, secrets := range s {\n\t\tswitch algo {\n\t\tcase SHA256, SHA256Short:\n\t\t\th = mergeHashes(h, NewSHA256(secrets...))\n\t\tcase SHA512, SHA512Short:\n\t\t\th = mergeHashes(h, NewSHA512(secrets...))\n\t\t}\n\t}\n\n\treturn h\n}\n"
  },
  {
    "path": "providers/supermicro/docs/x11.md",
    "content": "#### x11 XML API power commands\n\npower-off - immediate - `op=POWER_INFO.XML&r=(1,0)&_=`\npower-on - `op=POWER_INFO.XML&r=(1,1)&_=`\npower-off - `acpi/orderly - op=POWER_INFO.XML&r=(1,5)&_=`\nreset server - cold powercycle - `op=POWER_INFO.XML&r=(1,3)&_=`\npower cycle - `op=POWER_INFO.XML&r=(1,2)&_=`\n\n\nref invocation\n```go\n// powerCycle using SMC XML API\nfunc (c *x11) powerCycle(ctx context.Context) (bool, error) {\n\tpayload := []byte(`op=POWER_INFO.XML&r=(1,3)&_=`)\n\n\theaders := map[string]string{\n\t\t\"Content-type\": \"application/x-www-form-urlencoded; charset=UTF-8\",\n\t}\n\n\tbody, status, err := c.serviceClient.query(ctx, \"cgi/ipmi.cgi\", http.MethodPost, bytes.NewBuffer(payload), headers, 0)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif status != http.StatusOK {\n\t\treturn false, unexpectedResponseErr(payload, body, status)\n\t}\n\n\treturn true, nil\n}\n```\n"
  },
  {
    "path": "providers/supermicro/docs/x12.md",
    "content": "curl 'https://10.251.153.157/redfish/v1/UpdateService/upload' \\\n  -H 'Accept: */*' \\\n  -H 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' \\\n  -H 'CSRF_TOKEN: p9lTd1+h0qsz/inooljtRbrja+1/z6nBRLuAKV6JJkM' \\\n  -H 'Connection: keep-alive' \\\n  -H 'Content-Type: multipart/form-data; boundary=----WebKitFormBoundarytykIB3S8fDkno3cP' \\\n  -H 'Cookie: SID=1rnGhJ9HoMI6JpP' \\\n  -H 'Origin: https://10.251.153.157' \\\n  -H 'Referer: https://10.251.153.157/cgi/url_redirect.cgi?url_name=topmenu' \\\n  -H 'Sec-Fetch-Dest: empty' \\\n  -H 'Sec-Fetch-Mode: cors' \\\n  -H 'Sec-Fetch-Site: same-origin' \\\n  -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36' \\\n  -H 'X-Auth-Token: o5jk3881vldnk3hdkn20wu5kg8brl18r' \\\n  -H 'X-Requested-With: XMLHttpRequest' \\\n  -H 'sec-ch-ua: \"Google Chrome\";v=\"117\", \"Not;A=Brand\";v=\"8\", \"Chromium\";v=\"117\"' \\\n  -H 'sec-ch-ua-mobile: ?0' \\\n  -H 'sec-ch-ua-platform: \"macOS\"' \\\n  --data-raw $'------WebKitFormBoundarytykIB3S8fDkno3cP\\r\\nContent-Disposition: form-data; name=\"UpdateParameters\"\\r\\n\\r\\n{\"Targets\":[\"/redfish/v1/Managers/1\"],\"@Redfish.OperationApplyTime\":\"OnStartUpdateRequest\",\"Oem\":{\"Supermicro\":{\"BMC\":{\"PreserveCfg\":true,\"PreserveSdr\":true,\"PreserveSsl\":true}}}}\\r\\n------WebKitFormBoundarytykIB3S8fDkno3cP\\r\\nContent-Disposition: form-data; name=\"UpdateFile\"; filename=\"BMC_X12AST2600-F201MS_20220627_1.13.04_STDsp.bin\"\\r\\nContent-Type: application/macbinary\\r\\n\\r\\n\\r\\n------WebKitFormBoundarytykIB3S8fDkno3cP--\\r\\n' \\\n  --compressed\n\n\n// install parameters\n{\"Targets\":[\"/redfish/v1/Managers/1\"],\"@Redfish.OperationApplyTime\":\"OnStartUpdateRequest\",\"Oem\":{\"Supermicro\":{\"BMC\":{\"PreserveCfg\":true,\"PreserveSdr\":true,\"PreserveSsl\":true}}}}\n\n  ## look for task with name \"BMC Verify\"\n\n❯ curl 'https://10.251.153.157/redfish/v1/TaskService/Tasks/1' \\\n  -H 'Accept: application/json, text/javascript, */*; q=0.01' \\\n  -H 'CSRF_TOKEN: 10QMfkMegOzCe/WZZARLcs0cpxdDif8tSJcg5ZEnqVw' \\\n  -H 'Connection: keep-alive' \\\n  -H 'Content-Type: application/json' \\\n  -H 'Cookie: SID=1rnGhJ9HoMI6JpP' \\\n  -H 'X-Auth-Token: o5jk3881vldnk3hdkn20wu5kg8brl18r' \\\n  --compressed \\\n  --insecure\n\n\n  {\"@odata.type\":\"#Task.v1_4_3.Task\",\"@odata.id\":\"/redfish/v1/TaskService/Tasks/1\",\"Id\":\"1\",\"Name\":\"BMC Verify\",\"TaskState\":\"Completed\",\"StartTime\":\"2023-10-06T07:53:25+00:00\",\"EndTime\":\"2023-10-06T07:53:31+00:00\",\"PercentComplete\":100,\"HidePayload\":true,\"TaskMonitor\":\"/redfish/v1/TaskMonitor/fa37JncCHryDsbzayy4cBWDxS22Jjzh\",\"TaskStatus\":\"OK\",\"Messages\":[{\"MessageId\":\"\",\"RelatedProperties\":[\"\"],\"Message\":\"\",\"MessageArgs\":[\"\"],\"Severity\":\"\"}],\"Oem\":{}}\n\n ## look for task with \"BMC Update\"\n\n {\n  \"@odata.type\": \"#Task.v1_4_3.Task\",\n  \"@odata.id\": \"/redfish/v1/TaskService/Tasks/2\",\n  \"Id\": \"2\",\n  \"Name\": \"BMC Update\",\n  \"TaskState\": \"Running\",\n  \"StartTime\": \"2023-10-09T05:42:25+00:00\",\n  \"PercentComplete\": 2,\n  \"HidePayload\": true,\n  \"TaskMonitor\": \"/redfish/v1/TaskMonitor/MaiRrV41mtzxlYvKWrO72tK0LK0e1zL\",\n  \"TaskStatus\": \"OK\",\n  \"Messages\": [\n    {\n      \"MessageId\": \"\",\n      \"RelatedProperties\": [\n        \"\"\n      ],\n      \"Message\": \"\",\n      \"MessageArgs\": [\n        \"\"\n      ],\n      \"Severity\": \"\"\n    }\n  ],\n  \"Oem\": {}\n}\n\n\ncurl 'https://10.251.153.157/cgi/upgrade_process.cgi' \\\n  -H 'Accept: */*' \\\n  -H 'Accept-Language: en-GB,en-US;q=0.9,en;q=0.8' \\\n  -H 'CSRF_TOKEN: +5/2t9ZcRuEzRg6MbTU2/j5Ils1VM2zf7uVImW/wVMI' \\\n  -H 'Connection: keep-alive' \\\n  -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \\\n  -H 'Cookie: SID=qK4ZDz2cNet9nor' \\\n  -H 'Origin: https://10.251.153.157' \\\n  -H 'Referer: https://10.251.153.157/cgi/url_redirect.cgi?url_name=topmenu' \\\n  -H 'Sec-Fetch-Dest: empty' \\\n  -H 'Sec-Fetch-Mode: cors' \\\n  -H 'Sec-Fetch-Site: same-origin' \\\n  -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36' \\\n  -H 'X-Requested-With: XMLHttpRequest' \\\n  -H 'sec-ch-ua: \"Google Chrome\";v=\"117\", \"Not;A=Brand\";v=\"8\", \"Chromium\";v=\"117\"' \\\n  -H 'sec-ch-ua-mobile: ?0' \\\n  -H 'sec-ch-ua-platform: \"macOS\"' \\\n  --data-raw 'fwtype=255' \\\n  --compressed \\\n  --insecure\n\n\n  {\n  \"@odata.type\": \"#Task.v1_4_3.Task\",\n  \"@odata.id\": \"/redfish/v1/TaskService/Tasks/2\",\n  \"Id\": \"2\",\n  \"Name\": \"BMC Update\",\n  \"TaskState\": \"Running\",\n  \"StartTime\": \"2023-10-13T13:27:51+00:00\",\n  \"PercentComplete\": 5,\n  \"HidePayload\": true,\n  \"TaskMonitor\": \"/redfish/v1/TaskMonitor/MaiRrV41mtzxlYvKWrO72tK0LK0e1zL\",\n  \"TaskStatus\": \"OK\",\n  \"Messages\": [\n    {\n      \"MessageId\": \"\",\n      \"RelatedProperties\": [\n        \"\"\n      ],\n      \"Message\": \"\",\n      \"MessageArgs\": [\n        \"\"\n      ],\n      \"Severity\": \"\"\n    }\n  ],\n  \"Oem\": {}\n}"
  },
  {
    "path": "providers/supermicro/errors.go",
    "content": "package supermicro\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/pkg/errors\"\n)\n\nvar (\n\tErrQueryFRUInfo         = errors.New(\"FRU information query returned error\")\n\tErrXMLAPIUnsupported    = errors.New(\"XML API is unsupported\")\n\tErrModelUnknown         = errors.New(\"Model number unknown\")\n\tErrModelUnsupported     = errors.New(\"Model not supported\")\n\tErrBoardIDUnknown       = errors.New(\"BoardID could not be identified\")\n\tErrUnexpectedResponse   = errors.New(\"Unexpected response content\")\n\tErrUnexpectedStatusCode = errors.New(\"Unexpected status code\")\n)\n\ntype UnexpectedResponseError struct {\n\tpayload    string\n\tresponse   string\n\tstatusCode string\n}\n\nfunc (e *UnexpectedResponseError) Error() string {\n\treturn fmt.Sprintf(\n\t\t\"unexpected response - statusCode: %s, payload: %s, response: %s\",\n\t\te.statusCode,\n\t\te.payload,\n\t\te.response,\n\t)\n}\n\nfunc unexpectedResponseErr(payload, response []byte, statusCode int) error {\n\treturn &UnexpectedResponseError{\n\t\tstring(payload),\n\t\tstring(response),\n\t\tstrconv.Itoa(statusCode),\n\t}\n}\n"
  },
  {
    "path": "providers/supermicro/firmware.go",
    "content": "package supermicro\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/constants\"\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/pkg/errors\"\n)\n\nvar (\n\t// Its likely the X11 code works on all X11's\n\t// for now, we list only the ones its been tested on.\n\t//\n\t// board part numbers\n\t//\n\tsupportedModels = []string{\n\t\t\"X11SSL-F\",\n\t\t\"X11SCM-F\",\n\t\t\"X11DPH-T\",\n\t\t\"X11SCH-F\",\n\t\t\"X11DGQ\",\n\t\t\"X11DPG-SN\",\n\t\t\"X11DPT-B\",\n\t\t\"X11SSE-F\",\n\t\t\"X12STH-SYS\",\n\t\t\"X12SPO-NTF\",\n\t}\n\n\terrUploadTaskIDExpected = errors.New(\"expected an firmware upload taskID\")\n)\n\n// bmc client interface implementations methods\nfunc (c *Client) FirmwareInstallSteps(ctx context.Context, component string) ([]constants.FirmwareInstallStep, error) {\n\tif err := c.serviceClient.supportsFirmwareInstall(c.bmc.deviceModel()); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn c.bmc.firmwareInstallSteps(component)\n}\n\nfunc (c *Client) FirmwareUpload(ctx context.Context, component string, file *os.File) (taskID string, err error) {\n\tif err := c.serviceClient.supportsFirmwareInstall(c.bmc.deviceModel()); err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// expect atleast 5 minutes left in the deadline to proceed with the upload\n\td, set := ctx.Deadline()\n\tif set && time.Until(d) < 5*time.Minute {\n\t\treturn \"\", errors.New(\"remaining context deadline insufficient to perform update: \" + time.Until(d).String())\n\t}\n\n\treturn c.bmc.firmwareUpload(ctx, component, file)\n}\n\nfunc (c *Client) FirmwareInstallUploaded(ctx context.Context, component, uploadTaskID string) (installTaskID string, err error) {\n\tif err := c.serviceClient.supportsFirmwareInstall(c.bmc.deviceModel()); err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// x11's don't return a upload Task ID, since the upload mechanism is not redfish\n\tif !strings.HasPrefix(strings.ToLower(c.bmc.deviceModel()), \"x11\") && uploadTaskID == \"\" {\n\t\treturn \"\", errors.Wrap(errUploadTaskIDExpected, \"device model: \"+c.bmc.deviceModel())\n\t}\n\n\treturn c.bmc.firmwareInstallUploaded(ctx, component, uploadTaskID)\n}\n\n// FirmwareTaskStatus returns the status of a firmware related task queued on the BMC.\nfunc (c *Client) FirmwareTaskStatus(ctx context.Context, kind constants.FirmwareInstallStep, component, taskID, installVersion string) (state constants.TaskState, status string, err error) {\n\tif err := c.serviceClient.supportsFirmwareInstall(c.bmc.deviceModel()); err != nil {\n\t\treturn \"\", \"\", errors.Wrap(bmclibErrs.ErrFirmwareInstallStatus, err.Error())\n\t}\n\n\tcomponent = strings.ToUpper(component)\n\treturn c.bmc.firmwareTaskStatus(ctx, component, taskID)\n}\n"
  },
  {
    "path": "providers/supermicro/firmware_bios_test.go",
    "content": "package supermicro\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"testing\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/internal/httpclient\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc Test_setComponentUpdateMisc(t *testing.T) {\n\ttestcases := []struct {\n\t\tname          string\n\t\tstage         string\n\t\terrorContains string\n\t\tendpoint      string\n\t\thandler       func(http.ResponseWriter, *http.Request)\n\t}{\n\t\t{\n\t\t\t\"preUpdate\",\n\t\t\t\"preUpdate\",\n\t\t\t\"\",\n\t\t\t\"/cgi/ipmi.cgi\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tassert.Equal(t, http.MethodPost, r.Method)\n\t\t\t\tassert.Equal(t, \"application/x-www-form-urlencoded; charset=UTF-8\", r.Header.Get(\"Content-Type\"))\n\n\t\t\t\tb, err := io.ReadAll(r.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tassert.Equal(t, `op=COMPONENT_UPDATE_MISC.XML&r=(0,0)&_=`, string(b))\n\n\t\t\t\t_, _ = w.Write([]byte(`<?xml version=\"1.0\"?>\n\t\t\t\t<IPMI>\n\t\t\t\t  <MISC_INFO RES=\"-1\" SYSOFF=\"0\"/>\n\t\t\t\t</IPMI>`))\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"postUpdate\",\n\t\t\t\"postUpdate\",\n\t\t\t\"\",\n\t\t\t\"/cgi/ipmi.cgi\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tassert.Equal(t, http.MethodPost, r.Method)\n\t\t\t\tassert.Equal(t, \"application/x-www-form-urlencoded; charset=UTF-8\", r.Header.Get(\"Content-Type\"))\n\n\t\t\t\tb, err := io.ReadAll(r.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tassert.Equal(t, `op=COMPONENT_UPDATE_MISC.XML&r=(1,0)&_=`, string(b))\n\n\t\t\t\t_, _ = w.Write([]byte(`<?xml version=\"1.0\"?>\n\t\t\t\t<IPMI>\n\t\t\t\t  <MISC_INFO RES=\"0\" SYSOFF=\"0\"/>\n\t\t\t\t</IPMI>`))\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmux := http.NewServeMux()\n\t\t\tmux.HandleFunc(tc.endpoint, tc.handler)\n\n\t\t\tserver := httptest.NewTLSServer(mux)\n\t\t\tdefer server.Close()\n\n\t\t\tparsedURL, err := url.Parse(server.URL)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tserviceClient := newBmcServiceClient(parsedURL.Hostname(), parsedURL.Port(), \"foo\", \"bar\", httpclient.Build())\n\t\t\tassert.Nil(t, err)\n\n\t\t\tserviceClient.csrfToken = \"foobar\"\n\t\t\tclient := &x11{serviceClient: serviceClient, log: logr.Discard()}\n\n\t\t\tif err := client.checkComponentUpdateMisc(context.Background(), tc.stage); err != nil {\n\t\t\t\tif tc.errorContains != \"\" {\n\t\t\t\t\tassert.ErrorContains(t, err, tc.errorContains)\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tassert.Nil(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_setBIOSFirmwareInstallMode(t *testing.T) {\n\ttestcases := []struct {\n\t\tname          string\n\t\terrorContains string\n\t\tendpoint      string\n\t\thandler       func(http.ResponseWriter, *http.Request)\n\t}{\n\t\t{\n\t\t\t\"BIOS fw install lock acquired\",\n\t\t\t\"\",\n\t\t\t\"/cgi/ipmi.cgi\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tassert.Equal(t, http.MethodPost, r.Method)\n\t\t\t\tassert.Equal(t, \"application/x-www-form-urlencoded; charset=UTF-8\", r.Header.Get(\"Content-Type\"))\n\n\t\t\t\tb, err := io.ReadAll(r.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tassert.Equal(t, `op=LOCK_UPLOAD_FW.XML&r=(0,0)&_=`, string(b))\n\n\t\t\t\t_, _ = w.Write([]byte(`<?xml version=\"1.0\"?>\n\t\t\t\t<IPMI>\n\t\t\t\t  <LOCK_FW_UPLOAD RES=\"1\"/>\n\t\t\t\t</IPMI>`))\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"lock not acquired\",\n\t\t\t\"BMC cold reset required\",\n\t\t\t\"/cgi/ipmi.cgi\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tassert.Equal(t, http.MethodPost, r.Method)\n\t\t\t\tassert.Equal(t, \"application/x-www-form-urlencoded; charset=UTF-8\", r.Header.Get(\"Content-Type\"))\n\n\t\t\t\tb, err := io.ReadAll(r.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tassert.Equal(t, `op=LOCK_UPLOAD_FW.XML&r=(0,0)&_=`, string(b))\n\n\t\t\t\t_, _ = w.Write([]byte(`<?xml version=\"1.0\"?>\n\t\t\t\t<IPMI>\n\t\t\t\t  <LOCK_FW_UPLOAD RES=\"0\"/>\n\t\t\t\t</IPMI>`))\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"error returned\",\n\t\t\t\"400\",\n\t\t\t\"/cgi/ipmi.cgi\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tw.WriteHeader(400)\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmux := http.NewServeMux()\n\t\t\tmux.HandleFunc(tc.endpoint, tc.handler)\n\n\t\t\tserver := httptest.NewTLSServer(mux)\n\t\t\tdefer server.Close()\n\n\t\t\tparsedURL, err := url.Parse(server.URL)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tserviceClient := newBmcServiceClient(parsedURL.Hostname(), parsedURL.Port(), \"foo\", \"bar\", httpclient.Build())\n\t\t\tassert.Nil(t, err)\n\n\t\t\tserviceClient.csrfToken = \"foobar\"\n\t\t\tclient := &x11{serviceClient: serviceClient, log: logr.Discard()}\n\n\t\t\tif err := client.setBMCFirmwareInstallMode(context.Background()); err != nil {\n\t\t\t\tif tc.errorContains != \"\" {\n\t\t\t\t\tassert.ErrorContains(t, err, tc.errorContains)\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tassert.Nil(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "providers/supermicro/fixtures/serviceroot.json",
    "content": "{\n    \"@odata.type\": \"#ServiceRoot.v1_5_2.ServiceRoot\",\n    \"@odata.id\": \"/redfish/v1\",\n    \"Id\": \"ServiceRoot\",\n    \"Name\": \"Root Service\",\n    \"RedfishVersion\": \"1.9.0\",\n    \"UUID\": \"00000000-0000-0000-0000-3CECEFCEFEDA\",\n    \"Systems\": {\n        \"@odata.id\": \"/redfish/v1/Systems\"\n    },\n    \"Chassis\": {\n        \"@odata.id\": \"/redfish/v1/Chassis\"\n    },\n    \"Managers\": {\n        \"@odata.id\": \"/redfish/v1/Managers\"\n    },\n    \"Tasks\": {\n        \"@odata.id\": \"/redfish/v1/TaskService\"\n    },\n    \"SessionService\": {\n        \"@odata.id\": \"/redfish/v1/SessionService\"\n    },\n    \"AccountService\": {\n        \"@odata.id\": \"/redfish/v1/AccountService\"\n    },\n    \"EventService\": {\n        \"@odata.id\": \"/redfish/v1/EventService\"\n    },\n    \"UpdateService\": {\n        \"@odata.id\": \"/redfish/v1/UpdateService\"\n    },\n    \"CertificateService\": {\n        \"@odata.id\": \"/redfish/v1/CertificateService\"\n    },\n    \"Registries\": {\n        \"@odata.id\": \"/redfish/v1/Registries\"\n    },\n    \"JsonSchemas\": {\n        \"@odata.id\": \"/redfish/v1/JsonSchemas\"\n    },\n    \"TelemetryService\": {\n        \"@odata.id\": \"/redfish/v1/TelemetryService\"\n    },\n    \"Links\": {\n        \"Sessions\": {\n            \"@odata.id\": \"/redfish/v1/SessionService/Sessions\"\n        }\n    },\n    \"ProtocolFeaturesSupported\": {\n        \"FilterQuery\": true,\n        \"SelectQuery\": true,\n        \"ExcerptQuery\": false,\n        \"OnlyMemberQuery\": false,\n        \"ExpandQuery\": {\n            \"Links\": true,\n            \"NoLinks\": true,\n            \"ExpandAll\": true,\n            \"Levels\": true,\n            \"MaxLevels\": 2\n        }\n    }\n}"
  },
  {
    "path": "providers/supermicro/floppy.go",
    "content": "package supermicro\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"net/textproto\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n)\n\nvar (\n\terrFloppyImageMounted = errors.New(\"floppy image is currently mounted\")\n)\n\nfunc (c *Client) floppyImageMounted(ctx context.Context) (bool, error) {\n\tif err := c.serviceClient.redfishSession(ctx); err != nil {\n\t\treturn false, err\n\t}\n\n\tinserted, err := c.serviceClient.redfish.InsertedVirtualMedia(ctx)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tfor _, media := range inserted {\n\t\tif strings.Contains(strings.ToLower(media), \"floppy\") {\n\t\t\treturn true, nil\n\t\t}\n\t}\n\n\treturn false, nil\n}\n\nfunc (c *Client) MountFloppyImage(ctx context.Context, image io.Reader) error {\n\tmounted, err := c.floppyImageMounted(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif mounted {\n\t\treturn errFloppyImageMounted\n\t}\n\n\tvar payloadBuffer bytes.Buffer\n\n\ttype form struct {\n\t\tname string\n\t\tdata io.Reader\n\t}\n\n\tformParts := []form{\n\t\t{\n\t\t\tname: \"img_file\",\n\t\t\tdata: image,\n\t\t},\n\t}\n\n\tif c.serviceClient.csrfToken != \"\" {\n\t\tformParts = append(formParts, form{\n\t\t\tname: \"csrf-token\",\n\t\t\tdata: bytes.NewBufferString(c.serviceClient.csrfToken),\n\t\t})\n\t}\n\n\tpayloadWriter := multipart.NewWriter(&payloadBuffer)\n\n\tfor _, part := range formParts {\n\t\tvar partWriter io.Writer\n\n\t\tswitch part.name {\n\t\tcase \"img_file\":\n\t\t\tfile, ok := part.data.(*os.File)\n\t\t\tif !ok {\n\t\t\t\treturn errors.Wrap(ErrMultipartForm, \"expected io.Reader for a floppy image file\")\n\t\t\t}\n\n\t\t\tif partWriter, err = payloadWriter.CreateFormFile(part.name, filepath.Base(file.Name())); err != nil {\n\t\t\t\treturn errors.Wrap(ErrMultipartForm, err.Error())\n\t\t\t}\n\n\t\tcase \"csrf-token\":\n\t\t\t// Add csrf token field\n\t\t\th := make(textproto.MIMEHeader)\n\t\t\t// BMCs with newer firmware (>=1.74.09) accept the form with this name value\n\t\t\t// h.Set(\"Content-Disposition\", `form-data; name=\"CSRF-TOKEN\"`)\n\t\t\t//\n\t\t\t// the BMCs running older firmware (<=1.23.06) versions expects the name value in this format\n\t\t\t// and the newer firmware (>=1.74.09) seem to be backwards compatible with this name value format.\n\t\t\th.Set(\"Content-Disposition\", `form-data; name=\"CSRF_TOKEN\"`)\n\n\t\t\tif partWriter, err = payloadWriter.CreatePart(h); err != nil {\n\t\t\t\treturn errors.Wrap(ErrMultipartForm, err.Error())\n\t\t\t}\n\t\tdefault:\n\t\t\treturn errors.Wrap(ErrMultipartForm, \"unexpected form part: \"+part.name)\n\t\t}\n\n\t\tif _, err = io.Copy(partWriter, part.data); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tpayloadWriter.Close()\n\n\tresp, statusCode, err := c.serviceClient.query(\n\t\tctx,\n\t\t\"cgi/uimapin.cgi\",\n\t\thttp.MethodPost,\n\t\tbytes.NewReader(payloadBuffer.Bytes()),\n\t\tmap[string]string{\"Content-Type\": payloadWriter.FormDataContentType()},\n\t\t0,\n\t)\n\n\tif err != nil {\n\t\treturn errors.Wrap(ErrMultipartForm, err.Error())\n\t}\n\n\tif statusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"non 200 response: %d %s\", statusCode, resp)\n\t}\n\n\treturn nil\n}\n\nfunc (c *Client) UnmountFloppyImage(ctx context.Context) error {\n\tmounted, err := c.floppyImageMounted(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !mounted {\n\t\treturn nil\n\t}\n\n\tresp, statusCode, err := c.serviceClient.query(\n\t\tctx,\n\t\t\"cgi/uimapout.cgi\",\n\t\thttp.MethodPost,\n\t\tnil,\n\t\tnil,\n\t\t0,\n\t)\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif statusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"non 200 response: %d %s\", statusCode, resp)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "providers/supermicro/supermicro.go",
    "content": "package supermicro\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/x509\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"net/url\"\n\t\"os\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/constants\"\n\t\"github.com/bmc-toolbox/bmclib/v2/internal/httpclient\"\n\t\"github.com/bmc-toolbox/bmclib/v2/internal/redfishwrapper\"\n\t\"github.com/bmc-toolbox/bmclib/v2/internal/sum\"\n\t\"github.com/bmc-toolbox/bmclib/v2/providers\"\n\t\"github.com/bmc-toolbox/common\"\n\t\"github.com/stmcginnis/gofish/schemas\"\n\n\t\"github.com/go-logr/logr\"\n\t\"github.com/jacobweinstock/registrar\"\n\t\"github.com/pkg/errors\"\n\n\tbmclibconsts \"github.com/bmc-toolbox/bmclib/v2/constants\"\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n)\n\nconst (\n\t// ProviderName for the provider Supermicro implementation\n\tProviderName = \"supermicro\"\n\t// ProviderProtocol for the provider supermicro implementation\n\tProviderProtocol = \"vendorapi\"\n)\n\nvar (\n\t// Features implemented\n\tFeatures = registrar.Features{\n\t\tproviders.FeatureScreenshot,\n\t\tproviders.FeatureMountFloppyImage,\n\t\tproviders.FeatureUnmountFloppyImage,\n\t\tproviders.FeatureFirmwareUpload,\n\t\tproviders.FeatureFirmwareInstallUploaded,\n\t\tproviders.FeatureFirmwareTaskStatus,\n\t\tproviders.FeatureFirmwareInstallSteps,\n\t\tproviders.FeatureInventoryRead,\n\t\tproviders.FeaturePowerSet,\n\t\tproviders.FeaturePowerState,\n\t\tproviders.FeatureBmcReset,\n\t\tproviders.FeatureGetBiosConfiguration,\n\t\tproviders.FeatureSetBiosConfiguration,\n\t\tproviders.FeatureSetBiosConfigurationFromFile,\n\t\tproviders.FeatureResetBiosConfiguration,\n\t\tproviders.FeatureBootProgress,\n\t}\n)\n\n// supports\n//\n// product: SYS-5019C-MR, baseboard part number: X11SCM-F\n//   - screen capture\n//   - bios firmware install\n//   - bmc firmware install\n//\n// product: SYS-510T-MR, baseboard part number: X12STH-SYS, X12SPO-NTF\n//   - screen capture\n//   - floppy image mount\n// product: 6029P-E1CR12L, baseboard part number: X11DPH-T\n// . - screen capture\n//   - bios firmware install\n//   - bmc firmware install\n//   - floppy image mount\n\ntype Config struct {\n\tHttpClient           *http.Client\n\tPort                 string\n\thttpClientSetupFuncs []func(*http.Client)\n}\n\n// Option for setting optional Client values\ntype Option func(*Config)\n\nfunc WithHttpClient(httpClient *http.Client) Option {\n\treturn func(c *Config) {\n\t\tc.HttpClient = httpClient\n\t}\n}\n\n// WithSecureTLS returns an option that enables secure TLS with an optional cert pool.\nfunc WithSecureTLS(rootCAs *x509.CertPool) Option {\n\treturn func(c *Config) {\n\t\tc.httpClientSetupFuncs = append(c.httpClientSetupFuncs, httpclient.SecureTLSOption(rootCAs))\n\t}\n}\n\nfunc WithPort(port string) Option {\n\treturn func(c *Config) {\n\t\tc.Port = port\n\t}\n}\n\n// Connection details\ntype Client struct {\n\tserviceClient *serviceClient\n\tbmc           bmcQueryor\n\tlog           logr.Logger\n}\n\ntype bmcQueryor interface {\n\tfirmwareInstallSteps(component string) ([]constants.FirmwareInstallStep, error)\n\tfirmwareUpload(ctx context.Context, component string, file *os.File) (taskID string, err error)\n\tfirmwareInstallUploaded(ctx context.Context, component, uploadTaskID string) (installTaskID string, err error)\n\tfirmwareTaskStatus(ctx context.Context, component, taskID string) (state constants.TaskState, status string, err error)\n\t// query device model from the bmc\n\tqueryDeviceModel(ctx context.Context) (model string, err error)\n\t// returns the device model, that was queried previously with queryDeviceModel\n\tdeviceModel() (model string)\n\tsupportsInstall(component string) error\n\tgetBootProgress() (*schemas.BootProgress, error)\n\tbootComplete() (bool, error)\n}\n\n// New returns connection with a Supermicro client initialized\nfunc NewClient(host, user, pass string, log logr.Logger, opts ...Option) *Client {\n\tdefaultConfig := &Config{\n\t\tPort: \"443\",\n\t}\n\n\tfor _, opt := range opts {\n\t\topt(defaultConfig)\n\t}\n\n\tserviceClient := newBmcServiceClient(\n\t\thost,\n\t\tdefaultConfig.Port,\n\t\tuser,\n\t\tpass,\n\t\thttpclient.Build(defaultConfig.httpClientSetupFuncs...),\n\t)\n\n\treturn &Client{\n\t\tserviceClient: serviceClient,\n\t\tlog:           log,\n\t}\n}\n\nfunc (c *Client) login(ctx context.Context, encodeCreds bool) error {\n\tvar user, pass string\n\tif encodeCreds {\n\t\tuser = base64.StdEncoding.EncodeToString([]byte(c.serviceClient.user))\n\t\tpass = base64.StdEncoding.EncodeToString([]byte(c.serviceClient.pass))\n\t} else {\n\t\tuser = c.serviceClient.user\n\t\tpass = c.serviceClient.pass\n\t}\n\n\tdata := fmt.Sprintf(\n\t\t\"name=%s&pwd=%s&check=00\",\n\t\tuser,\n\t\tpass,\n\t)\n\n\theaders := map[string]string{\"Content-Type\": \"application/x-www-form-urlencoded\"}\n\n\tbody, status, err := c.serviceClient.query(ctx, \"cgi/login.cgi\", http.MethodPost, bytes.NewBufferString(data), headers, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif status != 200 {\n\t\treturn errors.Wrap(ErrUnexpectedStatusCode, strconv.Itoa(status))\n\t}\n\n\t// Older Supermicro boards return 200 for failed logins\n\tif !bytes.Contains(body, []byte(`url_redirect.cgi?url_name=mainmenu`)) &&\n\t\t!bytes.Contains(body, []byte(`url_redirect.cgi?url_name=topmenu`)) {\n\t\treturn ErrUnexpectedResponse\n\t}\n\n\treturn nil\n}\n\n// Open a connection to a Supermicro BMC using the vendor API.\nfunc (c *Client) Open(ctx context.Context) (err error) {\n\t// called after a session was opened but further login dependencies failed\n\tcloseWithError := func(ctx context.Context, err error) error {\n\t\t_ = c.Close(ctx)\n\t\treturn err\n\t}\n\n\t// first attempt login with base64 encoded user,pass\n\tif err := c.login(ctx, true); err != nil {\n\t\tif !errors.Is(err, ErrUnexpectedResponse) && !errors.Is(err, ErrUnexpectedStatusCode) {\n\t\t\treturn closeWithError(ctx, errors.Wrap(bmclibErrs.ErrLoginFailed, err.Error()))\n\t\t}\n\n\t\t// retry with plain text user, pass\n\t\tif err2 := c.login(ctx, false); err2 != nil {\n\t\t\treturn closeWithError(ctx, errors.Wrap(bmclibErrs.ErrLoginFailed, err2.Error()))\n\t\t}\n\t}\n\n\tcontentsTopMenu, status, err := c.serviceClient.query(ctx, \"cgi/url_redirect.cgi?url_name=topmenu\", http.MethodGet, nil, nil, 0)\n\tif err != nil {\n\t\treturn closeWithError(ctx, errors.Wrap(bmclibErrs.ErrLoginFailed, err.Error()))\n\t}\n\n\tif status != 200 {\n\t\treturn closeWithError(ctx, errors.Wrap(bmclibErrs.ErrLoginFailed, strconv.Itoa(status)))\n\t}\n\n\t// Note: older firmware version on the X11s don't use a CSRF token\n\t// so here theres no explicit requirement for it to be found.\n\t//\n\t// X11DPH-T 01.71.11 10/25/2019\n\tcsrfToken := parseToken(contentsTopMenu)\n\tc.serviceClient.setCsrfToken(csrfToken)\n\n\tc.bmc, err = c.bmcQueryor(ctx)\n\tif err != nil {\n\t\treturn closeWithError(ctx, errors.Wrap(bmclibErrs.ErrLoginFailed, err.Error()))\n\t}\n\n\tif err := c.serviceClient.redfishSession(ctx); err != nil {\n\t\treturn closeWithError(ctx, errors.Wrap(bmclibErrs.ErrLoginFailed, err.Error()))\n\t}\n\n\treturn nil\n}\n\n// PowerStateGet gets the power state of a BMC machine\nfunc (c *Client) PowerStateGet(ctx context.Context) (state string, err error) {\n\tif c.serviceClient == nil || c.serviceClient.redfish == nil {\n\t\treturn \"\", errors.Wrap(bmclibErrs.ErrLoginFailed, \"client not initialized\")\n\t}\n\n\treturn c.serviceClient.redfish.SystemPowerStatus(ctx)\n}\n\n// PowerSet sets the power state of a server\nfunc (c *Client) PowerSet(ctx context.Context, state string) (ok bool, err error) {\n\tif c.serviceClient == nil || c.serviceClient.redfish == nil {\n\t\treturn false, errors.Wrap(bmclibErrs.ErrLoginFailed, \"client not initialized\")\n\t}\n\n\treturn c.serviceClient.redfish.PowerSet(ctx, state)\n}\n\n// BmcReset power cycles the BMC\nfunc (c *Client) BmcReset(ctx context.Context, resetType string) (ok bool, err error) {\n\tif c.serviceClient == nil || c.serviceClient.redfish == nil {\n\t\treturn false, errors.Wrap(bmclibErrs.ErrLoginFailed, \"client not initialized\")\n\t}\n\n\treturn c.serviceClient.redfish.BMCReset(ctx, resetType)\n}\n\n// Inventory collects hardware inventory and install firmware information\nfunc (c *Client) Inventory(ctx context.Context) (device *common.Device, err error) {\n\tif c.serviceClient == nil || c.serviceClient.redfish == nil {\n\t\treturn nil, errors.Wrap(bmclibErrs.ErrLoginFailed, \"client not initialized\")\n\t}\n\n\treturn c.serviceClient.redfish.Inventory(ctx, false)\n}\n\n// GetBiosConfiguration return bios configuration\nfunc (c *Client) GetBiosConfiguration(ctx context.Context) (biosConfig map[string]string, err error) {\n\tif c.serviceClient == nil || c.serviceClient.sum == nil {\n\t\treturn nil, errors.Wrap(bmclibErrs.ErrLoginFailed, \"client not initialized\")\n\t}\n\n\treturn c.serviceClient.sum.GetBiosConfiguration(ctx)\n}\n\n// SetBiosConfiguration set bios configuration\nfunc (c *Client) SetBiosConfiguration(ctx context.Context, biosConfig map[string]string) (err error) {\n\tif c.serviceClient == nil || c.serviceClient.sum == nil {\n\t\treturn errors.Wrap(bmclibErrs.ErrLoginFailed, \"client not initialized\")\n\t}\n\n\treturn c.serviceClient.sum.SetBiosConfiguration(ctx, biosConfig)\n}\n\n// SetBiosConfigurationFromFile sets the bios configuration from a raw vendor config file\nfunc (c *Client) SetBiosConfigurationFromFile(ctx context.Context, cfg string) (err error) {\n\tif c.serviceClient == nil || c.serviceClient.sum == nil {\n\t\treturn errors.Wrap(bmclibErrs.ErrLoginFailed, \"client not initialized\")\n\t}\n\n\treturn c.serviceClient.sum.SetBiosConfigurationFromFile(ctx, cfg)\n}\n\n// ResetBiosConfiguration sets the bios configuration back to \"factory\" defaults\nfunc (c *Client) ResetBiosConfiguration(ctx context.Context) (err error) {\n\tif c.serviceClient == nil || c.serviceClient.sum == nil {\n\t\treturn errors.Wrap(bmclibErrs.ErrLoginFailed, \"client not initialized\")\n\t}\n\n\treturn c.serviceClient.sum.ResetBiosConfiguration(ctx)\n}\n\nfunc (c *Client) bmcQueryor(ctx context.Context) (bmcQueryor, error) {\n\tx11 := newX11Client(c.serviceClient, c.log)\n\tx12 := newX12Client(c.serviceClient, c.log)\n\n\tvar queryor bmcQueryor\n\n\tfor _, bmc := range []bmcQueryor{x11, x12} {\n\t\tvar err error\n\n\t\t// Note to maintainers: x12 lacks support for the ipmi.cgi endpoint,\n\t\t// which will lead to our graceful handling of ErrXMLAPIUnsupported below.\n\t\t_, err = bmc.queryDeviceModel(ctx)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, ErrXMLAPIUnsupported) {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treturn nil, errors.Wrap(ErrModelUnknown, err.Error())\n\t\t}\n\n\t\tqueryor = bmc\n\t\tbreak\n\t}\n\n\tif queryor == nil {\n\t\treturn nil, errors.Wrap(ErrModelUnknown, \"failed to setup query client\")\n\t}\n\n\tmodel := strings.ToLower(queryor.deviceModel())\n\tif !strings.HasPrefix(model, \"x12\") && !strings.HasPrefix(model, \"x11\") {\n\t\treturn nil, errors.Wrap(ErrModelUnsupported, \"expected one of X11* or X12*, got:\"+model)\n\t}\n\n\treturn queryor, nil\n}\n\nfunc parseToken(body []byte) string {\n\tvar key string\n\tif bytes.Contains(body, []byte(`CSRF-TOKEN`)) {\n\t\tkey = \"CSRF-TOKEN\"\n\t}\n\n\tif bytes.Contains(body, []byte(`CSRF_TOKEN`)) {\n\t\tkey = \"CSRF_TOKEN\"\n\t}\n\n\tif key == \"\" {\n\t\treturn \"\"\n\t}\n\n\tre, err := regexp.Compile(fmt.Sprintf(`\"%s\", \"(?P<token>.*)\"`, key))\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\n\tfound := re.FindSubmatch(body)\n\tif len(found) == 0 {\n\t\treturn \"\"\n\t}\n\n\treturn string(found[1])\n}\n\n// Close a connection to a Supermicro BMC using the vendor API.\nfunc (c *Client) Close(ctx context.Context) error {\n\tif c.serviceClient.client == nil {\n\t\treturn nil\n\t}\n\n\t_, status, err := c.serviceClient.query(ctx, \"cgi/logout.cgi\", http.MethodGet, nil, nil, 0)\n\tif err != nil {\n\t\treturn errors.Wrap(bmclibErrs.ErrLogoutFailed, err.Error())\n\t}\n\n\tif status != 200 {\n\t\treturn errors.Wrap(bmclibErrs.ErrLogoutFailed, strconv.Itoa(status))\n\t}\n\n\tif c.serviceClient.redfish != nil {\n\t\terr = c.serviceClient.redfish.Close(ctx)\n\t\tif err != nil {\n\t\t\treturn errors.Wrap(bmclibErrs.ErrLogoutFailed, err.Error())\n\t\t}\n\n\t\tc.serviceClient.redfish = nil\n\t}\n\n\treturn nil\n}\n\n// Name returns the client provider name.\nfunc (c *Client) Name() string {\n\treturn ProviderName\n}\n\nfunc (c *Client) Screenshot(ctx context.Context) (image []byte, fileType string, err error) {\n\tfileType = \"jpg\"\n\n\t// request screen preview to be saved\n\tif err := c.initScreenPreview(ctx); err != nil {\n\t\treturn nil, \"\", err\n\t}\n\n\t// give the bmc a few seconds to store the screen preview\n\ttime.Sleep(2 * time.Second)\n\n\t// retrieve screen preview\n\timage, errFetch := c.fetchScreenPreview(ctx)\n\tif errFetch != nil {\n\t\treturn nil, \"\", err\n\t}\n\n\treturn image, fileType, nil\n}\n\nfunc (c *Client) fetchScreenPreview(ctx context.Context) ([]byte, error) {\n\theaders := map[string]string{\"Content-Type\": \"application/x-www-form-urlencoded\"}\n\n\tendpoint := \"cgi/url_redirect.cgi?url_name=Snapshot&url_type=img\"\n\tbody, status, err := c.serviceClient.query(ctx, endpoint, http.MethodGet, nil, headers, 0)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(bmclibErrs.ErrScreenshot, strconv.Itoa(status))\n\t}\n\n\tif status != 200 {\n\t\treturn nil, errors.Wrap(bmclibErrs.ErrScreenshot, strconv.Itoa(status))\n\t}\n\n\treturn body, nil\n}\n\nfunc (c *Client) initScreenPreview(ctx context.Context) error {\n\theaders := map[string]string{\"Content-Type\": \"application/x-www-form-urlencoded\"}\n\n\tdata := \"op=sys_preview&_=\"\n\n\tbody, status, err := c.serviceClient.query(ctx, \"cgi/op.cgi\", http.MethodPost, bytes.NewBufferString(data), headers, 0)\n\tif err != nil {\n\t\treturn errors.Wrap(bmclibErrs.ErrScreenshot, err.Error())\n\t}\n\n\tif status != 200 {\n\t\treturn errors.Wrap(bmclibErrs.ErrScreenshot, strconv.Itoa(status))\n\t}\n\n\tif !bytes.Contains(body, []byte(`<IPMI>`)) {\n\t\treturn errors.Wrap(bmclibErrs.ErrScreenshot, \"unexpected response: \"+string(body))\n\t}\n\n\treturn nil\n}\n\ntype serviceClient struct {\n\thost      string\n\tport      string\n\tuser      string\n\tpass      string\n\tcsrfToken string\n\tclient    *http.Client\n\tredfish   *redfishwrapper.Client\n\tsum       *sum.Sum\n}\n\nfunc newBmcServiceClient(host, port, user, pass string, client *http.Client) *serviceClient {\n\tsc := &serviceClient{host: host, port: port, user: user, pass: pass, client: client}\n\n\tif !strings.HasPrefix(host, \"https://\") && !strings.HasPrefix(host, \"http://\") {\n\t\tsc.host = \"https://\" + host\n\t}\n\n\t// sum is only for firmware related operations. Failing the client entirely because of a sum error\n\t// means all the other functionality is not available. I don't think that is what we want. So, instead\n\t// of failing the function will just set sc.sum to nil. There are checks in place in this package to\n\t// handle sc.sum being nil. The tradeoff here is that the reason that sum failed, which from the current\n\t// code is only if the `sum` binary is not found, is not returned to the caller. So if firmware operations\n\t// are not working, binary not found is the reason.\n\ts, err := sum.New(host, user, pass)\n\tif err != nil {\n\t\tsc.sum = nil\n\t} else {\n\t\tsc.sum = s\n\t}\n\n\treturn sc\n}\n\nfunc (c *serviceClient) setCsrfToken(t string) {\n\tc.csrfToken = t\n}\n\nfunc (c *serviceClient) redfishSession(ctx context.Context) (err error) {\n\tif c.redfish != nil && c.redfish.SessionActive() == nil {\n\t\treturn nil\n\t}\n\n\tc.redfish = redfishwrapper.NewClient(\n\t\tc.host,\n\t\tc.port,\n\t\tc.user,\n\t\tc.pass,\n\t\tredfishwrapper.WithHTTPClient(c.client),\n\t)\n\tif err := c.redfish.Open(ctx); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *serviceClient) supportsFirmwareInstall(model string) error {\n\tif model == \"\" {\n\t\treturn errors.Wrap(ErrModelUnknown, \"unable to determine firmware install compatibility\")\n\t}\n\n\tfor _, s := range supportedModels {\n\t\tif strings.EqualFold(s, model) {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\treturn errors.Wrap(ErrModelUnsupported, \"firmware install not supported for: \"+model)\n}\n\nfunc (c *serviceClient) query(ctx context.Context, endpoint, method string, payload io.Reader, headers map[string]string, contentLength int64) ([]byte, int, error) {\n\tvar body []byte\n\tvar err error\n\tvar req *http.Request\n\n\thost := c.host\n\n\tif c.port != \"\" {\n\t\thost = c.host + \":\" + c.port\n\t}\n\n\thostEndpoint := fmt.Sprintf(\"%s/%s\", host, endpoint)\n\n\treq, err = http.NewRequestWithContext(ctx, method, hostEndpoint, payload)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\tif c.csrfToken != \"\" {\n\t\treq.Header.Add(\"Csrf-Token\", c.csrfToken)\n\t\t// because old firmware\n\t\treq.Header.Add(\"CSRF_TOKEN\", c.csrfToken)\n\t}\n\n\t// required on  X11SCM-F with 1.23.06 and older BMC firmware\n\t// https://go.googlesource.com/go/+/go1.20/src/net/http/request.go#124\n\treq.Host, err = hostIP(c.host)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\t// required on  X11SCM-F with 1.23.06 and older BMC firmware\n\treq.Header.Add(\"Referer\", c.host)\n\n\tfor k, v := range headers {\n\t\treq.Header.Add(k, v)\n\t}\n\n\t// Content-Length headers are ignored, unless defined in this manner\n\t// https://go.googlesource.com/go/+/go1.20/src/net/http/request.go#165\n\t// https://go.googlesource.com/go/+/go1.20/src/net/http/request.go#91\n\tif contentLength > 0 {\n\t\treq.ContentLength = contentLength\n\t}\n\n\tendpointURL, err := url.Parse(hostEndpoint)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\n\t// include session cookie\n\tfor _, cookie := range c.client.Jar.Cookies(endpointURL) {\n\t\tif cookie.Name == \"SID\" && cookie.Value != \"\" {\n\t\t\treq.AddCookie(cookie)\n\t\t}\n\n\t}\n\n\tvar reqDump []byte\n\n\tif os.Getenv(bmclibconsts.EnvEnableDebug) == \"true\" {\n\t\treqDump, _ = httputil.DumpRequestOut(req, true)\n\t}\n\n\tresp, err := c.client.Do(req)\n\tif err != nil {\n\t\treturn body, 0, err\n\t}\n\n\t// cookies are visible after the request has been made, so we dump the request and cookies here\n\t// https://github.com/golang/go/issues/22745\n\tif os.Getenv(bmclibconsts.EnvEnableDebug) == \"true\" {\n\t\tfmt.Println(string(reqDump))\n\n\t\tfor _, v := range req.Cookies() {\n\t\t\theader := \"Cookie: \" + v.String() + \"\\r\"\n\t\t\tfmt.Println(header)\n\t\t}\n\t}\n\n\t// debug dump response\n\tif os.Getenv(bmclibconsts.EnvEnableDebug) == \"true\" {\n\t\trespDump, _ := httputil.DumpResponse(resp, true)\n\n\t\tfmt.Println(string(respDump))\n\t}\n\n\tbody, err = io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn body, 0, err\n\t}\n\n\tdefer resp.Body.Close()\n\n\treturn body, resp.StatusCode, nil\n}\n\nfunc hostIP(hostURL string) (string, error) {\n\thostURLParsed, err := url.Parse(hostURL)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif strings.Contains(hostURLParsed.Host, \":\") {\n\t\treturn strings.Split(hostURLParsed.Host, \":\")[0], nil\n\n\t}\n\n\treturn hostURLParsed.Host, nil\n}\n\n// SendNMI tells the BMC to issue an NMI to the device\nfunc (c *Client) SendNMI(ctx context.Context) error {\n\treturn c.serviceClient.redfish.SendNMI(ctx)\n}\n\n// GetBootProgress allows a caller to follow along as the system goes through its boot sequence\nfunc (c *Client) GetBootProgress() (*schemas.BootProgress, error) {\n\treturn c.bmc.getBootProgress()\n}\n\n// BootComplete checks if this system has reached the last state for boot\nfunc (c *Client) BootComplete() (bool, error) {\n\treturn c.bmc.bootComplete()\n}\n"
  },
  {
    "path": "providers/supermicro/supermicro_test.go",
    "content": "package supermicro\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/internal/redfishwrapper\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nconst (\n\tfixturesDir = \"./fixtures\"\n)\n\nfunc TestParseToken(t *testing.T) {\n\ttestcases := []struct {\n\t\tname        string\n\t\tbody        []byte\n\t\texpectToken string\n\t}{\n\t\t{\n\t\t\t\"token with key type 1 found\",\n\t\t\t[]byte(`<script>SmcCsrfInsert (\"CSRF-TOKEN\", \"A0v9gild518yF36XZ6jqNZNsOUrHiEpkvM+QHKKVTFw\");/*SmcCsrfInsert (\"CSRF_TOKEN\", \"A0v9gild518yF36XZ6jqNZNsOUrHiEpkvM+QHKKVTFw\");*/</script></body>`),\n\t\t\t\"A0v9gild518yF36XZ6jqNZNsOUrHiEpkvM+QHKKVTFw\",\n\t\t},\n\t\t{\n\t\t\t\"token with key type 2 found\",\n\t\t\t[]byte(`                </td>\n            </tr>\n        </table>\n        </div>\n    <script>SmcCsrfInsert (\"CSRF_TOKEN\", \"Te6xHPx3NDhDmL4T21cZ/tXWbzatZQ3JHbQUCF5Hkns\");</script></body>\n</html>\n`),\n\t\t\t\"Te6xHPx3NDhDmL4T21cZ/tXWbzatZQ3JHbQUCF5Hkns\",\n\t\t},\n\t\t{\n\t\t\t\"token with key type 3 found\",\n\t\t\t[]byte(`</script>\n\t\t\t<CSRF_TOKEN>\n\t\t\t<input type=\"hidden\" name=\"initName\" id=\"initName\"/>\n\t\t\t<div id=\"refreshTag\"></div>\n\t\t\t<script>SmcCsrfInsert (\"CSRF_TOKEN\", \"fYQ/Xhd1AvA+kP/bM/tO5mhOzv4eM5evCOH/YSuBN70\");</script></body>\n\t\t\t</html>`),\n\t\t\t\"fYQ/Xhd1AvA+kP/bM/tO5mhOzv4eM5evCOH/YSuBN70\",\n\t\t},\n\t\t{\n\t\t\t\"token with key type 4 found\",\n\t\t\t[]byte(`<script>SmcCsrfInsert (\"CSRF_TOKEN\", \"RYjdEjWIhU+PCRFMBP2ZRPPePcQ4n3dM3s+rCgTnBBU\");</script></body>`),\n\t\t\t\"RYjdEjWIhU+PCRFMBP2ZRPPePcQ4n3dM3s+rCgTnBBU\",\n\t\t},\n\t\t{\n\t\t\t\"token with key type 5 found\",\n\t\t\t[]byte(`<script>SmcCsrfInsert (\"CSRF-TOKEN\", \"RYjdEjWIhU+PCRFMBP2ZRPPePcQ4n3dM3s+rCgTnBBU\");</script></body>`),\n\t\t\t\"RYjdEjWIhU+PCRFMBP2ZRPPePcQ4n3dM3s+rCgTnBBU\",\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgotToken := parseToken(tc.body)\n\t\t\tassert.Equal(t, tc.expectToken, gotToken)\n\n\t\t})\n\t}\n}\n\nfunc mustReadFile(t *testing.T, filename string) []byte {\n\tt.Helper()\n\n\tfixture := fixturesDir + \"/\" + filename\n\tfh, err := os.Open(fixture)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tdefer fh.Close()\n\n\tb, err := io.ReadAll(fh)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\treturn b\n}\n\nvar endpointFunc = func(t *testing.T, file string) http.HandlerFunc {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\t// expect either GET or Delete methods\n\t\tif r.Method != http.MethodGet && r.Method != http.MethodPost && r.Method != http.MethodDelete {\n\t\t\tw.WriteHeader(http.StatusNotFound)\n\t\t\treturn\n\t\t}\n\n\t\t_, _ = w.Write(mustReadFile(t, file))\n\t}\n}\n\nfunc TestOpen(t *testing.T) {\n\ttype handlerFuncMap map[string]func(http.ResponseWriter, *http.Request)\n\ttestcases := []struct {\n\t\tname           string\n\t\terrorContains  string\n\t\tuser           string\n\t\tpass           string\n\t\thandlerFuncMap handlerFuncMap\n\t}{\n\t\t{\n\t\t\t\"happy path\",\n\t\t\t\"\",\n\t\t\t\"foo\",\n\t\t\t\"bar\",\n\t\t\thandlerFuncMap{\n\t\t\t\t\"/\": func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\t\t},\n\t\t\t\t\"/redfish/v1/\": endpointFunc(t, \"serviceroot.json\"),\n\t\t\t\t// first request to login\n\t\t\t\t\"/cgi/login.cgi\": func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t\tassert.Equal(t, r.Method, http.MethodPost)\n\t\t\t\t\tassert.Equal(t, \"application/x-www-form-urlencoded\", r.Header.Get(\"Content-Type\"))\n\n\t\t\t\t\tb, err := io.ReadAll(r.Body)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Fatal(err)\n\t\t\t\t\t}\n\n\t\t\t\t\tassert.Equal(t, `name=Zm9v&pwd=YmFy&check=00`, string(b))\n\n\t\t\t\t\tresponse := []byte(`<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n\t\t\t\t<html xmlns=\"http://www.w3.org/1999/xhtml\">\n\t\t\t\t<head>\n\t\t\t\t\t<META http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n\t\t\t\t\t<META HTTP-EQUIV=\"Pragma\" CONTENT=\"no_cache\">\n\t\t\t\t\t<META NAME=\"ATEN International Co Ltd.\" CONTENT=\"(c) ATEN International Co Ltd. 2010\">\n\t\t\t\t\t<title></title>\n\t\t\t\t\t<script language=\"JavaScript\" type=\"text/javascript\">\n\t\t\t\t<!--\n\t\t\t\t\tself.location = \"../cgi/url_redirect.cgi?url_name=mainmenu\";\n\t\t\t\t-->\n\t\t\t\t\t</script>\n\t\t\t\t</head>\n\t\t\t\t<body>\n\t\t\t\t</body>\n\t\t\t\t</html>`)\n\t\t\t\t\t_, _ = w.Write(response)\n\t\t\t\t},\n\n\t\t\t\t// second request for the csrf token\n\t\t\t\t\"/cgi/url_redirect.cgi\": func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t\tassert.Equal(t, r.Method, http.MethodGet)\n\t\t\t\t\tassert.Equal(t, \"url_name=topmenu\", r.URL.RawQuery)\n\t\t\t\t\t_, err := io.ReadAll(r.Body)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Fatal(err)\n\t\t\t\t\t}\n\n\t\t\t\t\tresponse := []byte(`<script>SmcCsrfInsert (\"CSRF-TOKEN\", \"A0v9gild518yF36XZ6jqNZNsOUrHiEpkvM+QHKKVTFw\");/*SmcCsrfInsert (\"CSRF_TOKEN\", \"A0v9gild518yF36XZ6jqNZNsOUrHiEpkvM+QHKKVTFw\");*/</script></body>`)\n\t\t\t\t\t_, _ = w.Write(response)\n\t\t\t\t},\n\t\t\t\t// request for model\n\t\t\t\t\"/cgi/ipmi.cgi\": func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t\tassert.Equal(t, r.Method, http.MethodPost)\n\t\t\t\t\tassert.Equal(t, \"application/x-www-form-urlencoded; charset=UTF-8\", r.Header.Get(\"Content-Type\"))\n\n\t\t\t\t\tb, err := io.ReadAll(r.Body)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Fatal(err)\n\t\t\t\t\t}\n\n\t\t\t\t\tassert.Equal(t, `op=FRU_INFO.XML&r=(0,0)&_=`, string(b))\n\n\t\t\t\t\t_, _ = w.Write([]byte(`<IPMI>\n\t\t\t\t\t<FRU_INFO>\n\t\t\t\t\t  <BOARD MFC_NAME=\"SMC\" PART_NUM=\"X11SCM-F\" PROD_NAME=\"TestProduct\" SERIAL_NUM=\"789012345\" />\n\t\t\t\t\t</FRU_INFO>\n\t\t\t\t  </IPMI>`))\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"login error\",\n\t\t\t\"failed to login\",\n\t\t\t\"foo\",\n\t\t\t\"bar\",\n\t\t\thandlerFuncMap{\n\t\t\t\t\"/cgi/login.cgi\": func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\t\tassert.Equal(t, r.Method, http.MethodPost)\n\t\t\t\t\tassert.Equal(t, r.Header.Get(\"Content-Type\"), \"application/x-www-form-urlencoded\")\n\t\t\t\t\tresponse := []byte(`barf`)\n\t\t\t\t\tw.WriteHeader(401)\n\t\t\t\t\t_, _ = w.Write(response)\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmux := http.NewServeMux()\n\t\t\tfor endpoint, handler := range tc.handlerFuncMap {\n\t\t\t\tmux.HandleFunc(endpoint, handler)\n\t\t\t}\n\n\t\t\tserver := httptest.NewTLSServer(mux)\n\t\t\tdefer server.Close()\n\n\t\t\tserver.Config.ErrorLog = log.New(os.Stdout, \"foo\", 3)\n\t\t\tparsedURL, err := url.Parse(server.URL)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tclient := NewClient(parsedURL.Hostname(), tc.user, tc.pass, logr.Discard(), WithPort(parsedURL.Port()))\n\t\t\tclient.serviceClient.redfish = redfishwrapper.NewClient(\n\t\t\t\tparsedURL.Hostname(),\n\t\t\t\tparsedURL.Port(),\n\t\t\t\ttc.user,\n\t\t\t\ttc.pass,\n\t\t\t\tredfishwrapper.WithHTTPClient(client.serviceClient.client),\n\t\t\t)\n\n\t\t\terr = client.Open(context.Background())\n\t\t\tif tc.errorContains != \"\" {\n\t\t\t\tassert.ErrorContains(t, err, tc.errorContains)\n\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tassert.Nil(t, err)\n\t\t})\n\t}\n\n}\n\nfunc TestClose(t *testing.T) {\n\ttestcases := []struct {\n\t\tname          string\n\t\terrorContains string\n\t\tuser          string\n\t\tpass          string\n\t\tendpoint      string\n\t\thandler       func(http.ResponseWriter, *http.Request)\n\t}{\n\t\t{\n\t\t\t\"happy path\",\n\t\t\t\"\",\n\t\t\t\"foo\",\n\t\t\t\"bar\",\n\t\t\t\"/cgi/logout.cgi\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tassert.Equal(t, r.Method, http.MethodGet)\n\n\t\t\t\t_, err := io.ReadAll(r.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tw.WriteHeader(200)\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmux := http.NewServeMux()\n\t\t\tmux.HandleFunc(tc.endpoint, tc.handler)\n\n\t\t\tserver := httptest.NewTLSServer(mux)\n\t\t\tdefer server.Close()\n\n\t\t\tparsedURL, err := url.Parse(server.URL)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tclient := NewClient(parsedURL.Hostname(), tc.user, tc.pass, logr.Discard(), WithPort(parsedURL.Port()))\n\t\t\terr = client.Close(context.Background())\n\t\t\tif tc.errorContains != \"\" {\n\t\t\t\tassert.ErrorContains(t, err, tc.errorContains)\n\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tassert.Nil(t, err)\n\t\t\tassert.Nil(t, client.serviceClient.redfish)\n\t\t})\n\t}\n\n}\n\nfunc TestInitScreenPreview(t *testing.T) {\n\ttestcases := []struct {\n\t\tname          string\n\t\terrorContains string\n\t\tendpoint      string\n\t\thandler       func(http.ResponseWriter, *http.Request)\n\t}{\n\t\t{\n\t\t\t\"happy path\",\n\t\t\t\"\",\n\t\t\t\"/cgi/op.cgi\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tassert.Equal(t, r.Method, http.MethodPost)\n\t\t\t\tassert.Equal(t, r.Header.Get(\"Content-Type\"), \"application/x-www-form-urlencoded\")\n\n\t\t\t\tb, err := io.ReadAll(r.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tassert.Equal(t, `op=sys_preview&_=`, string(b))\n\n\t\t\t\t_, _ = w.Write([]byte(`<?xml version=\"1.0\" ?>\n\t\t\t\t<IPMI>\n\t\t\t\t</IPMI>`))\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"error returned\",\n\t\t\t\"400\",\n\t\t\t\"/cgi/op.cgi\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tw.WriteHeader(400)\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmux := http.NewServeMux()\n\t\t\tmux.HandleFunc(tc.endpoint, tc.handler)\n\n\t\t\tserver := httptest.NewTLSServer(mux)\n\t\t\tdefer server.Close()\n\n\t\t\tparsedURL, err := url.Parse(server.URL)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tclient := NewClient(parsedURL.Hostname(), \"foo\", \"bar\", logr.Discard(), WithPort(parsedURL.Port()))\n\t\t\terr = client.initScreenPreview(context.Background())\n\t\t\tif tc.errorContains != \"\" {\n\t\t\t\tassert.ErrorContains(t, err, tc.errorContains)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tassert.Nil(t, err)\n\n\t\t})\n\t}\n}\n\nfunc TestFetchScreenPreview(t *testing.T) {\n\ttestcases := []struct {\n\t\tname          string\n\t\texpectImage   []byte\n\t\terrorContains string\n\t\tendpoint      string\n\t\thandler       func(http.ResponseWriter, *http.Request)\n\t}{\n\t\t{\n\t\t\t\"happy path\",\n\t\t\t[]byte(`fake image is fake`),\n\t\t\t\"\",\n\t\t\t\"/cgi/url_redirect.cgi\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tassert.Equal(t, r.Method, http.MethodGet)\n\t\t\t\tassert.Equal(t, r.Header.Get(\"Content-Type\"), \"application/x-www-form-urlencoded\")\n\t\t\t\tassert.Equal(t, \"url_name=Snapshot&url_type=img\", r.URL.RawQuery)\n\n\t\t\t\t_, _ = w.Write([]byte(`fake image is fake`))\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"error returned\",\n\t\t\tnil,\n\t\t\t\"400\",\n\t\t\t\"/cgi/url_redirect.cgi\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tw.WriteHeader(400)\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmux := http.NewServeMux()\n\t\t\tmux.HandleFunc(tc.endpoint, tc.handler)\n\n\t\t\tserver := httptest.NewTLSServer(mux)\n\t\t\tdefer server.Close()\n\n\t\t\tparsedURL, err := url.Parse(server.URL)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tclient := NewClient(parsedURL.Hostname(), \"foo\", \"bar\", logr.Discard(), WithPort(parsedURL.Port()))\n\n\t\t\timage, err := client.fetchScreenPreview(context.Background())\n\t\t\tif tc.errorContains != \"\" {\n\t\t\t\tassert.ErrorContains(t, err, tc.errorContains)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tassert.Nil(t, err)\n\t\t\tassert.Equal(t, tc.expectImage, image)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "providers/supermicro/types.go",
    "content": "package supermicro\n\ntype IPMI struct {\n\tFruInfo *FruInfo `xml:\"FRU_INFO,omitempty\"`\n}\n\n// FruInfo contains the FRU information\ntype FruInfo struct {\n\tBoard *Board `xml:\"BOARD,omitempty\"`\n}\n\n// Board contains the product baseboard information\ntype Board struct {\n\tMfcName   string `xml:\"MFC_NAME,attr\"`\n\tPartNum   string `xml:\"PART_NUM,attr\"`\n\tProdName  string `xml:\"PROD_NAME,attr\"`\n\tSerialNum string `xml:\"SERIAL_NUM,attr\"`\n}\n"
  },
  {
    "path": "providers/supermicro/x11.go",
    "content": "package supermicro\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/xml\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/constants\"\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/bmc-toolbox/common\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/stmcginnis/gofish/oem/smc\"\n\t\"github.com/stmcginnis/gofish/schemas\"\n)\n\ntype x11 struct {\n\t*serviceClient\n\tmodel string\n\tlog   logr.Logger\n}\n\nfunc newX11Client(client *serviceClient, logger logr.Logger) bmcQueryor {\n\treturn &x11{\n\t\tserviceClient: client,\n\t\tlog:           logger,\n\t}\n}\n\nfunc (c *x11) deviceModel() string {\n\treturn c.model\n}\n\nfunc (c *x11) queryDeviceModel(ctx context.Context) (string, error) {\n\tmodel, err := c.deviceModelFromFruInfo(ctx)\n\tif err != nil {\n\t\t// Identify BoardID from Redfish since fru info failed to return the information\n\t\tmodel, err2 := c.deviceModelFromBoardID(ctx)\n\t\tif err2 != nil {\n\t\t\treturn \"\", errors.Wrap(err, err2.Error())\n\t\t}\n\n\t\tc.model = model\n\t\treturn model, nil\n\t}\n\n\tc.model = model\n\treturn model, nil\n}\n\nfunc (c *x11) deviceModelFromFruInfo(ctx context.Context) (string, error) {\n\terrBoardPartNumUnknown := errors.New(\"baseboard part number unknown\")\n\tdata, err := c.fruInfo(ctx)\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"404\") {\n\t\t\treturn \"\", ErrXMLAPIUnsupported\n\t\t}\n\n\t\treturn \"\", err\n\t}\n\n\tpartNum := strings.TrimSpace(data.Board.PartNum)\n\tif data.Board == nil || partNum == \"\" {\n\t\treturn \"\", errors.Wrap(errBoardPartNumUnknown, \"baseboard part number empty\")\n\t}\n\n\treturn common.FormatProductName(partNum), nil\n}\n\nfunc (c *x11) deviceModelFromBoardID(ctx context.Context) (string, error) {\n\tif err := c.redfishSession(ctx); err != nil {\n\t\treturn \"\", err\n\t}\n\n\tchassis, err := c.redfish.Chassis(ctx)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvar boardID string\n\tfor _, ch := range chassis {\n\t\tsmcChassis, err := smc.FromChassis(ch)\n\t\tif err != nil {\n\t\t\treturn \"\", errors.Wrap(ErrBoardIDUnknown, err.Error())\n\t\t}\n\n\t\tif smcChassis.BoardID != \"\" {\n\t\t\tboardID = smcChassis.BoardID\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif boardID == \"\" {\n\t\treturn \"\", ErrBoardIDUnknown\n\t}\n\n\tmodel := common.SupermicroModelFromBoardID(boardID)\n\tif model == \"\" {\n\t\treturn \"\", errors.Wrap(ErrModelUnknown, \"unable to identify model from board ID: \"+boardID)\n\t}\n\n\treturn model, nil\n}\n\nfunc (c *x11) fruInfo(ctx context.Context) (*FruInfo, error) {\n\theaders := map[string]string{\"Content-Type\": \"application/x-www-form-urlencoded; charset=UTF-8\"}\n\n\tpayload := \"op=FRU_INFO.XML&r=(0,0)&_=\"\n\n\tbody, status, err := c.query(ctx, \"cgi/ipmi.cgi\", http.MethodPost, bytes.NewBufferString(payload), headers, 0)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(ErrQueryFRUInfo, err.Error())\n\t}\n\n\tif status != 200 {\n\t\treturn nil, unexpectedResponseErr([]byte(payload), body, status)\n\t}\n\n\tif !bytes.Contains(body, []byte(`<IPMI>`)) {\n\t\treturn nil, unexpectedResponseErr([]byte(payload), body, status)\n\t}\n\n\tdata := &IPMI{}\n\tif err := xml.Unmarshal(body, data); err != nil {\n\t\treturn nil, errors.Wrap(ErrQueryFRUInfo, err.Error())\n\t}\n\n\treturn data.FruInfo, nil\n}\n\nfunc (c *x11) supportsInstall(component string) error {\n\terrComponentNotSupported := fmt.Errorf(\"component %s on device %s not supported\", component, c.model)\n\n\tsupported := []string{common.SlugBIOS, common.SlugBMC}\n\tif !slices.Contains(supported, strings.ToUpper(component)) {\n\t\treturn errComponentNotSupported\n\t}\n\n\treturn nil\n}\n\nfunc (c *x11) firmwareInstallSteps(component string) ([]constants.FirmwareInstallStep, error) {\n\tif err := c.supportsInstall(component); err != nil {\n\t\treturn nil, err\n\t}\n\n\tsteps := []constants.FirmwareInstallStep{\n\t\tconstants.FirmwareInstallStepUpload,\n\t\tconstants.FirmwareInstallStepInstallUploaded,\n\t\tconstants.FirmwareInstallStepInstallStatus,\n\t}\n\n\t// On a failure the X11 BMC has to be removed from the\n\t// flash mode - which is done through a BMC reset\n\tif strings.EqualFold(component, common.SlugBMC) {\n\t\tsteps = append(steps, constants.FirmwareInstallStepResetBMCOnInstallFailure)\n\t}\n\n\treturn steps, nil\n}\n\nfunc (c *x11) firmwareUpload(ctx context.Context, component string, file *os.File) (string, error) {\n\tcomponent = strings.ToUpper(component)\n\n\tswitch component {\n\tcase common.SlugBIOS:\n\t\treturn \"\", c.firmwareUploadBIOS(ctx, file)\n\tcase common.SlugBMC:\n\t\treturn \"\", c.firmwareUploadBMC(ctx, file)\n\t}\n\n\treturn \"\", errors.Wrap(bmclibErrs.ErrFirmwareInstall, \"component unsupported: \"+component)\n}\n\nfunc (c *x11) firmwareInstallUploaded(ctx context.Context, component, _ string) (string, error) {\n\tcomponent = strings.ToUpper(component)\n\n\tswitch component {\n\tcase common.SlugBIOS:\n\t\treturn \"\", c.firmwareInstallUploadedBIOS(ctx)\n\tcase common.SlugBMC:\n\t\treturn \"\", c.initiateBMCFirmwareInstall(ctx)\n\t}\n\n\treturn \"\", errors.Wrap(bmclibErrs.ErrFirmwareInstallUploaded, \"component unsupported: \"+component)\n}\n\nfunc (c *x11) firmwareTaskStatus(ctx context.Context, component, _ string) (state constants.TaskState, status string, err error) {\n\tcomponent = strings.ToUpper(component)\n\n\tswitch component {\n\tcase common.SlugBIOS:\n\t\treturn c.statusBIOSFirmwareInstall(ctx)\n\tcase common.SlugBMC:\n\t\treturn c.statusBMCFirmwareInstall(ctx)\n\t}\n\n\treturn \"\", \"\", errors.Wrap(bmclibErrs.ErrFirmwareTaskStatus, \"component unsupported: \"+component)\n}\n\nfunc (c *x11) getBootProgress() (*schemas.BootProgress, error) {\n\treturn nil, fmt.Errorf(\"%w: not supported on x11 models\", bmclibErrs.ErrRedfishVersionIncompatible)\n}\n\nfunc (c *x11) bootComplete() (bool, error) {\n\treturn false, fmt.Errorf(\"%w: not supported on x11 models\", bmclibErrs.ErrRedfishVersionIncompatible)\n}\n"
  },
  {
    "path": "providers/supermicro/x11_firmware_bios.go",
    "content": "package supermicro\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"net/textproto\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/constants\"\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\t\"github.com/pkg/errors\"\n)\n\nfunc (c *x11) firmwareUploadBIOS(ctx context.Context, reader io.Reader) error {\n\tvar err error\n\n\tc.log.V(2).Info(\"set firmware install mode\", \"ip\", c.host, \"component\", \"BIOS\", \"model\", c.model)\n\n\t// 0. pre flash mode requisite\n\tif err := c.checkComponentUpdateMisc(ctx, \"preUpdate\"); err != nil {\n\t\treturn err\n\t}\n\n\t// 1. set the device to flash mode - prepares the flash\n\terr = c.setBIOSFirmwareInstallMode(ctx)\n\tif err != nil {\n\t\treturn errors.Wrap(err, ErrFirmwareInstallMode.Error())\n\t}\n\n\terr = c.setBiosUpdateStart(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tc.log.V(2).Info(\"uploading firmware\", \"ip\", c.host, \"component\", \"BIOS\", \"model\", c.model)\n\n\t// 2. upload firmware image file\n\terr = c.uploadBIOSFirmware(ctx, reader)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tc.log.V(2).Info(\"verifying uploaded firmware\", \"ip\", c.host, \"component\", \"BIOS\", \"model\", c.model)\n\n\t// 3. BMC verifies the uploaded firmware version\n\treturn c.verifyBIOSFirmwareVersion(ctx)\n}\n\nfunc (c *x11) firmwareInstallUploadedBIOS(ctx context.Context) error {\n\tc.log.V(2).Info(\"initiating firmware install\", \"ip\", c.host, \"component\", \"BIOS\", \"model\", c.model)\n\n\t// pre install requisite\n\terr := c.setBIOSOp(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// 4. Run the firmware install process\n\treturn c.initiateBIOSFirmwareInstall(ctx)\n}\n\n// checks component update status\nfunc (c *x11) checkComponentUpdateMisc(ctx context.Context, stage string) error {\n\tvar payload, expectResponse []byte\n\n\tswitch stage {\n\tcase \"preUpdate\":\n\t\tpayload = []byte(`op=COMPONENT_UPDATE_MISC.XML&r=(0,0)&_=`)\n\t\t// RES=-1 indicates the BMC is not in BIOS update mode\n\t\texpectResponse = []byte(`<MISC_INFO RES=\"-1\" SYSOFF=\"0\"/>`)\n\n\tcase \"postUpdate\":\n\t\tpayload = []byte(`op=COMPONENT_UPDATE_MISC.XML&r=(1,0)&_=`)\n\t\t// RES=0 indicates the BMC is in BIOS update mode\n\t\texpectResponse = []byte(`<MISC_INFO RES=\"0\" SYSOFF=\"0\"/>`)\n\n\t// When SYSOFF=1 the system requires a power cycle\n\tdefault:\n\t\treturn errors.New(\"unknown stage: \" + stage)\n\n\t}\n\n\theaders := map[string]string{\n\t\t\"Content-type\": \"application/x-www-form-urlencoded; charset=UTF-8\",\n\t}\n\n\tbody, status, err := c.query(ctx, \"cgi/ipmi.cgi\", http.MethodPost, bytes.NewBuffer(payload), headers, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif status != http.StatusOK || !bytes.Contains(body, expectResponse) {\n\t\t// this indicates the BMC is in firmware update mode and now requires a reset\n\t\t// calling BIOS_UNLOCK.xml doesn't help here\n\t\tif stage == \"preUpdate\" && bytes.Contains(body, []byte(`<MISC_INFO RES=\"0\" SYSOFF=\"0\"/>`)) {\n\t\t\treturn bmclibErrs.ErrBMCColdResetRequired\n\t\t}\n\n\t\tif bytes.Contains(body, []byte(`<MISC_INFO RES=\"0\" SYSOFF=\"1\"/>`)) {\n\t\t\treturn bmclibErrs.ErrHostPowercycleRequired\n\t\t}\n\n\t\tif stage == \"postUpdate\" && bytes.Contains(body, []byte(`<html>`)) {\n\t\t\treturn bmclibErrs.ErrSessionExpired\n\t\t}\n\n\t\treturn unexpectedResponseErr(payload, body, status)\n\t}\n\n\treturn nil\n}\n\nfunc (c *x11) setBIOSFirmwareInstallMode(ctx context.Context) error {\n\n\tpayload := []byte(`op=BIOS_UPLOAD.XML&r=(0,0)&_=`)\n\n\theaders := map[string]string{\n\t\t\"Content-type\": \"application/x-www-form-urlencoded; charset=UTF-8\",\n\t}\n\n\tbody, status, err := c.query(ctx, \"cgi/ipmi.cgi\", http.MethodPost, bytes.NewBuffer(payload), headers, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif status != http.StatusOK {\n\t\treturn unexpectedResponseErr(payload, body, status)\n\t}\n\n\tswitch {\n\tcase bytes.Contains(body, []byte(`LOCK_FW_UPLOAD RES=\"0\"`)):\n\t\t// This response indicates another web session that initiated the firmware upload has the lock,\n\t\t// the BMC cannot be reset through a web session, nor can any other user obtain the firmware upload lock.\n\t\t// Since the firmware upload lock is associated with the cookie that initiated the request only the initiating session can cancel it.\n\t\t//\n\t\t// The only way to get out of this situation is through an IPMI (or redfish?) based BMC cold reset.\n\t\t///\n\t\t// The caller must check if a firmware update is in progress before proceeding with the reset.\n\t\t//\n\t\t// If after multiple calls to check the install progress - the progress seems stalled at 1%\n\t\t// it indicates no update was active, and the BMC can be reset.\n\t\t//\n\t\t// <IPMI><percent>1</percent></IPMI>\n\t\treturn errors.Wrap(\n\t\t\tbmclibErrs.ErrBMCColdResetRequired,\n\t\t\t\"firmware upload mode active, another session may have initiated an install\",\n\t\t)\n\n\tcase bytes.Contains(body, []byte(`LOCK_FW_UPLOAD RES=\"1\"`)):\n\t\treturn nil\n\tdefault:\n\t\treturn unexpectedResponseErr(payload, body, status)\n\t}\n\n}\n\nfunc (c *x11) setBiosUpdateStart(ctx context.Context) error {\n\tpayload := []byte(`op=BIOS_UPDATE_START.XML&r=(1,0)&_=`)\n\n\theaders := map[string]string{\n\t\t\"Content-type\": \"application/x-www-form-urlencoded; charset=UTF-8\",\n\t}\n\n\tbody, status, err := c.query(ctx, \"cgi/ipmi.cgi\", http.MethodPost, bytes.NewBuffer(payload), headers, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// yep, the endpoint returns 500 even when successful\n\tif status != http.StatusOK && status != 500 {\n\t\treturn unexpectedResponseErr(payload, body, status)\n\t}\n\n\treturn nil\n}\n\n// ------WebKitFormBoundaryXIAavwG4xzohdB6k\n// Content-Disposition: form-data; name=\"bios_rom\"; filename=\"BIOS_X11SCM-1B0F_20220916_1.9_STDsp.bin\"\n// Content-Type: application/macbinary\n//\n// ------WebKitFormBoundaryXIAavwG4xzohdB6k\n// Content-Disposition: form-data; name=\"CSRF-TOKEN\"\n//\n// OO8+cjamaZZOMf6ZiGDY3Lw+7O20r5lR8aI8ByuTo3E\n// ------WebKitFormBoundaryXIAavwG4xzohdB6k--\nfunc (c *x11) uploadBIOSFirmware(ctx context.Context, fwReader io.Reader) error {\n\tvar payloadBuffer bytes.Buffer\n\tvar err error\n\n\ttype form struct {\n\t\tname string\n\t\tdata io.Reader\n\t}\n\n\tformParts := []form{\n\t\t{\n\t\t\tname: \"bios_rom\",\n\t\t\tdata: fwReader,\n\t\t},\n\t}\n\n\tif c.csrfToken != \"\" {\n\t\tformParts = append(formParts, form{\n\t\t\tname: \"csrf-token\",\n\t\t\tdata: bytes.NewBufferString(c.csrfToken),\n\t\t})\n\t}\n\n\tpayloadWriter := multipart.NewWriter(&payloadBuffer)\n\n\tfor _, part := range formParts {\n\t\tvar partWriter io.Writer\n\n\t\tswitch part.name {\n\t\tcase \"bios_rom\":\n\t\t\tfile, ok := part.data.(*os.File)\n\t\t\tif !ok {\n\t\t\t\treturn errors.Wrap(ErrMultipartForm, \"expected io.Reader on firmware image file\")\n\t\t\t}\n\n\t\t\tif partWriter, err = payloadWriter.CreateFormFile(part.name, filepath.Base(file.Name())); err != nil {\n\t\t\t\treturn errors.Wrap(ErrMultipartForm, err.Error())\n\t\t\t}\n\n\t\tcase \"csrf-token\":\n\t\t\t// Add csrf token field\n\t\t\th := make(textproto.MIMEHeader)\n\t\t\t// BMCs with newer firmware (>=1.74.09) accept the form with this name value\n\t\t\t// h.Set(\"Content-Disposition\", `form-data; name=\"CSRF-TOKEN\"`)\n\t\t\t//\n\t\t\t// the BMCs running older firmware (<=1.23.06) versions expects the name value in this format\n\t\t\t// and the newer firmware (>=1.74.09) seem to be backwards compatible with this name value format.\n\t\t\th.Set(\"Content-Disposition\", `form-data; name=\"CSRF_TOKEN\"`)\n\n\t\t\tif partWriter, err = payloadWriter.CreatePart(h); err != nil {\n\t\t\t\treturn errors.Wrap(ErrMultipartForm, err.Error())\n\t\t\t}\n\t\tdefault:\n\t\t\treturn errors.Wrap(ErrMultipartForm, \"unexpected form part: \"+part.name)\n\t\t}\n\n\t\tif _, err = io.Copy(partWriter, part.data); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tpayloadWriter.Close()\n\n\tresp, statusCode, err := c.query(\n\t\tctx,\n\t\t\"cgi/bios_upload.cgi\",\n\t\thttp.MethodPost,\n\t\tbytes.NewReader(payloadBuffer.Bytes()),\n\t\tmap[string]string{\"Content-Type\": payloadWriter.FormDataContentType()},\n\t\t0,\n\t)\n\n\tif err != nil {\n\t\treturn errors.Wrap(ErrMultipartForm, err.Error())\n\t}\n\n\tif statusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"non 200 response: %d %s\", statusCode, resp)\n\t}\n\n\treturn nil\n}\n\nfunc (c *x11) verifyBIOSFirmwareVersion(ctx context.Context) error {\n\tpayload := []byte(`op=BIOS_UPDATE_CHECK.XML&r=(0,0)&_=`)\n\texpectResponse := []byte(`<BIOS_UPDATE_CHECK RES=\"00\"/>`)\n\n\theaders := map[string]string{\n\t\t\"Content-type\": \"application/x-www-form-urlencoded; charset=UTF-8\",\n\t}\n\n\tbody, status, err := c.query(ctx, \"cgi/ipmi.cgi\", http.MethodPost, bytes.NewBuffer(payload), headers, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif status != http.StatusOK || !bytes.Contains(body, expectResponse) {\n\t\treturn unexpectedResponseErr(payload, body, status)\n\t}\n\n\tpayload = []byte(`op=BIOS_REV.XML&_=`)\n\texpectResponse = []byte(`<BIOS_Rev OldRev`)\n\n\tbody, status, err = c.query(ctx, \"cgi/ipmi.cgi\", http.MethodPost, bytes.NewBuffer(payload), headers, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif status != http.StatusOK || !bytes.Contains(body, expectResponse) {\n\t\treturn unexpectedResponseErr(payload, body, status)\n\t}\n\n\treturn nil\n}\n\nfunc (c *x11) setBIOSOp(ctx context.Context) error {\n\tpayload := []byte(`op=BIOS_OPTION.XML&_=`)\n\texpectResponse := []byte(`<BIOS_OP Res=\"0\"/>`)\n\n\theaders := map[string]string{\n\t\t\"Content-type\": \"application/x-www-form-urlencoded; charset=UTF-8\",\n\t}\n\n\tbody, status, err := c.query(ctx, \"cgi/ipmi.cgi\", http.MethodPost, bytes.NewBuffer(payload), headers, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif status != http.StatusOK || !bytes.Contains(body, expectResponse) {\n\t\treturn unexpectedResponseErr(payload, body, status)\n\t}\n\n\treturn nil\n}\n\nfunc (c *x11) initiateBIOSFirmwareInstall(ctx context.Context) error {\n\t// save all current SMBIOS, NVRAM, ME configuration\n\tpayload := []byte(`op=main_biosupdate&_=`)\n\texpectResponse := []byte(`ok`)\n\n\theaders := map[string]string{\n\t\t\"Content-type\": \"application/x-www-form-urlencoded; charset=UTF-8\",\n\t}\n\n\t// don't spend much time on this call since it doesn't return and holds the connection.\n\tsctx, cancel := context.WithTimeout(ctx, 5*time.Second)\n\tdefer cancel()\n\n\tbody, status, err := c.query(sctx, \"cgi/op.cgi\", http.MethodPost, bytes.NewBuffer(payload), headers, 0)\n\tif err != nil {\n\t\t// this endpoint generally times out - its expected\n\t\tif strings.Contains(err.Error(), \"context deadline exceeded\") || strings.Contains(err.Error(), \"operation timed out\") {\n\t\t\treturn nil\n\t\t}\n\n\t\treturn err\n\t}\n\n\tif status != http.StatusOK || !bytes.Contains(body, expectResponse) {\n\t\treturn unexpectedResponseErr(payload, body, status)\n\t}\n\n\treturn nil\n}\n\nfunc (c *x11) setBIOSUpdateDone(ctx context.Context) error {\n\tpayload := []byte(`op=BIOS_UPDATE_DONE.XML&r=(1,0)&_=`)\n\n\theaders := map[string]string{\n\t\t\"Content-type\": \"application/x-www-form-urlencoded; charset=UTF-8\",\n\t}\n\n\tbody, status, err := c.query(ctx, \"cgi/ipmi.cgi\", http.MethodPost, bytes.NewBuffer(payload), headers, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// yep, the endpoint returns 500 even when successful\n\tif status != http.StatusOK && status != 500 {\n\t\treturn unexpectedResponseErr(payload, body, status)\n\t}\n\n\treturn nil\n}\n\n// statusBIOSFirmwareInstall returns the status of the firmware install process\nfunc (c *x11) statusBIOSFirmwareInstall(ctx context.Context) (state constants.TaskState, status string, err error) {\n\tpayload := []byte(`fwtype=1&_`)\n\n\theaders := map[string]string{\"Content-Type\": \"application/x-www-form-urlencoded; charset=UTF-8\"}\n\tresp, httpStatus, err := c.query(ctx, \"cgi/upgrade_process.cgi\", http.MethodPost, bytes.NewReader(payload), headers, 0)\n\tif err != nil {\n\t\treturn \"\", \"\", errors.Wrap(bmclibErrs.ErrFirmwareInstallStatus, err.Error())\n\t}\n\n\tif httpStatus != http.StatusOK {\n\t\treturn \"\", \"\", errors.Wrap(bmclibErrs.ErrFirmwareInstallStatus, \"Unexpected http status code: \"+strconv.Itoa(httpStatus))\n\t}\n\n\t// if theres html or no <percent> xml in the response, the session expired\n\t// at the end of the install the BMC resets itself and the response is in HTML.\n\tif bytes.Contains(resp, []byte(`<html>`)) || !bytes.Contains(resp, []byte(`<percent>`)) {\n\t\t// reopen session here, check firmware install status\n\t\treturn constants.Unknown, \"session expired/unexpected response\", bmclibErrs.ErrSessionExpired\n\t}\n\n\t// as long as the response is xml, the firmware install is running\n\tpart := strings.Split(string(resp), \"<percent>\")[1]\n\tpercent := strings.Split(part, \"</percent>\")[0]\n\tpercent += \"%\"\n\n\tswitch {\n\t// 1% indicates the file has been uploaded and the firmware install is not yet initiated\n\tcase bytes.Contains(resp, []byte(\"<status>0</status>\")) && bytes.Contains(resp, []byte(\"<percent>1</percent>\")):\n\t\treturn constants.Failed, percent, bmclibErrs.ErrBMCColdResetRequired\n\n\t// 0% along with the check on the component endpoint indicates theres no update in progress\n\tcase (bytes.Contains(resp, []byte(\"<status>0</status>\")) && bytes.Contains(resp, []byte(\"<percent>0</percent>\"))):\n\t\tif err := c.checkComponentUpdateMisc(ctx, \"postUpdate\"); err != nil {\n\t\t\tif errors.Is(err, bmclibErrs.ErrHostPowercycleRequired) {\n\t\t\t\treturn constants.PowerCycleHost, percent, nil\n\t\t\t}\n\t\t}\n\n\t\treturn constants.Complete, \"all done!\", nil\n\n\t// status 0 and 100% indicates the update is complete and requires a few post update calls\n\tcase bytes.Contains(resp, []byte(\"<status>0</status>\")) && bytes.Contains(resp, []byte(\"<percent>100</percent>\")):\n\t\t// TODO: create a new bmc method FirmwarePostInstall()\n\t\t// notifies the BMC the BIOS update is done\n\t\tif err := c.setBIOSUpdateDone(ctx); err != nil {\n\t\t\treturn \"\", \"\", err\n\t\t}\n\n\t\t// tells the BMC it can get out of the BIOS update mode\n\t\tif err := c.checkComponentUpdateMisc(ctx, \"postUpdate\"); err != nil {\n\t\t\tif errors.Is(err, bmclibErrs.ErrHostPowercycleRequired) {\n\t\t\t\treturn constants.PowerCycleHost, percent, nil\n\t\t\t}\n\n\t\t\treturn constants.PowerCycleHost, percent, err\n\t\t}\n\n\t\treturn constants.PowerCycleHost, percent, nil\n\n\t// status 8 and percent 0 indicates its initializing the update\n\tcase bytes.Contains(resp, []byte(\"<status>8</status>\")) && bytes.Contains(resp, []byte(\"<percent>0</percent>\")):\n\t\treturn constants.Running, percent, nil\n\n\t// status 8 and any other percent value indicates its running\n\tcase bytes.Contains(resp, []byte(\"<status>8</status>\")) && bytes.Contains(resp, []byte(\"<percent>\")):\n\t\treturn constants.Running, percent, nil\n\t}\n\n\treturn constants.Unknown, \"\", nil\n}\n"
  },
  {
    "path": "providers/supermicro/x11_firmware_bmc.go",
    "content": "package supermicro\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"net/textproto\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/constants\"\n\tbmclibErrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n)\n\nvar (\n\tErrFirmwareInstallMode = errors.New(\"firmware install mode error\")\n\tErrMultipartForm       = errors.New(\"multipart form error\")\n)\n\nfunc (c *x11) firmwareUploadBMC(ctx context.Context, reader io.Reader) error {\n\tc.log.V(2).Info(\"setting device to firmware install mode\", \"ip\", c.host, \"component\", \"BMC\", \"model\", c.model)\n\n\t// 1. set the device to flash mode - prepares the flash\n\terr := c.setBMCFirmwareInstallMode(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tc.log.V(2).Info(\"uploading firmware\", \"ip\", c.host, \"component\", \"BMC\", \"model\", c.model)\n\n\t// 2. upload firmware image file\n\terr = c.uploadBMCFirmware(ctx, reader)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tc.log.V(2).Info(\"verifying uploaded firmware\", \"ip\", c.host, \"component\", \"BMC\", \"model\", c.model)\n\n\t// 3. BMC verifies the uploaded firmware version\n\treturn c.verifyBMCFirmwareVersion(ctx)\n}\n\nfunc (c *x11) setBMCFirmwareInstallMode(ctx context.Context) error {\n\tpayload := []byte(`op=LOCK_UPLOAD_FW.XML&r=(0,0)&_=`)\n\n\theaders := map[string]string{\n\t\t\"Content-type\": \"application/x-www-form-urlencoded; charset=UTF-8\",\n\t}\n\n\tbody, status, err := c.query(ctx, \"cgi/ipmi.cgi\", http.MethodPost, bytes.NewBuffer(payload), headers, 0)\n\tif err != nil {\n\t\treturn errors.Wrap(ErrFirmwareInstallMode, err.Error())\n\t}\n\n\tif status != http.StatusOK {\n\t\treturn errors.Wrap(ErrFirmwareInstallMode, \"Unexpected status code: \"+strconv.Itoa(status))\n\t}\n\n\tswitch {\n\tcase bytes.Contains(body, []byte(`LOCK_FW_UPLOAD RES=\"0\"`)):\n\t\t// This response indicates another web session that initiated the firmware upload has the lock,\n\t\t// the BMC cannot be reset through a web session, nor can any other user obtain the firmware upload lock.\n\t\t// Since the firmware upload lock is associated with the cookie that initiated the request only the initiating session can cancel it.\n\t\t//\n\t\t// The only way to get out of this situation is through an IPMI (or redfish?) based BMC cold reset.\n\t\t///\n\t\t// The caller must check if a firmware update is in progress before proceeding with the reset.\n\t\t//\n\t\t// If after multiple calls to check the install progress - the progress seems stalled at 1%\n\t\t// it indicates no update was active, and the BMC can be reset.\n\t\t//\n\t\t// <IPMI><percent>1</percent></IPMI>\n\t\treturn errors.Wrap(\n\t\t\tbmclibErrs.ErrBMCColdResetRequired,\n\t\t\t\"unable to acquire lock for firmware upload, check if an update is in progress\",\n\t\t)\n\n\tcase bytes.Contains(body, []byte(`LOCK_FW_UPLOAD RES=\"1\"`)):\n\t\treturn nil\n\tdefault:\n\t\treturn errors.Wrap(ErrFirmwareInstallMode, \"set firmware install mode returned unexpected response body\")\n\t}\n\n}\n\n// -----------------------------212212001131894333502018521064\n// Content-Disposition: form-data; name=\"fw_image\"; filename=\"BMC_X11AST2500-4101MS_20221020_01.74.09_STDsp.bin\"\n// Content-Type: application/macbinary\n//\n// ... contents...\n//\n// -----------------------------348113760313214626342869148824\n// Content-Disposition: form-data; name=\"CSRF-TOKEN\"\n//\n// JhVe1BUiWzOVQdvXUKn7ClsQ5xffq8StMOxG7ZNlpKs\n// -----------------------------348113760313214626342869148824--\nfunc (c *x11) uploadBMCFirmware(ctx context.Context, fwReader io.Reader) error {\n\tvar payloadBuffer bytes.Buffer\n\tvar err error\n\n\ttype form struct {\n\t\tname string\n\t\tdata io.Reader\n\t}\n\n\tformParts := []form{\n\t\t{\n\t\t\tname: \"fw_image\",\n\t\t\tdata: fwReader,\n\t\t},\n\t}\n\n\tif c.csrfToken != \"\" {\n\t\tformParts = append(formParts, form{\n\t\t\tname: \"csrf-token\",\n\t\t\tdata: bytes.NewBufferString(c.csrfToken),\n\t\t})\n\t}\n\n\tpayloadWriter := multipart.NewWriter(&payloadBuffer)\n\n\tfor _, part := range formParts {\n\t\tvar partWriter io.Writer\n\n\t\tswitch part.name {\n\t\tcase \"fw_image\":\n\t\t\tfile, ok := part.data.(*os.File)\n\t\t\tif !ok {\n\t\t\t\treturn errors.Wrap(ErrMultipartForm, \"expected io.Reader on firmware image file\")\n\t\t\t}\n\n\t\t\tif partWriter, err = payloadWriter.CreateFormFile(part.name, filepath.Base(file.Name())); err != nil {\n\t\t\t\treturn errors.Wrap(ErrMultipartForm, err.Error())\n\t\t\t}\n\n\t\tcase \"csrf-token\":\n\t\t\t// Add csrf token field\n\t\t\th := make(textproto.MIMEHeader)\n\t\t\t// BMCs with newer firmware (>=1.74.09) accept the form with this name value\n\t\t\t// h.Set(\"Content-Disposition\", `form-data; name=\"CSRF-TOKEN\"`)\n\t\t\t//\n\t\t\t// the BMCs running older firmware (<=1.23.06) versions expects the name value in this format\n\t\t\t// this seems to be backwards compatible with the newer firmware.\n\t\t\th.Set(\"Content-Disposition\", `form-data; name=\"CSRF_TOKEN\"`)\n\n\t\t\tif partWriter, err = payloadWriter.CreatePart(h); err != nil {\n\t\t\t\treturn errors.Wrap(ErrMultipartForm, err.Error())\n\t\t\t}\n\t\tdefault:\n\t\t\treturn errors.Wrap(ErrMultipartForm, \"unexpected form part: \"+part.name)\n\t\t}\n\n\t\tif _, err = io.Copy(partWriter, part.data); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tpayloadWriter.Close()\n\n\tresp, statusCode, err := c.query(\n\t\tctx,\n\t\t\"cgi/oem_firmware_upload.cgi\",\n\t\thttp.MethodPost,\n\t\tbytes.NewReader(payloadBuffer.Bytes()),\n\t\tmap[string]string{\"Content-Type\": payloadWriter.FormDataContentType()},\n\t\t0,\n\t)\n\n\tif err != nil {\n\t\treturn errors.Wrap(ErrMultipartForm, err.Error())\n\t}\n\n\tif statusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"non 200 response: %d %s\", statusCode, resp)\n\t}\n\n\treturn nil\n}\n\nfunc (c *x11) verifyBMCFirmwareVersion(ctx context.Context) error {\n\terrUnexpectedResponse := errors.New(\"unexpected response\")\n\n\tpayload := []byte(`op=UPLOAD_FW_VERSION.XML&r=(0,0)&_=`)\n\n\theaders := map[string]string{\n\t\t\"Content-type\": \"application/x-www-form-urlencoded; charset=UTF-8\",\n\t}\n\n\tbody, status, err := c.query(ctx, \"cgi/ipmi.cgi\", http.MethodPost, bytes.NewBuffer(payload), headers, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif status != 200 {\n\t\treturn errors.Wrap(ErrFirmwareInstallMode, \"Unexpected status code: \"+strconv.Itoa(status))\n\t}\n\n\tif !bytes.Contains(body, []byte(`FW_VERSION NEW`)) {\n\t\treturn errors.Wrap(errUnexpectedResponse, string(body))\n\t}\n\n\treturn nil\n}\n\n// initiate BMC firmware install process\nfunc (c *x11) initiateBMCFirmwareInstall(ctx context.Context) error {\n\t// preserve all configuration, sensor data and SSL certs(?) during upgrade\n\tpayload := \"op=main_fwupdate&preserve_config=1&preserve_sdr=1&preserve_ssl=1\"\n\n\theaders := map[string]string{\"Content-Type\": \"application/x-www-form-urlencoded; charset=UTF-8\"}\n\n\t// don't spend much time on this call since it doesn't return and holds the connection.\n\tsctx, cancel := context.WithTimeout(ctx, 10*time.Second)\n\tdefer cancel()\n\n\t_, status, err := c.query(sctx, \"cgi/op.cgi\", http.MethodPost, bytes.NewBufferString(payload), headers, 0)\n\tif err != nil {\n\t\t// this operation causes the BMC to go AWOL and not send any response\n\t\t// so we ignore the error here, the caller can invoke FirmwareInstallStatus in the same session,\n\t\t// to check the install status to determine install progress.\n\n\t\t// whats returned is a *url.Error{} and errors.Is(err, context.DeadlineExceeded) doesn't seem to match\n\t\t// so a string contains it is.\n\t\tif strings.Contains(err.Error(), \"context deadline exceeded\") || strings.Contains(err.Error(), \"operation timed out\") {\n\t\t\treturn nil\n\t\t}\n\n\t\treturn err\n\t}\n\n\tif status != 200 {\n\t\treturn errors.Wrap(ErrFirmwareInstallMode, \"Unexpected status code: \"+strconv.Itoa(status))\n\t}\n\n\treturn nil\n}\n\n// statusBMCFirmwareInstall returns the status of the firmware install process\nfunc (c *x11) statusBMCFirmwareInstall(ctx context.Context) (state constants.TaskState, status string, err error) {\n\tpayload := []byte(`fwtype=0&_`)\n\n\theaders := map[string]string{\"Content-Type\": \"application/x-www-form-urlencoded; charset=UTF-8\"}\n\tresp, httpStatus, err := c.query(ctx, \"cgi/upgrade_process.cgi\", http.MethodPost, bytes.NewReader(payload), headers, 0)\n\tif err != nil {\n\t\treturn constants.Unknown, \"\", errors.Wrap(bmclibErrs.ErrFirmwareInstallStatus, err.Error())\n\t}\n\n\tif httpStatus != http.StatusOK {\n\t\treturn constants.Unknown, \"\", errors.Wrap(bmclibErrs.ErrFirmwareInstallStatus, \"Unexpected http status code: \"+strconv.Itoa(httpStatus))\n\t}\n\n\t// if theres html or no <percent> xml in the response, the session expired\n\t// at the end of the install the BMC resets itself and the response is in HTML.\n\tif bytes.Contains(resp, []byte(`<html>`)) || !bytes.Contains(resp, []byte(`<percent>`)) {\n\t\t// reopen session here, check firmware install status\n\t\treturn constants.Unknown, \"session expired/unexpected response\", bmclibErrs.ErrSessionExpired\n\t}\n\n\t// as long as the response is xml, the firmware install is running\n\tpart := strings.Split(string(resp), \"<percent>\")[1]\n\tpercent := strings.Split(part, \"</percent>\")[0]\n\tpercent += \"%\"\n\n\tswitch percent {\n\t// TODO:\n\t// X11DPH-T - returns percent 0 all the time\n\t//\n\t// 0% indicates its either not running or complete\n\tcase \"0%\", \"100%\":\n\t\treturn constants.Complete, percent, nil\n\t// until 2% its initializing\n\tcase \"1%\", \"2%\":\n\t\treturn constants.Initializing, percent, nil\n\t// any other percent value indicates its active\n\tdefault:\n\t\treturn constants.Running, percent, nil\n\t}\n}\n"
  },
  {
    "path": "providers/supermicro/x11_firmware_bmc_test.go",
    "content": "package supermicro\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"mime\"\n\t\"mime/multipart\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/constants\"\n\t\"github.com/bmc-toolbox/bmclib/v2/internal/httpclient\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestX11SetBMCFirmwareInstallMode(t *testing.T) {\n\ttestcases := []struct {\n\t\tname          string\n\t\terrorContains string\n\t\tendpoint      string\n\t\thandler       func(http.ResponseWriter, *http.Request)\n\t}{\n\t\t{\n\t\t\t\"BMC fw install lock acquired\",\n\t\t\t\"\",\n\t\t\t\"/cgi/ipmi.cgi\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tassert.Equal(t, http.MethodPost, r.Method)\n\t\t\t\tassert.Equal(t, \"application/x-www-form-urlencoded; charset=UTF-8\", r.Header.Get(\"Content-Type\"))\n\n\t\t\t\tb, err := io.ReadAll(r.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tassert.Equal(t, `op=LOCK_UPLOAD_FW.XML&r=(0,0)&_=`, string(b))\n\n\t\t\t\t_, _ = w.Write([]byte(`<?xml version=\"1.0\"?>\n\t\t\t\t<IPMI>\n\t\t\t\t  <LOCK_FW_UPLOAD RES=\"1\"/>\n\t\t\t\t</IPMI>`))\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"lock not acquired\",\n\t\t\t\"BMC cold reset required\",\n\t\t\t\"/cgi/ipmi.cgi\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tassert.Equal(t, http.MethodPost, r.Method)\n\t\t\t\tassert.Equal(t, \"application/x-www-form-urlencoded; charset=UTF-8\", r.Header.Get(\"Content-Type\"))\n\n\t\t\t\tb, err := io.ReadAll(r.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tassert.Equal(t, `op=LOCK_UPLOAD_FW.XML&r=(0,0)&_=`, string(b))\n\n\t\t\t\t_, _ = w.Write([]byte(`<?xml version=\"1.0\"?>\n\t\t\t\t<IPMI>\n\t\t\t\t  <LOCK_FW_UPLOAD RES=\"0\"/>\n\t\t\t\t</IPMI>`))\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"error returned\",\n\t\t\t\"400\",\n\t\t\t\"/cgi/ipmi.cgi\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tw.WriteHeader(400)\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmux := http.NewServeMux()\n\t\t\tmux.HandleFunc(tc.endpoint, tc.handler)\n\n\t\t\tserver := httptest.NewTLSServer(mux)\n\t\t\tdefer server.Close()\n\n\t\t\tparsedURL, err := url.Parse(server.URL)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tserviceClient := newBmcServiceClient(parsedURL.Hostname(), parsedURL.Port(), \"foo\", \"bar\", httpclient.Build())\n\t\t\tassert.Nil(t, err)\n\n\t\t\tclient := &x11{serviceClient: serviceClient, log: logr.Discard()}\n\n\t\t\tif err := client.setBMCFirmwareInstallMode(context.Background()); err != nil {\n\t\t\t\tif tc.errorContains != \"\" {\n\t\t\t\t\tassert.ErrorContains(t, err, tc.errorContains)\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tassert.Nil(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestX11UploadBMCFirmware(t *testing.T) {\n\ttestcases := []struct {\n\t\tname           string\n\t\terrorContains  string\n\t\tendpoint       string\n\t\tfwFilename     string\n\t\tfwFileContents string\n\t\thandler        func(http.ResponseWriter, *http.Request)\n\t}{\n\t\t{\n\t\t\t\"upload works\",\n\t\t\t\"\",\n\t\t\t\"/cgi/oem_firmware_upload.cgi\",\n\t\t\t\"blob.bin\",\n\t\t\t\"dummy fw image\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tassert.Equal(t, http.MethodPost, r.Method)\n\t\t\t\tb, err := io.ReadAll(r.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\t// validate content type\n\t\t\t\tmediaType, params, err := mime.ParseMediaType(r.Header.Get(\"Content-Type\"))\n\t\t\t\tassert.Nil(t, err)\n\n\t\t\t\tassert.Equal(t, \"multipart/form-data\", mediaType)\n\n\t\t\t\t// read form parts from boundary\n\t\t\t\treader := multipart.NewReader(bytes.NewReader(b), params[\"boundary\"])\n\n\t\t\t\t// validate firmware image part\n\t\t\t\tpart, err := reader.NextPart()\n\t\t\t\tassert.Nil(t, err)\n\n\t\t\t\tassert.Equal(t, `form-data; name=\"fw_image\"; filename=\"blob.bin\"`, part.Header.Get(\"Content-Disposition\"))\n\n\t\t\t\t// validate csrf-token part\n\t\t\t\tpart, err = reader.NextPart()\n\t\t\t\tassert.Nil(t, err)\n\n\t\t\t\tassert.Equal(t, `form-data; name=\"CSRF_TOKEN\"`, part.Header.Get(\"Content-Disposition\"))\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmux := http.NewServeMux()\n\t\t\tmux.HandleFunc(tc.endpoint, tc.handler)\n\n\t\t\tserver := httptest.NewTLSServer(mux)\n\t\t\tdefer server.Close()\n\n\t\t\tparsedURL, err := url.Parse(server.URL)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\t// create tmp firmware file\n\t\t\tvar fwReader *os.File\n\t\t\tif tc.fwFilename != \"\" {\n\t\t\t\ttmpdir := t.TempDir()\n\t\t\t\tbinPath := filepath.Join(tmpdir, tc.fwFilename)\n\t\t\t\terr := os.WriteFile(binPath, []byte(tc.fwFileContents), 0600)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tfwReader, err = os.Open(binPath)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"%s -> %s\", err.Error(), binPath)\n\t\t\t\t}\n\n\t\t\t\tdefer os.Remove(binPath)\n\t\t\t}\n\n\t\t\tserviceClient := newBmcServiceClient(parsedURL.Hostname(), parsedURL.Port(), \"foo\", \"bar\", httpclient.Build())\n\t\t\tassert.Nil(t, err)\n\t\t\tserviceClient.csrfToken = \"foobar\"\n\t\t\tclient := &x11{serviceClient: serviceClient, log: logr.Discard()}\n\n\t\t\tif err := client.uploadBMCFirmware(context.Background(), fwReader); err != nil {\n\t\t\t\tif tc.errorContains != \"\" {\n\t\t\t\t\tassert.ErrorContains(t, err, tc.errorContains)\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tassert.Nil(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestX11VerifyBMCFirmwareVersion(t *testing.T) {\n\ttestcases := []struct {\n\t\tname          string\n\t\terrorContains string\n\t\tendpoint      string\n\t\thandler       func(http.ResponseWriter, *http.Request)\n\t}{\n\t\t{\n\t\t\t\"verify successful\",\n\t\t\t\"\",\n\t\t\t\"/cgi/ipmi.cgi\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tassert.Equal(t, http.MethodPost, r.Method)\n\t\t\t\tb, err := io.ReadAll(r.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tassert.Equal(t, []byte(`op=UPLOAD_FW_VERSION.XML&r=(0,0)&_=`), b)\n\n\t\t\t\tresp := []byte(`<?xml version=\"1.0\"?>  <IPMI>  <FW_VERSION NEW=\"017409\" OLD=\"017409\"/>  </IPMI>`)\n\t\t\t\t_, err = w.Write(resp)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"unexpected response\",\n\t\t\t\"unexpected response\",\n\t\t\t\"/cgi/ipmi.cgi\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tassert.Equal(t, http.MethodPost, r.Method)\n\t\t\t\tresp := []byte(`bad bmc does not comply`)\n\t\t\t\t_, err := w.Write(resp)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"unexpected status code\",\n\t\t\t\"Unexpected status code: 403\",\n\t\t\t\"/cgi/ipmi.cgi\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tassert.Equal(t, http.MethodPost, r.Method)\n\t\t\t\tw.WriteHeader(http.StatusForbidden)\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmux := http.NewServeMux()\n\t\t\tmux.HandleFunc(tc.endpoint, tc.handler)\n\n\t\t\tserver := httptest.NewTLSServer(mux)\n\t\t\tdefer server.Close()\n\n\t\t\tparsedURL, err := url.Parse(server.URL)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tserviceClient := newBmcServiceClient(parsedURL.Hostname(), parsedURL.Port(), \"foo\", \"bar\", httpclient.Build())\n\t\t\tassert.Nil(t, err)\n\t\t\tserviceClient.csrfToken = \"foobar\"\n\t\t\tclient := &x11{serviceClient: serviceClient, log: logr.Discard()}\n\n\t\t\tif err := client.verifyBMCFirmwareVersion(context.Background()); err != nil {\n\t\t\t\tif tc.errorContains != \"\" {\n\t\t\t\t\tassert.ErrorContains(t, err, tc.errorContains)\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tassert.Nil(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestX11InitiateBMCFirmwareInstall(t *testing.T) {\n\ttestcases := []struct {\n\t\tname          string\n\t\terrorContains string\n\t\tendpoint      string\n\t\thandler       func(http.ResponseWriter, *http.Request)\n\t}{\n\t\t{\n\t\t\t\"install intiated successfully\",\n\t\t\t\"\",\n\t\t\t\"/cgi/op.cgi\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tassert.Equal(t, http.MethodPost, r.Method)\n\t\t\t\tb, err := io.ReadAll(r.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tassert.Equal(t, []byte(`op=main_fwupdate&preserve_config=1&preserve_sdr=1&preserve_ssl=1`), b)\n\n\t\t\t\tresp := []byte(`Upgrade progress.. 1%`)\n\t\t\t\t_, err = w.Write(resp)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"unexpected response\",\n\t\t\t\"unexpected response\",\n\t\t\t\"/cgi/op.cgi\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tassert.Equal(t, http.MethodPost, r.Method)\n\t\t\t\tresp := []byte(`bad bmc does not comply`)\n\t\t\t\t_, err := w.Write(resp)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"unexpected status code\",\n\t\t\t\"Unexpected status code: 403\",\n\t\t\t\"/cgi/op.cgi\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tassert.Equal(t, http.MethodPost, r.Method)\n\t\t\t\tw.WriteHeader(http.StatusForbidden)\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmux := http.NewServeMux()\n\t\t\tmux.HandleFunc(tc.endpoint, tc.handler)\n\n\t\t\tserver := httptest.NewTLSServer(mux)\n\t\t\tdefer server.Close()\n\n\t\t\tparsedURL, err := url.Parse(server.URL)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tserviceClient := newBmcServiceClient(parsedURL.Hostname(), parsedURL.Port(), \"foo\", \"bar\", httpclient.Build())\n\t\t\tassert.Nil(t, err)\n\t\t\tserviceClient.csrfToken = \"foobar\"\n\t\t\tclient := &x11{serviceClient: serviceClient, log: logr.Discard()}\n\n\t\t\tif err := client.initiateBMCFirmwareInstall(context.Background()); err != nil {\n\t\t\t\tif tc.errorContains != \"\" {\n\t\t\t\t\tassert.ErrorContains(t, err, tc.errorContains)\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tassert.Nil(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestX11StatusBMCFirmwareInstall(t *testing.T) {\n\ttestcases := []struct {\n\t\tname          string\n\t\texpectState   constants.TaskState\n\t\texpectStatus  string\n\t\terrorContains string\n\t\tendpoint      string\n\t\thandler       func(http.ResponseWriter, *http.Request)\n\t}{\n\t\t{\n\t\t\t\"state complete 0\",\n\t\t\tconstants.Complete,\n\t\t\t\"0%\",\n\t\t\t\"\",\n\t\t\t\"/cgi/upgrade_process.cgi\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tassert.Equal(t, http.MethodPost, r.Method)\n\t\t\t\tb, err := io.ReadAll(r.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tassert.Equal(t, []byte(`fwtype=0&_`), b)\n\n\t\t\t\tresp := []byte(`  <?xml version=\"1.0\"?>\n\t\t\t\t<IPMI>\n\t\t\t\t  <percent>0</percent>\n\t\t\t\t</IPMI>`)\n\t\t\t\t_, err = w.Write(resp)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"state complete 100\",\n\t\t\tconstants.Complete,\n\t\t\t\"100%\",\n\t\t\t\"\",\n\t\t\t\"/cgi/upgrade_process.cgi\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tassert.Equal(t, http.MethodPost, r.Method)\n\t\t\t\tb, err := io.ReadAll(r.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tassert.Equal(t, []byte(`fwtype=0&_`), b)\n\n\t\t\t\tresp := []byte(`  <?xml version=\"1.0\"?>\n\t\t\t\t<IPMI>\n\t\t\t\t  <percent>100</percent>\n\t\t\t\t</IPMI>`)\n\t\t\t\t_, err = w.Write(resp)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"state initializing\",\n\t\t\tconstants.Initializing,\n\t\t\t\"1%\",\n\t\t\t\"\",\n\t\t\t\"/cgi/upgrade_process.cgi\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tassert.Equal(t, http.MethodPost, r.Method)\n\t\t\t\tb, err := io.ReadAll(r.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tassert.Equal(t, []byte(`fwtype=0&_`), b)\n\n\t\t\t\tresp := []byte(`  <?xml version=\"1.0\"?>\n\t\t\t\t<IPMI>\n\t\t\t\t  <percent>1</percent>\n\t\t\t\t</IPMI>`)\n\t\t\t\t_, err = w.Write(resp)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"status running\",\n\t\t\tconstants.Running,\n\t\t\t\"95%\",\n\t\t\t\"\",\n\t\t\t\"/cgi/upgrade_process.cgi\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tassert.Equal(t, http.MethodPost, r.Method)\n\t\t\t\tb, err := io.ReadAll(r.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tassert.Equal(t, []byte(`fwtype=0&_`), b)\n\n\t\t\t\tresp := []byte(`  <?xml version=\"1.0\"?>\n\t\t\t\t<IPMI>\n\t\t\t\t  <percent>95</percent>\n\t\t\t\t</IPMI>`)\n\t\t\t\t_, err = w.Write(resp)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"status unknown\",\n\t\t\tconstants.Unknown,\n\t\t\t\"\",\n\t\t\t\"session expired\",\n\t\t\t\"/cgi/upgrade_process.cgi\",\n\t\t\tfunc(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tassert.Equal(t, http.MethodPost, r.Method)\n\t\t\t\tb, err := io.ReadAll(r.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\n\t\t\t\tassert.Equal(t, []byte(`fwtype=0&_`), b)\n\n\t\t\t\tresp := []byte(`<html> <head>uh what</head> </html>`)\n\t\t\t\t_, err = w.Write(resp)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatal(err)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range testcases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tmux := http.NewServeMux()\n\t\t\tmux.HandleFunc(tc.endpoint, tc.handler)\n\n\t\t\tserver := httptest.NewTLSServer(mux)\n\t\t\tdefer server.Close()\n\n\t\t\tparsedURL, err := url.Parse(server.URL)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tserviceClient := newBmcServiceClient(parsedURL.Hostname(), parsedURL.Port(), \"foo\", \"bar\", httpclient.Build())\n\t\t\tassert.Nil(t, err)\n\n\t\t\tserviceClient.csrfToken = \"foobar\"\n\t\t\tclient := &x11{serviceClient: serviceClient, log: logr.Discard()}\n\n\t\t\tgotState, gotStatus, err := client.statusBMCFirmwareInstall(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tif tc.errorContains != \"\" {\n\t\t\t\t\tassert.ErrorContains(t, err, tc.errorContains)\n\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tassert.Nil(t, err)\n\t\t\tassert.Equal(t, tc.expectState, gotState)\n\t\t\tassert.Equal(t, tc.expectStatus, gotStatus)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "providers/supermicro/x12.go",
    "content": "package supermicro\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/bmc-toolbox/bmclib/v2/constants\"\n\tbrrs \"github.com/bmc-toolbox/bmclib/v2/errors\"\n\trfw \"github.com/bmc-toolbox/bmclib/v2/internal/redfishwrapper\"\n\t\"github.com/bmc-toolbox/common\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/pkg/errors\"\n\t\"github.com/stmcginnis/gofish/schemas\"\n)\n\ntype x12 struct {\n\t*serviceClient\n\tmodel string\n\tlog   logr.Logger\n}\n\nfunc newX12Client(client *serviceClient, logger logr.Logger) bmcQueryor {\n\treturn &x12{\n\t\tserviceClient: client,\n\t\tlog:           logger,\n\t}\n}\n\nfunc (c *x12) deviceModel() string {\n\treturn c.model\n}\n\nfunc (c *x12) queryDeviceModel(ctx context.Context) (string, error) {\n\tif err := c.redfishSession(ctx); err != nil {\n\t\treturn \"\", err\n\t}\n\n\t_, model, err := c.redfish.DeviceVendorModel(ctx)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif model == \"\" {\n\t\treturn \"\", errors.Wrap(ErrModelUnknown, \"empty value\")\n\t}\n\n\tc.model = common.FormatProductName(model)\n\n\treturn c.model, nil\n}\n\nvar (\n\terrUploadTaskIDEmpty = errors.New(\"firmware upload request returned empty firmware upload verify TaskID\")\n)\n\nfunc (c *x12) supportsInstall(component string) error {\n\terrComponentNotSupported := fmt.Errorf(\"component %s on device %s not supported\", component, c.model)\n\n\tsupported := []string{common.SlugBIOS, common.SlugBMC}\n\tif !slices.Contains(supported, strings.ToUpper(component)) {\n\t\treturn errComponentNotSupported\n\t}\n\n\treturn nil\n}\n\nfunc (c *x12) firmwareInstallSteps(component string) ([]constants.FirmwareInstallStep, error) {\n\tif err := c.supportsInstall(component); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn []constants.FirmwareInstallStep{\n\t\tconstants.FirmwareInstallStepUpload,\n\t\tconstants.FirmwareInstallStepUploadStatus,\n\t\tconstants.FirmwareInstallStepInstallUploaded,\n\t\tconstants.FirmwareInstallStepInstallStatus,\n\t}, nil\n}\n\n// upload firmware\nfunc (c *x12) firmwareUpload(ctx context.Context, component string, file *os.File) (taskID string, err error) {\n\tif err = c.supportsInstall(component); err != nil {\n\t\treturn \"\", err\n\t}\n\n\terr = c.firmwareTaskActive(ctx, component)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\ttargetID, err := c.redfishOdataID(ctx, component)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tparams, err := c.redfishParameters(component, targetID)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\ttaskID, err = c.redfish.FirmwareUpload(ctx, file, params)\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"OemFirmwareAlreadyInUpdateMode\") {\n\t\t\treturn \"\", errors.Wrap(brrs.ErrBMCColdResetRequired, \"BMC currently in update mode, either continue the update OR if no update is currently running - reset the BMC\")\n\t\t}\n\n\t\treturn \"\", errors.Wrap(err, \"error in firmware upload\")\n\t}\n\n\tif taskID == \"\" {\n\t\treturn \"\", errUploadTaskIDEmpty\n\t}\n\n\treturn taskID, nil\n}\n\n// returns an error when a bmc firmware install is active\nfunc (c *x12) firmwareTaskActive(ctx context.Context, component string) error {\n\ttasks, err := c.redfish.Tasks(ctx)\n\tif err != nil {\n\t\treturn errors.Wrap(err, \"error querying redfish tasks\")\n\t}\n\n\tfor _, t := range tasks {\n\t\tt := t\n\n\t\tif stateFinalized(t.TaskState) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif err := noTasksRunning(component, t); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// noTasksRunning returns an error if a firmware related task was found active\nfunc noTasksRunning(component string, t *schemas.Task) error {\n\terrTaskActive := errors.New(\"A firmware task was found active for component: \" + component)\n\n\tconst (\n\t\t// The redfish task name when the BMC is verifies the uploaded BMC firmware.\n\t\tverifyBMCFirmware = \"BMC Verify\"\n\t\t// The redfish task name when the BMC is installing the uploaded BMC firmware.\n\t\tupdateBMCFirmware = \"BMC Update\"\n\t\t// The redfish task name when the BMC is verifies the uploaded BIOS firmware.\n\t\tverifyBIOSFirmware = \"BIOS Verify\"\n\t\t// The redfish task name when the BMC is installing the uploaded BIOS firmware.\n\t\tupdateBIOSFirmware = \"BIOS Update\"\n\t)\n\n\tvar verifyTaskName, updateTaskName string\n\n\tswitch strings.ToUpper(component) {\n\tcase common.SlugBMC:\n\t\tverifyTaskName = verifyBMCFirmware\n\t\tupdateTaskName = updateBMCFirmware\n\tcase common.SlugBIOS:\n\t\tverifyTaskName = verifyBIOSFirmware\n\t\tupdateTaskName = updateBIOSFirmware\n\t}\n\n\ttaskInfo := fmt.Sprintf(\"id: %s, state: %s, status: %s\", t.ID, t.TaskState, t.TaskStatus)\n\n\tswitch t.Name {\n\tcase verifyTaskName:\n\t\treturn errors.Wrap(errTaskActive, taskInfo)\n\tcase updateTaskName:\n\t\treturn errors.Wrap(errTaskActive, taskInfo)\n\tdefault:\n\t\treturn nil\n\t}\n}\n\nfunc stateFinalized(s schemas.TaskState) bool {\n\tfinalized := []schemas.TaskState{\n\t\tschemas.CompletedTaskState,\n\t\tschemas.CancelledTaskState,\n\t\tschemas.InterruptedTaskState,\n\t\tschemas.ExceptionTaskState,\n\t}\n\n\treturn slices.Contains(finalized, s)\n}\n\ntype Supermicro struct {\n\tBIOS map[string]bool `json:\"BIOS,omitempty\"`\n\tBMC  map[string]bool `json:\"BMC,omitempty\"`\n}\n\ntype OEM struct {\n\tSupermicro `json:\"Supermicro\"`\n}\n\n// redfish OEM fw install parameters\nfunc (c *x12) biosFwInstallParams() (map[string]bool, error) {\n\tswitch c.model {\n\tcase \"x12spo-ntf\":\n\t\treturn map[string]bool{\n\t\t\t\"PreserveME\":       false,\n\t\t\t\"PreserveNVRAM\":    false,\n\t\t\t\"PreserveSMBIOS\":   true,\n\t\t\t\"BackupBIOS\":       false,\n\t\t\t\"PreserveBOOTCONF\": true,\n\t\t}, nil\n\tcase \"x12sth-sys\":\n\t\treturn map[string]bool{\n\t\t\t\"PreserveME\":         false,\n\t\t\t\"PreserveNVRAM\":      false,\n\t\t\t\"PreserveSMBIOS\":     true,\n\t\t\t\"PreserveOA\":         true,\n\t\t\t\"PreserveSETUPCONF\":  true,\n\t\t\t\"PreserveSETUPPWD\":   true,\n\t\t\t\"PreserveSECBOOTKEY\": true,\n\t\t\t\"PreserveBOOTCONF\":   true,\n\t\t}, nil\n\tdefault:\n\t\t// ideally we never get in this position, since theres model number validation in parent callers.\n\t\treturn nil, errors.New(\"unsupported model for BIOS fw install: \" + c.model)\n\t}\n}\n\n// redfish OEM fw install parameters\nfunc (c *x12) bmcFwInstallParams() map[string]bool {\n\treturn map[string]bool{\n\t\t\"PreserveCfg\": true,\n\t\t\"PreserveSdr\": true,\n\t\t\"PreserveSsl\": true,\n\t}\n}\n\nfunc (c *x12) redfishParameters(component, targetODataID string) (*rfw.RedfishUpdateServiceParameters, error) {\n\terrUnsupported := errors.New(\"redfish parameters for x12 hardware component not supported: \" + component)\n\n\toem := OEM{}\n\n\tbiosInstallParams, err := c.biosFwInstallParams()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch strings.ToUpper(component) {\n\tcase common.SlugBIOS:\n\t\toem.Supermicro.BIOS = biosInstallParams\n\tcase common.SlugBMC:\n\t\toem.Supermicro.BMC = c.bmcFwInstallParams()\n\tdefault:\n\t\treturn nil, errUnsupported\n\t}\n\n\tb, err := json.Marshal(oem)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"error preparing redfish parameters\")\n\t}\n\n\treturn &rfw.RedfishUpdateServiceParameters{\n\t\t// NOTE:\n\t\t// X12s support the OnReset Apply time for BIOS updates if we want to implement that in the future.\n\t\tOperationApplyTime: constants.OnStartUpdateRequest,\n\t\tTargets:            []string{targetODataID},\n\t\tOem:                b,\n\t}, nil\n}\n\nfunc (c *x12) redfishOdataID(ctx context.Context, component string) (string, error) {\n\terrUnsupported := errors.New(\"unable to return redfish OData ID for unsupported component: \" + component)\n\n\tswitch strings.ToUpper(component) {\n\tcase common.SlugBMC:\n\t\treturn c.redfish.ManagerOdataID(ctx)\n\tcase common.SlugBIOS:\n\t\t// hardcoded since SMCs without the DCMS license will throw license errors\n\t\treturn \"/redfish/v1/Systems/1/Bios\", nil\n\t\t//return c.redfish.SystemsBIOSOdataID(ctx)\n\t}\n\n\treturn \"\", errUnsupported\n}\n\nfunc (c *x12) firmwareInstallUploaded(ctx context.Context, component, uploadTaskID string) (installTaskID string, err error) {\n\tif err = c.supportsInstall(component); err != nil {\n\t\treturn \"\", err\n\t}\n\n\ttask, err := c.redfish.Task(ctx, uploadTaskID)\n\tif err != nil {\n\t\te := fmt.Sprintf(\"error querying redfish tasks for firmware upload taskID: %s, err: %s\", uploadTaskID, err.Error())\n\t\treturn \"\", errors.Wrap(brrs.ErrFirmwareVerifyTask, e)\n\t}\n\n\ttaskInfo := fmt.Sprintf(\"id: %s, state: %s, status: %s\", task.ID, task.TaskState, task.TaskStatus)\n\n\tif task.TaskState != schemas.CompletedTaskState {\n\t\treturn \"\", errors.Wrap(brrs.ErrFirmwareVerifyTask, taskInfo)\n\t}\n\n\tif task.TaskStatus != \"OK\" {\n\t\treturn \"\", errors.Wrap(brrs.ErrFirmwareVerifyTask, taskInfo)\n\t}\n\n\treturn c.redfish.StartUpdateForUploadedFirmware(ctx)\n}\n\nfunc (c *x12) firmwareTaskStatus(ctx context.Context, component, taskID string) (state constants.TaskState, status string, err error) {\n\tif err = c.supportsInstall(component); err != nil {\n\t\treturn \"\", \"\", errors.Wrap(brrs.ErrFirmwareTaskStatus, err.Error())\n\t}\n\n\treturn c.redfish.TaskStatus(ctx, taskID)\n}\n\nfunc (c *x12) getBootProgress() (*schemas.BootProgress, error) {\n\tbps, err := c.redfish.GetBootProgress()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn bps[0], nil\n}\n\n// this is some syntactic sugar to avoid having to code potentially provider- or model-specific knowledge into a caller\nfunc (c *x12) bootComplete() (bool, error) {\n\tbp, err := c.getBootProgress()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\t// we determined this by experiment on X12STH-SYS with redfish 1.14.0\n\treturn bp.LastState == schemas.SystemHardwareInitializationCompleteBootProgressTypes, nil\n}\n"
  }
]