diff --git a/internal/provider/resources/agent.go b/internal/provider/resources/agent.go index 4be102d7..354fcfa7 100644 --- a/internal/provider/resources/agent.go +++ b/internal/provider/resources/agent.go @@ -27,25 +27,6 @@ type agentResourceModel struct { PCapMode types.String `tfsdk:"pcap_mode"` } -// TODO: Switch this to use the Client Model when that becomes available -type AgentResponseModel struct { - UID string - Serial string - Name string - ModelNumber string - WifiMacAddress string - EthernetMacAddress string - Notes string - PCapMode string -} - -// TODO: Switch this to use the Client Model when that becomes available -type AgentUpdateRequestModel struct { - Name string - Notes string - PCapMode string -} - func NewAgentResource() resource.Resource { return &agentResource{} } @@ -142,7 +123,7 @@ func (r *agentResource) Read( request := r.client.ConfigurationAPI. AgentsGet(ctx). Id(state.ID.ValueString()) - sensorResponse, response, err := util.RetryFor429(request.Execute) + agentResponse, response, err := util.RetryFor429(request.Execute) errorPresent, errorDetail := util.RaiseForStatus(response, err) errorSummary := util.GenerateErrorSummary("read", "uxi_agent") @@ -152,16 +133,16 @@ func (r *agentResource) Read( return } - if len(sensorResponse.Items) != 1 { + if len(agentResponse.Items) != 1 { resp.State.RemoveResource(ctx) return } - sensor := sensorResponse.Items[0] + agent := agentResponse.Items[0] - state.ID = types.StringValue(sensor.Id) - state.Name = types.StringValue(sensor.Name) - state.Notes = types.StringPointerValue(sensor.Notes.Get()) - state.PCapMode = types.StringPointerValue(sensor.PcapMode.Get()) + state.ID = types.StringValue(agent.Id) + state.Name = types.StringValue(agent.Name) + state.Notes = types.StringPointerValue(agent.Notes.Get()) + state.PCapMode = types.StringPointerValue(agent.PcapMode.Get()) diags = resp.State.Set(ctx, &state) resp.Diagnostics.Append(diags...) @@ -183,18 +164,35 @@ func (r *agentResource) Update( return } - // Update existing item - response := UpdateAgent(AgentUpdateRequestModel{ - Name: plan.Name.ValueString(), - Notes: plan.Notes.ValueString(), - PCapMode: plan.PCapMode.ValueString(), - }) + patchRequest := config_api_client.NewAgentsPatchRequest() + patchRequest.Name = plan.Name.ValueStringPointer() + if !plan.Notes.IsUnknown() { + patchRequest.Notes = plan.Notes.ValueStringPointer() + } + if !plan.PCapMode.IsUnknown() { + patchRequest.PcapMode = plan.PCapMode.ValueStringPointer() + } + request := r.client.ConfigurationAPI. + AgentsPatch(ctx, plan.ID.ValueString()). + AgentsPatchRequest(*patchRequest) + agent, response, err := util.RetryFor429(request.Execute) - // Update resource state with updated items - plan.ID = types.StringValue(response.UID) - plan.Name = types.StringValue(response.Name) - plan.Notes = types.StringValue(response.Notes) - plan.PCapMode = types.StringValue(response.PCapMode) + errorPresent, errorDetail := util.RaiseForStatus(response, err) + + if errorPresent { + resp.Diagnostics.AddError(util.GenerateErrorSummary("update", "uxi_agent"), errorDetail) + return + } + + // Update the state to match the plan (replace with response from client) + plan.ID = types.StringValue(agent.Id) + plan.Name = types.StringValue(agent.Name) + if agent.Notes.Get() != nil { + plan.Notes = types.StringValue(*agent.Notes.Get()) + } + if agent.PcapMode.Get() != nil { + plan.PCapMode = types.StringValue(*agent.PcapMode.Get()) + } // Set state to fully populated data diags = resp.State.Set(ctx, plan) @@ -235,19 +233,3 @@ func (r *agentResource) ImportState( ) { resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) } - -// Update the agent using the configuration-api client -var UpdateAgent = func(request AgentUpdateRequestModel) AgentResponseModel { - // TODO: Query the agent using the client - - return AgentResponseModel{ - UID: "mock_uid", - Serial: "mock_serial", - Name: request.Name, - ModelNumber: "mock_model_number", - WifiMacAddress: "mock_wifi_mac_address", - EthernetMacAddress: "mock_ethernet_mac_address", - Notes: request.Notes, - PCapMode: request.PCapMode, - } -} diff --git a/test/mocked/resources/agent_test.go b/test/mocked/resources/agent_test.go index 9d8e8c6c..7c5fc117 100644 --- a/test/mocked/resources/agent_test.go +++ b/test/mocked/resources/agent_test.go @@ -4,7 +4,6 @@ import ( "regexp" "testing" - "github.com/aruba-uxi/terraform-provider-hpeuxi/internal/provider/resources" "github.com/aruba-uxi/terraform-provider-hpeuxi/test/mocked/provider" "github.com/aruba-uxi/terraform-provider-hpeuxi/test/mocked/util" "github.com/h2non/gock" @@ -90,9 +89,12 @@ func TestAgentResource(t *testing.T) { ), 1, ) - resources.UpdateAgent = func(request resources.AgentUpdateRequestModel) resources.AgentResponseModel { - return util.GenerateMockedAgentResponseModel("uid", "_2") - } + util.MockUpdateAgent( + "uid", + util.GenerateAgentRequestUpdateModel("_2"), + util.GenerateAgentResponseModel("uid", "_2"), + 1, + ) // updated util.MockGetAgent( "uid", @@ -175,6 +177,50 @@ func TestAgentResource429Handling(t *testing.T) { }, ), }, + // Update testing + { + PreConfig: func() { + // original + util.MockGetAgent( + "uid", + util.GeneratePaginatedResponse( + []map[string]interface{}{util.GenerateAgentResponseModel("uid", "")}, + ), + 1, + ) + request429 = gock.New("https://test.api.capenetworks.com"). + Patch("/networking-uxi/v1alpha1/agents"). + Reply(429). + SetHeaders(util.RateLimitingHeaders) + util.MockUpdateAgent( + "uid", + util.GenerateAgentRequestUpdateModel("_2"), + util.GenerateAgentResponseModel("uid", "_2"), + 1, + ) + // updated + util.MockGetAgent( + "uid", + util.GeneratePaginatedResponse( + []map[string]interface{}{util.GenerateAgentResponseModel("uid", "_2")}, + ), + 1, + ) + }, + Config: provider.ProviderConfig + ` + resource "uxi_agent" "my_agent" { + name = "name_2" + notes = "notes_2" + pcap_mode = "light_2" + }`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("uxi_agent.my_agent", "name", "name_2"), + func(s *terraform.State) error { + st.Assert(t, request429.Mock.Request().Counter, 0) + return nil + }, + ), + }, // Delete testing { PreConfig: func() { @@ -292,6 +338,39 @@ func TestAgentResourceHttpErrorHandling(t *testing.T) { resource.TestCheckResourceAttr("uxi_agent.my_agent", "id", "uid"), ), }, + // update 4xx + { + PreConfig: func() { + // original + util.MockGetAgent( + "uid", + util.GeneratePaginatedResponse( + []map[string]interface{}{util.GenerateAgentResponseModel("uid", "")}, + ), + 1, + ) + // patch agent - with error + gock.New("https://test.api.capenetworks.com"). + Patch("/networking-uxi/v1alpha1/agents/uid"). + Reply(422). + JSON(map[string]interface{}{ + "httpStatusCode": 422, + "errorCode": "HPE_GL_UXI_INVALID_PCAP_MODE_ERROR", + "message": "Unable to update agent - pcap_mode must be one the following ['light', 'full', 'off'].", + "debugId": "12312-123123-123123-1231212", + "type": "hpe.greenlake.uxi.invalid_pcap_mode", + }) + }, + Config: provider.ProviderConfig + ` + resource "uxi_agent" "my_agent" { + name = "name_2" + notes = "notes_2" + pcap_mode = "light_2" + }`, + ExpectError: regexp.MustCompile( + `(?s)Unable to update agent - pcap_mode must be one the following \['light',\s*'full', 'off'\].\s*DebugID: 12312-123123-123123-1231212`, + ), + }, // Delete 4xx { PreConfig: func() { diff --git a/test/mocked/util/utils.go b/test/mocked/util/utils.go index 3fe9d478..8f1a0c10 100644 --- a/test/mocked/util/utils.go +++ b/test/mocked/util/utils.go @@ -26,13 +26,21 @@ func GenerateSensorResponseModel(uid string, postfix string) map[string]interfac func GenerateSensorRequestUpdateModel(postfix string) map[string]interface{} { return map[string]interface{}{ - "name": "name_2", + "name": "name" + postfix, "addressNote": "address_note" + postfix, "notes": "notes" + postfix, "pcapMode": "light" + postfix, } } +func GenerateAgentRequestUpdateModel(postfix string) map[string]interface{} { + return map[string]interface{}{ + "name": "name" + postfix, + "notes": "notes" + postfix, + "pcapMode": "light" + postfix, + } +} + func GenerateAgentResponseModel(uid string, postfix string) map[string]interface{} { return map[string]interface{}{ "id": uid, @@ -47,19 +55,6 @@ func GenerateAgentResponseModel(uid string, postfix string) map[string]interface } } -func GenerateMockedAgentResponseModel(uid string, postfix string) resources.AgentResponseModel { - return resources.AgentResponseModel{ - UID: uid, - Serial: "serial" + postfix, - Name: "name" + postfix, - ModelNumber: "model_number" + postfix, - WifiMacAddress: "wifi_mac_address" + postfix, - EthernetMacAddress: "ethernet_mac_address" + postfix, - Notes: "notes" + postfix, - PCapMode: "light" + postfix, - } -} - func GenerateNonRootGroupResponseModel( uid string, nonReplacementFieldPostfix string, @@ -252,6 +247,22 @@ func MockDeleteAgent(uid string, times int) { Reply(204) } +func MockUpdateAgent( + uid string, + request map[string]interface{}, + response map[string]interface{}, + times int, +) { + gock.New("https://test.api.capenetworks.com"). + Patch("/networking-uxi/v1alpha1/agents/"+uid). + MatchHeader("Content-Type", "application/merge-patch+json"). + MatchHeader("Authorization", "mock_token"). + JSON(request). + Times(times). + Reply(200). + JSON(response) +} + func MockPostGroup(request map[string]interface{}, response map[string]interface{}, times int) { gock.New("https://test.api.capenetworks.com"). Post("/networking-uxi/v1alpha1/groups"). @@ -314,13 +325,11 @@ func MockUpdateSensor( response map[string]interface{}, times int, ) { - // body, _ := json.Marshal(request) gock.New("https://test.api.capenetworks.com"). Patch("/networking-uxi/v1alpha1/sensors/"+uid). - // TODO: uncomment this once the patch endpoint uses correct header and body casing - // MatchHeader("Content-Type", "application/merge-patch+json"). + MatchHeader("Content-Type", "application/merge-patch+json"). MatchHeader("Authorization", "mock_token"). - // BodyString(string(body)). + JSON(request). Times(times). Reply(200). JSON(response)