From 0fed7a9c30b62ccfb83a5af057c129869ba0fa5e Mon Sep 17 00:00:00 2001 From: Pierre Lafievre Date: Thu, 2 Feb 2023 11:49:05 +0100 Subject: [PATCH 1/7] add minimal framework implementation and serve providers with mux --- go.mod | 16 +- go.sum | 112 ++------ main.go | 51 +++- opennebula/framework_migration/mutexkv.go | 89 ++++++ opennebula/framework_migration/provider.go | 298 +++++++++++++++++++++ 5 files changed, 457 insertions(+), 109 deletions(-) create mode 100644 opennebula/framework_migration/mutexkv.go create mode 100644 opennebula/framework_migration/provider.go diff --git a/go.mod b/go.mod index f195a95ca..311aeda2c 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,9 @@ require ( github.com/OpenNebula/one/src/oca/go/src/goca v0.0.0-20221028091935-0c75c6db2132 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.6.0 + github.com/hashicorp/terraform-plugin-framework v1.1.1 + github.com/hashicorp/terraform-plugin-go v0.14.3 + github.com/hashicorp/terraform-plugin-mux v0.8.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.1 ) @@ -22,15 +25,14 @@ require ( github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect github.com/hashicorp/go-hclog v1.2.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.4.6 // indirect + github.com/hashicorp/go-plugin v1.4.8 // indirect github.com/hashicorp/hc-install v0.4.0 // indirect github.com/hashicorp/hcl/v2 v2.15.0 // indirect github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.17.3 // indirect github.com/hashicorp/terraform-json v0.14.0 // indirect - github.com/hashicorp/terraform-plugin-go v0.14.1 // indirect github.com/hashicorp/terraform-plugin-log v0.7.0 // indirect - github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c // indirect + github.com/hashicorp/terraform-registry-address v0.1.0 // indirect github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect github.com/kolo/xmlrpc v0.0.0-20190909154602-56d5ec7c422e // indirect @@ -47,11 +49,11 @@ require ( github.com/vmihailenco/tagparser v0.1.1 // indirect github.com/zclconf/go-cty v1.12.1 // indirect golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167 // indirect - golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect - golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect + golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect + golang.org/x/text v0.4.0 // indirect google.golang.org/appengine v1.6.6 // indirect google.golang.org/genproto v0.0.0-20200711021454-869866162049 // indirect - google.golang.org/grpc v1.50.1 // indirect + google.golang.org/grpc v1.51.0 // indirect google.golang.org/protobuf v1.28.1 // indirect ) diff --git a/go.sum b/go.sum index 60b8ed10d..b3ca4094d 100644 --- a/go.sum +++ b/go.sum @@ -10,47 +10,28 @@ github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C6 github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= -github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= -github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 h1:MzVXffFUye+ZcSR6opIgz9Co7WcDx6ZcY+RjfFHoA0I= -github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= -github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0= github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= @@ -61,14 +42,12 @@ github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6 github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= -github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -76,8 +55,6 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= @@ -85,14 +62,10 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= @@ -103,13 +76,12 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI= github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= -github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.2.1 h1:YQsLlGDJgwhXFpucSPyVbCBviQtjlHv3jLTlp8YmtEw= github.com/hashicorp/go-hclog v1.2.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.4.6 h1:MDV3UrKQBM3du3G7MApDGvOsMYy3JQJ4exhSoKBAeVA= -github.com/hashicorp/go-plugin v1.4.6/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= +github.com/hashicorp/go-plugin v1.4.8 h1:CHGwpxYDOttQOY7HOWgETU9dyVjOXzniXDqJcYJE1zM= +github.com/hashicorp/go-plugin v1.4.8/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -127,17 +99,20 @@ github.com/hashicorp/terraform-exec v0.17.3 h1:MX14Kvnka/oWGmIkyuyvL6POx25ZmKrjl github.com/hashicorp/terraform-exec v0.17.3/go.mod h1:+NELG0EqQekJzhvikkeQsOAZpsw0cv/03rbeQJqscAI= github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e17dKDpqV7s= github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM= -github.com/hashicorp/terraform-plugin-go v0.14.1 h1:cwZzPYla82XwAqpLhSzdVsOMU+6H29tczAwrB0z9Zek= -github.com/hashicorp/terraform-plugin-go v0.14.1/go.mod h1:Bc/K6K26BQ2FHqIELPbpKtt2CzzbQou+0UQF3/0NsCQ= +github.com/hashicorp/terraform-plugin-framework v1.1.1 h1:PbnEKHsIU8KTTzoztHQGgjZUWx7Kk8uGtpGMMc1p+oI= +github.com/hashicorp/terraform-plugin-framework v1.1.1/go.mod h1:DyZPxQA+4OKK5ELxFIIcqggcszqdWWUpTLPHAhS/tkY= +github.com/hashicorp/terraform-plugin-go v0.14.3 h1:nlnJ1GXKdMwsC8g1Nh05tK2wsC3+3BL/DBBxFEki+j0= +github.com/hashicorp/terraform-plugin-go v0.14.3/go.mod h1:7ees7DMZ263q8wQ6E4RdIdR6nHHJtrdt4ogX5lPkX1A= github.com/hashicorp/terraform-plugin-log v0.7.0 h1:SDxJUyT8TwN4l5b5/VkiTIaQgY6R+Y2BQ0sRZftGKQs= github.com/hashicorp/terraform-plugin-log v0.7.0/go.mod h1:p4R1jWBXRTvL4odmEkFfDdhUjHf9zcs/BCoNHAc7IK4= +github.com/hashicorp/terraform-plugin-mux v0.8.0 h1:WCTP66mZ+iIaIrCNJnjPEYnVjawTshnDJu12BcXK1EI= +github.com/hashicorp/terraform-plugin-mux v0.8.0/go.mod h1:vdW0daEi8Kd4RFJmet5Ot+SIVB/B8SwQVJiYKQwdCy8= github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.1 h1:zHcMbxY0+rFO9gY99elV/XC/UnQVg7FhRCbj1i5b7vM= github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.1/go.mod h1:+tNlb0wkfdsDJ7JEiERLz4HzM19HyiuIoGzTsM7rPpw= -github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c h1:D8aRO6+mTqHfLsK/BC3j5OAoogv1WLRWzY1AaTo3rBg= -github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c/go.mod h1:Wn3Na71knbXc1G8Lh+yu/dQWWJeFQEpDeJMtWMtlmNI= +github.com/hashicorp/terraform-registry-address v0.1.0 h1:W6JkV9wbum+m516rCl5/NjKxCyTVaaUBbzYcMzBDO3U= +github.com/hashicorp/terraform-registry-address v0.1.0/go.mod h1:EnyO2jYO6j29DTHbJcm00E5nQTFeTtyZH3H5ycydQ5A= github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0= github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= @@ -146,7 +121,6 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= -github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kolo/xmlrpc v0.0.0-20190717152603-07c4ee3fd181/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ= @@ -162,14 +136,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= 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/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 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= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= @@ -177,10 +147,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-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 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 v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 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/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -188,8 +156,6 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758= -github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -197,20 +163,15 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= @@ -223,19 +184,14 @@ github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37w github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= -github.com/zclconf/go-cty v1.11.0/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= github.com/zclconf/go-cty v1.12.1 h1:PcupnljUm9EIvbgSHQnHhUr3fO6oFmkOrvs2BAFNXXY= github.com/zclconf/go-cty v1.12.1/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -245,8 +201,6 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -255,104 +209,75 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200711021454-869866162049 h1:YFTFpQhgvrLrmxtiIncJxFXeCyq84ixuKWVCaCAi9Oc= google.golang.org/genproto v0.0.0-20200711021454-869866162049/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= -google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0/go.mod h1:DNq5QpG7LJqD2AamLZ7zvKE0DEpVl2BSEVjFycAAjRY= +google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= +google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -364,7 +289,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 0fe1fd811..913c713c9 100644 --- a/main.go +++ b/main.go @@ -1,15 +1,50 @@ package main import ( - "github.com/OpenNebula/terraform-provider-opennebula/opennebula" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" + "context" + "flag" + "log" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-go/tfprotov5/tf5server" + "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" + + provider "github.com/OpenNebula/terraform-provider-opennebula/opennebula" + providerFramework "github.com/OpenNebula/terraform-provider-opennebula/opennebula/framework_migration" ) func main() { - plugin.Serve(&plugin.ServeOpts{ - ProviderFunc: func() *schema.Provider { - return opennebula.Provider() - }, - }) + + ctx := context.Background() + + var debug bool + + flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") + flag.Parse() + + providers := []func() tfprotov5.ProviderServer{ + providerserver.NewProtocol5(providerFramework.New()), + provider.Provider().GRPCProvider, + } + + muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...) + if err != nil { + log.Fatal(err) + } + + var serveOpts []tf5server.ServeOpt + + if debug { + serveOpts = append(serveOpts, tf5server.WithManagedDebug()) + } + + err = tf5server.Serve( + "registry.terraform.io/OpenNebula/opennebula", + muxServer.ProviderServer, + serveOpts..., + ) + if err != nil { + log.Fatal(err) + } } diff --git a/opennebula/framework_migration/mutexkv.go b/opennebula/framework_migration/mutexkv.go new file mode 100644 index 000000000..e900b037f --- /dev/null +++ b/opennebula/framework_migration/mutexkv.go @@ -0,0 +1,89 @@ +package opennebula + +import ( + "fmt" + "log" + "sync" +) + +type ResourceKey struct { + Type string + ID int +} + +func (k *ResourceKey) String() string { + return fmt.Sprintf("%s_%d", k.Type, k.ID) +} + +type SubResourceKey struct { + Type string + ID int + SubType string +} + +func (k *SubResourceKey) String() string { + return fmt.Sprintf("%s_%d_%s", k.Type, k.ID, k.SubType) +} + +// MutexKV is a simple key/value store for arbitrary mutexes. It can be used to +// serialize changes across arbitrary collaborators that share knowledge of the +// keys they must serialize on. +// +// The initial use case is to let aws_security_group_rule resources serialize +// their access to individual security groups based on SG ID. +type MutexKV struct { + lock sync.Mutex + store map[string]*sync.RWMutex +} + +// Locks the mutex for the given key. Caller is responsible for calling Unlock +// for the same key +func (m *MutexKV) Lock(key fmt.Stringer) { + keyStr := key.String() + log.Printf("[DEBUG] Locking %q", key) + m.get(keyStr).Lock() + log.Printf("[DEBUG] Locked %q", key) +} + +// Unlock the mutex for the given key. Caller must have called Lock for the same key first +func (m *MutexKV) Unlock(key fmt.Stringer) { + keyStr := key.String() + log.Printf("[DEBUG] Unlocking %q", key) + m.get(keyStr).Unlock() + log.Printf("[DEBUG] Unlocked %q", key) +} + +// Rlock +func (m *MutexKV) RLock(key fmt.Stringer) { + keyStr := key.String() + log.Printf("[DEBUG] Unlocking %q", key) + m.get(keyStr).RLock() + log.Printf("[DEBUG] Unlocked %q", key) +} + +// RUnlock +func (m *MutexKV) RUnlock(key fmt.Stringer) { + keyStr := key.String() + log.Printf("[DEBUG] Unlocking %q", key) + m.get(keyStr).RUnlock() + log.Printf("[DEBUG] Unlocked %q", key) +} + +// Returns a mutex for the given key, no guarantee of its lock status +func (m *MutexKV) get(key string) *sync.RWMutex { + m.lock.Lock() + defer m.lock.Unlock() + mutex, ok := m.store[key] + if !ok { + mutex = &sync.RWMutex{} + m.store[key] = mutex + } + return mutex +} + +// Returns a properly initialized MutexKV +func NewMutexKV() *MutexKV { + return &MutexKV{ + store: make(map[string]*sync.RWMutex), + } +} diff --git a/opennebula/framework_migration/provider.go b/opennebula/framework_migration/provider.go new file mode 100644 index 000000000..4d65b1671 --- /dev/null +++ b/opennebula/framework_migration/provider.go @@ -0,0 +1,298 @@ +package opennebula + +import ( + "context" + "crypto/tls" + "log" + "net/http" + "os" + "strconv" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/provider" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/OpenNebula/one/src/oca/go/src/goca" + ver "github.com/hashicorp/go-version" +) + +type OpenNebulaProvider struct { + OneVersion *ver.Version + Controller *goca.Controller + mutex MutexKV + //defaultTags map[string]interface{} + //oldDefaultTags map[string]interface{} + //newDefaultTags map[string]interface{} +} + +func New() provider.Provider { + return &OpenNebulaProvider{} +} + +// Metadata returns the provider type name. +func (p *OpenNebulaProvider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "opennebula" +} + +type opennebulaProviderModel struct { + Endpoint types.String `tfsdk:"endpoint"` + FlowEndpoint types.String `tfsdk:"flow_endpoint"` + Username types.String `tfsdk:"username"` + Password types.String `tfsdk:"password"` + Insecure types.Bool `tfsdk:"insecure"` + DefaultTags types.Object `tfsdk:"default_tags"` +} + +func (p *OpenNebulaProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "endpoint": schema.StringAttribute{ + Required: true, + //Computed: true, + Description: "The URL to your public or private OpenNebula", + //PlanModifiers: []planmodifier.String{ + // default + //}, + // defaultValue(types.BoolValue(true)), + //DefaultFunc: schema.EnvDefaultFunc("OPENNEBULA_ENDPOINT", nil), + }, + "flow_endpoint": schema.StringAttribute{ + Optional: true, + //Computed: true, + Description: "The URL to your public or private OpenNebula Flow server", + }, + "username": schema.StringAttribute{ + Required: true, + Description: "The ID of the user to identify as", + }, + "password": schema.StringAttribute{ + Required: true, + Description: "The password for the user", + }, + "insecure": schema.BoolAttribute{ + Optional: true, + Description: "Disable TLS validation", + //PlanModifiers: []planmodifier.Bool{ + // defaultValue(types.BoolValue(false)), + //}, + }, + }, + Blocks: map[string]schema.Block{ + "default_tags": schema.SetNestedBlock{ + Description: "Add default tags to the resources", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "tags": schema.MapAttribute{ + Optional: true, + Description: "Default tags to apply", + ElementType: types.StringType, + }, + }, + }, + }, + }, + } +} + +func (p *OpenNebulaProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + var config opennebulaProviderModel + + diags := req.Config.Get(ctx, &config) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if config.Endpoint.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("endpoint"), + "Unknown OpenNebula XML-RPC API endpoint", + "The provider cannot create the OpenNebula XML-RPC client as there is an unknown configuration value for the OpenNebula API endpoint. "+ + "Either target apply the source of the value first, set the value statically in the configuration, or use the OPENNEBULA_ENDPOINT environment variable.", + ) + } + + if config.Username.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("username"), + "Unknown OpenNebula XML-RPC API username", + "The provider cannot create the OpenNebula XML-RPC client as there is an unknown configuration value for the OpenNebula API username. "+ + "Either target apply the source of the value first, set the value statically in the configuration, or use the OPENNEBULA_USERNAME environment variable.", + ) + } + + if config.Password.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("password"), + "Unknown OpenNebula XML-RPC API password", + "The provider cannot create the OpenNebula XML-RPC client as there is an unknown configuration value for the OpenNebula API password. "+ + "Either target apply the source of the value first, set the value statically in the configuration, or use the OPENNEBULA_PASSWORD environment variable.", + ) + } + + if resp.Diagnostics.HasError() { + return + } + + // Default values to environment variables, but override + // with Terraform configuration value if set. + + endpoint := os.Getenv("OPENNEBULA_ENDPOINT") + flowEndpoint := os.Getenv("OPENNEBULA_FLOW_ENDPOINT") + username := os.Getenv("OPENNEBULA_USERNAME") + password := os.Getenv("OPENNEBULA_PASSWORD") + + insecureStr := os.Getenv("OPENNEBULA_INSECURE") + insecure := false + + var err error + if len(insecureStr) > 0 { + insecure, err = strconv.ParseBool(insecureStr) + if err != nil { + resp.Diagnostics.AddAttributeError( + path.Root("insecure"), + "Failed to parse boolean value from the OPENNEBULA_INSECURE environment variable", + "The provider cannot create the OpenNebula XML-RPC client as there is an unknown configuration value for the OPENNEBULA_INSECURE environment variable.", + ) + } + } + + if !config.Endpoint.IsNull() { + endpoint = config.Endpoint.ValueString() + } + + if !config.Endpoint.IsNull() { + flowEndpoint = config.Endpoint.ValueString() + } + + if !config.Username.IsNull() { + username = config.Username.ValueString() + } + + if !config.Password.IsNull() { + password = config.Password.ValueString() + } + + if !config.Insecure.IsNull() { + insecure = config.Insecure.ValueBool() + } + + // If any of the expected configurations are missing, return + // errors with provider-specific guidance. + + if endpoint == "" { + resp.Diagnostics.AddAttributeError( + path.Root("endpoint"), + "Missing OpenNebula XML-RPC endpoint", + "The provider cannot create the OpenNebula XML-RPC client as there is a missing or empty value for the OpenNebula API endpoint. "+ + "Set the endpoint value in the configuration or use the OPENNEBULA_ENDPOINT environment variable. "+ + "If either is already set, ensure the value is not empty.", + ) + } + + if username == "" { + resp.Diagnostics.AddAttributeError( + path.Root("username"), + "Missing OpenNebula account username", + "The provider cannot create the OpenNebula XML-RPC client as there is a missing or empty value for the OpenNebula username. "+ + "Set the endpoint value in the configuration or use the OPENNEBULA_USERNAME environment variable. "+ + "If either is already set, ensure the value is not empty.", + ) + } + + if password == "" { + resp.Diagnostics.AddAttributeError( + path.Root("endpoint"), + "Missing OpenNebula account password", + "The provider cannot create the OpenNebula XML-RPC client as there is a missing or empty value for the OpenNebula password. "+ + "Set the endpoint value in the configuration or use the OPENNEBULA_PASSWORD environment variable. "+ + "If either is already set, ensure the value is not empty.", + ) + } + + if resp.Diagnostics.HasError() { + return + } + + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure}, + } + + //defaultTags := d.Get("default_tags").(*schema.Set).List() + //if len(defaultTags) > 0 { + // defaultTagsMap := defaultTags[0].(map[string]interface{}) + // cfg.defaultTags = defaultTagsMap["tags"].(map[string]interface{}) + // if len(defaultTagsOld) > 0 { + // cfg.oldDefaultTags = defaultTagsOld[0].(map[string]interface{}) + // } + // if len(defaultTagsNew) > 0 { + // cfg.newDefaultTags = defaultTagsNew[0].(map[string]interface{}) + // } + //} + + // Create a new OpenNebula client using the configuration values + client := goca.NewClient(goca.NewConfig(username, + password, + endpoint), + &http.Client{Transport: tr}) + + versionStr, err := goca.NewController(client).SystemVersion() + if err != nil { + resp.Diagnostics.AddError( + "Failed to get OpenNebula release number", + err.Error(), + ) + return + } + version, err := ver.NewVersion(versionStr) + if err != nil { + resp.Diagnostics.AddError( + "Failed to parse OpenNebula version", + err.Error(), + ) + return + } + + log.Printf("[INFO] OpenNebula version: %s", versionStr) + + cfg := &OpenNebulaProvider{ + OneVersion: version, + mutex: *NewMutexKV(), + } + + if len(flowEndpoint) > 0 { + flowClient := goca.NewDefaultFlowClient( + goca.NewFlowConfig(username, + password, + flowEndpoint)) + + cfg.Controller = goca.NewGenericController(client, flowClient) + } else { + cfg.Controller = goca.NewController(client) + } + + // Make the OpenNebula client available during DataSource and Resource + // type Configure methods. + resp.DataSourceData = cfg + resp.ResourceData = cfg + +} + +func (p *OpenNebulaProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + func() resource.Resource { + return nil //resourceExample{} + }, + } +} + +func (p *OpenNebulaProvider) DataSources(ctx context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + func() datasource.DataSource { + return nil //dataSourceExample{} + }, + } +} From 6d7840ab998475f0214816fd772c0e99e8c5d4ff Mon Sep 17 00:00:00 2001 From: Pierre Lafievre Date: Thu, 2 Feb 2023 18:26:07 +0100 Subject: [PATCH 2/7] add fake resource to the framework to avoid crashing during tests --- .../framework_migration/example_datasource.go | 102 ++++++++++ .../framework_migration/example_resource.go | 176 ++++++++++++++++++ opennebula/framework_migration/provider.go | 4 +- 3 files changed, 280 insertions(+), 2 deletions(-) create mode 100644 opennebula/framework_migration/example_datasource.go create mode 100644 opennebula/framework_migration/example_resource.go diff --git a/opennebula/framework_migration/example_datasource.go b/opennebula/framework_migration/example_datasource.go new file mode 100644 index 000000000..77a4169df --- /dev/null +++ b/opennebula/framework_migration/example_datasource.go @@ -0,0 +1,102 @@ +package opennebula + +import ( + "context" + "fmt" + "net/http" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ datasource.DataSource = &ExampleDataSource{} + +func NewExampleDataSource() datasource.DataSource { + return &ExampleDataSource{} +} + +// ExampleDataSource defines the data source implementation. +type ExampleDataSource struct { + client *http.Client +} + +// ExampleDataSourceModel describes the data source data model. +type ExampleDataSourceModel struct { + ConfigurableAttribute types.String `tfsdk:"configurable_attribute"` + Id types.String `tfsdk:"id"` +} + +func (d *ExampleDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_example" +} + +func (d *ExampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: "Example data source", + + Attributes: map[string]schema.Attribute{ + "configurable_attribute": schema.StringAttribute{ + MarkdownDescription: "Example configurable attribute", + Optional: true, + }, + "id": schema.StringAttribute{ + MarkdownDescription: "Example identifier", + Computed: true, + }, + }, + } +} + +func (d *ExampleDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*http.Client) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + d.client = client +} + +func (d *ExampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data ExampleDataSourceModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // If applicable, this is a great opportunity to initialize any necessary + // provider client data and make a call using it. + // httpResp, err := d.client.Do(httpReq) + // if err != nil { + // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read example, got error: %s", err)) + // return + // } + + // For the purposes of this example code, hardcoding a response value to + // save into the Terraform state. + data.Id = types.StringValue("example-id") + + // Write logs using the tflog package + // Documentation: https://terraform.io/plugin/log + tflog.Trace(ctx, "read a data source") + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/opennebula/framework_migration/example_resource.go b/opennebula/framework_migration/example_resource.go new file mode 100644 index 000000000..1429a96b3 --- /dev/null +++ b/opennebula/framework_migration/example_resource.go @@ -0,0 +1,176 @@ +package opennebula + +import ( + "context" + "fmt" + "net/http" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &ExampleResource{} +var _ resource.ResourceWithImportState = &ExampleResource{} + +func NewExampleResource() resource.Resource { + return &ExampleResource{} +} + +// ExampleResource defines the resource implementation. +type ExampleResource struct { + client *http.Client +} + +// ExampleResourceModel describes the resource data model. +type ExampleResourceModel struct { + ConfigurableAttribute types.String `tfsdk:"configurable_attribute"` + Id types.String `tfsdk:"id"` +} + +func (r *ExampleResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_example" +} + +func (r *ExampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: "Example resource", + + Attributes: map[string]schema.Attribute{ + "configurable_attribute": schema.StringAttribute{ + MarkdownDescription: "Example configurable attribute", + Optional: true, + }, + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Example identifier", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} + +func (r *ExampleResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*http.Client) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = client +} + +func (r *ExampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data *ExampleResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // If applicable, this is a great opportunity to initialize any necessary + // provider client data and make a call using it. + // httpResp, err := r.client.Do(httpReq) + // if err != nil { + // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create example, got error: %s", err)) + // return + // } + + // For the purposes of this example code, hardcoding a response value to + // save into the Terraform state. + data.Id = types.StringValue("example-id") + + // Write logs using the tflog package + // Documentation: https://terraform.io/plugin/log + tflog.Trace(ctx, "created a resource") + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *ExampleResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data *ExampleResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // If applicable, this is a great opportunity to initialize any necessary + // provider client data and make a call using it. + // httpResp, err := r.client.Do(httpReq) + // if err != nil { + // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read example, got error: %s", err)) + // return + // } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *ExampleResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data *ExampleResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // If applicable, this is a great opportunity to initialize any necessary + // provider client data and make a call using it. + // httpResp, err := r.client.Do(httpReq) + // if err != nil { + // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update example, got error: %s", err)) + // return + // } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *ExampleResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data *ExampleResourceModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // If applicable, this is a great opportunity to initialize any necessary + // provider client data and make a call using it. + // httpResp, err := r.client.Do(httpReq) + // if err != nil { + // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete example, got error: %s", err)) + // return + // } +} + +func (r *ExampleResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/opennebula/framework_migration/provider.go b/opennebula/framework_migration/provider.go index 4d65b1671..a6796be75 100644 --- a/opennebula/framework_migration/provider.go +++ b/opennebula/framework_migration/provider.go @@ -284,7 +284,7 @@ func (p *OpenNebulaProvider) Configure(ctx context.Context, req provider.Configu func (p *OpenNebulaProvider) Resources(ctx context.Context) []func() resource.Resource { return []func() resource.Resource{ func() resource.Resource { - return nil //resourceExample{} + return NewExampleResource() }, } } @@ -292,7 +292,7 @@ func (p *OpenNebulaProvider) Resources(ctx context.Context) []func() resource.Re func (p *OpenNebulaProvider) DataSources(ctx context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ func() datasource.DataSource { - return nil //dataSourceExample{} + return NewExampleDataSource() }, } } From 749e3740ddd1bace6bd1e60555b68d0a29a46ee3 Mon Sep 17 00:00:00 2001 From: Pierre Lafievre Date: Fri, 10 Feb 2023 13:55:15 +0100 Subject: [PATCH 3/7] update minimal framework impl --- main.go | 2 +- .../example_datasource.go | 0 .../example_resource.go | 0 .../{framework_migration => framework}/mutexkv.go | 0 .../{framework_migration => framework}/provider.go | 2 +- opennebula/provider.go | 10 +++++----- 6 files changed, 7 insertions(+), 7 deletions(-) rename opennebula/{framework_migration => framework}/example_datasource.go (100%) rename opennebula/{framework_migration => framework}/example_resource.go (100%) rename opennebula/{framework_migration => framework}/mutexkv.go (100%) rename opennebula/{framework_migration => framework}/provider.go (99%) diff --git a/main.go b/main.go index 913c713c9..0244f7804 100644 --- a/main.go +++ b/main.go @@ -11,7 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" provider "github.com/OpenNebula/terraform-provider-opennebula/opennebula" - providerFramework "github.com/OpenNebula/terraform-provider-opennebula/opennebula/framework_migration" + providerFramework "github.com/OpenNebula/terraform-provider-opennebula/opennebula/framework" ) func main() { diff --git a/opennebula/framework_migration/example_datasource.go b/opennebula/framework/example_datasource.go similarity index 100% rename from opennebula/framework_migration/example_datasource.go rename to opennebula/framework/example_datasource.go diff --git a/opennebula/framework_migration/example_resource.go b/opennebula/framework/example_resource.go similarity index 100% rename from opennebula/framework_migration/example_resource.go rename to opennebula/framework/example_resource.go diff --git a/opennebula/framework_migration/mutexkv.go b/opennebula/framework/mutexkv.go similarity index 100% rename from opennebula/framework_migration/mutexkv.go rename to opennebula/framework/mutexkv.go diff --git a/opennebula/framework_migration/provider.go b/opennebula/framework/provider.go similarity index 99% rename from opennebula/framework_migration/provider.go rename to opennebula/framework/provider.go index a6796be75..b2631bdc5 100644 --- a/opennebula/framework_migration/provider.go +++ b/opennebula/framework/provider.go @@ -43,7 +43,7 @@ type opennebulaProviderModel struct { Username types.String `tfsdk:"username"` Password types.String `tfsdk:"password"` Insecure types.Bool `tfsdk:"insecure"` - DefaultTags types.Object `tfsdk:"default_tags"` + DefaultTags types.Set `tfsdk:"default_tags"` } func (p *OpenNebulaProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { diff --git a/opennebula/provider.go b/opennebula/provider.go index 9ae73c41b..9a7b7749b 100644 --- a/opennebula/provider.go +++ b/opennebula/provider.go @@ -20,31 +20,31 @@ func Provider() *schema.Provider { Type: schema.TypeString, Required: true, Description: "The URL to your public or private OpenNebula", - DefaultFunc: schema.EnvDefaultFunc("OPENNEBULA_ENDPOINT", nil), + //DefaultFunc: schema.EnvDefaultFunc("OPENNEBULA_ENDPOINT", nil), }, "flow_endpoint": { Type: schema.TypeString, Optional: true, Description: "The URL to your public or private OpenNebula Flow server", - DefaultFunc: schema.EnvDefaultFunc("OPENNEBULA_FLOW_ENDPOINT", nil), + //DefaultFunc: schema.EnvDefaultFunc("OPENNEBULA_FLOW_ENDPOINT", nil), }, "username": { Type: schema.TypeString, Required: true, Description: "The ID of the user to identify as", - DefaultFunc: schema.EnvDefaultFunc("OPENNEBULA_USERNAME", nil), + //DefaultFunc: schema.EnvDefaultFunc("OPENNEBULA_USERNAME", nil), }, "password": { Type: schema.TypeString, Required: true, Description: "The password for the user", - DefaultFunc: schema.EnvDefaultFunc("OPENNEBULA_PASSWORD", nil), + //DefaultFunc: schema.EnvDefaultFunc("OPENNEBULA_PASSWORD", nil), }, "insecure": { Type: schema.TypeBool, Optional: true, Description: "Disable TLS validation", - DefaultFunc: schema.EnvDefaultFunc("OPENNEBULA_INSECURE", false), + //DefaultFunc: schema.EnvDefaultFunc("OPENNEBULA_INSECURE", false), }, "default_tags": { Type: schema.TypeSet, From 3a9a6b22b105d50aeebbed563d9b57ae1d257539 Mon Sep 17 00:00:00 2001 From: Pierre Lafievre Date: Fri, 10 Feb 2023 15:16:19 +0100 Subject: [PATCH 4/7] try to manage default tags in the framework --- opennebula/framework/provider.go | 57 +++++++++++------------ opennebula/framework/tags.go | 35 ++++++++++++++ opennebula/provider.go | 79 ++++++++++++++++++++++---------- 3 files changed, 117 insertions(+), 54 deletions(-) create mode 100644 opennebula/framework/tags.go diff --git a/opennebula/framework/provider.go b/opennebula/framework/provider.go index b2631bdc5..8ac87399f 100644 --- a/opennebula/framework/provider.go +++ b/opennebula/framework/provider.go @@ -20,10 +20,10 @@ import ( ) type OpenNebulaProvider struct { - OneVersion *ver.Version - Controller *goca.Controller - mutex MutexKV - //defaultTags map[string]interface{} + OneVersion *ver.Version + Controller *goca.Controller + mutex MutexKV + defaultTags map[string]string //oldDefaultTags map[string]interface{} //newDefaultTags map[string]interface{} } @@ -50,34 +50,24 @@ func (p *OpenNebulaProvider) Schema(ctx context.Context, req provider.SchemaRequ resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ "endpoint": schema.StringAttribute{ - Required: true, - //Computed: true, + Optional: true, Description: "The URL to your public or private OpenNebula", - //PlanModifiers: []planmodifier.String{ - // default - //}, - // defaultValue(types.BoolValue(true)), - //DefaultFunc: schema.EnvDefaultFunc("OPENNEBULA_ENDPOINT", nil), }, "flow_endpoint": schema.StringAttribute{ - Optional: true, - //Computed: true, + Optional: true, Description: "The URL to your public or private OpenNebula Flow server", }, "username": schema.StringAttribute{ - Required: true, + Optional: true, Description: "The ID of the user to identify as", }, "password": schema.StringAttribute{ - Required: true, + Optional: true, Description: "The password for the user", }, "insecure": schema.BoolAttribute{ Optional: true, Description: "Disable TLS validation", - //PlanModifiers: []planmodifier.Bool{ - // defaultValue(types.BoolValue(false)), - //}, }, }, Blocks: map[string]schema.Block{ @@ -221,18 +211,6 @@ func (p *OpenNebulaProvider) Configure(ctx context.Context, req provider.Configu TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure}, } - //defaultTags := d.Get("default_tags").(*schema.Set).List() - //if len(defaultTags) > 0 { - // defaultTagsMap := defaultTags[0].(map[string]interface{}) - // cfg.defaultTags = defaultTagsMap["tags"].(map[string]interface{}) - // if len(defaultTagsOld) > 0 { - // cfg.oldDefaultTags = defaultTagsOld[0].(map[string]interface{}) - // } - // if len(defaultTagsNew) > 0 { - // cfg.newDefaultTags = defaultTagsNew[0].(map[string]interface{}) - // } - //} - // Create a new OpenNebula client using the configuration values client := goca.NewClient(goca.NewConfig(username, password, @@ -274,6 +252,25 @@ func (p *OpenNebulaProvider) Configure(ctx context.Context, req provider.Configu cfg.Controller = goca.NewController(client) } + var tags Tags + for _, t := range config.DefaultTags.Elements() { + element, err := t.ToTerraformValue(ctx) + if err != nil { + log.Print("[DEBUG] As err: ", err) + continue + } + err = element.As(&tags) + if err != nil { + log.Print("[DEBUG] As err: ", err) + continue + } + } + + if len(tags.elements) > 0 { + cfg.defaultTags = tags.elements + } + log.Printf("[DEBUG] default_tags: %+v", tags.elements) + // Make the OpenNebula client available during DataSource and Resource // type Configure methods. resp.DataSourceData = cfg diff --git a/opennebula/framework/tags.go b/opennebula/framework/tags.go new file mode 100644 index 000000000..111c5bcc3 --- /dev/null +++ b/opennebula/framework/tags.go @@ -0,0 +1,35 @@ +package opennebula + +import "github.com/hashicorp/terraform-plugin-go/tftypes" + +type Tags struct { + elements map[string]string +} + +func (t *Tags) FromTerraform5Value(val tftypes.Value) error { + + v := map[string]tftypes.Value{} + err := val.As(&v) + if err != nil { + return err + } + + tmpTags := make(map[string]tftypes.Value) + + err = v["tags"].As(&tmpTags) + if err != nil { + return err + } + + t.elements = make(map[string]string) + + for k, v := range tmpTags { + if v.Type().Is(tftypes.String) { + value := "" + v.As(&value) + t.elements[k] = value + } + } + + return nil +} diff --git a/opennebula/provider.go b/opennebula/provider.go index 9a7b7749b..9c8689d60 100644 --- a/opennebula/provider.go +++ b/opennebula/provider.go @@ -5,6 +5,8 @@ import ( "crypto/tls" "log" "net/http" + "os" + "strconv" ver "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -18,33 +20,28 @@ func Provider() *schema.Provider { Schema: map[string]*schema.Schema{ "endpoint": { Type: schema.TypeString, - Required: true, + Optional: true, Description: "The URL to your public or private OpenNebula", - //DefaultFunc: schema.EnvDefaultFunc("OPENNEBULA_ENDPOINT", nil), }, "flow_endpoint": { Type: schema.TypeString, Optional: true, Description: "The URL to your public or private OpenNebula Flow server", - //DefaultFunc: schema.EnvDefaultFunc("OPENNEBULA_FLOW_ENDPOINT", nil), }, "username": { Type: schema.TypeString, - Required: true, + Optional: true, Description: "The ID of the user to identify as", - //DefaultFunc: schema.EnvDefaultFunc("OPENNEBULA_USERNAME", nil), }, "password": { Type: schema.TypeString, - Required: true, + Optional: true, Description: "The password for the user", - //DefaultFunc: schema.EnvDefaultFunc("OPENNEBULA_PASSWORD", nil), }, "insecure": { Type: schema.TypeBool, Optional: true, Description: "Disable TLS validation", - //DefaultFunc: schema.EnvDefaultFunc("OPENNEBULA_INSECURE", false), }, "default_tags": { Type: schema.TypeSet, @@ -120,41 +117,70 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{} var diags diag.Diagnostics - username, ok := d.GetOk("username") - if !ok { + username := d.Get("username").(string) + if len(username) == 0 { + username = os.Getenv("OPENNEBULA_USERNAME") + } + if len(username) == 0 { diags = append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "username should be defined", + Detail: "username should be provided either via the configuration or via the OPENNEBULA_USERNAME environment variable", }) return nil, diags } - password, ok := d.GetOk("password") - if !ok { + password := d.Get("password").(string) + if len(password) == 0 { + password = os.Getenv("OPENNEBULA_PASSWORD") + } + if len(password) == 0 { diags = append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "password should be defined", + Detail: "password should be provided either via the configuration or via the OPENNEBULA_PASSWORD environment variable", }) return nil, diags } - endpoint, ok := d.GetOk("endpoint") - if !ok { + endpoint := d.Get("endpoint").(string) + if len(endpoint) == 0 { + endpoint = os.Getenv("OPENNEBULA_ENDPOINT") + } + if len(endpoint) == 0 { diags = append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "endpoint should be defined", + Detail: "endpoint should be provided either via the configuration or via the OPENNEBULA_ENDPOINT environment variable", }) return nil, diags } - insecure := d.Get("insecure") + insecureIf := d.Get("insecure") + insecure := false + if insecureIf == nil || !insecureIf.(bool) { + insecureStr := os.Getenv("OPENNEBULA_INSECURE") + + var err error + if len(insecureStr) > 0 { + insecure, err = strconv.ParseBool(insecureStr) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to parse boolean value from the OPENNEBULA_INSECURE environment variable", + Detail: err.Error(), + }) + return nil, diags + } + } + } tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure.(bool)}, + TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure}, } - oneClient := goca.NewClient(goca.NewConfig(username.(string), - password.(string), - endpoint.(string)), + oneClient := goca.NewClient(goca.NewConfig(username, + password, + endpoint), &http.Client{Transport: tr}) versionStr, err := goca.NewController(oneClient).SystemVersion() @@ -199,12 +225,17 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{} } } - flowEndpoint, ok := d.GetOk("flow_endpoint") - if ok { + flowEndpoint := d.Get("flow_endpoint").(string) + if len(flowEndpoint) == 0 { + flowEndpoint = os.Getenv("OPENNEBULA_FLOW_ENDPOINT") + } + + if len(flowEndpoint) > 0 { + flowClient := goca.NewDefaultFlowClient( - goca.NewFlowConfig(username.(string), - password.(string), - flowEndpoint.(string))) + goca.NewFlowConfig(username, + password, + flowEndpoint)) cfg.Controller = goca.NewGenericController(oneClient, flowClient) return cfg, nil From f79a4364304319c360fcfe0b328ad11489c9c226 Mon Sep 17 00:00:00 2001 From: Pierre Lafievre Date: Mon, 13 Feb 2023 15:02:08 +0100 Subject: [PATCH 5/7] remove dead code from SDK2 provider --- opennebula/provider.go | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/opennebula/provider.go b/opennebula/provider.go index 9c8689d60..eff3746e3 100644 --- a/opennebula/provider.go +++ b/opennebula/provider.go @@ -105,12 +105,10 @@ func Provider() *schema.Provider { } type Configuration struct { - OneVersion *ver.Version - Controller *goca.Controller - mutex MutexKV - defaultTags map[string]interface{} - oldDefaultTags map[string]interface{} - newDefaultTags map[string]interface{} + OneVersion *ver.Version + Controller *goca.Controller + mutex MutexKV + defaultTags map[string]interface{} } func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { @@ -209,20 +207,10 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{} mutex: *NewMutexKV(), } - defaultTagsOldIf, defaultTagsNewIf := d.GetChange("default_tags") - defaultTagsOld := defaultTagsOldIf.(*schema.Set).List() - defaultTagsNew := defaultTagsNewIf.(*schema.Set).List() - defaultTags := d.Get("default_tags").(*schema.Set).List() if len(defaultTags) > 0 { defaultTagsMap := defaultTags[0].(map[string]interface{}) cfg.defaultTags = defaultTagsMap["tags"].(map[string]interface{}) - if len(defaultTagsOld) > 0 { - cfg.oldDefaultTags = defaultTagsOld[0].(map[string]interface{}) - } - if len(defaultTagsNew) > 0 { - cfg.newDefaultTags = defaultTagsNew[0].(map[string]interface{}) - } } flowEndpoint := d.Get("flow_endpoint").(string) From 827b0e0fd5e7b5af3ca26e35c095977251632fde Mon Sep 17 00:00:00 2001 From: Pierre Lafievre Date: Thu, 16 Feb 2023 18:20:31 +0100 Subject: [PATCH 6/7] remove cluster from provider SDK side to avoid potential conflicts --- opennebula/provider.go | 6 +- opennebula/resource_opennebula_cluster.go | 599 ---------------------- 2 files changed, 3 insertions(+), 602 deletions(-) delete mode 100644 opennebula/resource_opennebula_cluster.go diff --git a/opennebula/provider.go b/opennebula/provider.go index eff3746e3..6e6da603b 100644 --- a/opennebula/provider.go +++ b/opennebula/provider.go @@ -95,9 +95,9 @@ func Provider() *schema.Provider { "opennebula_virtual_router": resourceOpennebulaVirtualRouter(), "opennebula_virtual_router_nic": resourceOpennebulaVirtualRouterNIC(), "opennebula_virtual_network_address_range": resourceOpennebulaVirtualNetworkAddressRange(), - "opennebula_cluster": resourceOpennebulaCluster(), - "opennebula_host": resourceOpennebulaHost(), - "opennebula_datastore": resourceOpennebulaDatastore(), + //"opennebula_cluster": resourceOpennebulaCluster(), + "opennebula_host": resourceOpennebulaHost(), + "opennebula_datastore": resourceOpennebulaDatastore(), }, ConfigureContextFunc: providerConfigure, diff --git a/opennebula/resource_opennebula_cluster.go b/opennebula/resource_opennebula_cluster.go deleted file mode 100644 index 42b382b0c..000000000 --- a/opennebula/resource_opennebula_cluster.go +++ /dev/null @@ -1,599 +0,0 @@ -package opennebula - -import ( - "context" - "fmt" - "log" - "strconv" - "strings" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - - "github.com/OpenNebula/one/src/oca/go/src/goca" - dyn "github.com/OpenNebula/one/src/oca/go/src/goca/dynamic" - "github.com/OpenNebula/one/src/oca/go/src/goca/parameters" - "github.com/OpenNebula/one/src/oca/go/src/goca/schemas/cluster" -) - -func resourceOpennebulaCluster() *schema.Resource { - return &schema.Resource{ - CreateContext: resourceOpennebulaClusterCreate, - ReadContext: resourceOpennebulaClusterRead, - UpdateContext: resourceOpennebulaClusterUpdate, - DeleteContext: resourceOpennebulaClusterDelete, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - CustomizeDiff: SetTagsDiff, - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - Description: "Name of the Cluster", - }, - "hosts": { - Type: schema.TypeSet, - Optional: true, - Description: "List of hosts IDs part of the cluster", - Elem: &schema.Schema{ - Type: schema.TypeInt, - }, - Deprecated: "use cluster_id field from the host resource instead", - }, - "datastores": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - Description: "List of datastores IDs part of the cluster", - Elem: &schema.Schema{ - Type: schema.TypeInt, - }, - Deprecated: "use cluster_ids field from the datastore resource instead", - }, - "virtual_networks": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - Description: "List of virtual network IDs part of the cluster", - Elem: &schema.Schema{ - Type: schema.TypeInt, - }, - Deprecated: "use cluster_ids field from the virtual network resource instead", - }, - "tags": tagsSchema(), - "default_tags": defaultTagsSchemaComputed(), - "tags_all": tagsSchemaComputed(), - "template_section": templateSectionSchema(), - }, - } -} - -func getClusterController(d *schema.ResourceData, meta interface{}) (*goca.ClusterController, error) { - config := meta.(*Configuration) - controller := config.Controller - var gc *goca.ClusterController - - if d.Id() != "" { - gid, err := strconv.ParseUint(d.Id(), 10, 0) - if err != nil { - return nil, err - } - gc = controller.Cluster(int(gid)) - } - - return gc, nil -} - -func resourceOpennebulaClusterCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - config := meta.(*Configuration) - controller := config.Controller - - var diags diag.Diagnostics - - clusterID, err := controller.Clusters().Create(d.Get("name").(string)) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to create the cluster", - Detail: err.Error(), - }) - return diags - } - d.SetId(fmt.Sprintf("%d", clusterID)) - - cc := controller.Cluster(clusterID) - - // add hosts - if hostsIf, ok := d.GetOk("hosts"); ok { - hostsList := hostsIf.(*schema.Set).List() - for i := 0; i < len(hostsList); i++ { - err = cc.AddHost(hostsList[i].(int)) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to add hosts", - Detail: fmt.Sprintf("cluster (ID: %d): %s", clusterID, err), - }) - return diags - } - } - } - - // add datastores - if datastoreIf, ok := d.GetOk("datastores"); ok { - datastoreList := datastoreIf.(*schema.Set).List() - for i := 0; i < len(datastoreList); i++ { - err = cc.AddDatastore(datastoreList[i].(int)) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to add datastore", - Detail: fmt.Sprintf("cluster (ID: %d): %s", clusterID, err), - }) - return diags - } - } - } - - // add virtual networks - if vnetIf, ok := d.GetOk("virtual_networks"); ok { - vnetList := vnetIf.(*schema.Set).List() - for i := 0; i < len(vnetList); i++ { - err = cc.AddVnet(vnetList[i].(int)) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to add virtual network", - Detail: fmt.Sprintf("cluster (ID: %d): %s", clusterID, err), - }) - return diags - } - } - } - - // template management - - tpl := dyn.NewTemplate() - - vectorsInterface := d.Get("template_section").(*schema.Set).List() - if len(vectorsInterface) > 0 { - addTemplateVectors(vectorsInterface, tpl) - } - - tagsInterface := d.Get("tags").(map[string]interface{}) - for k, v := range tagsInterface { - tpl.AddPair(strings.ToUpper(k), v) - } - - // add default tags if they aren't overriden - if len(config.defaultTags) > 0 { - for k, v := range config.defaultTags { - key := strings.ToUpper(k) - p, _ := tpl.GetPair(key) - if p != nil { - continue - } - tpl.AddPair(key, v) - } - } - - if len(tpl.Elements) > 0 { - err = cc.Update(tpl.String(), parameters.Merge) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to update the cluster content", - Detail: fmt.Sprintf("cluster (ID: %d): %s", clusterID, err), - }) - return diags - } - } - - return resourceOpennebulaClusterRead(ctx, d, meta) -} - -func resourceOpennebulaClusterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - - var diags diag.Diagnostics - - cc, err := getClusterController(d, meta) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to get the cluster controller", - Detail: err.Error(), - }) - return diags - } - - clusterInfos, err := cc.Info() - if err != nil { - if NoExists(err) { - log.Printf("[WARN] Removing cluster %s from state because it no longer exists in", d.Get("name")) - d.SetId("") - return nil - } - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed retrieve cluster informations", - Detail: fmt.Sprintf("cluster (ID: %s): %s", d.Id(), err), - }) - return diags - } - - d.Set("name", clusterInfos.Name) - - // read cluster hosts members - err = d.Set("hosts", clusterInfos.Hosts.ID) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to set hosts field", - Detail: fmt.Sprintf("cluster (ID: %s): %s", d.Id(), err), - }) - return diags - } - - // read cluster datastore members - err = d.Set("datastores", clusterInfos.Datastores.ID) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to set datastores field", - Detail: fmt.Sprintf("cluster (ID: %s): %s", d.Id(), err), - }) - return diags - } - - // read cluster virtual network members - err = d.Set("virtual_networks", clusterInfos.Vnets.ID) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to set virtual_networks field", - Detail: fmt.Sprintf("cluster (ID: %s): %s", d.Id(), err), - }) - return diags - } - - err = flattenClusterTemplate(d, meta, &clusterInfos.Template) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to flatten template", - Detail: fmt.Sprintf("cluster (ID: %s): %s", d.Id(), err), - }) - return diags - } - - return nil -} - -func flattenClusterTemplate(d *schema.ResourceData, meta interface{}, clusterTpl *cluster.Template) error { - config := meta.(*Configuration) - - err := flattenTemplateSection(d, meta, &clusterTpl.Template) - if err != nil { - return err - } - - tags := make(map[string]interface{}) - tagsAll := make(map[string]interface{}) - - // Get default tags - oldDefault := d.Get("default_tags").(map[string]interface{}) - for k, _ := range oldDefault { - tagValue, err := clusterTpl.GetStr(strings.ToUpper(k)) - if err != nil { - return nil - } - tagsAll[k] = tagValue - } - d.Set("default_tags", config.defaultTags) - - // Get only tags described in the configuration - if tagsInterface, ok := d.GetOk("tags"); ok { - - for k, _ := range tagsInterface.(map[string]interface{}) { - tagValue, err := clusterTpl.GetStr(strings.ToUpper(k)) - if err != nil { - return err - } - tags[k] = tagValue - tagsAll[k] = tagValue - } - - err := d.Set("tags", tags) - if err != nil { - return err - } - } - d.Set("tags_all", tagsAll) - - return nil -} - -func resourceOpennebulaClusterUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - - config := meta.(*Configuration) - controller := config.Controller - - var diags diag.Diagnostics - - cc, err := getClusterController(d, meta) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to get the cluster controller", - Detail: err.Error(), - }) - return diags - } - - // template management - - cluster, err := cc.Info() - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to retrieve clusters informations", - Detail: fmt.Sprintf("cluster (ID: %s): %s", d.Id(), err), - }) - return diags - } - - update := false - newTpl := cluster.Template - - if d.HasChange("hosts") { - - oldHostsIf, newHostsIf := d.GetChange("hosts") - - oldHosts := schema.NewSet(schema.HashInt, oldHostsIf.(*schema.Set).List()) - newHosts := schema.NewSet(schema.HashInt, newHostsIf.(*schema.Set).List()) - - // delete hosts - remHosts := oldHosts.Difference(newHosts) - - for _, id := range remHosts.List() { - - // we need to check is the ID is not an old ID - // i.e. the ID of an user deleted/replaced - _, err := controller.Host(id.(int)).Info(false) - if err != nil { - if NoExists(err) { - continue - } - } - - err = cc.DelHost(id.(int)) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to delete a host from the cluster", - Detail: fmt.Sprintf("cluster (ID: %d): %s", cc.ID, err), - }) - return diags - } - } - - // add hosts - addHosts := newHosts.Difference(oldHosts) - - for _, id := range addHosts.List() { - err := cc.AddHost(id.(int)) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to add a host to the cluster", - Detail: fmt.Sprintf("cluster (ID: %d): %s", cc.ID, err), - }) - return diags - } - } - } - - if d.HasChange("datastores") { - - oldDatastoresIf, newDatastoresIf := d.GetChange("datastores") - - oldDatastores := schema.NewSet(schema.HashInt, oldDatastoresIf.(*schema.Set).List()) - newDatastores := schema.NewSet(schema.HashInt, newDatastoresIf.(*schema.Set).List()) - - // delete datastores - remDatastores := oldDatastores.Difference(newDatastores) - - for _, id := range remDatastores.List() { - - // we need to check is the ID is not an old ID - // i.e. the ID of an user deleted/replaced - _, err := controller.Datastore(id.(int)).Info(false) - if err != nil { - if NoExists(err) { - continue - } - } - - err = cc.DelDatastore(id.(int)) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to delete a datastore from the cluster", - Detail: fmt.Sprintf("cluster (ID: %d): %s", cc.ID, err), - }) - return diags - } - } - - // add datastores - addDatastores := newDatastores.Difference(oldDatastores) - - for _, id := range addDatastores.List() { - err := cc.AddDatastore(id.(int)) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to add a datastore to the cluster", - Detail: fmt.Sprintf("cluster (ID: %d): %s", cc.ID, err), - }) - return diags - } - } - } - - if d.HasChange("virtual_networks") { - - oldVNetIf, newVNetIf := d.GetChange("virtual_networks") - - oldVNet := schema.NewSet(schema.HashInt, oldVNetIf.(*schema.Set).List()) - newVNet := schema.NewSet(schema.HashInt, newVNetIf.(*schema.Set).List()) - - // delete virtual network - remVNet := oldVNet.Difference(newVNet) - - for _, id := range remVNet.List() { - - // we need to check is the ID is not an old ID - // i.e. the ID of an user deleted/replaced - _, err := controller.VirtualNetwork(id.(int)).Info(false) - if err != nil { - if NoExists(err) { - continue - } - } - - // delete the user from group admin - err = cc.DelVnet(id.(int)) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to delete a virtual network from the cluster", - Detail: fmt.Sprintf("cluster (ID: %d): %s", cc.ID, err), - }) - return diags - } - } - - // add virtual networks - addVNet := newVNet.Difference(oldVNet) - - for _, id := range addVNet.List() { - err := cc.AddVnet(id.(int)) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to add a virtual network to the cluster", - Detail: fmt.Sprintf("cluster (ID: %d): %s", cc.ID, err), - }) - return diags - } - } - } - - if d.HasChange("template_section") { - - updateTemplateSection(d, &newTpl.Template) - - update = true - } - - if d.HasChange("tags") { - - oldTagsIf, newTagsIf := d.GetChange("tags") - oldTags := oldTagsIf.(map[string]interface{}) - newTags := newTagsIf.(map[string]interface{}) - - // delete tags - for k, _ := range oldTags { - _, ok := newTags[k] - if ok { - continue - } - newTpl.Del(strings.ToUpper(k)) - } - - // add/update tags - for k, v := range newTags { - key := strings.ToUpper(k) - newTpl.Del(key) - newTpl.AddPair(key, v) - } - - update = true - } - - if d.HasChange("tags_all") { - oldTagsAllIf, newTagsAllIf := d.GetChange("tags_all") - oldTagsAll := oldTagsAllIf.(map[string]interface{}) - newTagsAll := newTagsAllIf.(map[string]interface{}) - - tags := d.Get("tags").(map[string]interface{}) - - // delete tags - for k, _ := range oldTagsAll { - _, ok := newTagsAll[k] - if ok { - continue - } - newTpl.Del(strings.ToUpper(k)) - } - - // reapply all default tags that were neither applied nor overriden via tags section - for k, v := range newTagsAll { - _, ok := tags[k] - if ok { - continue - } - - key := strings.ToUpper(k) - newTpl.Del(key) - newTpl.AddPair(key, v) - } - - update = true - } - - if update { - err = cc.Update(newTpl.String(), parameters.Replace) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to update cluster content", - Detail: fmt.Sprintf("cluster (ID: %s): %s", d.Id(), err), - }) - return diags - } - - } - - return resourceOpennebulaClusterRead(ctx, d, meta) -} - -func resourceOpennebulaClusterDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - - var diags diag.Diagnostics - - cc, err := getClusterController(d, meta) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to get the cluster controller", - Detail: err.Error(), - }) - return diags - } - - err = cc.Delete() - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to delete", - Detail: fmt.Sprintf("cluster (ID: %d): %s", cc.ID, err), - }) - return diags - } - - return nil -} From 2b263fb59e52d5c515d9a1db5b6537210706b111 Mon Sep 17 00:00:00 2001 From: Pierre Lafievre Date: Tue, 14 Feb 2023 17:44:22 +0100 Subject: [PATCH 7/7] Cluster migration: create and read methods --- go.mod | 2 +- opennebula/framework/common/tags.go | 69 ++++ .../framework/common/template_section.go | 69 ++++ opennebula/framework/config/provider.go | 15 + opennebula/framework/default_tags.go | 65 ++++ opennebula/framework/provider.go | 41 +- opennebula/framework/resources/cluster.go | 350 ++++++++++++++++++ .../example.go} | 2 +- opennebula/framework/resources/helpers.go | 16 + opennebula/framework/tags.go | 35 -- opennebula/framework/{ => utils}/mutexkv.go | 2 +- 11 files changed, 611 insertions(+), 55 deletions(-) create mode 100644 opennebula/framework/common/tags.go create mode 100644 opennebula/framework/common/template_section.go create mode 100644 opennebula/framework/config/provider.go create mode 100644 opennebula/framework/default_tags.go create mode 100644 opennebula/framework/resources/cluster.go rename opennebula/framework/{example_resource.go => resources/example.go} (99%) create mode 100644 opennebula/framework/resources/helpers.go delete mode 100644 opennebula/framework/tags.go rename opennebula/framework/{ => utils}/mutexkv.go (99%) diff --git a/go.mod b/go.mod index 311aeda2c..f262bf5ba 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/terraform-plugin-framework v1.1.1 github.com/hashicorp/terraform-plugin-go v0.14.3 + github.com/hashicorp/terraform-plugin-log v0.7.0 github.com/hashicorp/terraform-plugin-mux v0.8.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.1 ) @@ -31,7 +32,6 @@ require ( github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.17.3 // indirect github.com/hashicorp/terraform-json v0.14.0 // indirect - github.com/hashicorp/terraform-plugin-log v0.7.0 // indirect github.com/hashicorp/terraform-registry-address v0.1.0 // indirect github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect diff --git a/opennebula/framework/common/tags.go b/opennebula/framework/common/tags.go new file mode 100644 index 000000000..95426dce5 --- /dev/null +++ b/opennebula/framework/common/tags.go @@ -0,0 +1,69 @@ +package common + +import ( + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TagsSchema() schema.Attribute { + return schema.MapAttribute{ + Optional: true, + // MarkdownDescription: "", + Description: "Add custom tags to the resource", + ElementType: types.StringType, + } +} + +type Tags struct { + Elements map[string]string +} + +func (t *Tags) FromTerraform5Value(val tftypes.Value) error { + + v := map[string]tftypes.Value{} + err := val.As(&v) + if err != nil { + return err + } + + t.Elements = make(map[string]string) + + for k, v := range v { + if v.Type().Is(tftypes.String) { + value := "" + v.As(&value) + t.Elements[k] = value + } + } + + return nil +} + +//type defaultTagsModifier struct { +// tags map[string]attr.Value +//} +// +//func (d defaultTagsModifier) Description(ctx context.Context) string { +// return fmt.Sprintf("Applies default tags then override with resource tags to produce the new plan") +//} +// +//func (d defaultTagsModifier) MarkdownDescription(ctx context.Context) string { +// return fmt.Sprintf("Applies default tags then override with resource tags to produce the new plan") +// +//} +// +//// PlanModifyString runs the logic of the plan modifier. +//// Access to the configuration, plan, and state is available in `req`, while +//// `resp` contains fields for updating the planned value, triggering resource +//// replacement, and returning diagnostics. +//func (d defaultTagsModifier) PlanModifyMap(ctx context.Context, req planmodifier.MapRequest, resp *planmodifier.MapResponse) { +// //req.Plan +// +// resp.PlanValue, resp.Diagnostics = types.MapValue(types.MapType{}, d.tags) +//} +// +//func defaultTagsModifierInit() *defaultTagsModifier { +// return &defaultTagsModifier{} +//} +// diff --git a/opennebula/framework/common/template_section.go b/opennebula/framework/common/template_section.go new file mode 100644 index 000000000..2065173be --- /dev/null +++ b/opennebula/framework/common/template_section.go @@ -0,0 +1,69 @@ +package common + +import ( + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TemplateSectionBlock() schema.Block { + return schema.SetNestedBlock{ + Description: "Add default tags to the resources", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Optional: true, + Description: "Name of the section", + //MarkdownDescription: "", + }, + "elements": schema.MapAttribute{ + Optional: true, + Description: "Tags of the section", + ElementType: types.StringType, + //MarkdownDescription: "", + }, + }, + }, + } +} + +type TemplateSection struct { + Name string + Elements map[string]string +} + +func (t *TemplateSection) FromTerraform5Value(val tftypes.Value) error { + + // Get tags representation as golang types + v := map[string]tftypes.Value{} + err := val.As(&v) + if err != nil { + return err + } + + // get name + err = v["name"].As(&t.Name) + if err != nil { + return err + } + + // get section elements + tmpTags := make(map[string]tftypes.Value) + + err = v["elements"].As(&tmpTags) + if err != nil { + return err + } + + t.Elements = make(map[string]string) + + for k, v := range tmpTags { + if v.Type().Is(tftypes.String) { + value := "" + v.As(&value) + t.Elements[k] = value + } + } + + return nil +} diff --git a/opennebula/framework/config/provider.go b/opennebula/framework/config/provider.go new file mode 100644 index 000000000..ac5e840a7 --- /dev/null +++ b/opennebula/framework/config/provider.go @@ -0,0 +1,15 @@ +package config + +import ( + "github.com/OpenNebula/one/src/oca/go/src/goca" + ver "github.com/hashicorp/go-version" + + "github.com/OpenNebula/terraform-provider-opennebula/opennebula/framework/utils" +) + +type Provider struct { + OneVersion *ver.Version + Controller *goca.Controller + Mutex utils.MutexKV + DefaultTags map[string]string +} diff --git a/opennebula/framework/default_tags.go b/opennebula/framework/default_tags.go new file mode 100644 index 000000000..05d06bd77 --- /dev/null +++ b/opennebula/framework/default_tags.go @@ -0,0 +1,65 @@ +package opennebula + +import ( + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +type DefaultTags struct { + Elements map[string]string +} + +func (t *DefaultTags) FromTerraform5Value(val tftypes.Value) error { + + v := map[string]tftypes.Value{} + err := val.As(&v) + if err != nil { + return err + } + + tmpTags := make(map[string]tftypes.Value) + + err = v["tags"].As(&tmpTags) + if err != nil { + return err + } + + t.Elements = make(map[string]string) + + for k, v := range tmpTags { + if v.Type().Is(tftypes.String) { + value := "" + v.As(&value) + t.Elements[k] = value + } + } + + return nil +} + +//type defaultTagsModifier struct { +// tags map[string]attr.Value +//} +// +//func (d defaultTagsModifier) Description(ctx context.Context) string { +// return fmt.Sprintf("Applies default tags then override with resource tags to produce the new plan") +//} +// +//func (d defaultTagsModifier) MarkdownDescription(ctx context.Context) string { +// return fmt.Sprintf("Applies default tags then override with resource tags to produce the new plan") +// +//} +// +//// PlanModifyString runs the logic of the plan modifier. +//// Access to the configuration, plan, and state is available in `req`, while +//// `resp` contains fields for updating the planned value, triggering resource +//// replacement, and returning diagnostics. +//func (d defaultTagsModifier) PlanModifyMap(ctx context.Context, req planmodifier.MapRequest, resp *planmodifier.MapResponse) { +// //req.Plan +// +// resp.PlanValue, resp.Diagnostics = types.MapValue(types.MapType{}, d.tags) +//} +// +//func defaultTagsModifierInit() *defaultTagsModifier { +// return &defaultTagsModifier{} +//} +// diff --git a/opennebula/framework/provider.go b/opennebula/framework/provider.go index 8ac87399f..2de16b6d3 100644 --- a/opennebula/framework/provider.go +++ b/opennebula/framework/provider.go @@ -17,15 +17,15 @@ import ( "github.com/OpenNebula/one/src/oca/go/src/goca" ver "github.com/hashicorp/go-version" + + "github.com/OpenNebula/terraform-provider-opennebula/opennebula/framework/config" + providerCfg "github.com/OpenNebula/terraform-provider-opennebula/opennebula/framework/config" + "github.com/OpenNebula/terraform-provider-opennebula/opennebula/framework/resources" + "github.com/OpenNebula/terraform-provider-opennebula/opennebula/framework/utils" ) type OpenNebulaProvider struct { - OneVersion *ver.Version - Controller *goca.Controller - mutex MutexKV - defaultTags map[string]string - //oldDefaultTags map[string]interface{} - //newDefaultTags map[string]interface{} + config.Provider } func New() provider.Provider { @@ -52,22 +52,27 @@ func (p *OpenNebulaProvider) Schema(ctx context.Context, req provider.SchemaRequ "endpoint": schema.StringAttribute{ Optional: true, Description: "The URL to your public or private OpenNebula", + //MarkdownDescription: "", }, "flow_endpoint": schema.StringAttribute{ Optional: true, Description: "The URL to your public or private OpenNebula Flow server", + //MarkdownDescription: "", }, "username": schema.StringAttribute{ Optional: true, Description: "The ID of the user to identify as", + //MarkdownDescription: "", }, "password": schema.StringAttribute{ Optional: true, Description: "The password for the user", + //MarkdownDescription: "", }, "insecure": schema.BoolAttribute{ Optional: true, Description: "Disable TLS validation", + //MarkdownDescription: "", }, }, Blocks: map[string]schema.Block{ @@ -237,8 +242,10 @@ func (p *OpenNebulaProvider) Configure(ctx context.Context, req provider.Configu log.Printf("[INFO] OpenNebula version: %s", versionStr) cfg := &OpenNebulaProvider{ - OneVersion: version, - mutex: *NewMutexKV(), + providerCfg.Provider{ + OneVersion: version, + Mutex: *utils.NewMutexKV(), + }, } if len(flowEndpoint) > 0 { @@ -252,11 +259,11 @@ func (p *OpenNebulaProvider) Configure(ctx context.Context, req provider.Configu cfg.Controller = goca.NewController(client) } - var tags Tags + var tags DefaultTags for _, t := range config.DefaultTags.Elements() { element, err := t.ToTerraformValue(ctx) if err != nil { - log.Print("[DEBUG] As err: ", err) + log.Print("[DEBUG] ToTerraformValue err: ", err) continue } err = element.As(&tags) @@ -266,22 +273,22 @@ func (p *OpenNebulaProvider) Configure(ctx context.Context, req provider.Configu } } - if len(tags.elements) > 0 { - cfg.defaultTags = tags.elements + if len(tags.Elements) > 0 { + cfg.DefaultTags = tags.Elements } - log.Printf("[DEBUG] default_tags: %+v", tags.elements) + log.Printf("[DEBUG] default_tags: %+v", tags.Elements) - // Make the OpenNebula client available during DataSource and Resource + // Make the OpenNebula controller available during DataSource and Resource // type Configure methods. - resp.DataSourceData = cfg - resp.ResourceData = cfg + resp.DataSourceData = &cfg.Provider + resp.ResourceData = &cfg.Provider } func (p *OpenNebulaProvider) Resources(ctx context.Context) []func() resource.Resource { return []func() resource.Resource{ func() resource.Resource { - return NewExampleResource() + return resources.NewCluster() }, } } diff --git a/opennebula/framework/resources/cluster.go b/opennebula/framework/resources/cluster.go new file mode 100644 index 000000000..18032c8b1 --- /dev/null +++ b/opennebula/framework/resources/cluster.go @@ -0,0 +1,350 @@ +package resources + +import ( + "context" + "fmt" + "log" + "strconv" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/OpenNebula/one/src/oca/go/src/goca" + dyn "github.com/OpenNebula/one/src/oca/go/src/goca/dynamic" + "github.com/OpenNebula/one/src/oca/go/src/goca/parameters" + "github.com/OpenNebula/terraform-provider-opennebula/opennebula/framework/common" + "github.com/OpenNebula/terraform-provider-opennebula/opennebula/framework/config" +) + +var _ resource.Resource = &Cluster{} +var _ resource.ResourceWithImportState = &Cluster{} + +type ClusterModel struct { + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Tags types.Map `tfsdk:"tags"` + DefaultTags types.Map `tfsdk:"default_tags"` + TagsAll types.Map `tfsdk:"tags_all"` + TemplateSection types.Set `tfsdk:"template_section"` +} + +// TODO: use cluster controller +type Cluster struct { + defaultTags map[string]string + controller *goca.Controller +} + +func NewCluster() resource.Resource { + return &Cluster{} +} + +func (r *Cluster) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_cluster" +} + +func (r *Cluster) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: "Cluster resource", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + // MarkdownDescription: "Example identifier", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + Description: "Name of the Cluster", + //MarkdownDescription: "", + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "tags": common.TagsSchema(), + "default_tags": schema.MapAttribute{ + Computed: true, + ElementType: types.StringType, + Description: "Default tags defined in the provider configuration", + }, + "tags_all": schema.MapAttribute{ + Computed: true, + ElementType: types.StringType, + Description: "Result of the applied default_tags and resource tags", + }, + }, + Blocks: map[string]schema.Block{ + "template_section": common.TemplateSectionBlock(), + }, + } +} + +func (r *Cluster) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + config, ok := req.ProviderData.(*config.Provider) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Cluster Configure Type", + fmt.Sprintf("Expected *config.Provider, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.controller = config.Controller + r.defaultTags = config.DefaultTags +} + +func (r *Cluster) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + var data *ClusterModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + // convert tags from terraform elements to Go types + var tags common.Tags + + element, err := data.Tags.ToTerraformValue(ctx) + if err != nil { + log.Print("[DEBUG] ToTerraformValue tags err: ", err) + } + err = element.As(&tags) + if err != nil { + log.Print("[DEBUG] As err: ", err) + } + + // build tags_all map content + newtagsAll := make(map[string]string, len(r.defaultTags)) + + // copy default tags map + for k, v := range r.defaultTags { + newtagsAll[k] = v + } + + for k, v := range tags.Elements { + newtagsAll[k] = v + } + + diags := resp.Plan.SetAttribute(ctx, path.Root("tags_all"), newtagsAll) + if len(diags) > 0 { + resp.Diagnostics.Append(diags...) + } +} + +func (r *Cluster) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data *ClusterModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + clusterID, err := r.controller.Clusters().Create(data.Name.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + "Failed to create the cluster", + err.Error(), + ) + return + } + + controller := r.controller.Cluster(clusterID) + + // template management + + tpl := dyn.NewTemplate() + + sectionElements := data.TemplateSection.Elements() + if len(sectionElements) > 0 { + + var templateSection common.TemplateSection + for _, t := range sectionElements { + element, err := t.ToTerraformValue(ctx) + if err != nil { + log.Print("[DEBUG] ToTerraformValue template section err: ", err) + continue + } + err = element.As(&templateSection) + if err != nil { + log.Print("[DEBUG] As err: ", err) + continue + } + } + + vec := tpl.AddVector(strings.ToUpper(templateSection.Name)) + for k, v := range templateSection.Elements { + vec.AddPair(k, v) + } + } + + // add tags + var tags common.Tags + element, err := data.Tags.ToTerraformValue(ctx) + if err != nil { + log.Print("[DEBUG] ToTerraformValue tags err: ", err) + } + + err = element.As(&tags) + if err != nil { + log.Print("[DEBUG] As err: ", err) + } + for k, v := range tags.Elements { + key := strings.ToUpper(k) + tpl.AddPair(key, v) + } + + // add default tags if they aren't overriden + if len(r.defaultTags) > 0 { + for k, v := range r.defaultTags { + key := strings.ToUpper(k) + p, _ := tpl.GetPair(key) + if p != nil { + continue + } + tpl.AddPair(key, v) + } + } + + if len(tpl.Elements) > 0 { + log.Printf("[DEBUG] Cluster update: %s", tpl.String()) + err = controller.Update(tpl.String(), parameters.Merge) + if err != nil { + resp.Diagnostics.AddError( + "Failed to update the cluster content", + fmt.Sprintf("cluster (ID: %d): %s", clusterID, err), + ) + return + } + } + + // Convert from the API data model to the Terraform data model + // and set any unknown attribute values. + data.Id = types.StringValue(fmt.Sprint(clusterID)) + + // fill default tags + // TODO: store an intermediary representation of default tags + // instead of converting values back here ? + var diags diag.Diagnostics + var defaultTags map[string]attr.Value + + if len(r.defaultTags) > 0 { + defaultTags = make(map[string]attr.Value) + for k, v := range r.defaultTags { + defaultTags[k] = types.StringValue(v) + } + } + + data.DefaultTags, diags = types.MapValue(types.StringType, defaultTags) + if len(diags) > 0 { + resp.Diagnostics = append(resp.Diagnostics, diags...) + return + } + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *Cluster) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data *ClusterModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + log.Print("[DEBUG] Cluster reading...") + + id64, err := strconv.ParseInt(data.Id.ValueString(), 10, 0) + if err != nil { + resp.Diagnostics.AddError( + "Failed to parse cluster ID", + err.Error(), + ) + return + } + id := int(id64) + clusterInfos, err := r.controller.Cluster(id).Info() + if err != nil { + + if NoExists(err) { + resp.Diagnostics.AddError( + "Failed to update the cluster content", + fmt.Sprintf("cluster (ID: %d): %s", id, err), + ) + log.Printf("[WARN] Removing cluster %s from state because it no longer exists in", data.Name) + return + + } + resp.Diagnostics.AddError( + "Failed retrieve cluster informations", + fmt.Sprintf("cluster (ID: %d): %s", id, err), + ) + return + } + + data.Name = types.StringValue(clusterInfos.Name) + + // read tags + // Retrieve and copy the tags names from the configuration then fill value with thoses from remote cluster + var stateTags common.Tags + element, err := data.Tags.ToTerraformValue(ctx) + if err != nil { + log.Print("[DEBUG] ToTerraformValue state tags err: ", err) + } + err = element.As(&stateTags) + if err != nil { + log.Print("[DEBUG] As err: ", err) + } + + if len(stateTags.Elements) > 0 { + readTags := make(map[string]attr.Value) + + for k, _ := range stateTags.Elements { + v, err := clusterInfos.Template.GetStr(strings.ToUpper(k)) + if err != nil { + continue + } + readTags[k] = types.StringValue(v) + } + + var diags diag.Diagnostics + data.Tags, diags = types.MapValue(types.StringType, readTags) + if len(diags) > 0 { + resp.Diagnostics = append(resp.Diagnostics, diags...) + return + } + } + + //data.TagsAll + //data.TemplateSection + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + +} + +func (r *Cluster) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { +} + +func (r *Cluster) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +} + +func (r *Cluster) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { +} diff --git a/opennebula/framework/example_resource.go b/opennebula/framework/resources/example.go similarity index 99% rename from opennebula/framework/example_resource.go rename to opennebula/framework/resources/example.go index 1429a96b3..28ce04fae 100644 --- a/opennebula/framework/example_resource.go +++ b/opennebula/framework/resources/example.go @@ -1,4 +1,4 @@ -package opennebula +package resources import ( "context" diff --git a/opennebula/framework/resources/helpers.go b/opennebula/framework/resources/helpers.go new file mode 100644 index 000000000..85a5f1419 --- /dev/null +++ b/opennebula/framework/resources/helpers.go @@ -0,0 +1,16 @@ +package resources + +import "github.com/OpenNebula/one/src/oca/go/src/goca/errors" + +// NoExists indicate if an entity exists in checking the error code returned from an Info call +func NoExists(err error) bool { + + respErr, ok := err.(*errors.ResponseError) + + // expected case, the entity does not exists so we doesn't return an error + if ok && respErr.Code == errors.OneNoExistsError { + return true + } + + return false +} diff --git a/opennebula/framework/tags.go b/opennebula/framework/tags.go deleted file mode 100644 index 111c5bcc3..000000000 --- a/opennebula/framework/tags.go +++ /dev/null @@ -1,35 +0,0 @@ -package opennebula - -import "github.com/hashicorp/terraform-plugin-go/tftypes" - -type Tags struct { - elements map[string]string -} - -func (t *Tags) FromTerraform5Value(val tftypes.Value) error { - - v := map[string]tftypes.Value{} - err := val.As(&v) - if err != nil { - return err - } - - tmpTags := make(map[string]tftypes.Value) - - err = v["tags"].As(&tmpTags) - if err != nil { - return err - } - - t.elements = make(map[string]string) - - for k, v := range tmpTags { - if v.Type().Is(tftypes.String) { - value := "" - v.As(&value) - t.elements[k] = value - } - } - - return nil -} diff --git a/opennebula/framework/mutexkv.go b/opennebula/framework/utils/mutexkv.go similarity index 99% rename from opennebula/framework/mutexkv.go rename to opennebula/framework/utils/mutexkv.go index e900b037f..46bc91782 100644 --- a/opennebula/framework/mutexkv.go +++ b/opennebula/framework/utils/mutexkv.go @@ -1,4 +1,4 @@ -package opennebula +package utils import ( "fmt"