diff --git a/.github/workflows/website-lint.yml b/.github/workflows/website-lint.yml index 291335ef3..1b7f17822 100644 --- a/.github/workflows/website-lint.yml +++ b/.github/workflows/website-lint.yml @@ -5,11 +5,11 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: - go-version: 1.19 + go-version-file: 'go.mod' - name: Install tools run: make tools - name: Website Lint diff --git a/.golangci.yml b/.golangci.yml index 9c0135ba7..34ef5e157 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -31,3 +31,11 @@ linters: linters-settings: errcheck: ignore: github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema:ForceNew|Set,fmt:.*,io:Close + revive: + rules: + - name: unreachable-code + - name: errorf + - name: range + - name: superfluous-else + - name: var-declaration + - name: duplicated-imports diff --git a/api/infra/segments/segment_configuration_state.go b/api/infra/segments/segment_configuration_state.go index 9cabe3bf2..dcfdbf55e 100644 --- a/api/infra/segments/segment_configuration_state.go +++ b/api/infra/segments/segment_configuration_state.go @@ -45,7 +45,7 @@ func (c SegmentConfigurationStateClientContext) Get(segmentsIdParam string, curs case utl.Local: client := c.Client.(client0.StateClient) - obj, err = client.Get(segmentsIdParam, cursorParam, edgePathParam, enforcementPointPathParam, includeMarkForDeleteObjectsParam, includedFieldsParam, pageSizeParam, sortAscendingParam, sortByParam, sourceParam, statsTypeParam, transportNodeIdParam) + obj, err = client.Get(segmentsIdParam, nil, cursorParam, edgePathParam, enforcementPointPathParam, includeMarkForDeleteObjectsParam, includedFieldsParam, pageSizeParam, sortAscendingParam, sortByParam, sourceParam, statsTypeParam, transportNodeIdParam) if err != nil { return obj, err } @@ -62,7 +62,7 @@ func (c SegmentConfigurationStateClientContext) Get(segmentsIdParam string, curs case utl.Multitenancy: client := c.Client.(client2.StateClient) - obj, err = client.Get(utl.DefaultOrgID, c.ProjectID, segmentsIdParam, cursorParam, edgePathParam, enforcementPointPathParam, includeMarkForDeleteObjectsParam, includedFieldsParam, pageSizeParam, sortAscendingParam, sortByParam, sourceParam, statsTypeParam, transportNodeIdParam) + obj, err = client.Get(utl.DefaultOrgID, c.ProjectID, segmentsIdParam, nil, cursorParam, edgePathParam, enforcementPointPathParam, includeMarkForDeleteObjectsParam, includedFieldsParam, pageSizeParam, sortAscendingParam, sortByParam, sourceParam, statsTypeParam, transportNodeIdParam) if err != nil { return obj, err } diff --git a/go.mod b/go.mod index 8771cc1a6..9cc598afa 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,14 @@ module github.com/vmware/terraform-provider-nsxt go 1.22 +replace ( + github.com/vmware/vsphere-automation-sdk-go/lib => github.com/vmware/vsphere-automation-sdk-go/lib v0.7.1-0.20241113023437-5938c535c194 + github.com/vmware/vsphere-automation-sdk-go/runtime => github.com/vmware/vsphere-automation-sdk-go/runtime v0.7.1-0.20241113023437-5938c535c194 + github.com/vmware/vsphere-automation-sdk-go/services/nsxt => github.com/vmware/vsphere-automation-sdk-go/services/nsxt v0.12.1-0.20241113023437-5938c535c194 + github.com/vmware/vsphere-automation-sdk-go/services/nsxt-gm => github.com/vmware/vsphere-automation-sdk-go/services/nsxt-gm v0.9.1-0.20241113023437-5938c535c194 + github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp => github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp v0.6.1-0.20241113023437-5938c535c194 +) + require ( github.com/google/uuid v1.6.0 github.com/hashicorp/go-version v1.7.0 @@ -9,7 +17,7 @@ require ( github.com/stretchr/testify v1.10.0 github.com/vmware/go-vmware-nsxt v0.0.0-20220328155605-f49a14c1ef5f github.com/vmware/vsphere-automation-sdk-go/lib v0.7.0 - github.com/vmware/vsphere-automation-sdk-go/runtime v0.7.0 + github.com/vmware/vsphere-automation-sdk-go/runtime v0.7.1-0.20240611083326-25a4e1834c4d github.com/vmware/vsphere-automation-sdk-go/services/nsxt v0.12.0 github.com/vmware/vsphere-automation-sdk-go/services/nsxt-gm v0.9.0 github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp v0.6.0 @@ -28,7 +36,7 @@ require ( github.com/gibson042/canonicaljson-go v1.0.3 // indirect github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -38,7 +46,7 @@ require ( github.com/hashicorp/go-plugin v1.5.1 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/hc-install v0.6.0 // indirect - github.com/hashicorp/hcl/v2 v2.18.0 // indirect + github.com/hashicorp/hcl/v2 v2.20.1 // indirect github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.19.0 // indirect github.com/hashicorp/terraform-json v0.17.1 // indirect @@ -51,7 +59,7 @@ require ( github.com/mattn/go-isatty v0.0.14 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect - github.com/mitchellh/go-wordwrap v1.0.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/oklog/run v1.0.0 // indirect @@ -59,13 +67,14 @@ require ( github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/zclconf/go-cty v1.14.0 // indirect + github.com/zclconf/go-cty v1.14.3 // indirect golang.org/x/crypto v0.21.0 // indirect - golang.org/x/mod v0.12.0 // indirect + golang.org/x/mod v0.16.0 // indirect golang.org/x/net v0.23.0 // indirect golang.org/x/oauth2 v0.7.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.19.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect google.golang.org/grpc v1.57.1 // indirect diff --git a/go.sum b/go.sum index f8a5c98d5..8c33cee12 100644 --- a/go.sum +++ b/go.sum @@ -51,8 +51,8 @@ github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= @@ -77,8 +77,8 @@ github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKe github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/hc-install v0.6.0 h1:fDHnU7JNFNSQebVKYhHZ0va1bC6SrPQ8fpebsvNr2w4= github.com/hashicorp/hc-install v0.6.0/go.mod h1:10I912u3nntx9Umo1VAeYPUUuehk0aRQJYpMwbX5wQA= -github.com/hashicorp/hcl/v2 v2.18.0 h1:wYnG7Lt31t2zYkcquwgKo6MWXzRUDIeIVU5naZwHLl8= -github.com/hashicorp/hcl/v2 v2.18.0/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE= +github.com/hashicorp/hcl/v2 v2.20.1 h1:M6hgdyz7HYt1UN9e61j+qKJBqR3orTWbI1HKBJEdxtc= +github.com/hashicorp/hcl/v2 v2.20.1/go.mod h1:TZDqQ4kNKCbh1iJp99FdPiUaVDDUPivbqxZulxDYqL4= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/terraform-exec v0.19.0 h1:FpqZ6n50Tk95mItTSS9BjeOVUb4eg81SpgVtZNNtFSM= @@ -108,8 +108,6 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= @@ -120,8 +118,8 @@ github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa1 github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= -github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= @@ -150,21 +148,23 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/vmware/go-vmware-nsxt v0.0.0-20220328155605-f49a14c1ef5f h1:NbC9yOr5At92seXK+kOr2TzU3mIWzcJOVzZasGSuwoU= github.com/vmware/go-vmware-nsxt v0.0.0-20220328155605-f49a14c1ef5f/go.mod h1:VEqcmf4Sp7gPB7z05QGyKVmn6xWppr7Nz8cVNvyC80o= -github.com/vmware/vsphere-automation-sdk-go/lib v0.7.0 h1:pT+oqJ8FD5eUBQkl+e7LZwwtbwPvW5kDyyGXvt66gOM= -github.com/vmware/vsphere-automation-sdk-go/lib v0.7.0/go.mod h1:f3+6YVZpNcK2pYyiQ94BoHWmjMj9BnYav0vNFuTiDVM= -github.com/vmware/vsphere-automation-sdk-go/runtime v0.7.0 h1:pSBxa9Agh6bgW8Hr0A1eQxuwnxGTnuAVox8iQb023hg= -github.com/vmware/vsphere-automation-sdk-go/runtime v0.7.0/go.mod h1:qdzEFm2iK3dvlmm99EYYNxs70HbzuiHyENFD24Ps8fQ= -github.com/vmware/vsphere-automation-sdk-go/services/nsxt v0.12.0 h1:+kcDO69bfIB87KZUAYQ4AqrXlnZhpZz+QwzIB+TseqU= -github.com/vmware/vsphere-automation-sdk-go/services/nsxt v0.12.0/go.mod h1:upLH9b9zpG86P0wwO4+gREf0lBXr8gYcs7P1FRZ9n30= -github.com/vmware/vsphere-automation-sdk-go/services/nsxt-gm v0.9.0 h1:vaWjTOvt9vPKZjA8ojTRC1o92Rm9ScR/GwWEd6JLMOk= -github.com/vmware/vsphere-automation-sdk-go/services/nsxt-gm v0.9.0/go.mod h1:gcEvyczWPFMZX2gkBiBVpOwvUGSNXSpxU19Sx9aiouY= -github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp v0.6.0 h1:+jS0YH9dEp8rC00SsaY5feFpVgp4Lu0YBnBe3T7zfqo= -github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp v0.6.0/go.mod h1:ugk9I4YM62SSAox57l5NAVBCRIkPQ1RNLb3URxyTADc= +github.com/vmware/vsphere-automation-sdk-go/lib v0.7.1-0.20241113023437-5938c535c194 h1:4+nN9YcmXs8uXoKhsQpg0K8ziNTg8zULbdvc3Mu/Dk0= +github.com/vmware/vsphere-automation-sdk-go/lib v0.7.1-0.20241113023437-5938c535c194/go.mod h1:f3+6YVZpNcK2pYyiQ94BoHWmjMj9BnYav0vNFuTiDVM= +github.com/vmware/vsphere-automation-sdk-go/runtime v0.7.1-0.20241113023437-5938c535c194 h1:4NmoWzW501ZhriPGzGULj6mJmr2Dx6QyUO9CmRbuMg0= +github.com/vmware/vsphere-automation-sdk-go/runtime v0.7.1-0.20241113023437-5938c535c194/go.mod h1:qdzEFm2iK3dvlmm99EYYNxs70HbzuiHyENFD24Ps8fQ= +github.com/vmware/vsphere-automation-sdk-go/services/nsxt v0.12.1-0.20241113023437-5938c535c194 h1:M4/j0HbfW+pFbcpZdbg7wid3WxkIFOZRXuWNi+llXEc= +github.com/vmware/vsphere-automation-sdk-go/services/nsxt v0.12.1-0.20241113023437-5938c535c194/go.mod h1:M+J1qwzF4o7sAb/2VRu/edl1HLCdC++C4SNUrgiuGlQ= +github.com/vmware/vsphere-automation-sdk-go/services/nsxt-gm v0.9.1-0.20241113023437-5938c535c194 h1:/+cOYJUazdOQ8sMePBcCoCnnLrQDvIvxjYYDKcdwe4Y= +github.com/vmware/vsphere-automation-sdk-go/services/nsxt-gm v0.9.1-0.20241113023437-5938c535c194/go.mod h1:gcEvyczWPFMZX2gkBiBVpOwvUGSNXSpxU19Sx9aiouY= +github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp v0.6.1-0.20241113023437-5938c535c194 h1:Q2jxex1UrZOMwJVJHSEyZcuWzQjsEeDpzDfc3zrtI1A= +github.com/vmware/vsphere-automation-sdk-go/services/nsxt-mp v0.6.1-0.20241113023437-5938c535c194/go.mod h1:ugk9I4YM62SSAox57l5NAVBCRIkPQ1RNLb3URxyTADc= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zclconf/go-cty v1.14.0 h1:/Xrd39K7DXbHzlisFP9c4pHao4yyf+/Ug9LEz+Y/yhc= -github.com/zclconf/go-cty v1.14.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty v1.14.3 h1:1JXy1XroaGrzZuG6X9dt7HL6s9AwbY+l4UNL8o5B6ho= +github.com/zclconf/go-cty v1.14.3/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI= +github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= @@ -175,8 +175,8 @@ golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoH golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -197,6 +197,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -230,8 +232,9 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= +golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= diff --git a/nsxt/data_source_nsxt_policy_distributed_vlan_connection.go b/nsxt/data_source_nsxt_policy_distributed_vlan_connection.go new file mode 100644 index 000000000..6bc59de67 --- /dev/null +++ b/nsxt/data_source_nsxt_policy_distributed_vlan_connection.go @@ -0,0 +1,32 @@ +/* Copyright © 2022 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceNsxtPolicyDistributedVlanConnection() *schema.Resource { + return &schema.Resource{ + Read: dataSourceNsxtPolicyDistributedVlanConnectionRead, + + Schema: map[string]*schema.Schema{ + "id": getDataSourceIDSchema(), + "path": getPathSchema(), + "display_name": getDisplayNameSchema(), + "description": getDescriptionSchema(), + }, + } +} + +func dataSourceNsxtPolicyDistributedVlanConnectionRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + _, err := policyDataSourceResourceReadWithValidation(d, connector, getSessionContext(d, m), "DistributedVlanConnection", nil, false) + if err != nil { + return err + } + + return nil +} diff --git a/nsxt/data_source_nsxt_policy_gateway_connection.go b/nsxt/data_source_nsxt_policy_gateway_connection.go new file mode 100644 index 000000000..cfcf6c3e1 --- /dev/null +++ b/nsxt/data_source_nsxt_policy_gateway_connection.go @@ -0,0 +1,38 @@ +/* Copyright © 2022 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceNsxtPolicyGatewayConnection() *schema.Resource { + return &schema.Resource{ + Read: dataSourceNsxtPolicyGatewayConnectionRead, + + Schema: map[string]*schema.Schema{ + "id": getDataSourceIDSchema(), + "tier0_path": getPolicyPathSchema(false, false, "Tier0 Gateway path"), + "path": getPathSchema(), + "display_name": getDisplayNameSchema(), + "description": getDescriptionSchema(), + }, + } +} + +func dataSourceNsxtPolicyGatewayConnectionRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + gwPath := d.Get("tier0_path").(string) + query := make(map[string]string) + if len(gwPath) > 0 { + query["tier0_path"] = gwPath + } + _, err := policyDataSourceResourceReadWithValidation(d, connector, getSessionContext(d, m), "GatewayConnection", query, false) + if err != nil { + return err + } + + return nil +} diff --git a/nsxt/data_source_nsxt_policy_ip_block_test.go b/nsxt/data_source_nsxt_policy_ip_block_test.go index f0d4d83ba..058a36ca2 100644 --- a/nsxt/data_source_nsxt_policy_ip_block_test.go +++ b/nsxt/data_source_nsxt_policy_ip_block_test.go @@ -12,6 +12,7 @@ import ( "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" "github.com/vmware/terraform-provider-nsxt/api/infra" + tf_api "github.com/vmware/terraform-provider-nsxt/api/utl" ) func TestAccDataSourceNsxtPolicyIpBlock_basic(t *testing.T) { @@ -37,12 +38,12 @@ func testAccDataSourceNsxtPolicyIPBlockBasic(t *testing.T, withContext bool, pre PreCheck: preCheck, Providers: testAccProviders, CheckDestroy: func(state *terraform.State) error { - return testAccDataSourceNsxtPolicyIPBlockDeleteByName(name) + return testAccDataSourceNsxtPolicyIPBlockDeleteByName(testAccGetSessionContext(), name) }, Steps: []resource.TestStep{ { PreConfig: func() { - if err := testAccDataSourceNsxtPolicyIPBlockCreate(name, newUUID(), "4001::/64", false); err != nil { + if err := testAccDataSourceNsxtPolicyIPBlockCreate(testAccGetSessionContext(), name, newUUID(), "4001::/64", false); err != nil { t.Error(err) } }, @@ -57,12 +58,12 @@ func testAccDataSourceNsxtPolicyIPBlockBasic(t *testing.T, withContext bool, pre }) } -func testAccDataSourceNsxtPolicyIPBlockCreate(name, id, cidr string, isPrivate bool) error { +func testAccDataSourceNsxtPolicyIPBlockCreate(context tf_api.SessionContext, name, id, cidr string, isPrivate bool) error { connector, err := testAccGetPolicyConnector() if err != nil { return fmt.Errorf("Error during test client initialization: %v", err) } - client := infra.NewIpBlocksClient(testAccGetSessionContext(), connector) + client := infra.NewIpBlocksClient(context, connector) if client == nil { return policyResourceNotSupportedError() } @@ -86,12 +87,12 @@ func testAccDataSourceNsxtPolicyIPBlockCreate(name, id, cidr string, isPrivate b return nil } -func testAccDataSourceNsxtPolicyIPBlockDeleteByName(name string) error { +func testAccDataSourceNsxtPolicyIPBlockDeleteByName(context tf_api.SessionContext, name string) error { connector, err := testAccGetPolicyConnector() if err != nil { return fmt.Errorf("Error during test client initialization: %v", err) } - client := infra.NewIpBlocksClient(testAccGetSessionContext(), connector) + client := infra.NewIpBlocksClient(context, connector) if client == nil { return policyResourceNotSupportedError() } diff --git a/nsxt/data_source_nsxt_policy_project.go b/nsxt/data_source_nsxt_policy_project.go index 2e2a08d36..70ef70d81 100644 --- a/nsxt/data_source_nsxt_policy_project.go +++ b/nsxt/data_source_nsxt_policy_project.go @@ -33,7 +33,14 @@ func dataSourceNsxtPolicyProject() *schema.Resource { Elem: &schema.Schema{ Type: schema.TypeString, }, - Optional: true, + Computed: true, + }, + "external_ipv4_blocks": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Computed: true, }, }, } @@ -126,6 +133,7 @@ func dataSourceNsxtPolicyProjectRead(d *schema.ResourceData, m interface{}) erro } d.Set("site_info", siteInfosList) d.Set("tier0_gateway_paths", obj.Tier0s) + d.Set("external_ipv4_blocks", obj.ExternalIpv4Blocks) return nil } diff --git a/nsxt/data_source_nsxt_policy_transit_gateway.go b/nsxt/data_source_nsxt_policy_transit_gateway.go new file mode 100644 index 000000000..6552c31ad --- /dev/null +++ b/nsxt/data_source_nsxt_policy_transit_gateway.go @@ -0,0 +1,30 @@ +/* Copyright © 2020 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceNsxtPolicyTransitGateway() *schema.Resource { + return &schema.Resource{ + Read: dataSourceNsxtPolicyTransitGatewayRead, + + Schema: map[string]*schema.Schema{ + "id": getDataSourceIDSchema(), + "display_name": getDataSourceDisplayNameSchema(), + "description": getDataSourceDescriptionSchema(), + "path": getPathSchema(), + "context": getContextSchema(true, false, false), + }, + } +} + +func dataSourceNsxtPolicyTransitGatewayRead(d *schema.ResourceData, m interface{}) error { + _, err := policyDataSourceResourceRead(d, getPolicyConnector(m), getSessionContext(d, m), "TransitGateway", nil) + if err != nil { + return err + } + return nil +} diff --git a/nsxt/data_source_nsxt_policy_transit_gateway_nat.go b/nsxt/data_source_nsxt_policy_transit_gateway_nat.go new file mode 100644 index 000000000..04e071592 --- /dev/null +++ b/nsxt/data_source_nsxt_policy_transit_gateway_nat.go @@ -0,0 +1,67 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + clientLayer "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/transit_gateways" +) + +var transitGatewayNatTypes = []string{ + model.PolicyNat_NAT_TYPE_USER, + model.PolicyNat_NAT_TYPE_DEFAULT, +} + +func dataSourceNsxtPolicyTransitGatewayNat() *schema.Resource { + return &schema.Resource{ + Read: dataSourceNsxtPolicyTransitGatewayNatRead, + + Schema: map[string]*schema.Schema{ + "id": getDataSourceIDSchema(), + "transit_gateway_path": getPolicyPathSchema(true, false, "Transit Gateway Path"), + "nat_type": { + Type: schema.TypeString, + Description: "Nat Type", + Optional: true, + Default: model.PolicyNat_NAT_TYPE_USER, + ValidateFunc: validation.StringInSlice(transitGatewayNatTypes, false), + }, + "display_name": getDataSourceExtendedDisplayNameSchema(), + "description": getDataSourceDescriptionSchema(), + "path": getPathSchema(), + }, + } +} + +func dataSourceNsxtPolicyTransitGatewayNatRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + parentPath := d.Get("transit_gateway_path").(string) + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 3) + if pathErr != nil { + return fmt.Errorf("invalid transit_gateway_path: %v", pathErr) + } + + natType := d.Get("nat_type").(string) + + client := clientLayer.NewNatClient(connector) + + // Nat type is the ID + obj, err := client.Get(parents[0], parents[1], parents[2], natType) + if err != nil { + return fmt.Errorf("NAT with type %s was not found for Transit Gateway %s", natType, parentPath) + } + + d.SetId(*obj.Id) + d.Set("display_name", obj.DisplayName) + d.Set("description", obj.Description) + d.Set("path", obj.Path) + + return nil +} diff --git a/nsxt/data_source_nsxt_project_ip_address_allocation.go b/nsxt/data_source_nsxt_project_ip_address_allocation.go new file mode 100644 index 000000000..0a9f865b5 --- /dev/null +++ b/nsxt/data_source_nsxt_project_ip_address_allocation.go @@ -0,0 +1,43 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceNsxtProjectIpAddressAllocation() *schema.Resource { + return &schema.Resource{ + Read: dataSourceNsxtProjectIpAddressAllocationRead, + + Schema: map[string]*schema.Schema{ + "id": getDataSourceIDSchema(), + "allocation_ips": { + Type: schema.TypeString, + Description: "Allocation IPs - single IP or cidr", + Required: true, + }, + "display_name": getDataSourceExtendedDisplayNameSchema(), + "description": getDataSourceDescriptionSchema(), + "path": getPathSchema(), + "context": getContextSchema(true, false, false), + }, + } +} + +func dataSourceNsxtProjectIpAddressAllocationRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + ips := d.Get("allocation_ips").(string) + query := make(map[string]string) + query["allocation_ips"] = ips + + _, err := policyDataSourceResourceReadWithValidation(d, connector, getSessionContext(d, m), "ProjectIpAddressAllocation", query, false) + if err != nil { + return err + } + // validate that IP address indeed matches + + return nil +} diff --git a/nsxt/data_source_nsxt_vpc_connectivity_profile.go b/nsxt/data_source_nsxt_vpc_connectivity_profile.go new file mode 100644 index 000000000..420d56476 --- /dev/null +++ b/nsxt/data_source_nsxt_vpc_connectivity_profile.go @@ -0,0 +1,30 @@ +/* Copyright © 2020 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceNsxtVpcConnectivityProfile() *schema.Resource { + return &schema.Resource{ + Read: dataSourceNsxtVpcConnectivityProfileRead, + + Schema: map[string]*schema.Schema{ + "id": getDataSourceIDSchema(), + "display_name": getDataSourceDisplayNameSchema(), + "description": getDataSourceDescriptionSchema(), + "path": getPathSchema(), + "context": getContextSchema(true, false, false), + }, + } +} + +func dataSourceNsxtVpcConnectivityProfileRead(d *schema.ResourceData, m interface{}) error { + _, err := policyDataSourceResourceRead(d, getPolicyConnector(m), getSessionContext(d, m), "VpcConnectivityProfile", nil) + if err != nil { + return err + } + return nil +} diff --git a/nsxt/data_source_nsxt_vpc_group.go b/nsxt/data_source_nsxt_vpc_group.go new file mode 100644 index 000000000..4519ffbba --- /dev/null +++ b/nsxt/data_source_nsxt_vpc_group.go @@ -0,0 +1,30 @@ +/* Copyright © 2020 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceNsxtVpcGroup() *schema.Resource { + return &schema.Resource{ + Read: dataSourceNsxtVpcGroupRead, + + Schema: map[string]*schema.Schema{ + "id": getDataSourceIDSchema(), + "display_name": getDataSourceDisplayNameSchema(), + "description": getDataSourceDescriptionSchema(), + "path": getPathSchema(), + "context": getContextSchema(true, false, true), + }, + } +} + +func dataSourceNsxtVpcGroupRead(d *schema.ResourceData, m interface{}) error { + _, err := policyDataSourceResourceRead(d, getPolicyConnector(m), getSessionContext(d, m), "Group", nil) + if err != nil { + return err + } + return nil +} diff --git a/nsxt/data_source_nsxt_vpc_ip_address_allocation.go b/nsxt/data_source_nsxt_vpc_ip_address_allocation.go new file mode 100644 index 000000000..2ae7efe82 --- /dev/null +++ b/nsxt/data_source_nsxt_vpc_ip_address_allocation.go @@ -0,0 +1,43 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceNsxtVpcIpAddressAllocation() *schema.Resource { + return &schema.Resource{ + Read: dataSourceNsxtVpcIpAddressAllocationRead, + + Schema: map[string]*schema.Schema{ + "id": getDataSourceIDSchema(), + "allocation_ips": { + Type: schema.TypeString, + Description: "Allocation IPs - single IP or cidr", + Required: true, + }, + "display_name": getDataSourceExtendedDisplayNameSchema(), + "description": getDataSourceDescriptionSchema(), + "path": getPathSchema(), + "context": getContextSchema(true, false, true), + }, + } +} + +func dataSourceNsxtVpcIpAddressAllocationRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + ips := d.Get("allocation_ips").(string) + query := make(map[string]string) + query["allocation_ips"] = ips + + _, err := policyDataSourceResourceReadWithValidation(d, connector, getSessionContext(d, m), "VpcIpAddressAllocation", query, false) + if err != nil { + return err + } + // validate that IP address indeed matches + + return nil +} diff --git a/nsxt/data_source_nsxt_vpc_nat.go b/nsxt/data_source_nsxt_vpc_nat.go new file mode 100644 index 000000000..d877480c1 --- /dev/null +++ b/nsxt/data_source_nsxt_vpc_nat.go @@ -0,0 +1,53 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" +) + +var vpcNatTypes = []string{ + model.PolicyNat_NAT_TYPE_INTERNAL, + model.PolicyNat_NAT_TYPE_USER, + model.PolicyNat_NAT_TYPE_DEFAULT, + model.PolicyNat_NAT_TYPE_NAT64, +} + +func dataSourceNsxtVpcNat() *schema.Resource { + return &schema.Resource{ + Read: dataSourceNsxtVpcNatRead, + + Schema: map[string]*schema.Schema{ + "id": getDataSourceIDSchema(), + "nat_type": { + Type: schema.TypeString, + Description: "Nat Type", + Required: true, + ValidateFunc: validation.StringInSlice(vpcNatTypes, false), + }, + "display_name": getDataSourceExtendedDisplayNameSchema(), + "description": getDataSourceDescriptionSchema(), + "path": getPathSchema(), + "context": getContextSchema(true, false, true), + }, + } +} + +func dataSourceNsxtVpcNatRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + natType := d.Get("nat_type").(string) + query := make(map[string]string) + query["nat_type"] = natType + + _, err := policyDataSourceResourceReadWithValidation(d, connector, getSessionContext(d, m), "PolicyNat", query, false) + if err != nil { + return err + } + + return nil +} diff --git a/nsxt/data_source_nsxt_vpc_service_profile.go b/nsxt/data_source_nsxt_vpc_service_profile.go new file mode 100644 index 000000000..8d281a63a --- /dev/null +++ b/nsxt/data_source_nsxt_vpc_service_profile.go @@ -0,0 +1,30 @@ +/* Copyright © 2020 VMware, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceNsxtVpcServiceProfile() *schema.Resource { + return &schema.Resource{ + Read: dataSourceNsxtVpcServiceProfileRead, + + Schema: map[string]*schema.Schema{ + "id": getDataSourceIDSchema(), + "display_name": getDataSourceDisplayNameSchema(), + "description": getDataSourceDescriptionSchema(), + "path": getPathSchema(), + "context": getContextSchema(true, false, false), + }, + } +} + +func dataSourceNsxtVpcServiceProfileRead(d *schema.ResourceData, m interface{}) error { + _, err := policyDataSourceResourceRead(d, getPolicyConnector(m), getSessionContext(d, m), "VpcServiceProfile", nil) + if err != nil { + return err + } + return nil +} diff --git a/nsxt/data_source_nsxt_vpc_subnet.go b/nsxt/data_source_nsxt_vpc_subnet.go new file mode 100644 index 000000000..786dba89b --- /dev/null +++ b/nsxt/data_source_nsxt_vpc_subnet.go @@ -0,0 +1,33 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceNsxtVpcSubnet() *schema.Resource { + return &schema.Resource{ + Read: dataSourceNsxtVpcSubnetRead, + + Schema: map[string]*schema.Schema{ + "id": getDataSourceIDSchema(), + "display_name": getDataSourceExtendedDisplayNameSchema(), + "description": getDataSourceDescriptionSchema(), + "path": getPathSchema(), + "context": getContextSchema(true, false, true), + }, + } +} + +func dataSourceNsxtVpcSubnetRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + _, err := policyDataSourceResourceReadWithValidation(d, connector, getSessionContext(d, m), "VpcSubnet", nil, false) + if err != nil { + return err + } + + return nil +} diff --git a/nsxt/data_source_nsxt_vpc_subnet_port.go b/nsxt/data_source_nsxt_vpc_subnet_port.go new file mode 100644 index 000000000..cba432ee2 --- /dev/null +++ b/nsxt/data_source_nsxt_vpc_subnet_port.go @@ -0,0 +1,109 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/vpcs/subnets" +) + +func dataSourceNsxtVpcSubnetPort() *schema.Resource { + return &schema.Resource{ + Read: dataSourceNsxtVpcSubnetPortRead, + + Schema: map[string]*schema.Schema{ + "id": getDataSourceIDSchema(), + "subnet_path": { + Type: schema.TypeString, + Description: "Path of parent subnet", + Required: true, + ValidateFunc: validatePolicyPath(), + }, + "vm_id": { + Type: schema.TypeString, + Description: "external ID of VM", + Required: true, + }, + "display_name": getDataSourceExtendedDisplayNameSchema(), + "description": getDataSourceDescriptionSchema(), + "path": getPathSchema(), + }, + } +} + +func listVpcSubnetPorts(connector client.Connector, subnetPath string) ([]model.VpcSubnetPort, error) { + + var results []model.VpcSubnetPort + parents, pathErr := parseStandardPolicyPathVerifySize(subnetPath, 4) + if pathErr != nil { + return results, pathErr + } + boolFalse := false + var cursor *string + total := 0 + var err error + var ports model.VpcSubnetPortListResult + + for { + portClient := subnets.NewPortsClient(connector) + if portClient == nil { + return results, policyResourceNotSupportedError() + } + ports, err = portClient.List(parents[0], parents[1], parents[2], parents[3], cursor, &boolFalse, nil, nil, &boolFalse, nil) + + if err != nil { + return results, err + } + results = append(results, ports.Results...) + if total == 0 && ports.ResultCount != nil { + // first response + total = int(*ports.ResultCount) + } + cursor = ports.Cursor + if len(results) >= total { + log.Printf("[DEBUG] Found %d ports for subnet %s", len(results), subnetPath) + return results, nil + } + } +} + +func dataSourceNsxtVpcSubnetPortRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + externalID := d.Get("vm_id").(string) + subnetPath := d.Get("subnet_path").(string) + vifAttachmentIds, err := listPolicyVifAttachmentsForVM(m, externalID) + if err != nil { + return fmt.Errorf("failed to list port attachments for VM id %s", externalID) + } + + ports, portsErr := listVpcSubnetPorts(connector, subnetPath) + if portsErr != nil { + return portsErr + } + for _, port := range ports { + if port.Attachment == nil || port.Attachment.Id == nil { + continue + } + + for _, attachment := range vifAttachmentIds { + if attachment == *port.Attachment.Id { + log.Printf("[DEBUG] Matching port %s found", *port.Path) + d.SetId(*port.Id) + d.Set("path", port.Path) + d.Set("display_name", port.DisplayName) + d.Set("description", port.Description) + return nil + } + } + } + + return fmt.Errorf("failed to find port for vm %s on subnet %s", externalID, subnetPath) +} diff --git a/nsxt/data_source_nsxt_vpc_subnet_port_test.go b/nsxt/data_source_nsxt_vpc_subnet_port_test.go new file mode 100644 index 000000000..f87348cac --- /dev/null +++ b/nsxt/data_source_nsxt_vpc_subnet_port_test.go @@ -0,0 +1,54 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDataSourceNsxtVpcSubnetPort_basic(t *testing.T) { + subnetObjectName := "data.nsxt_vpc_subnet.test" + portObjectName := "data.nsxt_vpc_subnet_port.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccOnlyVPC(t) + testAccPreCheck(t) + testAccNSXVersion(t, "9.0.0") + testAccEnvDefined(t, "NSXT_TEST_VPC_VM_ID") + testAccEnvDefined(t, "NSXT_TEST_VPC_SUBNET_NAME") + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccNsxtVpcSubnetPortReadTemplate(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(portObjectName, "display_name"), + resource.TestCheckResourceAttrSet(portObjectName, "path"), + resource.TestCheckResourceAttrSet(subnetObjectName, "path"), + ), + }, + }, + }) +} + +func testAccNsxtVpcSubnetPortReadTemplate() string { + context := testAccNsxtPolicyMultitenancyContext() + subnetName := os.Getenv("NSXT_TEST_VPC_SUBNET_NAME") + vmID := os.Getenv("NSXT_TEST_VPC_VM_ID") + return fmt.Sprintf(` +data "nsxt_vpc_subnet" "test" { +%s + display_name = "%s" +} + +data "nsxt_vpc_subnet_port" "test" { + subnet_path = data.nsxt_vpc_subnet.test.path + vm_id = "%s" +}`, context, subnetName, vmID) +} diff --git a/nsxt/data_source_nsxt_vpc_test.go b/nsxt/data_source_nsxt_vpc_test.go index 038021a45..5731935e9 100644 --- a/nsxt/data_source_nsxt_vpc_test.go +++ b/nsxt/data_source_nsxt_vpc_test.go @@ -55,7 +55,7 @@ func testAccDataSourceNsxtVPCCreate(name string) error { } ipBlockID := newUUID() - err = testAccDataSourceNsxtPolicyIPBlockCreate(name, ipBlockID, "192.168.240.0/24", true) + err = testAccDataSourceNsxtPolicyIPBlockCreate(testAccGetProjectContext(), name, ipBlockID, "192.168.240.0/24", true) if err != nil { return err } @@ -103,18 +103,19 @@ func testAccDataSourceNsxtVPCDeleteByName(name string) error { } for _, objInList := range objList.Results { if *objInList.DisplayName == name { - err := client.Delete(defaultOrgID, projID, *objInList.Id) + err := client.Delete(defaultOrgID, projID, *objInList.Id, nil) if err != nil { return handleDeleteError("VPC", *objInList.Id, err) } - return testAccDataSourceNsxtPolicyIPBlockDeleteByName(name) + return testAccDataSourceNsxtPolicyIPBlockDeleteByName(testAccGetProjectContext(), name) } } return fmt.Errorf("error while deleting VPC '%s': resource not found", name) } func testAccNsxtVPCReadTemplate(name string) string { - context := testAccNsxtPolicyMultitenancyContext() + // We just need the project context as VPC is not under VPC, but the VPC itself + context := testAccNsxtProjectContext() return fmt.Sprintf(` data "nsxt_policy_ip_block" "test" { %s diff --git a/nsxt/metadata/metadata.go b/nsxt/metadata/metadata.go index 1b4148ddf..e3d66ef11 100644 --- a/nsxt/metadata/metadata.go +++ b/nsxt/metadata/metadata.go @@ -286,6 +286,7 @@ func SchemaToStruct(elem reflect.Value, d *schema.ResourceData, metadata map[str logger.Printf("[TRACE] %s parent %s key %s", ctx, parent, key) } if len(item.Metadata.PolymorphicType) > 0 { + logger.Printf("[TRACE] %s inspecting polymorphic key %s", ctx, key) itemList := getItemListForSchemaToStruct(d, item.Metadata.SchemaType, key, parent, parentMap) switch item.Metadata.PolymorphicType { case PolymorphicTypeNested: @@ -306,7 +307,11 @@ func SchemaToStruct(elem reflect.Value, d *schema.ResourceData, metadata map[str exists := false if len(parent) > 0 && parentMap[key] != nil { value = parentMap[key].(string) - exists = true + // For nested maps, value is initialized to zero string even if not + // specified by user explicitly + if len(value) > 0 { + exists = true + } } else { var v interface{} v, exists = d.GetOk(key) diff --git a/nsxt/policy_common.go b/nsxt/policy_common.go index cf88a0d0f..490def339 100644 --- a/nsxt/policy_common.go +++ b/nsxt/policy_common.go @@ -28,21 +28,26 @@ var mpObjectDataSourceDeprecationMessage = "Please use corresponding policy data func getNsxIDSchema() *schema.Schema { return &schema.Schema{ - Type: schema.TypeString, - Description: "NSX ID for this resource", - Optional: true, - Computed: true, - ForceNew: true, + Type: schema.TypeString, + Description: "NSX ID for this resource", + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 1024), } } func getFlexNsxIDSchema(readOnly bool) *schema.Schema { - return &schema.Schema{ + s := schema.Schema{ Type: schema.TypeString, Description: "NSX ID for this resource", Optional: !readOnly, Computed: true, } + if !readOnly { + s.ValidateFunc = validation.StringLenBetween(1, 1024) + } + return &s } func getComputedNsxIDSchema() *schema.Schema { diff --git a/nsxt/policy_errors.go b/nsxt/policy_errors.go index b5175693c..e6ee7f1aa 100644 --- a/nsxt/policy_errors.go +++ b/nsxt/policy_errors.go @@ -18,19 +18,21 @@ import ( ) func printAPIError(apiError model.ApiError) string { - if apiError.ErrorMessage != nil && apiError.ErrorCode != nil { - return fmt.Sprintf("%s (code %v)", *apiError.ErrorMessage, *apiError.ErrorCode) - } + msg := "" if apiError.ErrorMessage != nil { - return *apiError.ErrorMessage + msg = *apiError.ErrorMessage + } + + if apiError.Details != nil { + msg += fmt.Sprintf(": %s", *apiError.Details) } if apiError.ErrorCode != nil { - return fmt.Sprintf("(code %v)", *apiError.ErrorCode) + msg += fmt.Sprintf(" (code %v)", *apiError.ErrorCode) } - return "" + return msg } // TODO: Remove duplicate code when sdk implements composition of API inheritance model diff --git a/nsxt/policy_utils.go b/nsxt/policy_utils.go index bdf40548c..69e3a24bc 100644 --- a/nsxt/policy_utils.go +++ b/nsxt/policy_utils.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "log" + "net/url" "regexp" "strings" "time" @@ -73,7 +74,6 @@ func getOrGenerateID(d *schema.ResourceData, m interface{}, presenceChecker func return id, nil } -//lint:ignore U1000 Ignore unused function temporarily until used in autogenerated resource func getOrGenerateIDWithParent(d *schema.ResourceData, m interface{}, presenceChecker func(utl.SessionContext, string, string, client.Connector) (bool, error)) (string, error) { connector := getPolicyConnector(m) @@ -143,7 +143,7 @@ func getIgnoredTagsFromSchema(d *schema.ResourceData) []model.Tag { return getPolicyTagsFromSet(discoveredTags) } -func getCustomizedPolicyTagsFromSchema(d *schema.ResourceData, schemaName string) []model.Tag { +func getCustomizedPolicyTagsFromSchema(d *schema.ResourceData, schemaName string) ([]model.Tag, error) { tags := d.Get(schemaName).(*schema.Set).List() ignoredTags := getIgnoredTagsFromSchema(d) tagList := make([]model.Tag, 0) @@ -151,6 +151,11 @@ func getCustomizedPolicyTagsFromSchema(d *schema.ResourceData, schemaName string data := tag.(map[string]interface{}) tagScope := data["scope"].(string) tagTag := data["tag"].(string) + + if len(tagScope)+len(tagTag) == 0 { + return tagList, fmt.Errorf("tag value or scope value needs to be specified") + + } elem := model.Tag{ Scope: &tagScope, Tag: &tagTag} @@ -160,7 +165,7 @@ func getCustomizedPolicyTagsFromSchema(d *schema.ResourceData, schemaName string if len(ignoredTags) > 0 { tagList = append(tagList, ignoredTags...) } - return tagList + return tagList, nil } func setIgnoredTagsInSchema(d *schema.ResourceData, scopesToIgnore []string, tags []map[string]interface{}) { @@ -221,6 +226,11 @@ func setCustomizedPolicyTagsInSchema(d *schema.ResourceData, tags []model.Tag, s } func getPolicyTagsFromSchema(d *schema.ResourceData) []model.Tag { + tags, _ := getCustomizedPolicyTagsFromSchema(d, "tag") + return tags +} + +func getValidatedTagsFromSchema(d *schema.ResourceData) ([]model.Tag, error) { return getCustomizedPolicyTagsFromSchema(d, "tag") } @@ -301,6 +311,9 @@ func parseStandardPolicyPath(path string) ([]string, error) { // append org and project parents = append(parents, segments[2]) parents = append(parents, segments[4]) + if len(segments) == 5 { // This is a project path, no further parsing is required + return parents, nil + } idx = 5 if len(segments) > 6 && segments[5] == "vpcs" { @@ -310,15 +323,14 @@ func parseStandardPolicyPath(path string) ([]string, error) { infraPath = false } } - if len(segments) <= idx { + if len(segments) < idx { return nil, fmt.Errorf("unexpected policy path %s", path) } if infraPath { // continue after infra marker - if segments[idx] != "infra" && segments[idx] != "global-infra" { - return nil, fmt.Errorf("policy path %s is expected to contain *infra marker", path) + if segments[idx] == "infra" || segments[idx] == "global-infra" { + idx++ } - idx++ } for i, seg := range segments[idx:] { @@ -381,6 +393,24 @@ func getParameterFromPolicyPath(startDelimiter, endDelimiter, policyPath string) return policyPath[startIndex+len(startDelimiter) : endIndex], nil } +func validateImportPolicyPath(policyPath string) error { + if isSpaceString(policyPath) { + return ErrEmptyImportID + } + _, err := url.ParseRequestURI(policyPath) + if err != nil { + return ErrNotAPolicyPath + } + if strings.Contains(policyPath, "//") { + return ErrNotAPolicyPath + } + if !isPolicyPath(policyPath) { + return ErrNotAPolicyPath + } + return nil +} + +// This importer function accepts policy path and resource ID func nsxtPolicyPathResourceImporter(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { rd, err := nsxtPolicyPathResourceImporterHelper(d, m) if errors.Is(err, ErrNotAPolicyPath) { @@ -391,6 +421,11 @@ func nsxtPolicyPathResourceImporter(d *schema.ResourceData, m interface{}) ([]*s return rd, nil } +// This importer function accepts policy path only as import ID +func nsxtPolicyPathOnlyResourceImporter(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + return nsxtPolicyPathResourceImporterHelper(d, m) +} + func nsxtVPCPathResourceImporter(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { rd, err := nsxtPolicyPathResourceImporterHelper(d, m) if err != nil { @@ -405,48 +440,45 @@ func nsxtVPCPathResourceImporter(d *schema.ResourceData, m interface{}) ([]*sche func nsxtPolicyPathResourceImporterHelper(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { importID := d.Id() - if isSpaceString(importID) { - return []*schema.ResourceData{d}, ErrEmptyImportID + err := validateImportPolicyPath(importID) + if err != nil { + return []*schema.ResourceData{}, err } - if isPolicyPath(importID) { - pathSegs := strings.Split(importID, "/") - if strings.Contains(pathSegs[1], "infra") { - d.SetId(pathSegs[len(pathSegs)-1]) - } else if pathSegs[1] == "orgs" && pathSegs[3] == "projects" { - if len(pathSegs) < 5 { - return nil, fmt.Errorf("invalid policy multitenancy path %s", importID) - } - // pathSegs[2] should contain the organization. Once we support multiple organization, it should be - // assigned into the context as well - ctxMap := make(map[string]interface{}) - ctxMap["project_id"] = pathSegs[4] - if pathSegs[5] == "vpcs" { - ctxMap["vpc_id"] = pathSegs[6] - } - d.Set("context", []interface{}{ctxMap}) - d.SetId(pathSegs[len(pathSegs)-1]) + + pathSegs := strings.Split(importID, "/") + if strings.Contains(pathSegs[1], "infra") { + d.SetId(pathSegs[len(pathSegs)-1]) + } else if pathSegs[1] == "orgs" && pathSegs[3] == "projects" { + if len(pathSegs) < 5 { + return nil, fmt.Errorf("invalid policy multitenancy path %s", importID) + } + // pathSegs[2] should contain the organization. Once we support multiple organization, it should be + // assigned into the context as well + ctxMap := make(map[string]interface{}) + ctxMap["project_id"] = pathSegs[4] + if pathSegs[5] == "vpcs" { + ctxMap["vpc_id"] = pathSegs[6] } - return []*schema.ResourceData{d}, nil + d.Set("context", []interface{}{ctxMap}) + d.SetId(pathSegs[len(pathSegs)-1]) } - return []*schema.ResourceData{d}, ErrNotAPolicyPath + return []*schema.ResourceData{d}, nil } -//lint:ignore U1000 Ignore unused function temporarily until used in autogenerated resource func nsxtParentPathResourceImporter(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { importID := d.Id() - if isSpaceString(importID) { - return []*schema.ResourceData{d}, ErrEmptyImportID - } - if isPolicyPath(importID) { - pathSegs := strings.Split(importID, "/") - segCount := len(pathSegs) - d.SetId(pathSegs[segCount-1]) - // to get parent path size, remove last two tokens (id and separator) plus two slashes - truncateSize := len(pathSegs[segCount-1]) + len(pathSegs[segCount-2]) + 2 - d.Set("parent_path", importID[:len(importID)-truncateSize]) - return []*schema.ResourceData{d}, nil + err := validateImportPolicyPath(importID) + if err != nil { + return []*schema.ResourceData{}, err } - return []*schema.ResourceData{d}, ErrNotAPolicyPath + + pathSegs := strings.Split(importID, "/") + segCount := len(pathSegs) + d.SetId(pathSegs[segCount-1]) + // to get parent path size, remove last two tokens (id and separator) plus two slashes + truncateSize := len(pathSegs[segCount-1]) + len(pathSegs[segCount-2]) + 2 + d.Set("parent_path", importID[:len(importID)-truncateSize]) + return []*schema.ResourceData{d}, nil } func isPolicyPath(policyPath string) bool { @@ -656,7 +688,6 @@ func getPolicyLbMonitorPortSchema() *schema.Schema { } } -//lint:ignore U1000 Ignore unused function temporarily until used in autogenerated resource func getVpcParentsFromContext(context utl.SessionContext) []string { return []string{utl.DefaultOrgID, context.ProjectID, context.VPCID} } diff --git a/nsxt/policy_utils_test.go b/nsxt/policy_utils_test.go index fe3877165..bb84aa77d 100644 --- a/nsxt/policy_utils_test.go +++ b/nsxt/policy_utils_test.go @@ -65,6 +65,7 @@ func TestIsPolicyPath(t *testing.T) { "/global-infra/tier-1s/mygw1", "/orgs/infra/tier-1s/mygw1", "/orgs/myorg/projects/myproj/domains/d", + "/orgs/myorg/projects/myproj/vpcs/nicevpc", } for _, test := range testData { diff --git a/nsxt/provider.go b/nsxt/provider.go index af2c05488..9f46d1167 100644 --- a/nsxt/provider.go +++ b/nsxt/provider.go @@ -328,6 +328,18 @@ func Provider() *schema.Provider { "nsxt_policy_gateway_flood_protection_profile": dataSourceNsxtPolicyGatewayFloodProtectionProfile(), "nsxt_manager_info": dataSourceNsxtManagerInfo(), "nsxt_vpc": dataSourceNsxtVPC(), + "nsxt_vpc_group": dataSourceNsxtVpcGroup(), + "nsxt_vpc_nat": dataSourceNsxtVpcNat(), + "nsxt_vpc_subnet": dataSourceNsxtVpcSubnet(), + "nsxt_vpc_subnet_port": dataSourceNsxtVpcSubnetPort(), + "nsxt_vpc_service_profile": dataSourceNsxtVpcServiceProfile(), + "nsxt_vpc_connectivity_profile": dataSourceNsxtVpcConnectivityProfile(), + "nsxt_policy_transit_gateway": dataSourceNsxtPolicyTransitGateway(), + "nsxt_policy_transit_gateway_nat": dataSourceNsxtPolicyTransitGatewayNat(), + "nsxt_policy_project_ip_address_allocation": dataSourceNsxtProjectIpAddressAllocation(), + "nsxt_vpc_ip_address_allocation": dataSourceNsxtVpcIpAddressAllocation(), + "nsxt_policy_gateway_connection": dataSourceNsxtPolicyGatewayConnection(), + "nsxt_policy_distributed_vlan_connection": dataSourceNsxtPolicyDistributedVlanConnection(), }, ResourcesMap: map[string]*schema.Resource{ @@ -499,8 +511,24 @@ func Provider() *schema.Provider { "nsxt_vpc_security_policy": resourceNsxtVPCSecurityPolicy(), "nsxt_vpc_group": resourceNsxtVPCGroup(), "nsxt_vpc_gateway_policy": resourceNsxtVPCGatewayPolicy(), + "nsxt_vpc_service_profile": resourceNsxtVpcServiceProfile(), + "nsxt_vpc_connectivity_profile": resourceNsxtVpcConnectivityProfile(), + "nsxt_policy_transit_gateway": resourceNsxtPolicyTransitGateway(), "nsxt_policy_share": resourceNsxtPolicyShare(), "nsxt_policy_shared_resource": resourceNsxtPolicySharedResource(), + "nsxt_policy_gateway_connection": resourceNsxtPolicyGatewayConnection(), + "nsxt_policy_distributed_vlan_connection": resourceNsxtPolicyDistributedVlanConnection(), + "nsxt_vpc": resourceNsxtVpc(), + "nsxt_vpc_attachment": resourceNsxtVpcAttachment(), + "nsxt_vpc_nat_rule": resourceNsxtPolicyVpcNatRule(), + "nsxt_policy_transit_gateway_attachment": resourceNsxtPolicyTransitGatewayAttachment(), + "nsxt_vpc_external_address": resourceNsxtVpcExternalAddress(), + "nsxt_vpc_ip_address_allocation": resourceNsxtVpcIpAddressAllocation(), + "nsxt_vpc_subnet": resourceNsxtVpcSubnet(), + "nsxt_policy_transit_gateway_nat_rule": resourceNsxtPolicyTransitGatewayNatRule(), + "nsxt_vpc_static_route": resourceNsxtVpcStaticRoutes(), + "nsxt_policy_project_ip_address_allocation": resourceNsxtPolicyProjectIpAddressAllocation(), + "nsxt_vpc_dhcp_v4_static_binding": resourceNsxtVpcSubnetDhcpV4StaticBindingConfig(), }, ConfigureFunc: providerConfigure, diff --git a/nsxt/resource_nsxt_policy_distributed_vlan_connection.go b/nsxt/resource_nsxt_policy_distributed_vlan_connection.go new file mode 100644 index 000000000..5b7a4c1b9 --- /dev/null +++ b/nsxt/resource_nsxt_policy_distributed_vlan_connection.go @@ -0,0 +1,199 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "log" + "reflect" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" + clientLayer "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/infra" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + + "github.com/vmware/terraform-provider-nsxt/nsxt/metadata" +) + +var distributedVlanConnectionSchema = map[string]*metadata.ExtendedSchema{ + "nsx_id": metadata.GetExtendedSchema(getNsxIDSchema()), + "path": metadata.GetExtendedSchema(getPathSchema()), + "display_name": metadata.GetExtendedSchema(getDisplayNameSchema()), + "description": metadata.GetExtendedSchema(getDescriptionSchema()), + "revision": metadata.GetExtendedSchema(getRevisionSchema()), + "tag": metadata.GetExtendedSchema(getTagsSchema()), + "vlan_id": { + Schema: schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "int", + SdkFieldName: "VlanId", + }, + }, + "gateway_addresses": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedSchema{ + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateIPCidr(), + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + }, + }, + Required: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "GatewayAddresses", + }, + }, +} + +func resourceNsxtPolicyDistributedVlanConnection() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtPolicyDistributedVlanConnectionCreate, + Read: resourceNsxtPolicyDistributedVlanConnectionRead, + Update: resourceNsxtPolicyDistributedVlanConnectionUpdate, + Delete: resourceNsxtPolicyDistributedVlanConnectionDelete, + Importer: &schema.ResourceImporter{ + State: nsxtPolicyPathOnlyResourceImporter, + }, + Schema: metadata.GetSchemaFromExtendedSchema(distributedVlanConnectionSchema), + } +} + +func resourceNsxtPolicyDistributedVlanConnectionExists(id string, connector client.Connector, isGlobalManager bool) (bool, error) { + var err error + + client := clientLayer.NewDistributedVlanConnectionsClient(connector) + _, err = client.Get(id) + if err == nil { + return true, nil + } + + if isNotFoundError(err) { + return false, nil + } + + return false, logAPIError("Error retrieving resource", err) +} + +func resourceNsxtPolicyDistributedVlanConnectionCreate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id, err := getOrGenerateID(d, m, resourceNsxtPolicyDistributedVlanConnectionExists) + if err != nil { + return err + } + + displayName := d.Get("display_name").(string) + description := d.Get("description").(string) + tags := getPolicyTagsFromSchema(d) + + obj := model.DistributedVlanConnection{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, distributedVlanConnectionSchema, "", nil); err != nil { + return err + } + + log.Printf("[INFO] Creating DistributedVlanConnection with ID %s", id) + + client := clientLayer.NewDistributedVlanConnectionsClient(connector) + err = client.Patch(id, obj) + if err != nil { + return handleCreateError("DistributedVlanConnection", id, err) + } + d.SetId(id) + d.Set("nsx_id", id) + + return resourceNsxtPolicyDistributedVlanConnectionRead(d, m) +} + +func resourceNsxtPolicyDistributedVlanConnectionRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining DistributedVlanConnection ID") + } + + client := clientLayer.NewDistributedVlanConnectionsClient(connector) + + obj, err := client.Get(id) + if err != nil { + return handleReadError(d, "DistributedVlanConnection", id, err) + } + + setPolicyTagsInSchema(d, obj.Tags) + d.Set("nsx_id", id) + d.Set("display_name", obj.DisplayName) + d.Set("description", obj.Description) + d.Set("revision", obj.Revision) + d.Set("path", obj.Path) + + elem := reflect.ValueOf(&obj).Elem() + return metadata.StructToSchema(elem, d, distributedVlanConnectionSchema, "", nil) +} + +func resourceNsxtPolicyDistributedVlanConnectionUpdate(d *schema.ResourceData, m interface{}) error { + + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining DistributedVlanConnection ID") + } + + description := d.Get("description").(string) + displayName := d.Get("display_name").(string) + tags := getPolicyTagsFromSchema(d) + + revision := int64(d.Get("revision").(int)) + + obj := model.DistributedVlanConnection{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + Revision: &revision, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, distributedVlanConnectionSchema, "", nil); err != nil { + return err + } + client := clientLayer.NewDistributedVlanConnectionsClient(connector) + _, err := client.Update(id, obj) + if err != nil { + return handleUpdateError("DistributedVlanConnection", id, err) + } + + return resourceNsxtPolicyDistributedVlanConnectionRead(d, m) +} + +func resourceNsxtPolicyDistributedVlanConnectionDelete(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining DistributedVlanConnection ID") + } + + connector := getPolicyConnector(m) + + client := clientLayer.NewDistributedVlanConnectionsClient(connector) + err := client.Delete(id) + + if err != nil { + return handleDeleteError("DistributedVlanConnection", id, err) + } + + return nil +} diff --git a/nsxt/resource_nsxt_policy_distributed_vlan_connection_test.go b/nsxt/resource_nsxt_policy_distributed_vlan_connection_test.go new file mode 100644 index 000000000..5fcc79db1 --- /dev/null +++ b/nsxt/resource_nsxt_policy_distributed_vlan_connection_test.go @@ -0,0 +1,203 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +var accTestPolicyDistributedVlanConnectionCreateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform created", + "vlan_id": "12", + "gateway_addresses": "3.3.3.1/24", +} + +var accTestPolicyDistributedVlanConnectionUpdateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform updated", + "vlan_id": "22", + "gateway_addresses": "2.2.2.1/24", +} + +func TestAccResourceNsxtPolicyDistributedVlanConnection_basic(t *testing.T) { + testResourceName := "nsxt_policy_distributed_vlan_connection.test" + testDataSourceName := "data.nsxt_policy_distributed_vlan_connection.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccOnlyVPC(t) + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicyDistributedVlanConnectionCheckDestroy(state, accTestPolicyDistributedVlanConnectionUpdateAttributes["display_name"]) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicyDistributedVlanConnectionTemplate(true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyDistributedVlanConnectionExists(accTestPolicyDistributedVlanConnectionCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestPolicyDistributedVlanConnectionCreateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestPolicyDistributedVlanConnectionCreateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "vlan_id", accTestPolicyDistributedVlanConnectionCreateAttributes["vlan_id"]), + resource.TestCheckResourceAttr(testResourceName, "gateway_addresses.0", accTestPolicyDistributedVlanConnectionCreateAttributes["gateway_addresses"]), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + resource.TestCheckResourceAttrSet(testDataSourceName, "path"), + ), + }, + { + Config: testAccNsxtPolicyDistributedVlanConnectionTemplate(false), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyDistributedVlanConnectionExists(accTestPolicyDistributedVlanConnectionUpdateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestPolicyDistributedVlanConnectionUpdateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestPolicyDistributedVlanConnectionUpdateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "vlan_id", accTestPolicyDistributedVlanConnectionUpdateAttributes["vlan_id"]), + resource.TestCheckResourceAttr(testResourceName, "gateway_addresses.0", accTestPolicyDistributedVlanConnectionUpdateAttributes["gateway_addresses"]), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + resource.TestCheckResourceAttrSet(testDataSourceName, "path"), + ), + }, + { + Config: testAccNsxtPolicyDistributedVlanConnectionMinimalistic(), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyDistributedVlanConnectionExists(accTestPolicyDistributedVlanConnectionCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + resource.TestCheckResourceAttrSet(testDataSourceName, "path"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtPolicyDistributedVlanConnection_importBasic(t *testing.T) { + name := getAccTestResourceName() + testResourceName := "nsxt_policy_distributed_vlan_connection.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccOnlyVPC(t) + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicyDistributedVlanConnectionCheckDestroy(state, name) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicyDistributedVlanConnectionMinimalistic(), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccNsxtPolicyDistributedVlanConnectionExists(displayName string, resourceName string) resource.TestCheckFunc { + return func(state *terraform.State) error { + + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + + rs, ok := state.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Policy GatewayConnection resource %s not found in resources", resourceName) + } + + resourceID := rs.Primary.ID + if resourceID == "" { + return fmt.Errorf("Policy GatewayConnection resource ID not set in resources") + } + + exists, err := resourceNsxtPolicyDistributedVlanConnectionExists(resourceID, connector, testAccIsGlobalManager()) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("Policy GatewayConnection %s does not exist", resourceID) + } + + return nil + } +} + +func testAccNsxtPolicyDistributedVlanConnectionCheckDestroy(state *terraform.State, displayName string) error { + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + for _, rs := range state.RootModule().Resources { + + if rs.Type != "nsxt_policy_distributed_vlan_connection" { + continue + } + + resourceID := rs.Primary.Attributes["id"] + exists, err := resourceNsxtPolicyDistributedVlanConnectionExists(resourceID, connector, testAccIsGlobalManager()) + if err == nil { + return err + } + + if exists { + return fmt.Errorf("Policy GatewayConnection %s still exists", displayName) + } + } + return nil +} + +func testAccNsxtPolicyDistributedVlanConnectionTemplate(createFlow bool) string { + var attrMap map[string]string + if createFlow { + attrMap = accTestPolicyDistributedVlanConnectionCreateAttributes + } else { + attrMap = accTestPolicyDistributedVlanConnectionUpdateAttributes + } + return fmt.Sprintf(` +resource "nsxt_policy_distributed_vlan_connection" "test" { + display_name = "%s" + description = "%s" + + vlan_id = %s + gateway_addresses = ["%s"] + + tag { + scope = "scope1" + tag = "tag1" + } +} + +data "nsxt_policy_distributed_vlan_connection" "test" { + display_name = "%s" + + depends_on = [nsxt_policy_distributed_vlan_connection.test] +}`, attrMap["display_name"], attrMap["description"], attrMap["vlan_id"], attrMap["gateway_addresses"], attrMap["display_name"]) +} + +func testAccNsxtPolicyDistributedVlanConnectionMinimalistic() string { + return fmt.Sprintf(` +resource "nsxt_policy_distributed_vlan_connection" "test" { + display_name = "%s" +} + +data "nsxt_policy_distributed_vlan_connection" "test" { + display_name = "%s" + + depends_on = [nsxt_policy_distributed_vlan_connection.test] +}`, accTestPolicyDistributedVlanConnectionUpdateAttributes["display_name"], accTestPolicyDistributedVlanConnectionUpdateAttributes["display_name"]) +} diff --git a/nsxt/resource_nsxt_policy_gateway_connection.go b/nsxt/resource_nsxt_policy_gateway_connection.go new file mode 100644 index 000000000..91e07bef5 --- /dev/null +++ b/nsxt/resource_nsxt_policy_gateway_connection.go @@ -0,0 +1,218 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "log" + "reflect" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" + clientLayer "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/infra" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + + "github.com/vmware/terraform-provider-nsxt/nsxt/metadata" +) + +var gatewayConnectionSchema = map[string]*metadata.ExtendedSchema{ + "nsx_id": metadata.GetExtendedSchema(getNsxIDSchema()), + "path": metadata.GetExtendedSchema(getPathSchema()), + "display_name": metadata.GetExtendedSchema(getDisplayNameSchema()), + "description": metadata.GetExtendedSchema(getDescriptionSchema()), + "revision": metadata.GetExtendedSchema(getRevisionSchema()), + "tag": metadata.GetExtendedSchema(getTagsSchema()), + "advertise_outbound_route_filters": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedSchema{ + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validatePolicyPath(), + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "AdvertiseOutboundRouteFilters", + }, + }, + "aggregate_routes": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedSchema{ + Schema: schema.Schema{ + Type: schema.TypeString, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "AggregateRoutes", + }, + }, + "tier0_path": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validatePolicyPath(), + Required: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "Tier0Path", + }, + }, +} + +func resourceNsxtPolicyGatewayConnection() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtPolicyGatewayConnectionCreate, + Read: resourceNsxtPolicyGatewayConnectionRead, + Update: resourceNsxtPolicyGatewayConnectionUpdate, + Delete: resourceNsxtPolicyGatewayConnectionDelete, + Importer: &schema.ResourceImporter{ + State: nsxtPolicyPathOnlyResourceImporter, + }, + Schema: metadata.GetSchemaFromExtendedSchema(gatewayConnectionSchema), + } +} + +func resourceNsxtPolicyGatewayConnectionExists(id string, connector client.Connector, isGlobalManager bool) (bool, error) { + var err error + + client := clientLayer.NewGatewayConnectionsClient(connector) + _, err = client.Get(id) + if err == nil { + return true, nil + } + + if isNotFoundError(err) { + return false, nil + } + + return false, logAPIError("Error retrieving resource", err) +} + +func resourceNsxtPolicyGatewayConnectionCreate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id, err := getOrGenerateID(d, m, resourceNsxtPolicyGatewayConnectionExists) + if err != nil { + return err + } + + displayName := d.Get("display_name").(string) + description := d.Get("description").(string) + tags := getPolicyTagsFromSchema(d) + + obj := model.GatewayConnection{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, gatewayConnectionSchema, "", nil); err != nil { + return err + } + + log.Printf("[INFO] Creating GatewayConnection with ID %s", id) + + client := clientLayer.NewGatewayConnectionsClient(connector) + err = client.Patch(id, obj) + if err != nil { + return handleCreateError("GatewayConnection", id, err) + } + d.SetId(id) + d.Set("nsx_id", id) + + return resourceNsxtPolicyGatewayConnectionRead(d, m) +} + +func resourceNsxtPolicyGatewayConnectionRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining GatewayConnection ID") + } + + client := clientLayer.NewGatewayConnectionsClient(connector) + + obj, err := client.Get(id) + if err != nil { + return handleReadError(d, "GatewayConnection", id, err) + } + + setPolicyTagsInSchema(d, obj.Tags) + d.Set("nsx_id", id) + d.Set("display_name", obj.DisplayName) + d.Set("description", obj.Description) + d.Set("revision", obj.Revision) + d.Set("path", obj.Path) + + elem := reflect.ValueOf(&obj).Elem() + return metadata.StructToSchema(elem, d, gatewayConnectionSchema, "", nil) +} + +func resourceNsxtPolicyGatewayConnectionUpdate(d *schema.ResourceData, m interface{}) error { + + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining GatewayConnection ID") + } + + description := d.Get("description").(string) + displayName := d.Get("display_name").(string) + tags := getPolicyTagsFromSchema(d) + + revision := int64(d.Get("revision").(int)) + + obj := model.GatewayConnection{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + Revision: &revision, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, gatewayConnectionSchema, "", nil); err != nil { + return err + } + client := clientLayer.NewGatewayConnectionsClient(connector) + _, err := client.Update(id, obj) + if err != nil { + return handleUpdateError("GatewayConnection", id, err) + } + + return resourceNsxtPolicyGatewayConnectionRead(d, m) +} + +func resourceNsxtPolicyGatewayConnectionDelete(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining GatewayConnection ID") + } + + connector := getPolicyConnector(m) + + client := clientLayer.NewGatewayConnectionsClient(connector) + err := client.Delete(id) + + if err != nil { + return handleDeleteError("GatewayConnection", id, err) + } + + return nil +} diff --git a/nsxt/resource_nsxt_policy_gateway_connection_test.go b/nsxt/resource_nsxt_policy_gateway_connection_test.go new file mode 100644 index 000000000..b08626cef --- /dev/null +++ b/nsxt/resource_nsxt_policy_gateway_connection_test.go @@ -0,0 +1,212 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +var accTestPolicyGatewayConnectionCreateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform created", + "aggregate_routes": "192.168.240.0/24", +} + +var accTestPolicyGatewayConnectionUpdateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform updated", + "aggregate_routes": "192.168.241.0/24", +} + +func TestAccResourceNsxtPolicyGatewayConnection_basic(t *testing.T) { + testResourceName := "nsxt_policy_gateway_connection.test" + testDataSourceName := "data.nsxt_policy_gateway_connection.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccOnlyVPC(t) + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicyGatewayConnectionCheckDestroy(state, accTestPolicyGatewayConnectionUpdateAttributes["display_name"]) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicyGatewayConnectionTemplate(true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyGatewayConnectionExists(accTestPolicyGatewayConnectionCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestPolicyGatewayConnectionCreateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestPolicyGatewayConnectionCreateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "aggregate_routes.0", accTestPolicyGatewayConnectionCreateAttributes["aggregate_routes"]), + resource.TestCheckResourceAttrSet(testResourceName, "tier0_path"), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + resource.TestCheckResourceAttrSet(testDataSourceName, "path"), + ), + }, + { + Config: testAccNsxtPolicyGatewayConnectionTemplate(false), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyGatewayConnectionExists(accTestPolicyGatewayConnectionUpdateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestPolicyGatewayConnectionUpdateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestPolicyGatewayConnectionUpdateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "aggregate_routes.0", accTestPolicyGatewayConnectionUpdateAttributes["aggregate_routes"]), + resource.TestCheckResourceAttrSet(testResourceName, "tier0_path"), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + resource.TestCheckResourceAttrSet(testDataSourceName, "path"), + ), + }, + { + Config: testAccNsxtPolicyGatewayConnectionMinimalistic(), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyGatewayConnectionExists(accTestPolicyGatewayConnectionCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + resource.TestCheckResourceAttrSet(testDataSourceName, "path"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtPolicyGatewayConnection_importBasic(t *testing.T) { + name := getAccTestResourceName() + testResourceName := "nsxt_policy_gateway_connection.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccOnlyVPC(t) + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicyGatewayConnectionCheckDestroy(state, name) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicyGatewayConnectionMinimalistic(), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccNsxtPolicyGatewayConnectionExists(displayName string, resourceName string) resource.TestCheckFunc { + return func(state *terraform.State) error { + + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + + rs, ok := state.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Policy GatewayConnection resource %s not found in resources", resourceName) + } + + resourceID := rs.Primary.ID + if resourceID == "" { + return fmt.Errorf("Policy GatewayConnection resource ID not set in resources") + } + + exists, err := resourceNsxtPolicyGatewayConnectionExists(resourceID, connector, testAccIsGlobalManager()) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("Policy GatewayConnection %s does not exist", resourceID) + } + + return nil + } +} + +func testAccNsxtPolicyGatewayConnectionCheckDestroy(state *terraform.State, displayName string) error { + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + for _, rs := range state.RootModule().Resources { + + if rs.Type != "nsxt_policy_gateway_connection" { + continue + } + + resourceID := rs.Primary.Attributes["id"] + exists, err := resourceNsxtPolicyGatewayConnectionExists(resourceID, connector, testAccIsGlobalManager()) + if err == nil { + return err + } + + if exists { + return fmt.Errorf("Policy GatewayConnection %s still exists", displayName) + } + } + return nil +} + +func testAccNsxtPolicyGatewayConnectionPrerequisites() string { + return fmt.Sprintf(` +data "nsxt_policy_edge_cluster" "EC" { + display_name = "%s" +} + +resource "nsxt_policy_tier0_gateway" "test" { + display_name = "terraformt0gw" + edge_cluster_path = data.nsxt_policy_edge_cluster.EC.path +}`, getEdgeClusterName()) +} + +func testAccNsxtPolicyGatewayConnectionTemplate(createFlow bool) string { + var attrMap map[string]string + if createFlow { + attrMap = accTestPolicyGatewayConnectionCreateAttributes + } else { + attrMap = accTestPolicyGatewayConnectionUpdateAttributes + } + return testAccNsxtPolicyGatewayConnectionPrerequisites() + fmt.Sprintf(` +resource "nsxt_policy_gateway_connection" "test" { + display_name = "%s" + description = "%s" + tier0_path = nsxt_policy_tier0_gateway.test.path + aggregate_routes = ["%s"] + + tag { + scope = "scope1" + tag = "tag1" + } +} + +data "nsxt_policy_gateway_connection" "test" { + tier0_path = nsxt_policy_tier0_gateway.test.path + display_name = "%s" + depends_on = [nsxt_policy_gateway_connection.test] +}`, attrMap["display_name"], attrMap["description"], attrMap["aggregate_routes"], attrMap["display_name"]) +} + +func testAccNsxtPolicyGatewayConnectionMinimalistic() string { + return testAccNsxtPolicyGatewayConnectionPrerequisites() + fmt.Sprintf(` +resource "nsxt_policy_gateway_connection" "test" { + tier0_path = nsxt_policy_tier0_gateway.test.path + display_name = "%s" +} + +data "nsxt_policy_gateway_connection" "test" { + display_name = "%s" + depends_on = [nsxt_policy_gateway_connection.test] +}`, accTestPolicyGatewayConnectionUpdateAttributes["display_name"], accTestPolicyGatewayConnectionUpdateAttributes["display_name"]) +} diff --git a/nsxt/resource_nsxt_policy_gateway_policy.go b/nsxt/resource_nsxt_policy_gateway_policy.go index 3ff0191d1..0bbe0d703 100644 --- a/nsxt/resource_nsxt_policy_gateway_policy.go +++ b/nsxt/resource_nsxt_policy_gateway_policy.go @@ -132,7 +132,11 @@ func policyGatewayPolicyBuildAndPatch(d *schema.ResourceData, m interface{}, con } displayName := d.Get("display_name").(string) description := d.Get("description").(string) - tags := getPolicyTagsFromSchema(d) + tags, tagErr := getValidatedTagsFromSchema(d) + if tagErr != nil { + return tagErr + } + comments := d.Get("comments").(string) locked := d.Get("locked").(bool) sequenceNumber := int64(d.Get("sequence_number").(int)) diff --git a/nsxt/resource_nsxt_policy_group.go b/nsxt/resource_nsxt_policy_group.go index a405d9eb0..1ec613fd8 100644 --- a/nsxt/resource_nsxt_policy_group.go +++ b/nsxt/resource_nsxt_policy_group.go @@ -55,6 +55,8 @@ var conditionMemberTypeValues = []string{ model.Condition_MEMBER_TYPE_KUBERNETESGATEWAY, model.Condition_MEMBER_TYPE_KUBERNETESSERVICE, model.Condition_MEMBER_TYPE_KUBERNETESNODE, + model.Condition_MEMBER_TYPE_VPCSUBNETPORT, + model.Condition_MEMBER_TYPE_VPCSUBNET, } var conditionOperatorValues = []string{ @@ -106,7 +108,7 @@ func getPolicyGroupSchema(withDomain bool) map[string]*schema.Schema { "description": getDescriptionSchema(), "revision": getRevisionSchema(), "tag": getTagsSchema(), - "context": getContextSchema(false, false, !withDomain), + "context": getContextSchema(!withDomain, false, !withDomain), "group_type": { Type: schema.TypeString, Description: "Indicates the group type", @@ -320,7 +322,7 @@ func getExtendedCriteriaSetSchema() *schema.Resource { Type: schema.TypeSet, Description: "Identity Group expression", Elem: getIdentityGroupSchema(), - Optional: true, + Required: true, }, }, } @@ -373,6 +375,9 @@ type criteriaMeta struct { func validateGroupCriteriaSets(criteriaSets []interface{}) ([]criteriaMeta, error) { var criteria []criteriaMeta for _, criteriaBlock := range criteriaSets { + if criteriaBlock == nil { + return nil, fmt.Errorf("found empty criteria block in configuration") + } seenExp := "" criteriaMap := criteriaBlock.(map[string]interface{}) for expName, expVal := range criteriaMap { @@ -856,10 +861,8 @@ func resourceNsxtPolicyGroupGeneralCreate(d *schema.ResourceData, m interface{}, if err != nil { return err } - criteriaSets := d.Get("criteria").([]interface{}) conjunctions := d.Get("conjunction").([]interface{}) - criteriaMeta, err := validateGroupCriteriaAndConjunctions(criteriaSets, conjunctions) if err != nil { return err @@ -881,7 +884,10 @@ func resourceNsxtPolicyGroupGeneralCreate(d *schema.ResourceData, m interface{}, } displayName := d.Get("display_name").(string) description := d.Get("description").(string) - tags := getPolicyTagsFromSchema(d) + tags, tagErr := getValidatedTagsFromSchema(d) + if tagErr != nil { + return tagErr + } var groupTypes []string groupType := d.Get("group_type").(string) diff --git a/nsxt/resource_nsxt_policy_group_test.go b/nsxt/resource_nsxt_policy_group_test.go index fe8e47d98..8a490c6fe 100644 --- a/nsxt/resource_nsxt_policy_group_test.go +++ b/nsxt/resource_nsxt_policy_group_test.go @@ -2,6 +2,7 @@ package nsxt import ( "fmt" + "regexp" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -58,6 +59,39 @@ func TestAccResourceNsxtPolicyGroup_basicImport_multitenancy(t *testing.T) { }) } +func TestAccResourceNsxtPolicyGroup_empty(t *testing.T) { + testAccResourceNsxtPolicyGroupEmpty(t, false, func() { + testAccPreCheck(t) + }) +} + +func TestAccResourceNsxtPolicyGroup_empty_multitenancy(t *testing.T) { + testAccResourceNsxtPolicyGroupEmpty(t, true, func() { + testAccPreCheck(t) + testAccOnlyMultitenancy(t) + }) +} + +func testAccResourceNsxtPolicyGroupEmpty(t *testing.T, withContext bool, preCheck func()) { + // A simple tests that verify successful creation of a group with no criteria + name := getAccTestResourceName() + resourceName := "nsxt_policy_group" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: preCheck, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicyGroupCheckDestroy(state, name, defaultDomain) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicyEmptyCreateTemplate(name, resourceName, withContext), + ExpectError: regexp.MustCompile("found empty criteria block in configuration"), + }, + }, + }) +} + func TestAccResourceNsxtPolicyGroup_addressCriteria(t *testing.T) { testAccResourceNsxtPolicyGroupAddressCriteria(t, false, func() { testAccPreCheck(t) @@ -735,6 +769,33 @@ resource "%s" "test" { `, resourceName, context, name) } +func testAccNsxtPolicyEmptyCreateTemplate(name, resourceName string, withContext bool) string { + context := "" + if withContext { + context = testAccNsxtPolicyMultitenancyContext() + } + return fmt.Sprintf(` +resource "%s" "test_empty" { +%s + display_name = "%s" + description = "Acceptance Test" + + criteria { + } + + tag { + scope = "scope1" + tag = "tag1" + } + + tag { + scope = "scope2" + tag = "tag2" + } +} +`, resourceName, context, name) +} + func testAccNsxtPolicyGroupAddressCreateTemplate(name, resourceName string, withContext bool) string { context := "" if withContext { diff --git a/nsxt/resource_nsxt_policy_lb_service_test.go b/nsxt/resource_nsxt_policy_lb_service_test.go index c8b4fb7de..869bc0c18 100644 --- a/nsxt/resource_nsxt_policy_lb_service_test.go +++ b/nsxt/resource_nsxt_policy_lb_service_test.go @@ -27,7 +27,7 @@ var accTestPolicyLBServiceUpdateAttributes = map[string]string{ "connectivity_path": "nsxt_policy_tier1_gateway.test2.path", "enabled": "false", "error_log_level": "EMERGENCY", - "size": "MEDIUM", + "size": "SMALL", } var accTestPolicyLBServiceGateway1Name = getAccTestResourceName() diff --git a/nsxt/resource_nsxt_policy_metadata_proxy.go b/nsxt/resource_nsxt_policy_metadata_proxy.go index 12f43279c..cc98f263a 100644 --- a/nsxt/resource_nsxt_policy_metadata_proxy.go +++ b/nsxt/resource_nsxt_policy_metadata_proxy.go @@ -169,7 +169,6 @@ func resourceNsxtPolicyMetadataProxyRead(d *schema.ResourceData, m interface{}) d.Set("path", obj.Path) d.Set("revision", obj.Revision) d.Set("crypto_protocols", stringList2Interface(obj.CryptoProtocols)) - d.Set("secret", obj.Secret) d.Set("edge_cluster_path", obj.EdgeClusterPath) d.Set("enable_standby_relocation", obj.EnableStandbyRelocation) d.Set("preferred_edge_paths", stringList2Interface(obj.PreferredEdgePaths)) diff --git a/nsxt/resource_nsxt_policy_metadata_proxy_test.go b/nsxt/resource_nsxt_policy_metadata_proxy_test.go index 8a321147b..d21b0aea9 100644 --- a/nsxt/resource_nsxt_policy_metadata_proxy_test.go +++ b/nsxt/resource_nsxt_policy_metadata_proxy_test.go @@ -44,7 +44,6 @@ func TestAccResourceNsxtPolicyMetadataProxy_basic(t *testing.T) { resource.TestCheckResourceAttr(testResourceName, "display_name", accTestPolicyMetadataProxyCreateAttributes["display_name"]), resource.TestCheckResourceAttr(testResourceName, "description", accTestPolicyMetadataProxyCreateAttributes["description"]), - resource.TestCheckResourceAttr(testResourceName, "secret", accTestPolicyMetadataProxyCreateAttributes["secret"]), resource.TestCheckResourceAttr(testResourceName, "server_address", accTestPolicyMetadataProxyCreateAttributes["server_address"]), resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), @@ -60,7 +59,6 @@ func TestAccResourceNsxtPolicyMetadataProxy_basic(t *testing.T) { resource.TestCheckResourceAttr(testResourceName, "display_name", accTestPolicyMetadataProxyUpdateAttributes["display_name"]), resource.TestCheckResourceAttr(testResourceName, "description", accTestPolicyMetadataProxyUpdateAttributes["description"]), - resource.TestCheckResourceAttr(testResourceName, "secret", accTestPolicyMetadataProxyUpdateAttributes["secret"]), resource.TestCheckResourceAttr(testResourceName, "server_address", accTestPolicyMetadataProxyUpdateAttributes["server_address"]), resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), @@ -90,9 +88,10 @@ func TestAccResourceNsxtPolicyMetadataProxy_importBasic(t *testing.T) { Config: testAccNsxtPolicyMetadataProxyTemplate(true), }, { - ResourceName: testResourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"secret"}, }, }, }) diff --git a/nsxt/resource_nsxt_policy_parent_security_policy.go b/nsxt/resource_nsxt_policy_parent_security_policy.go index 027117fe1..105130391 100644 --- a/nsxt/resource_nsxt_policy_parent_security_policy.go +++ b/nsxt/resource_nsxt_policy_parent_security_policy.go @@ -25,10 +25,13 @@ func resourceNsxtPolicyParentSecurityPolicy() *schema.Resource { } } -func parentSecurityPolicySchemaToModel(d *schema.ResourceData, id string) model.SecurityPolicy { +func parentSecurityPolicySchemaToModel(d *schema.ResourceData, id string) (model.SecurityPolicy, error) { displayName := d.Get("display_name").(string) description := d.Get("description").(string) - tags := getPolicyTagsFromSchema(d) + tags, tagErr := getValidatedTagsFromSchema(d) + if tagErr != nil { + return model.SecurityPolicy{}, tagErr + } cat, ok := d.GetOk("category") category := "" if ok { @@ -59,7 +62,7 @@ func parentSecurityPolicySchemaToModel(d *schema.ResourceData, id string) model. if category != "" { obj.Category = &category } - return obj + return obj, nil } func parentSecurityPolicyModelToSchema(d *schema.ResourceData, m interface{}, isVPC bool) (*model.SecurityPolicy, error) { diff --git a/nsxt/resource_nsxt_policy_project.go b/nsxt/resource_nsxt_policy_project.go index 1f9c6ad8d..15b3f54e3 100644 --- a/nsxt/resource_nsxt_policy_project.go +++ b/nsxt/resource_nsxt_policy_project.go @@ -8,9 +8,11 @@ import ( "log" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/vmware/terraform-provider-nsxt/nsxt/util" "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" infra "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects" ) func resourceNsxtPolicyProject() *schema.Resource { @@ -55,11 +57,46 @@ func resourceNsxtPolicyProject() *schema.Resource { Elem: getElemPolicyPathSchema(), Optional: true, }, + "activate_default_dfw_rules": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, "external_ipv4_blocks": { Type: schema.TypeList, Elem: getElemPolicyPathSchema(), Optional: true, }, + "tgw_external_connections": { + Type: schema.TypeList, + Elem: getElemPolicyPathSchema(), + Optional: true, + }, + "default_security_profile": { + Type: schema.TypeList, + Optional: true, + MinItems: 1, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "north_south_firewall": { + Type: schema.TypeList, + MinItems: 1, + MaxItems: 1, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enabled": { + Type: schema.TypeBool, + Required: true, + Description: "Flag that indicates whether north-south firewall (Gateway Firewall) is enabled", + }, + }, + }, + }, + }, + }, + }, }, } } @@ -101,7 +138,9 @@ func resourceNsxtPolicyProjectPatch(connector client.Connector, d *schema.Resour siteInfos = append(siteInfos, obj) } tier0s := getStringListFromSchemaList(d, "tier0_gateway_paths") - externalIPV4Blocks := getStringListFromSchemaList(d, "external_ipv4_blocks") + activateDefaultDFWRules := d.Get("activate_default_dfw_rules").(bool) + extIpv4BlocksList := getStringListFromSchemaList(d, "external_ipv4_blocks") + tgwConnectionsList := getStringListFromSchemaList(d, "tgw_external_connections") obj := model.Project{ DisplayName: &displayName, @@ -109,7 +148,19 @@ func resourceNsxtPolicyProjectPatch(connector client.Connector, d *schema.Resour Tags: tags, SiteInfos: siteInfos, Tier0s: tier0s, - ExternalIpv4Blocks: externalIPV4Blocks, + ExternalIpv4Blocks: extIpv4BlocksList, + } + + if util.NsxVersionHigherOrEqual("4.2.0") { + obj.ActivateDefaultDfwRules = &activateDefaultDFWRules + } + + if util.NsxVersionHigherOrEqual("4.1.1") { + obj.ExternalIpv4Blocks = extIpv4BlocksList + } + + if util.NsxVersionHigherOrEqual("9.0.0") { + obj.TgwExternalConnections = tgwConnectionsList } if shortID != "" { @@ -119,7 +170,62 @@ func resourceNsxtPolicyProjectPatch(connector client.Connector, d *schema.Resour log.Printf("[INFO] Patching Project with ID %s", id) client := infra.NewProjectsClient(connector) - return client.Patch(defaultOrgID, id, obj) + err := client.Patch(defaultOrgID, id, obj) + if err != nil { + return err + } + + if d.HasChanges("default_security_profile") { + err = patchVpcSecurityProfile(d, connector, id) + } + return err +} + +func patchVpcSecurityProfile(d *schema.ResourceData, connector client.Connector, projectID string) error { + enabled := false + defaultSecurityProfile := d.Get("default_security_profile") + if defaultSecurityProfile != nil { + dsp := defaultSecurityProfile.([]interface{}) + if len(dsp) > 0 { + northSouthFirewall := dsp[0].(map[string]interface{})["north_south_firewall"] + if northSouthFirewall != nil { + nsfw := northSouthFirewall.([]interface{}) + if len(nsfw) > 0 { + elem := nsfw[0].(map[string]interface{}) + enabled = elem["enabled"].(bool) + } + } + } + } + // Default security profile is created by NSX, we can assume that it's there already + client := projects.NewVpcSecurityProfilesClient(connector) + obj := model.VpcSecurityProfile{ + NorthSouthFirewall: &model.NorthSouthFirewall{ + Enabled: &enabled, + }, + } + return client.Patch(defaultOrgID, projectID, "default", obj) +} + +func setVpcSecurityProfileInSchema(d *schema.ResourceData, connector client.Connector, projectID string) error { + client := projects.NewVpcSecurityProfilesClient(connector) + obj, err := client.Get(defaultOrgID, projectID, "default") + if err != nil { + return err + } + + enabled := false + if obj.NorthSouthFirewall != nil && obj.NorthSouthFirewall.Enabled != nil { + enabled = *obj.NorthSouthFirewall.Enabled + } + + nsfw := map[string]interface{}{"enabled": &enabled} + nsfws := []interface{}{nsfw} + dsp := map[string]interface{}{"north_south_firewall": nsfws} + dsps := []interface{}{dsp} + + d.Set("default_security_profile", dsps) + return nil } func resourceNsxtPolicyProjectCreate(d *schema.ResourceData, m interface{}) error { @@ -147,7 +253,7 @@ func resourceNsxtPolicyProjectRead(d *schema.ResourceData, m interface{}) error id := d.Id() if id == "" { - return fmt.Errorf("Error obtaining Project ID") + return fmt.Errorf("error obtaining Project ID") } var obj model.Project @@ -175,8 +281,20 @@ func resourceNsxtPolicyProjectRead(d *schema.ResourceData, m interface{}) error } d.Set("site_info", siteInfosList) d.Set("tier0_gateway_paths", obj.Tier0s) - d.Set("external_ipv4_blocks", obj.ExternalIpv4Blocks) + err = setVpcSecurityProfileInSchema(d, connector, id) + if err != nil { + return err + } + if util.NsxVersionHigherOrEqual("4.2.0") { + d.Set("activate_default_dfw_rules", obj.ActivateDefaultDfwRules) + } + if util.NsxVersionHigherOrEqual("4.1.1") { + d.Set("external_ipv4_blocks", obj.ExternalIpv4Blocks) + } + if util.NsxVersionHigherOrEqual("9.0.0") { + d.Set("tgw_external_connections", obj.TgwExternalConnections) + } return nil } @@ -184,7 +302,7 @@ func resourceNsxtPolicyProjectUpdate(d *schema.ResourceData, m interface{}) erro id := d.Id() if id == "" { - return fmt.Errorf("Error obtaining Project ID") + return fmt.Errorf("error obtaining Project ID") } connector := getPolicyConnector(m) @@ -199,7 +317,7 @@ func resourceNsxtPolicyProjectUpdate(d *schema.ResourceData, m interface{}) erro func resourceNsxtPolicyProjectDelete(d *schema.ResourceData, m interface{}) error { id := d.Id() if id == "" { - return fmt.Errorf("Error obtaining Project ID") + return fmt.Errorf("error obtaining Project ID") } connector := getPolicyConnector(m) diff --git a/nsxt/resource_nsxt_policy_project_ip_address_allocation.go b/nsxt/resource_nsxt_policy_project_ip_address_allocation.go new file mode 100644 index 000000000..825a95bbd --- /dev/null +++ b/nsxt/resource_nsxt_policy_project_ip_address_allocation.go @@ -0,0 +1,219 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "log" + "reflect" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + clientLayer "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects" + + utl "github.com/vmware/terraform-provider-nsxt/api/utl" + "github.com/vmware/terraform-provider-nsxt/nsxt/metadata" +) + +var projectIpAddressAllocationSchema = map[string]*metadata.ExtendedSchema{ + "nsx_id": metadata.GetExtendedSchema(getNsxIDSchema()), + "path": metadata.GetExtendedSchema(getPathSchema()), + "display_name": metadata.GetExtendedSchema(getDisplayNameSchema()), + "description": metadata.GetExtendedSchema(getDescriptionSchema()), + "revision": metadata.GetExtendedSchema(getRevisionSchema()), + "tag": metadata.GetExtendedSchema(getTagsSchema()), + "context": metadata.GetExtendedSchema(getContextSchema(true, false, false)), + "allocation_ips": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateCidrOrIPOrRange(), + Optional: true, + Computed: true, + ForceNew: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "AllocationIps", + OmitIfEmpty: true, + }, + }, + "allocation_size": { + Schema: schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "int", + SdkFieldName: "AllocationSize", + OmitIfEmpty: true, + }, + }, + "ip_block": { + Schema: schema.Schema{ + Type: schema.TypeString, + Optional: true, + ValidateFunc: validatePolicyPath(), + ForceNew: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "IpBlock", + }, + }, +} + +func resourceNsxtPolicyProjectIpAddressAllocation() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtPolicyProjectIpAddressAllocationCreate, + Read: resourceNsxtPolicyProjectIpAddressAllocationRead, + Update: resourceNsxtPolicyProjectIpAddressAllocationUpdate, + Delete: resourceNsxtPolicyProjectIpAddressAllocationDelete, + Importer: &schema.ResourceImporter{ + State: nsxtPolicyPathOnlyResourceImporter, + }, + Schema: metadata.GetSchemaFromExtendedSchema(projectIpAddressAllocationSchema), + } +} + +func resourceNsxtPolicyProjectIpAddressAllocationExists(sessionContext utl.SessionContext, id string, connector client.Connector) (bool, error) { + var err error + parents := getVpcParentsFromContext(sessionContext) + client := clientLayer.NewIpAddressAllocationsClient(connector) + _, err = client.Get(parents[0], parents[1], id) + if err == nil { + return true, nil + } + + if isNotFoundError(err) { + return false, nil + } + + return false, logAPIError("Error retrieving resource", err) +} + +func resourceNsxtPolicyProjectIpAddressAllocationCreate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id, err := getOrGenerateID2(d, m, resourceNsxtPolicyProjectIpAddressAllocationExists) + if err != nil { + return err + } + + parents := getVpcParentsFromContext(getSessionContext(d, m)) + displayName := d.Get("display_name").(string) + description := d.Get("description").(string) + tags := getPolicyTagsFromSchema(d) + + obj := model.ProjectIpAddressAllocation{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, projectIpAddressAllocationSchema, "", nil); err != nil { + return err + } + + log.Printf("[INFO] Creating ProjectIpAddressAllocation with ID %s", id) + + client := clientLayer.NewIpAddressAllocationsClient(connector) + err = client.Patch(parents[0], parents[1], id, obj) + if err != nil { + return handleCreateError("ProjectIpAddressAllocation", id, err) + } + d.SetId(id) + d.Set("nsx_id", id) + + return resourceNsxtPolicyProjectIpAddressAllocationRead(d, m) +} + +func resourceNsxtPolicyProjectIpAddressAllocationRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining ProjectIpAddressAllocation ID") + } + + client := clientLayer.NewIpAddressAllocationsClient(connector) + parents := getVpcParentsFromContext(getSessionContext(d, m)) + obj, err := client.Get(parents[0], parents[1], id) + if err != nil { + return handleReadError(d, "ProjectIpAddressAllocation", id, err) + } + + setPolicyTagsInSchema(d, obj.Tags) + d.Set("nsx_id", id) + d.Set("display_name", obj.DisplayName) + d.Set("description", obj.Description) + d.Set("revision", obj.Revision) + d.Set("path", obj.Path) + + elem := reflect.ValueOf(&obj).Elem() + return metadata.StructToSchema(elem, d, projectIpAddressAllocationSchema, "", nil) +} + +func resourceNsxtPolicyProjectIpAddressAllocationUpdate(d *schema.ResourceData, m interface{}) error { + + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining ProjectIpAddressAllocation ID") + } + + parents := getVpcParentsFromContext(getSessionContext(d, m)) + description := d.Get("description").(string) + displayName := d.Get("display_name").(string) + tags := getPolicyTagsFromSchema(d) + + revision := int64(d.Get("revision").(int)) + + obj := model.ProjectIpAddressAllocation{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + Revision: &revision, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, projectIpAddressAllocationSchema, "", nil); err != nil { + return err + } + + // Only the above attributes can be updated, others force recreation + client := clientLayer.NewIpAddressAllocationsClient(connector) + _, err := client.Update(parents[0], parents[1], id, obj) + if err != nil { + // Trigger partial update to avoid terraform updating state based on failed intent + // TODO - move this into handleUpdateError + d.Partial(true) + return handleUpdateError("ProjectIpAddressAllocation", id, err) + } + + return resourceNsxtPolicyProjectIpAddressAllocationRead(d, m) +} + +func resourceNsxtPolicyProjectIpAddressAllocationDelete(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining ProjectIpAddressAllocation ID") + } + + connector := getPolicyConnector(m) + parents := getVpcParentsFromContext(getSessionContext(d, m)) + + client := clientLayer.NewIpAddressAllocationsClient(connector) + err := client.Delete(parents[0], parents[1], id) + + if err != nil { + return handleDeleteError("ProjectIpAddressAllocation", id, err) + } + + return nil +} diff --git a/nsxt/resource_nsxt_policy_project_ip_address_allocation_test.go b/nsxt/resource_nsxt_policy_project_ip_address_allocation_test.go new file mode 100644 index 000000000..af7a5bd1d --- /dev/null +++ b/nsxt/resource_nsxt_policy_project_ip_address_allocation_test.go @@ -0,0 +1,199 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +var accTestProjectIpAddressAllocationCreateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform created", + "allocation_size": "1", +} + +var accTestProjectIpAddressAllocationUpdateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform updated", + "allocation_size": "1", +} + +func TestAccResourceNsxtPolicyProjectIpAddressAllocation_basic(t *testing.T) { + testResourceName := "nsxt_policy_project_ip_address_allocation.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccOnlyVPC(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicyProjectIpAddressAllocationCheckDestroy(state, accTestProjectIpAddressAllocationUpdateAttributes["display_name"]) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicyProjectIpAddressAllocationTemplate(true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyProjectIpAddressAllocationExists(accTestProjectIpAddressAllocationCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestProjectIpAddressAllocationCreateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestProjectIpAddressAllocationCreateAttributes["description"]), + resource.TestCheckResourceAttrSet(testResourceName, "allocation_ips"), + resource.TestCheckResourceAttr(testResourceName, "allocation_size", accTestProjectIpAddressAllocationCreateAttributes["allocation_size"]), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNsxtPolicyProjectIpAddressAllocationTemplate(false), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyProjectIpAddressAllocationExists(accTestProjectIpAddressAllocationUpdateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestProjectIpAddressAllocationUpdateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestProjectIpAddressAllocationUpdateAttributes["description"]), + resource.TestCheckResourceAttrSet(testResourceName, "allocation_ips"), + resource.TestCheckResourceAttr(testResourceName, "allocation_size", accTestProjectIpAddressAllocationUpdateAttributes["allocation_size"]), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNsxtPolicyProjectIpAddressAllocationMinimalistic(), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyProjectIpAddressAllocationExists(accTestProjectIpAddressAllocationCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtPolicyProjectIpAddressAllocation_importBasic(t *testing.T) { + name := getAccTestResourceName() + testResourceName := "nsxt_policy_project_ip_address_allocation.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccOnlyVPC(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicyProjectIpAddressAllocationCheckDestroy(state, name) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicyProjectIpAddressAllocationMinimalistic(), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccResourceNsxtPolicyImportIDRetriever(testResourceName), + }, + }, + }) +} + +func testAccNsxtPolicyProjectIpAddressAllocationExists(displayName string, resourceName string) resource.TestCheckFunc { + return func(state *terraform.State) error { + + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + + rs, ok := state.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Policy ProjectIpAddressAllocation resource %s not found in resources", resourceName) + } + + resourceID := rs.Primary.ID + if resourceID == "" { + return fmt.Errorf("Policy ProjectIpAddressAllocation resource ID not set in resources") + } + + exists, err := resourceNsxtPolicyProjectIpAddressAllocationExists(testAccGetSessionContext(), resourceID, connector) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("Policy ProjectIpAddressAllocation %s does not exist", resourceID) + } + + return nil + } +} + +func testAccNsxtPolicyProjectIpAddressAllocationCheckDestroy(state *terraform.State, displayName string) error { + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + for _, rs := range state.RootModule().Resources { + + if rs.Type != "nsxt_policy_project_ip_address_allocation" { + continue + } + + resourceID := rs.Primary.Attributes["id"] + exists, err := resourceNsxtPolicyProjectIpAddressAllocationExists(testAccGetSessionContext(), resourceID, connector) + if err == nil { + return err + } + + if exists { + return fmt.Errorf("Policy ProjectIpAddressAllocation %s still exists", displayName) + } + } + return nil +} + +func testAccNsxtPolicyProjectIpAddressAllocationTemplate(createFlow bool) string { + var attrMap map[string]string + if createFlow { + attrMap = accTestProjectIpAddressAllocationCreateAttributes + } else { + attrMap = accTestProjectIpAddressAllocationUpdateAttributes + } + return fmt.Sprintf(` +data "nsxt_policy_project" "test" { + id = "%s" +} + +resource "nsxt_policy_project_ip_address_allocation" "test" { + %s + display_name = "%s" + description = "%s" + allocation_size = %s + ip_block = data.nsxt_policy_project.test.external_ipv4_blocks[0] + tag { + scope = "scope1" + tag = "tag1" + } +} + +data "nsxt_policy_project_ip_address_allocation" "test" { + %s + allocation_ips = nsxt_policy_project_ip_address_allocation.test.allocation_ips +}`, os.Getenv("NSXT_VPC_PROJECT_ID"), testAccNsxtProjectContext(), attrMap["display_name"], attrMap["description"], attrMap["allocation_size"], testAccNsxtProjectContext()) +} + +func testAccNsxtPolicyProjectIpAddressAllocationMinimalistic() string { + return fmt.Sprintf(` +data "nsxt_policy_project" "test" { + id = "%s" +} + +resource "nsxt_policy_project_ip_address_allocation" "test" { + %s + display_name = "%s" + allocation_size = %s + ip_block = data.nsxt_policy_project.test.external_ipv4_blocks[0] +} + +data "nsxt_policy_project_ip_address_allocation" "test" { + %s + allocation_ips = nsxt_policy_project_ip_address_allocation.test.allocation_ips +}`, os.Getenv("NSXT_VPC_PROJECT_ID"), testAccNsxtProjectContext(), accTestProjectIpAddressAllocationUpdateAttributes["display_name"], accTestProjectIpAddressAllocationUpdateAttributes["allocation_size"], testAccNsxtProjectContext()) +} diff --git a/nsxt/resource_nsxt_policy_project_test.go b/nsxt/resource_nsxt_policy_project_test.go index 18a493b9d..1eb1ae279 100644 --- a/nsxt/resource_nsxt_policy_project_test.go +++ b/nsxt/resource_nsxt_policy_project_test.go @@ -4,27 +4,31 @@ package nsxt import ( + "bytes" "fmt" + "strconv" "testing" - - "github.com/vmware/terraform-provider-nsxt/nsxt/util" + "text/template" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects" + + "github.com/vmware/terraform-provider-nsxt/nsxt/util" ) var shortID = getAccTestRandomString(6) var accTestPolicyProjectCreateAttributes = map[string]string{ - "display_name": getAccTestResourceName(), - "description": "terraform created", - "short_id": shortID, + "DisplayName": getAccTestResourceName(), + "Description": "terraform created", + "ShortId": shortID, } var accTestPolicyProjectUpdateAttributes = map[string]string{ - "display_name": getAccTestResourceName(), - "description": "terraform updated", - "short_id": shortID, + "DisplayName": getAccTestResourceName(), + "Description": "terraform updated", + "ShortId": shortID, } func getExpectedSiteInfoCount(t *testing.T) string { @@ -47,111 +51,296 @@ func getExpectedSiteInfoCount(t *testing.T) string { return "0" } +func runChecksNsx410(testResourceName string, attributes map[string]string, expectedValues map[string]string) resource.TestCheckFunc { + return resource.ComposeTestCheckFunc( + testAccNsxtPolicyProjectExists(accTestPolicyProjectCreateAttributes["DisplayName"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", attributes["DisplayName"]), + resource.TestCheckResourceAttr(testResourceName, "description", attributes["Description"]), + resource.TestCheckResourceAttr(testResourceName, "short_id", attributes["ShortId"]), + //TODO: add site info validation + resource.TestCheckResourceAttr(testResourceName, "site_info.#", expectedValues["site_count"]), + resource.TestCheckResourceAttr(testResourceName, "tier0_gateway_paths.#", expectedValues["t0_count"]), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ) +} + +func runChecksNsx411(testResourceName string, expectedValues map[string]string) resource.TestCheckFunc { + return resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(testResourceName, "external_ipv4_blocks.#", expectedValues["ip_block_count"]), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + ) +} + +func runChecksNsx420(testResourceName string, expectedValues map[string]string) resource.TestCheckFunc { + return resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(testResourceName, "activate_default_dfw_rules", expectedValues["activate_default_dfw_rules"]), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + ) +} + +func runChecksNsx900(testResourceName string, expectedValues map[string]string) resource.TestCheckFunc { + return resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(testResourceName, "tgw_external_connections.#", expectedValues["tgw_ext_conn_count"]), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + ) +} + func TestAccResourceNsxtPolicyProject_basic(t *testing.T) { testResourceName := "nsxt_policy_project.test" - + siteCount := getExpectedSiteInfoCount(t) + expectedValuesStep1 := map[string]string{ + "t0_count": "1", + "site_count": siteCount, + } + expectedValuesStep2 := expectedValuesStep1 + expectedValuesStep3 := map[string]string{ + "t0_count": "0", + "site_count": siteCount, + } resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) testAccOnlyLocalManager(t) testAccNSXVersion(t, "4.1.0") + testAccNSXVersionLessThan(t, "4.1.1") }, Providers: testAccProviders, CheckDestroy: func(state *terraform.State) error { - return testAccNsxtPolicyProjectCheckDestroy(state, accTestPolicyProjectUpdateAttributes["display_name"]) + return testAccNsxtPolicyProjectCheckDestroy(state, accTestPolicyProjectUpdateAttributes["DisplayName"]) }, Steps: []resource.TestStep{ { - Config: testAccNsxtPolicyProjectTemplate(true, true), + Config: testAccNsxtPolicyProjectTemplate410(true, true), + Check: runChecksNsx410(testResourceName, accTestPolicyProjectCreateAttributes, expectedValuesStep1), + }, + { + Config: testAccNsxtPolicyProjectTemplate410(false, true), + Check: runChecksNsx410(testResourceName, accTestPolicyProjectUpdateAttributes, expectedValuesStep2), + }, + { + Config: testAccNsxtPolicyProjectTemplate410(false, false), + Check: runChecksNsx410(testResourceName, accTestPolicyProjectUpdateAttributes, expectedValuesStep3), + }, + { + Config: testAccNsxtPolicyProjectMinimalistic(), Check: resource.ComposeTestCheckFunc( - testAccNsxtPolicyProjectExists(accTestPolicyProjectCreateAttributes["display_name"], testResourceName), - resource.TestCheckResourceAttr(testResourceName, "display_name", accTestPolicyProjectCreateAttributes["display_name"]), - resource.TestCheckResourceAttr(testResourceName, "description", accTestPolicyProjectCreateAttributes["description"]), - resource.TestCheckResourceAttr(testResourceName, "short_id", accTestPolicyProjectCreateAttributes["short_id"]), - //TODO: add site info validation - resource.TestCheckResourceAttr(testResourceName, "site_info.#", getExpectedSiteInfoCount(t)), - resource.TestCheckResourceAttr(testResourceName, "tier0_gateway_paths.#", "1"), - + testAccNsxtPolicyProjectExists(accTestPolicyProjectCreateAttributes["DisplayName"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "description", ""), resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), resource.TestCheckResourceAttrSet(testResourceName, "path"), resource.TestCheckResourceAttrSet(testResourceName, "revision"), - resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), ), }, + }, + }) +} + +func TestAccResourceNsxtPolicyProject_411basic(t *testing.T) { + testResourceName := "nsxt_policy_project.test" + siteCount := getExpectedSiteInfoCount(t) + expectedValuesStep1 := map[string]string{ + "t0_count": "1", + "ip_block_count": "1", + "site_count": siteCount, + } + expectedValuesStep2 := expectedValuesStep1 + expectedValuesStep3 := map[string]string{ + "t0_count": "0", + "ip_block_count": "0", + "site_count": siteCount, + } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccOnlyLocalManager(t) + testAccNSXVersion(t, "4.1.1") + testAccNSXVersionLessThan(t, "4.2.0") + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicyProjectCheckDestroy(state, accTestPolicyProjectUpdateAttributes["DisplayName"]) + }, + Steps: []resource.TestStep{ { - Config: testAccNsxtPolicyProjectTemplate(false, true), + Config: testAccNsxtPolicyProjectTemplate411(true, true, true), Check: resource.ComposeTestCheckFunc( - testAccNsxtPolicyProjectExists(accTestPolicyProjectUpdateAttributes["display_name"], testResourceName), - resource.TestCheckResourceAttr(testResourceName, "display_name", accTestPolicyProjectUpdateAttributes["display_name"]), - resource.TestCheckResourceAttr(testResourceName, "description", accTestPolicyProjectUpdateAttributes["description"]), - resource.TestCheckResourceAttr(testResourceName, "short_id", accTestPolicyProjectCreateAttributes["short_id"]), - resource.TestCheckResourceAttr(testResourceName, "site_info.#", getExpectedSiteInfoCount(t)), - resource.TestCheckResourceAttr(testResourceName, "tier0_gateway_paths.#", "1"), - - resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), - resource.TestCheckResourceAttrSet(testResourceName, "path"), - resource.TestCheckResourceAttrSet(testResourceName, "revision"), - resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + runChecksNsx410(testResourceName, accTestPolicyProjectCreateAttributes, expectedValuesStep1), + runChecksNsx411(testResourceName, expectedValuesStep1), ), }, { - Config: testAccNsxtPolicyProjectTemplate(false, false), + Config: testAccNsxtPolicyProjectTemplate411(false, true, true), Check: resource.ComposeTestCheckFunc( - testAccNsxtPolicyProjectExists(accTestPolicyProjectUpdateAttributes["display_name"], testResourceName), - resource.TestCheckResourceAttr(testResourceName, "display_name", accTestPolicyProjectUpdateAttributes["display_name"]), - resource.TestCheckResourceAttr(testResourceName, "description", accTestPolicyProjectUpdateAttributes["description"]), - resource.TestCheckResourceAttr(testResourceName, "short_id", accTestPolicyProjectCreateAttributes["short_id"]), - resource.TestCheckResourceAttr(testResourceName, "site_info.#", getExpectedSiteInfoCount(t)), - resource.TestCheckResourceAttr(testResourceName, "tier0_gateway_paths.#", "0"), + runChecksNsx410(testResourceName, accTestPolicyProjectUpdateAttributes, expectedValuesStep2), + runChecksNsx411(testResourceName, expectedValuesStep2), + ), + }, + { + Config: testAccNsxtPolicyProjectTemplate411(false, false, false), + Check: resource.ComposeTestCheckFunc( + runChecksNsx410(testResourceName, accTestPolicyProjectUpdateAttributes, expectedValuesStep3), + runChecksNsx411(testResourceName, expectedValuesStep3), + ), + }, + }, + }) +} - resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), - resource.TestCheckResourceAttrSet(testResourceName, "path"), - resource.TestCheckResourceAttrSet(testResourceName, "revision"), - resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), +func TestAccResourceNsxtPolicyProject_420basic(t *testing.T) { + testResourceName := "nsxt_policy_project.test" + siteCount := getExpectedSiteInfoCount(t) + expectedValuesStep1 := map[string]string{ + "t0_count": "1", + "ip_block_count": "1", + "site_count": siteCount, + "activate_default_dfw_rules": "false", + } + expectedValuesStep2 := expectedValuesStep1 + expectedValuesStep3 := map[string]string{ + "t0_count": "0", + "ip_block_count": "0", + "site_count": siteCount, + "activate_default_dfw_rules": "true", + } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccOnlyLocalManager(t) + testAccNSXVersion(t, "4.2.0") + testAccNSXVersionLessThan(t, "9.0.0") + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicyProjectCheckDestroy(state, accTestPolicyProjectUpdateAttributes["DisplayName"]) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicyProjectTemplate420(true, true, true, false), + Check: resource.ComposeTestCheckFunc( + runChecksNsx410(testResourceName, accTestPolicyProjectCreateAttributes, expectedValuesStep1), + runChecksNsx411(testResourceName, expectedValuesStep1), + runChecksNsx420(testResourceName, expectedValuesStep1), ), }, { - Config: testAccNsxtPolicyProjectMinimalistic(), + Config: testAccNsxtPolicyProjectTemplate420(false, true, true, false), Check: resource.ComposeTestCheckFunc( - testAccNsxtPolicyProjectExists(accTestPolicyProjectCreateAttributes["display_name"], testResourceName), - resource.TestCheckResourceAttr(testResourceName, "description", ""), - resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), - resource.TestCheckResourceAttrSet(testResourceName, "path"), - resource.TestCheckResourceAttrSet(testResourceName, "revision"), - resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + runChecksNsx410(testResourceName, accTestPolicyProjectUpdateAttributes, expectedValuesStep2), + runChecksNsx411(testResourceName, expectedValuesStep2), + runChecksNsx420(testResourceName, expectedValuesStep2), + ), + }, + { + Config: testAccNsxtPolicyProjectTemplate420(false, false, false, true), + Check: resource.ComposeTestCheckFunc( + runChecksNsx410(testResourceName, accTestPolicyProjectUpdateAttributes, expectedValuesStep3), + runChecksNsx411(testResourceName, expectedValuesStep3), + runChecksNsx420(testResourceName, expectedValuesStep3), ), }, }, }) } -func TestAccResourceNsxtPolicyProject_withIPBlock(t *testing.T) { +func TestAccResourceNsxtPolicyProject_900basic(t *testing.T) { testResourceName := "nsxt_policy_project.test" - resourceName := getAccTestResourceName() + siteCount := getExpectedSiteInfoCount(t) + expectedValuesStep1 := map[string]string{ + "t0_count": "1", + "ip_block_count": "1", + "tgw_ext_conn_count": "1", + "activate_default_dfw_rules": "true", + "site_count": siteCount, + } + expectedValuesStep2 := map[string]string{ + "t0_count": "1", + "ip_block_count": "0", + "tgw_ext_conn_count": "1", + "activate_default_dfw_rules": "false", + "site_count": siteCount, + } + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) testAccOnlyLocalManager(t) - testAccNSXVersion(t, "4.1.2") + testAccNSXVersion(t, "9.0.0") }, Providers: testAccProviders, CheckDestroy: func(state *terraform.State) error { - return testAccNsxtPolicyProjectCheckDestroy(state, resourceName) + return testAccNsxtPolicyProjectCheckDestroy(state, accTestPolicyProjectUpdateAttributes["DisplayName"]) }, Steps: []resource.TestStep{ { - Config: testAccNsxtPolicyProjectTemplateWithIPBlock(resourceName), + // Create: Set T0, Ext GW connection, Ext IPv4 Block, activate default DFW + Config: testAccNsxtPolicyProjectTemplate900(true, true, true, true), + Check: resource.ComposeTestCheckFunc( + runChecksNsx410(testResourceName, accTestPolicyProjectCreateAttributes, expectedValuesStep1), + runChecksNsx411(testResourceName, expectedValuesStep1), + runChecksNsx420(testResourceName, expectedValuesStep1), + runChecksNsx900(testResourceName, expectedValuesStep1), + ), + }, + { + // Update: Set T0, Ext GW connection, No Ext IPv4 Block, disable default DFW + Config: testAccNsxtPolicyProjectTemplate900(false, true, false, false), Check: resource.ComposeTestCheckFunc( - testAccNsxtPolicyProjectExists(accTestPolicyProjectCreateAttributes["display_name"], testResourceName), - resource.TestCheckResourceAttr(testResourceName, "display_name", resourceName), - //TODO: add site info validation - resource.TestCheckResourceAttr(testResourceName, "tier0_gateway_paths.#", "1"), - resource.TestCheckResourceAttr(testResourceName, "external_ipv4_blocks.#", "1"), + runChecksNsx410(testResourceName, accTestPolicyProjectUpdateAttributes, expectedValuesStep2), + runChecksNsx411(testResourceName, expectedValuesStep2), + runChecksNsx420(testResourceName, expectedValuesStep2), + runChecksNsx900(testResourceName, expectedValuesStep2), + ), + }, + }, + }) +} - resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), - resource.TestCheckResourceAttrSet(testResourceName, "path"), - resource.TestCheckResourceAttrSet(testResourceName, "revision"), - resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), +func TestAccResourceNsxtPolicyProject_900defaultSecurityProfile(t *testing.T) { + testResourceName := "nsxt_policy_project.test" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccOnlyLocalManager(t) + testAccNSXVersion(t, "9.0.0") + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicyProjectCheckDestroy(state, accTestPolicyProjectCreateAttributes["DisplayName"]) + }, + Steps: []resource.TestStep{ + { + // Create: Set T0, Ext GW connection, Ext IPv4 Block, activate default DFW + Config: testAccNsxtPolicyProjectMinimalistic(), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyProjectGetSecurityProfileNSEnabled(testResourceName, false), + ), + }, + { + // Update: Set T0, Ext GW connection, No Ext IPv4 Block, disable default DFW + Config: testAccNsxtPolicyProjectDefaultSecurityPolicy(false), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyProjectGetSecurityProfileNSEnabled(testResourceName, false), + ), + }, + { + // Update: Set T0, Ext GW connection, No Ext IPv4 Block, disable default DFW + Config: testAccNsxtPolicyProjectDefaultSecurityPolicy(true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyProjectGetSecurityProfileNSEnabled(testResourceName, true), + ), + }, + { + // Update: Set T0, Ext GW connection, No Ext IPv4 Block, disable default DFW + Config: testAccNsxtPolicyProjectDefaultSecurityPolicy(false), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyProjectGetSecurityProfileNSEnabled(testResourceName, false), ), }, }, @@ -185,6 +374,32 @@ func TestAccResourceNsxtPolicyProject_importBasic(t *testing.T) { }) } +func testAccNsxtPolicyProjectGetSecurityProfileNSEnabled(resourceName string, expectedVal bool) resource.TestCheckFunc { + return func(state *terraform.State) error { + + rs, ok := state.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Policy Project resource %s not found in resources", resourceName) + } + + resourceID := rs.Primary.ID + if resourceID == "" { + return fmt.Errorf("Policy Project resource ID not set in resources") + } + + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + client := projects.NewVpcSecurityProfilesClient(connector) + obj, err := client.Get(defaultOrgID, resourceID, "default") + if err != nil { + return err + } + if *obj.NorthSouthFirewall.Enabled != expectedVal { + return fmt.Errorf("expected NorthSouthFirewall to be %v, isntead status is %v", *obj.NorthSouthFirewall.Enabled, expectedVal) + } + return nil + } +} + func testAccNsxtPolicyProjectExists(displayName string, resourceName string) resource.TestCheckFunc { return func(state *terraform.State) error { @@ -233,34 +448,125 @@ func testAccNsxtPolicyProjectCheckDestroy(state *terraform.State, displayName st return nil } -func testAccNsxtPolicyProjectTemplate(createFlow, includeT0GW bool) string { +// TODO: Visibility should not be set for 410 tests (attribute exists, different enum values since 411) +var templateData = ` +data "nsxt_policy_tier0_gateway" "test" { + display_name = "{{.T0Name}}" +} + +resource "nsxt_policy_ip_block" "test_ip_block" { + display_name = "test_ip_block" + cidr = "10.20.0.0/16" + {{if .SetIpBlockVisibility}}visibility = "EXTERNAL"{{end}} +} + +{{if .ExternalTGWConnectionPath}}resource "nsxt_policy_gateway_connection" "test_gw_conn" { + display_name = "test_gw_conn" + tier0_path = {{.T0GwPath}} + aggregate_routes = ["192.168.240.0/24"] +}{{end}} + +resource "nsxt_policy_project" "test" { + display_name = "{{.DisplayName}}" + description = "{{.Description}}" + {{if .ShortId}}short_id = "{{.ShortId}}"{{end}} + {{if .T0GwPath}}tier0_gateway_paths = [{{.T0GwPath}}]{{end}} + {{if .ExternalIPv4BlockPath}}external_ipv4_blocks = [{{.ExternalIPv4BlockPath}}]{{end}} + {{if .ExternalTGWConnectionPath}}tgw_external_connections = [{{.ExternalTGWConnectionPath}}]{{end}} + {{if .ActivateDefaultDfwRules}}activate_default_dfw_rules = {{.ActivateDefaultDfwRules}}{{end}} + tag { + scope = "scope1" + tag = "tag1" + } +} +` + +func getBasicAttrMap(createFlow, includeT0GW bool) map[string]string { var attrMap map[string]string - shortIDSpec := "" - t0GW := "" if createFlow { attrMap = accTestPolicyProjectCreateAttributes - shortIDSpec = fmt.Sprintf("short_id = \"%s\"", attrMap["short_id"]) } else { - attrMap = accTestPolicyProjectUpdateAttributes + // do not pass short_id on update + attrMap = make(map[string]string) + attrMap["DisplayName"] = accTestPolicyProjectUpdateAttributes["DisplayName"] + attrMap["Description"] = accTestPolicyProjectUpdateAttributes["Description"] } if includeT0GW { - t0GW = "tier0_gateway_paths = [data.nsxt_policy_tier0_gateway.test.path]" + attrMap["T0GwPath"] = "data.nsxt_policy_tier0_gateway.test.path" } - return fmt.Sprintf(` -data "nsxt_policy_tier0_gateway" "test" { - display_name = "%s" + attrMap["T0Name"] = getTier0RouterName() + return attrMap } -resource "nsxt_policy_project" "test" { - display_name = "%s" - description = "%s" - %s - %s - tag { - scope = "scope1" - tag = "tag1" - } -}`, getTier0RouterName(), attrMap["display_name"], attrMap["description"], shortIDSpec, t0GW) +func testAccNsxtPolicyProjectTemplate410(createFlow, includeT0GW bool) string { + attrMap := getBasicAttrMap(createFlow, includeT0GW) + buffer := new(bytes.Buffer) + tmpl, err := template.New("testAaccNsxtPolicyProject").Parse(templateData) + if err != nil { + panic(err) + } + err = tmpl.Execute(buffer, attrMap) + if err != nil { + panic(err) + } + return buffer.String() +} + +func testAccNsxtPolicyProjectTemplate411(createFlow, includeT0GW, includeExternalIPv4Block bool) string { + attrMap := getBasicAttrMap(createFlow, includeT0GW) + if includeExternalIPv4Block { + attrMap["ExternalIPv4BlockPath"] = "nsxt_policy_ip_block.test_ip_block.path" + } + attrMap["SetIpBlockVisibility"] = "true" + buffer := new(bytes.Buffer) + tmpl, err := template.New("testAaccNsxtPolicyProject").Parse(templateData) + if err != nil { + panic(err) + } + err = tmpl.Execute(buffer, attrMap) + if err != nil { + panic(err) + } + return buffer.String() +} + +func testAccNsxtPolicyProjectTemplate420(createFlow, includeT0GW, includeExternalIPv4Block, activateDefaultDfwRules bool) string { + attrMap := getBasicAttrMap(createFlow, includeT0GW) + if includeExternalIPv4Block { + attrMap["ExternalIPv4BlockPath"] = "nsxt_policy_ip_block.test_ip_block.path" + } + attrMap["SetIpBlockVisibility"] = "true" + attrMap["ActivateDefaultDfwRules"] = strconv.FormatBool(activateDefaultDfwRules) + buffer := new(bytes.Buffer) + tmpl, err := template.New("testAaccNsxtPolicyProject").Parse(templateData) + if err != nil { + panic(err) + } + err = tmpl.Execute(buffer, attrMap) + if err != nil { + panic(err) + } + return buffer.String() +} + +func testAccNsxtPolicyProjectTemplate900(createFlow, includeT0GW, includeExternalIPv4Block, activateDefaultDfwRules bool) string { + attrMap := getBasicAttrMap(createFlow, includeT0GW) + if includeExternalIPv4Block { + attrMap["ExternalIPv4BlockPath"] = "nsxt_policy_ip_block.test_ip_block.path" + } + attrMap["SetIpBlockVisibility"] = "true" + attrMap["ActivateDefaultDfwRules"] = strconv.FormatBool(activateDefaultDfwRules) + attrMap["ExternalTGWConnectionPath"] = "nsxt_policy_gateway_connection.test_gw_conn.path" + buffer := new(bytes.Buffer) + tmpl, err := template.New("testAaccNsxtPolicyProject").Parse(templateData) + if err != nil { + panic(err) + } + err = tmpl.Execute(buffer, attrMap) + if err != nil { + panic(err) + } + return buffer.String() } func testAccNsxtPolicyProjectTemplateWithIPBlock(displayName string) string { @@ -289,5 +595,18 @@ func testAccNsxtPolicyProjectMinimalistic() string { resource "nsxt_policy_project" "test" { display_name = "%s" -}`, accTestPolicyProjectUpdateAttributes["display_name"]) +}`, accTestPolicyProjectUpdateAttributes["DisplayName"]) +} + +func testAccNsxtPolicyProjectDefaultSecurityPolicy(enabled bool) string { + return fmt.Sprintf(` +resource "nsxt_policy_project" "test" { + display_name = "%s" + default_security_profile { + north_south_firewall { + enabled = %s + } + } + +}`, accTestPolicyProjectCreateAttributes["DisplayName"], strconv.FormatBool(enabled)) } diff --git a/nsxt/resource_nsxt_policy_security_policy.go b/nsxt/resource_nsxt_policy_security_policy.go index a84fad80d..abdd4cb4d 100644 --- a/nsxt/resource_nsxt_policy_security_policy.go +++ b/nsxt/resource_nsxt_policy_security_policy.go @@ -62,7 +62,10 @@ func resourceNsxtPolicySecurityPolicyExistsPartial(domainName string) func(sessi } func policySecurityPolicyBuildAndPatch(d *schema.ResourceData, m interface{}, id string, createFlow, withRule, isVPC bool) error { - obj := parentSecurityPolicySchemaToModel(d, id) + obj, structErr := parentSecurityPolicySchemaToModel(d, id) + if structErr != nil { + return structErr + } domain := "" if !isVPC { domain = d.Get("domain").(string) diff --git a/nsxt/resource_nsxt_policy_segment_test.go b/nsxt/resource_nsxt_policy_segment_test.go index 518da8b42..e8b5794f2 100644 --- a/nsxt/resource_nsxt_policy_segment_test.go +++ b/nsxt/resource_nsxt_policy_segment_test.go @@ -1021,7 +1021,7 @@ resource "nsxt_policy_segment" "test" { next_hop = "2.3.1.3" } dhcp_option_121 { - network = "3.1.1.21/24" + network = "3.1.1.0/24" next_hop = "3.3.1.3" } dhcp_generic_option { diff --git a/nsxt/resource_nsxt_policy_share.go b/nsxt/resource_nsxt_policy_share.go index b98d6099a..3f0456757 100644 --- a/nsxt/resource_nsxt_policy_share.go +++ b/nsxt/resource_nsxt_policy_share.go @@ -26,7 +26,7 @@ func resourceNsxtPolicyShare() *schema.Resource { Update: resourceNsxtPolicyShareUpdate, Delete: resourceNsxtPolicyShareDelete, Importer: &schema.ResourceImporter{ - State: nsxtPolicyPathResourceImporter, + State: nsxtPolicyPathOnlyResourceImporter, }, Schema: map[string]*schema.Schema{ diff --git a/nsxt/resource_nsxt_policy_transit_gateway.go b/nsxt/resource_nsxt_policy_transit_gateway.go new file mode 100644 index 000000000..8d98efbf9 --- /dev/null +++ b/nsxt/resource_nsxt_policy_transit_gateway.go @@ -0,0 +1,201 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "log" + "reflect" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + clientLayer "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects" + + utl "github.com/vmware/terraform-provider-nsxt/api/utl" + "github.com/vmware/terraform-provider-nsxt/nsxt/metadata" +) + +var transitGatewaySchema = map[string]*metadata.ExtendedSchema{ + "nsx_id": metadata.GetExtendedSchema(getNsxIDSchema()), + "path": metadata.GetExtendedSchema(getPathSchema()), + "display_name": metadata.GetExtendedSchema(getDisplayNameSchema()), + "description": metadata.GetExtendedSchema(getDescriptionSchema()), + "revision": metadata.GetExtendedSchema(getRevisionSchema()), + "tag": metadata.GetExtendedSchema(getTagsSchema()), + "context": metadata.GetExtendedSchema(getContextSchema(true, false, false)), + "transit_subnets": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedSchema{ + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateIPCidr(), + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + }, + }, + Optional: true, + Computed: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "TransitSubnets", + }, + }, +} + +func resourceNsxtPolicyTransitGateway() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtPolicyTransitGatewayCreate, + Read: resourceNsxtPolicyTransitGatewayRead, + Update: resourceNsxtPolicyTransitGatewayUpdate, + Delete: resourceNsxtPolicyTransitGatewayDelete, + Importer: &schema.ResourceImporter{ + State: nsxtPolicyPathOnlyResourceImporter, + }, + Schema: metadata.GetSchemaFromExtendedSchema(transitGatewaySchema), + } +} + +func resourceNsxtPolicyTransitGatewayExists(sessionContext utl.SessionContext, id string, connector client.Connector) (bool, error) { + var err error + parents := getVpcParentsFromContext(sessionContext) + client := clientLayer.NewTransitGatewaysClient(connector) + _, err = client.Get(parents[0], parents[1], id) + if err == nil { + return true, nil + } + + if isNotFoundError(err) { + return false, nil + } + + return false, logAPIError("Error retrieving resource", err) +} + +func resourceNsxtPolicyTransitGatewayCreate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id, err := getOrGenerateID2(d, m, resourceNsxtPolicyTransitGatewayExists) + if err != nil { + return err + } + + parents := getVpcParentsFromContext(getSessionContext(d, m)) + displayName := d.Get("display_name").(string) + description := d.Get("description").(string) + tags, tagErr := getValidatedTagsFromSchema(d) + if tagErr != nil { + return tagErr + } + + obj := model.TransitGateway{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, transitGatewaySchema, "", nil); err != nil { + return err + } + + log.Printf("[INFO] Creating TransitGateway with ID %s", id) + + client := clientLayer.NewTransitGatewaysClient(connector) + err = client.Patch(parents[0], parents[1], id, obj) + if err != nil { + return handleCreateError("TransitGateway", id, err) + } + d.SetId(id) + d.Set("nsx_id", id) + + return resourceNsxtPolicyTransitGatewayRead(d, m) +} + +func resourceNsxtPolicyTransitGatewayRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining TransitGateway ID") + } + + client := clientLayer.NewTransitGatewaysClient(connector) + parents := getVpcParentsFromContext(getSessionContext(d, m)) + obj, err := client.Get(parents[0], parents[1], id) + if err != nil { + return handleReadError(d, "TransitGateway", id, err) + } + + setPolicyTagsInSchema(d, obj.Tags) + d.Set("nsx_id", id) + d.Set("display_name", obj.DisplayName) + d.Set("description", obj.Description) + d.Set("revision", obj.Revision) + d.Set("path", obj.Path) + + elem := reflect.ValueOf(&obj).Elem() + return metadata.StructToSchema(elem, d, transitGatewaySchema, "", nil) +} + +func resourceNsxtPolicyTransitGatewayUpdate(d *schema.ResourceData, m interface{}) error { + + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining TransitGateway ID") + } + + parents := getVpcParentsFromContext(getSessionContext(d, m)) + description := d.Get("description").(string) + displayName := d.Get("display_name").(string) + tags, tagErr := getValidatedTagsFromSchema(d) + if tagErr != nil { + return tagErr + } + + revision := int64(d.Get("revision").(int)) + + obj := model.TransitGateway{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + Revision: &revision, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, transitGatewaySchema, "", nil); err != nil { + return err + } + client := clientLayer.NewTransitGatewaysClient(connector) + _, err := client.Update(parents[0], parents[1], id, obj) + if err != nil { + return handleUpdateError("TransitGateway", id, err) + } + + return resourceNsxtPolicyTransitGatewayRead(d, m) +} + +func resourceNsxtPolicyTransitGatewayDelete(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining TransitGateway ID") + } + + connector := getPolicyConnector(m) + parents := getVpcParentsFromContext(getSessionContext(d, m)) + + client := clientLayer.NewTransitGatewaysClient(connector) + err := client.Delete(parents[0], parents[1], id) + + if err != nil { + return handleDeleteError("TransitGateway", id, err) + } + + return nil +} diff --git a/nsxt/resource_nsxt_policy_transit_gateway_attachment.go b/nsxt/resource_nsxt_policy_transit_gateway_attachment.go new file mode 100644 index 000000000..4bb27b353 --- /dev/null +++ b/nsxt/resource_nsxt_policy_transit_gateway_attachment.go @@ -0,0 +1,206 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "log" + "reflect" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + clientLayer "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/transit_gateways" + + utl "github.com/vmware/terraform-provider-nsxt/api/utl" + "github.com/vmware/terraform-provider-nsxt/nsxt/metadata" +) + +var transitGatewayAttachmentSchema = map[string]*metadata.ExtendedSchema{ + "nsx_id": metadata.GetExtendedSchema(getNsxIDSchema()), + "path": metadata.GetExtendedSchema(getPathSchema()), + "display_name": metadata.GetExtendedSchema(getDisplayNameSchema()), + "description": metadata.GetExtendedSchema(getDescriptionSchema()), + "revision": metadata.GetExtendedSchema(getRevisionSchema()), + "tag": metadata.GetExtendedSchema(getTagsSchema()), + "parent_path": metadata.GetExtendedSchema(getPolicyPathSchema(true, true, "Policy path of the parent")), + "connection_path": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validatePolicyPath(), + Required: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "ConnectionPath", + OmitIfEmpty: true, + }, + }, +} + +func resourceNsxtPolicyTransitGatewayAttachment() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtPolicyTransitGatewayAttachmentCreate, + Read: resourceNsxtPolicyTransitGatewayAttachmentRead, + Update: resourceNsxtPolicyTransitGatewayAttachmentUpdate, + Delete: resourceNsxtPolicyTransitGatewayAttachmentDelete, + Importer: &schema.ResourceImporter{ + State: nsxtParentPathResourceImporter, + }, + Schema: metadata.GetSchemaFromExtendedSchema(transitGatewayAttachmentSchema), + } +} + +func resourceNsxtPolicyTransitGatewayAttachmentExists(sessionContext utl.SessionContext, parentPath string, id string, connector client.Connector) (bool, error) { + var err error + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 3) + if pathErr != nil { + return false, pathErr + } + client := clientLayer.NewAttachmentsClient(connector) + _, err = client.Get(parents[0], parents[1], parents[2], id) + if err == nil { + return true, nil + } + + if isNotFoundError(err) { + return false, nil + } + + return false, logAPIError("Error retrieving resource", err) +} + +func resourceNsxtPolicyTransitGatewayAttachmentCreate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id, err := getOrGenerateIDWithParent(d, m, resourceNsxtPolicyTransitGatewayAttachmentExists) + if err != nil { + return err + } + + parentPath := d.Get("parent_path").(string) + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 3) + if pathErr != nil { + return pathErr + } + displayName := d.Get("display_name").(string) + description := d.Get("description").(string) + tags := getPolicyTagsFromSchema(d) + + obj := model.TransitGatewayAttachment{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, transitGatewayAttachmentSchema, "", nil); err != nil { + return err + } + + log.Printf("[INFO] Creating TransitGatewayAttachment with ID %s", id) + + client := clientLayer.NewAttachmentsClient(connector) + err = client.Patch(parents[0], parents[1], parents[2], id, obj) + if err != nil { + return handleCreateError("TransitGatewayAttachment", id, err) + } + d.SetId(id) + d.Set("nsx_id", id) + + return resourceNsxtPolicyTransitGatewayAttachmentRead(d, m) +} + +func resourceNsxtPolicyTransitGatewayAttachmentRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining TransitGatewayAttachment ID") + } + + client := clientLayer.NewAttachmentsClient(connector) + parentPath := d.Get("parent_path").(string) + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 3) + if pathErr != nil { + return pathErr + } + obj, err := client.Get(parents[0], parents[1], parents[2], id) + if err != nil { + return handleReadError(d, "TransitGatewayAttachment", id, err) + } + + setPolicyTagsInSchema(d, obj.Tags) + d.Set("nsx_id", id) + d.Set("display_name", obj.DisplayName) + d.Set("description", obj.Description) + d.Set("revision", obj.Revision) + d.Set("path", obj.Path) + + elem := reflect.ValueOf(&obj).Elem() + return metadata.StructToSchema(elem, d, transitGatewayAttachmentSchema, "", nil) +} + +func resourceNsxtPolicyTransitGatewayAttachmentUpdate(d *schema.ResourceData, m interface{}) error { + + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining TransitGatewayAttachment ID") + } + + parentPath := d.Get("parent_path").(string) + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 3) + if pathErr != nil { + return pathErr + } + description := d.Get("description").(string) + displayName := d.Get("display_name").(string) + tags := getPolicyTagsFromSchema(d) + + revision := int64(d.Get("revision").(int)) + + obj := model.TransitGatewayAttachment{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + Revision: &revision, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, transitGatewayAttachmentSchema, "", nil); err != nil { + return err + } + client := clientLayer.NewAttachmentsClient(connector) + _, err := client.Update(parents[0], parents[1], parents[2], id, obj) + if err != nil { + return handleUpdateError("TransitGatewayAttachment", id, err) + } + + return resourceNsxtPolicyTransitGatewayAttachmentRead(d, m) +} + +func resourceNsxtPolicyTransitGatewayAttachmentDelete(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining TransitGatewayAttachment ID") + } + + connector := getPolicyConnector(m) + parentPath := d.Get("parent_path").(string) + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 3) + if pathErr != nil { + return pathErr + } + + client := clientLayer.NewAttachmentsClient(connector) + err := client.Delete(parents[0], parents[1], parents[2], id) + + if err != nil { + return handleDeleteError("TransitGatewayAttachment", id, err) + } + + return nil +} diff --git a/nsxt/resource_nsxt_policy_transit_gateway_attachment_test.go b/nsxt/resource_nsxt_policy_transit_gateway_attachment_test.go new file mode 100644 index 000000000..ab3ea6aa9 --- /dev/null +++ b/nsxt/resource_nsxt_policy_transit_gateway_attachment_test.go @@ -0,0 +1,187 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +var dependantEntityName = getAccTestResourceName() + +var accTestPolicyTransitGatewayAttachmentCreateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform created", +} + +var accTestPolicyTransitGatewayAttachmentUpdateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform updated", +} + +func TestAccResourceNsxtPolicyTransitGatewayAttachment_basic(t *testing.T) { + testResourceName := "nsxt_policy_transit_gateway_attachment.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicyTransitGatewayAttachmentCheckDestroy(state, accTestPolicyTransitGatewayAttachmentUpdateAttributes["display_name"]) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicyTransitGatewayAttachmentTemplate(true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyTransitGatewayAttachmentExists(accTestPolicyTransitGatewayAttachmentCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestPolicyTransitGatewayAttachmentCreateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestPolicyTransitGatewayAttachmentCreateAttributes["description"]), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNsxtPolicyTransitGatewayAttachmentTemplate(false), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyTransitGatewayAttachmentExists(accTestPolicyTransitGatewayAttachmentUpdateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestPolicyTransitGatewayAttachmentUpdateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestPolicyTransitGatewayAttachmentUpdateAttributes["description"]), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtPolicyTransitGatewayAttachment_importBasic(t *testing.T) { + name := getAccTestResourceName() + testResourceName := "nsxt_policy_transit_gateway_attachment.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicyTransitGatewayAttachmentCheckDestroy(state, name) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicyTransitGatewayAttachmentTemplate(true), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccResourceNsxtPolicyImportIDRetriever(testResourceName), + }, + }, + }) +} + +func testAccNsxtPolicyTransitGatewayAttachmentExists(displayName string, resourceName string) resource.TestCheckFunc { + return func(state *terraform.State) error { + + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + + rs, ok := state.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Policy TransitGatewayAttachment resource %s not found in resources", resourceName) + } + + resourceID := rs.Primary.ID + if resourceID == "" { + return fmt.Errorf("Policy TransitGatewayAttachment resource ID not set in resources") + } + + parentPath := rs.Primary.Attributes["parent_path"] + exists, err := resourceNsxtPolicyTransitGatewayAttachmentExists(testAccGetSessionContext(), parentPath, resourceID, connector) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("Policy TransitGatewayAttachment %s does not exist", resourceID) + } + + return nil + } +} + +func testAccNsxtPolicyTransitGatewayAttachmentCheckDestroy(state *terraform.State, displayName string) error { + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + for _, rs := range state.RootModule().Resources { + + if rs.Type != "nsxt_policy_transit_gateway_attachment" { + continue + } + + resourceID := rs.Primary.Attributes["id"] + parentPath := rs.Primary.Attributes["parent_path"] + exists, err := resourceNsxtPolicyTransitGatewayAttachmentExists(testAccGetSessionContext(), parentPath, resourceID, connector) + if err == nil { + return err + } + + if exists { + return fmt.Errorf("Policy TransitGatewayAttachment %s still exists", displayName) + } + } + return nil +} + +func testAccNsxtPolicyTransitGatewayAttachmentTemplate(createFlow bool) string { + var attrMap map[string]string + if createFlow { + attrMap = accTestPolicyTransitGatewayAttachmentCreateAttributes + } else { + attrMap = accTestPolicyTransitGatewayAttachmentUpdateAttributes + } + return fmt.Sprintf(` +data "nsxt_policy_edge_cluster" "test" { + display_name = "%s" +} + +resource "nsxt_policy_tier0_gateway" "test" { + display_name = "%s" + edge_cluster_path = data.nsxt_policy_edge_cluster.test.path +} + +resource "nsxt_policy_gateway_connection" "test" { + display_name = "%s" + tier0_path = nsxt_policy_tier0_gateway.test.path + aggregate_routes = ["192.168.240.0/24"] +} + +resource "nsxt_policy_project" "test" { + display_name = "%s" + tier0_gateway_paths = [nsxt_policy_tier0_gateway.test.path] + tgw_external_connections = [nsxt_policy_gateway_connection.test.path] +} + +data "nsxt_policy_transit_gateway" "test" { + context { + project_id = nsxt_policy_project.test.id + } + id = "default" +} + +resource "nsxt_policy_transit_gateway_attachment" "test" { + parent_path = data.nsxt_policy_transit_gateway.test.path + connection_path = nsxt_policy_gateway_connection.test.path + display_name = "%s" + description = "%s" + + tag { + scope = "scope1" + tag = "tag1" + } +}`, getEdgeClusterName(), dependantEntityName, dependantEntityName, dependantEntityName, attrMap["display_name"], attrMap["description"]) +} diff --git a/nsxt/resource_nsxt_policy_transit_gateway_nat_rule.go b/nsxt/resource_nsxt_policy_transit_gateway_nat_rule.go new file mode 100644 index 000000000..d8a2e7b35 --- /dev/null +++ b/nsxt/resource_nsxt_policy_transit_gateway_nat_rule.go @@ -0,0 +1,186 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "log" + "reflect" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + clientLayer "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/transit_gateways/nat" + + utl "github.com/vmware/terraform-provider-nsxt/api/utl" + "github.com/vmware/terraform-provider-nsxt/nsxt/metadata" +) + +func resourceNsxtPolicyTransitGatewayNatRule() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtPolicyTransitGatewayNatRuleCreate, + Read: resourceNsxtPolicyTransitGatewayNatRuleRead, + Update: resourceNsxtPolicyTransitGatewayNatRuleUpdate, + Delete: resourceNsxtPolicyTransitGatewayNatRuleDelete, + Importer: &schema.ResourceImporter{ + State: nsxtParentPathResourceImporter, + }, + // Today, transit gateway nat rule schema is equal to VPC nat rule schema + // If in future they diverge, we'll introduce new schema here + Schema: metadata.GetSchemaFromExtendedSchema(policyVpcNatRuleSchema), + } +} + +func resourceNsxtPolicyTransitGatewayNatRuleExists(sessionContext utl.SessionContext, parentPath string, id string, connector client.Connector) (bool, error) { + var err error + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 4) + if pathErr != nil { + return false, pathErr + } + client := clientLayer.NewNatRulesClient(connector) + _, err = client.Get(parents[0], parents[1], parents[2], parents[3], id) + if err == nil { + return true, nil + } + + if isNotFoundError(err) { + return false, nil + } + + return false, logAPIError("Error retrieving resource", err) +} + +func resourceNsxtPolicyTransitGatewayNatRuleCreate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id, err := getOrGenerateIDWithParent(d, m, resourceNsxtPolicyTransitGatewayNatRuleExists) + if err != nil { + return err + } + + parentPath := d.Get("parent_path").(string) + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 4) + if pathErr != nil { + return pathErr + } + displayName := d.Get("display_name").(string) + description := d.Get("description").(string) + tags := getPolicyTagsFromSchema(d) + + obj := model.TransitGatewayNatRule{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, policyVpcNatRuleSchema, "", nil); err != nil { + return err + } + + log.Printf("[INFO] Creating PolicyTransitGatewayNatRule with ID %s", id) + + client := clientLayer.NewNatRulesClient(connector) + err = client.Patch(parents[0], parents[1], parents[2], parents[3], id, obj) + if err != nil { + return handleCreateError("PolicyTransitGatewayNatRule", id, err) + } + d.SetId(id) + d.Set("nsx_id", id) + + return resourceNsxtPolicyTransitGatewayNatRuleRead(d, m) +} + +func resourceNsxtPolicyTransitGatewayNatRuleRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining PolicyTransitGatewayNatRule ID") + } + + client := clientLayer.NewNatRulesClient(connector) + parentPath := d.Get("parent_path").(string) + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 4) + if pathErr != nil { + return pathErr + } + obj, err := client.Get(parents[0], parents[1], parents[2], parents[3], id) + if err != nil { + return handleReadError(d, "PolicyTransitGatewayNatRule", id, err) + } + + setPolicyTagsInSchema(d, obj.Tags) + d.Set("nsx_id", id) + d.Set("display_name", obj.DisplayName) + d.Set("description", obj.Description) + d.Set("revision", obj.Revision) + d.Set("path", obj.Path) + + elem := reflect.ValueOf(&obj).Elem() + return metadata.StructToSchema(elem, d, policyVpcNatRuleSchema, "", nil) +} + +func resourceNsxtPolicyTransitGatewayNatRuleUpdate(d *schema.ResourceData, m interface{}) error { + + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining PolicyTransitGatewayNatRule ID") + } + + parentPath := d.Get("parent_path").(string) + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 4) + if pathErr != nil { + return pathErr + } + description := d.Get("description").(string) + displayName := d.Get("display_name").(string) + tags := getPolicyTagsFromSchema(d) + + revision := int64(d.Get("revision").(int)) + + obj := model.TransitGatewayNatRule{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + Revision: &revision, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, policyVpcNatRuleSchema, "", nil); err != nil { + return err + } + client := clientLayer.NewNatRulesClient(connector) + _, err := client.Update(parents[0], parents[1], parents[2], parents[3], id, obj) + if err != nil { + return handleUpdateError("PolicyTransitGatewayNatRule", id, err) + } + + return resourceNsxtPolicyTransitGatewayNatRuleRead(d, m) +} + +func resourceNsxtPolicyTransitGatewayNatRuleDelete(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining PolicyTransitGatewayNatRule ID") + } + + connector := getPolicyConnector(m) + parentPath := d.Get("parent_path").(string) + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 4) + if pathErr != nil { + return pathErr + } + + client := clientLayer.NewNatRulesClient(connector) + err := client.Delete(parents[0], parents[1], parents[2], parents[3], id) + + if err != nil { + return handleDeleteError("PolicyTransitGatewayNatRule", id, err) + } + + return nil +} diff --git a/nsxt/resource_nsxt_policy_transit_gateway_nat_rule_test.go b/nsxt/resource_nsxt_policy_transit_gateway_nat_rule_test.go new file mode 100644 index 000000000..da00b1b5a --- /dev/null +++ b/nsxt/resource_nsxt_policy_transit_gateway_nat_rule_test.go @@ -0,0 +1,311 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +var accTestTransitGatewayNatRuleHelperName = getAccTestResourceName() +var accTestTransitGatewayNatRuleCreateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform created", + "firewall_match": "MATCH_EXTERNAL_ADDRESS", + "logging": "true", + "action": "DNAT", + "source_network": "2.2.2.14", + "destination_network": "2.1.1.14", + "translated_network": "2.3.3.24", + "enabled": "false", + "sequence_number": "16", +} + +var accTestTransitGatewayNatRuleUpdateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform updated", + "firewall_match": "MATCH_INTERNAL_ADDRESS", + "logging": "false", + "action": "DNAT", + "source_network": "3.3.3.14", + "destination_network": "3.1.1.14", + "translated_network": "30.3.3.14", + "enabled": "true", + "sequence_number": "3", +} + +func TestAccResourceNsxtTransitGatewayNatRule_basic(t *testing.T) { + testResourceName := "nsxt_policy_transit_gateway_nat_rule.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccOnlyVPC(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtTransitGatewayNatRuleCheckDestroy(state, accTestTransitGatewayNatRuleUpdateAttributes["display_name"]) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtTransitGatewayNatRuleTemplate(true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtTransitGatewayNatRuleExists(accTestTransitGatewayNatRuleCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestTransitGatewayNatRuleCreateAttributes["display_name"]), + resource.TestCheckResourceAttrSet(testResourceName, "parent_path"), + resource.TestCheckResourceAttr(testResourceName, "description", accTestTransitGatewayNatRuleCreateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "translated_network", accTestTransitGatewayNatRuleCreateAttributes["translated_network"]), + resource.TestCheckResourceAttr(testResourceName, "logging", accTestTransitGatewayNatRuleCreateAttributes["logging"]), + resource.TestCheckResourceAttrSet(testResourceName, "destination_network"), + resource.TestCheckResourceAttr(testResourceName, "action", accTestTransitGatewayNatRuleCreateAttributes["action"]), + resource.TestCheckResourceAttr(testResourceName, "firewall_match", accTestTransitGatewayNatRuleCreateAttributes["firewall_match"]), + resource.TestCheckResourceAttr(testResourceName, "source_network", accTestTransitGatewayNatRuleCreateAttributes["source_network"]), + resource.TestCheckResourceAttr(testResourceName, "enabled", accTestTransitGatewayNatRuleCreateAttributes["enabled"]), + resource.TestCheckResourceAttr(testResourceName, "sequence_number", accTestTransitGatewayNatRuleCreateAttributes["sequence_number"]), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNsxtTransitGatewayNatRuleTemplate(false), + Check: resource.ComposeTestCheckFunc( + testAccNsxtTransitGatewayNatRuleExists(accTestTransitGatewayNatRuleUpdateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestTransitGatewayNatRuleUpdateAttributes["display_name"]), + resource.TestCheckResourceAttrSet(testResourceName, "parent_path"), + resource.TestCheckResourceAttr(testResourceName, "description", accTestTransitGatewayNatRuleUpdateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "translated_network", accTestTransitGatewayNatRuleUpdateAttributes["translated_network"]), + resource.TestCheckResourceAttr(testResourceName, "logging", accTestTransitGatewayNatRuleUpdateAttributes["logging"]), + resource.TestCheckResourceAttrSet(testResourceName, "destination_network"), + resource.TestCheckResourceAttr(testResourceName, "action", accTestTransitGatewayNatRuleUpdateAttributes["action"]), + resource.TestCheckResourceAttr(testResourceName, "firewall_match", accTestTransitGatewayNatRuleUpdateAttributes["firewall_match"]), + resource.TestCheckResourceAttr(testResourceName, "source_network", accTestTransitGatewayNatRuleUpdateAttributes["source_network"]), + resource.TestCheckResourceAttr(testResourceName, "enabled", accTestTransitGatewayNatRuleUpdateAttributes["enabled"]), + resource.TestCheckResourceAttr(testResourceName, "sequence_number", accTestTransitGatewayNatRuleUpdateAttributes["sequence_number"]), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNsxtTransitGatewayNatRuleMinimalistic(), + Check: resource.ComposeTestCheckFunc( + testAccNsxtTransitGatewayNatRuleExists(accTestTransitGatewayNatRuleCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtTransitGatewayNatRule_changeTypes(t *testing.T) { + testResourceName := "nsxt_policy_transit_gateway_nat_rule.test" + sourceIP := "2.2.2.34" + translatedNetwork := "2.2.3.34" + ruleName := getAccTestResourceName() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccOnlyVPC(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtTransitGatewayNatRuleCheckDestroy(state, ruleName) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtTransitGatewayNatRuleSnatTemplate(ruleName, translatedNetwork), + Check: resource.ComposeTestCheckFunc( + testAccNsxtTransitGatewayNatRuleExists(ruleName, testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", ruleName), + resource.TestCheckResourceAttrSet(testResourceName, "parent_path"), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "translated_network"), + resource.TestCheckResourceAttr(testResourceName, "logging", "false"), + resource.TestCheckNoResourceAttr(testResourceName, "destination_network"), + resource.TestCheckNoResourceAttr(testResourceName, "source_network"), + resource.TestCheckResourceAttr(testResourceName, "action", "SNAT"), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + ), + }, + { + Config: testAccNsxtTransitGatewayNatRuleReflexiveTemplate(ruleName, sourceIP, translatedNetwork), + Check: resource.ComposeTestCheckFunc( + testAccNsxtTransitGatewayNatRuleExists(ruleName, testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", ruleName), + resource.TestCheckResourceAttrSet(testResourceName, "parent_path"), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "translated_network"), + resource.TestCheckResourceAttr(testResourceName, "logging", "false"), + resource.TestCheckNoResourceAttr(testResourceName, "destination_network"), + resource.TestCheckResourceAttr(testResourceName, "source_network", sourceIP), + resource.TestCheckResourceAttr(testResourceName, "action", "REFLEXIVE"), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtTransitGatewayNatRule_importBasic(t *testing.T) { + name := getAccTestResourceName() + testResourceName := "nsxt_policy_transit_gateway_nat_rule.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccOnlyVPC(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtTransitGatewayNatRuleCheckDestroy(state, name) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtTransitGatewayNatRuleMinimalistic(), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccResourceNsxtPolicyImportIDRetriever(testResourceName), + }, + }, + }) +} + +func testAccNsxtTransitGatewayNatRuleExists(displayName string, resourceName string) resource.TestCheckFunc { + return func(state *terraform.State) error { + + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + + rs, ok := state.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Policy TransitGatewayNatRule resource %s not found in resources", resourceName) + } + + resourceID := rs.Primary.ID + if resourceID == "" { + return fmt.Errorf("Policy TransitGatewayNatRule resource ID not set in resources") + } + + parentPath := rs.Primary.Attributes["parent_path"] + + exists, err := resourceNsxtPolicyTransitGatewayNatRuleExists(testAccGetSessionContext(), parentPath, resourceID, connector) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("TransitGatewayNatRule %s does not exist", resourceID) + } + + return nil + } +} + +func testAccNsxtTransitGatewayNatRuleCheckDestroy(state *terraform.State, displayName string) error { + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + for _, rs := range state.RootModule().Resources { + + if rs.Type != "nsxt_policy_transit_gateway_nat_rule" { + continue + } + + resourceID := rs.Primary.Attributes["id"] + parentPath := rs.Primary.Attributes["parent_path"] + + exists, err := resourceNsxtPolicyTransitGatewayNatRuleExists(testAccGetSessionContext(), parentPath, resourceID, connector) + if err == nil { + return err + } + + if exists { + return fmt.Errorf("TransitGatewayNatRule %s still exists", displayName) + } + } + return nil +} + +func testAccNsxtTransitGatewayNatRulePrerequisites() string { + return fmt.Sprintf(` +resource "nsxt_policy_transit_gateway" "test" { + %s + display_name = "%s" + transit_subnets = ["192.168.5.0/24"] +} + +data "nsxt_policy_transit_gateway_nat" "test" { + transit_gateway_path = data.nsxt_policy_transit_gateway.test.path +} +`, testAccNsxtProjectContext(), accTestTransitGatewayNatRuleHelperName) +} + +func testAccNsxtTransitGatewayNatRuleTemplate(createFlow bool) string { + var attrMap map[string]string + if createFlow { + attrMap = accTestTransitGatewayNatRuleCreateAttributes + } else { + attrMap = accTestTransitGatewayNatRuleUpdateAttributes + } + return testAccNsxtTransitGatewayNatRulePrerequisites() + fmt.Sprintf(` +resource "nsxt_policy_transit_gateway_nat_rule" "test" { + parent_path = data.nsxt_policy_transit_gateway_nat.test.path + display_name = "%s" + description = "%s" + translated_network = "%s" + logging = %s + destination_network = "%s" + action = "%s" + firewall_match = "%s" + source_network = "%s" + enabled = %s + sequence_number = %s + + tag { + scope = "scope1" + tag = "tag1" + } +}`, attrMap["display_name"], attrMap["description"], attrMap["translated_network"], attrMap["logging"], attrMap["destination_network"], attrMap["action"], attrMap["firewall_match"], attrMap["source_network"], attrMap["enabled"], attrMap["sequence_number"]) +} + +func testAccNsxtTransitGatewayNatRuleMinimalistic() string { + return testAccNsxtTransitGatewayNatRulePrerequisites() + fmt.Sprintf(` +resource "nsxt_policy_transit_gateway_nat_rule" "test" { + parent_path = data.nsxt_policy_transit_gateway_nat.test.path + display_name = "%s" + destination_network = "%s" + translated_network = "%s" + action = "%s" +}`, accTestTransitGatewayNatRuleUpdateAttributes["display_name"], accTestTransitGatewayNatRuleUpdateAttributes["translated_network"], accTestTransitGatewayNatRuleUpdateAttributes["destination_network"], accTestTransitGatewayNatRuleUpdateAttributes["action"]) +} + +func testAccNsxtTransitGatewayNatRuleSnatTemplate(name string, translatedNetwork string) string { + return testAccNsxtTransitGatewayNatRulePrerequisites() + fmt.Sprintf(` +resource "nsxt_policy_transit_gateway_nat_rule" "test" { + parent_path = data.nsxt_policy_transit_gateway_nat.test.path + display_name = "%s" + translated_network = "%s" + action = "SNAT" +}`, name, translatedNetwork) +} + +func testAccNsxtTransitGatewayNatRuleReflexiveTemplate(name string, sourceIP string, translatedNetwork string) string { + return testAccNsxtTransitGatewayNatRulePrerequisites() + fmt.Sprintf(` +resource "nsxt_policy_transit_gateway_nat_rule" "test" { + parent_path = data.nsxt_policy_transit_gateway_nat.test.path + display_name = "%s" + source_network = "%s" + translated_network = "%s" + action = "REFLEXIVE" +}`, name, sourceIP, translatedNetwork) +} diff --git a/nsxt/resource_nsxt_policy_transit_gateway_test.go b/nsxt/resource_nsxt_policy_transit_gateway_test.go new file mode 100644 index 000000000..c50f950c5 --- /dev/null +++ b/nsxt/resource_nsxt_policy_transit_gateway_test.go @@ -0,0 +1,201 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +var accTestTransitGatewayCreateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform created", + "transit_subnets": "192.168.5.0/24", +} + +var accTestTransitGatewayUpdateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform updated", + "transit_subnets": "192.168.7.0/24", +} + +func TestAccResourceNsxtPolicyTransitGateway_basic(t *testing.T) { + testResourceName := "nsxt_policy_transit_gateway.test" + testDataSourceName := "nsxt_policy_transit_gateway.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccOnlyVPC(t) + testAccNSXVersion(t, "9.1.0") + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicyTransitGatewayCheckDestroy(state, accTestTransitGatewayUpdateAttributes["display_name"]) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicyTransitGatewayTemplate(true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyTransitGatewayExists(accTestTransitGatewayCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestTransitGatewayCreateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestTransitGatewayCreateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "transit_subnets.0", accTestTransitGatewayCreateAttributes["transit_subnets"]), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + + resource.TestCheckResourceAttrSet(testDataSourceName, "path"), + resource.TestCheckResourceAttrSet(testDataSourceName, "description"), + ), + }, + { + Config: testAccNsxtPolicyTransitGatewayTemplate(false), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyTransitGatewayExists(accTestTransitGatewayUpdateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestTransitGatewayUpdateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestTransitGatewayUpdateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "transit_subnets.0", accTestTransitGatewayUpdateAttributes["transit_subnets"]), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + + resource.TestCheckResourceAttrSet(testDataSourceName, "path"), + resource.TestCheckResourceAttrSet(testDataSourceName, "description"), + ), + }, + { + Config: testAccNsxtPolicyTransitGatewayMinimalistic(), + Check: resource.ComposeTestCheckFunc( + testAccNsxtPolicyTransitGatewayExists(accTestTransitGatewayCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtPolicyTransitGateway_importBasic(t *testing.T) { + name := getAccTestResourceName() + testResourceName := "nsxt_policy_transit_gateway.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccOnlyVPC(t) + testAccNSXVersion(t, "9.1.0") + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtPolicyTransitGatewayCheckDestroy(state, name) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtPolicyTransitGatewayMinimalistic(), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccResourceNsxtPolicyImportIDRetriever(testResourceName), + }, + }, + }) +} + +func testAccNsxtPolicyTransitGatewayExists(displayName string, resourceName string) resource.TestCheckFunc { + return func(state *terraform.State) error { + + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + + rs, ok := state.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Policy TransitGateway resource %s not found in resources", resourceName) + } + + resourceID := rs.Primary.ID + if resourceID == "" { + return fmt.Errorf("Policy TransitGateway resource ID not set in resources") + } + + exists, err := resourceNsxtPolicyTransitGatewayExists(testAccGetSessionProjectContext(), resourceID, connector) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("Policy TransitGateway %s does not exist", resourceID) + } + + return nil + } +} + +func testAccNsxtPolicyTransitGatewayCheckDestroy(state *terraform.State, displayName string) error { + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + for _, rs := range state.RootModule().Resources { + + if rs.Type != "nsxt_policy_transit_gateway" { + continue + } + + resourceID := rs.Primary.Attributes["id"] + exists, err := resourceNsxtPolicyTransitGatewayExists(testAccGetSessionProjectContext(), resourceID, connector) + if err == nil { + return err + } + + if exists { + return fmt.Errorf("Policy TransitGateway %s still exists", displayName) + } + } + return nil +} + +func testAccNsxtPolicyTransitGatewayTemplate(createFlow bool) string { + var attrMap map[string]string + if createFlow { + attrMap = accTestTransitGatewayCreateAttributes + } else { + attrMap = accTestTransitGatewayUpdateAttributes + } + return fmt.Sprintf(` +resource "nsxt_policy_transit_gateway" "test" { +%s + display_name = "%s" + description = "%s" + transit_subnets = ["%s"] + + tag { + scope = "scope1" + tag = "tag1" + } +} + +data "nsxt_policy_transit_gateway" "test" { +%s + display_name = "%s" + depends_on = [nsxt_policy_transit_gateway.test] +}`, testAccNsxtProjectContext(), attrMap["display_name"], attrMap["description"], attrMap["transit_subnets"], + testAccNsxtProjectContext(), attrMap["display_name"]) +} + +func testAccNsxtPolicyTransitGatewayMinimalistic() string { + return fmt.Sprintf(` +resource "nsxt_policy_transit_gateway" "test" { +%s + display_name = "%s" + transit_subnets = ["%s"] +}`, testAccNsxtProjectContext(), accTestTransitGatewayUpdateAttributes["display_name"], accTestTransitGatewayUpdateAttributes["transit_subnets"]) +} diff --git a/nsxt/resource_nsxt_vpc.go b/nsxt/resource_nsxt_vpc.go new file mode 100644 index 000000000..4ec4a70bd --- /dev/null +++ b/nsxt/resource_nsxt_vpc.go @@ -0,0 +1,285 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "log" + "reflect" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + clientLayer "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects" + + utl "github.com/vmware/terraform-provider-nsxt/api/utl" + "github.com/vmware/terraform-provider-nsxt/nsxt/metadata" +) + +var vpcIPAddressTypeValues = []string{ + model.Vpc_IP_ADDRESS_TYPE_IPV4, +} + +var vpcSchema = map[string]*metadata.ExtendedSchema{ + "nsx_id": metadata.GetExtendedSchema(getNsxIDSchema()), + "path": metadata.GetExtendedSchema(getPathSchema()), + "display_name": metadata.GetExtendedSchema(getDisplayNameSchema()), + "description": metadata.GetExtendedSchema(getDescriptionSchema()), + "revision": metadata.GetExtendedSchema(getRevisionSchema()), + "tag": metadata.GetExtendedSchema(getTagsSchema()), + "context": metadata.GetExtendedSchema(getContextSchema(true, false, false)), + "private_ips": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedSchema{ + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateIPCidr(), + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + }, + }, + Optional: true, + ForceNew: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "PrivateIps", + }, + }, + "vpc_service_profile": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validatePolicyPath(), + Optional: true, + Computed: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "VpcServiceProfile", + OmitIfEmpty: true, + }, + }, + "load_balancer_vpc_endpoint": { + Schema: schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "enabled": { + Schema: schema.Schema{ + Type: schema.TypeBool, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "bool", + SdkFieldName: "Enabled", + }, + }, + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "struct", + SdkFieldName: "LoadBalancerVpcEndpoint", + ReflectType: reflect.TypeOf(model.LoadBalancerVPCEndpoint{}), + }, + }, + "ip_address_type": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(vpcIPAddressTypeValues, false), + Optional: true, + Default: model.Vpc_IP_ADDRESS_TYPE_IPV4, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "IpAddressType", + }, + }, + "short_id": { + Schema: schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "ShortId", + OmitIfEmpty: true, + }, + }, +} + +// VPC resource needs dedicated importer since its path is VPC path, +// but VPC does not need to be set in context +func nsxtVpcImporter(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + importID := d.Id() + if isPolicyPath(importID) { + pathSegs := strings.Split(importID, "/") + if len(pathSegs) != 7 || pathSegs[1] != "orgs" || pathSegs[3] != "projects" || pathSegs[5] != "vpcs" { + return []*schema.ResourceData{d}, fmt.Errorf("invalid VPC path: %s", importID) + } + ctxMap := make(map[string]interface{}) + ctxMap["project_id"] = pathSegs[4] + d.Set("context", []interface{}{ctxMap}) + d.SetId(pathSegs[6]) + return []*schema.ResourceData{d}, nil + } + return []*schema.ResourceData{d}, ErrNotAPolicyPath +} + +func resourceNsxtVpc() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtVpcCreate, + Read: resourceNsxtVpcRead, + Update: resourceNsxtVpcUpdate, + Delete: resourceNsxtVpcDelete, + Importer: &schema.ResourceImporter{ + State: nsxtVpcImporter, + }, + Schema: metadata.GetSchemaFromExtendedSchema(vpcSchema), + } +} + +func resourceNsxtVpcExists(sessionContext utl.SessionContext, id string, connector client.Connector) (bool, error) { + var err error + parents := getVpcParentsFromContext(sessionContext) + client := clientLayer.NewVpcsClient(connector) + _, err = client.Get(parents[0], parents[1], id) + if err == nil { + return true, nil + } + + if isNotFoundError(err) { + return false, nil + } + + return false, logAPIError("Error retrieving resource", err) +} + +func resourceNsxtVpcCreate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id, err := getOrGenerateID2(d, m, resourceNsxtVpcExists) + if err != nil { + return err + } + + parents := getVpcParentsFromContext(getSessionContext(d, m)) + displayName := d.Get("display_name").(string) + description := d.Get("description").(string) + tags := getPolicyTagsFromSchema(d) + + obj := model.Vpc{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, vpcSchema, "", nil); err != nil { + return err + } + + log.Printf("[INFO] Creating Vpc with ID %s", id) + + client := clientLayer.NewVpcsClient(connector) + err = client.Patch(parents[0], parents[1], id, obj) + if err != nil { + return handleCreateError("Vpc", id, err) + } + d.SetId(id) + d.Set("nsx_id", id) + + return resourceNsxtVpcRead(d, m) +} + +func resourceNsxtVpcRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining Vpc ID") + } + + client := clientLayer.NewVpcsClient(connector) + parents := getVpcParentsFromContext(getSessionContext(d, m)) + obj, err := client.Get(parents[0], parents[1], id) + if err != nil { + return handleReadError(d, "Vpc", id, err) + } + + setPolicyTagsInSchema(d, obj.Tags) + d.Set("nsx_id", id) + d.Set("display_name", obj.DisplayName) + d.Set("description", obj.Description) + d.Set("revision", obj.Revision) + d.Set("path", obj.Path) + + elem := reflect.ValueOf(&obj).Elem() + return metadata.StructToSchema(elem, d, vpcSchema, "", nil) +} + +func resourceNsxtVpcUpdate(d *schema.ResourceData, m interface{}) error { + + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining Vpc ID") + } + + parents := getVpcParentsFromContext(getSessionContext(d, m)) + description := d.Get("description").(string) + displayName := d.Get("display_name").(string) + tags := getPolicyTagsFromSchema(d) + + revision := int64(d.Get("revision").(int)) + + obj := model.Vpc{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + Revision: &revision, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, vpcSchema, "", nil); err != nil { + return err + } + client := clientLayer.NewVpcsClient(connector) + _, err := client.Update(parents[0], parents[1], id, obj) + if err != nil { + // Trigger partial update to avoid terraform updating state based on failed intent + // TODO - move this into handleUpdateError + d.Partial(true) + return handleUpdateError("Vpc", id, err) + } + + return resourceNsxtVpcRead(d, m) +} + +func resourceNsxtVpcDelete(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining Vpc ID") + } + + connector := getPolicyConnector(m) + parents := getVpcParentsFromContext(getSessionContext(d, m)) + + client := clientLayer.NewVpcsClient(connector) + err := client.Delete(parents[0], parents[1], id, nil) + + if err != nil { + return handleDeleteError("Vpc", id, err) + } + + return nil +} diff --git a/nsxt/resource_nsxt_vpc_attachment.go b/nsxt/resource_nsxt_vpc_attachment.go new file mode 100644 index 000000000..6f373418f --- /dev/null +++ b/nsxt/resource_nsxt_vpc_attachment.go @@ -0,0 +1,206 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "log" + "reflect" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + clientLayer "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/vpcs" + + utl "github.com/vmware/terraform-provider-nsxt/api/utl" + "github.com/vmware/terraform-provider-nsxt/nsxt/metadata" +) + +var vpcAttachmentSchema = map[string]*metadata.ExtendedSchema{ + "nsx_id": metadata.GetExtendedSchema(getNsxIDSchema()), + "path": metadata.GetExtendedSchema(getPathSchema()), + "display_name": metadata.GetExtendedSchema(getDisplayNameSchema()), + "description": metadata.GetExtendedSchema(getDescriptionSchema()), + "revision": metadata.GetExtendedSchema(getRevisionSchema()), + "tag": metadata.GetExtendedSchema(getTagsSchema()), + "parent_path": metadata.GetExtendedSchema(getPolicyPathSchema(true, true, "Policy path of the parent")), + "vpc_connectivity_profile": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validatePolicyPath(), + Required: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "VpcConnectivityProfile", + }, + }, +} + +func resourceNsxtVpcAttachment() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtVpcAttachmentCreate, + Read: resourceNsxtVpcAttachmentRead, + Update: resourceNsxtVpcAttachmentUpdate, + Delete: resourceNsxtVpcAttachmentDelete, + Importer: &schema.ResourceImporter{ + State: nsxtParentPathResourceImporter, + }, + Schema: metadata.GetSchemaFromExtendedSchema(vpcAttachmentSchema), + } +} + +func resourceNsxtVpcAttachmentExists(sessionContext utl.SessionContext, parentPath string, id string, connector client.Connector) (bool, error) { + var err error + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 3) + if pathErr != nil { + return false, pathErr + } + client := clientLayer.NewAttachmentsClient(connector) + _, err = client.Get(parents[0], parents[1], parents[2], id) + if err == nil { + return true, nil + } + + if isNotFoundError(err) { + return false, nil + } + + return false, logAPIError("error retrieving resource", err) +} + +func resourceNsxtVpcAttachmentCreate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id, err := getOrGenerateIDWithParent(d, m, resourceNsxtVpcAttachmentExists) + if err != nil { + return err + } + + parentPath := d.Get("parent_path").(string) + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 3) + if pathErr != nil { + return pathErr + } + displayName := d.Get("display_name").(string) + description := d.Get("description").(string) + tags := getPolicyTagsFromSchema(d) + + obj := model.VpcAttachment{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, vpcAttachmentSchema, "", nil); err != nil { + return err + } + + log.Printf("[INFO] Creating VpcAttachment with ID %s", id) + + client := clientLayer.NewAttachmentsClient(connector) + err = client.Patch(parents[0], parents[1], parents[2], id, obj) + if err != nil { + return handleCreateError("VpcAttachment", id, err) + } + d.SetId(id) + d.Set("nsx_id", id) + + return resourceNsxtVpcAttachmentRead(d, m) +} + +func resourceNsxtVpcAttachmentRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("error obtaining VpcAttachment ID") + } + + client := clientLayer.NewAttachmentsClient(connector) + parentPath := d.Get("parent_path").(string) + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 3) + if pathErr != nil { + return pathErr + } + obj, err := client.Get(parents[0], parents[1], parents[2], id) + if err != nil { + return handleReadError(d, "VpcAttachment", id, err) + } + + setPolicyTagsInSchema(d, obj.Tags) + d.Set("nsx_id", id) + d.Set("display_name", obj.DisplayName) + d.Set("description", obj.Description) + d.Set("revision", obj.Revision) + d.Set("path", obj.Path) + + elem := reflect.ValueOf(&obj).Elem() + return metadata.StructToSchema(elem, d, vpcAttachmentSchema, "", nil) +} + +func resourceNsxtVpcAttachmentUpdate(d *schema.ResourceData, m interface{}) error { + + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("error obtaining VpcAttachment ID") + } + + parentPath := d.Get("parent_path").(string) + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 3) + if pathErr != nil { + return pathErr + } + description := d.Get("description").(string) + displayName := d.Get("display_name").(string) + tags := getPolicyTagsFromSchema(d) + + revision := int64(d.Get("revision").(int)) + + obj := model.VpcAttachment{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + Revision: &revision, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, vpcAttachmentSchema, "", nil); err != nil { + return err + } + client := clientLayer.NewAttachmentsClient(connector) + _, err := client.Update(parents[0], parents[1], parents[2], id, obj) + if err != nil { + return handleUpdateError("VpcAttachment", id, err) + } + + return resourceNsxtVpcAttachmentRead(d, m) +} + +func resourceNsxtVpcAttachmentDelete(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + return fmt.Errorf("error obtaining VpcAttachment ID") + } + + connector := getPolicyConnector(m) + parentPath := d.Get("parent_path").(string) + + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 3) + if pathErr != nil { + return pathErr + } + + client := clientLayer.NewAttachmentsClient(connector) + err := client.Delete(parents[0], parents[1], parents[2], id) + + if err != nil { + return handleDeleteError("VpcAttachment", id, err) + } + + return nil +} diff --git a/nsxt/resource_nsxt_vpc_connectivity_profile.go b/nsxt/resource_nsxt_vpc_connectivity_profile.go new file mode 100644 index 000000000..988819805 --- /dev/null +++ b/nsxt/resource_nsxt_vpc_connectivity_profile.go @@ -0,0 +1,342 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "log" + "reflect" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + clientLayer "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects" + + utl "github.com/vmware/terraform-provider-nsxt/api/utl" + "github.com/vmware/terraform-provider-nsxt/nsxt/metadata" +) + +var vpcConnectivityProfileSchema = map[string]*metadata.ExtendedSchema{ + "nsx_id": metadata.GetExtendedSchema(getNsxIDSchema()), + "path": metadata.GetExtendedSchema(getPathSchema()), + "display_name": metadata.GetExtendedSchema(getDisplayNameSchema()), + "description": metadata.GetExtendedSchema(getDescriptionSchema()), + "revision": metadata.GetExtendedSchema(getRevisionSchema()), + "tag": metadata.GetExtendedSchema(getTagsSchema()), + "context": metadata.GetExtendedSchema(getContextSchema(true, false, false)), + "transit_gateway_path": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validatePolicyPath(), + Required: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "TransitGatewayPath", + }, + }, + "private_tgw_ip_blocks": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedSchema{ + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validatePolicyPath(), + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "PrivateTgwIpBlocks", + }, + }, + "external_ip_blocks": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedSchema{ + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validatePolicyPath(), + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "ExternalIpBlocks", + }, + }, + "service_gateway": { + Schema: schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "nat_config": { + Schema: schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "enable_default_snat": { + Schema: schema.Schema{ + Type: schema.TypeBool, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "bool", + SdkFieldName: "EnableDefaultSnat", + }, + }, + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "struct", + SdkFieldName: "NatConfig", + ReflectType: reflect.TypeOf(model.VpcNatConfig{}), + }, + }, + "qos_config": { + Schema: schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "ingress_qos_profile_path": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validatePolicyPath(), + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "IngressQosProfilePath", + }, + }, + "egress_qos_profile_path": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validatePolicyPath(), + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "EgressQosProfilePath", + }, + }, + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "struct", + SdkFieldName: "QosConfig", + ReflectType: reflect.TypeOf(model.GatewayQosProfileConfig{}), + }, + }, + "enable": { + Schema: schema.Schema{ + Type: schema.TypeBool, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "bool", + SdkFieldName: "Enable", + }, + }, + "edge_cluster_paths": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedSchema{ + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validatePolicyPath(), + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "EdgeClusterPaths", + }, + }, + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "struct", + SdkFieldName: "ServiceGateway", + ReflectType: reflect.TypeOf(model.VpcServiceGatewayConfig{}), + }, + }, +} + +func resourceNsxtVpcConnectivityProfile() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtVpcConnectivityProfileCreate, + Read: resourceNsxtVpcConnectivityProfileRead, + Update: resourceNsxtVpcConnectivityProfileUpdate, + Delete: resourceNsxtVpcConnectivityProfileDelete, + Importer: &schema.ResourceImporter{ + State: nsxtPolicyPathOnlyResourceImporter, + }, + Schema: metadata.GetSchemaFromExtendedSchema(vpcConnectivityProfileSchema), + } +} + +func resourceNsxtVpcConnectivityProfileExists(sessionContext utl.SessionContext, id string, connector client.Connector) (bool, error) { + var err error + parents := getVpcParentsFromContext(sessionContext) + client := clientLayer.NewVpcConnectivityProfilesClient(connector) + _, err = client.Get(parents[0], parents[1], id) + if err == nil { + return true, nil + } + + if isNotFoundError(err) { + return false, nil + } + + return false, logAPIError("Error retrieving resource", err) +} + +func resourceNsxtVpcConnectivityProfileCreate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id, err := getOrGenerateID2(d, m, resourceNsxtVpcConnectivityProfileExists) + if err != nil { + return err + } + + parents := getVpcParentsFromContext(getSessionContext(d, m)) + displayName := d.Get("display_name").(string) + description := d.Get("description").(string) + tags, tagErr := getValidatedTagsFromSchema(d) + if tagErr != nil { + return tagErr + } + + obj := model.VpcConnectivityProfile{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, vpcConnectivityProfileSchema, "", nil); err != nil { + return err + } + + log.Printf("[INFO] Creating VpcConnectivityProfile with ID %s", id) + + client := clientLayer.NewVpcConnectivityProfilesClient(connector) + err = client.Patch(parents[0], parents[1], id, obj) + if err != nil { + return handleCreateError("VpcConnectivityProfile", id, err) + } + d.SetId(id) + d.Set("nsx_id", id) + + return resourceNsxtVpcConnectivityProfileRead(d, m) +} + +func resourceNsxtVpcConnectivityProfileRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining VpcConnectivityProfile ID") + } + + client := clientLayer.NewVpcConnectivityProfilesClient(connector) + parents := getVpcParentsFromContext(getSessionContext(d, m)) + obj, err := client.Get(parents[0], parents[1], id) + if err != nil { + return handleReadError(d, "VpcConnectivityProfile", id, err) + } + + setPolicyTagsInSchema(d, obj.Tags) + d.Set("nsx_id", id) + d.Set("display_name", obj.DisplayName) + d.Set("description", obj.Description) + d.Set("revision", obj.Revision) + d.Set("path", obj.Path) + + elem := reflect.ValueOf(&obj).Elem() + return metadata.StructToSchema(elem, d, vpcConnectivityProfileSchema, "", nil) +} + +func resourceNsxtVpcConnectivityProfileUpdate(d *schema.ResourceData, m interface{}) error { + + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining VpcConnectivityProfile ID") + } + + parents := getVpcParentsFromContext(getSessionContext(d, m)) + description := d.Get("description").(string) + displayName := d.Get("display_name").(string) + tags, tagErr := getValidatedTagsFromSchema(d) + if tagErr != nil { + return tagErr + } + + revision := int64(d.Get("revision").(int)) + + obj := model.VpcConnectivityProfile{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + Revision: &revision, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, vpcConnectivityProfileSchema, "", nil); err != nil { + return err + } + client := clientLayer.NewVpcConnectivityProfilesClient(connector) + _, err := client.Update(parents[0], parents[1], id, obj) + if err != nil { + // Trigger partial update to avoid terraform updating state based on failed intent + // TODO - move this into handleUpdateError + d.Partial(true) + return handleUpdateError("VpcConnectivityProfile", id, err) + } + + return resourceNsxtVpcConnectivityProfileRead(d, m) +} + +func resourceNsxtVpcConnectivityProfileDelete(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining VpcConnectivityProfile ID") + } + + connector := getPolicyConnector(m) + parents := getVpcParentsFromContext(getSessionContext(d, m)) + + client := clientLayer.NewVpcConnectivityProfilesClient(connector) + err := client.Delete(parents[0], parents[1], id) + + if err != nil { + return handleDeleteError("VpcConnectivityProfile", id, err) + } + + return nil +} diff --git a/nsxt/resource_nsxt_vpc_connectivity_profile_test.go b/nsxt/resource_nsxt_vpc_connectivity_profile_test.go new file mode 100644 index 000000000..9bee4be6b --- /dev/null +++ b/nsxt/resource_nsxt_vpc_connectivity_profile_test.go @@ -0,0 +1,225 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +var accTestVpcConnectivityProfileCreateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform created", + "enable": "false", + "enable_default_snat": "false", +} + +var accTestVpcConnectivityProfileUpdateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform updated", + "enable": "false", + "enable_default_snat": "false", +} + +func TestAccResourceNsxtVpcConnectivityProfile_basic(t *testing.T) { + testResourceName := "nsxt_vpc_connectivity_profile.test" + testDataSourceName := "nsxt_vpc_connectivity_profile.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccOnlyVPC(t) + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtVpcConnectivityProfileCheckDestroy(state, accTestVpcConnectivityProfileUpdateAttributes["display_name"]) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtVpcConnectivityProfileTemplate(true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcConnectivityProfileExists(accTestVpcConnectivityProfileCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttrSet(testDataSourceName, "path"), + resource.TestCheckResourceAttrSet(testDataSourceName, "description"), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestVpcConnectivityProfileCreateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestVpcConnectivityProfileCreateAttributes["description"]), + resource.TestCheckResourceAttrSet(testResourceName, "transit_gateway_path"), + resource.TestCheckResourceAttr(testResourceName, "service_gateway.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "service_gateway.0.nat_config.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "service_gateway.0.enable", accTestVpcConnectivityProfileCreateAttributes["enable"]), + resource.TestCheckResourceAttr(testResourceName, "service_gateway.0.nat_config.0.enable_default_snat", accTestVpcConnectivityProfileCreateAttributes["enable_default_snat"]), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNsxtVpcConnectivityProfileTemplate(false), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcConnectivityProfileExists(accTestVpcConnectivityProfileUpdateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttrSet(testDataSourceName, "path"), + resource.TestCheckResourceAttrSet(testDataSourceName, "description"), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestVpcConnectivityProfileUpdateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestVpcConnectivityProfileUpdateAttributes["description"]), + resource.TestCheckResourceAttrSet(testResourceName, "transit_gateway_path"), + resource.TestCheckResourceAttr(testResourceName, "service_gateway.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "service_gateway.0.nat_config.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "service_gateway.0.enable", accTestVpcConnectivityProfileUpdateAttributes["enable"]), + resource.TestCheckResourceAttr(testResourceName, "service_gateway.0.nat_config.0.enable_default_snat", accTestVpcConnectivityProfileUpdateAttributes["enable_default_snat"]), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNsxtVpcConnectivityProfileMinimalistic(), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcConnectivityProfileExists(accTestVpcConnectivityProfileCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtVpcConnectivityProfile_importBasic(t *testing.T) { + name := getAccTestResourceName() + testResourceName := "nsxt_vpc_connectivity_profile.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccOnlyVPC(t) + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtVpcConnectivityProfileCheckDestroy(state, name) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtVpcConnectivityProfileMinimalistic(), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccResourceNsxtPolicyImportIDRetriever(testResourceName), + }, + }, + }) +} + +func testAccNsxtVpcConnectivityProfileExists(displayName string, resourceName string) resource.TestCheckFunc { + return func(state *terraform.State) error { + + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + + rs, ok := state.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Policy VpcConnectivityProfile resource %s not found in resources", resourceName) + } + + resourceID := rs.Primary.ID + if resourceID == "" { + return fmt.Errorf("Policy VpcConnectivityProfile resource ID not set in resources") + } + + exists, err := resourceNsxtVpcConnectivityProfileExists(testAccGetProjectContext(), resourceID, connector) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("Policy VpcConnectivityProfile %s does not exist", resourceID) + } + + return nil + } +} + +func testAccNsxtVpcConnectivityProfileCheckDestroy(state *terraform.State, displayName string) error { + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + for _, rs := range state.RootModule().Resources { + + if rs.Type != "nsxt_vpc_connectivity_profile" { + continue + } + + resourceID := rs.Primary.Attributes["id"] + exists, err := resourceNsxtVpcConnectivityProfileExists(testAccGetProjectContext(), resourceID, connector) + if err == nil { + return err + } + + if exists { + return fmt.Errorf("Policy VpcConnectivityProfile %s still exists", displayName) + } + } + return nil +} + +func testAccNsxtVpcConnectivityProfilePrerequisite() string { + //TODO: replace datasource with resource when transit GW creation is enabled + return fmt.Sprintf(` +data "nsxt_policy_transit_gateway" "test" { +%s + id = "default" +} +`, testAccNsxtProjectContext()) +} + +func testAccNsxtVpcConnectivityProfileTemplate(createFlow bool) string { + var attrMap map[string]string + if createFlow { + attrMap = accTestVpcConnectivityProfileCreateAttributes + } else { + attrMap = accTestVpcConnectivityProfileUpdateAttributes + } + return testAccNsxtVpcConnectivityProfilePrerequisite() + fmt.Sprintf(` +resource "nsxt_vpc_connectivity_profile" "test" { +%s + display_name = "%s" + description = "%s" + transit_gateway_path = data.nsxt_policy_transit_gateway.test.path + + service_gateway { + nat_config { + enable_default_snat = %s + } + + enable = %s + } + + tag { + scope = "scope1" + tag = "tag1" + } +} +data "nsxt_vpc_connectivity_profile" "test" { +%s + display_name = "%s" + + depends_on = [nsxt_vpc_connectivity_profile.test] +}`, testAccNsxtProjectContext(), attrMap["display_name"], attrMap["description"], attrMap["enable_default_snat"], attrMap["enable"], testAccNsxtProjectContext(), attrMap["display_name"]) +} + +func testAccNsxtVpcConnectivityProfileMinimalistic() string { + return testAccNsxtVpcConnectivityProfilePrerequisite() + fmt.Sprintf(` +resource "nsxt_vpc_connectivity_profile" "test" { +%s + display_name = "%s" + transit_gateway_path = data.nsxt_policy_transit_gateway.test.path + +}`, testAccNsxtProjectContext(), accTestVpcConnectivityProfileUpdateAttributes["display_name"]) +} diff --git a/nsxt/resource_nsxt_vpc_dhcp_v4_static_binding_config.go b/nsxt/resource_nsxt_vpc_dhcp_v4_static_binding_config.go new file mode 100644 index 000000000..1e7500f97 --- /dev/null +++ b/nsxt/resource_nsxt_vpc_dhcp_v4_static_binding_config.go @@ -0,0 +1,392 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "log" + "reflect" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/vmware/vsphere-automation-sdk-go/runtime/bindings" + "github.com/vmware/vsphere-automation-sdk-go/runtime/data" + "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + clientLayer "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/vpcs/subnets" + + utl "github.com/vmware/terraform-provider-nsxt/api/utl" + "github.com/vmware/terraform-provider-nsxt/nsxt/metadata" +) + +var dhcpV4StaticBindingConfigSchema = map[string]*metadata.ExtendedSchema{ + "nsx_id": metadata.GetExtendedSchema(getNsxIDSchema()), + "path": metadata.GetExtendedSchema(getPathSchema()), + "display_name": metadata.GetExtendedSchema(getDisplayNameSchema()), + "description": metadata.GetExtendedSchema(getDescriptionSchema()), + "revision": metadata.GetExtendedSchema(getRevisionSchema()), + "tag": metadata.GetExtendedSchema(getTagsSchema()), + "parent_path": metadata.GetExtendedSchema(getPolicyPathSchema(true, true, "Policy path of the parent")), + "gateway_address": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.IsIPv4Address, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "GatewayAddress", + OmitIfEmpty: true, + }, + }, + "host_name": { + Schema: schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "HostName", + }, + }, + "mac_address": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.IsMACAddress, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "MacAddress", + }, + }, + "lease_time": { + Schema: schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 86400, + }, + Metadata: metadata.Metadata{ + SchemaType: "int", + SdkFieldName: "LeaseTime", + }, + }, + "ip_address": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.IsIPv4Address, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "IpAddress", + }, + }, + "options": { + Schema: schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "option121": { + Schema: schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "static_route": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "next_hop": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateSingleIP(), + Required: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "NextHop", + }, + }, + "network": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateCidrOrIPOrRange(), + Required: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "Network", + }, + }, + }, + }, + Required: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "StaticRoutes", + ReflectType: reflect.TypeOf(model.ClasslessStaticRoute{}), + }, + }, + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "struct", + SdkFieldName: "Option121", + ReflectType: reflect.TypeOf(model.DhcpOption121{}), + }, + }, + "other": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "code": { + Schema: schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "int", + SdkFieldName: "Code", + }, + }, + "values": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedSchema{ + Schema: schema.Schema{ + Type: schema.TypeString, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "Values", + }, + }, + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "Others", + ReflectType: reflect.TypeOf(model.GenericDhcpOption{}), + }, + }, + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "struct", + SdkFieldName: "Options", + ReflectType: reflect.TypeOf(model.DhcpV4Options{}), + }, + }, +} + +func resourceNsxtVpcSubnetDhcpV4StaticBindingConfig() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtVpcSubnetDhcpV4StaticBindingConfigCreate, + Read: resourceNsxtVpcSubnetDhcpV4StaticBindingConfigRead, + Update: resourceNsxtVpcSubnetDhcpV4StaticBindingConfigUpdate, + Delete: resourceNsxtVpcSubnetDhcpV4StaticBindingConfigDelete, + Importer: &schema.ResourceImporter{ + State: nsxtParentPathResourceImporter, + }, + Schema: metadata.GetSchemaFromExtendedSchema(dhcpV4StaticBindingConfigSchema), + } +} + +func resourceNsxtVpcSubnetDhcpV4StaticBindingConfigExists(sessionContext utl.SessionContext, parentPath string, id string, connector client.Connector) (bool, error) { + var err error + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 4) + if pathErr != nil { + return false, pathErr + } + client := clientLayer.NewDhcpStaticBindingConfigsClient(connector) + _, err = client.Get(parents[0], parents[1], parents[2], parents[3], id) + if err == nil { + return true, nil + } + + if isNotFoundError(err) { + return false, nil + } + + return false, logAPIError("Error retrieving resource", err) +} + +func resourceNsxtVpcSubnetDhcpV4StaticBindingConfigCreate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id, err := getOrGenerateIDWithParent(d, m, resourceNsxtVpcSubnetDhcpV4StaticBindingConfigExists) + if err != nil { + return err + } + + parentPath := d.Get("parent_path").(string) + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 4) + if pathErr != nil { + return pathErr + } + displayName := d.Get("display_name").(string) + description := d.Get("description").(string) + tags := getPolicyTagsFromSchema(d) + + obj := model.DhcpV4StaticBindingConfig{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + ResourceType: model.DhcpStaticBindingConfig_RESOURCE_TYPE_DHCPV4STATICBINDINGCONFIG, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, dhcpV4StaticBindingConfigSchema, "", nil); err != nil { + return err + } + + log.Printf("[INFO] Creating DhcpV4StaticBindingConfig with ID %s", id) + + converter := bindings.NewTypeConverter() + convObj, convErrs := converter.ConvertToVapi(obj, model.DhcpV4StaticBindingConfigBindingType()) + if convErrs != nil { + return convErrs[0] + } + + client := clientLayer.NewDhcpStaticBindingConfigsClient(connector) + err = client.Patch(parents[0], parents[1], parents[2], parents[3], id, convObj.(*data.StructValue)) + if err != nil { + return handleCreateError("DhcpV4StaticBindingConfig", id, err) + } + d.SetId(id) + d.Set("nsx_id", id) + + return resourceNsxtVpcSubnetDhcpV4StaticBindingConfigRead(d, m) +} + +func resourceNsxtVpcSubnetDhcpV4StaticBindingConfigRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining DhcpV4StaticBindingConfig ID") + } + + client := clientLayer.NewDhcpStaticBindingConfigsClient(connector) + parentPath := d.Get("parent_path").(string) + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 4) + if pathErr != nil { + return pathErr + } + dhcpObj, err := client.Get(parents[0], parents[1], parents[2], parents[3], id) + if err != nil { + return handleReadError(d, "DhcpV4StaticBindingConfig", id, err) + } + + converter := bindings.NewTypeConverter() + convObj, errs := converter.ConvertToGolang(dhcpObj, model.DhcpV4StaticBindingConfigBindingType()) + if errs != nil { + return errs[0] + } + obj := convObj.(model.DhcpV4StaticBindingConfig) + if obj.ResourceType != "DhcpV4StaticBindingConfig" { + return handleReadError(d, "DhcpV4 Static Binding Config", id, fmt.Errorf("Unexpected ResourceType")) + } + + setPolicyTagsInSchema(d, obj.Tags) + d.Set("nsx_id", id) + d.Set("display_name", obj.DisplayName) + d.Set("description", obj.Description) + d.Set("revision", obj.Revision) + d.Set("path", obj.Path) + + elem := reflect.ValueOf(&obj).Elem() + return metadata.StructToSchema(elem, d, dhcpV4StaticBindingConfigSchema, "", nil) +} + +func resourceNsxtVpcSubnetDhcpV4StaticBindingConfigUpdate(d *schema.ResourceData, m interface{}) error { + + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining DhcpV4StaticBindingConfig ID") + } + + parentPath := d.Get("parent_path").(string) + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 4) + if pathErr != nil { + return pathErr + } + description := d.Get("description").(string) + displayName := d.Get("display_name").(string) + tags := getPolicyTagsFromSchema(d) + + revision := int64(d.Get("revision").(int)) + + obj := model.DhcpV4StaticBindingConfig{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + Revision: &revision, + ResourceType: model.DhcpStaticBindingConfig_RESOURCE_TYPE_DHCPV4STATICBINDINGCONFIG, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, dhcpV4StaticBindingConfigSchema, "", nil); err != nil { + return err + } + + converter := bindings.NewTypeConverter() + convObj, convErrs := converter.ConvertToVapi(obj, model.DhcpV4StaticBindingConfigBindingType()) + if convErrs != nil { + return convErrs[0] + } + + client := clientLayer.NewDhcpStaticBindingConfigsClient(connector) + _, err := client.Update(parents[0], parents[1], parents[2], parents[3], id, convObj.(*data.StructValue)) + if err != nil { + // Trigger partial update to avoid terraform updating state based on failed intent + // TODO - move this into handleUpdateError + d.Partial(true) + return handleUpdateError("DhcpV4StaticBindingConfig", id, err) + } + + return resourceNsxtVpcSubnetDhcpV4StaticBindingConfigRead(d, m) +} + +func resourceNsxtVpcSubnetDhcpV4StaticBindingConfigDelete(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining DhcpV4StaticBindingConfig ID") + } + + connector := getPolicyConnector(m) + parentPath := d.Get("parent_path").(string) + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 4) + if pathErr != nil { + return pathErr + } + + client := clientLayer.NewDhcpStaticBindingConfigsClient(connector) + err := client.Delete(parents[0], parents[1], parents[2], parents[3], id) + + if err != nil { + return handleDeleteError("DhcpV4StaticBindingConfig", id, err) + } + + return nil +} diff --git a/nsxt/resource_nsxt_vpc_dhcp_v4_static_binding_config_test.go b/nsxt/resource_nsxt_vpc_dhcp_v4_static_binding_config_test.go new file mode 100644 index 000000000..66fdf00ae --- /dev/null +++ b/nsxt/resource_nsxt_vpc_dhcp_v4_static_binding_config_test.go @@ -0,0 +1,271 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +var accTestVpcSubnetDhcpV4StaticBindingConfigCreateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform created", + "gateway_address": "192.168.240.1", + "host_name": "test-create", + "mac_address": "10:0e:00:11:22:02", + "lease_time": "162", + "ip_address": "192.168.240.41", + "opt121-1-gw": "192.168.240.6", + "opt121-1-net": "192.168.244.0/24", + "opt121-2-gw": "192.168.240.7", + "opt121-2-net": "192.168.245.0/24", + "opt-other-code": "35", + "opt-other-value": "50", +} + +var accTestVpcSubnetDhcpV4StaticBindingConfigUpdateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform updated", + "gateway_address": "192.168.240.2", + "host_name": "test-update", + "mac_address": "10:ff:22:11:cc:02", + "lease_time": "500", + "ip_address": "192.168.240.42", + "opt121-1-gw": "192.168.240.8", + "opt121-1-net": "192.168.244.0/24", + "opt121-2-gw": "192.168.240.9", + "opt121-2-net": "192.168.245.0/24", + "opt-other-code": "35", + "opt-other-value": "45", +} + +func TestAccResourceNsxtVpcSubnetDhcpV4StaticBindingConfig_basic(t *testing.T) { + testResourceName := "nsxt_vpc_dhcp_v4_static_binding.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccOnlyVPC(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtVpcSubnetDhcpV4StaticBindingConfigCheckDestroy(state, accTestVpcSubnetDhcpV4StaticBindingConfigUpdateAttributes["display_name"]) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtVpcSubnetDhcpV4StaticBindingConfigTemplate(true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcSubnetDhcpV4StaticBindingConfigExists(accTestVpcSubnetDhcpV4StaticBindingConfigCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestVpcSubnetDhcpV4StaticBindingConfigCreateAttributes["display_name"]), + resource.TestCheckResourceAttrSet(testResourceName, "parent_path"), + resource.TestCheckResourceAttr(testResourceName, "description", accTestVpcSubnetDhcpV4StaticBindingConfigCreateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "gateway_address", accTestVpcSubnetDhcpV4StaticBindingConfigCreateAttributes["gateway_address"]), + resource.TestCheckResourceAttr(testResourceName, "host_name", accTestVpcSubnetDhcpV4StaticBindingConfigCreateAttributes["host_name"]), + resource.TestCheckResourceAttr(testResourceName, "mac_address", accTestVpcSubnetDhcpV4StaticBindingConfigCreateAttributes["mac_address"]), + resource.TestCheckResourceAttr(testResourceName, "lease_time", accTestVpcSubnetDhcpV4StaticBindingConfigCreateAttributes["lease_time"]), + resource.TestCheckResourceAttr(testResourceName, "ip_address", accTestVpcSubnetDhcpV4StaticBindingConfigCreateAttributes["ip_address"]), + resource.TestCheckResourceAttr(testResourceName, "options.0.option121.0.static_route.#", "2"), + resource.TestCheckResourceAttr(testResourceName, "options.0.other.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "options.0.option121.0.static_route.0.next_hop", accTestVpcSubnetDhcpV4StaticBindingConfigCreateAttributes["opt121-1-gw"]), + resource.TestCheckResourceAttr(testResourceName, "options.0.option121.0.static_route.0.network", accTestVpcSubnetDhcpV4StaticBindingConfigCreateAttributes["opt121-1-net"]), + resource.TestCheckResourceAttr(testResourceName, "options.0.option121.0.static_route.1.next_hop", accTestVpcSubnetDhcpV4StaticBindingConfigCreateAttributes["opt121-2-gw"]), + resource.TestCheckResourceAttr(testResourceName, "options.0.option121.0.static_route.1.network", accTestVpcSubnetDhcpV4StaticBindingConfigCreateAttributes["opt121-2-net"]), + resource.TestCheckResourceAttr(testResourceName, "options.0.other.0.code", accTestVpcSubnetDhcpV4StaticBindingConfigCreateAttributes["opt-other-code"]), + resource.TestCheckResourceAttr(testResourceName, "options.0.other.0.values.0", accTestVpcSubnetDhcpV4StaticBindingConfigCreateAttributes["opt-other-value"]), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNsxtVpcSubnetDhcpV4StaticBindingConfigTemplate(false), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcSubnetDhcpV4StaticBindingConfigExists(accTestVpcSubnetDhcpV4StaticBindingConfigUpdateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestVpcSubnetDhcpV4StaticBindingConfigUpdateAttributes["display_name"]), + resource.TestCheckResourceAttrSet(testResourceName, "parent_path"), + resource.TestCheckResourceAttr(testResourceName, "description", accTestVpcSubnetDhcpV4StaticBindingConfigUpdateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "gateway_address", accTestVpcSubnetDhcpV4StaticBindingConfigUpdateAttributes["gateway_address"]), + resource.TestCheckResourceAttr(testResourceName, "host_name", accTestVpcSubnetDhcpV4StaticBindingConfigUpdateAttributes["host_name"]), + resource.TestCheckResourceAttr(testResourceName, "mac_address", accTestVpcSubnetDhcpV4StaticBindingConfigUpdateAttributes["mac_address"]), + resource.TestCheckResourceAttr(testResourceName, "lease_time", accTestVpcSubnetDhcpV4StaticBindingConfigUpdateAttributes["lease_time"]), + resource.TestCheckResourceAttr(testResourceName, "ip_address", accTestVpcSubnetDhcpV4StaticBindingConfigUpdateAttributes["ip_address"]), + resource.TestCheckResourceAttr(testResourceName, "options.0.option121.0.static_route.#", "2"), + resource.TestCheckResourceAttr(testResourceName, "options.0.other.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "options.0.option121.0.static_route.0.next_hop", accTestVpcSubnetDhcpV4StaticBindingConfigUpdateAttributes["opt121-1-gw"]), + resource.TestCheckResourceAttr(testResourceName, "options.0.option121.0.static_route.0.network", accTestVpcSubnetDhcpV4StaticBindingConfigUpdateAttributes["opt121-1-net"]), + resource.TestCheckResourceAttr(testResourceName, "options.0.option121.0.static_route.1.next_hop", accTestVpcSubnetDhcpV4StaticBindingConfigUpdateAttributes["opt121-2-gw"]), + resource.TestCheckResourceAttr(testResourceName, "options.0.option121.0.static_route.1.network", accTestVpcSubnetDhcpV4StaticBindingConfigUpdateAttributes["opt121-2-net"]), + resource.TestCheckResourceAttr(testResourceName, "options.0.other.0.code", accTestVpcSubnetDhcpV4StaticBindingConfigUpdateAttributes["opt-other-code"]), + resource.TestCheckResourceAttr(testResourceName, "options.0.other.0.values.0", accTestVpcSubnetDhcpV4StaticBindingConfigUpdateAttributes["opt-other-value"]), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNsxtVpcSubnetDhcpV4StaticBindingConfigMinimalistic(), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcSubnetDhcpV4StaticBindingConfigExists(accTestVpcSubnetDhcpV4StaticBindingConfigCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttrSet(testResourceName, "parent_path"), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtVpcSubnetDhcpV4StaticBindingConfig_importBasic(t *testing.T) { + name := getAccTestResourceName() + testResourceName := "nsxt_vpc_dhcp_v4_static_binding.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccOnlyVPC(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtVpcSubnetDhcpV4StaticBindingConfigCheckDestroy(state, name) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtVpcSubnetDhcpV4StaticBindingConfigMinimalistic(), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccResourceNsxtPolicyImportIDRetriever(testResourceName), + }, + }, + }) +} + +func testAccNsxtVpcSubnetDhcpV4StaticBindingConfigExists(displayName string, resourceName string) resource.TestCheckFunc { + return func(state *terraform.State) error { + + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + + rs, ok := state.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Policy DhcpV4StaticBindingConfig resource %s not found in resources", resourceName) + } + + resourceID := rs.Primary.ID + if resourceID == "" { + return fmt.Errorf("Policy DhcpV4StaticBindingConfig resource ID not set in resources") + } + + parentPath := rs.Primary.Attributes["parent_path"] + + exists, err := resourceNsxtVpcSubnetDhcpV4StaticBindingConfigExists(testAccGetSessionContext(), parentPath, resourceID, connector) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("Policy DhcpV4StaticBindingConfig %s does not exist", resourceID) + } + + return nil + } +} + +func testAccNsxtVpcSubnetDhcpV4StaticBindingConfigCheckDestroy(state *terraform.State, displayName string) error { + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + for _, rs := range state.RootModule().Resources { + + if rs.Type != "nsxt_vpc_dhcp_v4_static_binding" { + continue + } + + resourceID := rs.Primary.Attributes["id"] + parentPath := rs.Primary.Attributes["parent_path"] + + exists, err := resourceNsxtVpcSubnetDhcpV4StaticBindingConfigExists(testAccGetSessionContext(), parentPath, resourceID, connector) + if err == nil { + return err + } + + if exists { + return fmt.Errorf("Policy DhcpV4StaticBindingConfig %s still exists", displayName) + } + } + return nil +} + +func testAccNsxtVpcSubnetDhcpV4StaticBindingConfigPrerequisites() string { + return fmt.Sprintf(` +resource "nsxt_vpc_subnet" "test" { +%s + display_name = "test-subnet" + ip_addresses = ["192.168.240.0/26"] + access_mode = "Isolated" + dhcp_config { + mode = "DHCP_SERVER" + dhcp_server_additional_config { + reserved_ip_ranges = ["192.168.240.40-192.168.240.60"] + } + } +} +`, testAccNsxtPolicyMultitenancyContext()) +} + +func testAccNsxtVpcSubnetDhcpV4StaticBindingConfigTemplate(createFlow bool) string { + var attrMap map[string]string + if createFlow { + attrMap = accTestVpcSubnetDhcpV4StaticBindingConfigCreateAttributes + } else { + attrMap = accTestVpcSubnetDhcpV4StaticBindingConfigUpdateAttributes + } + return testAccNsxtVpcSubnetDhcpV4StaticBindingConfigPrerequisites() + fmt.Sprintf(` +resource "nsxt_vpc_dhcp_v4_static_binding" "test" { + parent_path = nsxt_vpc_subnet.test.path + display_name = "%s" + description = "%s" + gateway_address = "%s" + host_name = "%s" + mac_address = "%s" + lease_time = %s + ip_address = "%s" + + options { + option121 { + static_route { + next_hop = "%s" + network = "%s" + } + static_route { + next_hop = "%s" + network = "%s" + } + } + other { + code = %s + values = ["%s"] + } + } + tag { + scope = "scope1" + tag = "tag1" + } +}`, attrMap["display_name"], attrMap["description"], attrMap["gateway_address"], attrMap["host_name"], + attrMap["mac_address"], attrMap["lease_time"], attrMap["ip_address"], attrMap["opt121-1-gw"], attrMap["opt121-1-net"], + attrMap["opt121-2-gw"], attrMap["opt121-2-net"], attrMap["opt-other-code"], attrMap["opt-other-value"]) +} + +func testAccNsxtVpcSubnetDhcpV4StaticBindingConfigMinimalistic() string { + attrMap := accTestVpcSubnetDhcpV4StaticBindingConfigCreateAttributes + return testAccNsxtVpcSubnetDhcpV4StaticBindingConfigPrerequisites() + fmt.Sprintf(` +resource "nsxt_vpc_dhcp_v4_static_binding" "test" { + display_name = "%s" + parent_path = nsxt_vpc_subnet.test.path + ip_address = "%s" + mac_address = "%s" +}`, accTestVpcSubnetDhcpV4StaticBindingConfigUpdateAttributes["display_name"], attrMap["ip_address"], attrMap["mac_address"]) +} diff --git a/nsxt/resource_nsxt_vpc_external_address.go b/nsxt/resource_nsxt_vpc_external_address.go new file mode 100644 index 000000000..20f674e70 --- /dev/null +++ b/nsxt/resource_nsxt_vpc_external_address.go @@ -0,0 +1,126 @@ +package nsxt + +import ( + "log" + + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/vpcs/subnets" +) + +func resourceNsxtVpcExternalAddress() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtVpcExternalAddressCreate, + Read: resourceNsxtVpcExternalAddressRead, + Update: resourceNsxtVpcExternalAddressUpdate, + Delete: resourceNsxtVpcExternalAddressDelete, + Importer: &schema.ResourceImporter{ + State: nsxtVpcExternalAddressImporter, + }, + Schema: map[string]*schema.Schema{ + "parent_path": getPolicyPathSchema(true, true, "port path for address binding"), + "allocated_external_ip_path": getPolicyPathSchema(true, false, "allocated ip path"), + "external_ip_address": { + Type: schema.TypeString, + Description: "Computed external IP address", + Computed: true, + }, + }, + } +} + +func updatePort(d *schema.ResourceData, m interface{}, deleteFlow bool) error { + portPath := d.Get("parent_path").(string) + addressPath := d.Get("allocated_external_ip_path").(string) + log.Printf("[DEBUG] Updating external address binding for port %s", portPath) + + parents, pathErr := parseStandardPolicyPathVerifySize(portPath, 5) + if pathErr != nil { + return pathErr + } + + // Get port in order to Update + connector := getPolicyConnector(m) + portClient := subnets.NewPortsClient(connector) + port, err := portClient.Get(parents[0], parents[1], parents[2], parents[3], parents[4]) + if err != nil { + return err + } + + if deleteFlow { + port.ExternalAddressBinding = nil + } else { + port.ExternalAddressBinding = &model.ExternalAddressBinding{ + AllocatedExternalIpPath: &addressPath, + } + } + + _, err = portClient.Update(parents[0], parents[1], parents[2], parents[3], parents[4], port) + + return err +} + +func resourceNsxtVpcExternalAddressCreate(d *schema.ResourceData, m interface{}) error { + err := updatePort(d, m, false) + if err != nil { + return handleCreateError("External Address", "", err) + } + d.SetId(newUUID()) + return resourceNsxtVpcExternalAddressRead(d, m) +} + +func resourceNsxtVpcExternalAddressRead(d *schema.ResourceData, m interface{}) error { + portPath := d.Get("parent_path").(string) + + parents, pathErr := parseStandardPolicyPathVerifySize(portPath, 5) + if pathErr != nil { + return pathErr + } + + connector := getPolicyConnector(m) + portClient := subnets.NewPortsClient(connector) + port, err := portClient.Get(parents[0], parents[1], parents[2], parents[3], parents[4]) + if err != nil { + return handleReadError(d, "External Address", "", err) + } + + if port.ExternalAddressBinding == nil { + d.Set("allocated_external_ip_path", "") + d.Set("external_ip_address", "") + return nil + } + + d.Set("allocated_external_ip_path", port.ExternalAddressBinding.AllocatedExternalIpPath) + d.Set("external_ip_address", port.ExternalAddressBinding.ExternalIpAddress) + return nil +} + +func resourceNsxtVpcExternalAddressUpdate(d *schema.ResourceData, m interface{}) error { + err := updatePort(d, m, false) + if err != nil { + // Trigger partial update to avoid terraform updating state based on failed intent + // TODO - move this into handleUpdateError + d.Partial(true) + return handleUpdateError("External Address", "", err) + } + return resourceNsxtVpcExternalAddressRead(d, m) +} + +func resourceNsxtVpcExternalAddressDelete(d *schema.ResourceData, m interface{}) error { + return updatePort(d, m, true) +} + +func nsxtVpcExternalAddressImporter(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + importID := d.Id() + if isSpaceString(importID) { + return []*schema.ResourceData{d}, ErrEmptyImportID + } + if isPolicyPath(importID) { + // Since external address is part of Port API, parent path is the port URL + d.SetId(newUUID()) + d.Set("parent_path", importID) + return []*schema.ResourceData{d}, nil + } + return []*schema.ResourceData{d}, ErrNotAPolicyPath +} diff --git a/nsxt/resource_nsxt_vpc_group_test.go b/nsxt/resource_nsxt_vpc_group_test.go index cbb44e594..9a0a2b0c3 100644 --- a/nsxt/resource_nsxt_vpc_group_test.go +++ b/nsxt/resource_nsxt_vpc_group_test.go @@ -25,7 +25,7 @@ func TestAccResourceNsxtVPCGroup_basicImport(t *testing.T) { }, Steps: []resource.TestStep{ { - Config: testAccNsxtPolicyGroupIPAddressImportTemplate(name, resourceName, true), + Config: testAccNsxtVPCGroupAddressCreateTemplate(name), }, { ResourceName: testResourceName, @@ -53,7 +53,7 @@ func TestAccResourceNsxtVPCGroup_addressCriteria(t *testing.T) { }, Steps: []resource.TestStep{ { - Config: testAccNsxtVPCGroupAddressCreateTemplate(name, resourceName, true), + Config: testAccNsxtVPCGroupAddressCreateTemplate(name), Check: resource.ComposeTestCheckFunc( testAccNsxtPolicyGroupExists(testResourceName, defaultDomain), resource.TestCheckResourceAttr(testResourceName, "display_name", name), @@ -69,7 +69,7 @@ func TestAccResourceNsxtVPCGroup_addressCriteria(t *testing.T) { ), }, { - Config: testAccNsxtVPCGroupAddressUpdateTemplate(name, resourceName, true), + Config: testAccNsxtVPCGroupAddressUpdateTemplate(name), Check: resource.ComposeTestCheckFunc( testAccNsxtPolicyGroupExists(testResourceName, defaultDomain), resource.TestCheckResourceAttr(testResourceName, "display_name", name), @@ -112,13 +112,10 @@ func testAccNsxtVPCGroupCheckDestroy(state *terraform.State, displayName string) return nil } -func testAccNsxtVPCGroupAddressCreateTemplate(name, resourceName string, withContext bool) string { - context := "" - if withContext { - context = testAccNsxtPolicyMultitenancyContext() - } +func testAccNsxtVPCGroupAddressCreateTemplate(name string) string { + context := testAccNsxtPolicyMultitenancyContext() return fmt.Sprintf(` -resource "%s" "test" { +resource "nsxt_vpc_group" "test" { %s display_name = "%s" description = "Acceptance Test" @@ -149,16 +146,20 @@ resource "%s" "test" { tag = "tag2" } } -`, resourceName, context, name) + +data "nsxt_vpc_group" "test" { +%s + display_name = "%s" + + depends_on = [nsxt_vpc_group.test] +} +`, context, name, context, name) } -func testAccNsxtVPCGroupAddressUpdateTemplate(name, resourceName string, withContext bool) string { - context := "" - if withContext { - context = testAccNsxtPolicyMultitenancyContext() - } +func testAccNsxtVPCGroupAddressUpdateTemplate(name string) string { + context := testAccNsxtPolicyMultitenancyContext() return fmt.Sprintf(` -resource "%s" "test" { +resource "nsxt_vpc_group" "test" { %s display_name = "%s" description = "Acceptance Test" @@ -169,5 +170,5 @@ resource "%s" "test" { } } } -`, resourceName, context, name) +`, context, name) } diff --git a/nsxt/resource_nsxt_vpc_ip_address_allocation.go b/nsxt/resource_nsxt_vpc_ip_address_allocation.go new file mode 100644 index 000000000..4dc9b836b --- /dev/null +++ b/nsxt/resource_nsxt_vpc_ip_address_allocation.go @@ -0,0 +1,250 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "log" + "reflect" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + clientLayer "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/vpcs" + + utl "github.com/vmware/terraform-provider-nsxt/api/utl" + "github.com/vmware/terraform-provider-nsxt/nsxt/metadata" +) + +var vpcIpAddressAllocationIpAddressTypeValues = []string{ + model.VpcIpAddressAllocation_IP_ADDRESS_TYPE_IPV4, + model.VpcIpAddressAllocation_IP_ADDRESS_TYPE_IPV6, +} + +var vpcIpAddressAllocationIpAddressBlockVisibilityValues = []string{ + model.VpcIpAddressAllocation_IP_ADDRESS_BLOCK_VISIBILITY_EXTERNAL, + model.VpcIpAddressAllocation_IP_ADDRESS_BLOCK_VISIBILITY_PRIVATE, + model.VpcIpAddressAllocation_IP_ADDRESS_BLOCK_VISIBILITY_PRIVATE_TGW, +} + +var vpcIpAddressAllocationSchema = map[string]*metadata.ExtendedSchema{ + "nsx_id": metadata.GetExtendedSchema(getNsxIDSchema()), + "path": metadata.GetExtendedSchema(getPathSchema()), + "display_name": metadata.GetExtendedSchema(getDisplayNameSchema()), + "description": metadata.GetExtendedSchema(getDescriptionSchema()), + "revision": metadata.GetExtendedSchema(getRevisionSchema()), + "tag": metadata.GetExtendedSchema(getTagsSchema()), + "context": metadata.GetExtendedSchema(getContextSchema(true, false, true)), + "allocation_ips": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateCidrOrIPOrRange(), + Optional: true, + Computed: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "AllocationIps", + OmitIfEmpty: true, + }, + }, + "allocation_size": { + Schema: schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "int", + SdkFieldName: "AllocationSize", + OmitIfEmpty: true, + }, + }, + "ip_address_type": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(vpcIpAddressAllocationIpAddressTypeValues, false), + Optional: true, + Default: model.VpcIpAddressAllocation_IP_ADDRESS_TYPE_IPV4, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "IpAddressType", + }, + }, + "ip_address_block_visibility": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(vpcIpAddressAllocationIpAddressBlockVisibilityValues, false), + Optional: true, + Default: model.VpcIpAddressAllocation_IP_ADDRESS_BLOCK_VISIBILITY_EXTERNAL, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "IpAddressBlockVisibility", + }, + }, + "ip_block": { + Schema: schema.Schema{ + Type: schema.TypeString, + Optional: true, + ValidateFunc: validatePolicyPath(), + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "IpBlock", + }, + }, +} + +func resourceNsxtVpcIpAddressAllocation() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtVpcIpAddressAllocationCreate, + Read: resourceNsxtVpcIpAddressAllocationRead, + Update: resourceNsxtVpcIpAddressAllocationUpdate, + Delete: resourceNsxtVpcIpAddressAllocationDelete, + Importer: &schema.ResourceImporter{ + State: nsxtVPCPathResourceImporter, + }, + Schema: metadata.GetSchemaFromExtendedSchema(vpcIpAddressAllocationSchema), + } +} + +func resourceNsxtVpcIpAddressAllocationExists(sessionContext utl.SessionContext, id string, connector client.Connector) (bool, error) { + var err error + parents := getVpcParentsFromContext(sessionContext) + client := clientLayer.NewIpAddressAllocationsClient(connector) + _, err = client.Get(parents[0], parents[1], parents[2], id) + if err == nil { + return true, nil + } + + if isNotFoundError(err) { + return false, nil + } + + return false, logAPIError("Error retrieving resource", err) +} + +func resourceNsxtVpcIpAddressAllocationCreate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id, err := getOrGenerateID2(d, m, resourceNsxtVpcIpAddressAllocationExists) + if err != nil { + return err + } + + parents := getVpcParentsFromContext(getSessionContext(d, m)) + displayName := d.Get("display_name").(string) + description := d.Get("description").(string) + tags := getPolicyTagsFromSchema(d) + + obj := model.VpcIpAddressAllocation{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, vpcIpAddressAllocationSchema, "", nil); err != nil { + return err + } + + log.Printf("[INFO] Creating VpcIpAddressAllocation with ID %s", id) + + client := clientLayer.NewIpAddressAllocationsClient(connector) + err = client.Patch(parents[0], parents[1], parents[2], id, obj) + if err != nil { + return handleCreateError("VpcIpAddressAllocation", id, err) + } + d.SetId(id) + d.Set("nsx_id", id) + + return resourceNsxtVpcIpAddressAllocationRead(d, m) +} + +func resourceNsxtVpcIpAddressAllocationRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining VpcIpAddressAllocation ID") + } + + client := clientLayer.NewIpAddressAllocationsClient(connector) + parents := getVpcParentsFromContext(getSessionContext(d, m)) + obj, err := client.Get(parents[0], parents[1], parents[2], id) + if err != nil { + return handleReadError(d, "VpcIpAddressAllocation", id, err) + } + + setPolicyTagsInSchema(d, obj.Tags) + d.Set("nsx_id", id) + d.Set("display_name", obj.DisplayName) + d.Set("description", obj.Description) + d.Set("revision", obj.Revision) + d.Set("path", obj.Path) + + elem := reflect.ValueOf(&obj).Elem() + return metadata.StructToSchema(elem, d, vpcIpAddressAllocationSchema, "", nil) +} + +func resourceNsxtVpcIpAddressAllocationUpdate(d *schema.ResourceData, m interface{}) error { + + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining VpcIpAddressAllocation ID") + } + + parents := getVpcParentsFromContext(getSessionContext(d, m)) + description := d.Get("description").(string) + displayName := d.Get("display_name").(string) + tags := getPolicyTagsFromSchema(d) + + revision := int64(d.Get("revision").(int)) + + obj := model.VpcIpAddressAllocation{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + Revision: &revision, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, vpcIpAddressAllocationSchema, "", nil); err != nil { + return err + } + client := clientLayer.NewIpAddressAllocationsClient(connector) + _, err := client.Update(parents[0], parents[1], parents[2], id, obj) + if err != nil { + // Trigger partial update to avoid terraform updating state based on failed intent + // TODO - move this into handleUpdateError + d.Partial(true) + return handleUpdateError("VpcIpAddressAllocation", id, err) + } + + return resourceNsxtVpcIpAddressAllocationRead(d, m) +} + +func resourceNsxtVpcIpAddressAllocationDelete(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining VpcIpAddressAllocation ID") + } + + connector := getPolicyConnector(m) + parents := getVpcParentsFromContext(getSessionContext(d, m)) + + client := clientLayer.NewIpAddressAllocationsClient(connector) + err := client.Delete(parents[0], parents[1], parents[2], id) + + if err != nil { + return handleDeleteError("VpcIpAddressAllocation", id, err) + } + + return nil +} diff --git a/nsxt/resource_nsxt_vpc_ip_address_allocation_test.go b/nsxt/resource_nsxt_vpc_ip_address_allocation_test.go new file mode 100644 index 000000000..a009a7b74 --- /dev/null +++ b/nsxt/resource_nsxt_vpc_ip_address_allocation_test.go @@ -0,0 +1,190 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +var accTestVpcIpAddressAllocationCreateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform created", + "allocation_size": "2", +} + +var accTestVpcIpAddressAllocationUpdateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform updated", + "allocation_size": "2", +} + +func TestAccResourceNsxtVpcIpAddressAllocation_basic(t *testing.T) { + testResourceName := "nsxt_vpc_ip_address_allocation.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccOnlyVPC(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtVpcIpAddressAllocationCheckDestroy(state, accTestVpcIpAddressAllocationUpdateAttributes["display_name"]) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtVpcIpAddressAllocationTemplate(true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcIpAddressAllocationExists(accTestVpcIpAddressAllocationCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestVpcIpAddressAllocationCreateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestVpcIpAddressAllocationCreateAttributes["description"]), + resource.TestCheckResourceAttrSet(testResourceName, "allocation_ips"), + resource.TestCheckResourceAttr(testResourceName, "allocation_size", accTestVpcIpAddressAllocationCreateAttributes["allocation_size"]), + resource.TestCheckResourceAttrSet(testResourceName, "ip_address_type"), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNsxtVpcIpAddressAllocationTemplate(false), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcIpAddressAllocationExists(accTestVpcIpAddressAllocationUpdateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestVpcIpAddressAllocationUpdateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestVpcIpAddressAllocationUpdateAttributes["description"]), + resource.TestCheckResourceAttrSet(testResourceName, "allocation_ips"), + resource.TestCheckResourceAttr(testResourceName, "allocation_size", accTestVpcIpAddressAllocationUpdateAttributes["allocation_size"]), + resource.TestCheckResourceAttrSet(testResourceName, "ip_address_type"), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNsxtVpcIpAddressAllocationMinimalistic(), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcIpAddressAllocationExists(accTestVpcIpAddressAllocationCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtVpcIpAddressAllocation_importBasic(t *testing.T) { + name := getAccTestResourceName() + testResourceName := "nsxt_vpc_ip_address_allocation.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccOnlyVPC(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtVpcIpAddressAllocationCheckDestroy(state, name) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtVpcIpAddressAllocationMinimalistic(), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccResourceNsxtPolicyImportIDRetriever(testResourceName), + }, + }, + }) +} + +func testAccNsxtVpcIpAddressAllocationExists(displayName string, resourceName string) resource.TestCheckFunc { + return func(state *terraform.State) error { + + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + + rs, ok := state.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Policy VpcIpAddressAllocation resource %s not found in resources", resourceName) + } + + resourceID := rs.Primary.ID + if resourceID == "" { + return fmt.Errorf("Policy VpcIpAddressAllocation resource ID not set in resources") + } + + exists, err := resourceNsxtVpcIpAddressAllocationExists(testAccGetSessionContext(), resourceID, connector) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("Policy VpcIpAddressAllocation %s does not exist", resourceID) + } + + return nil + } +} + +func testAccNsxtVpcIpAddressAllocationCheckDestroy(state *terraform.State, displayName string) error { + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + for _, rs := range state.RootModule().Resources { + + if rs.Type != "nsxt_vpc_ip_address_allocation" { + continue + } + + resourceID := rs.Primary.Attributes["id"] + exists, err := resourceNsxtVpcIpAddressAllocationExists(testAccGetSessionContext(), resourceID, connector) + if err == nil { + return err + } + + if exists { + return fmt.Errorf("Policy VpcIpAddressAllocation %s still exists", displayName) + } + } + return nil +} + +func testAccNsxtVpcIpAddressAllocationTemplate(createFlow bool) string { + var attrMap map[string]string + if createFlow { + attrMap = accTestVpcIpAddressAllocationCreateAttributes + } else { + attrMap = accTestVpcIpAddressAllocationUpdateAttributes + } + return fmt.Sprintf(` +resource "nsxt_vpc_ip_address_allocation" "test" { + %s + display_name = "%s" + description = "%s" + allocation_size = %s + tag { + scope = "scope1" + tag = "tag1" + } +} + +data "nsxt_vpc_ip_address_allocation" "test" { + %s + allocation_ips = nsxt_vpc_ip_address_allocation.test.allocation_ips +}`, testAccNsxtPolicyMultitenancyContext(), attrMap["display_name"], attrMap["description"], attrMap["allocation_size"], testAccNsxtPolicyMultitenancyContext()) +} + +func testAccNsxtVpcIpAddressAllocationMinimalistic() string { + return fmt.Sprintf(` +resource "nsxt_vpc_ip_address_allocation" "test" { + %s + display_name = "%s" + allocation_size = %s +} + +data "nsxt_vpc_ip_address_allocation" "test" { + %s + allocation_ips = nsxt_vpc_ip_address_allocation.test.allocation_ips +}`, testAccNsxtPolicyMultitenancyContext(), accTestVpcIpAddressAllocationUpdateAttributes["display_name"], accTestVpcIpAddressAllocationUpdateAttributes["allocation_size"], testAccNsxtPolicyMultitenancyContext()) +} diff --git a/nsxt/resource_nsxt_vpc_nat_rule.go b/nsxt/resource_nsxt_vpc_nat_rule.go new file mode 100644 index 000000000..276183ae5 --- /dev/null +++ b/nsxt/resource_nsxt_vpc_nat_rule.go @@ -0,0 +1,299 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "log" + "reflect" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + clientLayer "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/vpcs/nat" + + utl "github.com/vmware/terraform-provider-nsxt/api/utl" + "github.com/vmware/terraform-provider-nsxt/nsxt/metadata" +) + +var policyVpcNatRuleActionValues = []string{ + model.PolicyVpcNatRule_ACTION_SNAT, + model.PolicyVpcNatRule_ACTION_DNAT, + model.PolicyVpcNatRule_ACTION_REFLEXIVE, +} + +var policyVpcNatRuleFirewallMatchValues = []string{ + model.PolicyVpcNatRule_FIREWALL_MATCH_MATCH_EXTERNAL_ADDRESS, + model.PolicyVpcNatRule_FIREWALL_MATCH_MATCH_INTERNAL_ADDRESS, + model.PolicyVpcNatRule_FIREWALL_MATCH_BYPASS, +} + +var policyVpcNatRuleSchema = map[string]*metadata.ExtendedSchema{ + "nsx_id": metadata.GetExtendedSchema(getNsxIDSchema()), + "path": metadata.GetExtendedSchema(getPathSchema()), + "display_name": metadata.GetExtendedSchema(getDisplayNameSchema()), + "description": metadata.GetExtendedSchema(getDescriptionSchema()), + "revision": metadata.GetExtendedSchema(getRevisionSchema()), + "tag": metadata.GetExtendedSchema(getTagsSchema()), + "parent_path": metadata.GetExtendedSchema(getPolicyPathSchema(true, true, "Policy path of the parent")), + "translated_network": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateCidrOrIPOrRangeList(), + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "TranslatedNetwork", + }, + }, + "logging": { + Schema: schema.Schema{ + Type: schema.TypeBool, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "bool", + SdkFieldName: "Logging", + }, + }, + "destination_network": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateCidrOrIPOrRangeList(), + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "DestinationNetwork", + OmitIfEmpty: true, + }, + }, + "action": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(policyVpcNatRuleActionValues, false), + Required: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "Action", + }, + }, + "firewall_match": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(policyVpcNatRuleFirewallMatchValues, false), + Optional: true, + Default: model.PolicyVpcNatRule_FIREWALL_MATCH_MATCH_INTERNAL_ADDRESS, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "FirewallMatch", + }, + }, + "source_network": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateCidrOrIPOrRangeList(), + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "SourceNetwork", + OmitIfEmpty: true, + }, + }, + "enabled": { + Schema: schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "bool", + SdkFieldName: "Enabled", + }, + }, + "sequence_number": { + Schema: schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "int", + SdkFieldName: "SequenceNumber", + }, + }, +} + +func resourceNsxtPolicyVpcNatRule() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtPolicyVpcNatRuleCreate, + Read: resourceNsxtPolicyVpcNatRuleRead, + Update: resourceNsxtPolicyVpcNatRuleUpdate, + Delete: resourceNsxtPolicyVpcNatRuleDelete, + Importer: &schema.ResourceImporter{ + State: nsxtParentPathResourceImporter, + }, + Schema: metadata.GetSchemaFromExtendedSchema(policyVpcNatRuleSchema), + } +} + +func resourceNsxtPolicyVpcNatRuleExists(sessionContext utl.SessionContext, parentPath string, id string, connector client.Connector) (bool, error) { + var err error + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 4) + if pathErr != nil { + return false, pathErr + } + client := clientLayer.NewNatRulesClient(connector) + _, err = client.Get(parents[0], parents[1], parents[2], parents[3], id) + if err == nil { + return true, nil + } + + if isNotFoundError(err) { + return false, nil + } + + return false, logAPIError("Error retrieving resource", err) +} + +func resourceNsxtPolicyVpcNatRuleCreate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id, err := getOrGenerateIDWithParent(d, m, resourceNsxtPolicyVpcNatRuleExists) + if err != nil { + return err + } + + parentPath := d.Get("parent_path").(string) + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 4) + if pathErr != nil { + return pathErr + } + displayName := d.Get("display_name").(string) + description := d.Get("description").(string) + tags := getPolicyTagsFromSchema(d) + + obj := model.PolicyVpcNatRule{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, policyVpcNatRuleSchema, "", nil); err != nil { + return err + } + + log.Printf("[INFO] Creating PolicyVpcNatRule with ID %s", id) + + client := clientLayer.NewNatRulesClient(connector) + err = client.Patch(parents[0], parents[1], parents[2], parents[3], id, obj) + if err != nil { + return handleCreateError("PolicyVpcNatRule", id, err) + } + d.SetId(id) + d.Set("nsx_id", id) + + return resourceNsxtPolicyVpcNatRuleRead(d, m) +} + +func resourceNsxtPolicyVpcNatRuleRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining PolicyVpcNatRule ID") + } + + client := clientLayer.NewNatRulesClient(connector) + parentPath := d.Get("parent_path").(string) + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 4) + if pathErr != nil { + return pathErr + } + obj, err := client.Get(parents[0], parents[1], parents[2], parents[3], id) + if err != nil { + return handleReadError(d, "PolicyVpcNatRule", id, err) + } + + setPolicyTagsInSchema(d, obj.Tags) + d.Set("nsx_id", id) + d.Set("display_name", obj.DisplayName) + d.Set("description", obj.Description) + d.Set("revision", obj.Revision) + d.Set("path", obj.Path) + + elem := reflect.ValueOf(&obj).Elem() + return metadata.StructToSchema(elem, d, policyVpcNatRuleSchema, "", nil) +} + +func resourceNsxtPolicyVpcNatRuleUpdate(d *schema.ResourceData, m interface{}) error { + + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining PolicyVpcNatRule ID") + } + + parentPath := d.Get("parent_path").(string) + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 4) + if pathErr != nil { + return pathErr + } + description := d.Get("description").(string) + displayName := d.Get("display_name").(string) + tags := getPolicyTagsFromSchema(d) + + revision := int64(d.Get("revision").(int)) + + obj := model.PolicyVpcNatRule{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + Revision: &revision, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, policyVpcNatRuleSchema, "", nil); err != nil { + return err + } + client := clientLayer.NewNatRulesClient(connector) + _, err := client.Update(parents[0], parents[1], parents[2], parents[3], id, obj) + if err != nil { + // Trigger partial update to avoid terraform updating state based on failed intent + // TODO - move this into handleUpdateError + d.Partial(true) + return handleUpdateError("PolicyVpcNatRule", id, err) + } + + return resourceNsxtPolicyVpcNatRuleRead(d, m) +} + +func resourceNsxtPolicyVpcNatRuleDelete(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining PolicyVpcNatRule ID") + } + + connector := getPolicyConnector(m) + parentPath := d.Get("parent_path").(string) + parents, pathErr := parseStandardPolicyPathVerifySize(parentPath, 4) + if pathErr != nil { + return pathErr + } + + client := clientLayer.NewNatRulesClient(connector) + err := client.Delete(parents[0], parents[1], parents[2], parents[3], id) + + if err != nil { + return handleDeleteError("PolicyVpcNatRule", id, err) + } + + return nil +} diff --git a/nsxt/resource_nsxt_vpc_nat_rule_test.go b/nsxt/resource_nsxt_vpc_nat_rule_test.go new file mode 100644 index 000000000..66bdc3e41 --- /dev/null +++ b/nsxt/resource_nsxt_vpc_nat_rule_test.go @@ -0,0 +1,309 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +var accTestVpcNatIPAllocationTestName = getAccTestResourceName() +var accTestVpcNatRuleCreateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform created", + "firewall_match": "MATCH_EXTERNAL_ADDRESS", + "logging": "true", + "action": "DNAT", + "source_network": "2.2.2.14", + "translated_network": "2.3.3.24", + "enabled": "false", + "sequence_number": "16", +} + +var accTestVpcNatRuleUpdateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform updated", + "firewall_match": "MATCH_INTERNAL_ADDRESS", + "logging": "false", + "action": "DNAT", + "source_network": "3.3.3.14", + "translated_network": "30.3.3.14", + "enabled": "true", + "sequence_number": "3", +} + +func TestAccResourceNsxtVpcNatRule_basic(t *testing.T) { + testResourceName := "nsxt_vpc_nat_rule.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccOnlyVPC(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtVpcNatRuleCheckDestroy(state, accTestVpcNatRuleUpdateAttributes["display_name"]) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtVpcNatRuleTemplate(true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcNatRuleExists(accTestVpcNatRuleCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestVpcNatRuleCreateAttributes["display_name"]), + resource.TestCheckResourceAttrSet(testResourceName, "parent_path"), + resource.TestCheckResourceAttr(testResourceName, "description", accTestVpcNatRuleCreateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "translated_network", accTestVpcNatRuleCreateAttributes["translated_network"]), + resource.TestCheckResourceAttr(testResourceName, "logging", accTestVpcNatRuleCreateAttributes["logging"]), + resource.TestCheckResourceAttrSet(testResourceName, "destination_network"), + resource.TestCheckResourceAttr(testResourceName, "action", accTestVpcNatRuleCreateAttributes["action"]), + resource.TestCheckResourceAttr(testResourceName, "firewall_match", accTestVpcNatRuleCreateAttributes["firewall_match"]), + resource.TestCheckResourceAttr(testResourceName, "source_network", accTestVpcNatRuleCreateAttributes["source_network"]), + resource.TestCheckResourceAttr(testResourceName, "enabled", accTestVpcNatRuleCreateAttributes["enabled"]), + resource.TestCheckResourceAttr(testResourceName, "sequence_number", accTestVpcNatRuleCreateAttributes["sequence_number"]), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNsxtVpcNatRuleTemplate(false), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcNatRuleExists(accTestVpcNatRuleUpdateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestVpcNatRuleUpdateAttributes["display_name"]), + resource.TestCheckResourceAttrSet(testResourceName, "parent_path"), + resource.TestCheckResourceAttr(testResourceName, "description", accTestVpcNatRuleUpdateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "translated_network", accTestVpcNatRuleUpdateAttributes["translated_network"]), + resource.TestCheckResourceAttr(testResourceName, "logging", accTestVpcNatRuleUpdateAttributes["logging"]), + resource.TestCheckResourceAttrSet(testResourceName, "destination_network"), + resource.TestCheckResourceAttr(testResourceName, "action", accTestVpcNatRuleUpdateAttributes["action"]), + resource.TestCheckResourceAttr(testResourceName, "firewall_match", accTestVpcNatRuleUpdateAttributes["firewall_match"]), + resource.TestCheckResourceAttr(testResourceName, "source_network", accTestVpcNatRuleUpdateAttributes["source_network"]), + resource.TestCheckResourceAttr(testResourceName, "enabled", accTestVpcNatRuleUpdateAttributes["enabled"]), + resource.TestCheckResourceAttr(testResourceName, "sequence_number", accTestVpcNatRuleUpdateAttributes["sequence_number"]), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNsxtVpcNatRuleMinimalistic(), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcNatRuleExists(accTestVpcNatRuleCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtVpcNatRule_changeTypes(t *testing.T) { + testResourceName := "nsxt_vpc_nat_rule.test" + sourceIP := "2.2.2.34" + ruleName := getAccTestResourceName() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccOnlyVPC(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtVpcNatRuleCheckDestroy(state, ruleName) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtVpcNatRuleSnatTemplate(ruleName), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcNatRuleExists(ruleName, testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", ruleName), + resource.TestCheckResourceAttrSet(testResourceName, "parent_path"), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "translated_network"), + resource.TestCheckResourceAttr(testResourceName, "logging", "false"), + resource.TestCheckNoResourceAttr(testResourceName, "destination_network"), + resource.TestCheckNoResourceAttr(testResourceName, "source_network"), + resource.TestCheckResourceAttr(testResourceName, "action", "SNAT"), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + ), + }, + { + Config: testAccNsxtVpcNatRuleReflexiveTemplate(ruleName, sourceIP), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcNatRuleExists(ruleName, testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", ruleName), + resource.TestCheckResourceAttrSet(testResourceName, "parent_path"), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "translated_network"), + resource.TestCheckResourceAttr(testResourceName, "logging", "false"), + resource.TestCheckNoResourceAttr(testResourceName, "destination_network"), + resource.TestCheckResourceAttr(testResourceName, "source_network", sourceIP), + resource.TestCheckResourceAttr(testResourceName, "action", "REFLEXIVE"), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtVpcNatRule_importBasic(t *testing.T) { + name := getAccTestResourceName() + testResourceName := "nsxt_vpc_nat_rule.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccOnlyVPC(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtVpcNatRuleCheckDestroy(state, name) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtVpcNatRuleMinimalistic(), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccResourceNsxtPolicyImportIDRetriever(testResourceName), + }, + }, + }) +} + +func testAccNsxtVpcNatRuleExists(displayName string, resourceName string) resource.TestCheckFunc { + return func(state *terraform.State) error { + + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + + rs, ok := state.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Policy VpcNatRule resource %s not found in resources", resourceName) + } + + resourceID := rs.Primary.ID + if resourceID == "" { + return fmt.Errorf("Policy VpcNatRule resource ID not set in resources") + } + + parentPath := rs.Primary.Attributes["parent_path"] + + exists, err := resourceNsxtPolicyVpcNatRuleExists(testAccGetSessionContext(), parentPath, resourceID, connector) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("VpcNatRule %s does not exist", resourceID) + } + + return nil + } +} + +func testAccNsxtVpcNatRuleCheckDestroy(state *terraform.State, displayName string) error { + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + for _, rs := range state.RootModule().Resources { + + if rs.Type != "nsxt_vpc_nat_rule" { + continue + } + + resourceID := rs.Primary.Attributes["id"] + parentPath := rs.Primary.Attributes["parent_path"] + + exists, err := resourceNsxtPolicyVpcNatRuleExists(testAccGetSessionContext(), parentPath, resourceID, connector) + if err == nil { + return err + } + + if exists { + return fmt.Errorf("VpcNatRule %s still exists", displayName) + } + } + return nil +} + +func testAccNsxtVpcNatRulePrerequisites() string { + return fmt.Sprintf(` +data "nsxt_vpc_nat" "test" { + %s + nat_type = "USER" +} + +resource "nsxt_vpc_ip_address_allocation" "test" { + %s + allocation_size = 1 + display_name = "%s" +} +`, testAccNsxtPolicyMultitenancyContext(), testAccNsxtPolicyMultitenancyContext(), accTestVpcNatIPAllocationTestName) +} + +func testAccNsxtVpcNatRuleTemplate(createFlow bool) string { + var attrMap map[string]string + if createFlow { + attrMap = accTestVpcNatRuleCreateAttributes + } else { + attrMap = accTestVpcNatRuleUpdateAttributes + } + return testAccNsxtVpcNatRulePrerequisites() + fmt.Sprintf(` +resource "nsxt_vpc_nat_rule" "test" { + parent_path = data.nsxt_vpc_nat.test.path + display_name = "%s" + description = "%s" + translated_network = "%s" + logging = %s + destination_network = nsxt_vpc_ip_address_allocation.test.allocation_ips + action = "%s" + firewall_match = "%s" + source_network = "%s" + enabled = %s + sequence_number = %s + + tag { + scope = "scope1" + tag = "tag1" + } +}`, attrMap["display_name"], attrMap["description"], attrMap["translated_network"], attrMap["logging"], attrMap["action"], attrMap["firewall_match"], attrMap["source_network"], attrMap["enabled"], attrMap["sequence_number"]) +} + +func testAccNsxtVpcNatRuleMinimalistic() string { + return testAccNsxtVpcNatRulePrerequisites() + fmt.Sprintf(` +resource "nsxt_vpc_nat_rule" "test" { + parent_path = data.nsxt_vpc_nat.test.path + display_name = "%s" + destination_network = nsxt_vpc_ip_address_allocation.test.allocation_ips + translated_network = "%s" + action = "%s" +}`, accTestVpcNatRuleUpdateAttributes["display_name"], accTestVpcNatRuleUpdateAttributes["translated_network"], accTestVpcNatRuleUpdateAttributes["action"]) +} + +func testAccNsxtVpcNatRuleSnatTemplate(name string) string { + return testAccNsxtVpcNatRulePrerequisites() + fmt.Sprintf(` +resource "nsxt_vpc_nat_rule" "test" { + parent_path = data.nsxt_vpc_nat.test.path + display_name = "%s" + translated_network = nsxt_vpc_ip_address_allocation.test.allocation_ips + action = "SNAT" +}`, name) +} + +func testAccNsxtVpcNatRuleReflexiveTemplate(name string, sourceIP string) string { + return testAccNsxtVpcNatRulePrerequisites() + fmt.Sprintf(` +resource "nsxt_vpc_nat_rule" "test" { + parent_path = data.nsxt_vpc_nat.test.path + display_name = "%s" + source_network = "%s" + translated_network = nsxt_vpc_ip_address_allocation.test.allocation_ips + action = "REFLEXIVE" +}`, name, sourceIP) +} diff --git a/nsxt/resource_nsxt_vpc_service_profile.go b/nsxt/resource_nsxt_vpc_service_profile.go new file mode 100644 index 000000000..c7338263f --- /dev/null +++ b/nsxt/resource_nsxt_vpc_service_profile.go @@ -0,0 +1,414 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "log" + "reflect" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + clientLayer "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects" + + utl "github.com/vmware/terraform-provider-nsxt/api/utl" + "github.com/vmware/terraform-provider-nsxt/nsxt/metadata" +) + +var vpcServiceProfileModeValues = []string{ + model.VpcProfileDhcpConfig_MODE_SERVER, + model.VpcProfileDhcpConfig_MODE_RELAY, + model.VpcProfileDhcpConfig_MODE_DEACTIVATED, +} + +var vpcServiceProfileSchema = map[string]*metadata.ExtendedSchema{ + "nsx_id": metadata.GetExtendedSchema(getNsxIDSchema()), + "path": metadata.GetExtendedSchema(getPathSchema()), + "display_name": metadata.GetExtendedSchema(getDisplayNameSchema()), + "description": metadata.GetExtendedSchema(getDescriptionSchema()), + "revision": metadata.GetExtendedSchema(getRevisionSchema()), + "tag": metadata.GetExtendedSchema(getTagsSchema()), + "context": metadata.GetExtendedSchema(getContextSchema(true, false, false)), + "mac_discovery_profile": { + Schema: schema.Schema{ + Type: schema.TypeString, + Optional: true, + ValidateFunc: validatePolicyPath(), + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "MacDiscoveryProfile", + }, + }, + "spoof_guard_profile": { + Schema: schema.Schema{ + Type: schema.TypeString, + Optional: true, + ValidateFunc: validatePolicyPath(), + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "SpoofGuardProfile", + }, + }, + "dhcp_config": { + Schema: schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Required: true, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "dhcp_relay_config": { + Schema: schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "server_addresses": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedSchema{ + Schema: schema.Schema{ + Type: schema.TypeString, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "ServerAddresses", + }, + }, + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "struct", + SdkFieldName: "DhcpRelayConfig", + ReflectType: reflect.TypeOf(model.VpcDhcpRelayConfig{}), + }, + }, + "dhcp_server_config": { + Schema: schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "dns_client_config": { + Schema: schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "dns_server_ips": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedSchema{ + Schema: schema.Schema{ + Type: schema.TypeString, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "DnsServerIps", + }, + }, + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "struct", + SdkFieldName: "DnsClientConfig", + ReflectType: reflect.TypeOf(model.DnsClientConfig{}), + }, + }, + "lease_time": { + Schema: schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 86400, + }, + Metadata: metadata.Metadata{ + SchemaType: "int", + SdkFieldName: "LeaseTime", + }, + }, + "ntp_servers": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedSchema{ + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateSingleIPOrHostName(), + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "NtpServers", + }, + }, + "advanced_config": { + Schema: schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "is_distributed_dhcp": { + Schema: schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "bool", + SdkFieldName: "IsDistributedDhcp", + }, + }, + }, + }, + Optional: true, + Computed: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "struct", + SdkFieldName: "AdvancedConfig", + ReflectType: reflect.TypeOf(model.VpcDhcpAdvancedConfig{}), + }, + }, + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "struct", + SdkFieldName: "DhcpServerConfig", + ReflectType: reflect.TypeOf(model.VpcDhcpServerConfig{}), + }, + }, + }, + }, + }, + Metadata: metadata.Metadata{ + SchemaType: "struct", + SdkFieldName: "DhcpConfig", + ReflectType: reflect.TypeOf(model.VpcProfileDhcpConfig{}), + }, + }, + "ip_discovery_profile": { + Schema: schema.Schema{ + Type: schema.TypeString, + Optional: true, + ValidateFunc: validatePolicyPath(), + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "IpDiscoveryProfile", + }, + }, + "security_profile": { + Schema: schema.Schema{ + Type: schema.TypeString, + Optional: true, + ValidateFunc: validatePolicyPath(), + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "SecurityProfile", + }, + }, + "qos_profile": { + Schema: schema.Schema{ + Type: schema.TypeString, + Optional: true, + ValidateFunc: validatePolicyPath(), + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "QosProfile", + }, + }, +} + +func resourceNsxtVpcServiceProfile() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtVpcServiceProfileCreate, + Read: resourceNsxtVpcServiceProfileRead, + Update: resourceNsxtVpcServiceProfileUpdate, + Delete: resourceNsxtVpcServiceProfileDelete, + Importer: &schema.ResourceImporter{ + State: nsxtPolicyPathOnlyResourceImporter, + }, + Schema: metadata.GetSchemaFromExtendedSchema(vpcServiceProfileSchema), + } +} + +func resourceNsxtVpcServiceProfileExists(sessionContext utl.SessionContext, id string, connector client.Connector) (bool, error) { + var err error + parents := getVpcParentsFromContext(sessionContext) + client := clientLayer.NewVpcServiceProfilesClient(connector) + _, err = client.Get(parents[0], parents[1], id) + if err == nil { + return true, nil + } + + if isNotFoundError(err) { + return false, nil + } + + return false, logAPIError("Error retrieving resource", err) +} + +func resourceNsxtVpcServiceProfileCreate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id, err := getOrGenerateID2(d, m, resourceNsxtVpcServiceProfileExists) + if err != nil { + return err + } + + parents := getVpcParentsFromContext(getSessionContext(d, m)) + displayName := d.Get("display_name").(string) + description := d.Get("description").(string) + tags, tagErr := getValidatedTagsFromSchema(d) + if tagErr != nil { + return tagErr + } + + obj := model.VpcServiceProfile{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, vpcServiceProfileSchema, "", nil); err != nil { + return err + } + + if obj.DhcpConfig == nil { + // NSX requires to send this struct even if empty + obj.DhcpConfig = &model.VpcProfileDhcpConfig{} + } + + log.Printf("[INFO] Creating VpcServiceProfile with ID %s", id) + + client := clientLayer.NewVpcServiceProfilesClient(connector) + err = client.Patch(parents[0], parents[1], id, obj) + if err != nil { + return handleCreateError("VpcServiceProfile", id, err) + } + d.SetId(id) + d.Set("nsx_id", id) + + return resourceNsxtVpcServiceProfileRead(d, m) +} + +func resourceNsxtVpcServiceProfileRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining VpcServiceProfile ID") + } + + client := clientLayer.NewVpcServiceProfilesClient(connector) + parents := getVpcParentsFromContext(getSessionContext(d, m)) + obj, err := client.Get(parents[0], parents[1], id) + if err != nil { + return handleReadError(d, "VpcServiceProfile", id, err) + } + + setPolicyTagsInSchema(d, obj.Tags) + d.Set("nsx_id", id) + d.Set("display_name", obj.DisplayName) + d.Set("description", obj.Description) + d.Set("revision", obj.Revision) + d.Set("path", obj.Path) + + elem := reflect.ValueOf(&obj).Elem() + return metadata.StructToSchema(elem, d, vpcServiceProfileSchema, "", nil) +} + +func resourceNsxtVpcServiceProfileUpdate(d *schema.ResourceData, m interface{}) error { + + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining VpcServiceProfile ID") + } + + parents := getVpcParentsFromContext(getSessionContext(d, m)) + description := d.Get("description").(string) + displayName := d.Get("display_name").(string) + tags, tagErr := getValidatedTagsFromSchema(d) + if tagErr != nil { + return tagErr + } + + revision := int64(d.Get("revision").(int)) + + obj := model.VpcServiceProfile{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + Revision: &revision, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, vpcServiceProfileSchema, "", nil); err != nil { + return err + } + + if obj.DhcpConfig == nil { + // NSX requires to send this struct even if empty + obj.DhcpConfig = &model.VpcProfileDhcpConfig{} + } + + client := clientLayer.NewVpcServiceProfilesClient(connector) + _, err := client.Update(parents[0], parents[1], id, obj) + if err != nil { + // Trigger partial update to avoid terraform updating state based on failed intent + // TODO - move this into handleUpdateError + d.Partial(true) + return handleUpdateError("VpcServiceProfile", id, err) + } + + return resourceNsxtVpcServiceProfileRead(d, m) +} + +func resourceNsxtVpcServiceProfileDelete(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining VpcServiceProfile ID") + } + + connector := getPolicyConnector(m) + parents := getVpcParentsFromContext(getSessionContext(d, m)) + + client := clientLayer.NewVpcServiceProfilesClient(connector) + err := client.Delete(parents[0], parents[1], id) + + if err != nil { + return handleDeleteError("VpcServiceProfile", id, err) + } + + return nil +} diff --git a/nsxt/resource_nsxt_vpc_service_profile_test.go b/nsxt/resource_nsxt_vpc_service_profile_test.go new file mode 100644 index 000000000..9cdb16268 --- /dev/null +++ b/nsxt/resource_nsxt_vpc_service_profile_test.go @@ -0,0 +1,321 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +var accTestPolicyVpcServiceProfileCreateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform created", + "ntp_servers": "5.5.5.5", + "lease_time": "50840", + "dns_server_ips": "7.7.7.7", + "server_addresses": "11.11.11.11", +} + +var accTestPolicyVpcServiceProfileUpdateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform updated", + "ntp_servers": "5.5.5.7", + "lease_time": "148000", + "dns_server_ips": "7.7.7.2", + "server_addresses": "11.11.11.111", +} + +func TestAccResourceNsxtVpcServiceProfile_basic(t *testing.T) { + testResourceName := "nsxt_vpc_service_profile.test" + testDataSourceName := "data.nsxt_vpc_service_profile.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccOnlyVPC(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtVpcServiceProfileCheckDestroy(state, accTestPolicyVpcServiceProfileUpdateAttributes["display_name"]) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtVpcServiceProfileTemplate(true, false), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcServiceProfileExists(accTestPolicyVpcServiceProfileCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttrSet(testDataSourceName, "path"), + resource.TestCheckResourceAttrSet(testDataSourceName, "description"), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestPolicyVpcServiceProfileCreateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestPolicyVpcServiceProfileCreateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_relay_config.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_config.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_config.0.ntp_servers.0", accTestPolicyVpcServiceProfileCreateAttributes["ntp_servers"]), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_config.0.lease_time", accTestPolicyVpcServiceProfileCreateAttributes["lease_time"]), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_config.0.dns_client_config.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_config.0.dns_client_config.0.dns_server_ips.0", accTestPolicyVpcServiceProfileCreateAttributes["dns_server_ips"]), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + // add profiles + Config: testAccNsxtVpcServiceProfileTemplate(true, true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcServiceProfileExists(accTestPolicyVpcServiceProfileCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttrSet(testDataSourceName, "path"), + resource.TestCheckResourceAttrSet(testDataSourceName, "description"), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestPolicyVpcServiceProfileCreateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestPolicyVpcServiceProfileCreateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_relay_config.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_config.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_config.0.ntp_servers.0", accTestPolicyVpcServiceProfileCreateAttributes["ntp_servers"]), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_config.0.lease_time", accTestPolicyVpcServiceProfileCreateAttributes["lease_time"]), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_config.0.dns_client_config.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_config.0.dns_client_config.0.dns_server_ips.0", accTestPolicyVpcServiceProfileCreateAttributes["dns_server_ips"]), + resource.TestCheckResourceAttrSet(testResourceName, "qos_profile"), + resource.TestCheckResourceAttrSet(testResourceName, "spoof_guard_profile"), + resource.TestCheckResourceAttrSet(testResourceName, "ip_discovery_profile"), + resource.TestCheckResourceAttrSet(testResourceName, "mac_discovery_profile"), + resource.TestCheckResourceAttrSet(testResourceName, "security_profile"), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + // update values, no profiles + Config: testAccNsxtVpcServiceProfileTemplate(false, false), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcServiceProfileExists(accTestPolicyVpcServiceProfileUpdateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestPolicyVpcServiceProfileUpdateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestPolicyVpcServiceProfileUpdateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_relay_config.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_config.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_config.0.ntp_servers.0", accTestPolicyVpcServiceProfileUpdateAttributes["ntp_servers"]), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_config.0.lease_time", accTestPolicyVpcServiceProfileUpdateAttributes["lease_time"]), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_config.0.dns_client_config.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_config.0.dns_client_config.0.dns_server_ips.0", accTestPolicyVpcServiceProfileUpdateAttributes["dns_server_ips"]), + + resource.TestCheckResourceAttr(testResourceName, "qos_profile", ""), + resource.TestCheckResourceAttr(testResourceName, "spoof_guard_profile", ""), + resource.TestCheckResourceAttr(testResourceName, "ip_discovery_profile", ""), + resource.TestCheckResourceAttr(testResourceName, "mac_discovery_profile", ""), + resource.TestCheckResourceAttr(testResourceName, "security_profile", ""), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNsxtVpcServiceProfileDhcpRelay(), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcServiceProfileExists(accTestPolicyVpcServiceProfileCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_relay_config.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_relay_config.0.server_addresses.#", "1"), + ), + }, + { + Config: testAccNsxtVpcServiceProfileMinimalistic(), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcServiceProfileExists(accTestPolicyVpcServiceProfileCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.#", "1"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtVpcServiceProfile_importBasic(t *testing.T) { + name := getAccTestResourceName() + testResourceName := "nsxt_vpc_service_profile.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccOnlyVPC(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtVpcServiceProfileCheckDestroy(state, name) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtVpcServiceProfileMinimalistic(), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccResourceNsxtPolicyImportIDRetriever(testResourceName), + }, + }, + }) +} + +func testAccNsxtVpcServiceProfileExists(displayName string, resourceName string) resource.TestCheckFunc { + return func(state *terraform.State) error { + + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + + rs, ok := state.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Policy VpcServiceProfile resource %s not found in resources", resourceName) + } + + resourceID := rs.Primary.ID + if resourceID == "" { + return fmt.Errorf("Policy VpcServiceProfile resource ID not set in resources") + } + + exists, err := resourceNsxtVpcServiceProfileExists(testAccGetSessionProjectContext(), resourceID, connector) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("Policy VpcServiceProfile %s does not exist", resourceID) + } + + return nil + } +} + +func testAccNsxtVpcServiceProfileCheckDestroy(state *terraform.State, displayName string) error { + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + for _, rs := range state.RootModule().Resources { + + if rs.Type != "nsxt_vpc_service_profile" { + continue + } + + resourceID := rs.Primary.Attributes["id"] + exists, err := resourceNsxtVpcServiceProfileExists(testAccGetSessionProjectContext(), resourceID, connector) + if err == nil { + return err + } + + if exists { + return fmt.Errorf("Policy VpcServiceProfile %s still exists", displayName) + } + } + return nil +} + +var testAccNsxtVpcServiceProfileHelper = getAccTestResourceName() + +func testAccNsxtProjectProfiles() string { + return fmt.Sprintf(` +resource "nsxt_policy_mac_discovery_profile" "test" { + %s + display_name = "%s" +} + +resource "nsxt_policy_ip_discovery_profile" "test" { + %s + display_name = "%s" +} + +resource "nsxt_policy_spoof_guard_profile" "test" { + %s + display_name = "%s" +} + +resource "nsxt_policy_segment_security_profile" "test"{ + %s + display_name = "%s" +} + +resource "nsxt_policy_qos_profile" "test" { + %s + display_name = "%s" +}`, testAccNsxtProjectContext(), testAccNsxtVpcServiceProfileHelper, testAccNsxtProjectContext(), testAccNsxtVpcServiceProfileHelper, testAccNsxtProjectContext(), testAccNsxtVpcServiceProfileHelper, testAccNsxtProjectContext(), testAccNsxtVpcServiceProfileHelper, testAccNsxtProjectContext(), testAccNsxtVpcServiceProfileHelper) +} + +func testAccNsxtVpcServiceProfileTemplate(createFlow bool, withProfiles bool) string { + var attrMap map[string]string + if createFlow { + attrMap = accTestPolicyVpcServiceProfileCreateAttributes + } else { + attrMap = accTestPolicyVpcServiceProfileUpdateAttributes + } + profileAssignment := "" + if withProfiles { + profileAssignment = ` + qos_profile = nsxt_policy_qos_profile.test.path + security_profile = nsxt_policy_segment_security_profile.test.path + ip_discovery_profile = nsxt_policy_ip_discovery_profile.test.path + spoof_guard_profile = nsxt_policy_spoof_guard_profile.test.path + mac_discovery_profile = nsxt_policy_mac_discovery_profile.test.path` + } + return testAccNsxtProjectProfiles() + fmt.Sprintf(` +resource "nsxt_vpc_service_profile" "test" { + %s + %s + display_name = "%s" + description = "%s" + + dhcp_config { + dhcp_server_config { + ntp_servers = ["%s"] + lease_time = %s + + dns_client_config { + dns_server_ips = ["%s"] + } + } + } + + tag { + scope = "scope1" + tag = "tag1" + } +} + +data "nsxt_vpc_service_profile" "test" { + %s + display_name = "%s" + + depends_on = [nsxt_vpc_service_profile.test] +}`, testAccNsxtProjectContext(), profileAssignment, attrMap["display_name"], attrMap["description"], attrMap["ntp_servers"], attrMap["lease_time"], attrMap["dns_server_ips"], testAccNsxtProjectContext(), attrMap["display_name"]) +} + +func testAccNsxtVpcServiceProfileDhcpRelay() string { + return fmt.Sprintf(` +resource "nsxt_vpc_service_profile" "test" { + %s + display_name = "%s" + dhcp_config { + dhcp_relay_config { + server_addresses = ["%s"] + } + } + +}`, testAccNsxtProjectContext(), accTestPolicyVpcServiceProfileUpdateAttributes["display_name"], accTestPolicyVpcServiceProfileUpdateAttributes["server_addresses"]) +} + +func testAccNsxtVpcServiceProfileMinimalistic() string { + return fmt.Sprintf(` +resource "nsxt_vpc_service_profile" "test" { + %s + display_name = "%s" + dhcp_config { + } + +}`, testAccNsxtProjectContext(), accTestPolicyVpcServiceProfileUpdateAttributes["display_name"]) +} diff --git a/nsxt/resource_nsxt_vpc_static_routes.go b/nsxt/resource_nsxt_vpc_static_routes.go new file mode 100644 index 000000000..9c4ab2d28 --- /dev/null +++ b/nsxt/resource_nsxt_vpc_static_routes.go @@ -0,0 +1,226 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "log" + "reflect" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + clientLayer "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/vpcs" + + utl "github.com/vmware/terraform-provider-nsxt/api/utl" + "github.com/vmware/terraform-provider-nsxt/nsxt/metadata" +) + +var staticRoutesSchema = map[string]*metadata.ExtendedSchema{ + "nsx_id": metadata.GetExtendedSchema(getNsxIDSchema()), + "path": metadata.GetExtendedSchema(getPathSchema()), + "display_name": metadata.GetExtendedSchema(getDisplayNameSchema()), + "description": metadata.GetExtendedSchema(getDescriptionSchema()), + "revision": metadata.GetExtendedSchema(getRevisionSchema()), + "tag": metadata.GetExtendedSchema(getTagsSchema()), + "context": metadata.GetExtendedSchema(getContextSchema(true, false, true)), + "next_hop": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "ip_address": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateSingleIP(), + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "IpAddress", + }, + }, + "admin_distance": { + Schema: schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 1, + }, + Metadata: metadata.Metadata{ + SchemaType: "int", + SdkFieldName: "AdminDistance", + }, + }, + }, + }, + Required: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "NextHops", + ReflectType: reflect.TypeOf(model.RouterNexthop{}), + }, + }, + "network": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateCidrOrIPOrRange(), + Required: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "Network", + }, + }, +} + +func resourceNsxtVpcStaticRoutes() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtVpcStaticRoutesCreate, + Read: resourceNsxtVpcStaticRoutesRead, + Update: resourceNsxtVpcStaticRoutesUpdate, + Delete: resourceNsxtVpcStaticRoutesDelete, + Importer: &schema.ResourceImporter{ + State: nsxtVPCPathResourceImporter, + }, + Schema: metadata.GetSchemaFromExtendedSchema(staticRoutesSchema), + } +} + +func resourceNsxtVpcStaticRoutesExists(sessionContext utl.SessionContext, id string, connector client.Connector) (bool, error) { + var err error + parents := getVpcParentsFromContext(sessionContext) + client := clientLayer.NewStaticRoutesClient(connector) + _, err = client.Get(parents[0], parents[1], parents[2], id) + if err == nil { + return true, nil + } + + if isNotFoundError(err) { + return false, nil + } + + return false, logAPIError("Error retrieving resource", err) +} + +func resourceNsxtVpcStaticRoutesCreate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id, err := getOrGenerateID2(d, m, resourceNsxtVpcStaticRoutesExists) + if err != nil { + return err + } + + parents := getVpcParentsFromContext(getSessionContext(d, m)) + displayName := d.Get("display_name").(string) + description := d.Get("description").(string) + tags := getPolicyTagsFromSchema(d) + + obj := model.StaticRoutes{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, staticRoutesSchema, "", nil); err != nil { + return err + } + + log.Printf("[INFO] Creating StaticRoutes with ID %s", id) + + client := clientLayer.NewStaticRoutesClient(connector) + err = client.Patch(parents[0], parents[1], parents[2], id, obj) + if err != nil { + return handleCreateError("StaticRoutes", id, err) + } + d.SetId(id) + d.Set("nsx_id", id) + + return resourceNsxtVpcStaticRoutesRead(d, m) +} + +func resourceNsxtVpcStaticRoutesRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining StaticRoutes ID") + } + + client := clientLayer.NewStaticRoutesClient(connector) + parents := getVpcParentsFromContext(getSessionContext(d, m)) + obj, err := client.Get(parents[0], parents[1], parents[2], id) + if err != nil { + return handleReadError(d, "StaticRoutes", id, err) + } + + setPolicyTagsInSchema(d, obj.Tags) + d.Set("nsx_id", id) + d.Set("display_name", obj.DisplayName) + d.Set("description", obj.Description) + d.Set("revision", obj.Revision) + d.Set("path", obj.Path) + + elem := reflect.ValueOf(&obj).Elem() + return metadata.StructToSchema(elem, d, staticRoutesSchema, "", nil) +} + +func resourceNsxtVpcStaticRoutesUpdate(d *schema.ResourceData, m interface{}) error { + + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining StaticRoutes ID") + } + + parents := getVpcParentsFromContext(getSessionContext(d, m)) + description := d.Get("description").(string) + displayName := d.Get("display_name").(string) + tags := getPolicyTagsFromSchema(d) + + revision := int64(d.Get("revision").(int)) + + obj := model.StaticRoutes{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + Revision: &revision, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, staticRoutesSchema, "", nil); err != nil { + return err + } + client := clientLayer.NewStaticRoutesClient(connector) + _, err := client.Update(parents[0], parents[1], parents[2], id, obj) + if err != nil { + // Trigger partial update to avoid terraform updating state based on failed intent + // TODO - move this into handleUpdateError + d.Partial(true) + return handleUpdateError("StaticRoutes", id, err) + } + + return resourceNsxtVpcStaticRoutesRead(d, m) +} + +func resourceNsxtVpcStaticRoutesDelete(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining StaticRoutes ID") + } + + connector := getPolicyConnector(m) + parents := getVpcParentsFromContext(getSessionContext(d, m)) + + client := clientLayer.NewStaticRoutesClient(connector) + err := client.Delete(parents[0], parents[1], parents[2], id) + + if err != nil { + return handleDeleteError("StaticRoutes", id, err) + } + + return nil +} diff --git a/nsxt/resource_nsxt_vpc_static_routes_test.go b/nsxt/resource_nsxt_vpc_static_routes_test.go new file mode 100644 index 000000000..626786d90 --- /dev/null +++ b/nsxt/resource_nsxt_vpc_static_routes_test.go @@ -0,0 +1,204 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +var accTestStaticRoutesCreateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform created", + "network": "2.2.2.0/24", + "ip_address": "3.1.1.1", + "admin_distance": "2", +} + +var accTestStaticRoutesUpdateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform updated", + "network": "3.3.3.0/24", + "ip_address": "4.1.1.1", + "admin_distance": "5", +} + +func TestAccResourceNsxtVpcStaticRoutes_basic(t *testing.T) { + testResourceName := "nsxt_vpc_static_route.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccOnlyVPC(t) + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtVpcStaticRoutesCheckDestroy(state, accTestStaticRoutesUpdateAttributes["display_name"]) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtVpcStaticRoutesTemplate(true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcStaticRoutesExists(accTestStaticRoutesCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestStaticRoutesCreateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestStaticRoutesCreateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "next_hop.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "network", accTestStaticRoutesCreateAttributes["network"]), + resource.TestCheckResourceAttr(testResourceName, "next_hop.0.ip_address", accTestStaticRoutesCreateAttributes["ip_address"]), + resource.TestCheckResourceAttr(testResourceName, "next_hop.0.admin_distance", accTestStaticRoutesCreateAttributes["admin_distance"]), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNsxtVpcStaticRoutesTemplate(false), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcStaticRoutesExists(accTestStaticRoutesUpdateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestStaticRoutesUpdateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestStaticRoutesUpdateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "next_hop.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "network", accTestStaticRoutesUpdateAttributes["network"]), + resource.TestCheckResourceAttr(testResourceName, "next_hop.0.ip_address", accTestStaticRoutesUpdateAttributes["ip_address"]), + resource.TestCheckResourceAttr(testResourceName, "next_hop.0.admin_distance", accTestStaticRoutesUpdateAttributes["admin_distance"]), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNsxtVpcStaticRoutesMinimalistic(), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcStaticRoutesExists(accTestStaticRoutesCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtVpcStaticRoutes_importBasic(t *testing.T) { + name := getAccTestResourceName() + testResourceName := "nsxt_vpc_static_route.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccOnlyVPC(t) + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtVpcStaticRoutesCheckDestroy(state, name) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtVpcStaticRoutesMinimalistic(), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccResourceNsxtPolicyImportIDRetriever(testResourceName), + }, + }, + }) +} + +func testAccNsxtVpcStaticRoutesExists(displayName string, resourceName string) resource.TestCheckFunc { + return func(state *terraform.State) error { + + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + + rs, ok := state.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Policy StaticRoutes resource %s not found in resources", resourceName) + } + + resourceID := rs.Primary.ID + if resourceID == "" { + return fmt.Errorf("Policy StaticRoutes resource ID not set in resources") + } + + exists, err := resourceNsxtVpcStaticRoutesExists(testAccGetSessionContext(), resourceID, connector) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("Policy StaticRoutes %s does not exist", resourceID) + } + + return nil + } +} + +func testAccNsxtVpcStaticRoutesCheckDestroy(state *terraform.State, displayName string) error { + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + for _, rs := range state.RootModule().Resources { + + if rs.Type != "nsxt_vpc_static_route" { + continue + } + + resourceID := rs.Primary.Attributes["id"] + exists, err := resourceNsxtVpcStaticRoutesExists(testAccGetSessionContext(), resourceID, connector) + if err == nil { + return err + } + + if exists { + return fmt.Errorf("Policy StaticRoutes %s still exists", displayName) + } + } + return nil +} + +func testAccNsxtVpcStaticRoutesTemplate(createFlow bool) string { + var attrMap map[string]string + if createFlow { + attrMap = accTestStaticRoutesCreateAttributes + } else { + attrMap = accTestStaticRoutesUpdateAttributes + } + return fmt.Sprintf(` +resource "nsxt_vpc_static_route" "test" { +%s + display_name = "%s" + description = "%s" + + network = "%s" + + next_hop { + ip_address = "%s" + admin_distance = %s + } + + tag { + scope = "scope1" + tag = "tag1" + } +}`, testAccNsxtPolicyMultitenancyContext(), attrMap["display_name"], attrMap["description"], attrMap["network"], attrMap["ip_address"], attrMap["admin_distance"]) +} + +func testAccNsxtVpcStaticRoutesMinimalistic() string { + return fmt.Sprintf(` +resource "nsxt_vpc_static_route" "test" { +%s + display_name = "%s" + network = "%s" + next_hop { + ip_address = "%s" + } +}`, testAccNsxtPolicyMultitenancyContext(), accTestStaticRoutesUpdateAttributes["display_name"], accTestStaticRoutesUpdateAttributes["network"], accTestStaticRoutesUpdateAttributes["ip_address"]) +} diff --git a/nsxt/resource_nsxt_vpc_subnet.go b/nsxt/resource_nsxt_vpc_subnet.go new file mode 100644 index 000000000..349f94d47 --- /dev/null +++ b/nsxt/resource_nsxt_vpc_subnet.go @@ -0,0 +1,664 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "log" + "reflect" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/vmware/vsphere-automation-sdk-go/runtime/protocol/client" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" + clientLayer "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/vpcs" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/orgs/projects/vpcs/subnets" + + utl "github.com/vmware/terraform-provider-nsxt/api/utl" + "github.com/vmware/terraform-provider-nsxt/nsxt/metadata" +) + +var vpcSubnetAccessModeValues = []string{ + model.VpcSubnet_ACCESS_MODE_PRIVATE, + model.VpcSubnet_ACCESS_MODE_PUBLIC, + model.VpcSubnet_ACCESS_MODE_ISOLATED, + model.VpcSubnet_ACCESS_MODE_PRIVATE_TGW, +} + +var vpcSubnetConnectivityStateValues = []string{ + model.SubnetAdvancedConfig_CONNECTIVITY_STATE_CONNECTED, + model.SubnetAdvancedConfig_CONNECTIVITY_STATE_DISCONNECTED, +} + +var vpcSubnetModeValues = []string{ + model.SubnetDhcpConfig_MODE_SERVER, + model.SubnetDhcpConfig_MODE_RELAY, + model.SubnetDhcpConfig_MODE_DEACTIVATED, +} + +var vpcSubnetSchema = map[string]*metadata.ExtendedSchema{ + "nsx_id": metadata.GetExtendedSchema(getNsxIDSchema()), + "path": metadata.GetExtendedSchema(getPathSchema()), + "display_name": metadata.GetExtendedSchema(getDisplayNameSchema()), + "description": metadata.GetExtendedSchema(getDescriptionSchema()), + "revision": metadata.GetExtendedSchema(getRevisionSchema()), + "tag": metadata.GetExtendedSchema(getTagsSchema()), + "context": metadata.GetExtendedSchema(getContextSchema(true, false, true)), + "ip_blocks": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedSchema{ + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validatePolicyPath(), + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + }, + }, + Optional: true, + ForceNew: true, + Computed: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "IpBlocks", + }, + }, + "advanced_config": { + Schema: schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "gateway_addresses": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedSchema{ + Schema: schema.Schema{ + Type: schema.TypeString, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "GatewayAddresses", + }, + }, + "extra_config": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "config_pair": { + Schema: schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "value": { + Schema: schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "Value", + }, + }, + "key": { + Schema: schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "Key", + }, + }, + }, + }, + Required: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "struct", + SdkFieldName: "ConfigPair", + ReflectType: reflect.TypeOf(model.UnboundedKeyValuePair{}), + }, + }, + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "ExtraConfigs", + ReflectType: reflect.TypeOf(model.SubnetExtraConfig{}), + OmitIfEmpty: true, + }, + }, + "dhcp_server_addresses": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedSchema{ + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateSingleIP(), + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "DhcpServerAddresses", + }, + }, + "connectivity_state": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(vpcSubnetConnectivityStateValues, false), + Optional: true, + Default: model.SubnetAdvancedConfig_CONNECTIVITY_STATE_CONNECTED, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "ConnectivityState", + }, + }, + "static_ip_allocation": { + Schema: schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "enabled": { + Schema: schema.Schema{ + Type: schema.TypeBool, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "bool", + SdkFieldName: "Enabled", + }, + }, + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "struct", + SdkFieldName: "StaticIpAllocation", + ReflectType: reflect.TypeOf(model.StaticIpAllocation{}), + }, + }, + }, + }, + Optional: true, + Computed: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "struct", + SdkFieldName: "AdvancedConfig", + ReflectType: reflect.TypeOf(model.SubnetAdvancedConfig{}), + }, + }, + "ipv4_subnet_size": { + Schema: schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"ip_addresses"}, + }, + Metadata: metadata.Metadata{ + SchemaType: "int", + SdkFieldName: "Ipv4SubnetSize", + OmitIfEmpty: true, + }, + }, + "ip_addresses": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedSchema{ + Schema: schema.Schema{ + Type: schema.TypeString, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + }, + }, + Optional: true, + Computed: true, + ForceNew: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "IpAddresses", + }, + }, + "access_mode": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(vpcSubnetAccessModeValues, false), + Optional: true, + Default: model.VpcSubnet_ACCESS_MODE_PRIVATE, + ForceNew: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "AccessMode", + }, + }, + "dhcp_config": { + Schema: schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Computed: true, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "mode": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(vpcSubnetModeValues, false), + Optional: true, + Default: model.SubnetDhcpConfig_MODE_DEACTIVATED, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "Mode", + }, + }, + "dhcp_server_additional_config": { + Schema: schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "options": { + Schema: schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "option121": { + Schema: schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "static_route": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "next_hop": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateSingleIP(), + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "NextHop", + }, + }, + "network": { + Schema: schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validateCidrOrIPOrRange(), + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + SdkFieldName: "Network", + }, + }, + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "StaticRoutes", + ReflectType: reflect.TypeOf(model.ClasslessStaticRoute{}), + }, + }, + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "struct", + SdkFieldName: "Option121", + ReflectType: reflect.TypeOf(model.DhcpOption121{}), + }, + }, + "other": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedResource{ + Schema: map[string]*metadata.ExtendedSchema{ + "code": { + Schema: schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "int", + SdkFieldName: "Code", + }, + }, + "values": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedSchema{ + Schema: schema.Schema{ + Type: schema.TypeString, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "Values", + }, + }, + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "Others", + ReflectType: reflect.TypeOf(model.GenericDhcpOption{}), + }, + }, + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "struct", + SdkFieldName: "Options", + ReflectType: reflect.TypeOf(model.DhcpV4Options{}), + }, + }, + "reserved_ip_ranges": { + Schema: schema.Schema{ + Type: schema.TypeList, + Elem: &metadata.ExtendedSchema{ + Schema: schema.Schema{ + Type: schema.TypeString, + }, + Metadata: metadata.Metadata{ + SchemaType: "string", + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "list", + SdkFieldName: "ReservedIpRanges", + }, + }, + }, + }, + Optional: true, + Computed: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "struct", + SdkFieldName: "DhcpServerAdditionalConfig", + ReflectType: reflect.TypeOf(model.DhcpServerAdditionalConfig{}), + }, + }, + }, + }, + Optional: true, + }, + Metadata: metadata.Metadata{ + SchemaType: "struct", + // Note that SubnetDhcpConfig is populated here (rather than the alernative VpcSubnetDhcpConfig) + // This is the recommended way to configure DHCP + SdkFieldName: "SubnetDhcpConfig", + ReflectType: reflect.TypeOf(model.SubnetDhcpConfig{}), + }, + }, +} + +func resourceNsxtVpcSubnet() *schema.Resource { + return &schema.Resource{ + Create: resourceNsxtVpcSubnetCreate, + Read: resourceNsxtVpcSubnetRead, + Update: resourceNsxtVpcSubnetUpdate, + Delete: resourceNsxtVpcSubnetDelete, + Importer: &schema.ResourceImporter{ + State: nsxtVPCPathResourceImporter, + }, + Schema: metadata.GetSchemaFromExtendedSchema(vpcSubnetSchema), + } +} + +func resourceNsxtVpcSubnetExists(sessionContext utl.SessionContext, id string, connector client.Connector) (bool, error) { + var err error + parents := getVpcParentsFromContext(sessionContext) + client := clientLayer.NewSubnetsClient(connector) + _, err = client.Get(parents[0], parents[1], parents[2], id) + if err == nil { + return true, nil + } + + if isNotFoundError(err) { + return false, nil + } + + return false, logAPIError("Error retrieving resource", err) +} + +func resourceNsxtVpcSubnetCreate(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id, err := getOrGenerateID2(d, m, resourceNsxtVpcSubnetExists) + if err != nil { + return err + } + + if err = validateDhcpConfig(d); err != nil { + return err + } + + parents := getVpcParentsFromContext(getSessionContext(d, m)) + displayName := d.Get("display_name").(string) + description := d.Get("description").(string) + tags := getPolicyTagsFromSchema(d) + + obj := model.VpcSubnet{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, vpcSubnetSchema, "", nil); err != nil { + return err + } + + log.Printf("[INFO] Creating VpcSubnet with ID %s", id) + + client := clientLayer.NewSubnetsClient(connector) + err = client.Patch(parents[0], parents[1], parents[2], id, obj) + if err != nil { + return handleCreateError("VpcSubnet", id, err) + } + d.SetId(id) + d.Set("nsx_id", id) + + return resourceNsxtVpcSubnetRead(d, m) +} + +func validateDhcpConfig(d *schema.ResourceData) error { + dhcpConfig, ok := d.GetOk("dhcp_config") + if !ok { + return nil + } + + configList := dhcpConfig.([]interface{}) + if len(configList) == 0 { + return nil + } + + config := configList[0].(map[string]interface{}) + + if config["mode"].(string) == model.SubnetDhcpConfig_MODE_RELAY { + if ac, ok := config["dhcp_server_additional_config"]; ok { + advancedConfig := ac.([]interface{}) + if len(advancedConfig) > 0 { + return fmt.Errorf("dhcp_server_additional_config can not be specified with DHCP_RELAY mode") + } + } + } + + return nil +} + +func resourceNsxtVpcSubnetRead(d *schema.ResourceData, m interface{}) error { + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining VpcSubnet ID") + } + + client := clientLayer.NewSubnetsClient(connector) + parents := getVpcParentsFromContext(getSessionContext(d, m)) + obj, err := client.Get(parents[0], parents[1], parents[2], id) + if err != nil { + return handleReadError(d, "VpcSubnet", id, err) + } + + setPolicyTagsInSchema(d, obj.Tags) + d.Set("nsx_id", id) + d.Set("display_name", obj.DisplayName) + d.Set("description", obj.Description) + d.Set("revision", obj.Revision) + d.Set("path", obj.Path) + // Depending on subnet type, this attribute might not be sent back by NSX + // If not provided by NSX, the next line will explicitly assign empty list to ip_blocks + d.Set("ip_blocks", obj.IpBlocks) + + elem := reflect.ValueOf(&obj).Elem() + return metadata.StructToSchema(elem, d, vpcSubnetSchema, "", nil) +} + +func resourceNsxtVpcSubnetUpdate(d *schema.ResourceData, m interface{}) error { + + connector := getPolicyConnector(m) + + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining VpcSubnet ID") + } + + if err := validateDhcpConfig(d); err != nil { + return err + } + + parents := getVpcParentsFromContext(getSessionContext(d, m)) + description := d.Get("description").(string) + displayName := d.Get("display_name").(string) + tags := getPolicyTagsFromSchema(d) + + revision := int64(d.Get("revision").(int)) + + obj := model.VpcSubnet{ + DisplayName: &displayName, + Description: &description, + Tags: tags, + Revision: &revision, + } + + elem := reflect.ValueOf(&obj).Elem() + if err := metadata.SchemaToStruct(elem, d, vpcSubnetSchema, "", nil); err != nil { + return err + } + + // Since dhcp block is Computed (sent back by NSX even if not specified), we need to + // explicitly clear out additional DHCP config in case of DHCP RELAY mode, otherwise + // NSX throws an error + if (obj.SubnetDhcpConfig != nil) && (obj.SubnetDhcpConfig.Mode != nil && *obj.SubnetDhcpConfig.Mode == model.SubnetDhcpConfig_MODE_RELAY) { + obj.SubnetDhcpConfig.DhcpServerAdditionalConfig = nil + } + + client := clientLayer.NewSubnetsClient(connector) + _, err := client.Update(parents[0], parents[1], parents[2], id, obj) + if err != nil { + // Trigger partial update to avoid terraform updating state based on failed intent + // TODO - move this into handleUpdateError + d.Partial(true) + return handleUpdateError("VpcSubnet", id, err) + } + + return resourceNsxtVpcSubnetRead(d, m) +} + +func resourceNsxtVpcSubnetDelete(d *schema.ResourceData, m interface{}) error { + id := d.Id() + if id == "" { + return fmt.Errorf("Error obtaining VpcSubnet ID") + } + + connector := getPolicyConnector(m) + parents := getVpcParentsFromContext(getSessionContext(d, m)) + + // Wait until potential VM ports are deleted + pendingStates := []string{"pending"} + targetStates := []string{"ok", "error"} + stateConf := &resource.StateChangeConf{ + Pending: pendingStates, + Target: targetStates, + Refresh: func() (interface{}, string, error) { + portsClient := subnets.NewPortsClient(connector) + ports, err := portsClient.List(parents[0], parents[1], parents[2], id, nil, nil, nil, nil, nil, nil) + if err != nil { + return ports, "error", logAPIError("Error listing VPC subnet ports", err) + } + numOfPorts := len(ports.Results) + log.Printf("[DEBUG] Current number of ports on subnet %s is %d", id, numOfPorts) + + if numOfPorts > 0 { + return ports, "pending", nil + } + return ports, "ok", nil + + }, + Timeout: d.Timeout(schema.TimeoutDelete), + MinTimeout: 1 * time.Second, + Delay: 1 * time.Second, + } + _, err := stateConf.WaitForState() + if err != nil { + return fmt.Errorf("Failed to get port information for subnet %s: %v", id, err) + } + + client := clientLayer.NewSubnetsClient(connector) + err = client.Delete(parents[0], parents[1], parents[2], id) + + if err != nil { + return handleDeleteError("VpcSubnet", id, err) + } + + return nil +} diff --git a/nsxt/resource_nsxt_vpc_subnet_test.go b/nsxt/resource_nsxt_vpc_subnet_test.go new file mode 100644 index 000000000..2f5795341 --- /dev/null +++ b/nsxt/resource_nsxt_vpc_subnet_test.go @@ -0,0 +1,327 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +var accTestVpcSubnetCreateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform created", + "access_mode": "Isolated", + "enabled": "true", + "ipv4_subnet_size": "32", + "ip_addresses": "192.168.240.0/26", + "reserved_ip_ranges": "11.11.50.7", +} + +var accTestVpcSubnetUpdateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform updated", + "access_mode": "Isolated", + "enabled": "true", + "ipv4_subnet_size": "32", + "ip_addresses": "192.168.240.0/26", + "reserved_ip_ranges": "11.11.50.4-11.11.50.6", +} + +func TestAccResourceNsxtVpcSubnet_basic(t *testing.T) { + testResourceName := "nsxt_vpc_subnet.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccOnlyVPC(t) + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtVpcSubnetCheckDestroy(state, accTestVpcSubnetUpdateAttributes["display_name"]) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtVpcSubnetTemplate(true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcSubnetExists(accTestVpcSubnetCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestVpcSubnetCreateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestVpcSubnetCreateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "ip_addresses.0", accTestVpcSubnetCreateAttributes["ip_addresses"]), + resource.TestCheckResourceAttr(testResourceName, "access_mode", accTestVpcSubnetCreateAttributes["access_mode"]), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.mode", "DHCP_DEACTIVATED"), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNsxtVpcSubnetTemplate(false), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcSubnetExists(accTestVpcSubnetUpdateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestVpcSubnetUpdateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestVpcSubnetUpdateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "ip_addresses.0", accTestVpcSubnetUpdateAttributes["ip_addresses"]), + resource.TestCheckResourceAttr(testResourceName, "access_mode", accTestVpcSubnetUpdateAttributes["access_mode"]), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.mode", "DHCP_DEACTIVATED"), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + ), + }, + { + Config: testAccNsxtVpcSubnetMinimalisticIsolated(), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcSubnetExists(accTestVpcSubnetCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtVpcSubnet_subnetSize(t *testing.T) { + testResourceName := "nsxt_vpc_subnet.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccOnlyVPC(t) + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtVpcSubnetCheckDestroy(state, accTestVpcSubnetUpdateAttributes["display_name"]) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtVpcSubnetSizeTemplate(true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcSubnetExists(accTestVpcSubnetCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestVpcSubnetCreateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestVpcSubnetCreateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "ipv4_subnet_size", accTestVpcSubnetCreateAttributes["ipv4_subnet_size"]), + resource.TestCheckResourceAttr(testResourceName, "ip_addresses.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_additional_config.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_additional_config.0.reserved_ip_ranges.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_additional_config.0.reserved_ip_ranges.0", accTestVpcSubnetCreateAttributes["reserved_ip_ranges"]), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_additional_config.0.options.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_additional_config.0.options.0.option121.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_additional_config.0.options.0.other.#", "1"), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + ), + }, + { + Config: testAccNsxtVpcSubnetSizeTemplate(false), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcSubnetExists(accTestVpcSubnetUpdateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestVpcSubnetUpdateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestVpcSubnetUpdateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "ip_addresses.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "ipv4_subnet_size", accTestVpcSubnetCreateAttributes["ipv4_subnet_size"]), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_additional_config.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_additional_config.0.reserved_ip_ranges.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_additional_config.0.reserved_ip_ranges.0", accTestVpcSubnetUpdateAttributes["reserved_ip_ranges"]), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_additional_config.0.options.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_additional_config.0.options.0.option121.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.0.dhcp_server_additional_config.0.options.0.other.#", "1"), + + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + ), + }, + { + Config: testAccNsxtVpcSubnetMinimalisticPublic(), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcSubnetExists(accTestVpcSubnetCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "dhcp_config.#", "1"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtVpcSubnet_importBasic(t *testing.T) { + name := getAccTestResourceName() + testResourceName := "nsxt_vpc_subnet.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccOnlyVPC(t) + }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtVpcSubnetCheckDestroy(state, name) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtVpcSubnetMinimalisticIsolated(), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccResourceNsxtPolicyImportIDRetriever(testResourceName), + }, + }, + }) +} + +func testAccNsxtVpcSubnetExists(displayName string, resourceName string) resource.TestCheckFunc { + return func(state *terraform.State) error { + + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + + rs, ok := state.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("VpcSubnet resource %s not found in resources", resourceName) + } + + resourceID := rs.Primary.ID + if resourceID == "" { + return fmt.Errorf("VpcSubnet resource ID not set in resources") + } + + exists, err := resourceNsxtVpcSubnetExists(testAccGetSessionContext(), resourceID, connector) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("VpcSubnet %s does not exist", resourceID) + } + + return nil + } +} + +func testAccNsxtVpcSubnetCheckDestroy(state *terraform.State, displayName string) error { + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + for _, rs := range state.RootModule().Resources { + + if rs.Type != "nsxt_vpc_subnet" { + continue + } + + resourceID := rs.Primary.Attributes["id"] + exists, err := resourceNsxtVpcSubnetExists(testAccGetSessionContext(), resourceID, connector) + if err == nil { + return err + } + + if exists { + return fmt.Errorf("VpcSubnet %s still exists", displayName) + } + } + return nil +} + +func testAccNsxtVpcSubnetTemplate(createFlow bool) string { + var attrMap map[string]string + if createFlow { + attrMap = accTestVpcSubnetCreateAttributes + } else { + attrMap = accTestVpcSubnetUpdateAttributes + } + + return fmt.Sprintf(` +resource "nsxt_vpc_subnet" "test" { +%s + display_name = "%s" + description = "%s" + + ip_addresses = ["%s"] + access_mode = "%s" + dhcp_config { + mode = "DHCP_DEACTIVATED" + } + + tag { + scope = "scope1" + tag = "tag1" + } +}`, testAccNsxtPolicyMultitenancyContext(), attrMap["display_name"], attrMap["description"], attrMap["ip_addresses"], attrMap["access_mode"]) +} + +func testAccNsxtVpcSubnetSizeTemplate(createFlow bool) string { + var attrMap map[string]string + if createFlow { + attrMap = accTestVpcSubnetCreateAttributes + } else { + attrMap = accTestVpcSubnetUpdateAttributes + } + + return fmt.Sprintf(` +resource "nsxt_vpc_subnet" "test" { +%s + display_name = "%s" + description = "%s" + + ipv4_subnet_size = %s + access_mode = "Public" + + dhcp_config { + mode = "DHCP_SERVER" + + dhcp_server_additional_config { + options { + option121 { + static_route { + network = "2.1.1.0/24" + next_hop = "2.3.1.3" + } + } + other { + code = "119" + values = ["abc", "def"] + } + } + reserved_ip_ranges = ["%s"] + } + } +}`, testAccNsxtPolicyMultitenancyContext(), attrMap["display_name"], attrMap["description"], attrMap["ipv4_subnet_size"], attrMap["reserved_ip_ranges"]) +} + +func testAccNsxtVpcSubnetMinimalisticIsolated() string { + return fmt.Sprintf(` +resource "nsxt_vpc_subnet" "test" { +%s + display_name = "%s" + ip_addresses = ["%s"] + access_mode = "Isolated" +}`, testAccNsxtPolicyMultitenancyContext(), accTestVpcSubnetUpdateAttributes["display_name"], accTestVpcSubnetUpdateAttributes["ip_addresses"]) +} + +func testAccNsxtVpcSubnetMinimalisticPublic() string { + return fmt.Sprintf(` +resource "nsxt_vpc_subnet" "test" { +%s + display_name = "%s" + access_mode = "Public" +}`, testAccNsxtPolicyMultitenancyContext(), accTestVpcSubnetUpdateAttributes["display_name"]) +} diff --git a/nsxt/resource_nsxt_vpc_test.go b/nsxt/resource_nsxt_vpc_test.go new file mode 100644 index 000000000..45c4aa52f --- /dev/null +++ b/nsxt/resource_nsxt_vpc_test.go @@ -0,0 +1,274 @@ +/* Copyright © 2024 Broadcom, Inc. All Rights Reserved. + SPDX-License-Identifier: MPL-2.0 */ + +package nsxt + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/vmware/vsphere-automation-sdk-go/services/nsxt/model" +) + +var testAccNsxtVpcHelperName = getAccTestResourceName() + +// shortId is limited to 8 chars +var testAccNsxtVpcShortID = testAccNsxtVpcHelperName[len(testAccNsxtVpcHelperName)-8:] +var accTestVpcCreateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform created", + "private_ips": "192.168.44.0/24", + "short_id": testAccNsxtVpcShortID, + "enabled": "false", +} + +var accTestVpcUpdateAttributes = map[string]string{ + "display_name": getAccTestResourceName(), + "description": "terraform updated", + "private_ips": "192.168.44.0/24", + "short_id": testAccNsxtVpcShortID, + "enabled": "false", +} + +func TestAccResourceNsxtVpc_basic(t *testing.T) { + testResourceName := "nsxt_vpc.test" + attachmentResourceName := "nsxt_vpc_attachment.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccOnlyVPC(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtVpcCheckDestroy(state, accTestVpcUpdateAttributes["display_name"]) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtVpcTemplate(true), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcExists(accTestVpcCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestVpcCreateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestVpcCreateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "private_ips.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "private_ips.0", accTestVpcCreateAttributes["private_ips"]), + resource.TestCheckResourceAttr(testResourceName, "ip_address_type", model.Vpc_IP_ADDRESS_TYPE_IPV4), + resource.TestCheckResourceAttr(testResourceName, "short_id", accTestVpcCreateAttributes["short_id"]), + resource.TestCheckResourceAttr(testResourceName, "load_balancer_vpc_endpoint.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "load_balancer_vpc_endpoint.0.enabled", accTestVpcCreateAttributes["enabled"]), + resource.TestCheckResourceAttrSet(testResourceName, "vpc_service_profile"), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + resource.TestCheckResourceAttrSet(attachmentResourceName, "parent_path"), + resource.TestCheckResourceAttrSet(attachmentResourceName, "vpc_connectivity_profile"), + ), + }, + { + Config: testAccNsxtVpcTemplate(false), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcExists(accTestVpcUpdateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttr(testResourceName, "display_name", accTestVpcUpdateAttributes["display_name"]), + resource.TestCheckResourceAttr(testResourceName, "description", accTestVpcUpdateAttributes["description"]), + resource.TestCheckResourceAttr(testResourceName, "private_ips.0", accTestVpcUpdateAttributes["private_ips"]), + resource.TestCheckResourceAttr(testResourceName, "ip_address_type", model.Vpc_IP_ADDRESS_TYPE_IPV4), + resource.TestCheckResourceAttr(testResourceName, "short_id", accTestVpcUpdateAttributes["short_id"]), + resource.TestCheckResourceAttr(testResourceName, "load_balancer_vpc_endpoint.#", "1"), + resource.TestCheckResourceAttr(testResourceName, "load_balancer_vpc_endpoint.0.enabled", accTestVpcUpdateAttributes["enabled"]), + resource.TestCheckResourceAttrSet(testResourceName, "vpc_service_profile"), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "1"), + resource.TestCheckResourceAttrSet(attachmentResourceName, "parent_path"), + resource.TestCheckResourceAttrSet(attachmentResourceName, "vpc_connectivity_profile"), + ), + }, + { + Config: testAccNsxtVpcMinimalistic(), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcExists(accTestVpcCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttrSet(testResourceName, "vpc_service_profile"), + resource.TestCheckResourceAttr(testResourceName, "short_id", accTestVpcUpdateAttributes["short_id"]), + resource.TestCheckResourceAttr(testResourceName, "load_balancer_vpc_endpoint.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttrSet(testResourceName, "nsx_id"), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + ), + }, + { + Config: testAccNsxtVpcMinimalisticNoShortId(), + Check: resource.ComposeTestCheckFunc( + testAccNsxtVpcExists(accTestVpcCreateAttributes["display_name"], testResourceName), + resource.TestCheckResourceAttrSet(testResourceName, "vpc_service_profile"), + resource.TestCheckResourceAttr(testResourceName, "load_balancer_vpc_endpoint.#", "0"), + resource.TestCheckResourceAttr(testResourceName, "description", ""), + resource.TestCheckResourceAttr(testResourceName, "short_id", accTestVpcUpdateAttributes["short_id"]), + resource.TestCheckResourceAttr(testResourceName, "nsx_id", accTestVpcUpdateAttributes["short_id"]), + resource.TestCheckResourceAttrSet(testResourceName, "path"), + resource.TestCheckResourceAttrSet(testResourceName, "revision"), + resource.TestCheckResourceAttr(testResourceName, "tag.#", "0"), + ), + }, + }, + }) +} + +func TestAccResourceNsxtVpc_importBasic(t *testing.T) { + name := getAccTestResourceName() + testResourceName := "nsxt_vpc.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccOnlyVPC(t) }, + Providers: testAccProviders, + CheckDestroy: func(state *terraform.State) error { + return testAccNsxtVpcCheckDestroy(state, name) + }, + Steps: []resource.TestStep{ + { + Config: testAccNsxtVpcMinimalistic(), + }, + { + ResourceName: testResourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccResourceNsxtPolicyImportIDRetriever(testResourceName), + }, + }, + }) +} + +func testAccNsxtVpcExists(displayName string, resourceName string) resource.TestCheckFunc { + return func(state *terraform.State) error { + + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + + rs, ok := state.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Policy Vpc resource %s not found in resources", resourceName) + } + + resourceID := rs.Primary.ID + if resourceID == "" { + return fmt.Errorf("Policy Vpc resource ID not set in resources") + } + + exists, err := resourceNsxtVpcExists(testAccGetProjectContext(), resourceID, connector) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("Policy Vpc %s does not exist", resourceID) + } + + return nil + } +} + +func testAccNsxtVpcCheckDestroy(state *terraform.State, displayName string) error { + connector := getPolicyConnector(testAccProvider.Meta().(nsxtClients)) + for _, rs := range state.RootModule().Resources { + + if rs.Type != "nsxt_vpc" { + continue + } + + resourceID := rs.Primary.Attributes["id"] + exists, err := resourceNsxtVpcExists(testAccGetProjectContext(), resourceID, connector) + if err == nil { + return err + } + + if exists { + return fmt.Errorf("Policy Vpc %s still exists", displayName) + } + } + return nil +} + +var testAccNsxtVpcHelper = getAccTestResourceName() + +func testAccNsxtVpcPrerequisites() string { + return testAccNsxtVpcConnectivityProfilePrerequisite() + fmt.Sprintf(` +resource "nsxt_vpc_service_profile" "test" { + %s + display_name = "%s" + dhcp_config { + dhcp_server_config { + lease_time = 15600 + } + } +} + +resource "nsxt_vpc_connectivity_profile" "test" { + %s + display_name = "%s" + + transit_gateway_path = data.nsxt_policy_transit_gateway.test.path + service_gateway { + enable = false + } +}`, testAccNsxtProjectContext(), testAccNsxtVpcHelper, testAccNsxtProjectContext(), testAccNsxtVpcHelper) +} + +func testAccNsxtVpcTemplate(createFlow bool) string { + var attrMap map[string]string + if createFlow { + attrMap = accTestVpcCreateAttributes + } else { + attrMap = accTestVpcUpdateAttributes + } + return testAccNsxtVpcPrerequisites() + fmt.Sprintf(` +resource "nsxt_vpc" "test" { + %s + + display_name = "%s" + description = "%s" + private_ips = ["%s"] + short_id = "%s" + + vpc_service_profile = nsxt_vpc_service_profile.test.path + + load_balancer_vpc_endpoint { + enabled = %s + } + + tag { + scope = "scope1" + tag = "tag1" + } +} + +resource "nsxt_vpc_attachment" "test" { + display_name = "%s" + parent_path = nsxt_vpc.test.path + vpc_connectivity_profile = nsxt_vpc_connectivity_profile.test.path +} +`, testAccNsxtProjectContext(), attrMap["display_name"], attrMap["description"], attrMap["private_ips"], attrMap["short_id"], attrMap["enabled"], attrMap["display_name"]) +} + +func testAccNsxtVpcMinimalistic() string { + return testAccNsxtVpcPrerequisites() + fmt.Sprintf(` +resource "nsxt_vpc" "test" { + %s + + display_name = "%s" + short_id = "%s" + # TODO - remove when default profiles are supported + vpc_service_profile = nsxt_vpc_service_profile.test.path +}`, testAccNsxtProjectContext(), accTestVpcUpdateAttributes["display_name"], accTestVpcUpdateAttributes["short_id"]) +} + +// We use short_id as nsx_id to make sure NSX populates the short_id correctly +func testAccNsxtVpcMinimalisticNoShortId() string { + return testAccNsxtVpcPrerequisites() + fmt.Sprintf(` +resource "nsxt_vpc" "test" { + %s + nsx_id = "%s" + display_name = "%s" + # TODO - remove when default profiles are supported + vpc_service_profile = nsxt_vpc_service_profile.test.path +}`, testAccNsxtProjectContext(), accTestVpcUpdateAttributes["short_id"], accTestVpcUpdateAttributes["display_name"]) +} diff --git a/nsxt/utils_test.go b/nsxt/utils_test.go index 4fc1b7181..b00aec437 100644 --- a/nsxt/utils_test.go +++ b/nsxt/utils_test.go @@ -262,6 +262,18 @@ func testAccGetSessionContext() tf_api.SessionContext { return tf_api.SessionContext{ProjectID: projectID, ClientType: clientType, VPCID: vpcID} } +func testAccGetSessionProjectContext() tf_api.SessionContext { + clientType := testAccIsGlobalManager2() + projectID := os.Getenv("NSXT_PROJECT_ID") + vpcID := "" + return tf_api.SessionContext{ProjectID: projectID, ClientType: clientType, VPCID: vpcID} +} + +func testAccGetProjectContext() tf_api.SessionContext { + projectID := os.Getenv("NSXT_PROJECT_ID") + return tf_api.SessionContext{ProjectID: projectID, ClientType: tf_api.Multitenancy} +} + func testAccIsGlobalManager2() tf_api.ClientType { if testAccIsVPC() { return tf_api.VPC @@ -725,6 +737,15 @@ func testAccNsxtPolicyMultitenancyContext() string { return "" } +func testAccNsxtProjectContext() string { + projectID := os.Getenv("NSXT_PROJECT_ID") + return fmt.Sprintf(` + context { + project_id = "%s" + } +`, projectID) +} + func testAccNsxtMultitenancyContext(includeVpc bool) string { if testAccIsVPC() { // Some tests run in VPC context, however dependency resources are @@ -766,10 +787,6 @@ func testAccResourceNsxtPolicyImportIDRetriever(resourceID string) func(*terrafo if !ok { return "", fmt.Errorf("NSX Policy %s resource not found in resources", resourceID) } - resourceID := rs.Primary.ID - if resourceID == "" { - return "", fmt.Errorf("NSX Policy %s resource ID not set in resources", resourceID) - } path := rs.Primary.Attributes["path"] if path == "" { return "", fmt.Errorf("NSX Policy %s path not set in resources ", resourceID) diff --git a/nsxt/validators.go b/nsxt/validators.go index f28205ca8..b420e0dff 100644 --- a/nsxt/validators.go +++ b/nsxt/validators.go @@ -6,6 +6,7 @@ package nsxt import ( "fmt" "net" + "regexp" "strconv" "strings" @@ -196,6 +197,25 @@ func validateSingleIP() schema.SchemaValidateFunc { } } +func validateSingleIPOrHostName() schema.SchemaValidateFunc { + return func(i interface{}, k string) (s []string, es []error) { + v, ok := i.(string) + if !ok { + es = append(es, fmt.Errorf("expected type of %s to be string", k)) + return + } + + if !isSingleIP(v) { + match, _ := regexp.MatchString(`^[a-zA-Z0-9\.\-]+$`, v) + if !match { + es = append(es, fmt.Errorf( + "expected %s to contain a valid IP or hostname, got: %s", k, v)) + } + } + return + } +} + func isValidStringUint(value string, bits int) bool { _, err := strconv.ParseUint(value, 10, bits) return (err == nil) diff --git a/website/docs/d/policy_distributed_vlan_connection.html.markdown b/website/docs/d/policy_distributed_vlan_connection.html.markdown new file mode 100644 index 000000000..42c0967ab --- /dev/null +++ b/website/docs/d/policy_distributed_vlan_connection.html.markdown @@ -0,0 +1,32 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: policy_distributed_vlan_connection" +description: Policy distributed VLAN connection data source. +--- + +# nsxt_policy_distributed_vlan_connection + +This data source provides information about Distributed VLAN Connection on NSX. + +This data source is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +data "nsxt_policy_distributed_vlan_connection" "test" { + display_name = "test" +} +``` + +## Argument Reference + +* `id` - (Optional) The ID of transit gateway to retrieve. +* `display_name` - (Optional) The Display Name prefix of the transit gateway to retrieve. + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `description` - The description of the resource. +* `path` - The NSX path of the policy resource. diff --git a/website/docs/d/policy_gateway_connection.html.markdown b/website/docs/d/policy_gateway_connection.html.markdown new file mode 100644 index 000000000..2053d651d --- /dev/null +++ b/website/docs/d/policy_gateway_connection.html.markdown @@ -0,0 +1,34 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: policy_gateway_connection" +description: Policy gateway connection data source. +--- + +# nsxt_policy_gateway_connection + +This data source provides information about Gateway Connection on NSX. + +This data source is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +data "nsxt_policy_gateway_connection" "test" { + display_name = "test" + tier0_path = data.nsxt_policy_tier0_gateway.path +} +``` + +## Argument Reference + +* `id` - (Optional) The ID of transit gateway to retrieve. +* `display_name` - (Optional) The Display Name prefix of the transit gateway to retrieve. +* `tier0_path` - (Optional) Path of Tier0 for this connection + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `description` - The description of the resource. +* `path` - The NSX path of the policy resource. diff --git a/website/docs/d/policy_project.html.markdown b/website/docs/d/policy_project.html.markdown index a9e7c912c..bab0a2384 100644 --- a/website/docs/d/policy_project.html.markdown +++ b/website/docs/d/policy_project.html.markdown @@ -8,7 +8,7 @@ description: Policy Project data source. # nsxt_policy_project This data source provides information about policy Project configured on NSX. -This data source is applicable to NSX Policy Manager and VMC. +This data source is applicable to NSX Policy Manager. ## Example Usage @@ -29,8 +29,9 @@ In addition to arguments listed above, the following attributes are exported: * `description` - The description of the resource. * `path` - The NSX path of the policy resource. -* `short_id` - Defaults to id if id is less than equal to 8 characters or defaults to random generated id if not set. +* `short_id` - Unique ID used for logging. * `site_info` - Information related to sites applicable for given Project. - * `edge_cluster_paths` - The edge cluster on which the networking elements for the Org will be created. - * `site_path` - This represents the path of the site which is managed by Global Manager. For the local manager, if set, this needs to point to 'default'. -* `tier0_gateway_paths` - The tier 0 has to be pre-created before Project is created. + * `edge_cluster_paths` - The edge cluster on which the networking elements for the Org are be created. + * `site_path` - This represents the path of the site which is managed by Global Manager. For the local manager the value would be 'default'. +* `tier0_gateway_paths` - Policy paths of Tier0 gateways associated with the project. +* `external_ipv4_blocks` - Policy paths of IPv4 blocks associated with the project. diff --git a/website/docs/d/policy_project_ip_address_allocation.html.markdown b/website/docs/d/policy_project_ip_address_allocation.html.markdown new file mode 100644 index 000000000..9c42dcad3 --- /dev/null +++ b/website/docs/d/policy_project_ip_address_allocation.html.markdown @@ -0,0 +1,51 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: policy_project_ip_address_allocation" +description: Project IP address allocation data source. +--- + +# nsxt_policy_project_ip_address_allocation + +This data source provides information about IP address allocation under VPC on NSX. + +This data source is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +data "nsxt_policy_project" "demoproj" { + display_name = "demoproj" +} + +data "nsxt_vpc" "demovpc" { + context { + project_id = data.nsxt_policy_project.demoproj.id + } + display_name = "vpc1" +} + +data "nsxt_policy_project_ip_address_allocation" "test" { + context { + project_id = data.nsxt_policy_project.demoproj.id + vpc_id = data.nsxt_vpc.demovpc.id + } + allocation_ips = "10.203.5.19" +} +``` + +## Argument Reference + +* `id` - (Optional) The ID of Subnet to retrieve. +* `display_name` - (Optional) The Display Name prefix of the Subnet to retrieve. +* `context` - (Required) The context which the object belongs to + * `project_id` - (Required) The ID of the project which the object belongs to + * `vpc_id` - (Required) The ID of the VPC which the object belongs to +* `allocation_ips` - (Optional) IP address or CIDR that was allocated + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `description` - The description of the resource. +* `path` - The NSX path of the policy resource. diff --git a/website/docs/d/policy_transit_gateway.html.markdown b/website/docs/d/policy_transit_gateway.html.markdown new file mode 100644 index 000000000..6d938cb62 --- /dev/null +++ b/website/docs/d/policy_transit_gateway.html.markdown @@ -0,0 +1,41 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: policy_transit_gateway" +description: Policy transit gateway data source. +--- + +# nsxt_policy_transit_gateway + +This data source provides information about an inventory transit gateway on NSX. + +This data source is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +data "nsxt_policy_project" "demoproj" { + display_name = "demoproj" +} + +data "nsxt_policy_transit_gateway" "test" { + context { + project_id = data.nsxt_policy_project.demoproj.id + } + display_name = "tgw1" +} +``` + +## Argument Reference + +* `id` - (Optional) The ID of transit gateway to retrieve. +* `display_name` - (Optional) The Display Name prefix of the transit gateway to retrieve. +* `context` - (Required) The context which the object belongs to + * `project_id` - (Required) The ID of the project which the object belongs to + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `description` - The description of the resource. +* `path` - The NSX path of the policy resource. diff --git a/website/docs/d/policy_transit_gateway_nat.html.markdown b/website/docs/d/policy_transit_gateway_nat.html.markdown new file mode 100644 index 000000000..fc8a6c826 --- /dev/null +++ b/website/docs/d/policy_transit_gateway_nat.html.markdown @@ -0,0 +1,45 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: policy_transit_gateway_nat" +description: Transit Gateway NAT data source. +--- + +# nsxt_policy_transit_gateway_nat + +This data source provides information about an NAT section configured under Transit Gateway on NSX. + +This data source is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +data "nsxt_policy_project" "proj" { + display_name = "demoproj" +} + +data "nsxt_policy_transit_gateway" "tgw1" { + context { + project_id = data.nsxt_policy_project.proj.id + } + display_name = "TGW1" +} + +data "nsxt_policy_transit_gateway_nat" "test" { + transit_gateway_path = data.nsxt_policy_transit_gateway.tgw1.path +} +``` + +## Argument Reference + +* `transit_gateway_path` - (Required) Policy path of parent Transit Gateway +* `type` - (Optional) Type of NAT, one of `USER`, `DEFAULT`. Default is `USER`. + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `id` - (Optional) The ID of the resource. +* `display_name` - (Optional) Display Name of the resource. +* `description` - The description of the resource. +* `path` - The NSX path of the policy resource. diff --git a/website/docs/d/vpc.html.markdown b/website/docs/d/vpc.html.markdown index cf4e960a3..0eb02facf 100644 --- a/website/docs/d/vpc.html.markdown +++ b/website/docs/d/vpc.html.markdown @@ -1,5 +1,5 @@ --- -subcategory: "Multitenancy" +subcategory: "VPC" layout: "nsxt" page_title: "NSXT: vpc" description: VPC data source. diff --git a/website/docs/d/vpc_connectivity_profile.html.markdown b/website/docs/d/vpc_connectivity_profile.html.markdown new file mode 100644 index 000000000..b69a86887 --- /dev/null +++ b/website/docs/d/vpc_connectivity_profile.html.markdown @@ -0,0 +1,41 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: vpc_connectivity_profile" +description: VPC Connectivity Profile data source. +--- + +# nsxt_vpc_connectivity_profile + +This data source provides information about an inventory Connectivity Profile configured under VPC on NSX. + +This data source is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +data "nsxt_policy_project" "demoproj" { + display_name = "demoproj" +} + +data "nsxt_vpc_connectivity_profile" "test" { + context { + project_id = data.nsxt_policy_project.demoproj.id + } + display_name = "profile1" +} +``` + +## Argument Reference + +* `id` - (Optional) The ID of Connectivity Profile to retrieve. +* `display_name` - (Optional) The Display Name prefix of the Connectivity Profile to retrieve. +* `context` - (Required) The context which the object belongs to + * `project_id` - (Required) The ID of the project which the object belongs to + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `description` - The description of the resource. +* `path` - The NSX path of the policy resource. diff --git a/website/docs/d/vpc_group.html.markdown b/website/docs/d/vpc_group.html.markdown new file mode 100644 index 000000000..1e6086dec --- /dev/null +++ b/website/docs/d/vpc_group.html.markdown @@ -0,0 +1,50 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: vpc_group" +description: VPC Group data source. +--- + +# nsxt_vpc_group + +This data source provides information about an inventory Group configured under VPC on NSX. + +This data source is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +data "nsxt_policy_project" "demoproj" { + display_name = "demoproj" +} + +data "nsxt_vpc" "demovpc" { + context { + project_id = data.nsxt_policy_project.demoproj.id + } + display_name = "vpc1" +} + +data "nsxt_vpc_group" "test" { + context { + project_id = data.nsxt_policy_project.demoproj.id + vpc_id = data.nsxt_vpc.demovpc.id + } + display_name = "group1" +} +``` + +## Argument Reference + +* `id` - (Optional) The ID of Group to retrieve. +* `display_name` - (Optional) The Display Name prefix of the Group to retrieve. +* `context` - (Required) The context which the object belongs to + * `project_id` - (Required) The ID of the project which the object belongs to + * `vpc_id` - (Required) The ID of the VPC which the object belongs to + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `description` - The description of the resource. +* `path` - The NSX path of the policy resource. diff --git a/website/docs/d/vpc_ip_address_allocation.html.markdown b/website/docs/d/vpc_ip_address_allocation.html.markdown new file mode 100644 index 000000000..e3494ebf5 --- /dev/null +++ b/website/docs/d/vpc_ip_address_allocation.html.markdown @@ -0,0 +1,51 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: vpc_ip_address_allocation" +description: VPC IP address allocation data source. +--- + +# nsxt_vpc_ip_address_allocation + +This data source provides information about IP address allocation under VPC on NSX. + +This data source is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +data "nsxt_policy_project" "demoproj" { + display_name = "demoproj" +} + +data "nsxt_vpc" "demovpc" { + context { + project_id = data.nsxt_policy_project.demoproj.id + } + display_name = "vpc1" +} + +data "nsxt_vpc_ip_address_allocation" "test" { + context { + project_id = data.nsxt_policy_project.demoproj.id + vpc_id = data.nsxt_vpc.demovpc.id + } + allocation_ips = "10.203.5.19" +} +``` + +## Argument Reference + +* `id` - (Optional) The ID of Subnet to retrieve. +* `display_name` - (Optional) The Display Name prefix of the Subnet to retrieve. +* `context` - (Required) The context which the object belongs to + * `project_id` - (Required) The ID of the project which the object belongs to + * `vpc_id` - (Required) The ID of the VPC which the object belongs to +* `allocation_ips` - (Optional) IP or CIDR that was allocated + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `description` - The description of the resource. +* `path` - The NSX path of the policy resource. diff --git a/website/docs/d/vpc_nat.html.markdown b/website/docs/d/vpc_nat.html.markdown new file mode 100644 index 000000000..9b9c115f8 --- /dev/null +++ b/website/docs/d/vpc_nat.html.markdown @@ -0,0 +1,51 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: vpc_nat" +description: VPC NAT data source. +--- + +# nsxt_vpc_nat + +This data source provides information about an NAT section configured under VPC on NSX. + +This data source is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +data "nsxt_policy_project" "proj" { + display_name = "demoproj" +} + +data "nsxt_vpc" "vpc1" { + context { + project_id = data.nsxt_policy_project.proj.id + } + display_name = "vpc1" +} + +data "nsxt_vpc_nat" "test" { + context { + project_id = data.nsxt_policy_project.proj.id + vpc_id = data.nsxt_vpc.vpc1.id + } + nat_type = "USER" +} +``` + +## Argument Reference + +* `nat_type` - (Required) Type of NAT, one of `USER`, `INTERNAL`, `DEFAULT` or `NAT64`. +* `context` - (Required) The context which the object belongs to + * `project_id` - (Required) The ID of the project which the object belongs to + * `vpc_id` - (Required) The ID of the VPC which the object belongs to + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `id` - (Optional) The ID of the resource. +* `display_name` - (Optional) Display Name of the resource. +* `description` - The description of the resource. +* `path` - The NSX path of the policy resource. diff --git a/website/docs/d/vpc_service_profile.html.markdown b/website/docs/d/vpc_service_profile.html.markdown new file mode 100644 index 000000000..b1c04e1fd --- /dev/null +++ b/website/docs/d/vpc_service_profile.html.markdown @@ -0,0 +1,41 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: vpc_service_profile" +description: VPC Service Profile data source. +--- + +# nsxt_vpc_service_profile + +This data source provides information about an inventory Service Profile configured under VPC on NSX. + +This data source is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +data "nsxt_policy_project" "demoproj" { + display_name = "demoproj" +} + +data "nsxt_vpc_service_profile" "test" { + context { + project_id = data.nsxt_policy_project.demoproj.id + } + display_name = "profile1" +} +``` + +## Argument Reference + +* `id` - (Optional) The ID of Service Profile to retrieve. +* `display_name` - (Optional) The Display Name prefix of the Service Profile to retrieve. +* `context` - (Required) The context which the object belongs to + * `project_id` - (Required) The ID of the project which the object belongs to + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `description` - The description of the resource. +* `path` - The NSX path of the policy resource. diff --git a/website/docs/d/vpc_subnet.html.markdown b/website/docs/d/vpc_subnet.html.markdown new file mode 100644 index 000000000..31e5219da --- /dev/null +++ b/website/docs/d/vpc_subnet.html.markdown @@ -0,0 +1,50 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: vpc_subnet" +description: VPC Subnet data source. +--- + +# nsxt_vpc_subnet + +This data source provides information about a Subnet configured under VPC on NSX. + +This data source is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +data "nsxt_policy_project" "demoproj" { + display_name = "demoproj" +} + +data "nsxt_vpc" "demovpc" { + context { + project_id = data.nsxt_policy_project.demoproj.id + } + display_name = "vpc1" +} + +data "nsxt_vpc_subnet" "test" { + context { + project_id = data.nsxt_policy_project.demoproj.id + vpc_id = data.nsxt_vpc.demovpc.id + } + display_name = "subnet1" +} +``` + +## Argument Reference + +* `id` - (Optional) The ID of Subnet to retrieve. +* `display_name` - (Optional) The Display Name prefix of the Subnet to retrieve. +* `context` - (Required) The context which the object belongs to + * `project_id` - (Required) The ID of the project which the object belongs to + * `vpc_id` - (Required) The ID of the VPC which the object belongs to + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `description` - The description of the resource. +* `path` - The NSX path of the policy resource. diff --git a/website/docs/d/vpc_subnet_port.html.markdown b/website/docs/d/vpc_subnet_port.html.markdown new file mode 100644 index 000000000..b03af5f67 --- /dev/null +++ b/website/docs/d/vpc_subnet_port.html.markdown @@ -0,0 +1,53 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: vpc_subnet_port" +description: VPC Subnet Port data source. +--- + +# nsxt_vpc_subnet_port + +This data source provides information about Subnet Port configured under VPC on NSX. + +This data source is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +data "nsxt_policy_project" "demoproj" { + display_name = "demoproj" +} + +data "nsxt_vpc" "demovpc" { + context { + project_id = data.nsxt_policy_project.demoproj.id + } + display_name = "vpc1" +} + +data "nsxt_vpc_subnet" "test" { + context { + project_id = data.nsxt_policy_project.demoproj.id + vpc_id = data.nsxt_vpc.demovpc.id + } + display_name = "subnet1" +} + +data "nsxt_vpc_subnet_port" "test" { + subnet_path = data.nsxt_vpc_subnet.test.path + vm_id = data.vpshere_virtual_machine.vm1.id +} +``` + +## Argument Reference + +* `subnet_path` - (Required) Policy path of Subnet for the port. +* `vm_id` - (Required) Policy path of VM connected to the port. + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `description` - The description of the resource. +* `path` - The NSX path of the policy resource. +* `display_name` - The Display Name of the resource. diff --git a/website/docs/r/policy_distributed_vlan_connection.html.markdown b/website/docs/r/policy_distributed_vlan_connection.html.markdown new file mode 100644 index 000000000..6670d20f0 --- /dev/null +++ b/website/docs/r/policy_distributed_vlan_connection.html.markdown @@ -0,0 +1,55 @@ +--- +subcategory: "Beta" +layout: "nsxt" +page_title: "NSXT: nsxt_policy_distributed_vlan_connection" +description: A resource to configure a Distributed Vlan Connection. +--- + +# nsxt_policy_distributed_vlan_connection + +This resource provides a method for the management of a Distributed Vlan Connection. + +This resource is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +resource "nsxt_policy_distributed_vlan_connection" "test" { + display_name = "test" + description = "Terraform provisioned Distributed Vlan Connection" + gateway_addresses = ["192.168.2.1/24", "192.168.3.1/24"] + vlan_id = 12 +} +``` + +## Argument Reference + +The following arguments are supported: + +* `display_name` - (Required) Display name of the resource. +* `description` - (Optional) Description of the resource. +* `tag` - (Optional) A list of scope + tag pairs to associate with this resource. +* `nsx_id` - (Optional) The NSX ID of this resource. If set, this ID will be used to create the resource. +* `vlan_id` - (Required) Vlan id for external gateway traffic. +* `gateway_addresses` - (Required) List of gateway addresses in CIDR format. + + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `id` - ID of the resource. +* `revision` - Indicates current revision number of the object as seen by NSX-T API server. This attribute can be useful for debugging. +* `path` - The NSX path of the policy resource. + +## Importing + +An existing object can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_policy_distributed_vlan_connection.test PATH +``` + +The above command imports Distributed Vlan Connection named `test` with the policy path `PATH`. diff --git a/website/docs/r/policy_gateway_connection.html.markdown b/website/docs/r/policy_gateway_connection.html.markdown new file mode 100644 index 000000000..fe2e96999 --- /dev/null +++ b/website/docs/r/policy_gateway_connection.html.markdown @@ -0,0 +1,62 @@ +--- +subcategory: "Beta" +layout: "nsxt" +page_title: "NSXT: nsxt_policy_gateway_connection" +description: A resource to configure a GatewayConnection. +--- + +# nsxt_policy_gateway_connection + +This resource provides a method for the management of a GatewayConnection. + +This resource is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +data "nsxt_policy_tier0_gateway" "test" { + display_name = "test-t0gw" +} + +resource "nsxt_policy_gateway_connection" "test" { + display_name = "test" + description = "Terraform provisioned GatewayConnection" + tier0_path = data.nsxt_policy_tier0_gateway.test.path + aggregate_routes = ["192.168.240.0/24"] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `display_name` - (Required) Display name of the resource. +* `description` - (Optional) Description of the resource. +* `tier0_path` - (Required) Tier-0 gateway object path +* `tag` - (Optional) A list of scope + tag pairs to associate with this resource. +* `nsx_id` - (Optional) The NSX ID of this resource. If set, this ID will be used to create the resource. +* `advertise_outbound_route_filters` - (Optional) List of prefixlist object paths that will have Transit gateway to tier-0 gateway advertise route filter. +* `aggregate_routes` - (Optional) Configure aggregate TGW_PREFIXES routes on Tier-0 gateway for prefixes owned by TGW gateway. +If not specified then in-use prefixes are configured as TGW_PREFIXES routes on Tier-0 gateway. + + + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `id` - ID of the resource. +* `revision` - Indicates current revision number of the object as seen by NSX-T API server. This attribute can be useful for debugging. +* `path` - The NSX path of the policy resource. + +## Importing + +An existing object can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_policy_gateway_connection.test PATH +``` + +The above command imports GatewayConnection named `test` with the policy path `PATH`. diff --git a/website/docs/r/policy_host_transport_node_profile.html.markdown b/website/docs/r/policy_host_transport_node_profile.html.markdown index ba0e2eb8d..06e19d668 100644 --- a/website/docs/r/policy_host_transport_node_profile.html.markdown +++ b/website/docs/r/policy_host_transport_node_profile.html.markdown @@ -10,6 +10,8 @@ description: A resource to configure a Policy Host Transport Node Profile. This resource provides a method for the management of a Policy Host Transport Node Profile. This resource is supported with NSX 4.1.0 onwards. +**NOTE:** In case that the `nsxt_policy_host_transport_node_profile` is created in the same plan with `nsxt_compute_manager`, it is required to add a dependency between the two. + ## Example Usage ```hcl resource "nsxt_policy_host_transport_node_profile" "test" { diff --git a/website/docs/r/policy_project.html.markdown b/website/docs/r/policy_project.html.markdown index e4845e2f1..2326bb81e 100644 --- a/website/docs/r/policy_project.html.markdown +++ b/website/docs/r/policy_project.html.markdown @@ -32,12 +32,16 @@ The following arguments are supported: * `tag` - (Optional) A list of scope + tag pairs to associate with this resource. * `nsx_id` - (Optional) The NSX ID of this resource. If set, this ID will be used to create the resource. * `short_id` - (Optional) Defaults to id if id is less than equal to 8 characters or defaults to random generated id if not set. +* `activate_default_dfw_rules` - (Optional) By default, Project is created with default distributed firewall rules, this boolean flag allows to deactivate those default rules. If not set, the default rules are enabled. Available since NSX 4.2.0. * `site_info` - (Optional) Information related to sites applicable for given Project. For on-prem deployment, only 1 is allowed. * `edge_cluster_paths` - (Optional) The edge cluster on which the networking elements for the Org will be created. * `site_path` - (Optional) This represents the path of the site which is managed by Global Manager. For the local manager, if set, this needs to point to 'default'. * `tier0_gateway_paths` - (Optional) The tier 0 has to be pre-created before Project is created. The tier 0 typically provides connectivity to external world. List of sites for Project has to be subset of sites where the tier 0 spans. -* `external_ipv4_blocks` - (Optional) PolicyPath of public ip block - +* `external_ipv4_blocks` - (Optional) IP blocks used for allocating CIDR blocks for public subnets. These can be consumed by all the VPCs under this project. Available since NSX 4.1.1. +* `tgw_external_connections` - (Optional) Transit gateway connection objects available to the project. Gateway connection and distributed VLAN connection object path will be allowed. Available since NSX 9.0.0. +* `default_security_profile`- (Optional) Default security profile properties for project. + * `north_south_firewall` - (Required) North South firewall configuration. + * `enabled` - (Required) This flag indicates whether north-south firewall (Gateway Firewall) is enabled. If set to false, then gateway firewall policies will not be enforced on the VPCs associated with this configuration. ## Attributes Reference diff --git a/website/docs/r/policy_shared_resource.html.markdown b/website/docs/r/policy_shared_resource.html.markdown index 4c218b635..c0b733657 100644 --- a/website/docs/r/policy_shared_resource.html.markdown +++ b/website/docs/r/policy_shared_resource.html.markdown @@ -14,7 +14,7 @@ This resource is supported with NSX 4.1.1 onwards. ## Example Usage ```hcl -resource "nsxt_policy_shraed_resource" "test" { +resource "nsxt_policy_shared_resource" "test" { display_name = "test" description = "Terraform provisioned Shared Resource" diff --git a/website/docs/r/policy_transit_gateway.html.markdown b/website/docs/r/policy_transit_gateway.html.markdown new file mode 100644 index 000000000..09fa856ce --- /dev/null +++ b/website/docs/r/policy_transit_gateway.html.markdown @@ -0,0 +1,58 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: nsxt_policy_transit_gateway" +description: A resource to configure a Transit Gateway. +--- + +# nsxt_policy_transit_gateway + +This resource provides a method for the management of a Transit Gateway. + +This resource is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +resource "nsxt_policy_transit_gateway" "test" { + context { + project_id = "dev" + } + + display_name = "test" + description = "Terraform provisioned TransitGateway" + transit_subnets = ["10.203.4.0/24"] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `display_name` - (Required) Display name of the resource. +* `description` - (Optional) Description of the resource. +* `tag` - (Optional) A list of scope + tag pairs to associate with this resource. +* `nsx_id` - (Optional) The NSX ID of this resource. If set, this ID will be used to create the resource. +* `transit_subnets` - (Optional) Array of IPV4 CIDRs for internal VPC attachment networks. + + + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `id` - ID of the resource. +* `revision` - Indicates current revision number of the object as seen by NSX-T API server. This attribute can be useful for debugging. +* `path` - The NSX path of the policy resource. + +## Importing + +An existing object can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_policy_transit_gateway.test PATH +``` + +The above command imports Transit Gateway named `test` with the NSX path `PATH`. diff --git a/website/docs/r/policy_transit_gateway_attachment.html.markdown b/website/docs/r/policy_transit_gateway_attachment.html.markdown new file mode 100644 index 000000000..712ec2774 --- /dev/null +++ b/website/docs/r/policy_transit_gateway_attachment.html.markdown @@ -0,0 +1,69 @@ +--- +subcategory: "Beta" +layout: "nsxt" +page_title: "NSXT: nsxt_policy_transit_gateway_attachment" +description: A resource to configure a Transit Gateway Attachment. +--- + +# nsxt_policy_transit_gateway_attachment + +This resource provides a method for the management of a Transit Gateway Attachment. + +This resource is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +data "nsxt_policy_project" "test_proj" { + display_name = "test_project" +} + +data "nsxt_policy_transit_gateway" "test_tgw" { + context { + project_id = nsxt_policy_project.test_proj.id + } + id = "default" +} + +resource "nsxt_policy_gateway_connection" "test_gw_conn" { + display_name = "test_gw_conn" + tier0_path = "/infra/tier-0s/test-t0" +} + +resource "nsxt_policy_transit_gateway_attachment" "test_tgw_att" { + display_name = "test" + parent_path = nsxt_policy_transit_gateway.test_tgw.path + connection_path = nsxt_policy_gateway_connection.test_gw_conn.path +} +``` + +## Argument Reference + +The following arguments are supported: + +* `parent_path` - (Required) The path of the parent to bind with the profile. This is a policy path of a transit gateway. +* `display_name` - (Required) Display name of the resource. +* `description` - (Optional) Description of the resource. +* `tag` - (Optional) A list of scope + tag pairs to associate with this resource. +* `nsx_id` - (Optional) The NSX ID of this resource. If set, this ID will be used to create the resource. +* `connection_path` - (Required) Policy path of the desired transit gateway external connection. + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `id` - ID of the resource. +* `revision` - Indicates current revision number of the object as seen by NSX-T API server. This attribute can be useful for debugging. +* `path` - The NSX path of the policy resource. + +## Importing + +An existing object can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_policy_transit_gateway_attachment.test PATH +``` + +The above command imports Transit Gateway Attachment named `test` with the policy path `PATH`. diff --git a/website/docs/r/policy_transit_gateway_nat_rule.html.markdown b/website/docs/r/policy_transit_gateway_nat_rule.html.markdown new file mode 100644 index 000000000..ada589da8 --- /dev/null +++ b/website/docs/r/policy_transit_gateway_nat_rule.html.markdown @@ -0,0 +1,81 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: nsxt_policy_transit_gateway_nat_rule" +description: A resource to configure a NAT rule under Transit Gateway. +--- + +# nsxt_policy_transit_gateway_nat_rule + +This resource provides a method for the management of NAT Rule under Transit Gateway. + +This resource is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +data "nsxt_policy_project" "proj" { + display_name = "demoproj" +} + +data "nsxt_policy_transit_gateway" "tgw1" { + context { + project_id = data.nsxt_policy_project.proj.id + } + display_name = "TGW1" +} + +data "nsxt_policy_transit_gateway_nat" "test" { + transit_gateway_path = data.nsxt_policy_transit_gateway.tgw1.path +} + +resource "nsxt_policy_transit_gateway_nat_rule" "test" { + display_name = "test" + description = "terraform provisioned nat rule for vpc" + parent_path = data.nsxt_policy_transit_gateway_nat.test.path + destination_network = nsxt_vpc_ip_address_allocation.nat.allocation_ips + action = "DNAT" + source_network = "10.205.1.13" + translated_network = "2.2.2.13" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `display_name` - (Required) Display name of the resource. +* `description` - (Optional) Description of the resource. +* `tag` - (Optional) A list of scope + tag pairs to associate with this resource. +* `nsx_id` - (Optional) The NSX ID of this resource. If set, this ID will be used to create the resource. +* `parent_path` - (Required) Policy path of parent NAT object, typically reference to `path` in `nsxt_policy_transit_gateway_nat` data source. +* `translated_network` - (Optional) Translated network address. +* `logging` - (Optional) Boolean flag to indicate whether logging is enabled. The default is `false`. +* `destination_network` - (Optional) For `DNAT` rules, this is a required field, and represents the destination network for the incoming packets. For other type of rules, it may contain destination network of outgoing packets. +* `action` - (Required) NAT action, one of `SNAT` (translates a source IP address into an outbound packet so that +the packet appears to originate from a different network), `DNAT` (translates the destination IP address of inbound packets so that packets are delivered to a target address into another network), and `REFLEXIVE` (one-to-one mapping of source and destination IP addresses). +* `firewall_match` - (Optional) Indicates how the firewall matches the address after NATing if firewall +stage is not skipped, one of `MATCH_EXTERNAL_ADDRESS`, `MATCH_INTERNAL_ADDRESS` or `BYPASS`. Default is `MATCH_INTERNAL_ADDRESS`. +* `source_network` - (Optional) Source network. For `SNAT` and `REFLEXIVE` rules, this is a required field. For `DNAT` rules, it may contain source network for incoming packets. +* `enabled` - (Optional) Flag for enabling the NAT rule, default is `true`. +* `sequence_number` - (Optional) The sequence_number decides the rule_priority of a NAT rule. + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `id` - ID of the resource. +* `revision` - Indicates current revision number of the object as seen by NSX-T API server. This attribute can be useful for debugging. +* `path` - The NSX path of the policy resource. + +## Importing + +An existing object can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_policy_transit_gateway_nat_rule.test PATH +``` + +The above command imports Nat Rule named `test` with the policy path `PATH`. diff --git a/website/docs/r/project_ip_address_allocation.html.markdown b/website/docs/r/project_ip_address_allocation.html.markdown new file mode 100644 index 000000000..b4db7e3e0 --- /dev/null +++ b/website/docs/r/project_ip_address_allocation.html.markdown @@ -0,0 +1,57 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: nsxt_policy_project_ip_address_allocation" +description: A resource to configure IP Address Allocation under Project. +--- + +# nsxt_policy_project_ip_address_allocation + +This resource provides a method for allocating IP Address from IP block associated with VPC. + +This resource is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +resource "nsxt_policy_project_ip_address_allocation" "nat" { + context { + project_id = data.nsxt_policy_project.dev.id + } + display_name = "nat" + ip_block = data.nsxt_policy_project.dev.external_ipv4_blocks[0] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `display_name` - (Required) Display name of the resource. +* `description` - (Optional) Description of the resource. +* `tag` - (Optional) A list of scope + tag pairs to associate with this resource. +* `nsx_id` - (Optional) The NSX ID of this resource. If set, this ID will be used to create the resource. +* `allocation_size` - (Optional) The system will allocate IP addresses from unused IP addresses based on allocation size. Currently only size `1` is supported. +* `allocation_ips` - (Optional) If specified, IPs have to be within range of respective IP blocks. +* `ip_block` - (Optional) Policy path for IP Block for the allocation. + + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `id` - ID of the resource. +* `revision` - Indicates current revision number of the object as seen by NSX-T API server. This attribute can be useful for debugging. +* `path` - The NSX path of the policy resource. + +## Importing + +An existing object can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_policy_ip_address_allocation.test PATH +``` + +The above command imports IP Address Allocation named `test` with the policy path `PATH`. diff --git a/website/docs/r/vpc.html.markdown b/website/docs/r/vpc.html.markdown new file mode 100644 index 000000000..2dbf6ca83 --- /dev/null +++ b/website/docs/r/vpc.html.markdown @@ -0,0 +1,69 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: nsxt_vpc" +description: A resource to configure a VPC under Project. +--- + +# nsxt_vpc + +This resource provides a method for the management of a VPC. + +This resource is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +resource "nsxt_vpc" "test" { + context { + project_id = "test_proj" + } + + display_name = "test" + description = "Terraform provisioned VPC" + + private_ips = ["192.168.55.0/24"] + short_id = "vpc-tf" + + load_balancer_vpc_endpoint { + enabled = false + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `display_name` - (Required) Display name of the resource. +* `description` - (Optional) Description of the resource. +* `tag` - (Optional) A list of scope + tag pairs to associate with this resource. +* `nsx_id` - (Optional) The NSX ID of this resource. If set, this ID will be used to create the resource. +* `private_ips` - (Optional) IP CIDRs to manage private IPv4 subnets. +* `vpc_service_profile` - (Optional) The path of the configuration profile of the VPC services. +* `load_balancer_vpc_endpoint` - (Optional) Configuration for Load Balancer Endpoint + * `enabled` - (Optional) Flag to indicate whether support for load balancing is needed. Setting this flag to `true` causes allocation of private IPs from the private block associated with this VPC for the use of the load balancer. +* `ip_address_type` - (Optional) This defines the IP address type that will be allocated for subnets. +* `short_id` - (Optional) Defaults to id if id is less than equal to 8 characters or defaults to random generated id if not set. Can not be updated once VPC is created. + + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `id` - ID of the resource. +* `revision` - Indicates current revision number of the object as seen by NSX-T API server. This attribute can be useful for debugging. +* `path` - The NSX path of the policy resource. +* `private_ipv4_blocks` - (Optional) Policy paths of automatically created private IPv4 blocks, based on `private_ips` specified for the VPC. + +## Importing + +An existing object can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_vpc.test PATH +``` + +The above command imports VOC named `test` with the NSX path `PATH`. diff --git a/website/docs/r/vpc_attachment.html.markdown b/website/docs/r/vpc_attachment.html.markdown new file mode 100644 index 000000000..d34c7ac24 --- /dev/null +++ b/website/docs/r/vpc_attachment.html.markdown @@ -0,0 +1,68 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: nsxt_vpc_attachment" +description: A resource to configure VPC attachment. +--- + +# nsxt_vpc_attachment + +This resource provides a method for the management of VPC Attachment. + +This resource is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +data "nsxt_vpc" "test" { + context { + project_id = data.nsxt_policy_project.dev.id + } + display_name = "test" +} + +data "nsxt_vpc_connectivity_profile" "test" { + context { + project_id = data.nsxt_policy_project.dev.id + } + display_name = "test" +} + +resource "nsxt_vpc_attachment" "test" { + display_name = "test" + description = "terraform provisioned vpc attachment" + parent_path = data.nsxt_vpc.test.path + vpc_connectivity_profile = data.nsxt_vpc_connectivity_profile.test.path +} +``` + +## Argument Reference + +The following arguments are supported: + +* `display_name` - (Required) Display name of the resource. +* `description` - (Optional) Description of the resource. +* `tag` - (Optional) A list of scope + tag pairs to associate with this resource. +* `nsx_id` - (Optional) The NSX ID of this resource. If set, this ID will be used to create the resource. +* `parent_path` - (Required) Policy path of parent VPC object. +* `vpc_connectivity_profile` - (Required) Path of VPC connectivity profile to attach to the VPC. + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `id` - ID of the resource. +* `revision` - Indicates current revision number of the object as seen by NSX-T API server. This attribute can be useful for debugging. +* `path` - The NSX path of the policy resource. + +## Importing + +An existing object can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_vpc_attachment.test PATH +``` + +The above command imports VPC Attachment named `test` with the policy path `PATH`. diff --git a/website/docs/r/vpc_connectivity_profile.html.markdown b/website/docs/r/vpc_connectivity_profile.html.markdown new file mode 100644 index 000000000..91bc6069d --- /dev/null +++ b/website/docs/r/vpc_connectivity_profile.html.markdown @@ -0,0 +1,73 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: nsxt_vpc_connectivity_profile" +description: A resource to configure Connectivity Profile for VPC. +--- + +# nsxt_vpc_connectivity_profile + +This resource provides a method for the management of a Connectivity Profile for VPC. + +This resource is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +resource "nsxt_vpc_connectivity_profile" "test" { + context { + project_id = "dev" + } + + display_name = "test" + description = "Terraform provisioned profile" + + transit_gateway_path = nsxt_policy_transit_gateway.gw1.path + service_gateway { + nat_config { + enable_default_snat = true + } + enable = true + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `display_name` - (Required) Display name of the resource. +* `description` - (Optional) Description of the resource. +* `tag` - (Optional) A list of scope + tag pairs to associate with this resource. +* `nsx_id` - (Optional) The NSX ID of this resource. If set, this ID will be used to create the resource. +* `transit_gateway_path` - (Required) Transit Gateway path. +* `private_tgw_ip_blocks` - (Optional) Policy path of Private IP block +* `external_ip_blocks` - (Optional) Policy path of External IP block +* `service_gateway` - (Optional) Service Gateway configuration + * `nat_config` - (Optional) NAT configuration + * `enable_default_snat` - (Optional) Auto configured SNAT for private subnet. + * `qos_config` - (Optional) None + * `ingress_qos_profile_path` - (Optional) Policy path to gateway QoS profile in ingress direction. + * `egress_qos_profile_path` - (Optional) Policy path to gateway QoS profile in egress direction. + * `enable` - (Optional) Status of the VPC attachment SR. + * `edge_cluster_paths` - (Optional) List of edge cluster paths for VPC attachment SR realization. If edge cluster is not specified transit gateway's edge cluster will be used. + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `id` - ID of the resource. +* `revision` - Indicates current revision number of the object as seen by NSX-T API server. This attribute can be useful for debugging. +* `path` - The NSX path of the policy resource. + +## Importing + +An existing object can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_vpc_connectivity_profile.test PATH +``` + +The above command imports VpcConnectivityProfile named `test` with the NSX policy path `PATH`. diff --git a/website/docs/r/vpc_dhcp_v4_static_binding_config.html.markdown b/website/docs/r/vpc_dhcp_v4_static_binding_config.html.markdown new file mode 100644 index 000000000..19cbc5206 --- /dev/null +++ b/website/docs/r/vpc_dhcp_v4_static_binding_config.html.markdown @@ -0,0 +1,121 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: nsxt_vpc_dhcp_v4_static_binding" +description: A resource to configure a DHCP IPv4 Static Binding. +--- + +# nsxt_vpc_dhcp_v4_static_binding + +This resource provides a method for the management of a DhcpV4StaticBindingConfig. +This resource is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +data "nsxt_policy_project" "demoproj" { + display_name = "demoproj" +} + +data "nsxt_vpc" "demovpc" { + context { + project_id = data.nsxt_policy_project.demoproj.id + } + display_name = "vpc1" +} + +data "nsxt_vpc_subnet" "test" { + context { + project_id = data.nsxt_policy_project.demoproj.id + vpc_id = data.nsxt_vpc.demovpc.id + } + display_name = "subnet1" +} + +resource "nsxt_vpc_dhcp_v4_static_binding" "test" { + parent_path = data.nsxt_vpc_subnet.test.path + display_name = "test" + description = "Terraform provisioned DhcpV4StaticBindingConfig" + gateway_address = "192.168.240.1" + host_name = "host.example.org" + mac_address = "10:0e:00:11:22:02" + lease_time = 162 + ip_address = "192.168.240.41" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `parent_path` - (Required) Policy path of parent VpcSubnet object, typically reference to `path` in `nsxt_vpc_subnet` data source or resource. +* `display_name` - (Required) Display name of the resource. +* `description` - (Optional) Description of the resource. +* `tag` - (Optional) A list of scope + tag pairs to associate with this resource. +* `nsx_id` - (Optional) The NSX ID of this resource. If set, this ID will be used to create the resource. +* `gateway_address` - (Optional) When not specified, gateway address is auto-assigned from segment configuration. +* `host_name` - (Optional) Hostname to assign to the host. +* `mac_address` - (Optional) MAC address of the host. +* `lease_time` - (Optional) DHCP lease time in seconds. +* `ip_address` - (Optional) IP assigned to host. The IP address must belong to the subnet, if any, configured on Segment. +* `options` - (Optional) DHCPv4 options block + * `option121` - (Optional) Specification for DHCP option 121 + * `static_route` - (Required) Classless static route of DHCP option 121. + * `next_hop` - (Required) IP address of next hop of the route. + * `network` - (Required) Destination network in CIDR format. + * `other` - (Optional) To define DHCP options other than option 121 in generic format. + * `code` - (Required) Code of the dhcp option. + * `values` - (Optional) Value of the option. + +Please note, only the following options can be defined in generic +format. Those other options will be accepted without validation +but will not take effect. +-------------------------- +Code Name +-------------------------- + 2 Time Offset + 6 Domain Name Server + 13 Boot File Size + 19 Forward On/Off + 26 MTU Interface + 28 Broadcast Address + 35 ARP Timeout + 40 NIS Domain + 41 NIS Servers + 42 NTP Servers + 44 NETBIOS Name Srv + 45 NETBIOS Dist Srv + 46 NETBIOS Node Type + 47 NETBIOS Scope + 58 Renewal Time + 59 Rebinding Time + 64 NIS+-Domain-Name + 65 NIS+-Server-Addr + 66 TFTP Server-Name (used by PXE) + 67 Bootfile-Name (used by PXE) + 117 Name Service Search + 119 Domain Search + 150 TFTP server address (used by PXE) + 209 PXE Configuration File + 210 PXE Path Prefix + 211 PXE Reboot Time + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `id` - ID of the resource. +* `revision` - Indicates current revision number of the object as seen by NSX-T API server. This attribute can be useful for debugging. +* `path` - The NSX path of the policy resource. + +## Importing + +An existing object can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_vpc_dhcp_v4_static_binding.test PATH +``` + +The above command imports DhcpV4StaticBindingConfig named `test` with the policy path `PATH`. diff --git a/website/docs/r/vpc_external_address.html.markdown b/website/docs/r/vpc_external_address.html.markdown new file mode 100644 index 000000000..5763d4a16 --- /dev/null +++ b/website/docs/r/vpc_external_address.html.markdown @@ -0,0 +1,67 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: nsxt_vpc_external_address" +description: A resource to configure a VPC External Address Binding on Port. +--- + +# nsxt_vpc_external_address + +This resource provides a method to configure External Address Binding on Subnet Port under VPC. +Only single resource should be configured for any given port. + +This resource is applicable to NSX Policy Manager. + +```hcl +resource "nsxt_vpc_ip_address_allocation" "test" { + context { + project_id = "Dev_project" + vpc_id = "dev_vpc" + } + display_name = "external" + allocation_size = 1 +} + +data "nsxt_policy_vm" "vm1" { + context { + project_id = "Dev_project" + vpc_id = "dev_vpc" + } + display_name = "myvm-1" +} + +data "nsxt_vpc_subnet_port" "test" { + subnet_path = nsxt_vpc_subnet.test.path + vm_id = data.nsxt_policy_vm.vm1.instance_id +} + +resource "nsxt_vpc_external_address" "test" { + parent_path = data.nsxt_vpc_subnet_port.test.path + allocated_external_ip_path = nsxt_vpc_ip_address_allocation.test.path +} +``` + +## Argument Reference + +The following arguments are supported: + +* `parent_path` - (Required) Policy path for the Subnet Port. +* `allocated_external_ip_path` - (Required) Policy path for extrenal IP address allocation object. + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `external_ip_address` - The actual IP address that was allocated with `allocated_external_ip_path`. + +## Importing + +An existing VPC External Address Binding can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_vpc_external_address.external_address1 PATH +``` + +The above command imports the VPC External Address Binding named `external_address` with the NSX Policy path of the parent port `PATH`. diff --git a/website/docs/r/vpc_gateway_policy.html.markdown b/website/docs/r/vpc_gateway_policy.html.markdown index 652687b23..893da14d0 100644 --- a/website/docs/r/vpc_gateway_policy.html.markdown +++ b/website/docs/r/vpc_gateway_policy.html.markdown @@ -13,14 +13,12 @@ This resource is applicable to NSX Policy Manager. ## Example Usage -## Example Usage - ```hcl data "nsxt_policy_project" "demoproj" { display_name = "demoproj" } -data "nsxt_policy_vpc" "demovpc" { +data "nsxt_vpc" "demovpc" { context { project_id = data.nsxt_policy_project.demoproj.id } @@ -30,7 +28,7 @@ data "nsxt_policy_vpc" "demovpc" { resource "nsxt_vpc_gateway_policy" "test" { context { project_id = data.nsxt_policy_project.demoproj.id - vpc_id = data.nsxt_policy_vpc.demovpc.id + vpc_id = data.nsxt_vpc.demovpc.id } display_name = "tf-gw-policy" description = "Terraform provisioned Gateway Policy" diff --git a/website/docs/r/vpc_group.html.markdown b/website/docs/r/vpc_group.html.markdown index cada321bb..fc0526910 100644 --- a/website/docs/r/vpc_group.html.markdown +++ b/website/docs/r/vpc_group.html.markdown @@ -12,12 +12,14 @@ used as sources and destinations, as well as in the Applied To field, in firewal This resource is applicable to NSX Policy Manager. +## Example Usage + ```hcl data "nsxt_policy_project" "demoproj" { display_name = "demoproj" } -data "nsxt_policy_vpc" "demovpc" { +data "nsxt_vpc" "demovpc" { context { project_id = data.nsxt_policy_project.demoproj.id } @@ -27,7 +29,7 @@ data "nsxt_policy_vpc" "demovpc" { resource "nsxt_vpc_group" "group1" { context { project_id = data.nsxt_policy_project.demoproj.id - vpc_id = data.nsxt_policy_vpc.demovpc.id + vpc_id = data.nsxt_vpc.demovpc.id } display_name = "tf-group1" @@ -67,13 +69,13 @@ The following arguments are supported: * `macaddress_expression` - (Optional) An expression block to specify individual MAC Addresses for this Group. * `mac_addresses` - (Required) List of MAC addresses. * `path_expression` - (Optional) An expression block to specify direct group members by policy path. - * `member_paths` - (Required) List of policy paths for direct members for this Group (such as Segments, Segment ports, Groups etc). + * `member_paths` - (Required) List of policy paths for direct members for this Group (such as Subnets, Subnet ports, Groups etc). * `external_id_expression` - (Optional) An expression block to specify external IDs for the specified member type for this Group. * `member_type` - (Optional) External ID member type. Must be one of: `VirtualMachine`, `VirtualNetworkInterface`, `CloudNativeServiceInstance`, or `PhysicalServer`. Defaults to `VirtualMachine`. * `external_ids` - (Required) List of external IDs for the specified member type. * `condition` (Optional) A repeatable condition block to select this Group's members. When multiple `condition` blocks are used in a single `criteria` they form a nested expression that's implicitly ANDed together and each nested condition must used the same `member_type`. - * `key` (Required) Specifies the attribute to query. Must be one of: `Tag`, `ComputerName`, `OSName`, `Name`, `NodeType`, `GroupType`, `ALL`, `IPAddress`, `PodCidr`. Please note that certain keys are only applicable to certain member types. - * `member_type` (Required) Specifies the type of resource to query. Must be one of: `IPSet`, `LogicalPort`, `LogicalSwitch`, `Segment`, `SegmentPort`, `VirtualMachine`, `Group`, `DVPG`, `DVPort`, `IPAddress`, `TransportNode`, `Pod`. `Service`, `Namespace`, `KubernetesCluster`, `KubernetesNamespace`, `KubernetesIngress`, `KubernetesService`, `KubernetesNode`, `AntreaEgress`, `AntreaIPPool`. Not that certain member types are only applicable to certain environments. + * `key` (Required) Specifies the attribute to query. Must be one of: `Tag`, `ComputerName`, `OSName`, `Name`. Please note that certain keys are only applicable to certain member types. + * `member_type` (Required) Specifies the type of resource to query. Must be one of: `Subnet`, `SubnetPort`, `VirtualMachine` * `operator` (Required) Specifies the query operator to use. Must be one of: `CONTAINS`, `ENDSWITH`, `EQUALS`, `NOTEQUALS`, `STARTSWITH`, `IN`, `NOTIN`, `MATCHES`. Not that certain operators are only applicable to certain keys/member types.:w * `value` (Required) User specified string value to use in the query. For `Tag` criteria, use 'scope|value' notation if you wish to specify scope in criteria. * `conjunction` (Required for multiple `criteria`) When specifying multiple `criteria`, a conjunction is used to specify if the criteria should selected using `AND` or `OR`. diff --git a/website/docs/r/vpc_ip_address_allocation.html.markdown b/website/docs/r/vpc_ip_address_allocation.html.markdown new file mode 100644 index 000000000..2dd23397e --- /dev/null +++ b/website/docs/r/vpc_ip_address_allocation.html.markdown @@ -0,0 +1,60 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: nsxt_vpc_ip_address_allocation" +description: A resource to configure IP Address Allocation from IP block associated with VPC. +--- + +# nsxt_vpc_ip_address_allocation + +This resource provides a method for allocating IP Address from IP block associated with VPC. + +This resource is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +resource "nsxt_vpc_ip_address_allocation" "nat" { + context { + project_id = data.nsxt_policy_project.dev.id + vpc_id = nsxt_vpc.test.id + } + display_name = "nat" + allocation_size = 1 +} +``` + +## Argument Reference + +The following arguments are supported: + +* `display_name` - (Required) Display name of the resource. +* `description` - (Optional) Description of the resource. +* `tag` - (Optional) A list of scope + tag pairs to associate with this resource. +* `nsx_id` - (Optional) The NSX ID of this resource. If set, this ID will be used to create the resource. +* `allocation_size` - (Optional) The system will allocate IP addresses from unused IP addresses based on allocation size. +* `allocation_ips` - (Optional) If specified, IPs have to be within range of respectiveIP blocks. +* `ip_address_block_visibility` - (Optional) Represents visibility of IP address block. This field is not applicable if IPAddressType at VPC is IPV6. Valid values are `EXTERNAL`, `PRIVATE`, `PRIVATE_TGW`, default is `EXTERNAL`. +* `ip_address_type` - (Optional) This defines the type of IP address block that will be used to allocate IP. This field is applicable only if IP address type at VPC is `DUAL`. Valid values are `IPV4` and `IPV6`, default is `IPV4`. +* `ip_block` - (Optional) Policy path for IP Block for the allocation. + + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `id` - ID of the resource. +* `revision` - Indicates current revision number of the object as seen by NSX-T API server. This attribute can be useful for debugging. +* `path` - The NSX path of the policy resource. + +## Importing + +An existing object can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_policy_ip_address_allocation.test PATH +``` + +The above command imports VpcIpAddressAllocation named `test` with the policy path `PATH`. diff --git a/website/docs/r/vpc_nat_rule.html.markdown b/website/docs/r/vpc_nat_rule.html.markdown new file mode 100644 index 000000000..ca1c2845b --- /dev/null +++ b/website/docs/r/vpc_nat_rule.html.markdown @@ -0,0 +1,84 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: nsxt_vpc_nat_rule" +description: A resource to configure a NAT rule under VPC. +--- + +# nsxt_vpc_nat_rule + +This resource provides a method for the management of a VPC NAT Rule. + +This resource is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +resource "nsxt_vpc_ip_address_allocation" "nat" { + context { + project_id = data.nsxt_policy_project.dev.id + vpc_id = nsxt_vpc.vpc1.id + } + display_name = "for-nat" + allocation_size = 1 +} + +data "nsxt_vpc_nat" "test" { + context { + project_id = data.nsxt_policy_project.dev.id + vpc_id = nsxt_vpc.vpc1.id + } + nat_type = "USER" +} + +resource "nsxt_vpc_nat_rule" "test" { + display_name = "test" + description = "terraform provisioned nat rule for vpc" + parent_path = data.nsxt_vpc_nat.test.path + destination_network = nsxt_vpc_ip_address_allocation.nat.allocation_ips + action = "DNAT" + source_network = "10.205.1.13" + translated_network = "2.2.2.13" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `display_name` - (Required) Display name of the resource. +* `description` - (Optional) Description of the resource. +* `tag` - (Optional) A list of scope + tag pairs to associate with this resource. +* `nsx_id` - (Optional) The NSX ID of this resource. If set, this ID will be used to create the resource. +* `parent_path` - (Required) Policy path of parent NAT object, typically reference to `path` in `nsxt_vpc_nat` data source. +* `translated_network` - (Optional) For `SNAT`, `DNAT` and `REFLEXIVE` rules, this ia a required field, which +represents the translated network address. In case of `SNAT` and `REFLEXIVE` rule, translated network address should be single IPv4 address allocated from External Block associated with VPC. +* `logging` - (Optional) Boolean flag to indicate whether logging is enabled. The default is `false`. +* `destination_network` - (Optional) For `DNAT` rules, this is a required field, and represents the destination network for the incoming packets. For other type of rules, it may contain destination network of outgoing packets. In case of `DNAT` rule, destination network address should be IPv4 address allocated from External Block associated with VPC. +* `action` - (Required) NAT action, one of `SNAT` (translates a source IP address into an outbound packet so that +the packet appears to originate from a different network), `DNAT` (translates the destination IP address of inbound packets so that packets are delivered to a target address into another network), and `REFLEXIVE` (one-to-one mapping of source and destination IP addresses). +* `firewall_match` - (Optional) Indicates how the firewall matches the address after NATing if firewall +stage is not skipped, one of `MATCH_EXTERNAL_ADDRESS`, `MATCH_INTERNAL_ADDRESS` or `BYPASS`. Default is `MATCH_INTERNAL_ADDRESS`. +* `source_network` - (Optional) Source network. For `SNAT` and `REFLEXIVE` rules, this is a required field. For `DNAT` rules, it may contain source network for incoming packets. +* `enabled` - (Optional) Flag for enabling the NAT rule, default is `true`. +* `sequence_number` - (Optional) The sequence_number decides the rule_priority of a NAT rule. + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `id` - ID of the resource. +* `revision` - Indicates current revision number of the object as seen by NSX-T API server. This attribute can be useful for debugging. +* `path` - The NSX path of the policy resource. + +## Importing + +An existing object can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_vpc_nat_rule.test PATH +``` + +The above command imports PolicyVpcNatRule named `test` with the policy path `PATH`. diff --git a/website/docs/r/vpc_security_policy.html.markdown b/website/docs/r/vpc_security_policy.html.markdown index b76e48e77..815a8303c 100644 --- a/website/docs/r/vpc_security_policy.html.markdown +++ b/website/docs/r/vpc_security_policy.html.markdown @@ -18,7 +18,7 @@ data "nsxt_policy_project" "demoproj" { display_name = "demoproj" } -data "nsxt_policy_vpc" "demovpc" { +data "nsxt_vpc" "demovpc" { context { project_id = data.nsxt_policy_project.demoproj.id } @@ -28,7 +28,7 @@ data "nsxt_policy_vpc" "demovpc" { resource "nsxt_vpc_security_policy" "policy1" { context { project_id = data.nsxt_policy_project.demoproj.id - vpc_id = data.nsxt_policy_vpc.demovpc.id + vpc_id = data.nsxt_vpc.demovpc.id } display_name = "policy1" description = "Terraform provisioned Security Policy" diff --git a/website/docs/r/vpc_service_profile.html.markdown b/website/docs/r/vpc_service_profile.html.markdown new file mode 100644 index 000000000..9d4e75f1b --- /dev/null +++ b/website/docs/r/vpc_service_profile.html.markdown @@ -0,0 +1,98 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: nsxt_vpc_service_profile" +description: A resource to configure a VPC Service Profile. +--- + +# nsxt_vpc_service_profile + +This resource provides a method for the management of a VPC Service Profile. + +This resource is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +data "nsxt_policy_project" "demoproj" { + display_name = "demoproj" +} + +resource "nsxt_vpc_service_profile" "vpc1_service_profile" { + context { + project_id = data.nsxt_policy_project.demoproj.id + } + + display_name = "vpc1" + description = "Terraform provisioned Vpc Service Profile" + + mac_discovery_profile = nsxt_policy_mac_discovery_profile.for_vpc1.path + spoof_guard_profile = nsxt_policy_spoof_guard_profile.for_vpc1.path + ip_discovery_profile = nsxt_policy_ip_discovery_profile.for_vpc1.path + qos_profile = nsxt_policy_qos_profile.for_vpc1.path + + dhcp_config { + dhcp_server_config { + ntp_servers = ["20.2.60.5"] + lease_time = 50840 + + dns_client_config { + dns_server_ips = ["10.204.2.20"] + } + + advanced_config { + is_distributed_dhcp = false + } + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `display_name` - (Required) Display name of the resource. +* `description` - (Optional) Description of the resource. +* `tag` - (Optional) A list of scope + tag pairs to associate with this resource. +* `nsx_id` - (Optional) The NSX ID of this resource. If set, this ID will be used to create the resource. +* `context` - (Required) The context which the object belongs to + * `project_id` - (Required) The ID of the project which the object belongs to +* `mac_discovery_profile` - (Optional) Policy path for Mac Discovery Profile +* `spoof_guard_profile` - (Optional) Policy path for Spoof Guard Profile +* `ip_discovery_profile` - (Optional) Policy path for IP Discovery Profile +* `security_profile` - (Optional) Policy path for Security Profile +* `qos_profile` - (Optional) Policy path for QoS profile +* `dhcp_config` - (Required) DHCP configuration for this profile + * `dhcp_server_config` - (Optionl) DHCP server configuration for this profile + * `ntp_servers` - (Optional) List of NTP servers + * `dns_client_config` - (Optional) DNS Client configuration + * `dns_server_ips` - (Optional) List of IP addresses of the DNS servers which need to be configured on the workload VMs + * `lease_time` - (Optional) DHCP lease time in seconds. + * `advanced_config` - (Optional) VPC DHCP advanced configuration + * `is_distributed_dhcp` - DHCP server's IP allocation model based on workloads subnet port id. Can be `false` only when Edge cluster is available, in + which case edge cluster in VPC connectivity profile must be configured. This is the traditional DHCP server that dynamically allocates IP per VM's MAC. + If value is `true`, edge cluster will not be required. This is a DHCP server that dynamically assigns IP per VM port. + * `dhcp_relay_config` - (Optional) DHCP Relay configuration + * `server_addresses` - (Optional) List of DHCP server IP addresses for DHCP relay configuration. Both IPv4 and IPv6 addresses are supported. + + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `id` - ID of the resource. +* `revision` - Indicates current revision number of the object as seen by NSX-T API server. This attribute can be useful for debugging. +* `path` - The NSX path of the policy resource. + +## Importing + +An existing object can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_vpc_service_profile.test PATH +``` + +The above command imports VPC Service Profile named `test` with the NSX policy path `PATH`. diff --git a/website/docs/r/vpc_static_routes.html.markdown b/website/docs/r/vpc_static_routes.html.markdown new file mode 100644 index 000000000..df61c14ca --- /dev/null +++ b/website/docs/r/vpc_static_routes.html.markdown @@ -0,0 +1,66 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: nsxt_vpc_static_route" +description: A resource to configure a Static Routes under VPC. +--- + +# nsxt_vpc_static_route + +This resource provides a method for the management of VPC Static Routes. + +This resource is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +resource "nsxt_vpc_static_route" "test" { + context { + project_id = data.nsxt_policy_project.demoproj.id + vpc_id = data.nsxt_vpc.demovpc.id + } + + display_name = "test" + description = "Terraform provisioned StaticRoutes" + + network = "3.3.3.0/24" + + next_hop { + ip_address = "10.230.3.1" + admin_distance = 4 + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `display_name` - (Required) Display name of the resource. +* `description` - (Optional) Description of the resource. +* `tag` - (Optional) A list of scope + tag pairs to associate with this resource. +* `nsx_id` - (Optional) The NSX ID of this resource. If set, this ID will be used to create the resource. +* `network` - (Required) Specify network address in CIDR format. Optionally this can be allocated IP from one of the external blocks associated with VPC. Only /32 CIDR is allowed in case IP overlaps with external blocks. +* `next_hop` - (Required) Specify next hop routes for network. + * `ip_address` - (Optional) Next hop gateway IP address + * `admin_distance` - (Optional) Cost associated with next hop route. Default is 1. + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `id` - ID of the resource. +* `revision` - Indicates current revision number of the object as seen by NSX-T API server. This attribute can be useful for debugging. +* `path` - The NSX path of the policy resource. + +## Importing + +An existing object can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_vpc_static_route.test +``` + +The above command imports Static Route named `test` with the NSX policy VPC path `PATH`. diff --git a/website/docs/r/vpc_subnet.html.markdown b/website/docs/r/vpc_subnet.html.markdown new file mode 100644 index 000000000..14cdc764e --- /dev/null +++ b/website/docs/r/vpc_subnet.html.markdown @@ -0,0 +1,105 @@ +--- +subcategory: "VPC" +layout: "nsxt" +page_title: "NSXT: nsxt_vpc_subnet" +description: A resource to configure a VpcSubnet. +--- + +# nsxt_vpc_subnet + +This resource provides a method for the management of a Vpc Subnet. + +This resource is applicable to NSX Policy Manager. + +## Example Usage + +```hcl +data "nsxt_policy_project" "demoproj" { + display_name = "demoproj" +} + +data "nsxt_vpc" "demovpc" { + context { + project_id = data.nsxt_policy_project.demoproj.id + } + display_name = "vpc1" +} + + +resource "nsxt_vpc_subnet" "test" { + context { + project_id = data.nsxt_policy_project.demoproj.id + vpc_id = data.nsxt_vpc.demovpc.id + } + + display_name = "test-subnet" + description = "Test VPC subnet" + ipv4_subnet_size = 32 + ip_addresses = ["192.168.240.0/24"] + access_mode = "Isolated" +} +``` + +~> **NOTE:** In some cases, subnet creation will depend on VPC attachment. If both resources are being created within same apply, + explicit `depends_on` meta argument needs to be added to enforce this dependency. + +## Argument Reference + +The following arguments are supported: + +* `display_name` - (Required) Display name of the resource. +* `description` - (Optional) Description of the resource. +* `tag` - (Optional) A list of scope + tag pairs to associate with this resource. +* `nsx_id` - (Optional) The NSX ID of this resource. If set, this ID will be used to create the resource. +* `ipv4_subnet_size` - (Optional) If IP Addresses are not provided, this field will be used to carve out the ips + from respective ip block defined in the parent VPC. The default is 64. +* `ip_addresses` - (Optional) If not provided, Ip assignment will be done based on VPC CIDRs +* `access_mode` - (Optional) Subnet access mode, one of `Private`, `Public`, `Isolated` or `Private_TGW`. Default is `Private` +* `advanced_config` - (Optional) Advanced Configuration for the Subnet + * `gateway_addresses` - (Optional) List of Gateway IP Addresses per address family, in CIDR format + * `connectivity_state` - (Optional) Connectivity state for the subnet, one of `CONNECTED`, `DISCONNECTED` + * `dhcp_server_addresses` - (Optional) List of DHCP server addresses per address family, in CIDR format + * `static_ip_allocation` - (Optional) Static IP allocation configuration + * `enabled` - (Optional) Enable ip and mac addresse allocation for VPC Subnet ports from static ip pool. To + enable this, + dhcp pool shall be empty and static ip pool shall own all available ip addresses. + * `extra_configs` - (Optional) List of vendor specific configuration key/value pairs + * `config_pair` - (Required) + * `key` - (Required) key for vendor-specific configuration + * `value` - (Required) value for vendor-specific configuration +* `dhcp_config` - (Optional) DHCP configuration block + * `mode` - (Optional) The operational mode of DHCP within the subnet, can be one of `DHCP_SERVER`, `DHCP_RELAY`, `DHCP_DEACTIVATED`. + Default is `DHCP_DEACTIVATED` + * `dhcp_server_additional_config` - (Optional) Additional DHCP server config + * `options` - (Optional) DHCPv4 options block + * `option121` - (Optional) Specification for DHCP option 121 + * `static_route` - (Optional) Static route + * `network` - (Optional) Destination network in CIDR format + * `next_hop` - (Optional) IP Address for next hop of the route + * `other` - (Optional) DHCP option in generic format + * `code` - (Optional) Code of DHCP option + * `values` - (Optional) List of values in string format + * `reserved_ip_ranges` - (Optional) Specifies IP ranges that are reserved and excluded from being assigned by the DHCP server to clients. + This is a list of IP ranges or IP addresses. +* `ip_blocks` - (Optional) List of IP block path for subnet IP allocation + +## Attributes Reference + +In addition to arguments listed above, the following attributes are exported: + +* `id` - ID of the resource. +* `revision` - Indicates current revision number of the object as seen by NSX-T API server. This attribute can be useful + for debugging. +* `path` - The NSX path of the policy resource. + +## Importing + +An existing object can be [imported][docs-import] into this resource, via the following command: + +[docs-import]: https://www.terraform.io/cli/import + +``` +terraform import nsxt_vpc_subnet.test PATH +``` + +The above command imports VpcSubnet named `test` with the policy path `PATH`.