From c764c623e97333c53ec23322df8eee8ff68e413a Mon Sep 17 00:00:00 2001 From: Christopher Dickson Date: Thu, 29 Feb 2024 12:03:29 +0000 Subject: [PATCH] [LH-75098] Fix: Support label groups (#134) * WIP * ci: temporarily enable acceptance tests for branch push * fix: address broken tests * docs: generate docs * refactor: remove debugging logs * refactor: make use of test fixtures rather than repeated literals * ci: restore github ci definition after debugging --- client/device/asa/create.go | 27 ++++---- client/device/asa/create_test.go | 5 +- client/device/asa/read.go | 1 + client/device/cloudftd/create.go | 58 ++++++++++++----- .../device/cloudftd/create_outputbuilder.go | 8 ++- client/device/cloudftd/create_test.go | 9 +-- client/device/cloudftd/fixture_test.go | 4 +- client/device/cloudftd/read_by_uid.go | 1 + client/device/cloudftd/update.go | 3 +- client/device/cloudftd/update_test.go | 11 ++-- client/device/duoadminpanel/create.go | 12 ++-- client/device/duoadminpanel/create_test.go | 5 +- client/device/duoadminpanel/read_by_uid.go | 1 + client/device/duoadminpanel/update.go | 1 + client/device/duoadminpanel/update_test.go | 9 +-- client/device/genericssh/create.go | 1 + client/device/genericssh/create_test.go | 11 ++-- client/device/genericssh/update_test.go | 11 ++-- client/device/ios/create.go | 15 ++--- client/device/ios/create_test.go | 5 +- client/device/ios/read_test.go | 9 +-- client/device/ios/update.go | 1 + client/device/ios/update_test.go | 9 +-- client/internal/maputil/filter.go | 14 +++++ client/internal/testing/asa.go | 6 +- client/internal/testing/duo.go | 4 +- client/internal/testing/ftd.go | 5 +- client/internal/testing/ios.go | 4 +- client/internal/testing/labels.go | 10 +++ client/internal/testing/tags.go | 12 ++++ client/model/device/publicapilabels/tags.go | 30 +++++++++ client/model/device/tags/tags.go | 46 ++++++++------ docs/data-sources/asa_device.md | 1 + docs/data-sources/ftd_device.md | 3 +- docs/data-sources/ios_device.md | 1 + docs/data-sources/tenant_settings.md | 28 +++++++++ docs/resources/asa_device.md | 1 + docs/resources/duo_admin_panel.md | 1 + docs/resources/ftd_device.md | 1 + docs/resources/ios_device.md | 1 + docs/resources/tenant_settings.md | 31 +++++++++ provider/internal/acctest/environment.go | 43 +++++-------- provider/internal/acctest/helper.go | 24 +++++++ provider/internal/device/asa/data_source.go | 14 ++++- .../internal/device/asa/data_source_test.go | 7 +-- provider/internal/device/asa/resource.go | 63 +++++++++++++++++-- provider/internal/device/asa/resource_test.go | 46 +++++++++++--- provider/internal/device/ftd/data_source.go | 13 +++- provider/internal/device/ftd/operation.go | 60 +++++++++++++++--- provider/internal/device/ftd/resource.go | 12 ++++ provider/internal/device/ftd/resource_test.go | 48 +++++++++++--- provider/internal/device/ios/data_source.go | 14 ++++- .../internal/device/ios/data_source_test.go | 13 ++-- provider/internal/device/ios/operation.go | 53 ++++++++++++++-- provider/internal/device/ios/resource.go | 14 ++++- provider/internal/device/ios/resource_test.go | 49 ++++++++++++--- .../service/duoadminpanel/operation.go | 60 +++++++++++++++--- .../service/duoadminpanel/resource.go | 12 ++++ .../service/duoadminpanel/resource_test.go | 25 ++++++-- provider/internal/util/testutil/testutil.go | 14 ++++- provider/internal/util/togo.go | 46 ++++++++++++-- provider/internal/util/totf.go | 24 +++++++ 62 files changed, 849 insertions(+), 221 deletions(-) create mode 100644 client/internal/maputil/filter.go create mode 100644 client/internal/testing/labels.go create mode 100644 client/internal/testing/tags.go create mode 100644 client/model/device/publicapilabels/tags.go create mode 100644 docs/data-sources/tenant_settings.md create mode 100644 docs/resources/tenant_settings.md diff --git a/client/device/asa/create.go b/client/device/asa/create.go index 4641890e..3ab5d532 100644 --- a/client/device/asa/create.go +++ b/client/device/asa/create.go @@ -2,11 +2,12 @@ package asa import ( "context" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/connector" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/http" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/publicapi" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/url" - "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/publicapilabels" ) type CreateInput struct { @@ -14,7 +15,7 @@ type CreateInput struct { ConnectorUid string ConnectorType string SocketAddress string - Tags tags.Type + Labels publicapilabels.Type Username string Password string @@ -34,21 +35,21 @@ type CreateError struct { } type createBody struct { - Name string `json:"name"` - DeviceAddress string `json:"deviceAddress"` - Username string `json:"username"` - Password string `json:"password"` - ConnectorType string `json:"connectorType"` - IgnoreCertificate bool `json:"ignoreCertificate"` - ConnectorName string `json:"connectorName"` - Labels []string `json:"labels"` + Name string `json:"name"` + DeviceAddress string `json:"deviceAddress"` + Username string `json:"username"` + Password string `json:"password"` + ConnectorType string `json:"connectorType"` + IgnoreCertificate bool `json:"ignoreCertificate"` + ConnectorName string `json:"connectorName"` + Labels publicapilabels.Type `json:"labels"` } func (r *CreateError) Error() string { return r.Err.Error() } -func NewCreateRequestInput(name, connectorUid, connectorType, socketAddress, username, password string, ignoreCertificate bool, tags tags.Type) *CreateInput { +func NewCreateRequestInput(name, connectorUid, connectorType, socketAddress, username, password string, ignoreCertificate bool, labels publicapilabels.Type) *CreateInput { return &CreateInput{ Name: name, ConnectorUid: connectorUid, @@ -57,7 +58,7 @@ func NewCreateRequestInput(name, connectorUid, connectorType, socketAddress, use Username: username, Password: password, IgnoreCertificate: ignoreCertificate, - Tags: tags, + Labels: labels, } } @@ -87,7 +88,7 @@ func Create(ctx context.Context, client http.Client, createInp CreateInput) (*Cr ConnectorType: createInp.ConnectorType, IgnoreCertificate: createInp.IgnoreCertificate, ConnectorName: conn.Name, - Labels: createInp.Tags.Labels, + Labels: createInp.Labels, }, ) if err != nil { diff --git a/client/device/asa/create_test.go b/client/device/asa/create_test.go index a94ace10..5247411f 100644 --- a/client/device/asa/create_test.go +++ b/client/device/asa/create_test.go @@ -2,13 +2,14 @@ package asa_test import ( "context" + "testing" + "time" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/http" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/publicapi/transaction/transactiontype" internalTesting "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/testing" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/url" "github.com/stretchr/testify/assert" - "testing" - "time" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/asa" "github.com/jarcoal/httpmock" diff --git a/client/device/asa/read.go b/client/device/asa/read.go index e3fb6f12..05500da3 100644 --- a/client/device/asa/read.go +++ b/client/device/asa/read.go @@ -2,6 +2,7 @@ package asa import ( "context" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/devicetype" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/statemachine" diff --git a/client/device/cloudftd/create.go b/client/device/cloudftd/create.go index 903892a8..f7247654 100644 --- a/client/device/cloudftd/create.go +++ b/client/device/cloudftd/create.go @@ -6,6 +6,7 @@ package cloudftd import ( "context" "fmt" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/cloudfmc" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/cloudfmc/fmcplatform" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/cdo" @@ -13,7 +14,7 @@ import ( "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/publicapi" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/url" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/cloudfmc/accesspolicies" - "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/publicapilabels" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/devicetype" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/ftd/license" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/ftd/tier" @@ -25,10 +26,30 @@ type CreateInput struct { PerformanceTier *tier.Type // ignored if it is physical device Virtual bool Licenses *[]license.Type - Tags tags.Type + Labels publicapilabels.Type +} + +type CreateOutput struct { + Uid string `json:"uid"` + Name string `json:"name"` + Metadata Metadata `json:"metadata,omitempty"` + State string `json:"state"` + Labels publicapilabels.Type `json:"labels"` } -type CreateOutput = ReadOutput +func FromDeviceReadOutput(readOutput *ReadOutput) *CreateOutput { + if readOutput == nil { + return nil + } + + return &CreateOutput{ + Uid: readOutput.Uid, + Name: readOutput.Name, + Metadata: readOutput.Metadata, + State: readOutput.State, + Labels: publicapilabels.New(readOutput.Tags.UngroupedTags(), readOutput.Tags.GroupedTags()), + } +} func NewCreateInput( name string, @@ -36,7 +57,7 @@ func NewCreateInput( performanceTier *tier.Type, virtual bool, licenses *[]license.Type, - tags tags.Type, + labels publicapilabels.Type, ) CreateInput { return CreateInput{ Name: name, @@ -44,18 +65,18 @@ func NewCreateInput( PerformanceTier: performanceTier, Virtual: virtual, Licenses: licenses, - Tags: tags, + Labels: labels, } } type createRequestBody struct { - Name string `json:"name"` - DeviceType devicetype.Type `json:"deviceType"` - FmcAccessPolicyUid string `json:"fmcAccessPolicyUid"` - PerformanceTier *tier.Type `json:"performanceTier"` - Virtual bool `json:"virtual"` - Licenses *[]license.Type `json:"licenses"` - Labels []string `json:"labels"` + Name string `json:"name"` + DeviceType devicetype.Type `json:"deviceType"` + FmcAccessPolicyUid string `json:"fmcAccessPolicyUid"` + PerformanceTier *tier.Type `json:"performanceTier"` + Virtual bool `json:"virtual"` + Licenses *[]license.Type `json:"licenses"` + Labels publicapilabels.Type `json:"labels"` } func Create(ctx context.Context, client http.Client, createInp CreateInput) (*CreateOutput, error) { @@ -79,7 +100,7 @@ func Create(ctx context.Context, client http.Client, createInp CreateInput) (*Cr FmcAccessPolicyUid: selectedPolicy.Id, PerformanceTier: createInp.PerformanceTier, Virtual: createInp.Virtual, - Labels: createInp.Tags.Labels, + Labels: createInp.Labels, Licenses: createInp.Licenses, }, ) @@ -99,7 +120,12 @@ func Create(ctx context.Context, client http.Client, createInp CreateInput) (*Cr return nil, err } - return ReadByUid(ctx, client, NewReadByUidInput(transaction.EntityUid)) + cloudFtdReadOutput, err := ReadByUid(ctx, client, NewReadByUidInput(transaction.EntityUid)) + if err != nil { + return nil, err + } + + return FromDeviceReadOutput(cloudFtdReadOutput), nil } func readPolicyUidFromPolicyName(ctx context.Context, client http.Client, accessPolicyName string) (accesspolicies.Item, error) { @@ -123,6 +149,10 @@ func readPolicyUidFromPolicyName(ctx context.Context, client http.Client, access client, cloudfmc.NewReadAccessPoliciesInput(fmcRes.Host, readFmcDomainRes.Items[0].Uuid, 1000), // 1000 is what CDO UI uses ) + if err != nil { + return accesspolicies.Item{}, err + } + selectedPolicy, ok := accessPoliciesRes.Find(accessPolicyName) if !ok { return accesspolicies.Item{}, fmt.Errorf( diff --git a/client/device/cloudftd/create_outputbuilder.go b/client/device/cloudftd/create_outputbuilder.go index cd6f89a9..d1bfd2f9 100644 --- a/client/device/cloudftd/create_outputbuilder.go +++ b/client/device/cloudftd/create_outputbuilder.go @@ -1,6 +1,8 @@ package cloudftd -import "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" +import ( + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/publicapilabels" +) type CreateOutputBuilder struct { createOutput *CreateOutput @@ -27,8 +29,8 @@ func (b *CreateOutputBuilder) Metadata(metadata Metadata) *CreateOutputBuilder { return b } -func (b *CreateOutputBuilder) Tags(tags tags.Type) *CreateOutputBuilder { - b.createOutput.Tags = tags +func (b *CreateOutputBuilder) Labels(labels publicapilabels.Type) *CreateOutputBuilder { + b.createOutput.Labels = labels return b } diff --git a/client/device/cloudftd/create_test.go b/client/device/cloudftd/create_test.go index 3a92752a..58a3ba03 100644 --- a/client/device/cloudftd/create_test.go +++ b/client/device/cloudftd/create_test.go @@ -2,6 +2,10 @@ package cloudftd_test import ( "context" + "net/http" + "testing" + "time" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/cloudfmc" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/cloudftd" internalHttp "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/http" @@ -10,9 +14,6 @@ import ( "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/url" "github.com/jarcoal/httpmock" "github.com/stretchr/testify/assert" - "net/http" - "testing" - "time" ) func TestCreateCloudFtd(t *testing.T) { @@ -48,7 +49,7 @@ func TestCreateCloudFtd(t *testing.T) { assertFunc: func(output *cloudftd.CreateOutput, err error, t *testing.T) { assert.Nil(t, err) assert.NotNil(t, output) - assert.Equal(t, *output, ftdReadOutput) + assert.Equal(t, output, cloudftd.FromDeviceReadOutput(&ftdReadOutput)) }, }, { diff --git a/client/device/cloudftd/fixture_test.go b/client/device/cloudftd/fixture_test.go index 3c9f4b98..f473ad28 100644 --- a/client/device/cloudftd/fixture_test.go +++ b/client/device/cloudftd/fixture_test.go @@ -6,10 +6,10 @@ import ( "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/cloudfmc/fmcappliance" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/cloudftd" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/statemachine" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/testing" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/cloudfmc/accesspolicies" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/cloudfmc/fmcconfig" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/cloudfmc/fmcdomain" - "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/ftd/license" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/ftd/tier" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/statemachine/state" @@ -66,7 +66,7 @@ const ( var ( ftdLicenseCaps = &[]license.Type{license.Base, license.Carrier} ftdPerformanceTier = tier.FTDv5 - ftdTags = tags.New("tags1", "tags2", "tags3") + ftdTags = testing.NewTestingTags() ) var ( diff --git a/client/device/cloudftd/read_by_uid.go b/client/device/cloudftd/read_by_uid.go index 1922bd7b..2a57b075 100644 --- a/client/device/cloudftd/read_by_uid.go +++ b/client/device/cloudftd/read_by_uid.go @@ -2,6 +2,7 @@ package cloudftd 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/device/tags" diff --git a/client/device/cloudftd/update.go b/client/device/cloudftd/update.go index ca0364c9..4eecc4c8 100644 --- a/client/device/cloudftd/update.go +++ b/client/device/cloudftd/update.go @@ -2,6 +2,8 @@ package cloudftd import ( "context" + "time" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/cloudfmc" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/cloudfmc/fmcappliance" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/cloudfmc/fmcplatform" @@ -10,7 +12,6 @@ import ( "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/url" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/ftd/license" - "time" ) type UpdateInput struct { diff --git a/client/device/cloudftd/update_test.go b/client/device/cloudftd/update_test.go index 6b9f4d10..8adcac8d 100644 --- a/client/device/cloudftd/update_test.go +++ b/client/device/cloudftd/update_test.go @@ -2,20 +2,21 @@ package cloudftd_test import ( "context" + "net/http" + "testing" + "time" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/cloudfmc" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/cloudfmc/fmcplatform" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/cloudftd" internalHttp "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/http" + internalTesting "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/testing" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/url" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/cloudfmc/devicelicense" - "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/ftd/license" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/statemachine/state" "github.com/jarcoal/httpmock" "github.com/stretchr/testify/assert" - "net/http" - "testing" - "time" ) func TestUpdateCloudFtd(t *testing.T) { @@ -29,7 +30,7 @@ func TestUpdateCloudFtd(t *testing.T) { Uid("test-uid"). Licenses([]license.Type{license.Essentials}). Name("test-name"). - Tags(tags.Type{Labels: []string{"test-tag"}}). + Tags(internalTesting.NewTestingTags()). Build() testMetadata := cloudftd.NewMetadataBuilder(). diff --git a/client/device/duoadminpanel/create.go b/client/device/duoadminpanel/create.go index cb7924ae..bb46ae21 100644 --- a/client/device/duoadminpanel/create.go +++ b/client/device/duoadminpanel/create.go @@ -2,17 +2,19 @@ package duoadminpanel import ( "context" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/http" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/publicapi" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/url" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/publicapilabels" ) type CreateInput struct { - Name string `json:"name"` - Host string `json:"host"` - IntegrationKey string `json:"integrationKey"` - SecretKey string `json:"secretKey"` - Labels []string `json:"labels"` + Name string `json:"name"` + Host string `json:"host"` + IntegrationKey string `json:"integrationKey"` + SecretKey string `json:"secretKey"` + Labels publicapilabels.Type `json:"labels"` } type CreateOutput = ReadOutput diff --git a/client/device/duoadminpanel/create_test.go b/client/device/duoadminpanel/create_test.go index 0ca2dc64..dcdb4be7 100644 --- a/client/device/duoadminpanel/create_test.go +++ b/client/device/duoadminpanel/create_test.go @@ -2,13 +2,14 @@ package duoadminpanel_test import ( "context" + "testing" + "time" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/duoadminpanel" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/publicapi/transaction/transactiontype" internalTesting "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/testing" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/url" "github.com/stretchr/testify/assert" - "testing" - "time" internalHttp "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/http" "github.com/jarcoal/httpmock" diff --git a/client/device/duoadminpanel/read_by_uid.go b/client/device/duoadminpanel/read_by_uid.go index fb0dd821..1e060b16 100644 --- a/client/device/duoadminpanel/read_by_uid.go +++ b/client/device/duoadminpanel/read_by_uid.go @@ -2,6 +2,7 @@ package duoadminpanel 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/device/tags" diff --git a/client/device/duoadminpanel/update.go b/client/device/duoadminpanel/update.go index 02b6e736..f8fb9034 100644 --- a/client/device/duoadminpanel/update.go +++ b/client/device/duoadminpanel/update.go @@ -2,6 +2,7 @@ package duoadminpanel 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/device/tags" diff --git a/client/device/duoadminpanel/update_test.go b/client/device/duoadminpanel/update_test.go index ef871727..7da4be38 100644 --- a/client/device/duoadminpanel/update_test.go +++ b/client/device/duoadminpanel/update_test.go @@ -2,13 +2,14 @@ package duoadminpanel_test import ( "context" + "net/http" + "testing" + "time" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/duoadminpanel" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/url" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" "github.com/stretchr/testify/assert" - "net/http" - "testing" - "time" internalHttp "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/http" "github.com/jarcoal/httpmock" @@ -21,7 +22,7 @@ func TestDuoAdminPanelUpdate(t *testing.T) { updateInput := duoadminpanel.UpdateInput{ Uid: "test-uid", Name: "test-name", - Tags: tags.New([]string{"1", "2", "3"}...), + Tags: tags.NewUngrouped([]string{"1", "2", "3"}...), } updateOutput := duoadminpanel.UpdateOutput{ diff --git a/client/device/genericssh/create.go b/client/device/genericssh/create.go index bcf419db..63a24d3a 100644 --- a/client/device/genericssh/create.go +++ b/client/device/genericssh/create.go @@ -2,6 +2,7 @@ package genericssh import ( "context" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/goutil" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" diff --git a/client/device/genericssh/create_test.go b/client/device/genericssh/create_test.go index 8599736a..88f43665 100644 --- a/client/device/genericssh/create_test.go +++ b/client/device/genericssh/create_test.go @@ -2,16 +2,17 @@ package genericssh_test import ( "context" + "net/http" + "testing" + "time" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/genericssh" internalHttp "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/http" + internalTesting "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/testing" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/url" - "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/statemachine/state" "github.com/jarcoal/httpmock" "github.com/stretchr/testify/assert" - "net/http" - "testing" - "time" ) func TestGenericSshCreate(t *testing.T) { @@ -22,7 +23,7 @@ func TestGenericSshCreate(t *testing.T) { Uid: genericSshUid, Name: genericSshName, State: state.DONE, - Tags: tags.New("tags1", "tags2", "tags3"), + Tags: internalTesting.NewTestingTags(), } testCases := []struct { diff --git a/client/device/genericssh/update_test.go b/client/device/genericssh/update_test.go index 0a5f7c51..6ea1c7e2 100644 --- a/client/device/genericssh/update_test.go +++ b/client/device/genericssh/update_test.go @@ -4,19 +4,20 @@ import ( "context" "crypto/rand" "crypto/rsa" + "net/http" + "testing" + "time" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/genericssh" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/crypto" internalHttp "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/http" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/jsonutil" + internalTesting "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/testing" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/url" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model" - "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/statemachine/state" "github.com/jarcoal/httpmock" "github.com/stretchr/testify/assert" - "net/http" - "testing" - "time" ) func TestGenericSshUpdate(t *testing.T) { @@ -27,7 +28,7 @@ func TestGenericSshUpdate(t *testing.T) { Uid: genericSshUid, Name: genericSshName, State: state.DONE, - Tags: tags.New("tags1", "tags2", "tags3"), + Tags: internalTesting.NewTestingTags(), } testCases := []struct { diff --git a/client/device/ios/create.go b/client/device/ios/create.go index eb2744b0..03846e78 100644 --- a/client/device/ios/create.go +++ b/client/device/ios/create.go @@ -2,11 +2,12 @@ package ios import ( "context" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/connector" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/http" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/publicapi" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/url" - "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/publicapilabels" ) type CreateInput struct { @@ -14,7 +15,7 @@ type CreateInput struct { ConnectorUid string ConnectorType string SocketAddress string - Tags tags.Type + Labels publicapilabels.Type Username string Password string @@ -42,11 +43,11 @@ type createBody struct { Username string `json:"username"` Password string `json:"password"` - IgnoreCertificate bool `json:"ignoreCertificate"` - Tags tags.Type `json:"tags"` + IgnoreCertificate bool `json:"ignoreCertificate"` + Labels publicapilabels.Type `json:"labels"` } -func NewCreateRequestInput(name, connectorUid, connectorType, socketAddress, username, password string, ignoreCertificate bool, tags tags.Type) *CreateInput { +func NewCreateRequestInput(name, connectorUid, connectorType, socketAddress, username, password string, ignoreCertificate bool, labels publicapilabels.Type) *CreateInput { return &CreateInput{ Name: name, ConnectorUid: connectorUid, @@ -55,7 +56,7 @@ func NewCreateRequestInput(name, connectorUid, connectorType, socketAddress, use Username: username, Password: password, IgnoreCertificate: ignoreCertificate, - Tags: tags, + Labels: labels, } } @@ -82,7 +83,7 @@ func Create(ctx context.Context, client http.Client, createInp CreateInput) (*Cr Username: createInp.Username, Password: createInp.Password, IgnoreCertificate: createInp.IgnoreCertificate, - Tags: createInp.Tags, + Labels: createInp.Labels, }, ) if err != nil { diff --git a/client/device/ios/create_test.go b/client/device/ios/create_test.go index 82c067cc..d6fb2d8b 100644 --- a/client/device/ios/create_test.go +++ b/client/device/ios/create_test.go @@ -2,14 +2,15 @@ package ios_test import ( "context" + "testing" + "time" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/ios" internalHttp "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/http" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/publicapi/transaction/transactiontype" internalTesting "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/testing" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/url" "github.com/stretchr/testify/assert" - "testing" - "time" "github.com/jarcoal/httpmock" ) diff --git a/client/device/ios/read_test.go b/client/device/ios/read_test.go index c17c863e..b4f2a7ca 100644 --- a/client/device/ios/read_test.go +++ b/client/device/ios/read_test.go @@ -2,14 +2,15 @@ package ios_test import ( "context" - "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/ios" - "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" - "github.com/stretchr/testify/assert" "testing" "time" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/ios" + "github.com/stretchr/testify/assert" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/http" + internalTesting "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/testing" "github.com/jarcoal/httpmock" ) @@ -23,7 +24,7 @@ func TestIosRead(t *testing.T) { WithName("my-ios"). OnboardedUsingOnPremConnector("88888888-8888-8888-8888-888888888888"). WithLocation("10.10.0.1", 443). - WithTags(tags.New("tags1", "tags2", "tags3")). + WithTags(internalTesting.NewTestingTags()). Build() testCases := []struct { diff --git a/client/device/ios/update.go b/client/device/ios/update.go index a38443a7..66ac5451 100644 --- a/client/device/ios/update.go +++ b/client/device/ios/update.go @@ -2,6 +2,7 @@ package ios import ( "context" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device" diff --git a/client/device/ios/update_test.go b/client/device/ios/update_test.go index b58dd2a3..e8f72851 100644 --- a/client/device/ios/update_test.go +++ b/client/device/ios/update_test.go @@ -2,15 +2,16 @@ package ios_test import ( "context" + "testing" + "time" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/connector" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/ios" - "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" "github.com/stretchr/testify/assert" - "testing" - "time" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/http" + internalTesting "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/testing" "github.com/jarcoal/httpmock" ) @@ -31,7 +32,7 @@ func TestIosUpdate(t *testing.T) { WithName("my-ios"). OnboardedUsingOnPremConnector(onPremConnector.Uid). WithLocation("10.10.0.1", 443). - WithTags(tags.New("tags1", "tags2", "tags3")). + WithTags(internalTesting.NewTestingTags()). Build() testCases := []struct { diff --git a/client/internal/maputil/filter.go b/client/internal/maputil/filter.go new file mode 100644 index 00000000..9f7a473e --- /dev/null +++ b/client/internal/maputil/filter.go @@ -0,0 +1,14 @@ +package maputil + +// FilterKeys creates a copy of the passed map containing only keys that return true when passed into the predicate +func FilterKeys[Key comparable, Value any](m map[Key]Value, predicate func(Key) bool) map[Key]Value { + resultMap := map[Key]Value{} + + for k, v := range m { + if predicate(k) { + resultMap[k] = v + } + } + + return resultMap +} diff --git a/client/internal/testing/asa.go b/client/internal/testing/asa.go index e50e291d..b62cadcf 100644 --- a/client/internal/testing/asa.go +++ b/client/internal/testing/asa.go @@ -2,12 +2,14 @@ package testing import ( "fmt" + "time" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/asa" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/publicapilabels" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/devicetype" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/statemachine" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/statemachine/state" - "time" ) func (m Model) AsaReadOutput() asa.ReadOutput { @@ -38,7 +40,7 @@ func (m Model) AsaCreateInput() asa.CreateInput { ConnectorUid: m.CdgUid.String(), ConnectorType: "CDG", SocketAddress: fmt.Sprintf("%s:%s", m.AsaHost, m.AsaPort), - Tags: tags.Type{}, + Labels: publicapilabels.Empty(), Username: m.AsaUsername, Password: m.AsaPassword, IgnoreCertificate: false, diff --git a/client/internal/testing/duo.go b/client/internal/testing/duo.go index 737cab31..5fcf0a06 100644 --- a/client/internal/testing/duo.go +++ b/client/internal/testing/duo.go @@ -12,7 +12,7 @@ func (m Model) DuoAdminPanelCreateInput() duoadminpanel.CreateInput { Host: m.DuoAdminPanelHost, IntegrationKey: "test-int-key", SecretKey: "test-secret-key", - Labels: []string{"lab1", "lab2", "lab3"}, + Labels: NewTestingLabels(), } } @@ -25,6 +25,6 @@ func (m Model) DuoAdminPanelReadOutput() duoadminpanel.ReadOutput { Uid: m.DuoAdminPanelUid.String(), Name: m.DuoAdminPanelName, State: state.DONE, - Tags: tags.New(m.DuoAdminPanelLabels...), + Tags: tags.NewUngrouped(m.DuoAdminPanelLabels...), } } diff --git a/client/internal/testing/ftd.go b/client/internal/testing/ftd.go index ec00dc56..4ef9cd58 100644 --- a/client/internal/testing/ftd.go +++ b/client/internal/testing/ftd.go @@ -3,6 +3,7 @@ package testing import ( "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/cloudftd" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/cloudftd/cloudftdonboarding" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/publicapilabels" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" ) @@ -19,7 +20,7 @@ func (m Model) FtdCreateInput() cloudftd.CreateInput { PerformanceTier: &m.FtdPerformanceTier, Virtual: false, Licenses: nil, - Tags: tags.Type{}, + Labels: publicapilabels.Empty(), } } @@ -38,6 +39,6 @@ func (m Model) FtdReadOutput() cloudftd.ReadOutput { RegKey: "", }, State: "", - Tags: tags.Type{}, + Tags: tags.Empty(), } } diff --git a/client/internal/testing/ios.go b/client/internal/testing/ios.go index 43c6ea47..bfaba522 100644 --- a/client/internal/testing/ios.go +++ b/client/internal/testing/ios.go @@ -2,7 +2,9 @@ package testing import ( "fmt" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/ios" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/publicapilabels" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/devicetype" ) @@ -13,7 +15,7 @@ func (m Model) CreateIosInput() ios.CreateInput { ConnectorUid: m.CdgUid.String(), ConnectorType: "CDG", SocketAddress: m.IosHost, - Tags: tags.Type{}, + Labels: publicapilabels.Type{}, Username: m.IosUsername, Password: m.IosPassword, IgnoreCertificate: false, diff --git a/client/internal/testing/labels.go b/client/internal/testing/labels.go new file mode 100644 index 00000000..6bf5af7d --- /dev/null +++ b/client/internal/testing/labels.go @@ -0,0 +1,10 @@ +package testing + +import "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/publicapilabels" + +func NewTestingLabels() publicapilabels.Type { + return publicapilabels.New( + []string{"label-1", "label-2", "label-3"}, + map[string][]string{"grouped-labels": {"grouped-label-1", "grouped-label-2", "grouped-label-3"}}, + ) +} diff --git a/client/internal/testing/tags.go b/client/internal/testing/tags.go new file mode 100644 index 00000000..f8261b8d --- /dev/null +++ b/client/internal/testing/tags.go @@ -0,0 +1,12 @@ +package testing + +import ( + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" +) + +func NewTestingTags() tags.Type { + return tags.New( + []string{"label-1", "label-2", "label-3"}, + map[string][]string{"grouped-labels": {"grouped-label-1", "grouped-label-2", "grouped-label-3"}}, + ) +} diff --git a/client/model/device/publicapilabels/tags.go b/client/model/device/publicapilabels/tags.go new file mode 100644 index 00000000..4cc7f2da --- /dev/null +++ b/client/model/device/publicapilabels/tags.go @@ -0,0 +1,30 @@ +package publicapilabels + +type Type struct { + GroupedLabels map[string][]string `json:"groupedLabels"` + UngroupedLabels []string `json:"ungroupedLabels"` +} + +func Empty() Type { + return Type{} +} + +func NewUnlabelled(tags ...string) Type { + return Type{ + UngroupedLabels: tags, + GroupedLabels: map[string][]string{}, + } +} + +func New(tags []string, groupedTags map[string][]string) Type { + groupedLabels := map[string][]string{} + + for k, v := range groupedTags { + groupedLabels[k] = v + } + + return Type{ + UngroupedLabels: tags, + GroupedLabels: groupedLabels, + } +} diff --git a/client/model/device/tags/tags.go b/client/model/device/tags/tags.go index 88b70de8..1ef95f8f 100644 --- a/client/model/device/tags/tags.go +++ b/client/model/device/tags/tags.go @@ -1,35 +1,43 @@ package tags -import ( - "encoding/json" - "fmt" -) +import "github.com/CiscoDevnet/terraform-provider-cdo/go-client/internal/maputil" + +const ungroupedLabelKeyName = "labels" // Type should be used with json:"tags" -type Type struct { - Labels []string `json:"labels,omitempty"` -} +type Type map[string][]string func Empty() Type { - return New() + return Type{} } -func New(tags ...string) Type { +func NewUngrouped(tags ...string) Type { return Type{ - Labels: tags, + ungroupedLabelKeyName: tags, } } -func (tags Type) GetLabelsJsonArrayString() string { - b, _ := json.Marshal(tags.Labels) - return fmt.Sprintf("%v", string(b)) +func New(tags []string, groupedTags map[string][]string) Type { + outputTags := Type{} + + for k, v := range groupedTags { + outputTags[k] = v + } + + outputTags[ungroupedLabelKeyName] = tags + + return outputTags } -func MustParseJsonArrayString(s string) []string { - var labels []string - err := json.Unmarshal([]byte(s), &labels) - if err != nil { - panic(err) +func (t Type) UngroupedTags() []string { + label, ok := t[ungroupedLabelKeyName] + if !ok { + return []string{} } - return labels + + return label +} + +func (t Type) GroupedTags() map[string][]string { + return maputil.FilterKeys(t, func(s string) bool { return s != ungroupedLabelKeyName }) } diff --git a/docs/data-sources/asa_device.md b/docs/data-sources/asa_device.md index e5ec8105..83aca156 100644 --- a/docs/data-sources/asa_device.md +++ b/docs/data-sources/asa_device.md @@ -22,6 +22,7 @@ ASA data source ### Read-Only - `connector_type` (String) The type of the connector that is used to communicate with the device. CDO can communicate with your device using either a Cloud Connector (CDG) or a Secure Device Connector (SDC); see [the CDO documentation](https://docs.defenseorchestrator.com/c-connect-cisco-defense-orchestratortor-the-secure-device-connector.html) to learn more (Valid values: [CDG, SDC]). +- `grouped_labels` (Map of Set of String) The grouped labels applied to the device. Labels are used to group devices in CDO. 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. - `host` (String) The host used to connect to the device. - `id` (String) Universally unique identifier of the device. - `ignore_certificate` (Boolean) This attribute indicates whether certificates were ignored when onboarding this device. diff --git a/docs/data-sources/ftd_device.md b/docs/data-sources/ftd_device.md index c7f46ecd..1b429512 100644 --- a/docs/data-sources/ftd_device.md +++ b/docs/data-sources/ftd_device.md @@ -24,9 +24,10 @@ Ftd data source - `access_policy_id` (String) The ID of the cloud-delivered FMC (cdFMC) access policy applied to this FTD. - `access_policy_name` (String) The name of the Cloud-Delivered FMC (cdFMC) access policy that will be used by the FTD. - `generated_command` (String) The command to run in the FTD CLI to register it with the cloud-delivered FMC (cdFMC). +- `grouped_labels` (Map of Set of String) A map of grouped 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. - `hostname` (String) The Hostname of the cloud-delivered FMC (cdFMC) manages this FTD. - `id` (String) Unique identifier of the device. This is a UUID and is automatically generated when the device is created. -- `labels` (List of String) 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. +- `labels` (Set of String) 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. - `licenses` (List of String) Comma-separated list of licenses to apply to this FTD. You must enable at least the "BASE" license. Allowed values are: ["BASE", "CARRIER", "THREAT", "MALWARE", "URLFilter",]. - `nat_id` (String) The Network Address Translation (NAT) ID of this FTD. - `performance_tier` (String) The performance tier of the virtual FTD, if virtual is set to false, this field is ignored as performance tiers are not applicable to physical FTD devices. Allowed values are: ["FTDv5", "FTDv10", "FTDv20", "FTDv30", "FTDv50", "FTDv100", "FTDv"]. diff --git a/docs/data-sources/ios_device.md b/docs/data-sources/ios_device.md index 85baa99b..474788d9 100644 --- a/docs/data-sources/ios_device.md +++ b/docs/data-sources/ios_device.md @@ -22,6 +22,7 @@ IOS data source ### Read-Only - `connector_name` (String) The name of the Secure Device Connector (SDC) that is used by CDO to communicate with the device. +- `grouped_labels` (Map of Set of String) The grouped labels applied to the device. Labels are used to group devices in CDO. 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. - `host` (String) The host used to connect to the device. - `id` (String) Universally unique identifier of the device. - `ignore_certificate` (Boolean) This attribute indicates whether certificates were ignored when onboarding this device. diff --git a/docs/data-sources/tenant_settings.md b/docs/data-sources/tenant_settings.md new file mode 100644 index 00000000..35f507a5 --- /dev/null +++ b/docs/data-sources/tenant_settings.md @@ -0,0 +1,28 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "cdo_tenant_settings Data Source - cdo" +subcategory: "" +description: |- + Tenant-wide settings +--- + +# cdo_tenant_settings (Data Source) + +Tenant-wide settings + + + + +## Schema + +### Read-Only + +- `auto_accept_device_changes_enabled` (Boolean) This attribute indicates whether auto accept device changes is enabled for the tenant +- `auto_discover_on_prem_fmcs_enabled` (Boolean) This attribute indicates whether change request support is enabled for the tenant +- `change_request_support_enabled` (Boolean) This attribute indicates whether change request support is enabled for the tenant +- `conflict_detection_interval` (String) The interval used by CDO to detect conflicts on devices +- `deny_cisco_support_access_to_tenant_enabled` (Boolean) This attribute indicates whether denying cisco support engineers access to the tenant is enabled +- `id` (String) Universally unique identifier of the tenant +- `multi_cloud_defense_enabled` (Boolean) This attribute indicates whether multi cloud defense is enabled for the tenant +- `scheduled_deployments_enabled` (Boolean) This attribute indicates whether scheduled deployments is enabled for the tenant +- `web_analytics_enabled` (Boolean) This attribute indicates whether web analytics is enabled for the tenant diff --git a/docs/resources/asa_device.md b/docs/resources/asa_device.md index 5103c772..529c087d 100644 --- a/docs/resources/asa_device.md +++ b/docs/resources/asa_device.md @@ -27,6 +27,7 @@ Provides an ASA device resource. This allows ASA devices to be onboarded, update ### Optional - `connector_name` (String) The name of the Secure Device Connector (SDC) that will be used to communicate with the device. This value is not required if the connector type selected is Cloud Connector (CDG). +- `grouped_labels` (Map of Set of String) Specify a map of grouped 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. - `labels` (Set of String) Specify a set 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. ### Read-Only diff --git a/docs/resources/duo_admin_panel.md b/docs/resources/duo_admin_panel.md index 74c1c97d..9aabd79d 100644 --- a/docs/resources/duo_admin_panel.md +++ b/docs/resources/duo_admin_panel.md @@ -24,6 +24,7 @@ Provides an Duo Admin Panel resource. This allows Duo Admin Panels to be onboard ### Optional +- `grouped_labels` (Map of Set of String) Specify a set of grouped labels to identify the Duo Admin Panel 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. - `labels` (Set of String) Specify a set of labels to identify the Duo Admin Panel 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. ### Read-Only diff --git a/docs/resources/ftd_device.md b/docs/resources/ftd_device.md index 35bc9dde..b3ba97c4 100644 --- a/docs/resources/ftd_device.md +++ b/docs/resources/ftd_device.md @@ -24,6 +24,7 @@ Provides a Firewall Threat Defense device resource. Use this to onboard, update, ### Optional +- `grouped_labels` (Map of Set of String) Specify a map of grouped 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. - `labels` (Set of String) Specify a set 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. - `performance_tier` (String) The performance tier of the virtual FTD, if virtual is set to false, this field is ignored as performance tiers are not applicable to physical FTD devices. Allowed values are: ["FTDv5", "FTDv10", "FTDv20", "FTDv30", "FTDv50", "FTDv100", "FTDv"]. diff --git a/docs/resources/ios_device.md b/docs/resources/ios_device.md index 279aaf50..12757721 100644 --- a/docs/resources/ios_device.md +++ b/docs/resources/ios_device.md @@ -26,6 +26,7 @@ Provides an iOS device resource. This allows iOS devices to be onboarded, update ### Optional +- `grouped_labels` (Map of Set of String) Specify a set of grouped 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. - `labels` (Set of String) Specify a set 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. ### Read-Only diff --git a/docs/resources/tenant_settings.md b/docs/resources/tenant_settings.md new file mode 100644 index 00000000..bd16e4c0 --- /dev/null +++ b/docs/resources/tenant_settings.md @@ -0,0 +1,31 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "cdo_tenant_settings Resource - cdo" +subcategory: "" +description: |- + Tenant-wide settings +--- + +# cdo_tenant_settings (Resource) + +Tenant-wide settings + + + + +## Schema + +### Optional + +- `auto_accept_device_changes_enabled` (Boolean) This attribute indicates whether auto accept device changes is enabled for the tenant +- `auto_discover_on_prem_fmcs_enabled` (Boolean) This attribute indicates whether change request support is enabled for the tenant +- `change_request_support_enabled` (Boolean) This attribute indicates whether change request support is enabled for the tenant +- `conflict_detection_interval` (String) The interval used by CDO to detect conflicts on devices +- `deny_cisco_support_access_to_tenant_enabled` (Boolean) This attribute indicates whether denying cisco support engineers access to the tenant is enabled +- `multi_cloud_defense_enabled` (Boolean) This attribute indicates whether multi cloud defense is enabled for the tenant +- `scheduled_deployments_enabled` (Boolean) This attribute indicates whether scheduled deployments is enabled for the tenant +- `web_analytics_enabled` (Boolean) This attribute indicates whether web analytics is enabled for the tenant + +### Read-Only + +- `id` (String) Universally unique identifier of the tenant diff --git a/provider/internal/acctest/environment.go b/provider/internal/acctest/environment.go index 0e46ca93..ab67e09d 100644 --- a/provider/internal/acctest/environment.go +++ b/provider/internal/acctest/environment.go @@ -5,8 +5,6 @@ import ( "os" "strconv" "strings" - - "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" ) type env struct{} @@ -77,9 +75,8 @@ func (e *env) IosResourceIgnoreCertificate() string { return e.mustGetString("IOS_RESOURCE_IGNORE_CERTIFICATE") } -func (e *env) IosResourceTags() tags.Type { - tagsEnv := e.mustGetString("IOS_RESOURCE_TAGS") - return tags.New(strings.Split(tagsEnv, ",")...) +func (e *env) IosResourceTags() []string { + return e.mustGetCommaSeparatedSlice("IOS_RESOURCE_TAGS") } func (e *env) IosResourceHost() string { @@ -102,9 +99,8 @@ func (e *env) IosDataSourceIgnoreCertificate() string { return e.mustGetString("IOS_DATA_SOURCE_IGNORE_CERTIFICATE") } -func (e *env) IosDataSourceTags() tags.Type { - tagsEnv := e.mustGetString("IOS_DATA_SOURCE_TAGS") - return tags.New(strings.Split(tagsEnv, ",")...) +func (e *env) IosDataSourceTags() []string { + return e.mustGetCommaSeparatedSlice("IOS_DATA_SOURCE_TAGS") } func (e *env) FtdDataSourceName() string { @@ -144,11 +140,6 @@ func (e *env) FtdResourceLicenses() string { return e.mustGetString("FTD_RESOURCE_LICENSES") } -func (e *env) FtdResourceTags() tags.Type { - tagsEnv := e.mustGetString("FTD_RESOURCE_TAGS") - return tags.New(strings.Split(tagsEnv, ",")...) -} - func (e *env) FtdResourceNewName() string { return e.mustGetString("FTD_RESOURCE_NEW_NAME") } @@ -189,11 +180,6 @@ func (e *env) AsaResourceSdcIgnoreCertificate() bool { return e.mustGetBool("ASA_RESOURCE_SDC_IGNORE_CERTIFICATE") } -func (e *env) AsaResourceSdcTags() tags.Type { - tagsEnv := e.mustGetString("ASA_RESOURCE_SDC_TAGS") - return tags.New(strings.Split(tagsEnv, ",")...) -} - func (e *env) AsaResourceAlternativeDeviceLocation() string { return e.mustGetString("ASA_RESOURCE_SDC_ALTERNATIVE_DEVICE_LOCATION") } @@ -234,9 +220,8 @@ func (e *env) AsaResourceCdgIgnoreCertificate() bool { return e.mustGetBool("ASA_RESOURCE_CDG_IGNORE_CERTIFICATE") } -func (e *env) AsaResourceCdgTags() tags.Type { - tagsEnv := e.mustGetString("ASA_RESOURCE_CDG_TAGS") - return tags.New(strings.Split(tagsEnv, ",")...) +func (e *env) AsaResourceCdgTags() []string { + return e.mustGetCommaSeparatedSlice("ASA_RESOURCE_CDG_TAGS") } func (e *env) AsaResourceCdgHost() string { @@ -279,9 +264,8 @@ func (e *env) AsaDataSourceIgnoreCertificate() bool { return e.mustGetBool("ASA_DATA_SOURCE_IGNORE_CERTIFICATE") } -func (e *env) AsaDataSourceTags() tags.Type { - tagsEnv := e.mustGetString("ASA_DATA_SOURCE_TAGS") - return tags.New(strings.Split(tagsEnv, ",")...) +func (e *env) AsaDataSourceTags() []string { + return e.mustGetCommaSeparatedSlice("ASA_DATA_SOURCE_TAGS") } func (e *env) ConnectorDataSourceName() string { @@ -328,11 +312,6 @@ func (e *env) DuoAdminPanelResourceSecretKey() string { return e.mustGetString("DUO_ADMIN_PANEL_RESOURCE_SECRET_KEY") } -func (e *env) DuoAdminPanelResourceTags() tags.Type { - tagsEnv := e.mustGetString("DUO_ADMIN_PANEL_RESOURCE_TAGS") - return tags.New(strings.Split(tagsEnv, ",")...) -} - func (e *env) TenantSettingsTenantUid() string { return e.mustGetString("TENANT_SETTINGS_TENANT_UID") } @@ -345,6 +324,12 @@ func (e *env) mustGetString(envName string) string { panic(fmt.Sprintf("acceptance test requires environment variable: %s to be set.", envName)) } +func (e *env) mustGetCommaSeparatedSlice(envVarName string) []string { + str := e.mustGetString(envVarName) + + return strings.Split(str, ",") +} + func (e *env) mustGetBool(envName string) bool { value, ok := os.LookupEnv(envName) if ok { diff --git a/provider/internal/acctest/helper.go b/provider/internal/acctest/helper.go index 6839412c..a5506554 100644 --- a/provider/internal/acctest/helper.go +++ b/provider/internal/acctest/helper.go @@ -4,7 +4,10 @@ import ( "bytes" "fmt" "reflect" + "strings" "text/template" + + "github.com/CiscoDevnet/terraform-provider-cdo/internal/util/testutil" ) // given a string template and an object, @@ -31,3 +34,24 @@ func MustOverrideFields[K any](obj K, fields map[string]any) K { return copyObj } + +func MustGenerateLabelsTF(labels map[string][]string) string { + type keyValue struct { + Key string + Value string + } + + fieldLines := []string{} + template := `"{{.Key}}" = toset({{.Value}})` + + for k, v := range labels { + valueJsonArr := testutil.MustJson(v) + fieldLine := MustParseTemplate(template, keyValue{k, valueJsonArr}) + + fieldLines = append(fieldLines, fieldLine) + } + + return fmt.Sprintf(`{ + %s + }`, strings.Join(fieldLines, ",\n")) +} diff --git a/provider/internal/device/asa/data_source.go b/provider/internal/device/asa/data_source.go index 41e8d081..febe9e9f 100644 --- a/provider/internal/device/asa/data_source.go +++ b/provider/internal/device/asa/data_source.go @@ -6,9 +6,10 @@ package asa import ( "context" "fmt" + "strconv" + "github.com/CiscoDevnet/terraform-provider-cdo/internal/util" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" - "strconv" cdoClient "github.com/CiscoDevnet/terraform-provider-cdo/go-client" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device" @@ -46,6 +47,7 @@ type AsaDataSourceModel struct { Port types.Int64 `tfsdk:"port"` IgnoreCertificate types.Bool `tfsdk:"ignore_certificate"` Labels []types.String `tfsdk:"labels"` + GroupedLabels types.Map `tfsdk:"grouped_labels"` } // define the name for this data source. @@ -104,6 +106,13 @@ func (d *AsaDataSource) Schema(ctx context.Context, req datasource.SchemaRequest listvalidator.UniqueValues(), }, }, + "grouped_labels": schema.MapAttribute{ + ElementType: types.SetType{ + ElemType: types.StringType, + }, + Computed: true, + MarkdownDescription: "The grouped labels applied to the device. Labels are used to group devices in CDO. 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.", + }, }, } } @@ -171,7 +180,8 @@ func (d *AsaDataSource) Read(ctx context.Context, req datasource.ReadRequest, re configData.Ipv4 = types.StringValue(readOutp.SocketAddress) configData.Host = types.StringValue(readOutp.Host) configData.IgnoreCertificate = types.BoolValue(readOutp.IgnoreCertificate) - configData.Labels = util.GoStringSliceToTFStringList(readOutp.Tags.Labels) + configData.Labels = util.GoStringSliceToTFStringList(readOutp.Tags.UngroupedTags()) + configData.GroupedLabels = util.GoMapToStringSetTFMap(readOutp.Tags.GroupedTags()) tflog.Trace(ctx, "done read ASA device data source") diff --git a/provider/internal/device/asa/data_source_test.go b/provider/internal/device/asa/data_source_test.go index 28807ea1..836a2ea0 100644 --- a/provider/internal/device/asa/data_source_test.go +++ b/provider/internal/device/asa/data_source_test.go @@ -4,7 +4,6 @@ import ( "strconv" "testing" - "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" "github.com/CiscoDevnet/terraform-provider-cdo/internal/util/testutil" "github.com/CiscoDevnet/terraform-provider-cdo/internal/acctest" @@ -18,7 +17,7 @@ var testAsaDataSource = struct { Host string Port int64 IgnoreCertificate bool - Tags tags.Type + Tags []string }{ ConnectorType: acctest.Env.AsaDataSourceConnectorType(), Name: acctest.Env.AsaDataSourceName(), @@ -51,8 +50,8 @@ func TestAccAsaDeviceDataSource(t *testing.T) { resource.TestCheckResourceAttr("data.cdo_asa_device.test", "host", testAsaDataSource.Host), resource.TestCheckResourceAttr("data.cdo_asa_device.test", "port", strconv.FormatInt(testAsaDataSource.Port, 10)), resource.TestCheckResourceAttr("data.cdo_asa_device.test", "ignore_certificate", strconv.FormatBool(testAsaDataSource.IgnoreCertificate)), - resource.TestCheckResourceAttr("data.cdo_asa_device.test", "labels.#", strconv.Itoa(len(testAsaDataSource.Tags.Labels))), - resource.TestCheckResourceAttrWith("data.cdo_asa_device.test", "labels.0", testutil.CheckEqual(testAsaDataSource.Tags.Labels[0])), + resource.TestCheckResourceAttr("data.cdo_asa_device.test", "labels.#", strconv.Itoa(len(testAsaDataSource.Tags))), + resource.TestCheckResourceAttrWith("data.cdo_asa_device.test", "labels.0", testutil.CheckEqual(testAsaDataSource.Tags[0])), ), }, }, diff --git a/provider/internal/device/asa/resource.go b/provider/internal/device/asa/resource.go index 44e1569e..2eba4466 100644 --- a/provider/internal/device/asa/resource.go +++ b/provider/internal/device/asa/resource.go @@ -2,15 +2,20 @@ package asa import ( "context" + "errors" "fmt" + "strconv" + "strings" + "github.com/CiscoDevnet/terraform-provider-cdo/internal/util" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault" - "strconv" - "strings" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/connector" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/publicapilabels" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" "github.com/CiscoDevnet/terraform-provider-cdo/validators" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" @@ -48,6 +53,7 @@ type AsaDeviceResourceModel struct { Host types.String `tfsdk:"host"` Port types.Int64 `tfsdk:"port"` Labels types.Set `tfsdk:"labels"` + GroupedLabels types.Map `tfsdk:"grouped_labels"` Username types.String `tfsdk:"username"` Password types.String `tfsdk:"password"` @@ -120,6 +126,15 @@ func (r *AsaDeviceResource) Schema(ctx context.Context, req resource.SchemaReque ElementType: types.StringType, Default: setdefault.StaticValue(types.SetValueMust(types.StringType, []attr.Value{})), // default to empty list }, + "grouped_labels": schema.MapAttribute{ + MarkdownDescription: "Specify a map of grouped 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, + Computed: true, + ElementType: types.SetType{ + ElemType: types.StringType, + }, + Default: mapdefault.StaticValue(types.MapValueMust(types.SetType{ElemType: types.StringType}, map[string]attr.Value{})), // default to empty list + }, "username": schema.StringAttribute{ MarkdownDescription: "The username used to authenticate with the device.", Required: true, @@ -200,7 +215,8 @@ func (r *AsaDeviceResource) Read(ctx context.Context, req resource.ReadRequest, stateData.SocketAddress = types.StringValue(asaReadOutp.SocketAddress) stateData.Host = types.StringValue(asaReadOutp.Host) stateData.IgnoreCertificate = types.BoolValue(asaReadOutp.IgnoreCertificate) - stateData.Labels = util.GoStringSliceToTFStringSet(asaReadOutp.Tags.Labels) + stateData.Labels = util.GoStringSliceToTFStringSet(asaReadOutp.Tags.UngroupedTags()) + stateData.GroupedLabels = util.GoMapToStringSetTFMap(asaReadOutp.Tags.GroupedTags()) tflog.Trace(ctx, "done read ASA device resource") @@ -236,7 +252,7 @@ func (r *AsaDeviceResource) Create(ctx context.Context, req resource.CreateReque } // convert tf tags to go tags - planTags, err := util.TFStringSetToTagLabels(ctx, planData.Labels) + planTags, err := labelsFromAsaDeviceResourceModel(ctx, &planData) if err != nil { res.Diagnostics.AddError("error while converting terraform tags to go tags", err.Error()) return @@ -302,7 +318,7 @@ func (r *AsaDeviceResource) Update(ctx context.Context, req resource.UpdateReque } // convert tf tags to go tags - planTags, err := util.TFStringSetToTagLabels(ctx, planData.Labels) + planTags, err := tagsFromAsaDeviceResourceModel(ctx, planData) if err != nil { res.Diagnostics.AddError("error while converting terraform tags to go tags", err.Error()) return @@ -356,6 +372,7 @@ func (r *AsaDeviceResource) Update(ctx context.Context, req resource.UpdateReque stateData.Host = types.StringValue(updateOutp.Host) stateData.Port = types.Int64Value(port) stateData.Labels = planData.Labels + stateData.GroupedLabels = planData.GroupedLabels stateData.IgnoreCertificate = planData.IgnoreCertificate @@ -435,3 +452,39 @@ func getConnectorName(planData *AsaDeviceResourceModel) basetypes.StringValue { return types.StringNull() } } + +func ungroupedAndGroupedLabelsFromResourceModel(ctx context.Context, resourceModel *AsaDeviceResourceModel) ([]string, map[string][]string, error) { + if resourceModel == nil { + return nil, nil, errors.New("resource model cannot be nil") + } + + ungroupedLabels, err := util.TFStringSetToGoStringList(ctx, resourceModel.Labels) + if err != nil { + return nil, nil, fmt.Errorf("error while converting terraform labels to go slice, %s", resourceModel.Labels) + } + + groupedLabels, err := util.TFMapToGoMapOfStringSlices(ctx, resourceModel.GroupedLabels) + if err != nil { + return nil, nil, fmt.Errorf("error while converting terraform grouped labels to go map, %v", resourceModel.GroupedLabels) + } + + return ungroupedLabels, groupedLabels, nil +} + +func tagsFromAsaDeviceResourceModel(ctx context.Context, resourceModel *AsaDeviceResourceModel) (tags.Type, error) { + ungroupedLabels, groupedLabels, err := ungroupedAndGroupedLabelsFromResourceModel(ctx, resourceModel) + if err != nil { + return nil, err + } + + return tags.New(ungroupedLabels, groupedLabels), nil +} + +func labelsFromAsaDeviceResourceModel(ctx context.Context, resourceModel *AsaDeviceResourceModel) (publicapilabels.Type, error) { + ungroupedLabels, groupedLabels, err := ungroupedAndGroupedLabelsFromResourceModel(ctx, resourceModel) + if err != nil { + return publicapilabels.Empty(), err + } + + return publicapilabels.New(ungroupedLabels, groupedLabels), nil +} diff --git a/provider/internal/device/asa/resource_test.go b/provider/internal/device/asa/resource_test.go index 2bd581d6..6fb543eb 100644 --- a/provider/internal/device/asa/resource_test.go +++ b/provider/internal/device/asa/resource_test.go @@ -2,18 +2,20 @@ package asa_test import ( - "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" - "github.com/CiscoDevnet/terraform-provider-cdo/internal/util/sliceutil" "regexp" "strconv" "testing" + "github.com/CiscoDevnet/terraform-provider-cdo/internal/util/sliceutil" "github.com/CiscoDevnet/terraform-provider-cdo/internal/util/testutil" "github.com/CiscoDevnet/terraform-provider-cdo/internal/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) +var labels = []string{"acceptancetest", "testasa", "terraform"} +var groupedLabels = map[string][]string{"acceptancetest": sliceutil.Map(labels, func(input string) string { return "grouped-" + input })} + type testAsaResourceType struct { Name string SocketAddress string @@ -23,6 +25,7 @@ type testAsaResourceType struct { Password string IgnoreCertificate bool Labels string + GroupedLabels string Host string Port int64 @@ -38,6 +41,7 @@ resource "cdo_asa_device" "test" { password = "{{.Password}}" ignore_certificate = "{{.IgnoreCertificate}}" labels = {{.Labels}} + grouped_labels = {{.GroupedLabels}} }` const asaResourceTemplateNoLabels = ` @@ -62,17 +66,19 @@ var testAsaResource_SDC = testAsaResourceType{ Username: acctest.Env.AsaResourceSdcUsername(), Password: acctest.Env.AsaResourceSdcPassword(), IgnoreCertificate: acctest.Env.AsaResourceSdcIgnoreCertificate(), - Labels: acctest.Env.AsaResourceSdcTags().GetLabelsJsonArrayString(), + Labels: testutil.MustJson(labels), + GroupedLabels: acctest.MustGenerateLabelsTF(groupedLabels), Host: acctest.Env.AsaResourceSdcHost(), Port: acctest.Env.AsaResourceSdcPort(), } var testAsaResourceConfig_SDC = acctest.MustParseTemplate(asaResourceTemplate, testAsaResource_SDC) + var testAsaResourceConfig_SDC_NoLabels = acctest.MustParseTemplate(asaResourceTemplateNoLabels, testAsaResource_SDC) // new label order config. -var reorderedLabels = tags.New(sliceutil.Reverse[string](tags.MustParseJsonArrayString(testAsaResource_SDC.Labels))...).GetLabelsJsonArrayString() +var reorderedLabels = testutil.MustJson(sliceutil.Reverse(labels)) var testAsaResource_SDC_ReorderedLabels = acctest.MustOverrideFields(testAsaResource_SDC, map[string]any{ "Labels": reorderedLabels, @@ -91,6 +97,14 @@ var testAsaResource_SDC_BadCreds = acctest.MustOverrideFields(testAsaResource_SD }) var testAsaResourceConfig_SDC_NewCreds = acctest.MustParseTemplate(asaResourceTemplate, testAsaResource_SDC_BadCreds) +var renamedGroupedLabels = map[string][]string{ + "my-cool-new-label-group": groupedLabels["acceptancetest"], +} +var testAsaResource_SDC_ReplaceGroupedLabels = acctest.MustOverrideFields(testAsaResource_SDC, map[string]any{ + "GroupedLabels": acctest.MustGenerateLabelsTF(renamedGroupedLabels), +}) +var testAsaResourceConfig_SDC_ReplaceGroupedLabels = acctest.MustParseTemplate(asaResourceTemplate, testAsaResource_SDC_ReplaceGroupedLabels) + func TestAccAsaDeviceResource_SDC(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: acctest.PreCheckFunc(t), @@ -107,10 +121,15 @@ func TestAccAsaDeviceResource_SDC(t *testing.T) { resource.TestCheckResourceAttr("cdo_asa_device.test", "connector_type", testAsaResource_SDC.ConnectorType), resource.TestCheckResourceAttr("cdo_asa_device.test", "username", testAsaResource_SDC.Username), resource.TestCheckResourceAttr("cdo_asa_device.test", "password", testAsaResource_SDC.Password), - resource.TestCheckResourceAttr("cdo_asa_device.test", "labels.#", strconv.Itoa(len(acctest.Env.AsaResourceSdcTags().Labels))), - resource.TestCheckResourceAttrWith("cdo_asa_device.test", "labels.0", testutil.CheckEqual(acctest.Env.AsaResourceSdcTags().Labels[0])), - resource.TestCheckResourceAttrWith("cdo_asa_device.test", "labels.1", testutil.CheckEqual(acctest.Env.AsaResourceSdcTags().Labels[1])), - resource.TestCheckResourceAttrWith("cdo_asa_device.test", "labels.2", testutil.CheckEqual(acctest.Env.AsaResourceSdcTags().Labels[2])), + resource.TestCheckResourceAttr("cdo_asa_device.test", "labels.#", strconv.Itoa(len(labels))), + resource.TestCheckTypeSetElemAttr("cdo_asa_device.test", "labels.*", labels[0]), + resource.TestCheckTypeSetElemAttr("cdo_asa_device.test", "labels.*", labels[1]), + resource.TestCheckTypeSetElemAttr("cdo_asa_device.test", "labels.*", labels[2]), + resource.TestCheckResourceAttr("cdo_asa_device.test", "grouped_labels.%", "1"), + resource.TestCheckResourceAttr("cdo_asa_device.test", "grouped_labels.acceptancetest.#", strconv.Itoa(len(groupedLabels["acceptancetest"]))), + resource.TestCheckTypeSetElemAttr("cdo_asa_device.test", "grouped_labels.acceptancetest.*", groupedLabels["acceptancetest"][0]), + resource.TestCheckTypeSetElemAttr("cdo_asa_device.test", "grouped_labels.acceptancetest.*", groupedLabels["acceptancetest"][1]), + resource.TestCheckTypeSetElemAttr("cdo_asa_device.test", "grouped_labels.acceptancetest.*", groupedLabels["acceptancetest"][2]), ), }, // bad credential tests @@ -142,6 +161,17 @@ func TestAccAsaDeviceResource_SDC(t *testing.T) { resource.TestCheckResourceAttr("cdo_asa_device.test", "name", testAsaResource_SDC_NewName.Name), ), }, + // Replace grouped labels + { + Config: acctest.ProviderConfig() + testAsaResourceConfig_SDC_ReplaceGroupedLabels, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("cdo_asa_device.test", "grouped_labels.%", "1"), + resource.TestCheckResourceAttr("cdo_asa_device.test", "grouped_labels.my-cool-new-label-group.#", strconv.Itoa(len(renamedGroupedLabels["my-cool-new-label-group"]))), + resource.TestCheckTypeSetElemAttr("cdo_asa_device.test", "grouped_labels.my-cool-new-label-group.*", renamedGroupedLabels["my-cool-new-label-group"][0]), + resource.TestCheckTypeSetElemAttr("cdo_asa_device.test", "grouped_labels.my-cool-new-label-group.*", renamedGroupedLabels["my-cool-new-label-group"][1]), + resource.TestCheckTypeSetElemAttr("cdo_asa_device.test", "grouped_labels.my-cool-new-label-group.*", renamedGroupedLabels["my-cool-new-label-group"][2]), + ), + }, // change location test - disabled until we create another asa // { diff --git a/provider/internal/device/ftd/data_source.go b/provider/internal/device/ftd/data_source.go index d9ce85a9..af496917 100644 --- a/provider/internal/device/ftd/data_source.go +++ b/provider/internal/device/ftd/data_source.go @@ -3,6 +3,7 @@ package ftd import ( "context" "fmt" + cdoClient "github.com/CiscoDevnet/terraform-provider-cdo/go-client" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" @@ -34,6 +35,7 @@ type DataSourceModel struct { Virtual types.Bool `tfsdk:"virtual"` Licenses []types.String `tfsdk:"licenses"` Labels []types.String `tfsdk:"labels"` + GroupedLabels types.Map `tfsdk:"grouped_labels"` AccessPolicyUid types.String `tfsdk:"access_policy_id"` GeneratedCommand types.String `tfsdk:"generated_command"` @@ -80,11 +82,18 @@ func (d *DataSource) Schema(ctx context.Context, req datasource.SchemaRequest, r MarkdownDescription: "Comma-separated list of licenses to apply to this FTD. You must enable at least the \"BASE\" license. Allowed values are: [\"BASE\", \"CARRIER\", \"THREAT\", \"MALWARE\", \"URLFilter\",].", Computed: true, }, - "labels": schema.ListAttribute{ - 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.", + "labels": schema.SetAttribute{ + MarkdownDescription: "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.", Computed: true, ElementType: types.StringType, }, + "grouped_labels": schema.MapAttribute{ + MarkdownDescription: "A map of grouped 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.", + Computed: true, + ElementType: types.SetType{ + ElemType: types.StringType, + }, + }, "generated_command": schema.StringAttribute{ MarkdownDescription: "The command to run in the FTD CLI to register it with the cloud-delivered FMC (cdFMC).", Computed: true, diff --git a/provider/internal/device/ftd/operation.go b/provider/internal/device/ftd/operation.go index 595d21df..8060a7a7 100644 --- a/provider/internal/device/ftd/operation.go +++ b/provider/internal/device/ftd/operation.go @@ -2,9 +2,14 @@ package ftd import ( "context" - "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/ftd/license" + "errors" + "fmt" "strings" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/publicapilabels" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/ftd/license" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/cloudftd" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/ftd/tier" "github.com/CiscoDevnet/terraform-provider-cdo/internal/util" @@ -31,7 +36,8 @@ func ReadDataSource(ctx context.Context, resource *DataSource, stateData *DataSo stateData.PerformanceTier = types.StringValue(string(*res.Metadata.PerformanceTier)) } stateData.Hostname = types.StringValue(res.Metadata.CloudManagerDomain) - stateData.Labels = util.GoStringSliceToTFStringList(res.Tags.Labels) + stateData.Labels = util.GoStringSliceToTFStringList(res.Tags.UngroupedTags()) + stateData.GroupedLabels = util.GoMapToStringSetTFMap(res.Tags.GroupedTags()) return nil } @@ -65,7 +71,8 @@ func Read(ctx context.Context, resource *Resource, stateData *ResourceModel) err stateData.Hostname = types.StringValue(res.Metadata.CloudManagerDomain) stateData.NatId = types.StringValue(res.Metadata.NatID) stateData.RegKey = types.StringValue(res.Metadata.RegKey) - stateData.Labels = util.GoStringSliceToTFStringSet(res.Tags.Labels) + stateData.Labels = util.GoStringSliceToTFStringSet(res.Tags.UngroupedTags()) + stateData.GroupedLabels = util.GoMapToStringSetTFMap(res.Tags.GroupedTags()) return nil } @@ -89,14 +96,11 @@ func Create(ctx context.Context, resource *Resource, planData *ResourceModel) er } // convert tf tags to go tags - planTags, err := util.TFStringSetToTagLabels(ctx, planData.Labels) + planTags, err := labelsFromResourceModel(ctx, planData) if err != nil { return err } - if err != nil { - return err - } createInp := cloudftd.NewCreateInput( planData.Name.ValueString(), planData.AccessPolicyName.ValueString(), @@ -122,7 +126,8 @@ func Create(ctx context.Context, resource *Resource, planData *ResourceModel) er planData.AccessPolicyName = types.StringValue(res.Metadata.AccessPolicyName) planData.AccessPolicyUid = types.StringValue(res.Metadata.AccessPolicyUid) planData.Licenses = util.GoStringSliceToTFStringSet(licenseStrings) - planData.Labels = util.GoStringSliceToTFStringSet(res.Tags.Labels) + planData.Labels = util.GoStringSliceToTFStringSet(res.Labels.UngroupedLabels) + planData.GroupedLabels = util.GoMapToStringSetTFMap(res.Labels.GroupedLabels) if res.Metadata.PerformanceTier != nil { // nil means physical cloud ftd planData.PerformanceTier = types.StringValue(string(*res.Metadata.PerformanceTier)) } @@ -139,7 +144,7 @@ func Update(ctx context.Context, resource *Resource, planData *ResourceModel, st // do update // convert tf tags to go tags - planTags, err := util.TFStringSetToTagLabels(ctx, planData.Labels) + planTags, err := tagsFromResourceModel(ctx, planData) if err != nil { return err } @@ -168,6 +173,7 @@ func Update(ctx context.Context, resource *Resource, planData *ResourceModel, st // map return struct to model stateData.Name = types.StringValue(res.Name) stateData.Labels = planData.Labels + stateData.GroupedLabels = planData.GroupedLabels stateData.Licenses = util.GoStringSliceToTFStringSet(licensesStrings) return nil @@ -181,3 +187,39 @@ func Delete(ctx context.Context, resource *Resource, stateData *ResourceModel) e return err } + +func ungroupedAndGroupedLabelsFromResourceModel(ctx context.Context, resourceModel *ResourceModel) ([]string, map[string][]string, error) { + if resourceModel == nil { + return nil, nil, errors.New("resource model cannot be nil") + } + + ungroupedLabels, err := util.TFStringSetToGoStringList(ctx, resourceModel.Labels) + if err != nil { + return nil, nil, fmt.Errorf("error while converting terraform labels to go slice, %s", resourceModel.Labels) + } + + groupedLabels, err := util.TFMapToGoMapOfStringSlices(ctx, resourceModel.GroupedLabels) + if err != nil { + return nil, nil, fmt.Errorf("error while converting terraform grouped labels to go map, %v", resourceModel.GroupedLabels) + } + + return ungroupedLabels, groupedLabels, nil +} + +func tagsFromResourceModel(ctx context.Context, resourceModel *ResourceModel) (tags.Type, error) { + ungroupedLabels, groupedLabels, err := ungroupedAndGroupedLabelsFromResourceModel(ctx, resourceModel) + if err != nil { + return nil, err + } + + return tags.New(ungroupedLabels, groupedLabels), nil +} + +func labelsFromResourceModel(ctx context.Context, resourceModel *ResourceModel) (publicapilabels.Type, error) { + ungroupedLabels, groupedLabels, err := ungroupedAndGroupedLabelsFromResourceModel(ctx, resourceModel) + if err != nil { + return publicapilabels.Empty(), err + } + + return publicapilabels.New(ungroupedLabels, groupedLabels), nil +} diff --git a/provider/internal/device/ftd/resource.go b/provider/internal/device/ftd/resource.go index 04b0b784..6266bda6 100644 --- a/provider/internal/device/ftd/resource.go +++ b/provider/internal/device/ftd/resource.go @@ -3,6 +3,7 @@ package ftd import ( "context" "fmt" + cdoClient "github.com/CiscoDevnet/terraform-provider-cdo/go-client" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/ftd/license" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/ftd/tier" @@ -15,6 +16,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" @@ -44,6 +46,7 @@ type ResourceModel struct { Virtual types.Bool `tfsdk:"virtual"` Licenses types.Set `tfsdk:"licenses"` Labels types.Set `tfsdk:"labels"` + GroupedLabels types.Map `tfsdk:"grouped_labels"` AccessPolicyUid types.String `tfsdk:"access_policy_id"` GeneratedCommand types.String `tfsdk:"generated_command"` @@ -116,6 +119,15 @@ func (r *Resource) Schema(ctx context.Context, req resource.SchemaRequest, resp Computed: true, Default: setdefault.StaticValue(types.SetValueMust(types.StringType, []attr.Value{})), // default to empty set }, + "grouped_labels": schema.MapAttribute{ + MarkdownDescription: "Specify a map of grouped 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.SetType{ + ElemType: types.StringType, + }, + Computed: true, + Default: mapdefault.StaticValue(types.MapValueMust(types.SetType{ElemType: types.StringType}, map[string]attr.Value{})), // default to empty set + }, "generated_command": schema.StringAttribute{ MarkdownDescription: "The command to run in the FTD CLI to register it with the cloud-delivered FMC (cdFMC).", Computed: true, diff --git a/provider/internal/device/ftd/resource_test.go b/provider/internal/device/ftd/resource_test.go index 35abc6e1..a0989893 100644 --- a/provider/internal/device/ftd/resource_test.go +++ b/provider/internal/device/ftd/resource_test.go @@ -2,17 +2,20 @@ package ftd_test import ( "fmt" - "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" - "github.com/CiscoDevnet/terraform-provider-cdo/internal/util/sliceutil" - "github.com/CiscoDevnet/terraform-provider-cdo/internal/util/testutil" "strconv" "strings" "testing" + "github.com/CiscoDevnet/terraform-provider-cdo/internal/util/sliceutil" + "github.com/CiscoDevnet/terraform-provider-cdo/internal/util/testutil" + "github.com/CiscoDevnet/terraform-provider-cdo/internal/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) +var labels = []string{"acceptancetest", "terraform", "test_ftd"} +var groupedLabels = map[string][]string{"acceptancetest": sliceutil.Map(labels, func(input string) string { return "grouped-" + input })} + type ResourceType struct { Name string AccessPolicyName string @@ -21,6 +24,7 @@ type ResourceType struct { Licenses string AccessPolicyUid string Labels string + GroupedLabels string } const ResourceTemplate = ` @@ -31,6 +35,7 @@ resource "cdo_ftd_device" "test" { virtual = "{{.Virtual}}" licenses = {{.Licenses}} labels = {{.Labels}} + grouped_labels = {{.GroupedLabels}} }` var testResource = ResourceType{ @@ -39,7 +44,8 @@ var testResource = ResourceType{ PerformanceTier: acctest.Env.FtdResourcePerformanceTier(), Virtual: acctest.Env.FtdResourceVirtual(), Licenses: acctest.Env.FtdResourceLicenses(), - Labels: acctest.Env.FtdResourceTags().GetLabelsJsonArrayString(), + Labels: testutil.MustJson(labels), + GroupedLabels: acctest.MustGenerateLabelsTF(groupedLabels), } var testResourceConfig = acctest.MustParseTemplate(ResourceTemplate, testResource) @@ -49,7 +55,7 @@ var testResource_NewName = acctest.MustOverrideFields(testResource, map[string]a var testResourceConfig_NewName = acctest.MustParseTemplate(ResourceTemplate, testResource_NewName) -var reorderedLabels = tags.New(sliceutil.Reverse[string](tags.MustParseJsonArrayString(testResource.Labels))...).GetLabelsJsonArrayString() +var reorderedLabels = testutil.MustJson(sliceutil.Reverse(labels)) var testResource_ReorderLabels = acctest.MustOverrideFields(testResource, map[string]any{ "Labels": reorderedLabels, @@ -57,6 +63,14 @@ var testResource_ReorderLabels = acctest.MustOverrideFields(testResource, map[st var testResourceConfig_ReorderLabels = acctest.MustParseTemplate(ResourceTemplate, testResource_ReorderLabels) +var renamedGroupedLabels = map[string][]string{ + "my-cool-new-label-group": groupedLabels["acceptancetest"], +} +var testResource_ReplaceGroupedLabels = acctest.MustOverrideFields(testResource, map[string]any{ + "GroupedLabels": acctest.MustGenerateLabelsTF(renamedGroupedLabels), +}) +var testResourceConfig_ReplaceGroupedLabels = acctest.MustParseTemplate(ResourceTemplate, testResource_ReplaceGroupedLabels) + func TestAccFtdResource(t *testing.T) { resource.Test(t, resource.TestCase{ @@ -74,10 +88,15 @@ func TestAccFtdResource(t *testing.T) { resource.TestCheckResourceAttrSet("cdo_ftd_device.test", "licenses.0"), // there is something at position 0 of licenses array resource.TestCheckResourceAttr("cdo_ftd_device.test", "licenses.#", "1"), // number of licenses = 1 resource.TestCheckResourceAttr("cdo_ftd_device.test", "access_policy_name", testResource.AccessPolicyName), - resource.TestCheckResourceAttr("cdo_ftd_device.test", "labels.#", strconv.Itoa(len(acctest.Env.FtdResourceTags().Labels))), - resource.TestCheckResourceAttrWith("cdo_ftd_device.test", "labels.0", testutil.CheckEqual(acctest.Env.FtdResourceTags().Labels[0])), - resource.TestCheckResourceAttrWith("cdo_ftd_device.test", "labels.1", testutil.CheckEqual(acctest.Env.FtdResourceTags().Labels[1])), - resource.TestCheckResourceAttrWith("cdo_ftd_device.test", "labels.2", testutil.CheckEqual(acctest.Env.FtdResourceTags().Labels[2])), + resource.TestCheckResourceAttr("cdo_ftd_device.test", "labels.#", strconv.Itoa(len(labels))), + resource.TestCheckTypeSetElemAttr("cdo_ftd_device.test", "labels.*", labels[0]), + resource.TestCheckTypeSetElemAttr("cdo_ftd_device.test", "labels.*", labels[1]), + resource.TestCheckTypeSetElemAttr("cdo_ftd_device.test", "labels.*", labels[2]), + resource.TestCheckResourceAttr("cdo_ftd_device.test", "grouped_labels.%", "1"), + resource.TestCheckResourceAttr("cdo_ftd_device.test", "grouped_labels.acceptancetest.#", strconv.Itoa(len(groupedLabels["acceptancetest"]))), + resource.TestCheckTypeSetElemAttr("cdo_ftd_device.test", "grouped_labels.acceptancetest.*", groupedLabels["acceptancetest"][0]), + resource.TestCheckTypeSetElemAttr("cdo_ftd_device.test", "grouped_labels.acceptancetest.*", groupedLabels["acceptancetest"][1]), + resource.TestCheckTypeSetElemAttr("cdo_ftd_device.test", "grouped_labels.acceptancetest.*", groupedLabels["acceptancetest"][2]), resource.TestCheckResourceAttrWith("cdo_ftd_device.test", "generated_command", func(value string) error { ok := strings.HasPrefix(value, "configure manager add") if !ok { @@ -99,6 +118,17 @@ func TestAccFtdResource(t *testing.T) { resource.TestCheckResourceAttr("cdo_ftd_device.test", "name", testResource_NewName.Name), ), }, + // Replace Grouped Labels + { + Config: acctest.ProviderConfig() + testResourceConfig_ReplaceGroupedLabels, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("cdo_ftd_device.test", "grouped_labels.%", "1"), + resource.TestCheckResourceAttr("cdo_ftd_device.test", "grouped_labels.my-cool-new-label-group.#", strconv.Itoa(len(renamedGroupedLabels["my-cool-new-label-group"]))), + resource.TestCheckTypeSetElemAttr("cdo_ftd_device.test", "grouped_labels.my-cool-new-label-group.*", renamedGroupedLabels["my-cool-new-label-group"][0]), + resource.TestCheckTypeSetElemAttr("cdo_ftd_device.test", "grouped_labels.my-cool-new-label-group.*", renamedGroupedLabels["my-cool-new-label-group"][1]), + resource.TestCheckTypeSetElemAttr("cdo_ftd_device.test", "grouped_labels.my-cool-new-label-group.*", renamedGroupedLabels["my-cool-new-label-group"][2]), + ), + }, // Delete testing automatically occurs in TestCase }, }) diff --git a/provider/internal/device/ios/data_source.go b/provider/internal/device/ios/data_source.go index 868d2007..c922973e 100644 --- a/provider/internal/device/ios/data_source.go +++ b/provider/internal/device/ios/data_source.go @@ -6,10 +6,11 @@ package ios import ( "context" "fmt" + "strconv" + "github.com/CiscoDevnet/terraform-provider-cdo/internal/util" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework/schema/validator" - "strconv" cdoClient "github.com/CiscoDevnet/terraform-provider-cdo/go-client" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device" @@ -44,6 +45,7 @@ type IosDataSourceModel struct { Port types.Int64 `tfsdk:"port"` IgnoreCertificate types.Bool `tfsdk:"ignore_certificate"` Labels []types.String `tfsdk:"labels"` + GroupedLabels types.Map `tfsdk:"grouped_labels"` } // define the name for this data source. @@ -95,6 +97,13 @@ func (d *IosDataSource) Schema(ctx context.Context, req datasource.SchemaRequest listvalidator.UniqueValues(), }, }, + "grouped_labels": schema.MapAttribute{ + MarkdownDescription: "The grouped labels applied to the device. Labels are used to group devices in CDO. 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.", + Computed: true, + ElementType: types.SetType{ + ElemType: types.StringType, + }, + }, }, } } @@ -161,7 +170,8 @@ func (d *IosDataSource) Read(ctx context.Context, req datasource.ReadRequest, re configData.SocketAddress = types.StringValue(readOutp.SocketAddress) configData.Host = types.StringValue(readOutp.Host) configData.IgnoreCertificate = types.BoolValue(readOutp.IgnoreCertificate) - configData.Labels = util.GoStringSliceToTFStringList(readOutp.Tags.Labels) + configData.Labels = util.GoStringSliceToTFStringList(readOutp.Tags.UngroupedTags()) + configData.GroupedLabels = util.GoMapToStringSetTFMap(readOutp.Tags.GroupedTags()) tflog.Trace(ctx, "done read IOS device data source") diff --git a/provider/internal/device/ios/data_source_test.go b/provider/internal/device/ios/data_source_test.go index f03fbc5b..e435859f 100644 --- a/provider/internal/device/ios/data_source_test.go +++ b/provider/internal/device/ios/data_source_test.go @@ -1,10 +1,11 @@ package ios_test import ( - "github.com/CiscoDevnet/terraform-provider-cdo/internal/util/testutil" "strconv" "testing" + "github.com/CiscoDevnet/terraform-provider-cdo/internal/util/testutil" + "github.com/CiscoDevnet/terraform-provider-cdo/internal/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) @@ -18,7 +19,7 @@ var testIosDataSource = struct { }{ Name: acctest.Env.IosDataSourceName(), IgnoreCertificate: acctest.Env.IosDataSourceIgnoreCertificate(), - Labels: asaDataSourceTags.GetLabelsJsonArrayString(), + Labels: testutil.MustJson(asaDataSourceTags), } var testIosDataSourceTemplate = ` @@ -38,10 +39,10 @@ func TestAccIosDeviceDataSource(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("data.cdo_ios_device.test", "name", testIosDataSource.Name), resource.TestCheckResourceAttr("data.cdo_ios_device.test", "ignore_certificate", testIosDataSource.IgnoreCertificate), - resource.TestCheckResourceAttr("data.cdo_ios_device.test", "labels.#", strconv.Itoa(len(asaDataSourceTags.Labels))), - resource.TestCheckResourceAttrWith("data.cdo_ios_device.test", "labels.0", testutil.CheckEqual(asaDataSourceTags.Labels[0])), - resource.TestCheckResourceAttrWith("data.cdo_ios_device.test", "labels.1", testutil.CheckEqual(asaDataSourceTags.Labels[1])), - resource.TestCheckResourceAttrWith("data.cdo_ios_device.test", "labels.2", testutil.CheckEqual(asaDataSourceTags.Labels[2])), + resource.TestCheckResourceAttr("data.cdo_ios_device.test", "labels.#", strconv.Itoa(len(asaDataSourceTags))), + resource.TestCheckResourceAttrWith("data.cdo_ios_device.test", "labels.0", testutil.CheckEqual(asaDataSourceTags[0])), + resource.TestCheckResourceAttrWith("data.cdo_ios_device.test", "labels.1", testutil.CheckEqual(asaDataSourceTags[1])), + resource.TestCheckResourceAttrWith("data.cdo_ios_device.test", "labels.2", testutil.CheckEqual(asaDataSourceTags[2])), ), }, }, diff --git a/provider/internal/device/ios/operation.go b/provider/internal/device/ios/operation.go index 5add2d42..c1cc4cb7 100644 --- a/provider/internal/device/ios/operation.go +++ b/provider/internal/device/ios/operation.go @@ -2,10 +2,14 @@ package ios import ( "context" + "errors" "fmt" + "strconv" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/connector" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/publicapilabels" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" "github.com/CiscoDevnet/terraform-provider-cdo/internal/util" - "strconv" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/ios" "github.com/hashicorp/terraform-plugin-framework/types" @@ -34,7 +38,8 @@ func Read(ctx context.Context, resource *IosDeviceResource, stateData *IosDevice stateData.Ipv4 = types.StringValue(readOutp.SocketAddress) stateData.Host = types.StringValue(readOutp.Host) stateData.IgnoreCertificate = types.BoolValue(readOutp.IgnoreCertificate) - stateData.Labels = util.GoStringSliceToTFStringSet(readOutp.Tags.Labels) + stateData.Labels = util.GoStringSliceToTFStringSet(readOutp.Tags.UngroupedTags()) + stateData.GroupedLabels = util.GoMapToStringSetTFMap(readOutp.Tags.GroupedTags()) return nil } @@ -51,7 +56,7 @@ func Create(ctx context.Context, resource *IosDeviceResource, planData *IosDevic } // convert tf tags to go tags - planTags, err := util.TFStringSetToTagLabels(ctx, planData.Labels) + planTags, err := labelsFromIosDeviceResourceModel(ctx, planData) if err != nil { return err } @@ -83,7 +88,8 @@ func Create(ctx context.Context, resource *IosDeviceResource, planData *IosDevic return fmt.Errorf("failed to parse IOS port, cause=%w", err) } planData.Port = types.Int64Value(port) - planData.Labels = util.GoStringSliceToTFStringSet(createOutp.Tags.Labels) + planData.Labels = util.GoStringSliceToTFStringSet(createOutp.Tags.UngroupedTags()) + planData.GroupedLabels = util.GoMapToStringSetTFMap(createOutp.Tags.GroupedTags()) return nil } @@ -91,7 +97,7 @@ func Create(ctx context.Context, resource *IosDeviceResource, planData *IosDevic func Update(ctx context.Context, resource *IosDeviceResource, planData *IosDeviceResourceModel, stateData *IosDeviceResourceModel) error { // convert tf tags to go tags - planTags, err := util.TFStringSetToTagLabels(ctx, planData.Labels) + planTags, err := tagsFromIosDeviceResourceModel(ctx, planData) if err != nil { return err } @@ -107,6 +113,7 @@ func Update(ctx context.Context, resource *IosDeviceResource, planData *IosDevic } stateData.Name = types.StringValue(updateOutp.Name) stateData.Labels = planData.Labels + stateData.GroupedLabels = planData.GroupedLabels return nil } @@ -116,3 +123,39 @@ func Delete(ctx context.Context, resource *IosDeviceResource, stateData *IosDevi _, err := resource.client.DeleteIos(ctx, *deleteInp) return err } + +func ungroupedAndGroupedLabelsFromIosDeviceResourceModel(ctx context.Context, resourceModel *IosDeviceResourceModel) ([]string, map[string][]string, error) { + if resourceModel == nil { + return nil, nil, errors.New("resource model cannot be nil") + } + + ungroupedLabels, err := util.TFStringSetToGoStringList(ctx, resourceModel.Labels) + if err != nil { + return nil, nil, fmt.Errorf("error while converting terraform labels to go slice, %s", resourceModel.Labels) + } + + groupedLabels, err := util.TFMapToGoMapOfStringSlices(ctx, resourceModel.GroupedLabels) + if err != nil { + return nil, nil, fmt.Errorf("error while converting terraform grouped labels to go map, %v", resourceModel.GroupedLabels) + } + + return ungroupedLabels, groupedLabels, nil +} + +func tagsFromIosDeviceResourceModel(ctx context.Context, resourceModel *IosDeviceResourceModel) (tags.Type, error) { + ungroupedLabels, groupedLabels, err := ungroupedAndGroupedLabelsFromIosDeviceResourceModel(ctx, resourceModel) + if err != nil { + return nil, err + } + + return tags.New(ungroupedLabels, groupedLabels), nil +} + +func labelsFromIosDeviceResourceModel(ctx context.Context, resourceModel *IosDeviceResourceModel) (publicapilabels.Type, error) { + ungroupedLabels, groupedLabels, err := ungroupedAndGroupedLabelsFromIosDeviceResourceModel(ctx, resourceModel) + if err != nil { + return publicapilabels.Empty(), err + } + + return publicapilabels.New(ungroupedLabels, groupedLabels), nil +} diff --git a/provider/internal/device/ios/resource.go b/provider/internal/device/ios/resource.go index 4e5d091e..6de180e7 100644 --- a/provider/internal/device/ios/resource.go +++ b/provider/internal/device/ios/resource.go @@ -3,11 +3,13 @@ package ios import ( "context" "fmt" - "github.com/CiscoDevnet/terraform-provider-cdo/internal/util" "strings" + "github.com/CiscoDevnet/terraform-provider-cdo/internal/util" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault" "github.com/CiscoDevnet/terraform-provider-cdo/validators" @@ -42,6 +44,7 @@ type IosDeviceResourceModel struct { Host types.String `tfsdk:"host"` Port types.Int64 `tfsdk:"port"` Labels types.Set `tfsdk:"labels"` + GroupedLabels types.Map `tfsdk:"grouped_labels"` Username types.String `tfsdk:"username"` Password types.String `tfsdk:"password"` @@ -127,6 +130,15 @@ func (r *IosDeviceResource) Schema(ctx context.Context, req resource.SchemaReque Computed: true, Default: setdefault.StaticValue(types.SetValueMust(types.StringType, []attr.Value{})), // default to empty list }, + "grouped_labels": schema.MapAttribute{ + MarkdownDescription: "Specify a set of grouped 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.SetType{ + ElemType: types.StringType, + }, + Computed: true, + Default: mapdefault.StaticValue(types.MapValueMust(types.SetType{ElemType: types.StringType}, map[string]attr.Value{})), // default to empty list + }, }, } } diff --git a/provider/internal/device/ios/resource_test.go b/provider/internal/device/ios/resource_test.go index edde7ba9..1238ba0f 100644 --- a/provider/internal/device/ios/resource_test.go +++ b/provider/internal/device/ios/resource_test.go @@ -1,16 +1,19 @@ package ios_test import ( - "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" - "github.com/CiscoDevnet/terraform-provider-cdo/internal/util/sliceutil" - "github.com/CiscoDevnet/terraform-provider-cdo/internal/util/testutil" "strconv" "testing" + "github.com/CiscoDevnet/terraform-provider-cdo/internal/util/sliceutil" + "github.com/CiscoDevnet/terraform-provider-cdo/internal/util/testutil" + "github.com/CiscoDevnet/terraform-provider-cdo/internal/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) +var labels = []string{"acceptancetest", "test-ios-device", "terraform"} +var groupedLabels = map[string][]string{"acceptancetest": sliceutil.Map(labels, func(input string) string { return "grouped-" + input })} + type testIosResourceType struct { Name string SocketAddress string @@ -20,6 +23,7 @@ type testIosResourceType struct { ConnectorName string IgnoreCertificate string Labels string + GroupedLabels string Host string Port int64 @@ -34,6 +38,7 @@ resource "cdo_ios_device" "test" { connector_name = "{{.ConnectorName}}" ignore_certificate = "{{.IgnoreCertificate}}" labels = {{.Labels}} + grouped_labels = {{.GroupedLabels}} }` var testIosResource = testIosResourceType{ @@ -43,14 +48,15 @@ var testIosResource = testIosResourceType{ Password: acctest.Env.IosResourcePassword(), ConnectorName: acctest.Env.IosResourceConnectorName(), IgnoreCertificate: acctest.Env.IosResourceIgnoreCertificate(), - Labels: acctest.Env.IosResourceTags().GetLabelsJsonArrayString(), + Labels: testutil.MustJson(labels), + GroupedLabels: acctest.MustGenerateLabelsTF(groupedLabels), Host: acctest.Env.IosResourceHost(), Port: acctest.Env.IosResourcePort(), } var testIosResourceConfig = acctest.MustParseTemplate(testIosResourceTemplate, testIosResource) -var reorderedLabels = tags.New(sliceutil.Reverse[string](tags.MustParseJsonArrayString(testIosResource.Labels))...).GetLabelsJsonArrayString() +var reorderedLabels = testutil.MustJson(sliceutil.Reverse(labels)) var testIosResource_ReorderedLabels = acctest.MustOverrideFields(testIosResource, map[string]any{ "Labels": reorderedLabels, @@ -62,6 +68,14 @@ var testIosResource_NewName = acctest.MustOverrideFields(testIosResource, map[st }) var testIosResourceConfig_NewName = acctest.MustParseTemplate(testIosResourceTemplate, testIosResource_NewName) +var renamedGroupedLabels = map[string][]string{ + "my-cool-new-label-group": groupedLabels["acceptancetest"], +} +var testIosResource_ReplaceGroupTags = acctest.MustOverrideFields(testIosResource, map[string]any{ + "GroupedLabels": acctest.MustGenerateLabelsTF(renamedGroupedLabels), +}) +var testIosResourceConfig_ReplaceGroupTags = acctest.MustParseTemplate(testIosResourceTemplate, testIosResource_ReplaceGroupTags) + func TestAccIosDeviceResource_SDC(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: acctest.PreCheckFunc(t), @@ -77,10 +91,15 @@ func TestAccIosDeviceResource_SDC(t *testing.T) { resource.TestCheckResourceAttr("cdo_ios_device.test", "port", strconv.FormatInt(testIosResource.Port, 10)), resource.TestCheckResourceAttr("cdo_ios_device.test", "username", testIosResource.Username), resource.TestCheckResourceAttr("cdo_ios_device.test", "password", testIosResource.Password), - resource.TestCheckResourceAttr("cdo_ios_device.test", "labels.#", strconv.Itoa(len(acctest.Env.FtdResourceTags().Labels))), - resource.TestCheckResourceAttrWith("cdo_ios_device.test", "labels.0", testutil.CheckEqual(acctest.Env.IosResourceTags().Labels[0])), - resource.TestCheckResourceAttrWith("cdo_ios_device.test", "labels.1", testutil.CheckEqual(acctest.Env.IosResourceTags().Labels[1])), - resource.TestCheckResourceAttrWith("cdo_ios_device.test", "labels.2", testutil.CheckEqual(acctest.Env.IosResourceTags().Labels[2])), + resource.TestCheckResourceAttr("cdo_ios_device.test", "labels.#", strconv.Itoa(len(labels))), + resource.TestCheckTypeSetElemAttr("cdo_ios_device.test", "labels.*", labels[0]), + resource.TestCheckTypeSetElemAttr("cdo_ios_device.test", "labels.*", labels[1]), + resource.TestCheckTypeSetElemAttr("cdo_ios_device.test", "labels.*", labels[2]), + resource.TestCheckResourceAttr("cdo_ios_device.test", "grouped_labels.%", "1"), + resource.TestCheckResourceAttr("cdo_ios_device.test", "grouped_labels.acceptancetest.#", strconv.Itoa(len(groupedLabels["acceptancetest"]))), + resource.TestCheckTypeSetElemAttr("cdo_ios_device.test", "grouped_labels.acceptancetest.*", groupedLabels["acceptancetest"][0]), + resource.TestCheckTypeSetElemAttr("cdo_ios_device.test", "grouped_labels.acceptancetest.*", groupedLabels["acceptancetest"][1]), + resource.TestCheckTypeSetElemAttr("cdo_ios_device.test", "grouped_labels.acceptancetest.*", groupedLabels["acceptancetest"][2]), ), }, // Update order of label testing @@ -95,6 +114,18 @@ func TestAccIosDeviceResource_SDC(t *testing.T) { resource.TestCheckResourceAttr("cdo_ios_device.test", "name", testIosResource_NewName.Name), ), }, + + // Replace group labels test + { + Config: acctest.ProviderConfig() + testIosResourceConfig_ReplaceGroupTags, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("cdo_ios_device.test", "grouped_labels.%", "1"), + resource.TestCheckResourceAttr("cdo_ios_device.test", "grouped_labels.my-cool-new-label-group.#", strconv.Itoa(len(renamedGroupedLabels["my-cool-new-label-group"]))), + resource.TestCheckTypeSetElemAttr("cdo_ios_device.test", "grouped_labels.my-cool-new-label-group.*", renamedGroupedLabels["my-cool-new-label-group"][0]), + resource.TestCheckTypeSetElemAttr("cdo_ios_device.test", "grouped_labels.my-cool-new-label-group.*", renamedGroupedLabels["my-cool-new-label-group"][1]), + resource.TestCheckTypeSetElemAttr("cdo_ios_device.test", "grouped_labels.my-cool-new-label-group.*", renamedGroupedLabels["my-cool-new-label-group"][2]), + ), + }, // Delete testing automatically occurs in TestCase }, }) diff --git a/provider/internal/service/duoadminpanel/operation.go b/provider/internal/service/duoadminpanel/operation.go index 766c4f09..992ff9a3 100644 --- a/provider/internal/service/duoadminpanel/operation.go +++ b/provider/internal/service/duoadminpanel/operation.go @@ -2,8 +2,12 @@ package duoadminpanel import ( "context" + "errors" "fmt" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/device/duoadminpanel" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/publicapilabels" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" "github.com/CiscoDevnet/terraform-provider-cdo/internal/util" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -16,7 +20,8 @@ func Read(ctx context.Context, resource *Resource, stateData *ResourceModel) err if err != nil { return err } - stateData.Labels = util.GoStringSliceToTFStringSet(output.Tags.Labels) + stateData.Labels = util.GoStringSliceToTFStringSet(output.Tags.UngroupedTags()) + stateData.GroupedLabels = util.GoMapToStringSetTFMap(output.Tags.GroupedTags()) stateData.Name = types.StringValue(output.Name) return nil @@ -24,10 +29,9 @@ func Read(ctx context.Context, resource *Resource, stateData *ResourceModel) err func Create(ctx context.Context, resource *Resource, planData *ResourceModel) error { - // convert tf tags to go tags - planTags, err := util.TFStringSetToTagLabels(ctx, planData.Labels) + labels, err := publicapiLabelsFromResourceModel(ctx, planData) if err != nil { - return fmt.Errorf("error while converting terraform tags to go tags, %s", planData.Labels) + return err } // do create @@ -36,7 +40,7 @@ func Create(ctx context.Context, resource *Resource, planData *ResourceModel) er Host: planData.Host.ValueString(), IntegrationKey: planData.IntegrationKey.ValueString(), SecretKey: planData.SecretKey.ValueString(), - Labels: planTags.Labels, + Labels: labels, }) if err != nil { return err @@ -44,7 +48,8 @@ func Create(ctx context.Context, resource *Resource, planData *ResourceModel) er // map response to terraform types, it should be unchanged for most parts planData.Id = types.StringValue(output.Uid) - planData.Labels = util.GoStringSliceToTFStringSet(output.Tags.Labels) + planData.Labels = util.GoStringSliceToTFStringSet(output.Tags.UngroupedTags()) + planData.GroupedLabels = util.GoMapToStringSetTFMap(output.Tags.GroupedTags()) return nil } @@ -52,9 +57,9 @@ func Create(ctx context.Context, resource *Resource, planData *ResourceModel) er func Update(ctx context.Context, resource *Resource, planData *ResourceModel, stateData *ResourceModel) error { // do update - planTags, err := util.TFStringSetToTagLabels(ctx, planData.Labels) + planTags, err := tagsFromResourceModel(ctx, stateData) if err != nil { - return fmt.Errorf("error while converting terraform tags to go tags, %s", planData.Labels) + return err } output, err := resource.client.UpdateDuoAdminPanel(ctx, duoadminpanel.UpdateInput{ @@ -65,7 +70,8 @@ func Update(ctx context.Context, resource *Resource, planData *ResourceModel, st if err != nil { return err } - stateData.Labels = util.GoStringSliceToTFStringSet(output.Tags.Labels) + stateData.Labels = util.GoStringSliceToTFStringSet(output.Tags.UngroupedTags()) + stateData.GroupedLabels = util.GoMapToStringSetTFMap(output.Tags.GroupedTags()) stateData.Name = types.StringValue(output.Name) return nil @@ -83,3 +89,39 @@ func Delete(ctx context.Context, resource *Resource, stateData *ResourceModel) e return nil } + +func publicapiLabelsFromResourceModel(ctx context.Context, resourceModel *ResourceModel) (publicapilabels.Type, error) { + ungroupedLabels, groupedLabels, err := extractLabelsFromResourceModel(ctx, resourceModel) + if err != nil { + return publicapilabels.Empty(), err + } + + return publicapilabels.New(ungroupedLabels, groupedLabels), nil +} + +func tagsFromResourceModel(ctx context.Context, resourceModel *ResourceModel) (tags.Type, error) { + ungroupedLabels, groupedLabels, err := extractLabelsFromResourceModel(ctx, resourceModel) + if err != nil { + return nil, err + } + + return tags.New(ungroupedLabels, groupedLabels), nil +} + +func extractLabelsFromResourceModel(ctx context.Context, resourceModel *ResourceModel) ([]string, map[string][]string, error) { + if resourceModel == nil { + return nil, nil, errors.New("resource model cannot be nil") + } + + ungroupedLabels, err := util.TFStringSetToGoStringList(ctx, resourceModel.Labels) + if err != nil { + return nil, nil, fmt.Errorf("error while converting terraform labels to go slice, %s", resourceModel.Labels) + } + + groupedLabels, err := util.TFMapToGoMapOfStringSlices(ctx, resourceModel.GroupedLabels) + if err != nil { + return nil, nil, fmt.Errorf("error while converting terraform grouped labels to go map, %v", resourceModel.GroupedLabels) + } + + return ungroupedLabels, groupedLabels, nil +} diff --git a/provider/internal/service/duoadminpanel/resource.go b/provider/internal/service/duoadminpanel/resource.go index cfea4421..e700e9c3 100644 --- a/provider/internal/service/duoadminpanel/resource.go +++ b/provider/internal/service/duoadminpanel/resource.go @@ -3,8 +3,10 @@ package duoadminpanel import ( "context" "fmt" + "github.com/CiscoDevnet/terraform-provider-cdo/internal/util" "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault" "github.com/hashicorp/terraform-plugin-framework/types" @@ -33,6 +35,7 @@ type ResourceModel struct { SecretKey types.String `tfsdk:"secret_key"` Host types.String `tfsdk:"host"` Labels types.Set `tfsdk:"labels"` + GroupedLabels types.Map `tfsdk:"grouped_labels"` } func (r *Resource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { @@ -77,6 +80,15 @@ func (r *Resource) Schema(ctx context.Context, req resource.SchemaRequest, resp ElementType: types.StringType, Default: setdefault.StaticValue(types.SetValueMust(types.StringType, []attr.Value{})), // default to empty list }, + "grouped_labels": schema.MapAttribute{ + MarkdownDescription: "Specify a set of grouped labels to identify the Duo Admin Panel 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, + Computed: true, + ElementType: types.SetType{ + ElemType: types.StringType, + }, + Default: mapdefault.StaticValue(types.MapValueMust(types.SetType{ElemType: types.StringType}, map[string]attr.Value{})), // default to empty map + }, }, } } diff --git a/provider/internal/service/duoadminpanel/resource_test.go b/provider/internal/service/duoadminpanel/resource_test.go index 3c45e20b..c17440d9 100644 --- a/provider/internal/service/duoadminpanel/resource_test.go +++ b/provider/internal/service/duoadminpanel/resource_test.go @@ -1,26 +1,33 @@ package duoadminpanel_test import ( - "github.com/CiscoDevnet/terraform-provider-cdo/internal/util/testutil" "strconv" "testing" + "github.com/CiscoDevnet/terraform-provider-cdo/internal/util/sliceutil" + "github.com/CiscoDevnet/terraform-provider-cdo/internal/util/testutil" + "github.com/CiscoDevnet/terraform-provider-cdo/internal/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) +var labels = []string{"testdevice", "acceptancetest", "terraformprovider"} +var groupedLabels = map[string][]string{"acceptancetest": sliceutil.Map(labels, func(input string) string { return "grouped-" + input })} + var resourceModel = struct { Name string Host string IntegrationKey string SecretKey string Labels string + GroupedLabels string }{ Name: acctest.Env.DuoAdminPanelResourceName(), Host: acctest.Env.DuoAdminPanelResourceHost(), IntegrationKey: acctest.Env.DuoAdminPanelResourceIntegrationKey(), SecretKey: acctest.Env.DuoAdminPanelResourceSecretKey(), - Labels: acctest.Env.DuoAdminPanelResourceTags().GetLabelsJsonArrayString(), + Labels: testutil.MustJson(labels), + GroupedLabels: acctest.MustGenerateLabelsTF(groupedLabels), } const resourceTemplate = ` @@ -30,6 +37,7 @@ resource "cdo_duo_admin_panel" "test" { integration_key = "{{.IntegrationKey}}" secret_key = "{{.SecretKey}}" labels = {{.Labels}} + grouped_labels = {{.GroupedLabels}} }` var resourceConfig = acctest.MustParseTemplate(resourceTemplate, resourceModel) @@ -52,10 +60,15 @@ func TestAccDuoAdminPanelResource(t *testing.T) { resource.TestCheckResourceAttr("cdo_duo_admin_panel.test", "host", resourceModel.Host), resource.TestCheckResourceAttr("cdo_duo_admin_panel.test", "integration_key", resourceModel.IntegrationKey), resource.TestCheckResourceAttr("cdo_duo_admin_panel.test", "secret_key", resourceModel.SecretKey), - resource.TestCheckResourceAttr("cdo_duo_admin_panel.test", "labels.#", strconv.Itoa(len(acctest.Env.DuoAdminPanelResourceTags().Labels))), - resource.TestCheckResourceAttrWith("cdo_duo_admin_panel.test", "labels.0", testutil.CheckEqual(acctest.Env.DuoAdminPanelResourceTags().Labels[0])), - resource.TestCheckResourceAttrWith("cdo_duo_admin_panel.test", "labels.1", testutil.CheckEqual(acctest.Env.DuoAdminPanelResourceTags().Labels[1])), - resource.TestCheckResourceAttrWith("cdo_duo_admin_panel.test", "labels.2", testutil.CheckEqual(acctest.Env.DuoAdminPanelResourceTags().Labels[2])), + resource.TestCheckResourceAttr("cdo_duo_admin_panel.test", "labels.#", strconv.Itoa(len(labels))), + resource.TestCheckTypeSetElemAttr("cdo_duo_admin_panel.test", "labels.*", labels[0]), + resource.TestCheckTypeSetElemAttr("cdo_duo_admin_panel.test", "labels.*", labels[1]), + resource.TestCheckTypeSetElemAttr("cdo_duo_admin_panel.test", "labels.*", labels[2]), + resource.TestCheckResourceAttr("cdo_duo_admin_panel.test", "grouped_labels.%", "1"), + resource.TestCheckResourceAttr("cdo_duo_admin_panel.test", "grouped_labels.acceptancetest.#", strconv.Itoa(len(groupedLabels["acceptancetest"]))), + resource.TestCheckTypeSetElemAttr("cdo_duo_admin_panel.test", "grouped_labels.acceptancetest.*", groupedLabels["acceptancetest"][0]), + resource.TestCheckTypeSetElemAttr("cdo_duo_admin_panel.test", "grouped_labels.acceptancetest.*", groupedLabels["acceptancetest"][1]), + resource.TestCheckTypeSetElemAttr("cdo_duo_admin_panel.test", "grouped_labels.acceptancetest.*", groupedLabels["acceptancetest"][2]), ), }, // Update and Read testing diff --git a/provider/internal/util/testutil/testutil.go b/provider/internal/util/testutil/testutil.go index eff9c319..37d868aa 100644 --- a/provider/internal/util/testutil/testutil.go +++ b/provider/internal/util/testutil/testutil.go @@ -1,6 +1,9 @@ package testutil -import "fmt" +import ( + "encoding/json" + "fmt" +) func CheckEqual(expected string) func(value string) error { return func(value string) error { @@ -10,3 +13,12 @@ func CheckEqual(expected string) func(value string) error { return nil } } + +func MustJson(input any) string { + output, err := json.Marshal(input) + if err != nil { + panic("unable to marshall json for") + } + + return string(output) +} diff --git a/provider/internal/util/togo.go b/provider/internal/util/togo.go index 75833ba7..b983be5d 100644 --- a/provider/internal/util/togo.go +++ b/provider/internal/util/togo.go @@ -2,12 +2,16 @@ package util import ( "context" + "errors" "fmt" + "strings" + "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/device/tags" "github.com/CiscoDevnet/terraform-provider-cdo/go-client/model/ftd/license" "github.com/CiscoDevnet/terraform-provider-cdo/internal/util/sliceutil" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" - "strings" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) func TFStringListToGoStringList(l []types.String) []string { @@ -26,12 +30,18 @@ func TFStringSetToGoStringList(ctx context.Context, s types.Set) ([]string, erro return stringList, nil } -func TFStringSetToTagLabels(ctx context.Context, s types.Set) (tags.Type, error) { - stringList, err := TFStringSetToGoStringList(ctx, s) +func ToLabels(ctx context.Context, labels types.Set, groupedLabels types.Map) (tags.Type, error) { + convertedLabels, err := TFStringSetToGoStringList(ctx, labels) + if err != nil { + return nil, err + } + + convertedGroupLabels, err := TFMapToGoMapOfStringSlices(ctx, groupedLabels) if err != nil { - return tags.Type{}, err + return nil, err } - return tags.New(stringList...), nil + + return tags.New(convertedLabels, convertedGroupLabels), nil } func TFStringSetToLicenses(ctx context.Context, s types.Set) ([]license.Type, error) { @@ -45,3 +55,29 @@ func TFStringSetToLicenses(ctx context.Context, s types.Set) ([]license.Type, er } return licenses, nil } + +func TFMapToGoMap[GoType any](m types.Map, elemConverter func(attr.Value) (GoType, error)) (map[string]GoType, error) { + resultMap := map[string]GoType{} + + for k, v := range m.Elements() { + elem, err := elemConverter(v) + if err != nil { + return nil, err + } + + resultMap[k] = elem + } + + return resultMap, nil +} + +func TFMapToGoMapOfStringSlices(ctx context.Context, m types.Map) (map[string][]string, error) { + return TFMapToGoMap[[]string](m, func(v attr.Value) ([]string, error) { + n, ok := v.(basetypes.SetValue) + if !ok { + return nil, errors.New("unexpected element type in tf map value") + } + + return TFStringSetToGoStringList(ctx, n) + }) +} diff --git a/provider/internal/util/totf.go b/provider/internal/util/totf.go index 039612f1..35f8a816 100644 --- a/provider/internal/util/totf.go +++ b/provider/internal/util/totf.go @@ -4,6 +4,7 @@ import ( "github.com/CiscoDevnet/terraform-provider-cdo/internal/util/sliceutil" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) func GoStringSliceToTFStringList(stringSlice []string) []types.String { @@ -18,3 +19,26 @@ func GoStringSliceToTFStringSet(stringSlice []string) types.Set { }) return types.SetValueMust(types.StringType, elements) } + +func GoMapToTFMap[GoType any](m map[string]GoType, elementType attr.Type, converter func(GoType) attr.Value) types.Map { + tfMap := map[string]attr.Value{} + + for k, v := range m { + tfMap[k] = converter(v) + } + + return types.MapValueMust(elementType, tfMap) +} + +func GoMapToStringSetTFMap(m map[string][]string) types.Map { + return GoMapToTFMap(m, basetypes.SetType{ElemType: types.StringType}, func(slice []string) attr.Value { + list := GoStringSliceToTFStringList(slice) + + listOfValues := make([]attr.Value, len(list)) + for i, v := range list { + listOfValues[i] = v + } + + return types.SetValueMust(types.StringType, listOfValues) + }) +}