From 89407a8cbb07950df20db17376aac43818cee468 Mon Sep 17 00:00:00 2001 From: Weilue Luo Date: Wed, 4 Oct 2023 12:45:26 +0100 Subject: [PATCH] fix(LH-70705): Fix FTD onboarding fails when ftd is already register (#71) --- client/device/cloudfmc/fmcconfig/common.go | 11 ++ .../cloudfmc/fmcconfig/read_devicerecord.go | 39 ++++ .../fmcconfig/readall_devicerecords.go | 39 ++++ .../cloudftd/cloudftdonboarding/create.go | 38 +++- .../cloudftdonboarding/create_test.go | 172 +++++++++++++++++- .../cloudftdonboarding/fixture_test.go | 35 +++- client/device/cloudftd/fixture_test.go | 2 +- client/internal/url/url.go | 8 + client/model/accesspolicies/accesspolicies.go | 67 ------- .../cloudfmc/accesspolicies/accesspolicies.go | 43 +---- .../model/cloudfmc/accesspolicies/builder.go | 30 --- .../cloudfmc/fmcconfig/alldevicerecords.go | 7 + client/model/cloudfmc/fmcconfig/common.go | 11 ++ .../model/cloudfmc/fmcconfig/devicerecord.go | 54 ++++++ client/model/cloudfmc/internal/builder.go | 29 +++ client/model/cloudfmc/internal/common.go | 54 ++++++ provider/examples/resources/ftd/cdo_ftd.tf | 2 +- provider/internal/device/ftd/resource.go | 4 + provider/internal/device/ios/resource.go | 4 + 19 files changed, 499 insertions(+), 150 deletions(-) create mode 100644 client/device/cloudfmc/fmcconfig/common.go create mode 100644 client/device/cloudfmc/fmcconfig/read_devicerecord.go create mode 100644 client/device/cloudfmc/fmcconfig/readall_devicerecords.go delete mode 100644 client/model/accesspolicies/accesspolicies.go delete mode 100644 client/model/cloudfmc/accesspolicies/builder.go create mode 100644 client/model/cloudfmc/fmcconfig/alldevicerecords.go create mode 100644 client/model/cloudfmc/fmcconfig/common.go create mode 100644 client/model/cloudfmc/fmcconfig/devicerecord.go create mode 100644 client/model/cloudfmc/internal/builder.go diff --git a/client/device/cloudfmc/fmcconfig/common.go b/client/device/cloudfmc/fmcconfig/common.go new file mode 100644 index 00000000..ec63c2fc --- /dev/null +++ b/client/device/cloudfmc/fmcconfig/common.go @@ -0,0 +1,11 @@ +package fmcconfig + +import "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/cloudfmc/fmcconfig" + +type Item = fmcconfig.Item + +var NewItem = fmcconfig.NewItem + +type Link = fmcconfig.Links + +var NewLinks = fmcconfig.NewLinks diff --git a/client/device/cloudfmc/fmcconfig/read_devicerecord.go b/client/device/cloudfmc/fmcconfig/read_devicerecord.go new file mode 100644 index 00000000..9a5c890e --- /dev/null +++ b/client/device/cloudfmc/fmcconfig/read_devicerecord.go @@ -0,0 +1,39 @@ +package fmcconfig + +import ( + "context" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/http" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/url" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/cloudfmc/fmcconfig" +) + +type ReadDeviceRecordInput struct { + FmcDomainUid string + FmcHostname string + DeviceRecordUid string +} + +func NewReadDeviceRecordInput(fmcDomainUid, fmcHostname, deviceRecordUid string) ReadDeviceRecordInput { + return ReadDeviceRecordInput{ + FmcDomainUid: fmcDomainUid, + FmcHostname: fmcHostname, + DeviceRecordUid: deviceRecordUid, + } +} + +type ReadDeviceRecordOutput = fmcconfig.DeviceRecord + +func ReadDeviceRecord(ctx context.Context, client http.Client, readInp ReadDeviceRecordInput) (*ReadDeviceRecordOutput, error) { + + readUrl := url.ReadFmcDeviceRecord(client.BaseUrl(), readInp.FmcDomainUid, readInp.DeviceRecordUid) + + req := client.NewGet(ctx, readUrl) + req.Header.Add("Fmc-Hostname", readInp.FmcHostname) + + var readOutp fmcconfig.DeviceRecord + if err := req.Send(&readOutp); err != nil { + return nil, err + } + + return &readOutp, nil +} diff --git a/client/device/cloudfmc/fmcconfig/readall_devicerecords.go b/client/device/cloudfmc/fmcconfig/readall_devicerecords.go new file mode 100644 index 00000000..b71a3bad --- /dev/null +++ b/client/device/cloudfmc/fmcconfig/readall_devicerecords.go @@ -0,0 +1,39 @@ +package fmcconfig + +import ( + "context" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/http" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/url" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/cloudfmc/fmcconfig" +) + +type ReadAllDeviceRecordsInput struct { + FmcDomainUid string + FmcHostname string +} + +func NewReadAllDeviceRecordsInput(fmcDomainUid string, fmcHostname string) ReadAllDeviceRecordsInput { + return ReadAllDeviceRecordsInput{ + FmcDomainUid: fmcDomainUid, + FmcHostname: fmcHostname, + } +} + +type ReadAllDeviceRecordsOutput = fmcconfig.AllDeviceRecords + +var NewReadAllDeviceRecordsOutputBuilder = fmcconfig.NewAllDeviceRecordsBuilder + +func ReadAllDeviceRecords(ctx context.Context, client http.Client, readInp ReadAllDeviceRecordsInput) (*ReadAllDeviceRecordsOutput, error) { + + readUrl := url.ReadFmcAllDeviceRecords(client.BaseUrl(), readInp.FmcDomainUid) + + req := client.NewGet(ctx, readUrl) + req.Header.Add("Fmc-Hostname", readInp.FmcHostname) + + var readOutp ReadAllDeviceRecordsOutput + if err := req.Send(&readOutp); err != nil { + return nil, err + } + + return &readOutp, nil +} diff --git a/client/device/cloudftd/cloudftdonboarding/create.go b/client/device/cloudftd/cloudftdonboarding/create.go index dd209403..9b2d5bdd 100644 --- a/client/device/cloudftd/cloudftdonboarding/create.go +++ b/client/device/cloudftd/cloudftdonboarding/create.go @@ -3,6 +3,7 @@ package cloudftdonboarding import ( "context" "fmt" + "strings" "time" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/cloudfmc" @@ -49,6 +50,39 @@ func Create(ctx context.Context, client http.Client, createInp CreateInput) (*Cr } fmcDomainUid := readFmcDomainRes.Items[0].Uuid + // 1.5 check device already registered + // 1.5.1 read FTD name + readFtdOutp, err := cloudftd.ReadByUid(ctx, client, cloudftd.NewReadByUidInput(createInp.FtdUid)) + if err != nil { + return nil, err + } + // 1.5.2 read all device records + allDeviceRecords, err := fmcconfig.ReadAllDeviceRecords(ctx, client, fmcconfig.NewReadAllDeviceRecordsInput(fmcDomainUid, fmcRes.Host)) + if err != nil { + return nil, err + } + // 1.5.3 check if FTD name is present in device records, logic: same name + both are FTDs = duplicate + client.Logger.Printf("checking if FTD already exists with id=%s and name=%s\n", createInp.FtdUid, fmcRes.Name) + for _, record := range allDeviceRecords.Items { + if record.Name != readFtdOutp.Name { + // different name, ignore + continue + } + // the allDeviceRecords only contains the name, so we need to make another call to retrieve the details of the device to check whether this is a FTD + // potentially we will be making a lot of network calls and cause this loop to run for long time if + // we have many device records with the same name, I suppose that rarely happens + deviceRecord, err := fmcconfig.ReadDeviceRecord(ctx, client, fmcconfig.NewReadDeviceRecordInput(fmcDomainUid, fmcRes.Host, record.Id)) + if err != nil { + return nil, err + } + if strings.Contains(deviceRecord.Model, "Firepower Threat Defense") { // Question: is there a better way to check? Does this check cover all cases? + return nil, fmt.Errorf("FTD with id=%s and name=%s is already registered", createInp.FtdUid, fmcRes.Name) + } else { + // not a FTD, just some other device with the same name, ignore + } + } + client.Logger.Printf("FTD with id=%s and name=%s is not registered, proceeding\n", createInp.FtdUid, fmcRes.Name) + // 2. get a system token for creating FTD device record in FMC // CDO token does not work, we will get a 405 method not allowed if we do that client.Logger.Println("getting a system token for creating FTD device record in FMC") @@ -69,10 +103,6 @@ func Create(ctx context.Context, client http.Client, createInp CreateInput) (*Cr client.Logger.Println("creating FTD device record in FMC") // 3.1 read ftd metadata - readFtdOutp, err := cloudftd.ReadByUid(ctx, client, cloudftd.NewReadByUidInput(createInp.FtdUid)) - if err != nil { - return nil, err - } // 3.1.5 handle license licenseCaps, err := license.DeserializeAllFromCdo(readFtdOutp.Metadata.LicenseCaps) if err != nil { diff --git a/client/device/cloudftd/cloudftdonboarding/create_test.go b/client/device/cloudftd/cloudftdonboarding/create_test.go index 25b118d9..727ef5af 100644 --- a/client/device/cloudftd/cloudftdonboarding/create_test.go +++ b/client/device/cloudftd/cloudftdonboarding/create_test.go @@ -24,11 +24,12 @@ func TestCloudFtdOnboardingCreate(t *testing.T) { assertFunc func(output *cloudftdonboarding.CreateOutput, err error, t *testing.T) }{ { - testName: "successful ftd onboarding", + testName: "successful ftd onboarding, with no existing records", input: cloudftdonboarding.NewCreateInput(ftdUid), setupFunc: func(t *testing.T) { ReadFmcIsSuccessful(true) ReadFmcDomainInfoIsSuccessful(true) + CheckFtdDuplicateIsSuccessful_NoExistingRecords(true) ReadApiTokenInfoIsSuccessful(true) CreateSystemApiTokenIsSuccessful(true) ReadFtdMetadataIsSuccessful(true) @@ -44,12 +45,79 @@ func TestCloudFtdOnboardingCreate(t *testing.T) { assert.Equal(t, *output, validCreateFmcDeviceRecordOutput) }, }, + { + testName: "successful ftd onboarding, with existing records of different name", + input: cloudftdonboarding.NewCreateInput(ftdUid), + setupFunc: func(t *testing.T) { + ReadFmcIsSuccessful(true) + ReadFmcDomainInfoIsSuccessful(true) + CheckFtdDuplicateIsSuccessful_ExistingRecordsWithDifferentName(true) + ReadApiTokenInfoIsSuccessful(true) + CreateSystemApiTokenIsSuccessful(true) + ReadFtdMetadataIsSuccessful(true) + CreateFmcDeviceRecordIsSuccessful(true) + ReadTaskStatusIsSuccessful(true) + ReadFtdSpecificDeviceIsSuccessful(true) + TriggerRegisterFmcStateMachineSuccess(true) + TriggerRegisterFmcStateMachineEndsInDone(true) + }, + assertFunc: func(output *cloudftdonboarding.CreateOutput, err error, t *testing.T) { + assert.Nil(t, err) + assert.NotNil(t, output) + assert.Equal(t, *output, validCreateFmcDeviceRecordOutput) + }, + }, + { + testName: "successful ftd onboarding, with existing records of same name and different type", + input: cloudftdonboarding.NewCreateInput(ftdUid), + setupFunc: func(t *testing.T) { + ReadFmcIsSuccessful(true) + ReadFmcDomainInfoIsSuccessful(true) + CheckFtdDuplicateIsSuccessful_ExistingRecordsWithSameNameButDifferentType(true) + ReadApiTokenInfoIsSuccessful(true) + CreateSystemApiTokenIsSuccessful(true) + ReadFtdMetadataIsSuccessful(true) + CreateFmcDeviceRecordIsSuccessful(true) + ReadTaskStatusIsSuccessful(true) + ReadFtdSpecificDeviceIsSuccessful(true) + TriggerRegisterFmcStateMachineSuccess(true) + TriggerRegisterFmcStateMachineEndsInDone(true) + }, + assertFunc: func(output *cloudftdonboarding.CreateOutput, err error, t *testing.T) { + assert.Nil(t, err) + assert.NotNil(t, output) + assert.Equal(t, *output, validCreateFmcDeviceRecordOutput) + }, + }, + { + testName: "error when there is existing record of same name and same type", + input: cloudftdonboarding.NewCreateInput(ftdUid), + setupFunc: func(t *testing.T) { + ReadFmcIsSuccessful(true) + ReadFmcDomainInfoIsSuccessful(true) + CheckFtdDuplicateIsFail_ExistingRecordsWithSameNameAndSameType() + ReadApiTokenInfoIsSuccessful(true) + CreateSystemApiTokenIsSuccessful(true) + ReadFtdMetadataIsSuccessful(true) + CreateFmcDeviceRecordIsSuccessful(true) + ReadTaskStatusIsSuccessful(true) + ReadFtdSpecificDeviceIsSuccessful(true) + TriggerRegisterFmcStateMachineSuccess(true) + TriggerRegisterFmcStateMachineEndsInDone(true) + }, + assertFunc: func(output *cloudftdonboarding.CreateOutput, err error, t *testing.T) { + assert.NotNil(t, err) + assert.Nil(t, output) + assert.True(t, strings.Contains(err.Error(), "is already registered")) + }, + }, { testName: "error when read fmc failed", input: cloudftdonboarding.NewCreateInput(ftdUid), setupFunc: func(t *testing.T) { ReadFmcIsSuccessful(false) ReadFmcDomainInfoIsSuccessful(true) + CheckFtdDuplicateIsSuccessful_NoExistingRecords(true) ReadApiTokenInfoIsSuccessful(true) CreateSystemApiTokenIsSuccessful(true) ReadFtdMetadataIsSuccessful(true) @@ -71,6 +139,7 @@ func TestCloudFtdOnboardingCreate(t *testing.T) { setupFunc: func(t *testing.T) { ReadFmcIsSuccessful(true) ReadFmcDomainInfoIsSuccessful(false) + CheckFtdDuplicateIsSuccessful_NoExistingRecords(true) ReadApiTokenInfoIsSuccessful(true) CreateSystemApiTokenIsSuccessful(true) ReadFtdMetadataIsSuccessful(true) @@ -86,12 +155,35 @@ func TestCloudFtdOnboardingCreate(t *testing.T) { assert.True(t, strings.Contains(err.Error(), url.ReadFmcDomainInfo(fmcHost))) }, }, + { + testName: "error when read all fmc device records failed", + input: cloudftdonboarding.NewCreateInput(ftdUid), + setupFunc: func(t *testing.T) { + ReadFmcIsSuccessful(true) + ReadFmcDomainInfoIsSuccessful(true) + CheckFtdDuplicateIsSuccessful_NoExistingRecords(false) + ReadApiTokenInfoIsSuccessful(true) + CreateSystemApiTokenIsSuccessful(true) + ReadFtdMetadataIsSuccessful(true) + CreateFmcDeviceRecordIsSuccessful(true) + ReadTaskStatusIsSuccessful(true) + ReadFtdSpecificDeviceIsSuccessful(true) + TriggerRegisterFmcStateMachineSuccess(true) + TriggerRegisterFmcStateMachineEndsInDone(true) + }, + assertFunc: func(output *cloudftdonboarding.CreateOutput, err error, t *testing.T) { + assert.NotNil(t, err) + assert.Nil(t, output) + assert.True(t, strings.Contains(err.Error(), url.ReadFmcAllDeviceRecords(baseUrl, fmcDomainUid))) + }, + }, { testName: "error when read api token info failed", input: cloudftdonboarding.NewCreateInput(ftdUid), setupFunc: func(t *testing.T) { ReadFmcIsSuccessful(true) ReadFmcDomainInfoIsSuccessful(true) + CheckFtdDuplicateIsSuccessful_NoExistingRecords(true) ReadApiTokenInfoIsSuccessful(false) CreateSystemApiTokenIsSuccessful(true) ReadFtdMetadataIsSuccessful(true) @@ -113,6 +205,7 @@ func TestCloudFtdOnboardingCreate(t *testing.T) { setupFunc: func(t *testing.T) { ReadFmcIsSuccessful(true) ReadFmcDomainInfoIsSuccessful(true) + CheckFtdDuplicateIsSuccessful_NoExistingRecords(true) ReadApiTokenInfoIsSuccessful(true) CreateSystemApiTokenIsSuccessful(false) ReadFtdMetadataIsSuccessful(true) @@ -134,6 +227,7 @@ func TestCloudFtdOnboardingCreate(t *testing.T) { setupFunc: func(t *testing.T) { ReadFmcIsSuccessful(true) ReadFmcDomainInfoIsSuccessful(true) + CheckFtdDuplicateIsSuccessful_NoExistingRecords(true) ReadApiTokenInfoIsSuccessful(true) CreateSystemApiTokenIsSuccessful(true) ReadFtdMetadataIsSuccessful(false) @@ -156,6 +250,7 @@ func TestCloudFtdOnboardingCreate(t *testing.T) { t.Skip("requires override inner retry config support") ReadFmcIsSuccessful(true) ReadFmcDomainInfoIsSuccessful(true) + CheckFtdDuplicateIsSuccessful_NoExistingRecords(true) ReadApiTokenInfoIsSuccessful(true) CreateSystemApiTokenIsSuccessful(true) ReadFtdMetadataIsSuccessful(true) @@ -178,6 +273,7 @@ func TestCloudFtdOnboardingCreate(t *testing.T) { t.Skip("requires override inner retry config support") ReadFmcIsSuccessful(true) ReadFmcDomainInfoIsSuccessful(true) + CheckFtdDuplicateIsSuccessful_NoExistingRecords(true) ReadApiTokenInfoIsSuccessful(true) CreateSystemApiTokenIsSuccessful(true) ReadFtdMetadataIsSuccessful(true) @@ -200,6 +296,7 @@ func TestCloudFtdOnboardingCreate(t *testing.T) { setupFunc: func(t *testing.T) { ReadFmcIsSuccessful(true) ReadFmcDomainInfoIsSuccessful(true) + CheckFtdDuplicateIsSuccessful_NoExistingRecords(true) ReadApiTokenInfoIsSuccessful(true) CreateSystemApiTokenIsSuccessful(true) ReadFtdMetadataIsSuccessful(true) @@ -221,6 +318,7 @@ func TestCloudFtdOnboardingCreate(t *testing.T) { setupFunc: func(t *testing.T) { ReadFmcIsSuccessful(true) ReadFmcDomainInfoIsSuccessful(true) + CheckFtdDuplicateIsSuccessful_NoExistingRecords(true) ReadApiTokenInfoIsSuccessful(true) CreateSystemApiTokenIsSuccessful(true) ReadFtdMetadataIsSuccessful(true) @@ -243,6 +341,7 @@ func TestCloudFtdOnboardingCreate(t *testing.T) { t.Skip("requires override inner retry config support") ReadFmcIsSuccessful(true) ReadFmcDomainInfoIsSuccessful(true) + CheckFtdDuplicateIsSuccessful_NoExistingRecords(true) ReadApiTokenInfoIsSuccessful(true) CreateSystemApiTokenIsSuccessful(true) ReadFtdMetadataIsSuccessful(true) @@ -325,6 +424,77 @@ func ReadApiTokenInfoIsSuccessful(success bool) { } } +func CheckFtdDuplicateIsSuccessful_NoExistingRecords(success bool) { + if success { + httpmock.RegisterResponder( + http.MethodGet, + url.ReadFmcAllDeviceRecords(baseUrl, fmcDomainUid), + httpmock.NewJsonResponderOrPanic(http.StatusOK, allDeviceRecords_NoExistingRecords), + ) + } else { + httpmock.RegisterResponder( + http.MethodGet, + url.ReadFmcAllDeviceRecords(baseUrl, fmcDomainUid), + httpmock.NewJsonResponderOrPanic(http.StatusInternalServerError, "internal server error"), + ) + } +} + +func CheckFtdDuplicateIsSuccessful_ExistingRecordsWithDifferentName(success bool) { + if success { + httpmock.RegisterResponder( + http.MethodGet, + url.ReadFmcAllDeviceRecords(baseUrl, fmcDomainUid), + httpmock.NewJsonResponderOrPanic(http.StatusOK, allDeviceRecords_ExistingRecords_DifferentName), + ) + } else { + httpmock.RegisterResponder( + http.MethodGet, + url.ReadFmcAllDeviceRecords(baseUrl, fmcDomainUid), + httpmock.NewJsonResponderOrPanic(http.StatusInternalServerError, "internal server error"), + ) + } +} + +func CheckFtdDuplicateIsSuccessful_ExistingRecordsWithSameNameButDifferentType(success bool) { + if success { + httpmock.RegisterResponder( + http.MethodGet, + url.ReadFmcAllDeviceRecords(baseUrl, fmcDomainUid), + httpmock.NewJsonResponderOrPanic(http.StatusOK, allDeviceRecords_ExistingRecords_SameName), + ) + httpmock.RegisterResponder( + http.MethodGet, + url.ReadFmcDeviceRecord(baseUrl, fmcDomainUid, allDeviceRecords_ExistingRecords_SameName.Items[0].Id), + httpmock.NewJsonResponderOrPanic(http.StatusOK, deviceRecord_NotFtd), + ) + } else { + httpmock.RegisterResponder( + http.MethodGet, + url.ReadFmcAllDeviceRecords(baseUrl, fmcDomainUid), + httpmock.NewJsonResponderOrPanic(http.StatusOK, allDeviceRecords_ExistingRecords_SameName), + ) + httpmock.RegisterResponder( + http.MethodGet, + url.ReadFmcDeviceRecord(baseUrl, fmcDomainUid, allDeviceRecords_ExistingRecords_SameName.Items[0].Id), + httpmock.NewJsonResponderOrPanic(http.StatusInternalServerError, "internal server error"), + ) + } +} + +func CheckFtdDuplicateIsFail_ExistingRecordsWithSameNameAndSameType() { + httpmock.RegisterResponder( + http.MethodGet, + url.ReadFmcAllDeviceRecords(baseUrl, fmcDomainUid), + httpmock.NewJsonResponderOrPanic(http.StatusOK, allDeviceRecords_ExistingRecords_SameName), + ) + httpmock.RegisterResponder( + http.MethodGet, + url.ReadFmcDeviceRecord(baseUrl, fmcDomainUid, allDeviceRecords_ExistingRecords_SameName.Items[0].Id), + httpmock.NewJsonResponderOrPanic(http.StatusOK, deviceRecord_IsFtd), + ) +} + func CreateSystemApiTokenIsSuccessful(success bool) { if success { httpmock.RegisterResponder( diff --git a/client/device/cloudftd/cloudftdonboarding/fixture_test.go b/client/device/cloudftd/cloudftdonboarding/fixture_test.go index caa97d52..acf5689f 100644 --- a/client/device/cloudftd/cloudftdonboarding/fixture_test.go +++ b/client/device/cloudftd/cloudftdonboarding/fixture_test.go @@ -1,8 +1,6 @@ package cloudftdonboarding_test import ( - "encoding/json" - "fmt" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/cloudfmc/fmcconfig" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/cloudfmc/fmcplatform" @@ -149,10 +147,31 @@ var ( Principle: "", Name: "", }} -) -func init() { - fmt.Println("json.Marshal(validReadApiTokenInfo)") - t, _ := json.Marshal(validReadApiTokenInfo) - fmt.Println(string(t)) -} + deviceRecordItemId = "unit-test-deviceRecordItemId" + deviceRecordItemSameName = ftdName + deviceRecordItemDifferentName = "unit-test-not-ftd-name" + deviceRecordItemType = "unit-test-deviceRecordItemType" + deviceRecordItemLinks = fmcconfig.NewLinks("unit-test-deviceRecordItemLinks") + + allDeviceRecords_NoExistingRecords = fmcconfig.NewReadAllDeviceRecordsOutputBuilder(). + Items([]fmcconfig.Item{}). + Build() + allDeviceRecords_ExistingRecords_DifferentName = fmcconfig.NewReadAllDeviceRecordsOutputBuilder(). + Items([]fmcconfig.Item{ + fmcconfig.NewItem(deviceRecordItemId, deviceRecordItemDifferentName, deviceRecordItemType, deviceRecordItemLinks), + }). + Build() + allDeviceRecords_ExistingRecords_SameName = fmcconfig.NewReadAllDeviceRecordsOutputBuilder(). + Items([]fmcconfig.Item{ + fmcconfig.NewItem(deviceRecordItemId, deviceRecordItemSameName, deviceRecordItemType, deviceRecordItemLinks), + }). + Build() + + deviceRecord_IsFtd = fmcconfig.ReadDeviceRecordOutput{ + Model: "Firepower Threat Defense", + } + deviceRecord_NotFtd = fmcconfig.ReadDeviceRecordOutput{ + Model: "ASA", + } +) diff --git a/client/device/cloudftd/fixture_test.go b/client/device/cloudftd/fixture_test.go index a94a61a1..db94c869 100644 --- a/client/device/cloudftd/fixture_test.go +++ b/client/device/cloudftd/fixture_test.go @@ -88,7 +88,7 @@ var ( }). Build() - validReadAccessPoliciesOutput = accesspolicies.NewAccessPoliciesBuilder(). + validReadAccessPoliciesOutput = accesspolicies.Builder(). Links(accesspolicies.NewLinks(fmcLink)). Paging(accesspolicies.NewPaging( fmcAccessPolicyPages, diff --git a/client/internal/url/url.go b/client/internal/url/url.go index 777bb8c9..21bed49e 100644 --- a/client/internal/url/url.go +++ b/client/internal/url/url.go @@ -134,6 +134,14 @@ func CreateFmcDeviceRecord(baseUrl string, fmcDomainId string) string { return fmt.Sprintf("%s/fmc/api/fmc_config/v1/domain/%s/devices/devicerecords", baseUrl, fmcDomainId) } +func ReadFmcDeviceRecord(baseUrl string, fmcDomainId string, deviceUid string) string { + return fmt.Sprintf("%s/fmc/api/fmc_config/v1/domain/%s/devices/devicerecords/%s", baseUrl, fmcDomainId, deviceUid) +} + +func ReadFmcAllDeviceRecords(baseUrl string, fmcDomainId string) string { + return fmt.Sprintf("%s/fmc/api/fmc_config/v1/domain/%s/devices/devicerecords", baseUrl, fmcDomainId) +} + func ReadFmcTaskStatus(baseUrl string, fmcDomainUid string, taskId string) string { return fmt.Sprintf("%s/fmc/api/fmc_config/v1/domain/%s/job/taskstatuses/%s", baseUrl, fmcDomainUid, taskId) } diff --git a/client/model/accesspolicies/accesspolicies.go b/client/model/accesspolicies/accesspolicies.go deleted file mode 100644 index 4f03e0eb..00000000 --- a/client/model/accesspolicies/accesspolicies.go +++ /dev/null @@ -1,67 +0,0 @@ -package accesspolicies - -type AccessPolicies struct { - Items Items `json:"items"` - Links Links `json:"links"` - Paging Paging `json:"paging"` -} - -func New(items Items, links Links, paging Paging) AccessPolicies { - return AccessPolicies{ - Items: items, - Links: links, - Paging: paging, - } -} - -type Items struct { - Items []Item `json:"items"` -} - -func NewItems(items ...Item) Items { - return Items{ - Items: items, - } -} - -type Item struct { - Links Links `json:"links"` - Id string `json:"id"` - Name string `json:"name"` - Type string `json:"type"` -} - -func NewItem(id, name, type_ string, links Links) Item { - return Item{ - Id: id, - Name: name, - Type: type_, - Links: links, - } -} - -type Paging struct { - Count int `json:"count"` - Offset int `json:"offset"` - Limit int `json:"limit"` - Pages int `json:"pages"` -} - -func NewPaging(count, offset, limit, pages int) Paging { - return Paging{ - Count: count, - Offset: offset, - Limit: limit, - Pages: pages, - } -} - -type Links struct { - Self string `json:"self"` -} - -func NewLinks(self string) Links { - return Links{ - Self: self, - } -} diff --git a/client/model/cloudfmc/accesspolicies/accesspolicies.go b/client/model/cloudfmc/accesspolicies/accesspolicies.go index d385b56c..97b132f7 100644 --- a/client/model/cloudfmc/accesspolicies/accesspolicies.go +++ b/client/model/cloudfmc/accesspolicies/accesspolicies.go @@ -2,48 +2,15 @@ package accesspolicies import "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/cloudfmc/internal" -type AccessPolicies struct { - Items []Item `json:"items"` - Links Links `json:"links"` - Paging Paging `json:"paging"` -} +type AccessPolicies = internal.Response -func New(items []Item, links Links, paging Paging) AccessPolicies { - return AccessPolicies{ - Items: items, - Links: links, - Paging: paging, - } -} - -// Find return the access policy item with the given name, second return value ok indicate whether the item is found. -func (policies *AccessPolicies) Find(name string) (item Item, ok bool) { - for _, policy := range policies.Items { - if policy.Name == name { - return policy, true - } - } - return Item{}, false -} - -type Item struct { - Links Links `json:"links"` - Id string `json:"id"` - Name string `json:"name"` - Type string `json:"type"` -} - -func NewItem(id, name, type_ string, links Links) Item { - return Item{ - Id: id, - Name: name, - Type: type_, - Links: links, - } -} +var Builder = internal.NewResponseBuilder +var New = internal.NewResponse type Links = internal.Links type Paging = internal.Paging +type Item = internal.Item +var NewItem = internal.NewItem var NewLinks = internal.NewLinks var NewPaging = internal.NewPaging diff --git a/client/model/cloudfmc/accesspolicies/builder.go b/client/model/cloudfmc/accesspolicies/builder.go deleted file mode 100644 index d9064644..00000000 --- a/client/model/cloudfmc/accesspolicies/builder.go +++ /dev/null @@ -1,30 +0,0 @@ -package accesspolicies - -type Builder struct { - accessPolicies *AccessPolicies -} - -func NewAccessPoliciesBuilder() *Builder { - accessPolicies := &AccessPolicies{} - b := &Builder{accessPolicies: accessPolicies} - return b -} - -func (b *Builder) Items(items []Item) *Builder { - b.accessPolicies.Items = items - return b -} - -func (b *Builder) Links(links Links) *Builder { - b.accessPolicies.Links = links - return b -} - -func (b *Builder) Paging(paging Paging) *Builder { - b.accessPolicies.Paging = paging - return b -} - -func (b *Builder) Build() AccessPolicies { - return *b.accessPolicies -} diff --git a/client/model/cloudfmc/fmcconfig/alldevicerecords.go b/client/model/cloudfmc/fmcconfig/alldevicerecords.go new file mode 100644 index 00000000..cf3a47d8 --- /dev/null +++ b/client/model/cloudfmc/fmcconfig/alldevicerecords.go @@ -0,0 +1,7 @@ +package fmcconfig + +import "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/cloudfmc/internal" + +type AllDeviceRecords = internal.Response + +var NewAllDeviceRecordsBuilder = internal.NewResponseBuilder diff --git a/client/model/cloudfmc/fmcconfig/common.go b/client/model/cloudfmc/fmcconfig/common.go new file mode 100644 index 00000000..6895c751 --- /dev/null +++ b/client/model/cloudfmc/fmcconfig/common.go @@ -0,0 +1,11 @@ +package fmcconfig + +import "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/cloudfmc/internal" + +type Item = internal.Item + +var NewItem = internal.NewItem + +type Links = internal.Links + +var NewLinks = internal.NewLinks diff --git a/client/model/cloudfmc/fmcconfig/devicerecord.go b/client/model/cloudfmc/fmcconfig/devicerecord.go new file mode 100644 index 00000000..24dbe911 --- /dev/null +++ b/client/model/cloudfmc/fmcconfig/devicerecord.go @@ -0,0 +1,54 @@ +package fmcconfig + +import "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/cloudfmc/internal" + +// DeviceRecord schema is from the device tab of /api/api-explorer/ +type DeviceRecord struct { + Id string `json:"id"` + Type string `json:"type"` + Links internal.Links `json:"links"` + Name string `json:"name"` + Description string `json:"description"` + Model string `json:"model"` + ModelId string `json:"modelId"` + ModelNumber string `json:"modelNumber"` + ModelType string `json:"modelType"` + HealthStatus string `json:"healthStatus"` + HealthMessage string `json:"healthMessage"` + SwVersion string `json:"sw_version"` + HealthPolicy internal.NoLinkItem `json:"healthPolicy"` + AccessPolicy internal.NoLinkItem `json:"accessPolicy"` + Hostname string `json:"hostname"` + LicenseCaps []string `json:"license_caps"` // this is different from the license_caps we have in CDO, e.g. it has ESSENTIALS instead of BASE + PerformanceTier string `json:"performance_tier"` + KeepLocalEvents bool `json:"keepLocalEvents"` + ProhibitPacketTransfer bool `json:"prohibitPacketTransfer"` + IsConnected bool `json:"isConnected"` + FtdMode string `json:"ftdMode"` // e.g. ROUTED / TRANSPARENT + AnalyticsOnly bool `json:"analyticsOnly"` + SnortEngine string `json:"snortEngine"` + Metadata DeviceRecordMetadata `json:"metadata"` + DeploymentStatus string `json:"deploymentStatus"` +} + +type DeviceRecordMetadata struct { + ReadOnly ReadOnly `json:"readOnly"` + InventoryData InventoryData `json:"inventoryData"` + DeviceSerialNumber string `json:"deviceSerialNumber"` + Domain internal.NoLinkItem `json:"domain"` + IsMultiInstance bool `json:"isMultiInstance"` + SnortVersion string `json:"snortVersion"` + VdbVersion string `json:"vdbVersion"` + LspVersion string `json:"lspVersion"` + ClusterBootstrapSupported bool `json:"clusterBootstrapSupported"` +} + +type InventoryData struct { + CPUCores string `json:"cpuCores"` + CPUType string `json:"cpuType"` + MemoryInMB string `json:"memoryInMB"` +} +type ReadOnly struct { + State bool `json:"state"` + Reason string `json:"reason"` +} diff --git a/client/model/cloudfmc/internal/builder.go b/client/model/cloudfmc/internal/builder.go new file mode 100644 index 00000000..06502433 --- /dev/null +++ b/client/model/cloudfmc/internal/builder.go @@ -0,0 +1,29 @@ +package internal + +type Builder struct { + response *Response +} + +func NewResponseBuilder() *Builder { + b := &Builder{response: &Response{}} + return b +} + +func (b *Builder) Items(items []Item) *Builder { + b.response.Items = items + return b +} + +func (b *Builder) Links(links Links) *Builder { + b.response.Links = links + return b +} + +func (b *Builder) Paging(paging Paging) *Builder { + b.response.Paging = paging + return b +} + +func (b *Builder) Build() Response { + return *b.response +} diff --git a/client/model/cloudfmc/internal/common.go b/client/model/cloudfmc/internal/common.go index 8a5bfd80..49ce68cd 100644 --- a/client/model/cloudfmc/internal/common.go +++ b/client/model/cloudfmc/internal/common.go @@ -30,3 +30,57 @@ func NewLinks(self string) Links { Self: self, } } + +type Item struct { + Links Links `json:"links"` + Id string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` +} + +func NewItem(id, name, type_ string, links Links) Item { + return Item{ + Id: id, + Name: name, + Type: type_, + Links: links, + } +} + +type NoLinkItem struct { + Id string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` +} + +func NewNoLinkItem(id, name, type_ string) NoLinkItem { + return NoLinkItem{ + Id: id, + Name: name, + Type: type_, + } +} + +type Response struct { + Items []Item `json:"items"` + Links Links `json:"links"` + Paging Paging `json:"paging"` +} + +func NewResponse(items []Item, links Links, paging Paging) Response { + return Response{ + Items: items, + Links: links, + Paging: paging, + } +} + +// Find return the item with the given name, second return value ok indicate whether the item is found. +func (response *Response) Find(name string) (Item, bool) { + for _, item := range response.Items { + if item.Name == name { + return item, true + } + } + return Item{}, false +} diff --git a/provider/examples/resources/ftd/cdo_ftd.tf b/provider/examples/resources/ftd/cdo_ftd.tf index 810178ec..05556048 100644 --- a/provider/examples/resources/ftd/cdo_ftd.tf +++ b/provider/examples/resources/ftd/cdo_ftd.tf @@ -5,7 +5,7 @@ terraform { } aws = { source = "hashicorp/aws" - version = "~> 3.27.0" + version = "~> 3.75.0" } } } diff --git a/provider/internal/device/ftd/resource.go b/provider/internal/device/ftd/resource.go index 6bdc7edf..dd9e8d20 100644 --- a/provider/internal/device/ftd/resource.go +++ b/provider/internal/device/ftd/resource.go @@ -3,6 +3,8 @@ package ftd import ( "context" "fmt" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault" cdoClient "github.com/CiscoDevnet/terraform-provider-cdo/go-client" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/ftd/license" @@ -114,6 +116,8 @@ func (r *Resource) Schema(ctx context.Context, req resource.SchemaRequest, resp MarkdownDescription: "Set a list of labels to identify the device as part of a group. Refer to the [CDO documentation](https://docs.defenseorchestrator.com/t-applying-labels-to-devices-and-objects.html#!c-labels-and-filtering.html) for details on how labels are used in CDO.", Optional: true, ElementType: types.StringType, + Computed: true, + Default: listdefault.StaticValue(types.ListValueMust(types.StringType, []attr.Value{})), // default to empty list Validators: []validator.List{ listvalidator.UniqueValues(), }, diff --git a/provider/internal/device/ios/resource.go b/provider/internal/device/ios/resource.go index 1a226e98..8cb54938 100644 --- a/provider/internal/device/ios/resource.go +++ b/provider/internal/device/ios/resource.go @@ -4,6 +4,8 @@ import ( "context" "fmt" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault" "strings" "github.com/CiscoDevnet/terraform-provider-cdo/validators" @@ -114,6 +116,8 @@ func (r *IosDeviceResource) Schema(ctx context.Context, req resource.SchemaReque MarkdownDescription: "Set a list of labels to identify the device as part of a group. Refer to the [CDO documentation](https://docs.defenseorchestrator.com/t-applying-labels-to-devices-and-objects.html#!c-labels-and-filtering.html) for details on how labels are used in CDO.", Optional: true, ElementType: types.StringType, + Computed: true, + Default: listdefault.StaticValue(types.ListValueMust(types.StringType, []attr.Value{})), // default to empty list Validators: []validator.List{ listvalidator.UniqueValues(), },