Skip to content

feat: add ActionHeaders to only include If-Match for same-URI requests#512

Draft
y-isono wants to merge 3 commits intostmcginnis:mainfrom
y-isono:fix/action-headers-and-settings-target
Draft

feat: add ActionHeaders to only include If-Match for same-URI requests#512
y-isono wants to merge 3 commits intostmcginnis:mainfrom
y-isono:fix/action-headers-and-settings-target

Conversation

@y-isono
Copy link
Copy Markdown
Contributor

@y-isono y-isono commented Mar 5, 2026

Fixes #516

Entity.Headers() currently includes If-Match with the entity's ETag on all requests, regardless of whether the target URI matches the entity's ODataID. Per RFC 7232 Section 3.1, If-Match compares against the target resource, so the entity's ETag does not apply when the target URI differs. This affects action POST endpoints (e.g., Reset, InsertMedia) as well as PATCH operations targeting a different URI (e.g., Settings resources).

Additionally, #511 added @Redfish.Settings support to SetBoot(), which this PR preserves. However, the ETag handling should be improved: instead of sanitizing the -gzip suffix from HTTP Etag headers, this PR fetches the Settings resource's ETag through the standard entity path, which picks up @odata.etag from the JSON body and is not affected by gzip content-encoding.

This PR reverts #510 and #511, and replaces them with an improved approach.

1. Headers(targetURI) — URI-aware If-Match (replaces #510)

Instead of retrying on 412 Precondition Failed (#510), this PR extends Headers() to accept a targetURI parameter:

  • Headers(targetURI) includes If-Match only when targetURI matches the entity's ODataID. When the target URI differs — whether for action POST (e.g., /Systems/1/Actions/ComputerSystem.Reset) or PATCH to a Settings resource — If-Match is correctly omitted.
  • Removed Is412PreconditionFailed() and the 412 retry logic from PostWithTask() — no longer needed since If-Match is not sent to different URIs in the first place.
  • All call sites (Patch(), PostWithResponse(), PostWithTask()) now pass the target URI to Headers().

2. SetBoot settingsTarget improvement (replaces #511)

SetBoot() continues to use settingsTarget from @Redfish.Settings, but the ETag handling is improved:

  • Removed sanitizeETag() — The previous approach stripped -gzip suffixes from HTTP Etag headers. Instead, SetBoot() now uses GetObject to fetch the Settings resource as an Entity, which picks up @odata.etag from the JSON body. The JSON @odata.etag is not affected by gzip content-encoding, eliminating the need for suffix handling. When @odata.etag is not present in the JSON body, Entity.Get() falls back to the HTTP Etag header as before.
  • UpdateBootAttributesApplyAt() — Updated with the same GetObject + Entity.Headers() approach.

Testing

Unit tests: All existing tests pass (go test ./schemas/). New tests added: TestHeaders (4 cases: same URI, different URI, no ETag, disabled), TestSetBootWithSettings, TestSetBootWithoutSettings.

Integration tests on Dell PowerEdge R470 (iDRAC10): Reset, InsertMedia (real ISO), EjectMedia, SetBoot PXE/Cd, SubmitTestEvent — all pass.

References

@y-isono y-isono marked this pull request as draft March 5, 2026 07:36
@y-isono y-isono changed the title Fix/action headers and settings target feat: add ActionHeaders to only include If-Match for same-URI requests Mar 5, 2026
@y-isono y-isono changed the title feat: add ActionHeaders to only include If-Match for same-URI requests feat: add ActionHeaders to only include If-Match for same-URI requests Mar 5, 2026
@y-isono
Copy link
Copy Markdown
Contributor Author

y-isono commented Mar 5, 2026

Test Evidence: ActionHeaders + settingsTarget

Hardware: Dell PowerEdge R470 (iDRAC10)


Test Code

main.go — ActionHeaders integration test (click to expand)
// BMC_HOST=<bmc-ip> BMC_USER=<user> BMC_PASS=<pass> go run main.go [--dry-run] [--skip-reset]
package main

import (
    "crypto/tls"
    "flag"
    "fmt"
    "net/http"
    "os"
    "strings"

    gofish "github.com/stmcginnis/gofish"
    "github.com/stmcginnis/gofish/schemas"
)

func main() {
    dryRun := flag.Bool("dry-run", false, "Only show debug info")
    skipReset := flag.Bool("skip-reset", false, "Skip Reset test")
    flag.Parse()

    host := os.Getenv("BMC_HOST")
    user := os.Getenv("BMC_USER")
    pass := os.Getenv("BMC_PASS")

    config := gofish.ClientConfig{
        Endpoint: "https://" + host, Username: user, Password: pass, Insecure: true,
        HTTPClient: &http.Client{
            Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}},
        },
    }
    client, _ := gofish.Connect(config)
    defer client.Logout()
    service := client.GetService()
    systems, _ := service.Systems()
    system := systems[0]

    // Debug: verify ActionHeaders behavior
    // ActionHeaders(ODataID)     → includes If-Match
    // ActionHeaders(actionURI)   → empty (no If-Match)

    // Test 1: SetBoot Cd OneTimeBoot (settingsTarget + @odata.etag)
    system.SetBoot(&schemas.Boot{
        BootSourceOverrideTarget:  schemas.CdBootSource,
        BootSourceOverrideEnabled: schemas.OnceBootSourceOverrideEnabled,
    })

    // Test 2: SetBoot PXE OneTimeBoot
    system.SetBoot(&schemas.Boot{
        BootSourceOverrideTarget:  schemas.PxeBootSource,
        BootSourceOverrideEnabled: schemas.OnceBootSourceOverrideEnabled,
    })

    // Test 3: Restore boot settings
    system.SetBoot(&schemas.Boot{
        BootSourceOverrideTarget:  schemas.NoneBootSource,
        BootSourceOverrideEnabled: schemas.DisabledBootSourceOverrideEnabled,
    })

    // Test 4: Reset (ActionHeaders — no If-Match)
    system.Reset(schemas.GracefulShutdownResetType) // or OnResetType

    // Test 5: VirtualMedia InsertMedia (real ISO)
    isoURL := os.Getenv("ISO_URL")
    vmList, _ := system.VirtualMedia() // or via Managers
    cdVM := vmList[0] // find CD/DVD device
    cdVM.InsertMedia(&schemas.VirtualMediaInsertMediaParameters{Image: isoURL})

    // Test 6: VirtualMedia EjectMedia
    cdVM.EjectMedia()

    // Test 7: LogService ClearLog
    managers, _ := service.Managers()
    logServices, _ := managers[0].LogServices()
    logServices[0].ClearLog("") // SEL log

    // Test 8: EventService SubmitTestEvent
    eventSvc, _ := service.EventService()
    eventSvc.SubmitTestEvent(&schemas.EventServiceSubmitTestEventParameters{
        EventGroupID: 123, EventID: "Test", EventType: schemas.AlertEventType,
        Message: "ActionHeaders test", MessageID: "Test.1.0.Test", Severity: "OK",
    })
}

Integration Test Results (Dell iDRAC10)

All tests passed with no 412 errors. ISO was served from an internal HTTP server.

# Test Resource Result Notes
1 SetBoot (Cd OneTimeBoot) ComputerSystem ✅ PASS PATCH to Settings URI via @odata.etag
2 SetBoot (PXE OneTimeBoot) ComputerSystem ✅ PASS Same settingsTarget flow
3 Restore boot settings ComputerSystem ✅ PASS Restored to None/Disabled
4 Reset (GracefulShutdown) ComputerSystem ✅ PASS No If-Match sent, no 412
5 InsertMedia (real ISO) VirtualMedia ✅ PASS ISO mounted successfully
6 EjectMedia VirtualMedia ✅ PASS ISO unmounted successfully
7 ClearLog (SEL) LogService ✅ PASS SEL cleared, no 412
8 SubmitTestEvent EventService ✅ PASS Test event sent, no 412

Unit Test Results

=== RUN   TestActionHeaders
=== RUN   TestActionHeaders/same_URI_includes_If-Match
=== RUN   TestActionHeaders/different_URI_omits_If-Match
=== RUN   TestActionHeaders/no_ETag_omits_If-Match
=== RUN   TestActionHeaders/disableEtagMatch_omits_If-Match
--- PASS: TestActionHeaders (0.00s)

=== RUN   TestSetBootWithSettings
--- PASS: TestSetBootWithSettings (0.00s)

=== RUN   TestSetBootWithoutSettings
--- PASS: TestSetBootWithoutSettings (0.00s)

PASS

…ODataID

Headers(targetURI) now includes If-Match only when targetURI matches
the entity ODataID. This prevents sending If-Match to action POST
endpoints (e.g., Reset, InsertMedia) whose URI differs from the
resource, which caused 412 Precondition Failed on Dell iDRAC10.

Also updates SetBoot to use @Redfish.Settings target URI with
@odata.etag for concurrency control on Settings resources.

Fixes stmcginnis#516
@y-isono y-isono force-pushed the fix/action-headers-and-settings-target branch from 0512619 to 701a295 Compare March 18, 2026 15:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

If-Match sent on action POST to different URIs; SetBoot() ignores @Redfish.Settings

1 participant