diff --git a/pkg/config-api-provider/provider/data-sources/agent.go b/pkg/config-api-provider/provider/data-sources/agent.go new file mode 100644 index 00000000..490138f8 --- /dev/null +++ b/pkg/config-api-provider/provider/data-sources/agent.go @@ -0,0 +1,160 @@ +package datasources + +import ( + "context" + + config_api_client "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/config-api-client" + "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/terraform-provider-configuration/provider/util" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ datasource.DataSource = &agentDataSource{} + _ datasource.DataSourceWithConfigure = &agentDataSource{} +) + +func NewAgentDataSource() datasource.DataSource { + return &agentDataSource{} +} + +type agentDataSource struct { + client *config_api_client.APIClient +} + +type agentDataSourceModel struct { + Id types.String `tfsdk:"id"` + Serial types.String `tfsdk:"serial"` + Name types.String `tfsdk:"name"` + ModelNumber types.String `tfsdk:"model_number"` + WifiMacAddress types.String `tfsdk:"wifi_mac_address"` + EthernetMacAddress types.String `tfsdk:"ethernet_mac_address"` + AddressNote types.String `tfsdk:"address_note"` + Longitude types.Float32 `tfsdk:"longitude"` + Latitude types.Float32 `tfsdk:"latitude"` + Notes types.String `tfsdk:"notes"` + PcapMode types.String `tfsdk:"pcap_mode"` + Filter struct { + AgentID types.String `tfsdk:"agent_id"` + } `tfsdk:"filter"` +} + +func (d *agentDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_agent" +} + +func (d *agentDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + }, + "serial": schema.StringAttribute{ + Computed: true, + }, + "name": schema.StringAttribute{ + Computed: true, + }, + "model_number": schema.StringAttribute{ + Computed: true, + }, + "wifi_mac_address": schema.StringAttribute{ + Computed: true, + }, + "ethernet_mac_address": schema.StringAttribute{ + Computed: true, + }, + "address_note": schema.StringAttribute{ + Computed: true, + }, + "longitude": schema.Float32Attribute{ + Computed: true, + }, + "latitude": schema.Float32Attribute{ + Computed: true, + }, + "notes": schema.StringAttribute{ + Computed: true, + }, + "pcap_mode": schema.StringAttribute{ + Computed: true, + }, + "filter": schema.SingleNestedAttribute{ + Required: true, + Attributes: map[string]schema.Attribute{ + "agent_id": schema.StringAttribute{ + Required: true, + }, + }, + }, + }, + } +} + +func (d *agentDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var state agentDataSourceModel + + // Read configuration from request + diags := req.Config.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + request := d.client.ConfigurationAPI. + AgentsGet(ctx). + Id(state.Filter.AgentID.ValueString()) + + agentResponse, response, err := util.RetryFor429(request.Execute) + errorPresent, errorDetail := util.RaiseForStatus(response, err) + + errorSummary := util.GenerateErrorSummary("read", "uxi_agent") + + if errorPresent { + resp.Diagnostics.AddError(errorSummary, errorDetail) + return + } + + if len(agentResponse.Items) != 1 { + resp.Diagnostics.AddError(errorSummary, "Could not find specified data source") + return + } + + agent := agentResponse.Items[0] + + state.Id = types.StringValue(agent.Id) + state.Name = types.StringValue(agent.Name) + state.ModelNumber = types.StringPointerValue(agent.ModelNumber.Get()) + state.WifiMacAddress = types.StringPointerValue(agent.WifiMacAddress.Get()) + state.EthernetMacAddress = types.StringPointerValue(agent.EthernetMacAddress.Get()) + state.Notes = types.StringPointerValue(agent.Notes.Get()) + state.PcapMode = types.StringPointerValue(agent.PcapMode.Get()) + + // Set state + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +func (d *agentDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Add a nil check when handling ProviderData because Terraform + // sets that data after it calls the ConfigureProvider RPC. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*config_api_client.APIClient) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + "Data Source type: Agent. Please report this issue to the provider developers.", + ) + return + } + + d.client = client +} diff --git a/pkg/config-api-provider/provider/provider.go b/pkg/config-api-provider/provider/provider.go index a5f20b96..7f9ae59e 100644 --- a/pkg/config-api-provider/provider/provider.go +++ b/pkg/config-api-provider/provider/provider.go @@ -155,6 +155,7 @@ func (p *uxiConfigurationProvider) Configure(ctx context.Context, req provider.C // DataSources defines the data sources implemented in the provider. func (p *uxiConfigurationProvider) DataSources(_ context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ + datasources.NewAgentDataSource, datasources.NewGroupDataSource, datasources.NewSensorDataSource, datasources.NewWiredNetworkDataSource, diff --git a/pkg/config-api-provider/provider/resources/agent.go b/pkg/config-api-provider/provider/resources/agent.go index 47f8d131..29598c3b 100644 --- a/pkg/config-api-provider/provider/resources/agent.go +++ b/pkg/config-api-provider/provider/resources/agent.go @@ -3,7 +3,9 @@ package resources import ( "context" - // "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/config-api-client" + "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/config-api-client" + "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/terraform-provider-configuration/provider/util" + "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -48,7 +50,9 @@ func NewAgentResource() resource.Resource { return &agentResource{} } -type agentResource struct{} +type agentResource struct { + client *config_api_client.APIClient +} func (r *agentResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_agent" @@ -77,7 +81,23 @@ func (r *agentResource) Schema(_ context.Context, _ resource.SchemaRequest, resp } func (r *agentResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Add a nil check when handling ProviderData because Terraform + // sets that data after it calls the ConfigureProvider RPC. + if req.ProviderData == nil { + return + } + client, ok := req.ProviderData.(*config_api_client.APIClient) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + "Resource type: Group. Please report this issue to the provider developers.", + ) + return + } + + r.client = client } func (r *agentResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { @@ -89,7 +109,6 @@ func (r *agentResource) Create(ctx context.Context, req resource.CreateRequest, } func (r *agentResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - // Get current state var state agentResourceModel diags := req.State.Get(ctx, &state) resp.Diagnostics.Append(diags...) @@ -97,14 +116,29 @@ func (r *agentResource) Read(ctx context.Context, req resource.ReadRequest, resp return } - response := GetAgent(state.ID.ValueString()) + request := r.client.ConfigurationAPI. + AgentsGet(ctx). + Id(state.ID.ValueString()) + sensorResponse, response, err := util.RetryFor429(request.Execute) + errorPresent, errorDetail := util.RaiseForStatus(response, err) + + errorSummary := util.GenerateErrorSummary("read", "uxi_agent") - // Update state from client response - state.Name = types.StringValue(response.Name) - state.Notes = types.StringValue(response.Notes) - state.PCapMode = types.StringValue(response.PCapMode) + if errorPresent { + resp.Diagnostics.AddError(errorSummary, errorDetail) + return + } + + if len(sensorResponse.Items) != 1 { + resp.State.RemoveResource(ctx) + return + } + sensor := sensorResponse.Items[0] + + state.Name = types.StringValue(sensor.Name) + state.Notes = types.StringPointerValue(sensor.Notes.Get()) + state.PCapMode = types.StringPointerValue(sensor.PcapMode.Get()) - // Set refreshed state diags = resp.State.Set(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -158,22 +192,6 @@ func (r *agentResource) ImportState(ctx context.Context, req resource.ImportStat resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) } -// Get the agent using the configuration-api client -var GetAgent = func(uid string) AgentResponseModel { - // TODO: Query the agent using the client - - return AgentResponseModel{ - UID: uid, - Serial: "mock_serial", - Name: "mock_name", - ModelNumber: "mock_model_number", - WifiMacAddress: "mock_wifi_mac_address", - EthernetMacAddress: "mock_ethernet_mac_address", - Notes: "mock_notes", - PCapMode: "mock_pcap_mode", - } -} - // Update the agent using the configuration-api client var UpdateAgent = func(request AgentUpdateRequestModel) AgentResponseModel { // TODO: Query the agent using the client diff --git a/pkg/config-api-provider/test/data-sources/agent_test.go b/pkg/config-api-provider/test/data-sources/agent_test.go new file mode 100644 index 00000000..aee72f2c --- /dev/null +++ b/pkg/config-api-provider/test/data-sources/agent_test.go @@ -0,0 +1,141 @@ +package data_source_test + +import ( + "regexp" + "testing" + + "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/terraform-provider-configuration/test/provider" + "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/terraform-provider-configuration/test/util" + "github.com/h2non/gock" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/nbio/st" +) + +func TestAgentDataSource(t *testing.T) { + defer gock.Off() + mockOAuth := util.MockOAuth() + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: provider.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Test Read + { + PreConfig: func() { + util.MockGetAgent( + "uid", + util.GeneratePaginatedResponse([]map[string]interface{}{util.GenerateAgentResponseModel("uid", "")}), + 3, + ) + }, + Config: provider.ProviderConfig + ` + data "uxi_agent" "my_agent" { + filter = { + agent_id = "uid" + } + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.uxi_agent.my_agent", "id", "uid"), + ), + }, + }, + }) + + mockOAuth.Mock.Disable() +} + +func TestAgentDataSource429Handling(t *testing.T) { + defer gock.Off() + mockOAuth := util.MockOAuth() + var mock429 *gock.Response + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: provider.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + + // Test Read + { + PreConfig: func() { + mock429 = gock.New("https://test.api.capenetworks.com"). + Get("/networking-uxi/v1alpha1/agents"). + Reply(429). + SetHeaders(util.RateLimitingHeaders) + util.MockGetAgent( + "uid", + util.GeneratePaginatedResponse([]map[string]interface{}{util.GenerateAgentResponseModel("uid", "")}), + 3, + ) + }, + Config: provider.ProviderConfig + ` + data "uxi_agent" "my_agent" { + filter = { + agent_id = "uid" + } + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.uxi_agent.my_agent", "id", "uid"), + func(s *terraform.State) error { + st.Assert(t, mock429.Mock.Request().Counter, 0) + return nil + }, + ), + }, + }, + }) + + mockOAuth.Mock.Disable() +} + +func TestAgentDataSourceHttpErrorHandling(t *testing.T) { + defer gock.Off() + mockOAuth := util.MockOAuth() + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: provider.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // 5xx error + { + PreConfig: func() { + gock.New("https://test.api.capenetworks.com"). + Get("/networking-uxi/v1alpha1/agents"). + Reply(500). + JSON(map[string]interface{}{ + "httpStatusCode": 500, + "errorCode": "HPE_GL_ERROR_INTERNAL_SERVER_ERROR", + "message": "Current request cannot be processed due to unknown issue", + "debugId": "12312-123123-123123-1231212", + }) + }, + Config: provider.ProviderConfig + ` + data "uxi_agent" "my_agent" { + filter = { + agent_id = "uid" + } + } + `, + ExpectError: regexp.MustCompile(`(?s)Current request cannot be processed due to unknown issue\s*DebugID: 12312-123123-123123-1231212`), + }, + // Not found error + { + PreConfig: func() { + util.MockGetAgent( + "uid", + util.GeneratePaginatedResponse([]map[string]interface{}{}), + 1, + ) + }, + Config: provider.ProviderConfig + ` + data "uxi_agent" "my_agent" { + filter = { + agent_id = "uid" + } + } + `, + ExpectError: regexp.MustCompile(`Could not find specified data source`), + }, + }, + }) + + mockOAuth.Mock.Disable() +} diff --git a/pkg/config-api-provider/test/resources/agent_group_assignment_test.go b/pkg/config-api-provider/test/resources/agent_group_assignment_test.go index 02e471d7..8510958e 100644 --- a/pkg/config-api-provider/test/resources/agent_group_assignment_test.go +++ b/pkg/config-api-provider/test/resources/agent_group_assignment_test.go @@ -21,9 +21,11 @@ func TestAgentGroupAssignmentResource(t *testing.T) { { PreConfig: func() { // required for agent import - resources.GetAgent = func(uid string) resources.AgentResponseModel { - return util.GenerateAgentResponseModel(uid, "") - } + util.MockGetAgent( + "uid", + util.GeneratePaginatedResponse([]map[string]interface{}{util.GenerateAgentResponseModel("uid", "")}), + 2, + ) // required for group create util.MockPostGroup( @@ -83,13 +85,16 @@ func TestAgentGroupAssignmentResource(t *testing.T) { // Update and Read testing { PreConfig: func() { - resources.GetAgent = func(uid string) resources.AgentResponseModel { - if uid == "agent_uid" { - return util.GenerateAgentResponseModel(uid, "") - } else { - return util.GenerateAgentResponseModel(uid, "_2") - } - } + util.MockGetAgent( + "agent_uid_2", + util.GeneratePaginatedResponse([]map[string]interface{}{util.GenerateAgentResponseModel("agent_uid_2", "_2")}), + 2, + ) + util.MockGetAgent( + "agent_uid", + util.GeneratePaginatedResponse([]map[string]interface{}{util.GenerateAgentResponseModel("agent_uid", "")}), + 2, + ) util.MockGetGroup( "group_uid_2", util.GeneratePaginatedResponse([]map[string]interface{}{util.GenerateGroupResponseModel("group_uid_2", "_2", "_2")}), @@ -169,6 +174,16 @@ func TestAgentGroupAssignmentResource(t *testing.T) { // Delete testing automatically occurs in TestCase { PreConfig: func() { + util.MockGetAgent( + "agent_uid", + util.GeneratePaginatedResponse([]map[string]interface{}{util.GenerateAgentResponseModel("agent_uid", "")}), + 2, + ) + util.MockGetAgent( + "agent_uid_2", + util.GeneratePaginatedResponse([]map[string]interface{}{util.GenerateAgentResponseModel("agent_uid_2", "_2")}), + 1, + ) util.MockGetGroup( "group_uid", util.GeneratePaginatedResponse([]map[string]interface{}{util.GenerateGroupResponseModel("group_uid", "", "")}), diff --git a/pkg/config-api-provider/test/resources/agent_test.go b/pkg/config-api-provider/test/resources/agent_test.go index aff358ee..cd34160b 100644 --- a/pkg/config-api-provider/test/resources/agent_test.go +++ b/pkg/config-api-provider/test/resources/agent_test.go @@ -1,11 +1,13 @@ package resource_test import ( - "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/terraform-provider-configuration/provider/resources" "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/terraform-provider-configuration/test/provider" "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/terraform-provider-configuration/test/util" "github.com/h2non/gock" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + "github.com/nbio/st" "regexp" "testing" ) @@ -31,9 +33,11 @@ func TestAgentResource(t *testing.T) { // Importing an agent { PreConfig: func() { - resources.GetAgent = func(uid string) resources.AgentResponseModel { - return util.GenerateAgentResponseModel(uid, "") - } + util.MockGetAgent( + "uid", + util.GeneratePaginatedResponse([]map[string]interface{}{util.GenerateAgentResponseModel("uid", "")}), + 2, + ) }, Config: provider.ProviderConfig + ` resource "uxi_agent" "my_agent" { @@ -56,6 +60,13 @@ func TestAgentResource(t *testing.T) { }, // ImportState testing { + PreConfig: func() { + util.MockGetAgent( + "uid", + util.GeneratePaginatedResponse([]map[string]interface{}{util.GenerateAgentResponseModel("uid", "")}), + 1, + ) + }, ResourceName: "uxi_agent.my_agent", ImportState: true, ImportStateVerify: true, @@ -63,9 +74,18 @@ func TestAgentResource(t *testing.T) { // Update and Read testing { PreConfig: func() { - resources.GetAgent = func(uid string) resources.AgentResponseModel { - return util.GenerateAgentResponseModel(uid, "_2") - } + // original + util.MockGetAgent( + "uid", + util.GeneratePaginatedResponse([]map[string]interface{}{util.GenerateAgentResponseModel("uid", "")}), + 1, + ) + // updated + util.MockGetAgent( + "uid", + util.GeneratePaginatedResponse([]map[string]interface{}{util.GenerateAgentResponseModel("uid", "_2")}), + 1, + ) }, Config: provider.ProviderConfig + ` resource "uxi_agent" "my_agent" { @@ -85,3 +105,121 @@ func TestAgentResource(t *testing.T) { mockOAuth.Mock.Disable() } + +func TestAgentResource429Handling(t *testing.T) { + defer gock.Off() + mockOAuth := util.MockOAuth() + var request429 *gock.Response + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: provider.TestAccProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // we required terraform 1.7.0 and above for the `removed` block + tfversion.RequireAbove(tfversion.Version1_7_0), + }, + Steps: []resource.TestStep{ + // Importing a agent + { + PreConfig: func() { + request429 = gock.New("https://test.api.capenetworks.com"). + Get("/networking-uxi/v1alpha1/agents"). + Reply(429). + SetHeaders(util.RateLimitingHeaders) + util.MockGetAgent("uid", util.GeneratePaginatedResponse( + []map[string]interface{}{util.GenerateAgentResponseModel("uid", "")}), + 2, + ) + }, + Config: provider.ProviderConfig + ` + resource "uxi_agent" "my_agent" { + name = "name" + notes = "notes" + pcap_mode = "light" + } + + import { + to = uxi_agent.my_agent + id = "uid" + }`, + + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("uxi_agent.my_agent", "id", "uid"), + func(s *terraform.State) error { + st.Assert(t, request429.Mock.Request().Counter, 0) + return nil + }, + ), + }, + // Deletion occurs automatically + }, + }) + + mockOAuth.Mock.Disable() + +} +func TestAgentResourceHttpErrorHandling(t *testing.T) { + defer gock.Off() + mockOAuth := util.MockOAuth() + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: provider.TestAccProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // we required terraform 1.7.0 and above for the `removed` block + tfversion.RequireAbove(tfversion.Version1_7_0), + }, + Steps: []resource.TestStep{ + // Read 5xx error + { + PreConfig: func() { + gock.New("https://test.api.capenetworks.com"). + Get("/networking-uxi/v1alpha1/agents"). + Reply(500). + JSON(map[string]interface{}{ + "httpStatusCode": 500, + "errorCode": "HPE_GL_ERROR_INTERNAL_SERVER_ERROR", + "message": "Current request cannot be processed due to unknown issue", + "debugId": "12312-123123-123123-1231212", + }) + }, + Config: provider.ProviderConfig + ` + resource "uxi_agent" "my_agent" { + name = "name" + notes = "notes" + pcap_mode = "light" + } + + import { + to = uxi_agent.my_agent + id = "uid" + }`, + + ExpectError: regexp.MustCompile(`(?s)Current request cannot be processed due to unknown issue\s*DebugID: 12312-123123-123123-1231212`), + }, + // Read not found + { + PreConfig: func() { + util.MockGetAgent( + "uid", + util.GeneratePaginatedResponse([]map[string]interface{}{}), + 1, + ) + }, + Config: provider.ProviderConfig + ` + resource "uxi_agent" "my_agent" { + name = "name" + notes = "notes" + pcap_mode = "light" + } + + import { + to = uxi_agent.my_agent + id = "uid" + }`, + + ExpectError: regexp.MustCompile(`Error: Cannot import non-existent remote object`), + }, + }, + }) + + mockOAuth.Mock.Disable() +} diff --git a/pkg/config-api-provider/test/util/utils.go b/pkg/config-api-provider/test/util/utils.go index 5b6ca886..50a68704 100644 --- a/pkg/config-api-provider/test/util/utils.go +++ b/pkg/config-api-provider/test/util/utils.go @@ -25,7 +25,21 @@ func GenerateSensorResponseModel(uid string, postfix string) map[string]interfac } } -func GenerateAgentResponseModel(uid string, postfix string) resources.AgentResponseModel { +func GenerateAgentResponseModel(uid string, postfix string) map[string]interface{} { + return map[string]interface{}{ + "id": 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, + "type": "networking-uxi/sensor", + } +} + +func GenerateMockedAgentResponseModel(uid string, postfix string) resources.AgentResponseModel { return resources.AgentResponseModel{ UID: uid, Serial: "serial" + postfix, @@ -192,6 +206,16 @@ func MockOAuth() *gock.Response { } +func MockGetAgent(uid string, response map[string]interface{}, times int) { + gock.New("https://test.api.capenetworks.com"). + Get("/networking-uxi/v1alpha1/agents"). + MatchHeader("Authorization", "mock_token"). + MatchParam("id", uid). + 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").