diff --git a/internal/rps/message.go b/internal/rps/message.go index b7093915..a57bdf33 100644 --- a/internal/rps/message.go +++ b/internal/rps/message.go @@ -40,6 +40,28 @@ type StatusMessage struct { Network string `json:"Network,omitempty"` CIRAConnection string `json:"CIRAConnection,omitempty"` TLSConfiguration string `json:"TLSConfiguration,omitempty"` + // Components carries the additive, structured per-component provisioning result + // introduced by RPS (rps#2665). Older RPS releases omit it; in that case the flat + // fields above remain the source of truth. + Components *ComponentResults `json:"Components,omitempty"` +} + +// ComponentResult is the per-component outcome of an activation step. +type ComponentResult struct { + Result string `json:"Result"` + Mode string `json:"Mode,omitempty"` + Details string `json:"Details,omitempty"` + ErrorCode int `json:"ErrorCode"` +} + +// ComponentResults groups the structured per-component activation results. +type ComponentResults struct { + Activation *ComponentResult `json:"Activation,omitempty"` + WiredNetwork *ComponentResult `json:"WiredNetwork,omitempty"` + WirelessNetwork *ComponentResult `json:"WirelessNetwork,omitempty"` + TLS *ComponentResult `json:"TLS,omitempty"` + CIRAProxy *ComponentResult `json:"CIRAProxy,omitempty"` + CIRAConnection *ComponentResult `json:"CIRAConnection,omitempty"` } // MessagePayload struct is used for the initial request to RPS to activate or manage a device diff --git a/internal/rps/rps.go b/internal/rps/rps.go index 37a3ae5c..8f3ecf5e 100644 --- a/internal/rps/rps.go +++ b/internal/rps/rps.go @@ -235,10 +235,7 @@ func (amt *AMTActivationServer) ProcessMessage(message []byte) []byte { log.Error(err) log.Info(activation.Message) } else { - log.Info("Status: " + statusMessage.Status) - log.Info("Network: " + statusMessage.Network) - log.Info("CIRA: " + statusMessage.CIRAConnection) - log.Info("TLS: " + statusMessage.TLSConfiguration) + logStatusMessage(statusMessage) } return nil @@ -263,6 +260,46 @@ func (amt *AMTActivationServer) ProcessMessage(message []byte) []byte { return msgPayload } +// logStatusMessage reports the activation result. When RPS provides the structured +// per-component breakdown (rps#2665) it is logged component-by-component; otherwise the +// legacy flat status fields are logged for compatibility with older RPS releases. +func logStatusMessage(statusMessage StatusMessage) { + if statusMessage.Components != nil { + c := statusMessage.Components + logComponentResult("Activation", c.Activation) + logComponentResult("Wired Network", c.WiredNetwork) + logComponentResult("Wireless Network", c.WirelessNetwork) + logComponentResult("TLS", c.TLS) + logComponentResult("CIRA Proxy", c.CIRAProxy) + logComponentResult("CIRA Connection", c.CIRAConnection) + + return + } + + log.Info("Status: " + statusMessage.Status) + log.Info("Network: " + statusMessage.Network) + log.Info("CIRA: " + statusMessage.CIRAConnection) + log.Info("TLS: " + statusMessage.TLSConfiguration) +} + +// logComponentResult logs a single component result, skipping components RPS did not report. +func logComponentResult(label string, result *ComponentResult) { + if result == nil { + return + } + + line := label + ": " + result.Result + if result.Details != "" { + line += " (" + result.Details + ")" + } + + if result.Result == "Failure" { + log.Error(line) + } else { + log.Info(line) + } +} + func (amt *AMTActivationServer) GenerateHeartbeatResponse(activation Message) ([]byte, error) { activation.Method = "heartbeat_response" activation.Status = "success" diff --git a/internal/rps/rps_test.go b/internal/rps/rps_test.go index 0006e82e..0e1824d9 100644 --- a/internal/rps/rps_test.go +++ b/internal/rps/rps_test.go @@ -227,6 +227,43 @@ func TestProcessMessageSuccess(t *testing.T) { assert.Nil(t, decodedMessage) } +func TestProcessMessageStructuredSuccess(t *testing.T) { + // RPS rps#2665 structured per-component result rides alongside the legacy flat fields. + activation := `{ + "method": "success", + "message": "{\"Status\":\"Admin control mode.\",\"Components\":{\"Activation\":{\"Result\":\"Success\",\"Mode\":\"Admin control mode.\",\"ErrorCode\":0},\"WirelessNetwork\":{\"Result\":\"Failure\",\"Details\":\"Failed to add 1\",\"ErrorCode\":1}}}" + }` + server := NewAMTActivationServer(testFlags) + server.Connect(true) + decodedMessage := server.ProcessMessage([]byte(activation)) + assert.Nil(t, decodedMessage) +} + +func TestLogStatusMessageStructured(t *testing.T) { + // Structured Components present: per-component lines are logged, no panic. + statusMessage := StatusMessage{ + Status: "Admin control mode.", + Components: &ComponentResults{ + Activation: &ComponentResult{Result: "Success", Mode: "Admin control mode.", ErrorCode: 0}, + WirelessNetwork: &ComponentResult{Result: "Failure", Details: "Failed to add 1", ErrorCode: 1}, + }, + } + + assert.NotPanics(t, func() { logStatusMessage(statusMessage) }) +} + +func TestLogStatusMessageLegacyFallback(t *testing.T) { + // No Components: falls back to legacy flat-field logging. + statusMessage := StatusMessage{ + Status: "Admin control mode.", + Network: "configured", + CIRAConnection: "configured", + TLSConfiguration: "configured", + } + + assert.NotPanics(t, func() { logStatusMessage(statusMessage) }) +} + func TestProcessMessageUnformattedSuccess(t *testing.T) { activation := `{ "method": "success",