From 4904bae85a39dc19008401808208e62fb17ef025 Mon Sep 17 00:00:00 2001 From: Anton Aksola Date: Fri, 22 Oct 2021 13:09:49 +0200 Subject: [PATCH 1/6] feat(resource): implement managed database resource types --- go.mod | 2 +- go.sum | 124 +- internal/server/networking.go | 4 +- internal/server/server.go | 9 +- internal/storage/storage.go | 2 +- internal/utils/utils.go | 2 +- internal/utils/utils_test.go | 2 +- upcloud/config.go | 6 +- upcloud/datasource_upcloud_hosts.go | 2 +- upcloud/datasource_upcloud_ip_addresses.go | 2 +- upcloud/datasource_upcloud_networks.go | 9 +- upcloud/datasource_upcloud_tags.go | 2 +- upcloud/datasource_upcloud_zone.go | 7 +- upcloud/datasource_upcloud_zones.go | 4 +- upcloud/provider.go | 27 +- upcloud/resource_upcloud_firewall_rules.go | 6 +- .../resource_upcloud_firewall_rules_test.go | 6 +- .../resource_upcloud_floating_ip_address.go | 6 +- ...source_upcloud_floating_ip_address_test.go | 2 +- upcloud/resource_upcloud_managed_database.go | 694 ++++ ...cloud_managed_database_logical_database.go | 202 ++ ..._managed_database_logical_database_test.go | 44 + .../resource_upcloud_managed_database_test.go | 139 + .../resource_upcloud_managed_database_user.go | 229 ++ ...urce_upcloud_managed_database_user_test.go | 65 + upcloud/resource_upcloud_network.go | 6 +- upcloud/resource_upcloud_objectstorage.go | 6 +- .../resource_upcloud_objectstorage_test.go | 6 +- upcloud/resource_upcloud_router.go | 4 +- upcloud/resource_upcloud_router_test.go | 6 +- upcloud/resource_upcloud_server.go | 13 +- upcloud/resource_upcloud_storage.go | 13 +- upcloud/resource_upcloud_storage_test.go | 6 +- upcloud/resource_upcloud_tag.go | 6 +- upcloud/resource_upcloud_tag_test.go | 4 +- upcloud/upcloudschema/managed_database.go | 41 + .../managed_database_service_types.json | 2964 +++++++++++++++++ upcloud/upcloudschema/terraform.go | 93 + upcloud/upcloudschema/terraform_test.go | 28 + 39 files changed, 4706 insertions(+), 87 deletions(-) create mode 100644 upcloud/resource_upcloud_managed_database.go create mode 100644 upcloud/resource_upcloud_managed_database_logical_database.go create mode 100644 upcloud/resource_upcloud_managed_database_logical_database_test.go create mode 100644 upcloud/resource_upcloud_managed_database_test.go create mode 100644 upcloud/resource_upcloud_managed_database_user.go create mode 100644 upcloud/resource_upcloud_managed_database_user_test.go create mode 100644 upcloud/upcloudschema/managed_database.go create mode 100644 upcloud/upcloudschema/managed_database_service_types.json create mode 100644 upcloud/upcloudschema/terraform.go create mode 100644 upcloud/upcloudschema/terraform_test.go diff --git a/go.mod b/go.mod index 1089ac22..260212ce 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/UpCloudLtd/terraform-provider-upcloud go 1.16 require ( - github.com/UpCloudLtd/upcloud-go-api v0.0.0-20210525102709-661419a1956b + github.com/UpCloudLtd/upcloud-go-api/v4 v4.1.2 github.com/agext/levenshtein v1.2.3 // indirect github.com/fatih/color v1.10.0 // indirect github.com/google/uuid v1.2.0 // indirect diff --git a/go.sum b/go.sum index ec894456..d16efb4a 100644 --- a/go.sum +++ b/go.sum @@ -37,14 +37,15 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= -github.com/UpCloudLtd/upcloud-go-api v0.0.0-20210525102709-661419a1956b h1:WDp7g7xKKvXJvfEPicT8A7dLwMDgEN5cb5fgnsgvzJw= -github.com/UpCloudLtd/upcloud-go-api v0.0.0-20210525102709-661419a1956b/go.mod h1:nKv1Y0cTJTGYSd3lEcFfEnbPPiIj3gT4lJzV0ZfTlpQ= +github.com/UpCloudLtd/upcloud-go-api/v4 v4.1.2 h1:cBavAg71YonD59yI7JPnPrwu79wqT8RhfChWiS0+WoA= +github.com/UpCloudLtd/upcloud-go-api/v4 v4.1.2/go.mod h1:yGgbryc2wCR6fzfRCMnH6ZrjWPfZjKCWaJDnp4IameY= 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= @@ -81,12 +82,16 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn 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/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 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/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY= -github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= 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= @@ -111,8 +116,14 @@ github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 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/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -235,6 +246,52 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.10.0 h1:4EYhlDVEMsJ30nNj0mmgwIUXoq7e9sMJrVC2ED6QlCU= +github.com/jackc/pgconn v1.10.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.8.1 h1:9k0IXtdJXHJbyAWQgbWr1lU+MEhPXZz6RIXxfR5oxXs= +github.com/jackc/pgtype v1.8.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.13.0 h1:JCjhT5vmhMAf/YwBHLvrBn4OGdIQBiFG6ym8Zmdx570= +github.com/jackc/pgx/v4 v4.13.0/go.mod h1:9P4X524sErlaxj0XSGZk7s+LD0eOyu1ZDUrrpznYDF0= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 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= @@ -263,23 +320,33 @@ github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02 github.com/klauspost/cpuid/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI= github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 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 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= @@ -321,6 +388,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= 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= @@ -337,12 +405,18 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 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/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= @@ -350,6 +424,7 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9 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/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 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= @@ -376,24 +451,42 @@ github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUA github.com/zclconf/go-cty v1.8.4 h1:pwhhz5P+Fjxse7S7UriBrMu6AUJSZM5pKqGem1PjGAs= github.com/zclconf/go-cty v1.8.4/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= 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-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= 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= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -441,6 +534,7 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -480,16 +574,20 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h 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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -513,8 +611,10 @@ golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7w 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 h1:RX8C8PRZc2hTIod4ds8ij+/4RQX3AqhYj3uOHmyaz4E= golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -522,9 +622,11 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= 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/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -536,14 +638,18 @@ golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -551,6 +657,7 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -569,6 +676,8 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= @@ -665,6 +774,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= diff --git a/internal/server/networking.go b/internal/server/networking.go index e8d25979..c6f645e0 100644 --- a/internal/server/networking.go +++ b/internal/server/networking.go @@ -3,8 +3,8 @@ package server import ( "fmt" - "github.com/UpCloudLtd/upcloud-go-api/upcloud" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/request" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) diff --git a/internal/server/server.go b/internal/server/server.go index dffdfdc3..4906648a 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -5,12 +5,13 @@ import ( "log" "time" - "github.com/UpCloudLtd/terraform-provider-upcloud/internal/storage" - "github.com/UpCloudLtd/upcloud-go-api/upcloud" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/request" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" "github.com/hashicorp/go-uuid" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/UpCloudLtd/terraform-provider-upcloud/internal/storage" ) func BuildServerOpts(d *schema.ResourceData, meta interface{}) (*request.CreateServerRequest, error) { diff --git a/internal/storage/storage.go b/internal/storage/storage.go index 2ce91971..9d03e9b5 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -1,7 +1,7 @@ package storage import ( - "github.com/UpCloudLtd/upcloud-go-api/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 26352467..773285cd 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -4,7 +4,7 @@ import ( "strings" "time" - "github.com/UpCloudLtd/upcloud-go-api/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" ) func FilterZoneIds(vs []upcloud.Zone, f func(upcloud.Zone) bool) []string { diff --git a/internal/utils/utils_test.go b/internal/utils/utils_test.go index 5ac9af23..af2d57eb 100644 --- a/internal/utils/utils_test.go +++ b/internal/utils/utils_test.go @@ -5,7 +5,7 @@ import ( "regexp" "testing" - "github.com/UpCloudLtd/upcloud-go-api/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" "github.com/stretchr/testify/assert" ) diff --git a/upcloud/config.go b/upcloud/config.go index e3297d6e..9440542a 100644 --- a/upcloud/config.go +++ b/upcloud/config.go @@ -5,9 +5,9 @@ import ( "log" "time" - "github.com/UpCloudLtd/upcloud-go-api/upcloud" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/client" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/client" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" ) type Config struct { diff --git a/upcloud/datasource_upcloud_hosts.go b/upcloud/datasource_upcloud_hosts.go index 48a8ba1c..ffeee7b2 100644 --- a/upcloud/datasource_upcloud_hosts.go +++ b/upcloud/datasource_upcloud_hosts.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) diff --git a/upcloud/datasource_upcloud_ip_addresses.go b/upcloud/datasource_upcloud_ip_addresses.go index d94d4f7e..df9af352 100644 --- a/upcloud/datasource_upcloud_ip_addresses.go +++ b/upcloud/datasource_upcloud_ip_addresses.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) diff --git a/upcloud/datasource_upcloud_networks.go b/upcloud/datasource_upcloud_networks.go index 661f071f..500bbf2b 100644 --- a/upcloud/datasource_upcloud_networks.go +++ b/upcloud/datasource_upcloud_networks.go @@ -5,12 +5,13 @@ import ( "regexp" "time" - "github.com/UpCloudLtd/terraform-provider-upcloud/internal/utils" - "github.com/UpCloudLtd/upcloud-go-api/upcloud" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/request" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/UpCloudLtd/terraform-provider-upcloud/internal/utils" ) func dataSourceNetworks() *schema.Resource { diff --git a/upcloud/datasource_upcloud_tags.go b/upcloud/datasource_upcloud_tags.go index 9e0567bd..f053d9e0 100644 --- a/upcloud/datasource_upcloud_tags.go +++ b/upcloud/datasource_upcloud_tags.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) diff --git a/upcloud/datasource_upcloud_zone.go b/upcloud/datasource_upcloud_zone.go index 5880a04c..c672a944 100644 --- a/upcloud/datasource_upcloud_zone.go +++ b/upcloud/datasource_upcloud_zone.go @@ -4,11 +4,12 @@ import ( "context" "fmt" - "github.com/UpCloudLtd/terraform-provider-upcloud/internal/utils" - "github.com/UpCloudLtd/upcloud-go-api/upcloud" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/UpCloudLtd/terraform-provider-upcloud/internal/utils" ) func dataSourceUpCloudZone() *schema.Resource { diff --git a/upcloud/datasource_upcloud_zones.go b/upcloud/datasource_upcloud_zones.go index 402c164c..7e4a5b35 100644 --- a/upcloud/datasource_upcloud_zones.go +++ b/upcloud/datasource_upcloud_zones.go @@ -6,8 +6,8 @@ import ( "time" "github.com/UpCloudLtd/terraform-provider-upcloud/internal/utils" - "github.com/UpCloudLtd/upcloud-go-api/upcloud" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" diff --git a/upcloud/provider.go b/upcloud/provider.go index 8b9b897a..ef1bfa9d 100644 --- a/upcloud/provider.go +++ b/upcloud/provider.go @@ -8,11 +8,12 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/UpCloudLtd/terraform-provider-upcloud/internal/config" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/client" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/client" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/UpCloudLtd/terraform-provider-upcloud/internal/config" + retryablehttp "github.com/hashicorp/go-retryablehttp" ) @@ -56,14 +57,18 @@ func Provider() *schema.Provider { }, ResourcesMap: map[string]*schema.Resource{ - "upcloud_server": resourceUpCloudServer(), - "upcloud_router": resourceUpCloudRouter(), - "upcloud_storage": resourceUpCloudStorage(), - "upcloud_firewall_rules": resourceUpCloudFirewallRules(), - "upcloud_tag": resourceUpCloudTag(), - "upcloud_network": resourceUpCloudNetwork(), - "upcloud_floating_ip_address": resourceUpCloudFloatingIPAddress(), - "upcloud_object_storage": resourceUpCloudObjectStorage(), + "upcloud_server": resourceUpCloudServer(), + "upcloud_router": resourceUpCloudRouter(), + "upcloud_storage": resourceUpCloudStorage(), + "upcloud_firewall_rules": resourceUpCloudFirewallRules(), + "upcloud_tag": resourceUpCloudTag(), + "upcloud_network": resourceUpCloudNetwork(), + "upcloud_floating_ip_address": resourceUpCloudFloatingIPAddress(), + "upcloud_object_storage": resourceUpCloudObjectStorage(), + "upcloud_managed_database_postgresql": resourceUpCloudManagedDatabasePostgreSQL(), + "upcloud_managed_database_mysql": resourceUpCloudManagedDatabaseMySQL(), + "upcloud_managed_database_user": resourceUpCloudManagedDatabaseUser(), + "upcloud_managed_database_logical_database": resourceUpCloudManagedDatabaseLogicalDatabase(), }, DataSourcesMap: map[string]*schema.Resource{ diff --git a/upcloud/resource_upcloud_firewall_rules.go b/upcloud/resource_upcloud_firewall_rules.go index 025b4690..8d089724 100644 --- a/upcloud/resource_upcloud_firewall_rules.go +++ b/upcloud/resource_upcloud_firewall_rules.go @@ -5,9 +5,9 @@ import ( "strconv" "time" - "github.com/UpCloudLtd/upcloud-go-api/upcloud" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/request" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" diff --git a/upcloud/resource_upcloud_firewall_rules_test.go b/upcloud/resource_upcloud_firewall_rules_test.go index 8b1fdd44..37a6d69f 100644 --- a/upcloud/resource_upcloud_firewall_rules_test.go +++ b/upcloud/resource_upcloud_firewall_rules_test.go @@ -4,9 +4,9 @@ import ( "fmt" "testing" - "github.com/UpCloudLtd/upcloud-go-api/upcloud" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/request" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" diff --git a/upcloud/resource_upcloud_floating_ip_address.go b/upcloud/resource_upcloud_floating_ip_address.go index d694f353..702991c3 100644 --- a/upcloud/resource_upcloud_floating_ip_address.go +++ b/upcloud/resource_upcloud_floating_ip_address.go @@ -3,9 +3,9 @@ package upcloud import ( "context" - "github.com/UpCloudLtd/upcloud-go-api/upcloud" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/request" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" diff --git a/upcloud/resource_upcloud_floating_ip_address_test.go b/upcloud/resource_upcloud_floating_ip_address_test.go index 3bbecc49..0b03bb57 100644 --- a/upcloud/resource_upcloud_floating_ip_address_test.go +++ b/upcloud/resource_upcloud_floating_ip_address_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" diff --git a/upcloud/resource_upcloud_managed_database.go b/upcloud/resource_upcloud_managed_database.go new file mode 100644 index 00000000..9a0915d9 --- /dev/null +++ b/upcloud/resource_upcloud_managed_database.go @@ -0,0 +1,694 @@ +package upcloud + +import ( + "context" + "fmt" + "log" + "strings" + "time" + + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + + "github.com/UpCloudLtd/terraform-provider-upcloud/upcloud/upcloudschema" +) + +const ( + managedDatabaseTypePostgreSQL = upcloud.ManagedDatabaseServiceTypePostgreSQL + managedDatabaseTypeMySQL = upcloud.ManagedDatabaseServiceTypeMySQL +) + +var resourceUpcloudManagedDatabaseModifiableStates = []upcloud.ManagedDatabaseState{ + upcloud.ManagedDatabaseStateRunning, + upcloud.ManagedDatabaseState("rebalancing"), +} + +func resourceUpCloudManagedDatabasePostgreSQL() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceUpCloudManagedDatabaseCreate(managedDatabaseTypePostgreSQL), + ReadContext: resourceUpCloudManagedDatabaseRead, + UpdateContext: resourceUpCloudManagedDatabaseUpdate, + DeleteContext: resourceUpCloudManagedDatabaseDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: buildSchemaForUpCloudManagedDatabase(schemaUpcloudManagedDatabaseEngine(managedDatabaseTypePostgreSQL)), + } +} + +func resourceUpCloudManagedDatabaseMySQL() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceUpCloudManagedDatabaseCreate(managedDatabaseTypeMySQL), + ReadContext: resourceUpCloudManagedDatabaseRead, + UpdateContext: resourceUpCloudManagedDatabaseUpdate, + DeleteContext: resourceUpCloudManagedDatabaseDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: buildSchemaForUpCloudManagedDatabase(schemaUpcloudManagedDatabaseEngine(managedDatabaseTypeMySQL)), + } +} + +func schemaUpcloudManagedDatabaseEngine(serviceType upcloud.ManagedDatabaseServiceType) map[string]*schema.Schema { + switch serviceType { + case managedDatabaseTypePostgreSQL: + return map[string]*schema.Schema{ + "primary_database": { + Description: "Primary database name", + Type: schema.TypeString, + Computed: true, + }, + "properties": { + Description: "Database Engine properties for PostgreSQL", + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{Schema: buildManagedDatabasePropertiesSchema(serviceType)}, + }, + "sslmode": { + Description: "SSL Connection Mode for PostgreSQL", + Type: schema.TypeString, + Computed: true, + }, + } + case managedDatabaseTypeMySQL: + return map[string]*schema.Schema{ + "primary_database": { + Description: "Primary database name", + Type: schema.TypeString, + Computed: true, + }, + "properties": { + Description: "Database Engine properties for MySQL", + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{Schema: buildManagedDatabasePropertiesSchema(serviceType)}, + }, + } + } + return nil +} + +func schemaUpCloudManagedDatabaseCommon() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "name": { + Description: "Name of the service. The name is used as a prefix for the logical hostname. Must be unique within an account", + Type: schema.TypeString, + ForceNew: true, + Required: true, + ValidateFunc: validation.StringLenBetween(3, 30), + }, + "components": { + Description: "Service component information", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "component": { + Description: "Type of the component", + Type: schema.TypeString, + Computed: true, + }, + "host": { + Description: "Hostname of the component", + Type: schema.TypeString, + Computed: true, + }, + "port": { + Description: "Port number of the component", + Type: schema.TypeInt, + Computed: true, + }, + "route": { + Description: "Component network route type", + Type: schema.TypeString, + Computed: true, + }, + "usage": { + Description: "Usage of the component", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "maintenance_window_dow": { + Description: "Maintenance window day of week. Lower case weekday name (monday, tuesday, ...)", + Type: schema.TypeString, + Computed: true, + Optional: true, + ValidateDiagFunc: func(i interface{}, path cty.Path) diag.Diagnostics { + validDays := map[string]struct{}{ + "monday": {}, "tuesday": {}, "wednesday": {}, "thursday": {}, "friday": {}, "saturday": {}, "sunday": {}, + } + if _, ok := validDays[i.(string)]; !ok { + return diag.FromErr(fmt.Errorf("invalid weekday")) + } + return nil + }, + }, + "maintenance_window_time": { + Description: "Maintenance window UTC time in hh:mm:ss format", + Type: schema.TypeString, + Computed: true, + Optional: true, + ValidateDiagFunc: func(i interface{}, path cty.Path) diag.Diagnostics { + if _, err := time.Parse("03:04:05", i.(string)); err != nil { + return diag.FromErr(fmt.Errorf("invalid time")) + } + return nil + }, + }, + "node_states": { + Description: "Information about nodes providing the managed service", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Description: "Name plus a node iteration", + Type: schema.TypeString, + Computed: true, + }, + "role": { + Description: "Role of the node", + Type: schema.TypeString, + Computed: true, + }, + "state": { + Description: "State of the node", + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "plan": { + Description: "Service plan to use. This determines how much resources the instance will have", + Type: schema.TypeString, + Required: true, + }, + "powered": { + Description: "The administrative power state of the service", + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "service_uri": { + Description: "URI to the service instance", + Type: schema.TypeString, + Computed: true, + Sensitive: true, + }, + "service_host": { + Description: "Hostname to the service instance", + Type: schema.TypeString, + Computed: true, + }, + "service_port": { + Description: "Port to the service instance", + Type: schema.TypeString, + Computed: true, + }, + "service_username": { + Description: "Primary username to the service instance", + Type: schema.TypeString, + Computed: true, + }, + "service_password": { + Description: "Primary username's password to the service instance", + Type: schema.TypeString, + Computed: true, + Sensitive: true, + }, + "state": { + Description: "State of the service", + Type: schema.TypeString, + Computed: true, + }, + "title": { + Description: "Title of a managed database instance", + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(0, 255), + }, + "type": { + Description: "Type of the service", + Type: schema.TypeString, + Computed: true, + }, + "zone": { + Description: "Zone where the instance resides", + Type: schema.TypeString, + Required: true, + }, + } +} + +func buildSchemaForUpCloudManagedDatabase(engineSchema map[string]*schema.Schema) map[string]*schema.Schema { + serviceSchema := schemaUpCloudManagedDatabaseCommon() + for k, v := range engineSchema { + serviceSchema[k] = v + } + return serviceSchema +} + +func resourceUpCloudManagedDatabaseCreate(serviceType upcloud.ManagedDatabaseServiceType) schema.CreateContextFunc { + return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*service.Service) + + if err := d.Set("type", string(serviceType)); err != nil { + return diag.FromErr(err) + } + + req := request.CreateManagedDatabaseRequest{ + HostNamePrefix: d.Get("name").(string), + Plan: d.Get("plan").(string), + Title: d.Get("title").(string), + Type: serviceType, + Zone: d.Get("zone").(string), + } + + if d.HasChange("properties.0") { + req.Properties = buildManagedDatabasePropertiesFromResourceData( + d, + upcloudschema.ManagedDatabaseServicePropertiesSchema(serviceType), + "properties", "0", + ) + } + + details, err := client.CreateManagedDatabase(&req) + if err != nil { + return diag.FromErr(err) + } + d.SetId(details.UUID) + log.Printf("[INFO] managed database %v (%v) created", details.UUID, d.Get("name")) + + return resourceUpCloudManagedDatabaseUpdate(ctx, d, meta) + } +} + +func resourceUpCloudManagedDatabaseRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*service.Service) + + req := request.GetManagedDatabaseRequest{UUID: d.Id()} + details, err := client.GetManagedDatabase(&req) + if err != nil { + return diag.FromErr(err) + } + log.Printf("[DEBUG] managed database %v (%v) read", d.Id(), d.Get("name")) + return copyManagedDatabaseDetailsToResourceData(d, details) +} + +func resourceUpCloudManagedDatabaseUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*service.Service) + + var updated bool + + // Skip modifying these changes if are creating a new resource + if !d.IsNewResource() && d.HasChanges("plan", "title", "zone", + "maintenance_window_dow", "maintenance_window_time", "properties.0") { + req := request.ModifyManagedDatabaseRequest{UUID: d.Id()} + req.Plan = d.Get("plan").(string) + req.Title = d.Get("title").(string) + req.Zone = d.Get("zone").(string) + if d.HasChange("maintenance_window_dow") { + req.Maintenance.DayOfWeek = d.Get("maintenance_window_dow").(string) + } + if d.HasChange("maintenance_window_time") { + req.Maintenance.Time = d.Get("maintenance_window_time").(string) + } + if d.HasChange("properties.0") { + req.Properties = buildManagedDatabasePropertiesFromResourceData( + d, + upcloudschema.ManagedDatabaseServicePropertiesSchema(upcloud.ManagedDatabaseServiceType(d.Get("type").(string))), + "properties", "0", + ) + } + + _, err := client.ModifyManagedDatabase(&req) + if err != nil { + return diag.FromErr(err) + } + updated = true + } + + // Weirdly, if this is a new resource then we need to evaluate the power state as the defaults are ignored + // in terraform in create phase. + if d.IsNewResource() || d.HasChange("powered") { + doPowerOn := !d.IsNewResource() && d.Get("powered").(bool) + doPowerOff := !d.Get("powered").(bool) + if doPowerOn { + _, err := client.StartManagedDatabase(&request.StartManagedDatabaseRequest{UUID: d.Id()}) + if err != nil { + return diag.FromErr(err) + } + log.Printf("[INFO] managed database %v (%v) is powered on", d.Id(), d.Get("name")) + updated = true + } + if doPowerOff { + _, err := client.ShutdownManagedDatabase(&request.ShutdownManagedDatabaseRequest{UUID: d.Id()}) + if err != nil { + return diag.FromErr(err) + } + log.Printf("[INFO] managed database %v (%v) is powered off", d.Id(), d.Get("name")) + updated = true + } + } + + if updated { + log.Printf("[INFO] managed database %v (%v) updated", d.Id(), d.Get("name")) + } + + return resourceUpCloudManagedDatabaseRead(ctx, d, meta) +} + +func resourceUpCloudManagedDatabaseDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*service.Service) + + req := request.DeleteManagedDatabaseRequest{UUID: d.Id()} + if err := client.DeleteManagedDatabase(&req); err != nil { + return diag.FromErr(err) + } + log.Printf("[INFO] managed database %v (%v) deleted", req.UUID, d.Get("name")) + + return nil +} + +func buildManagedDatabaseSubResourceID(serviceID, subResourceID string) string { + return fmt.Sprintf("%s/%s", serviceID, subResourceID) +} + +func splitManagedDatabaseSubResourceID(packed string) (serviceID string, subResourceID string) { + parts := strings.SplitN(packed, "/", 2) + serviceID = parts[0] + if len(parts) > 1 { + subResourceID = parts[1] + } + return serviceID, subResourceID +} + +func copyManagedDatabaseDetailsToResourceData(d *schema.ResourceData, details *upcloud.ManagedDatabase) diag.Diagnostics { + convertComponents := func(details *upcloud.ManagedDatabase) (r []map[string]interface{}) { + for _, comp := range details.Components { + r = append(r, map[string]interface{}{ + "component": comp.Component, + "host": comp.Host, + "port": comp.Port, + "route": comp.Route, + "usage": comp.Usage, + }) + } + return r + } + convertNodes := func(details *upcloud.ManagedDatabase) (r []map[string]interface{}) { + for _, node := range details.NodeStates { + r = append(r, map[string]interface{}{ + "name": node.Name, + "role": node.Role, + "state": node.State, + }) + } + return r + } + type sf struct { + name string + val interface{} + } + setFields := []sf{ + {name: "name", val: details.Name}, + {name: "components", val: convertComponents(details)}, + {name: "maintenance_window_dow", val: details.Maintenance.DayOfWeek}, + {name: "maintenance_window_time", val: details.Maintenance.Time}, + {name: "node_states", val: convertNodes(details)}, + {name: "plan", val: details.Plan}, + {name: "service_uri", val: details.ServiceURI}, + {name: "service_host", val: details.ServiceURIParams.Host}, + {name: "service_port", val: details.ServiceURIParams.Port}, + {name: "service_username", val: details.ServiceURIParams.User}, + {name: "service_password", val: details.ServiceURIParams.Password}, + {name: "state", val: string(details.State)}, + {name: "title", val: details.Title}, + {name: "zone", val: details.Zone}, + {name: "powered", val: details.Powered}, + } + + switch d.Get("type") { + case "pg": + setFields = append(setFields, + sf{name: "primary_database", val: details.ServiceURIParams.DatabaseName}, + sf{name: "sslmode", val: details.ServiceURIParams.SSLMode}) + case "mysql": + setFields = append(setFields, + sf{name: "primary_database", val: details.ServiceURIParams.DatabaseName}) + } + + for _, sf := range setFields { + if err := d.Set(sf.name, sf.val); err != nil { + return diag.FromErr(err) + } + } + + if len(details.Properties) > 0 { + props := make(map[string]interface{}) + for k, v := range details.Properties { + props[string(k)] = v + } + newProps, err := buildManagedDatabasePropertiesResourceDataFromAPIProperties(props, + upcloudschema.ManagedDatabaseServicePropertiesSchema( + upcloud.ManagedDatabaseServiceType(d.Get("type").(string)))) + if err != nil { + return diag.FromErr(err) + } + + if err := d.Set("properties", []interface{}{newProps}); err != nil { + return diag.FromErr(err) + } + } + + return nil +} + +func resourceUpCloudManagedDatabaseWaitState( + ctx context.Context, + id string, + m interface{}, + timeout time.Duration, + targetStates ...upcloud.ManagedDatabaseState, +) (*upcloud.ManagedDatabase, error) { + client := m.(*service.Service) + refresher := func() (result interface{}, state string, err error) { + resp, err := client.GetManagedDatabase(&request.GetManagedDatabaseRequest{UUID: id}) + if err != nil { + return nil, "", err + } + return resp, string(resp.State), nil + } + res, state, err := refresher() + if err != nil { + return nil, err + } + if len(targetStates) == 0 { + return res.(*upcloud.ManagedDatabase), nil + } + for _, targetState := range targetStates { + if upcloud.ManagedDatabaseState(state) == targetState { + return res.(*upcloud.ManagedDatabase), nil + } + } + states := make([]string, 0, len(targetStates)) + for _, targetState := range targetStates { + states = append(states, string(targetState)) + } + waiter := resource.StateChangeConf{ + Delay: 1 * time.Second, + Refresh: refresher, + Target: states, + Timeout: timeout, + MinTimeout: 2 * time.Second, + } + res, err = waiter.WaitForStateContext(ctx) + if err != nil { + return nil, err + } + return res.(*upcloud.ManagedDatabase), nil +} + +func buildManagedDatabasePropertiesResourceDataFromAPIProperties( + apiProps map[string]interface{}, + propertiesSchema map[string]interface{}, +) (map[string]interface{}, error) { + r := make(map[string]interface{}) + for k, iv := range apiProps { + propertySchema := propertiesSchema[k].(map[string]interface{}) + var newValue interface{} + switch propertySchema["type"] { + //nolint // not really necessary to make these constants + case "object": + if _, ok := iv.(map[string]interface{}); !ok { + return nil, fmt.Errorf("invalid api response for property %q (not an object)", k) + } + objectProperties := propertySchema["properties"].(map[string]interface{}) + subProps, err := buildManagedDatabasePropertiesResourceDataFromAPIProperties(iv.(map[string]interface{}), objectProperties) + if err != nil { + return nil, err + } + newValue = []interface{}{subProps} + default: + newValue = iv + } + r[k] = newValue + } + return r, nil +} + +func buildManagedDatabasePropertiesFromResourceData( + d *schema.ResourceData, + propertiesSchema map[string]interface{}, + keyPath ...string, +) map[upcloud.ManagedDatabasePropertyKey]interface{} { + resourceProps := d.Get(strings.Join(keyPath, ".")).(map[string]interface{}) + r := make(map[upcloud.ManagedDatabasePropertyKey]interface{}) + for k, iv := range resourceProps { + if !d.HasChange(strings.Join(append(keyPath, k), ".")) { + continue + } + propertySchema := propertiesSchema[k].(map[string]interface{}) + var setValue interface{} + switch v := iv.(type) { + case []interface{}: + if propertySchema["type"] == "object" { + setValue = make(map[upcloud.ManagedDatabasePropertyKey]interface{}) + if len(v) > 0 { + objectProperties := propertySchema["properties"].(map[string]interface{}) + setValue = buildManagedDatabasePropertiesFromResourceData(d, objectProperties, append(keyPath, k, "0")...) + } + } else { + setValue = v + } + default: + setValue = v + } + r[upcloud.ManagedDatabasePropertyKey(k)] = setValue + } + return r +} + +func composeManagedDatabasePropertiesOverrideChain(fns ...upcloudschema.FnGenerateTerraformSchemaOverride) upcloudschema.FnGenerateTerraformSchemaOverride { + return func(keyPath []string, proposedSchema *schema.Schema, source map[string]interface{}) { + for _, fn := range fns { + fn(keyPath, proposedSchema, source) + } + } +} + +func overrideManagedDatabasePropertiesAllOptional() upcloudschema.FnGenerateTerraformSchemaOverride { + return func(keyPath []string, proposedSchema *schema.Schema, source map[string]interface{}) { + proposedSchema.Optional = true + } +} + +func overrideManagedDatabasePropertiesComputed() upcloudschema.FnGenerateTerraformSchemaOverride { + return func(keyPath []string, proposedSchema *schema.Schema, source map[string]interface{}) { + if proposedSchema.Default == nil && proposedSchema.DefaultFunc == nil { + proposedSchema.Computed = true + } + } +} +func overrideManagedDatabasePropertiesDefaults() upcloudschema.FnGenerateTerraformSchemaOverride { + return func(keyPath []string, proposedSchema *schema.Schema, source map[string]interface{}) { + def, ok := source["default"] + if !ok { + return + } + switch source["type"] { + case "object", "array": + return + case "integer": + def = int(def.(float64)) + default: + } + proposedSchema.Default = def + } +} + +func overrideManagedDatabasePropertiesIPFilter() upcloudschema.FnGenerateTerraformSchemaOverride { + return func(keyPath []string, proposedSchema *schema.Schema, source map[string]interface{}) { + if len(keyPath) > 0 && keyPath[len(keyPath)-1] == "ip_filter" { + prev := proposedSchema.DiffSuppressFunc + proposedSchema.DiffSuppressFunc = func(k, old, new string, d *schema.ResourceData) bool { + if prev != nil && prev(k, old, new, d) { + return true + } + if strings.TrimSuffix(old, "/32") == strings.TrimSuffix(new, "/32") { + return true + } + return false + } + } + } +} + +func overrideManagedDatabasePropertiesMarkSensitive() upcloudschema.FnGenerateTerraformSchemaOverride { + patterns := []string{"password"} + return func(keyPath []string, proposedSchema *schema.Schema, source map[string]interface{}) { + for _, pat := range patterns { + if strings.Contains(keyPath[len(keyPath)-1], pat) { + proposedSchema.Sensitive = true + } + } + } +} + +func overrideManagedDatabasePropertiesCreateOnly() upcloudschema.FnGenerateTerraformSchemaOverride { + return func(keyPath []string, proposedSchema *schema.Schema, source map[string]interface{}) { + if v, ok := source["createOnly"].(bool); ok && v { + proposedSchema.DiffSuppressFunc = func(k, old, new string, d *schema.ResourceData) bool { + return d.Id() != "" + } + } + } +} + +func overrideManagedDatabasePropertiesIgnoreClearing() upcloudschema.FnGenerateTerraformSchemaOverride { + return func(keyPath []string, proposedSchema *schema.Schema, source map[string]interface{}) { + prev := proposedSchema.DiffSuppressFunc + proposedSchema.DiffSuppressFunc = func(k, old, new string, d *schema.ResourceData) bool { + if prev != nil && prev(k, old, new, d) { + return true + } + if proposedSchema.Default != nil || proposedSchema.DefaultFunc != nil { + return false + } + if old != "" && new == "" { + log.Printf("[DEBUG] ignoring diff for %s (schema=%+v source=%+v)", k, proposedSchema, source) + return true + } + + return false + } + } +} + +func buildManagedDatabasePropertiesSchema(serviceType upcloud.ManagedDatabaseServiceType) map[string]*schema.Schema { + jsonSchema := upcloudschema.ManagedDatabaseServicePropertiesSchema(serviceType) + overrides := composeManagedDatabasePropertiesOverrideChain( + overrideManagedDatabasePropertiesAllOptional(), + overrideManagedDatabasePropertiesIPFilter(), + overrideManagedDatabasePropertiesMarkSensitive(), + overrideManagedDatabasePropertiesDefaults(), + overrideManagedDatabasePropertiesCreateOnly(), + overrideManagedDatabasePropertiesComputed(), + overrideManagedDatabasePropertiesIgnoreClearing(), + ) + return upcloudschema.GenerateTerraformSchemaFromJSONSchema(jsonSchema, overrides) +} diff --git a/upcloud/resource_upcloud_managed_database_logical_database.go b/upcloud/resource_upcloud_managed_database_logical_database.go new file mode 100644 index 00000000..f64ce9dc --- /dev/null +++ b/upcloud/resource_upcloud_managed_database_logical_database.go @@ -0,0 +1,202 @@ +package upcloud + +import ( + "context" + "fmt" + "log" + "regexp" + + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceUpCloudManagedDatabaseLogicalDatabase() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceUpCloudManagedDatabaseLogicalDatabaseCreate, + ReadContext: resourceUpCloudManagedDatabaseLogicalDatabaseRead, + DeleteContext: resourceUpCloudManagedDatabaseLogicalDatabaseDelete, + Importer: &schema.ResourceImporter{ + StateContext: func(ctx context.Context, data *schema.ResourceData, i interface{}) ([]*schema.ResourceData, error) { + serviceID, name := splitManagedDatabaseSubResourceID(data.Id()) + if serviceID == "" || name == "" { + return nil, fmt.Errorf("invalid import id. Format: /") + } + if err := data.Set("service", serviceID); err != nil { + return nil, err + } + if err := data.Set("name", name); err != nil { + return nil, err + } + return []*schema.ResourceData{data}, nil + }, + }, + Schema: schemaUpCloudManagedDatabaseLogicalDatabase(), + } +} + +func schemaUpCloudManagedDatabaseLogicalDatabase() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "service": { + Description: "Service's UUID for which this user belongs to", + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": { + Description: "Name of the logical database", + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "character_set": { + Description: "Default character set for the database (LC_CTYPE)", + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateDiagFunc: validateManagedDatabaseLocale, + }, + "collation": { + Description: "Default collation for the database (LC_COLLATE)", + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateDiagFunc: validateManagedDatabaseLocale, + }, + } +} + +func resourceUpCloudManagedDatabaseLogicalDatabaseCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*service.Service) + + serviceID := d.Get("service").(string) + serviceDetails, err := client.GetManagedDatabase(&request.GetManagedDatabaseRequest{UUID: serviceID}) + if err != nil { + return diag.FromErr(err) + } + if !serviceDetails.Powered { + return diag.FromErr(fmt.Errorf("cannot create a logical database while managed database %v (%v) is powered off", serviceDetails.Name, serviceID)) + } + + if d.HasChanges("character_set", "collation") && serviceDetails.Type != upcloud.ManagedDatabaseServiceTypePostgreSQL { + return diag.FromErr(fmt.Errorf("setting character_set or collation is only possible for PostgreSQL service")) + } + + serviceDetails, err = resourceUpCloudManagedDatabaseWaitState(ctx, serviceID, meta, + d.Timeout(schema.TimeoutCreate), resourceUpcloudManagedDatabaseModifiableStates...) + if err != nil { + return diag.FromErr(err) + } + _, err = client.CreateManagedDatabaseLogicalDatabase(&request.CreateManagedDatabaseLogicalDatabaseRequest{ + ServiceUUID: serviceID, + Name: d.Get("name").(string), + LCCType: d.Get("character_set").(string), + LCCollate: d.Get("collation").(string), + }) + if err != nil { + return diag.FromErr(err) + } + d.SetId(buildManagedDatabaseSubResourceID(serviceID, d.Get("name").(string))) + log.Printf("[INFO] managed database logical database %v/%v (%v/%v) created", + serviceDetails.Name, d.Get("name").(string), + serviceID, d.Get("name").(string)) + + return resourceUpCloudManagedDatabaseLogicalDatabaseRead(ctx, d, meta) +} + +func resourceUpCloudManagedDatabaseLogicalDatabaseRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*service.Service) + + serviceID, name := splitManagedDatabaseSubResourceID(d.Id()) + + serviceDetails, err := client.GetManagedDatabase(&request.GetManagedDatabaseRequest{UUID: serviceID}) + if err != nil { + return diag.FromErr(err) + } + + ldbs, err := client.GetManagedDatabaseLogicalDatabases(&request.GetManagedDatabaseLogicalDatabasesRequest{ + ServiceUUID: serviceID, + }) + if err != nil { + return diag.FromErr(err) + } + var details *upcloud.ManagedDatabaseLogicalDatabase + for i, ldb := range ldbs { + if ldb.Name == name { + details = &ldbs[i] + } + } + if details == nil { + return diag.FromErr(fmt.Errorf("logical database %q was not found", name)) + } + + log.Printf("[DEBUG] managed database logical database %v/%v (%v/%v) read", + serviceDetails.Name, name, + serviceID, name) + return copyManagedDatabaseLogicalDatabaseDetailsToResource(d, details) +} + +func resourceUpCloudManagedDatabaseLogicalDatabaseDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*service.Service) + + serviceID := d.Get("service").(string) + serviceDetails, err := client.GetManagedDatabase(&request.GetManagedDatabaseRequest{UUID: serviceID}) + if err != nil { + return diag.FromErr(err) + } + if !serviceDetails.Powered { + return diag.FromErr(fmt.Errorf("cannot delete a logical database while managed database %v (%v) is powered off", serviceDetails.Name, serviceID)) + } + + serviceID, name := splitManagedDatabaseSubResourceID(d.Id()) + serviceDetails, err = resourceUpCloudManagedDatabaseWaitState(ctx, serviceID, meta, + d.Timeout(schema.TimeoutCreate), resourceUpcloudManagedDatabaseModifiableStates...) + if err != nil { + return diag.FromErr(err) + } + + err = client.DeleteManagedDatabaseLogicalDatabase(&request.DeleteManagedDatabaseLogicalDatabaseRequest{ + ServiceUUID: serviceID, + Name: name, + }) + if err != nil { + return diag.FromErr(err) + } + log.Printf("[INFO] managed database logical database %v/%v (%v/%v) deleted", + serviceDetails.Name, name, + serviceID, name) + return nil +} + +func copyManagedDatabaseLogicalDatabaseDetailsToResource(d *schema.ResourceData, details *upcloud.ManagedDatabaseLogicalDatabase) diag.Diagnostics { + setFields := []struct { + name string + val interface{} + }{ + {name: "name", val: details.Name}, + {name: "character_set", val: details.LCCType}, + {name: "collation", val: details.LCCollate}, + } + + for _, sf := range setFields { + if err := d.Set(sf.name, sf.val); err != nil { + return diag.FromErr(err) + } + } + + return nil +} + +func validateManagedDatabaseLocale(i interface{}, path cty.Path) diag.Diagnostics { + s := i.(string) + pat := regexp.MustCompile(`^[a-z]{2}_[A-Z]{2}\.[A-z0-9-]+$`) + if !pat.MatchString(s) { + return diag.FromErr(fmt.Errorf("invalid locale. Must be in form en_US.UTF8 (language_TERRITORY.CODEPOINT)")) + } + return nil +} diff --git a/upcloud/resource_upcloud_managed_database_logical_database_test.go b/upcloud/resource_upcloud_managed_database_logical_database_test.go new file mode 100644 index 00000000..58b46e01 --- /dev/null +++ b/upcloud/resource_upcloud_managed_database_logical_database_test.go @@ -0,0 +1,44 @@ +package upcloud + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func TestAccUpcloudManagedDatabasePostgreSQLLogicalDatabase_CreateUpdate(t *testing.T) { + var providers []*schema.Provider + rNameManagedDatabase := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + rNameLdb := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + resourceIdentifierManagedDatabase := fmt.Sprintf("upcloud_managed_database_postgresql.%s", rNameManagedDatabase) + resourceIdentifierManagedDatabaseLdb := fmt.Sprintf("upcloud_managed_database_logical_database.%s", rNameLdb) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories(&providers), + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(` + resource "upcloud_managed_database_postgresql" "%[1]s" { + name = "%[1]s" + plan = "1x1xCPU-2GB-25GB" + title = "testtitle" + zone = "fi-hel1" + } + resource "upcloud_managed_database_logical_database" "%[2]s" { + service = upcloud_managed_database_postgresql.%[1]s.id + name = "%[2]s" + } + + `, rNameManagedDatabase, rNameLdb), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceIdentifierManagedDatabase, "name", rNameManagedDatabase), + resource.TestCheckResourceAttrSet(resourceIdentifierManagedDatabase, "service_uri"), + resource.TestCheckResourceAttr(resourceIdentifierManagedDatabaseLdb, "name", rNameLdb), + ), + }, + }, + }) +} diff --git a/upcloud/resource_upcloud_managed_database_test.go b/upcloud/resource_upcloud_managed_database_test.go new file mode 100644 index 00000000..508d0d02 --- /dev/null +++ b/upcloud/resource_upcloud_managed_database_test.go @@ -0,0 +1,139 @@ +package upcloud + +import ( + "fmt" + "testing" + + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func TestAccUpcloudManagedDatabasePostgreSQL_CreateUpdate(t *testing.T) { + var providers []*schema.Provider + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + resourceIdentifier := fmt.Sprintf("upcloud_managed_database_postgresql.%s", rName) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories(&providers), + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(` + resource "upcloud_managed_database_postgresql" "%[1]s" { + name = "%[1]s" + plan = "1x1xCPU-2GB-25GB" + title = "testtitle" + zone = "fi-hel1" + + properties { + public_access = true + ip_filter = ["10.0.0.1/32"] + } + }`, rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceIdentifier, "name", rName), + resource.TestCheckResourceAttr(resourceIdentifier, "plan", "1x1xCPU-2GB-25GB"), + resource.TestCheckResourceAttr(resourceIdentifier, "title", "testtitle"), + resource.TestCheckResourceAttr(resourceIdentifier, "zone", "fi-hel1"), + resource.TestCheckResourceAttr(resourceIdentifier, "powered", "true"), + resource.TestCheckResourceAttr(resourceIdentifier, "properties.0.ip_filter.0", "10.0.0.1/32"), + resource.TestCheckResourceAttr(resourceIdentifier, "type", string(upcloud.ManagedDatabaseServiceTypePostgreSQL)), + resource.TestCheckResourceAttrSet(resourceIdentifier, "service_uri"), + ), + }, + { + Config: fmt.Sprintf(` + resource "upcloud_managed_database_postgresql" "%[1]s" { + name = "%[1]s" + plan = "1x1xCPU-2GB-25GB" + title = "testtitle modified" + zone = "fi-hel1" + + properties { + ip_filter = [] + } + }`, rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceIdentifier, "title", "testtitle modified"), + resource.TestCheckResourceAttr(resourceIdentifier, "properties.0.public_access", "false"), + resource.TestCheckResourceAttr(resourceIdentifier, "properties.0.ip_filter.#", "0"), + ), + }, + }, + }) +} + +func TestAccUpcloudManagedDatabasePostgreSQL_CreateAsPoweredOff(t *testing.T) { + var providers []*schema.Provider + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + resourceIdentifier := fmt.Sprintf("upcloud_managed_database_postgresql.%s", rName) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories(&providers), + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(` + resource "upcloud_managed_database_postgresql" "%[1]s" { + name = "%[1]s" + plan = "1x1xCPU-2GB-25GB" + title = "testtitle" + zone = "fi-hel1" + powered = false + }`, rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceIdentifier, "name", rName), + resource.TestCheckResourceAttr(resourceIdentifier, "plan", "1x1xCPU-2GB-25GB"), + resource.TestCheckResourceAttr(resourceIdentifier, "title", "testtitle"), + resource.TestCheckResourceAttr(resourceIdentifier, "zone", "fi-hel1"), + resource.TestCheckResourceAttr(resourceIdentifier, "powered", "false"), + resource.TestCheckResourceAttr(resourceIdentifier, "type", string(upcloud.ManagedDatabaseServiceTypePostgreSQL)), + resource.TestCheckResourceAttrSet(resourceIdentifier, "service_uri"), + ), + }, + { + Config: fmt.Sprintf(` + resource "upcloud_managed_database_postgresql" "%[1]s" { + name = "%[1]s" + plan = "1x1xCPU-2GB-25GB" + title = "testtitle" + zone = "fi-hel1" + powered = "true" + }`, rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceIdentifier, "powered", "true"), + ), + }, + }, + }) +} + +func TestAccUpcloudManagedDatabaseMySQL_Create(t *testing.T) { + var providers []*schema.Provider + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + resourceIdentifier := fmt.Sprintf("upcloud_managed_database_mysql.%s", rName) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories(&providers), + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(` + resource "upcloud_managed_database_mysql" "%[1]s" { + name = "%[1]s" + plan = "1x1xCPU-2GB-25GB" + title = "testtitle" + zone = "fi-hel1" + }`, rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceIdentifier, "name", rName), + resource.TestCheckResourceAttr(resourceIdentifier, "plan", "1x1xCPU-2GB-25GB"), + resource.TestCheckResourceAttr(resourceIdentifier, "title", "testtitle"), + resource.TestCheckResourceAttr(resourceIdentifier, "zone", "fi-hel1"), + resource.TestCheckResourceAttr(resourceIdentifier, "powered", "true"), + resource.TestCheckResourceAttr(resourceIdentifier, "type", string(upcloud.ManagedDatabaseServiceTypeMySQL)), + resource.TestCheckResourceAttrSet(resourceIdentifier, "service_uri"), + ), + }, + }, + }) +} diff --git a/upcloud/resource_upcloud_managed_database_user.go b/upcloud/resource_upcloud_managed_database_user.go new file mode 100644 index 00000000..92dd2b5f --- /dev/null +++ b/upcloud/resource_upcloud_managed_database_user.go @@ -0,0 +1,229 @@ +package upcloud + +import ( + "context" + "fmt" + "log" + + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceUpCloudManagedDatabaseUser() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceUpCloudManagedDatabaseUserCreate, + ReadContext: resourceUpCloudManagedDatabaseUserRead, + UpdateContext: resourceUpCloudManagedDatabaseUserUpdate, + DeleteContext: resourceUpCloudManagedDatabaseUserDelete, + Importer: &schema.ResourceImporter{ + StateContext: func(ctx context.Context, data *schema.ResourceData, i interface{}) ([]*schema.ResourceData, error) { + serviceID, user := splitManagedDatabaseSubResourceID(data.Id()) + if serviceID == "" || user == "" { + return nil, fmt.Errorf("invalid import id. Format: /") + } + if err := data.Set("service", serviceID); err != nil { + return nil, err + } + if err := data.Set("username", user); err != nil { + return nil, err + } + return []*schema.ResourceData{data}, nil + }, + }, + Schema: schemaUpCloudManagedDatabaseUser(), + } +} + +func schemaUpCloudManagedDatabaseUser() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "service": { + Description: "Service's UUID for which this user belongs to", + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "username": { + Description: "Name of the database user", + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "password": { + Description: "Password for the database user. Defaults to a random value", + Type: schema.TypeString, + Sensitive: true, + Computed: true, + Optional: true, + ValidateDiagFunc: func(i interface{}, path cty.Path) diag.Diagnostics { + if i == "" { + return diag.FromErr(fmt.Errorf("password cannot be set to empty")) + } + if len(i.(string)) < 8 { + return diag.FromErr(fmt.Errorf("password needs to be at least 8 characters")) + } + return nil + }, + }, + "type": { + Description: "Type of the user. Only normal type users can be created", + Type: schema.TypeString, + Computed: true, + }, + } +} + +func resourceUpCloudManagedDatabaseUserCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*service.Service) + + if d.HasChange("type") && d.Get("type").(string) != string(upcloud.ManagedDatabaseUserTypeNormal) { + return diag.FromErr(fmt.Errorf("only type `normal` users can be created")) + } + + serviceID := d.Get("service").(string) + serviceDetails, err := client.GetManagedDatabase(&request.GetManagedDatabaseRequest{UUID: serviceID}) + if err != nil { + return diag.FromErr(err) + } + if !serviceDetails.Powered { + return diag.FromErr(fmt.Errorf("cannot create a user while managed database %v (%v) is powered off", serviceDetails.Name, serviceID)) + } + + serviceDetails, err = resourceUpCloudManagedDatabaseWaitState(ctx, serviceID, meta, + d.Timeout(schema.TimeoutCreate), resourceUpcloudManagedDatabaseModifiableStates...) + if err != nil { + return diag.FromErr(err) + } + _, err = client.CreateManagedDatabaseUser(&request.CreateManagedDatabaseUserRequest{ + ServiceUUID: serviceID, + Username: d.Get("username").(string), + Password: d.Get("password").(string), + }) + if err != nil { + return diag.FromErr(err) + } + d.SetId(buildManagedDatabaseSubResourceID(serviceID, d.Get("username").(string))) + log.Printf("[INFO] managed database user %v/%v (%v/%v) created", + serviceDetails.Name, d.Get("username").(string), + serviceID, d.Get("username").(string)) + + return resourceUpCloudManagedDatabaseUserRead(ctx, d, meta) +} + +func resourceUpCloudManagedDatabaseUserRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*service.Service) + + serviceID, username := splitManagedDatabaseSubResourceID(d.Id()) + + serviceDetails, err := client.GetManagedDatabase(&request.GetManagedDatabaseRequest{UUID: serviceID}) + if err != nil { + return diag.FromErr(err) + } + + userDetails, err := client.GetManagedDatabaseUser(&request.GetManagedDatabaseUserRequest{ + ServiceUUID: serviceID, + Username: username}) + if err != nil { + return diag.FromErr(err) + } + + log.Printf("[DEBUG] managed database user %v/%v (%v/%v) read", + serviceDetails.Name, username, + serviceID, username) + return copyManagedDatabaseUserDetailsToResource(d, userDetails) +} + +func resourceUpCloudManagedDatabaseUserUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*service.Service) + + serviceID := d.Get("service").(string) + serviceDetails, err := client.GetManagedDatabase(&request.GetManagedDatabaseRequest{UUID: serviceID}) + if err != nil { + return diag.FromErr(err) + } + if !serviceDetails.Powered { + return diag.FromErr(fmt.Errorf("cannot modify a user while managed database %v (%v) is powered off", serviceDetails.Name, serviceID)) + } + + serviceID, username := splitManagedDatabaseSubResourceID(d.Id()) + serviceDetails, err = resourceUpCloudManagedDatabaseWaitState(ctx, serviceID, meta, + d.Timeout(schema.TimeoutCreate), resourceUpcloudManagedDatabaseModifiableStates...) + if err != nil { + return diag.FromErr(err) + } + + _, err = client.ModifyManagedDatabaseUser(&request.ModifyManagedDatabaseUserRequest{ + ServiceUUID: serviceID, + Username: username, + Password: d.Get("password").(string), + }) + if err != nil { + return diag.FromErr(err) + } + log.Printf("[INFO] managed database user %v/%v (%v/%v) deleted", + serviceDetails.Name, username, + serviceID, username) + return resourceUpCloudManagedDatabaseUserRead(ctx, d, meta) +} + +func resourceUpCloudManagedDatabaseUserDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*service.Service) + + if d.Get("type").(string) == string(upcloud.ManagedDatabaseUserTypePrimary) { + if d.HasChange("username") { + return diag.FromErr(fmt.Errorf("primary username cannot be changed %q", d.Id())) + } + log.Printf("[DEBUG] ignoring delete for primary user %q", d.Id()) + return nil + } + + serviceID := d.Get("service").(string) + serviceDetails, err := client.GetManagedDatabase(&request.GetManagedDatabaseRequest{UUID: serviceID}) + if err != nil { + return diag.FromErr(err) + } + if !serviceDetails.Powered { + return diag.FromErr(fmt.Errorf("cannot delete a user while managed database %v (%v) is powered off", serviceDetails.Name, serviceID)) + } + + serviceID, username := splitManagedDatabaseSubResourceID(d.Id()) + serviceDetails, err = resourceUpCloudManagedDatabaseWaitState(ctx, serviceID, meta, + d.Timeout(schema.TimeoutCreate), resourceUpcloudManagedDatabaseModifiableStates...) + if err != nil { + return diag.FromErr(err) + } + + err = client.DeleteManagedDatabaseUser(&request.DeleteManagedDatabaseUserRequest{ + ServiceUUID: serviceID, + Username: username, + }) + if err != nil { + return diag.FromErr(err) + } + log.Printf("[INFO] managed database user %v/%v (%v/%v) deleted", + serviceDetails.Name, username, + serviceID, username) + return nil +} + +func copyManagedDatabaseUserDetailsToResource(d *schema.ResourceData, details *upcloud.ManagedDatabaseUser) diag.Diagnostics { + setFields := []struct { + name string + val interface{} + }{ + {name: "username", val: details.Username}, + {name: "password", val: details.Password}, + {name: "type", val: string(details.Type)}, + } + + for _, sf := range setFields { + if err := d.Set(sf.name, sf.val); err != nil { + return diag.FromErr(err) + } + } + + return nil +} diff --git a/upcloud/resource_upcloud_managed_database_user_test.go b/upcloud/resource_upcloud_managed_database_user_test.go new file mode 100644 index 00000000..823fa0f1 --- /dev/null +++ b/upcloud/resource_upcloud_managed_database_user_test.go @@ -0,0 +1,65 @@ +package upcloud + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func TestAccUpcloudManagedDatabasePostgreSQLUser_CreateUpdate(t *testing.T) { + var providers []*schema.Provider + rNameManagedDatabase := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + rNameUser := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + testPassword := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + testPassword2 := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) + resourceIdentifierManagedDatabase := fmt.Sprintf("upcloud_managed_database_postgresql.%s", rNameManagedDatabase) + resourceIdentifierManagedDatabaseUser := fmt.Sprintf("upcloud_managed_database_user.%s", rNameUser) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviderFactories(&providers), + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(` + resource "upcloud_managed_database_postgresql" "%[1]s" { + name = "%[1]s" + plan = "1x1xCPU-2GB-25GB" + title = "testtitle" + zone = "fi-hel1" + } + resource "upcloud_managed_database_user" "%[2]s" { + service = upcloud_managed_database_postgresql.%[1]s.id + username = "%[2]s" + password = "%[3]s" + } + `, rNameManagedDatabase, rNameUser, testPassword), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceIdentifierManagedDatabase, "name", rNameManagedDatabase), + resource.TestCheckResourceAttrSet(resourceIdentifierManagedDatabase, "service_uri"), + resource.TestCheckResourceAttr(resourceIdentifierManagedDatabaseUser, "username", rNameUser), + resource.TestCheckResourceAttr(resourceIdentifierManagedDatabaseUser, "password", testPassword), + ), + }, + { + Config: fmt.Sprintf(` + resource "upcloud_managed_database_postgresql" "%[1]s" { + name = "%[1]s" + plan = "1x1xCPU-2GB-25GB" + title = "testtitle" + zone = "fi-hel1" + } + resource "upcloud_managed_database_user" "%[2]s" { + service = upcloud_managed_database_postgresql.%[1]s.id + username = "%[2]s" + password = "%[3]s" + } + `, rNameManagedDatabase, rNameUser, testPassword2), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceIdentifierManagedDatabaseUser, "password", testPassword2), + ), + }, + }, + }) +} diff --git a/upcloud/resource_upcloud_network.go b/upcloud/resource_upcloud_network.go index ee5425ef..3c5b8db3 100644 --- a/upcloud/resource_upcloud_network.go +++ b/upcloud/resource_upcloud_network.go @@ -6,9 +6,9 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "github.com/UpCloudLtd/upcloud-go-api/upcloud" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/request" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" diff --git a/upcloud/resource_upcloud_objectstorage.go b/upcloud/resource_upcloud_objectstorage.go index 43946211..3edbcef7 100644 --- a/upcloud/resource_upcloud_objectstorage.go +++ b/upcloud/resource_upcloud_objectstorage.go @@ -5,9 +5,9 @@ import ( "net/url" "time" - "github.com/UpCloudLtd/upcloud-go-api/upcloud" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/request" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" diff --git a/upcloud/resource_upcloud_objectstorage_test.go b/upcloud/resource_upcloud_objectstorage_test.go index a91bdb79..899e19bf 100644 --- a/upcloud/resource_upcloud_objectstorage_test.go +++ b/upcloud/resource_upcloud_objectstorage_test.go @@ -7,9 +7,9 @@ import ( "strings" "testing" - "github.com/UpCloudLtd/upcloud-go-api/upcloud" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/request" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" "github.com/hashicorp/go-retryablehttp" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" diff --git a/upcloud/resource_upcloud_router.go b/upcloud/resource_upcloud_router.go index 11718675..591f09b7 100644 --- a/upcloud/resource_upcloud_router.go +++ b/upcloud/resource_upcloud_router.go @@ -4,8 +4,8 @@ import ( "context" "fmt" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/request" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) diff --git a/upcloud/resource_upcloud_router_test.go b/upcloud/resource_upcloud_router_test.go index 40c98392..a2b26b27 100644 --- a/upcloud/resource_upcloud_router_test.go +++ b/upcloud/resource_upcloud_router_test.go @@ -4,9 +4,9 @@ import ( "fmt" "testing" - "github.com/UpCloudLtd/upcloud-go-api/upcloud" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/request" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" diff --git a/upcloud/resource_upcloud_server.go b/upcloud/resource_upcloud_server.go index 6eb5a49d..5b149a48 100644 --- a/upcloud/resource_upcloud_server.go +++ b/upcloud/resource_upcloud_server.go @@ -6,16 +6,17 @@ import ( "log" "time" - "github.com/UpCloudLtd/terraform-provider-upcloud/internal/server" - "github.com/UpCloudLtd/terraform-provider-upcloud/internal/storage" - "github.com/UpCloudLtd/terraform-provider-upcloud/internal/utils" - "github.com/UpCloudLtd/upcloud-go-api/upcloud" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/request" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + + "github.com/UpCloudLtd/terraform-provider-upcloud/internal/server" + "github.com/UpCloudLtd/terraform-provider-upcloud/internal/storage" + "github.com/UpCloudLtd/terraform-provider-upcloud/internal/utils" ) func resourceUpCloudServer() *schema.Resource { diff --git a/upcloud/resource_upcloud_storage.go b/upcloud/resource_upcloud_storage.go index 1ad579ea..f78377ae 100644 --- a/upcloud/resource_upcloud_storage.go +++ b/upcloud/resource_upcloud_storage.go @@ -8,15 +8,16 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "github.com/UpCloudLtd/terraform-provider-upcloud/internal/server" - "github.com/UpCloudLtd/terraform-provider-upcloud/internal/storage" - "github.com/UpCloudLtd/terraform-provider-upcloud/internal/utils" - "github.com/UpCloudLtd/upcloud-go-api/upcloud" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/request" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/UpCloudLtd/terraform-provider-upcloud/internal/server" + "github.com/UpCloudLtd/terraform-provider-upcloud/internal/storage" + "github.com/UpCloudLtd/terraform-provider-upcloud/internal/utils" ) func resourceUpCloudStorage() *schema.Resource { diff --git a/upcloud/resource_upcloud_storage_test.go b/upcloud/resource_upcloud_storage_test.go index 03f7e6ce..e83f8299 100644 --- a/upcloud/resource_upcloud_storage_test.go +++ b/upcloud/resource_upcloud_storage_test.go @@ -11,9 +11,9 @@ import ( "regexp" "testing" - "github.com/UpCloudLtd/upcloud-go-api/upcloud" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/request" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" diff --git a/upcloud/resource_upcloud_tag.go b/upcloud/resource_upcloud_tag.go index 59301774..07fd76c5 100644 --- a/upcloud/resource_upcloud_tag.go +++ b/upcloud/resource_upcloud_tag.go @@ -4,9 +4,9 @@ import ( "context" "regexp" - "github.com/UpCloudLtd/upcloud-go-api/upcloud" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/request" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/request" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" diff --git a/upcloud/resource_upcloud_tag_test.go b/upcloud/resource_upcloud_tag_test.go index 39d191bc..ad1f9914 100644 --- a/upcloud/resource_upcloud_tag_test.go +++ b/upcloud/resource_upcloud_tag_test.go @@ -5,8 +5,8 @@ import ( "strings" "testing" - "github.com/UpCloudLtd/upcloud-go-api/upcloud" - "github.com/UpCloudLtd/upcloud-go-api/upcloud/service" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud/service" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" diff --git a/upcloud/upcloudschema/managed_database.go b/upcloud/upcloudschema/managed_database.go new file mode 100644 index 00000000..1e2b7eff --- /dev/null +++ b/upcloud/upcloudschema/managed_database.go @@ -0,0 +1,41 @@ +package upcloudschema + +import ( + _ "embed" + "encoding/json" + "fmt" + "sync" + + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" +) + +var ( + //go:embed managed_database_service_types.json + rawManagedDatabaseSchema []byte +) + +var ( + onceParseManagedDatabaseSchema sync.Once + managedDatabaseServiceTypes map[upcloud.ManagedDatabaseServiceType]map[string]interface{} +) + +// ManagedDatabaseServiceTypeSchema parses the schema once and then caches it. The requested service type schema +// is then returned. All errors panic +func ManagedDatabaseServiceTypeSchema(serviceType upcloud.ManagedDatabaseServiceType) map[string]interface{} { + onceParseManagedDatabaseSchema.Do(func() { + if err := json.Unmarshal(rawManagedDatabaseSchema, &managedDatabaseServiceTypes); err != nil { + panic(fmt.Sprintf("managed database service types schema load failure: %v", err)) + } + }) + return managedDatabaseServiceTypes[serviceType] +} + +// ManagedDatabaseServicePropertiesSchema returns the service's properties schema. It calls ManagedDatabaseServiceTypeSchema +// so every error will be a panic +func ManagedDatabaseServicePropertiesSchema(serviceType upcloud.ManagedDatabaseServiceType) map[string]interface{} { + svcSchema := ManagedDatabaseServiceTypeSchema(serviceType) + if _, ok := svcSchema["properties"]; !ok { + return nil + } + return svcSchema["properties"].(map[string]interface{}) +} diff --git a/upcloud/upcloudschema/managed_database_service_types.json b/upcloud/upcloudschema/managed_database_service_types.json new file mode 100644 index 00000000..0b1ea05b --- /dev/null +++ b/upcloud/upcloudschema/managed_database_service_types.json @@ -0,0 +1,2964 @@ +{ + "mysql": { + "name": "mysql", + "description": "MySQL - Relational Database Management System", + "latest_available_version": "8.0.26", + "service_plans": [ + { + "backup_config": { + "interval": 24, + "max_count": 2, + "recovery_mode": "pitr" + }, + "node_count": 1, + "zones": { + "zone": [ + { + "name": "es-mad1" + }, + { + "name": "de-fra1" + }, + { + "name": "nl-ams1" + }, + { + "name": "pl-waw1" + }, + { + "name": "us-nyc1" + }, + { + "name": "fi-hel1" + }, + { + "name": "uk-lon1" + }, + { + "name": "fi-hel2" + }, + { + "name": "sg-sin1" + }, + { + "name": "au-syd1" + }, + { + "name": "us-chi1" + }, + { + "name": "us-sjo1" + } + ] + }, + "plan": "1x1xCPU-2GB-25GB", + "core_number": 1, + "storage_size": 25600, + "memory_amount": 2048 + }, + { + "backup_config": { + "interval": 24, + "max_count": 2, + "recovery_mode": "pitr" + }, + "node_count": 1, + "zones": { + "zone": [ + { + "name": "de-fra1" + }, + { + "name": "fi-hel2" + }, + { + "name": "us-nyc1" + }, + { + "name": "us-sjo1" + }, + { + "name": "es-mad1" + }, + { + "name": "nl-ams1" + }, + { + "name": "sg-sin1" + }, + { + "name": "uk-lon1" + }, + { + "name": "pl-waw1" + }, + { + "name": "au-syd1" + }, + { + "name": "fi-hel1" + }, + { + "name": "us-chi1" + } + ] + }, + "plan": "1x2xCPU-4GB-100GB", + "core_number": 2, + "storage_size": 102400, + "memory_amount": 4096 + }, + { + "backup_config": { + "interval": 24, + "max_count": 2, + "recovery_mode": "pitr" + }, + "node_count": 1, + "zones": { + "zone": [ + { + "name": "au-syd1" + }, + { + "name": "fi-hel1" + }, + { + "name": "us-chi1" + }, + { + "name": "fi-hel2" + }, + { + "name": "nl-ams1" + }, + { + "name": "sg-sin1" + }, + { + "name": "uk-lon1" + }, + { + "name": "es-mad1" + }, + { + "name": "us-nyc1" + }, + { + "name": "us-sjo1" + }, + { + "name": "de-fra1" + }, + { + "name": "pl-waw1" + } + ] + }, + "plan": "1x2xCPU-4GB-50GB", + "core_number": 2, + "storage_size": 51200, + "memory_amount": 4096 + }, + { + "backup_config": { + "interval": 24, + "max_count": 8, + "recovery_mode": "pitr" + }, + "node_count": 2, + "zones": { + "zone": [ + { + "name": "es-mad1" + }, + { + "name": "sg-sin1" + }, + { + "name": "uk-lon1" + }, + { + "name": "au-syd1" + }, + { + "name": "us-nyc1" + }, + { + "name": "de-fra1" + }, + { + "name": "fi-hel1" + }, + { + "name": "nl-ams1" + }, + { + "name": "us-chi1" + }, + { + "name": "us-sjo1" + }, + { + "name": "pl-waw1" + }, + { + "name": "fi-hel2" + } + ] + }, + "plan": "2x2xCPU-4GB-100GB", + "core_number": 2, + "storage_size": 102400, + "memory_amount": 4096 + }, + { + "backup_config": { + "interval": 24, + "max_count": 8, + "recovery_mode": "pitr" + }, + "node_count": 2, + "zones": { + "zone": [ + { + "name": "fi-hel1" + }, + { + "name": "nl-ams1" + }, + { + "name": "uk-lon1" + }, + { + "name": "us-chi1" + }, + { + "name": "us-nyc1" + }, + { + "name": "de-fra1" + }, + { + "name": "fi-hel2" + }, + { + "name": "us-sjo1" + }, + { + "name": "pl-waw1" + }, + { + "name": "au-syd1" + }, + { + "name": "es-mad1" + }, + { + "name": "sg-sin1" + } + ] + }, + "plan": "2x2xCPU-4GB-50GB", + "core_number": 2, + "storage_size": 51200, + "memory_amount": 4096 + }, + { + "backup_config": { + "interval": 24, + "max_count": 8, + "recovery_mode": "pitr" + }, + "node_count": 2, + "zones": { + "zone": [ + { + "name": "es-mad1" + }, + { + "name": "us-chi1" + }, + { + "name": "de-fra1" + }, + { + "name": "au-syd1" + }, + { + "name": "fi-hel2" + }, + { + "name": "pl-waw1" + }, + { + "name": "sg-sin1" + }, + { + "name": "uk-lon1" + }, + { + "name": "fi-hel1" + }, + { + "name": "nl-ams1" + }, + { + "name": "us-nyc1" + }, + { + "name": "us-sjo1" + } + ] + }, + "plan": "2x4xCPU-8GB-100GB", + "core_number": 4, + "storage_size": 102400, + "memory_amount": 8192 + }, + { + "backup_config": { + "interval": 24, + "max_count": 8, + "recovery_mode": "pitr" + }, + "node_count": 2, + "zones": { + "zone": [ + { + "name": "fi-hel2" + }, + { + "name": "nl-ams1" + }, + { + "name": "pl-waw1" + }, + { + "name": "fi-hel1" + }, + { + "name": "es-mad1" + }, + { + "name": "us-chi1" + }, + { + "name": "au-syd1" + }, + { + "name": "sg-sin1" + }, + { + "name": "uk-lon1" + }, + { + "name": "us-nyc1" + }, + { + "name": "us-sjo1" + }, + { + "name": "de-fra1" + } + ] + }, + "plan": "2x4xCPU-8GB-50GB", + "core_number": 4, + "storage_size": 51200, + "memory_amount": 8192 + }, + { + "backup_config": { + "interval": 24, + "max_count": 8, + "recovery_mode": "pitr" + }, + "node_count": 2, + "zones": { + "zone": [ + { + "name": "de-fra1" + }, + { + "name": "us-nyc1" + }, + { + "name": "us-sjo1" + }, + { + "name": "sg-sin1" + }, + { + "name": "fi-hel1" + }, + { + "name": "pl-waw1" + }, + { + "name": "au-syd1" + }, + { + "name": "es-mad1" + }, + { + "name": "uk-lon1" + }, + { + "name": "us-chi1" + }, + { + "name": "fi-hel2" + }, + { + "name": "nl-ams1" + } + ] + }, + "plan": "2x6xCPU-16GB-100GB", + "core_number": 6, + "storage_size": 102400, + "memory_amount": 16384 + }, + { + "backup_config": { + "interval": 24, + "max_count": 8, + "recovery_mode": "pitr" + }, + "node_count": 2, + "zones": { + "zone": [ + { + "name": "uk-lon1" + }, + { + "name": "de-fra1" + }, + { + "name": "nl-ams1" + }, + { + "name": "us-chi1" + }, + { + "name": "us-nyc1" + }, + { + "name": "us-sjo1" + }, + { + "name": "au-syd1" + }, + { + "name": "es-mad1" + }, + { + "name": "fi-hel1" + }, + { + "name": "fi-hel2" + }, + { + "name": "pl-waw1" + }, + { + "name": "sg-sin1" + } + ] + }, + "plan": "2x6xCPU-16GB-250GB", + "core_number": 6, + "storage_size": 256000, + "memory_amount": 16384 + }, + { + "backup_config": { + "interval": 24, + "max_count": 8, + "recovery_mode": "pitr" + }, + "node_count": 2, + "zones": { + "zone": [ + { + "name": "es-mad1" + }, + { + "name": "fi-hel2" + }, + { + "name": "nl-ams1" + }, + { + "name": "au-syd1" + }, + { + "name": "sg-sin1" + }, + { + "name": "us-chi1" + }, + { + "name": "us-sjo1" + }, + { + "name": "de-fra1" + }, + { + "name": "fi-hel1" + }, + { + "name": "pl-waw1" + }, + { + "name": "uk-lon1" + }, + { + "name": "us-nyc1" + } + ] + }, + "plan": "2x8xCPU-32GB-100GB", + "core_number": 8, + "storage_size": 102400, + "memory_amount": 32768 + }, + { + "backup_config": { + "interval": 24, + "max_count": 8, + "recovery_mode": "pitr" + }, + "node_count": 2, + "zones": { + "zone": [ + { + "name": "uk-lon1" + }, + { + "name": "us-nyc1" + }, + { + "name": "us-sjo1" + }, + { + "name": "au-syd1" + }, + { + "name": "pl-waw1" + }, + { + "name": "fi-hel2" + }, + { + "name": "nl-ams1" + }, + { + "name": "sg-sin1" + }, + { + "name": "de-fra1" + }, + { + "name": "es-mad1" + }, + { + "name": "fi-hel1" + }, + { + "name": "us-chi1" + } + ] + }, + "plan": "2x8xCPU-32GB-250GB", + "core_number": 8, + "storage_size": 256000, + "memory_amount": 32768 + }, + { + "backup_config": { + "interval": 24, + "max_count": 15, + "recovery_mode": "pitr" + }, + "node_count": 3, + "zones": { + "zone": [ + { + "name": "pl-waw1" + }, + { + "name": "fi-hel1" + }, + { + "name": "sg-sin1" + }, + { + "name": "au-syd1" + }, + { + "name": "es-mad1" + }, + { + "name": "us-chi1" + }, + { + "name": "us-nyc1" + }, + { + "name": "us-sjo1" + }, + { + "name": "de-fra1" + }, + { + "name": "nl-ams1" + }, + { + "name": "uk-lon1" + }, + { + "name": "fi-hel2" + } + ] + }, + "plan": "3x2xCPU-4GB-100GB", + "core_number": 2, + "storage_size": 102400, + "memory_amount": 4096 + }, + { + "backup_config": { + "interval": 24, + "max_count": 15, + "recovery_mode": "pitr" + }, + "node_count": 3, + "zones": { + "zone": [ + { + "name": "fi-hel2" + }, + { + "name": "nl-ams1" + }, + { + "name": "sg-sin1" + }, + { + "name": "us-sjo1" + }, + { + "name": "es-mad1" + }, + { + "name": "au-syd1" + }, + { + "name": "de-fra1" + }, + { + "name": "uk-lon1" + }, + { + "name": "fi-hel1" + }, + { + "name": "pl-waw1" + }, + { + "name": "us-chi1" + }, + { + "name": "us-nyc1" + } + ] + }, + "plan": "3x2xCPU-4GB-200GB", + "core_number": 2, + "storage_size": 204800, + "memory_amount": 4096 + }, + { + "backup_config": { + "interval": 24, + "max_count": 15, + "recovery_mode": "pitr" + }, + "node_count": 3, + "zones": { + "zone": [ + { + "name": "us-sjo1" + }, + { + "name": "au-syd1" + }, + { + "name": "es-mad1" + }, + { + "name": "pl-waw1" + }, + { + "name": "us-nyc1" + }, + { + "name": "fi-hel1" + }, + { + "name": "fi-hel2" + }, + { + "name": "nl-ams1" + }, + { + "name": "uk-lon1" + }, + { + "name": "us-chi1" + }, + { + "name": "de-fra1" + }, + { + "name": "sg-sin1" + } + ] + }, + "plan": "3x4xCPU-8GB-100GB", + "core_number": 4, + "storage_size": 102400, + "memory_amount": 8192 + }, + { + "backup_config": { + "interval": 24, + "max_count": 15, + "recovery_mode": "pitr" + }, + "node_count": 3, + "zones": { + "zone": [ + { + "name": "us-sjo1" + }, + { + "name": "es-mad1" + }, + { + "name": "sg-sin1" + }, + { + "name": "us-nyc1" + }, + { + "name": "au-syd1" + }, + { + "name": "pl-waw1" + }, + { + "name": "de-fra1" + }, + { + "name": "fi-hel2" + }, + { + "name": "nl-ams1" + }, + { + "name": "uk-lon1" + }, + { + "name": "us-chi1" + }, + { + "name": "fi-hel1" + } + ] + }, + "plan": "3x4xCPU-8GB-200GB", + "core_number": 4, + "storage_size": 204800, + "memory_amount": 8192 + }, + { + "backup_config": { + "interval": 24, + "max_count": 15, + "recovery_mode": "pitr" + }, + "node_count": 3, + "zones": { + "zone": [ + { + "name": "au-syd1" + }, + { + "name": "es-mad1" + }, + { + "name": "fi-hel1" + }, + { + "name": "nl-ams1" + }, + { + "name": "us-chi1" + }, + { + "name": "sg-sin1" + }, + { + "name": "fi-hel2" + }, + { + "name": "uk-lon1" + }, + { + "name": "us-nyc1" + }, + { + "name": "de-fra1" + }, + { + "name": "pl-waw1" + }, + { + "name": "us-sjo1" + } + ] + }, + "plan": "3x6xCPU-16GB-200GB", + "core_number": 6, + "storage_size": 204800, + "memory_amount": 16384 + }, + { + "backup_config": { + "interval": 24, + "max_count": 15, + "recovery_mode": "pitr" + }, + "node_count": 3, + "zones": { + "zone": [ + { + "name": "fi-hel2" + }, + { + "name": "de-fra1" + }, + { + "name": "pl-waw1" + }, + { + "name": "sg-sin1" + }, + { + "name": "uk-lon1" + }, + { + "name": "us-sjo1" + }, + { + "name": "nl-ams1" + }, + { + "name": "au-syd1" + }, + { + "name": "fi-hel1" + }, + { + "name": "us-nyc1" + }, + { + "name": "es-mad1" + }, + { + "name": "us-chi1" + } + ] + }, + "plan": "3x6xCPU-16GB-500GB", + "core_number": 6, + "storage_size": 512000, + "memory_amount": 16384 + }, + { + "backup_config": { + "interval": 24, + "max_count": 15, + "recovery_mode": "pitr" + }, + "node_count": 3, + "zones": { + "zone": [ + { + "name": "de-fra1" + }, + { + "name": "nl-ams1" + }, + { + "name": "uk-lon1" + }, + { + "name": "fi-hel2" + }, + { + "name": "au-syd1" + }, + { + "name": "us-chi1" + }, + { + "name": "us-nyc1" + }, + { + "name": "sg-sin1" + }, + { + "name": "us-sjo1" + }, + { + "name": "es-mad1" + }, + { + "name": "fi-hel1" + }, + { + "name": "pl-waw1" + } + ] + }, + "plan": "3x8xCPU-32GB-200GB", + "core_number": 8, + "storage_size": 204800, + "memory_amount": 32768 + }, + { + "backup_config": { + "interval": 24, + "max_count": 15, + "recovery_mode": "pitr" + }, + "node_count": 3, + "zones": { + "zone": [ + { + "name": "es-mad1" + }, + { + "name": "sg-sin1" + }, + { + "name": "us-chi1" + }, + { + "name": "us-sjo1" + }, + { + "name": "nl-ams1" + }, + { + "name": "fi-hel2" + }, + { + "name": "au-syd1" + }, + { + "name": "fi-hel1" + }, + { + "name": "uk-lon1" + }, + { + "name": "de-fra1" + }, + { + "name": "pl-waw1" + }, + { + "name": "us-nyc1" + } + ] + }, + "plan": "3x8xCPU-32GB-500GB", + "core_number": 8, + "storage_size": 512000, + "memory_amount": 32768 + } + ], + "properties": { + "admin_password": { + "createOnly": true, + "example": "z66o9QXqKM", + "maxLength": 256, + "minLength": 8, + "pattern": "^[a-zA-Z0-9-_]+$", + "title": "Custom password for admin user. Defaults to random string. This must be set only when a new service is being created.", + "type": [ + "string", + "null" + ], + "user_error": "Must consist of alpha-numeric characters, underscores or dashes" + }, + "admin_username": { + "createOnly": true, + "example": "avnadmin", + "maxLength": 64, + "pattern": "^[_A-Za-z0-9][-._A-Za-z0-9]{0,63}$", + "title": "Custom username for admin user. This must be set only when a new service is being created.", + "type": [ + "string", + "null" + ], + "user_error": "Must consist of alpha-numeric characters, dots, underscores or dashes, may not start with dash or dot, max 64 characters" + }, + "automatic_utility_network_ip_filter": { + "default": true, + "title": "Automatic utility network IP Filter", + "type": "boolean", + "description": "Automatically allow connections from servers in the utility network within the same zone" + }, + "backup_hour": { + "example": 3, + "title": "The hour of day (in UTC) when backup for the service is started. New backup is only started if previous backup has already completed.", + "type": [ + "integer", + "null" + ], + "minimum": 0, + "maximum": 23 + }, + "backup_minute": { + "example": 30, + "title": "The minute of an hour when backup for the service is started. New backup is only started if previous backup has already completed.", + "type": [ + "integer", + "null" + ], + "minimum": 0, + "maximum": 59 + }, + "binlog_retention_period": { + "example": 600, + "title": "The minimum amount of time in seconds to keep binlog entries before deletion. This may be extended for services that require binlog entries for longer than the default for example if using the MySQL Debezium Kafka connector.", + "type": "integer", + "minimum": 600, + "maximum": 86400 + }, + "connect_timeout": { + "example": 10, + "title": "connect_timeout", + "type": "integer", + "description": "The number of seconds that the mysqld server waits for a connect packet before responding with Bad handshake", + "minimum": 2, + "maximum": 3600 + }, + "default_time_zone": { + "example": "+03:00", + "maxLength": 100, + "minLength": 2, + "title": "default_time_zone", + "type": "string", + "description": "Default server time zone as an offset from UTC (from -12:00 to +12:00), a time zone name, or 'SYSTEM' to use the MySQL server default." + }, + "group_concat_max_len": { + "example": 1024, + "title": "group_concat_max_len", + "type": "integer", + "description": "The maximum permitted result length in bytes for the GROUP_CONCAT() function.", + "minimum": 4, + "maximum": 18446744073709552000 + }, + "information_schema_stats_expiry": { + "example": 86400, + "title": "information_schema_stats_expiry", + "type": "integer", + "description": "The time, in seconds, before cached statistics expire", + "minimum": 900, + "maximum": 31536000 + }, + "innodb_ft_min_token_size": { + "example": 3, + "title": "innodb_ft_min_token_size", + "type": "integer", + "description": "Minimum length of words that are stored in an InnoDB FULLTEXT index.", + "minimum": 0, + "maximum": 16 + }, + "innodb_ft_server_stopword_table": { + "example": "db_name/table_name", + "maxLength": 1024, + "pattern": "^.+/.+$", + "title": "innodb_ft_server_stopword_table", + "type": [ + "null", + "string" + ], + "description": "This option is used to specify your own InnoDB FULLTEXT index stopword list for all InnoDB tables." + }, + "innodb_lock_wait_timeout": { + "example": 50, + "title": "innodb_lock_wait_timeout", + "type": "integer", + "description": "The length of time in seconds an InnoDB transaction waits for a row lock before giving up.", + "minimum": 1, + "maximum": 3600 + }, + "innodb_log_buffer_size": { + "example": 16777216, + "title": "innodb_log_buffer_size", + "type": "integer", + "description": "The size in bytes of the buffer that InnoDB uses to write to the log files on disk.", + "minimum": 1048576, + "maximum": 4294967295 + }, + "innodb_online_alter_log_max_size": { + "example": 134217728, + "title": "innodb_online_alter_log_max_size", + "type": "integer", + "description": "The upper limit in bytes on the size of the temporary log files used during online DDL operations for InnoDB tables.", + "minimum": 65536, + "maximum": 1099511627776 + }, + "innodb_print_all_deadlocks": { + "example": true, + "title": "innodb_print_all_deadlocks", + "type": "boolean", + "description": "When enabled, information about all deadlocks in InnoDB user transactions is recorded in the error log. Disabled by default." + }, + "innodb_rollback_on_timeout": { + "example": true, + "title": "innodb_rollback_on_timeout", + "type": "boolean", + "description": "When enabled a transaction timeout causes InnoDB to abort and roll back the entire transaction." + }, + "interactive_timeout": { + "example": 3600, + "title": "interactive_timeout", + "type": "integer", + "description": "The number of seconds the server waits for activity on an interactive connection before closing it.", + "minimum": 30, + "maximum": 604800 + }, + "internal_tmp_mem_storage_engine": { + "example": "TempTable", + "title": "internal_tmp_mem_storage_engine", + "type": "string", + "description": "The storage engine for in-memory internal temporary tables.", + "enum": [ + "TempTable", + "MEMORY" + ] + }, + "ip_filter": { + "default": [], + "title": "IP filter", + "type": "array", + "items": { + "maxLength": 18, + "title": "CIDR address block", + "type": "string" + }, + "maxItems": 1024, + "description": "Allow incoming connections from CIDR address block, e.g. '10.20.0.0/16'" + }, + "long_query_time": { + "example": 10, + "title": "long_query_time", + "type": "number", + "description": "The slow_query_logs work as SQL statements that take more than long_query_time seconds to execute. Default is 10s", + "minimum": 0, + "maximum": 3600 + }, + "max_allowed_packet": { + "example": 67108864, + "title": "max_allowed_packet", + "type": "integer", + "description": "Size of the largest message in bytes that can be received by the server. Default is 67108864 (64M)", + "minimum": 102400, + "maximum": 1073741824 + }, + "max_heap_table_size": { + "example": 16777216, + "title": "max_heap_table_size", + "type": "integer", + "description": "Limits the size of internal in-memory tables. Also set tmp_table_size. Default is 16777216 (16M)", + "minimum": 1048576, + "maximum": 1073741824 + }, + "migration": { + "title": "Migrate data from existing server", + "type": [ + "object", + "null" + ], + "properties": { + "dbname": { + "example": "defaultdb", + "maxLength": 63, + "title": "Database name for bootstrapping the initial connection", + "type": "string" + }, + "host": { + "example": "my.server.com", + "maxLength": 255, + "title": "Hostname or IP address of the server where to migrate data from", + "type": "string" + }, + "ignore_dbs": { + "example": "db1,db2", + "maxLength": 2048, + "title": "Comma-separated list of databases, which should be ignored during migration (supported by MySQL only at the moment)", + "type": "string" + }, + "password": { + "example": "jjKk45Nnd", + "maxLength": 256, + "title": "Password for authentication with the server where to migrate data from", + "type": "string" + }, + "port": { + "example": 1234, + "title": "Port number of the server where to migrate data from", + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "ssl": { + "default": true, + "title": "The server where to migrate data from is secured with SSL", + "type": "boolean" + }, + "username": { + "example": "myname", + "maxLength": 256, + "title": "User name for authentication with the server where to migrate data from", + "type": "string" + } + }, + "required": [ + "host", + "port" + ] + }, + "net_read_timeout": { + "example": 30, + "title": "net_read_timeout", + "type": "integer", + "description": "The number of seconds to wait for more data from a connection before aborting the read.", + "minimum": 1, + "maximum": 3600 + }, + "net_write_timeout": { + "example": 30, + "title": "net_write_timeout", + "type": "integer", + "description": "The number of seconds to wait for a block to be written to a connection before aborting the write.", + "minimum": 1, + "maximum": 3600 + }, + "public_access": { + "default": false, + "title": "Public Access", + "type": "boolean", + "description": "Allow access to the service from the public Internet" + }, + "slow_query_log": { + "example": true, + "title": "slow_query_log", + "type": "boolean", + "description": "Slow query log enables capturing of slow queries. Setting slow_query_log to false also truncates the mysql.slow_log table. Default is off" + }, + "sort_buffer_size": { + "example": 262144, + "title": "sort_buffer_size", + "type": "integer", + "description": "Sort buffer size in bytes for ORDER BY optimization. Default is 262144 (256K)", + "minimum": 32768, + "maximum": 1073741824 + }, + "sql_mode": { + "example": "ANSI,TRADITIONAL", + "maxLength": 1024, + "pattern": "^[A-Z_]*(,[A-Z_]+)*$", + "title": "sql_mode", + "type": "string", + "user_error": "Must be uppercase alphabetic characters, underscores and commas", + "description": "Global SQL mode. Set to empty to use MySQL server defaults. When creating a new service and not setting this field UpCloud default SQL mode (strict, SQL standard compliant) will be assigned." + }, + "sql_require_primary_key": { + "example": true, + "title": "sql_require_primary_key", + "type": "boolean", + "description": "Require primary key to be defined for new tables or old tables modified with ALTER TABLE and fail if missing. It is recommended to always have primary keys because various functionality may break if any large table is missing them." + }, + "tmp_table_size": { + "example": 16777216, + "title": "tmp_table_size", + "type": "integer", + "description": "Limits the size of internal in-memory tables. Also set max_heap_table_size. Default is 16777216 (16M)", + "minimum": 1048576, + "maximum": 1073741824 + }, + "version": { + "title": "MySQL major version", + "type": [ + "string", + "null" + ], + "enum": [ + "8" + ] + }, + "wait_timeout": { + "example": 28800, + "title": "wait_timeout", + "type": "integer", + "description": "The number of seconds the server waits for activity on a noninteractive connection before closing it.", + "minimum": 1, + "maximum": 2147483 + } + } + }, + "pg": { + "name": "pg", + "description": "PostgreSQL - Object-Relational Database Management System", + "latest_available_version": "13.4", + "service_plans": [ + { + "backup_config": { + "interval": 24, + "max_count": 3, + "recovery_mode": "pitr" + }, + "node_count": 1, + "zones": { + "zone": [ + { + "name": "au-syd1" + }, + { + "name": "es-mad1" + }, + { + "name": "nl-ams1" + }, + { + "name": "de-fra1" + }, + { + "name": "fi-hel1" + }, + { + "name": "fi-hel2" + }, + { + "name": "pl-waw1" + }, + { + "name": "sg-sin1" + }, + { + "name": "us-chi1" + }, + { + "name": "us-sjo1" + }, + { + "name": "uk-lon1" + }, + { + "name": "us-nyc1" + } + ] + }, + "plan": "1x1xCPU-2GB-25GB", + "core_number": 1, + "storage_size": 25600, + "memory_amount": 2048 + }, + { + "backup_config": { + "interval": 24, + "max_count": 3, + "recovery_mode": "pitr" + }, + "node_count": 1, + "zones": { + "zone": [ + { + "name": "es-mad1" + }, + { + "name": "pl-waw1" + }, + { + "name": "us-sjo1" + }, + { + "name": "uk-lon1" + }, + { + "name": "us-nyc1" + }, + { + "name": "de-fra1" + }, + { + "name": "au-syd1" + }, + { + "name": "fi-hel2" + }, + { + "name": "fi-hel1" + }, + { + "name": "nl-ams1" + }, + { + "name": "sg-sin1" + }, + { + "name": "us-chi1" + } + ] + }, + "plan": "1x2xCPU-4GB-100GB", + "core_number": 2, + "storage_size": 102400, + "memory_amount": 4096 + }, + { + "backup_config": { + "interval": 24, + "max_count": 3, + "recovery_mode": "pitr" + }, + "node_count": 1, + "zones": { + "zone": [ + { + "name": "uk-lon1" + }, + { + "name": "us-sjo1" + }, + { + "name": "fi-hel2" + }, + { + "name": "nl-ams1" + }, + { + "name": "de-fra1" + }, + { + "name": "es-mad1" + }, + { + "name": "fi-hel1" + }, + { + "name": "pl-waw1" + }, + { + "name": "us-chi1" + }, + { + "name": "sg-sin1" + }, + { + "name": "au-syd1" + }, + { + "name": "us-nyc1" + } + ] + }, + "plan": "1x2xCPU-4GB-50GB", + "core_number": 2, + "storage_size": 51200, + "memory_amount": 4096 + }, + { + "backup_config": { + "interval": 24, + "max_count": 15, + "recovery_mode": "pitr" + }, + "node_count": 2, + "zones": { + "zone": [ + { + "name": "es-mad1" + }, + { + "name": "de-fra1" + }, + { + "name": "fi-hel2" + }, + { + "name": "nl-ams1" + }, + { + "name": "sg-sin1" + }, + { + "name": "uk-lon1" + }, + { + "name": "us-nyc1" + }, + { + "name": "pl-waw1" + }, + { + "name": "us-chi1" + }, + { + "name": "au-syd1" + }, + { + "name": "fi-hel1" + }, + { + "name": "us-sjo1" + } + ] + }, + "plan": "2x2xCPU-4GB-100GB", + "core_number": 2, + "storage_size": 102400, + "memory_amount": 4096 + }, + { + "backup_config": { + "interval": 24, + "max_count": 15, + "recovery_mode": "pitr" + }, + "node_count": 2, + "zones": { + "zone": [ + { + "name": "de-fra1" + }, + { + "name": "es-mad1" + }, + { + "name": "fi-hel1" + }, + { + "name": "nl-ams1" + }, + { + "name": "us-chi1" + }, + { + "name": "us-sjo1" + }, + { + "name": "au-syd1" + }, + { + "name": "sg-sin1" + }, + { + "name": "uk-lon1" + }, + { + "name": "us-nyc1" + }, + { + "name": "fi-hel2" + }, + { + "name": "pl-waw1" + } + ] + }, + "plan": "2x2xCPU-4GB-50GB", + "core_number": 2, + "storage_size": 51200, + "memory_amount": 4096 + }, + { + "backup_config": { + "interval": 24, + "max_count": 15, + "recovery_mode": "pitr" + }, + "node_count": 2, + "zones": { + "zone": [ + { + "name": "us-nyc1" + }, + { + "name": "au-syd1" + }, + { + "name": "de-fra1" + }, + { + "name": "es-mad1" + }, + { + "name": "us-sjo1" + }, + { + "name": "uk-lon1" + }, + { + "name": "us-chi1" + }, + { + "name": "fi-hel2" + }, + { + "name": "nl-ams1" + }, + { + "name": "pl-waw1" + }, + { + "name": "sg-sin1" + }, + { + "name": "fi-hel1" + } + ] + }, + "plan": "2x4xCPU-8GB-100GB", + "core_number": 4, + "storage_size": 102400, + "memory_amount": 8192 + }, + { + "backup_config": { + "interval": 24, + "max_count": 15, + "recovery_mode": "pitr" + }, + "node_count": 2, + "zones": { + "zone": [ + { + "name": "us-nyc1" + }, + { + "name": "de-fra1" + }, + { + "name": "fi-hel1" + }, + { + "name": "fi-hel2" + }, + { + "name": "pl-waw1" + }, + { + "name": "uk-lon1" + }, + { + "name": "us-sjo1" + }, + { + "name": "sg-sin1" + }, + { + "name": "us-chi1" + }, + { + "name": "au-syd1" + }, + { + "name": "nl-ams1" + }, + { + "name": "es-mad1" + } + ] + }, + "plan": "2x4xCPU-8GB-50GB", + "core_number": 4, + "storage_size": 51200, + "memory_amount": 8192 + }, + { + "backup_config": { + "interval": 24, + "max_count": 15, + "recovery_mode": "pitr" + }, + "node_count": 2, + "zones": { + "zone": [ + { + "name": "de-fra1" + }, + { + "name": "fi-hel2" + }, + { + "name": "nl-ams1" + }, + { + "name": "es-mad1" + }, + { + "name": "us-sjo1" + }, + { + "name": "pl-waw1" + }, + { + "name": "uk-lon1" + }, + { + "name": "us-nyc1" + }, + { + "name": "sg-sin1" + }, + { + "name": "us-chi1" + }, + { + "name": "au-syd1" + }, + { + "name": "fi-hel1" + } + ] + }, + "plan": "2x6xCPU-16GB-100GB", + "core_number": 6, + "storage_size": 102400, + "memory_amount": 16384 + }, + { + "backup_config": { + "interval": 24, + "max_count": 15, + "recovery_mode": "pitr" + }, + "node_count": 2, + "zones": { + "zone": [ + { + "name": "fi-hel2" + }, + { + "name": "es-mad1" + }, + { + "name": "uk-lon1" + }, + { + "name": "us-chi1" + }, + { + "name": "us-nyc1" + }, + { + "name": "au-syd1" + }, + { + "name": "de-fra1" + }, + { + "name": "fi-hel1" + }, + { + "name": "nl-ams1" + }, + { + "name": "pl-waw1" + }, + { + "name": "sg-sin1" + }, + { + "name": "us-sjo1" + } + ] + }, + "plan": "2x6xCPU-16GB-250GB", + "core_number": 6, + "storage_size": 256000, + "memory_amount": 16384 + }, + { + "backup_config": { + "interval": 24, + "max_count": 15, + "recovery_mode": "pitr" + }, + "node_count": 2, + "zones": { + "zone": [ + { + "name": "au-syd1" + }, + { + "name": "de-fra1" + }, + { + "name": "es-mad1" + }, + { + "name": "us-chi1" + }, + { + "name": "nl-ams1" + }, + { + "name": "sg-sin1" + }, + { + "name": "us-sjo1" + }, + { + "name": "fi-hel1" + }, + { + "name": "fi-hel2" + }, + { + "name": "pl-waw1" + }, + { + "name": "uk-lon1" + }, + { + "name": "us-nyc1" + } + ] + }, + "plan": "2x8xCPU-32GB-100GB", + "core_number": 8, + "storage_size": 102400, + "memory_amount": 32768 + }, + { + "backup_config": { + "interval": 24, + "max_count": 15, + "recovery_mode": "pitr" + }, + "node_count": 2, + "zones": { + "zone": [ + { + "name": "fi-hel2" + }, + { + "name": "pl-waw1" + }, + { + "name": "sg-sin1" + }, + { + "name": "us-nyc1" + }, + { + "name": "au-syd1" + }, + { + "name": "fi-hel1" + }, + { + "name": "es-mad1" + }, + { + "name": "nl-ams1" + }, + { + "name": "uk-lon1" + }, + { + "name": "us-chi1" + }, + { + "name": "us-sjo1" + }, + { + "name": "de-fra1" + } + ] + }, + "plan": "2x8xCPU-32GB-250GB", + "core_number": 8, + "storage_size": 256000, + "memory_amount": 32768 + }, + { + "backup_config": { + "interval": 24, + "max_count": 31, + "recovery_mode": "pitr" + }, + "node_count": 3, + "zones": { + "zone": [ + { + "name": "au-syd1" + }, + { + "name": "us-chi1" + }, + { + "name": "us-sjo1" + }, + { + "name": "de-fra1" + }, + { + "name": "fi-hel1" + }, + { + "name": "fi-hel2" + }, + { + "name": "uk-lon1" + }, + { + "name": "nl-ams1" + }, + { + "name": "pl-waw1" + }, + { + "name": "sg-sin1" + }, + { + "name": "es-mad1" + }, + { + "name": "us-nyc1" + } + ] + }, + "plan": "3x2xCPU-4GB-100GB", + "core_number": 2, + "storage_size": 102400, + "memory_amount": 4096 + }, + { + "backup_config": { + "interval": 24, + "max_count": 31, + "recovery_mode": "pitr" + }, + "node_count": 3, + "zones": { + "zone": [ + { + "name": "es-mad1" + }, + { + "name": "fi-hel1" + }, + { + "name": "fi-hel2" + }, + { + "name": "pl-waw1" + }, + { + "name": "sg-sin1" + }, + { + "name": "au-syd1" + }, + { + "name": "us-sjo1" + }, + { + "name": "de-fra1" + }, + { + "name": "nl-ams1" + }, + { + "name": "uk-lon1" + }, + { + "name": "us-chi1" + }, + { + "name": "us-nyc1" + } + ] + }, + "plan": "3x2xCPU-4GB-200GB", + "core_number": 2, + "storage_size": 204800, + "memory_amount": 4096 + }, + { + "backup_config": { + "interval": 24, + "max_count": 31, + "recovery_mode": "pitr" + }, + "node_count": 3, + "zones": { + "zone": [ + { + "name": "us-sjo1" + }, + { + "name": "fi-hel1" + }, + { + "name": "fi-hel2" + }, + { + "name": "sg-sin1" + }, + { + "name": "us-chi1" + }, + { + "name": "us-nyc1" + }, + { + "name": "au-syd1" + }, + { + "name": "de-fra1" + }, + { + "name": "nl-ams1" + }, + { + "name": "es-mad1" + }, + { + "name": "pl-waw1" + }, + { + "name": "uk-lon1" + } + ] + }, + "plan": "3x4xCPU-8GB-100GB", + "core_number": 4, + "storage_size": 102400, + "memory_amount": 8192 + }, + { + "backup_config": { + "interval": 24, + "max_count": 31, + "recovery_mode": "pitr" + }, + "node_count": 3, + "zones": { + "zone": [ + { + "name": "au-syd1" + }, + { + "name": "pl-waw1" + }, + { + "name": "uk-lon1" + }, + { + "name": "us-chi1" + }, + { + "name": "es-mad1" + }, + { + "name": "fi-hel2" + }, + { + "name": "us-nyc1" + }, + { + "name": "de-fra1" + }, + { + "name": "fi-hel1" + }, + { + "name": "nl-ams1" + }, + { + "name": "sg-sin1" + }, + { + "name": "us-sjo1" + } + ] + }, + "plan": "3x4xCPU-8GB-200GB", + "core_number": 4, + "storage_size": 204800, + "memory_amount": 8192 + }, + { + "backup_config": { + "interval": 24, + "max_count": 31, + "recovery_mode": "pitr" + }, + "node_count": 3, + "zones": { + "zone": [ + { + "name": "de-fra1" + }, + { + "name": "fi-hel1" + }, + { + "name": "fi-hel2" + }, + { + "name": "nl-ams1" + }, + { + "name": "sg-sin1" + }, + { + "name": "uk-lon1" + }, + { + "name": "us-nyc1" + }, + { + "name": "es-mad1" + }, + { + "name": "us-chi1" + }, + { + "name": "us-sjo1" + }, + { + "name": "au-syd1" + }, + { + "name": "pl-waw1" + } + ] + }, + "plan": "3x6xCPU-16GB-200GB", + "core_number": 6, + "storage_size": 204800, + "memory_amount": 16384 + }, + { + "backup_config": { + "interval": 24, + "max_count": 31, + "recovery_mode": "pitr" + }, + "node_count": 3, + "zones": { + "zone": [ + { + "name": "au-syd1" + }, + { + "name": "us-sjo1" + }, + { + "name": "us-chi1" + }, + { + "name": "us-nyc1" + }, + { + "name": "de-fra1" + }, + { + "name": "nl-ams1" + }, + { + "name": "uk-lon1" + }, + { + "name": "es-mad1" + }, + { + "name": "fi-hel2" + }, + { + "name": "fi-hel1" + }, + { + "name": "pl-waw1" + }, + { + "name": "sg-sin1" + } + ] + }, + "plan": "3x6xCPU-16GB-500GB", + "core_number": 6, + "storage_size": 512000, + "memory_amount": 16384 + }, + { + "backup_config": { + "interval": 24, + "max_count": 31, + "recovery_mode": "pitr" + }, + "node_count": 3, + "zones": { + "zone": [ + { + "name": "fi-hel1" + }, + { + "name": "us-nyc1" + }, + { + "name": "de-fra1" + }, + { + "name": "es-mad1" + }, + { + "name": "fi-hel2" + }, + { + "name": "pl-waw1" + }, + { + "name": "uk-lon1" + }, + { + "name": "us-chi1" + }, + { + "name": "us-sjo1" + }, + { + "name": "nl-ams1" + }, + { + "name": "sg-sin1" + }, + { + "name": "au-syd1" + } + ] + }, + "plan": "3x8xCPU-32GB-200GB", + "core_number": 8, + "storage_size": 204800, + "memory_amount": 32768 + }, + { + "backup_config": { + "interval": 24, + "max_count": 31, + "recovery_mode": "pitr" + }, + "node_count": 3, + "zones": { + "zone": [ + { + "name": "nl-ams1" + }, + { + "name": "us-sjo1" + }, + { + "name": "au-syd1" + }, + { + "name": "fi-hel1" + }, + { + "name": "fi-hel2" + }, + { + "name": "sg-sin1" + }, + { + "name": "uk-lon1" + }, + { + "name": "pl-waw1" + }, + { + "name": "us-nyc1" + }, + { + "name": "de-fra1" + }, + { + "name": "es-mad1" + }, + { + "name": "us-chi1" + } + ] + }, + "plan": "3x8xCPU-32GB-500GB", + "core_number": 8, + "storage_size": 512000, + "memory_amount": 32768 + } + ], + "properties": { + "admin_password": { + "createOnly": true, + "example": "z66o9QXqKM", + "maxLength": 256, + "minLength": 8, + "pattern": "^[a-zA-Z0-9-_]+$", + "title": "Custom password for admin user. Defaults to random string. This must be set only when a new service is being created.", + "type": [ + "string", + "null" + ], + "user_error": "Must consist of alpha-numeric characters, underscores or dashes" + }, + "admin_username": { + "createOnly": true, + "example": "avnadmin", + "maxLength": 64, + "pattern": "^[_A-Za-z0-9][-._A-Za-z0-9]{0,63}$", + "title": "Custom username for admin user. This must be set only when a new service is being created.", + "type": [ + "string", + "null" + ], + "user_error": "Must consist of alpha-numeric characters, dots, underscores or dashes, may not start with dash or dot, max 64 characters" + }, + "automatic_utility_network_ip_filter": { + "default": true, + "title": "Automatic utility network IP Filter", + "type": "boolean", + "description": "Automatically allow connections from servers in the utility network within the same zone" + }, + "autovacuum_analyze_scale_factor": { + "title": "autovacuum_analyze_scale_factor", + "type": "number", + "description": "Specifies a fraction of the table size to add to autovacuum_analyze_threshold when deciding whether to trigger an ANALYZE. The default is 0.2 (20% of table size)", + "minimum": 0, + "maximum": 1 + }, + "autovacuum_analyze_threshold": { + "title": "autovacuum_analyze_threshold", + "type": "integer", + "description": "Specifies the minimum number of inserted, updated or deleted tuples needed to trigger an ANALYZE in any one table. The default is 50 tuples.", + "minimum": 0, + "maximum": 2147483647 + }, + "autovacuum_freeze_max_age": { + "example": 200000000, + "title": "autovacuum_freeze_max_age", + "type": "integer", + "description": "Specifies the maximum age (in transactions) that a table's pg_class.relfrozenxid field can attain before a VACUUM operation is forced to prevent transaction ID wraparound within the table. Note that the system will launch autovacuum processes to prevent wraparound even when autovacuum is otherwise disabled. This parameter will cause the server to be restarted.", + "minimum": 200000000, + "maximum": 1500000000 + }, + "autovacuum_max_workers": { + "title": "autovacuum_max_workers", + "type": "integer", + "description": "Specifies the maximum number of autovacuum processes (other than the autovacuum launcher) that may be running at any one time. The default is three. This parameter can only be set at server start.", + "minimum": 1, + "maximum": 20 + }, + "autovacuum_naptime": { + "title": "autovacuum_naptime", + "type": "integer", + "description": "Specifies the minimum delay between autovacuum runs on any given database. The delay is measured in seconds, and the default is one minute", + "minimum": 0, + "maximum": 86400 + }, + "autovacuum_vacuum_cost_delay": { + "title": "autovacuum_vacuum_cost_delay", + "type": "integer", + "description": "Specifies the cost delay value that will be used in automatic VACUUM operations. If -1 is specified, the regular vacuum_cost_delay value will be used. The default value is 20 milliseconds", + "minimum": -1, + "maximum": 100 + }, + "autovacuum_vacuum_cost_limit": { + "title": "autovacuum_vacuum_cost_limit", + "type": "integer", + "description": "Specifies the cost limit value that will be used in automatic VACUUM operations. If -1 is specified (which is the default), the regular vacuum_cost_limit value will be used.", + "minimum": -1, + "maximum": 10000 + }, + "autovacuum_vacuum_scale_factor": { + "title": "autovacuum_vacuum_scale_factor", + "type": "number", + "description": "Specifies a fraction of the table size to add to autovacuum_vacuum_threshold when deciding whether to trigger a VACUUM. The default is 0.2 (20% of table size)", + "minimum": 0, + "maximum": 1 + }, + "autovacuum_vacuum_threshold": { + "title": "autovacuum_vacuum_threshold", + "type": "integer", + "description": "Specifies the minimum number of updated or deleted tuples needed to trigger a VACUUM in any one table. The default is 50 tuples", + "minimum": 0, + "maximum": 2147483647 + }, + "backup_hour": { + "example": 3, + "title": "The hour of day (in UTC) when backup for the service is started. New backup is only started if previous backup has already completed.", + "type": [ + "integer", + "null" + ], + "minimum": 0, + "maximum": 23 + }, + "backup_minute": { + "example": 30, + "title": "The minute of an hour when backup for the service is started. New backup is only started if previous backup has already completed.", + "type": [ + "integer", + "null" + ], + "minimum": 0, + "maximum": 59 + }, + "bgwriter_delay": { + "example": 200, + "title": "bgwriter_delay", + "type": "integer", + "description": "Specifies the delay between activity rounds for the background writer in milliseconds. Default is 200.", + "minimum": 10, + "maximum": 10000 + }, + "bgwriter_flush_after": { + "example": 512, + "title": "bgwriter_flush_after", + "type": "integer", + "description": "Whenever more than bgwriter_flush_after bytes have been written by the background writer, attempt to force the OS to issue these writes to the underlying storage. Specified in kilobytes, default is 512. Setting of 0 disables forced writeback.", + "minimum": 0, + "maximum": 2048 + }, + "bgwriter_lru_maxpages": { + "example": 100, + "title": "bgwriter_lru_maxpages", + "type": "integer", + "description": "In each round, no more than this many buffers will be written by the background writer. Setting this to zero disables background writing. Default is 100.", + "minimum": 0, + "maximum": 1073741823 + }, + "bgwriter_lru_multiplier": { + "example": 2, + "title": "bgwriter_lru_multiplier", + "type": "number", + "description": "The average recent need for new buffers is multiplied by bgwriter_lru_multiplier to arrive at an estimate of the number that will be needed during the next round, (up to bgwriter_lru_maxpages). 1.0 represents a “just in time” policy of writing exactly the number of buffers predicted to be needed. Larger values provide some cushion against spikes in demand, while smaller values intentionally leave writes to be done by server processes. The default is 2.0.", + "minimum": 0, + "maximum": 10 + }, + "deadlock_timeout": { + "example": 1000, + "title": "deadlock_timeout", + "type": "integer", + "description": "This is the amount of time, in milliseconds, to wait on a lock before checking to see if there is a deadlock condition.", + "minimum": 500, + "maximum": 1800000 + }, + "idle_in_transaction_session_timeout": { + "title": "idle_in_transaction_session_timeout", + "type": "integer", + "description": "Time out sessions with open transactions after this number of milliseconds", + "minimum": 0, + "maximum": 604800000 + }, + "ip_filter": { + "default": [], + "title": "IP filter", + "type": "array", + "items": { + "maxLength": 18, + "title": "CIDR address block", + "type": "string" + }, + "maxItems": 1024, + "description": "Allow incoming connections from CIDR address block, e.g. '10.20.0.0/16'" + }, + "jit": { + "example": true, + "title": "jit", + "type": "boolean", + "description": "Controls system-wide use of Just-in-Time Compilation (JIT)." + }, + "log_autovacuum_min_duration": { + "title": "log_autovacuum_min_duration", + "type": "integer", + "description": "Causes each action executed by autovacuum to be logged if it ran for at least the specified number of milliseconds. Setting this to zero logs all autovacuum actions. Minus-one (the default) disables logging autovacuum actions.", + "minimum": -1, + "maximum": 2147483647 + }, + "log_error_verbosity": { + "title": "log_error_verbosity", + "type": "string", + "description": "Controls the amount of detail written in the server log for each message that is logged.", + "enum": [ + "TERSE", + "DEFAULT", + "VERBOSE" + ] + }, + "log_line_prefix": { + "title": "log_line_prefix", + "type": "string", + "description": "Choose from one of the available log-formats. These can support popular log analyzers like pgbadger, pganalyze etc.", + "enum": [ + "'%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h '", + "'%m [%p] %q[user=%u,db=%d,app=%a] '", + "'pid=%p,user=%u,db=%d,app=%a,client=%h '" + ] + }, + "log_min_duration_statement": { + "title": "log_min_duration_statement", + "type": "integer", + "description": "Log statements that take more than this number of milliseconds to run, -1 disables", + "minimum": -1, + "maximum": 86400000 + }, + "max_files_per_process": { + "title": "max_files_per_process", + "type": "integer", + "description": "PostgreSQL maximum number of files that can be open per process", + "minimum": 1000, + "maximum": 4096 + }, + "max_locks_per_transaction": { + "title": "max_locks_per_transaction", + "type": "integer", + "description": "PostgreSQL maximum locks per transaction", + "minimum": 64, + "maximum": 6400 + }, + "max_logical_replication_workers": { + "title": "max_logical_replication_workers", + "type": "integer", + "description": "PostgreSQL maximum logical replication workers (taken from the pool of max_parallel_workers)", + "minimum": 4, + "maximum": 64 + }, + "max_parallel_workers": { + "title": "max_parallel_workers", + "type": "integer", + "description": "Sets the maximum number of workers that the system can support for parallel queries", + "minimum": 0, + "maximum": 96 + }, + "max_parallel_workers_per_gather": { + "title": "max_parallel_workers_per_gather", + "type": "integer", + "description": "Sets the maximum number of workers that can be started by a single Gather or Gather Merge node", + "minimum": 0, + "maximum": 96 + }, + "max_pred_locks_per_transaction": { + "title": "max_pred_locks_per_transaction", + "type": "integer", + "description": "PostgreSQL maximum predicate locks per transaction", + "minimum": 64, + "maximum": 640 + }, + "max_prepared_transactions": { + "title": "max_prepared_transactions", + "type": "integer", + "description": "PostgreSQL maximum prepared transactions", + "minimum": 0, + "maximum": 10000 + }, + "max_replication_slots": { + "title": "max_replication_slots", + "type": "integer", + "description": "PostgreSQL maximum replication slots", + "minimum": 8, + "maximum": 64 + }, + "max_stack_depth": { + "title": "max_stack_depth", + "type": "integer", + "description": "Maximum depth of the stack in bytes", + "minimum": 2097152, + "maximum": 6291456 + }, + "max_standby_archive_delay": { + "title": "max_standby_archive_delay", + "type": "integer", + "description": "Max standby archive delay in milliseconds", + "minimum": 1, + "maximum": 43200000 + }, + "max_standby_streaming_delay": { + "title": "max_standby_streaming_delay", + "type": "integer", + "description": "Max standby streaming delay in milliseconds", + "minimum": 1, + "maximum": 43200000 + }, + "max_wal_senders": { + "title": "max_wal_senders", + "type": "integer", + "description": "PostgreSQL maximum WAL senders", + "minimum": 8, + "maximum": 64 + }, + "max_worker_processes": { + "title": "max_worker_processes", + "type": "integer", + "description": "Sets the maximum number of background processes that the system can support", + "minimum": 8, + "maximum": 96 + }, + "migration": { + "title": "Migrate data from existing server", + "type": [ + "object", + "null" + ], + "properties": { + "dbname": { + "example": "defaultdb", + "maxLength": 63, + "title": "Database name for bootstrapping the initial connection", + "type": "string" + }, + "host": { + "example": "my.server.com", + "maxLength": 255, + "title": "Hostname or IP address of the server where to migrate data from", + "type": "string" + }, + "ignore_dbs": { + "example": "db1,db2", + "maxLength": 2048, + "title": "Comma-separated list of databases, which should be ignored during migration (supported by MySQL only at the moment)", + "type": "string" + }, + "password": { + "example": "jjKk45Nnd", + "maxLength": 256, + "title": "Password for authentication with the server where to migrate data from", + "type": "string" + }, + "port": { + "example": 1234, + "title": "Port number of the server where to migrate data from", + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "ssl": { + "default": true, + "title": "The server where to migrate data from is secured with SSL", + "type": "boolean" + }, + "username": { + "example": "myname", + "maxLength": 256, + "title": "User name for authentication with the server where to migrate data from", + "type": "string" + } + }, + "required": [ + "host", + "port" + ] + }, + "pg_partman_bgw.interval": { + "example": 3600, + "title": "pg_partman_bgw.interval", + "type": "integer", + "description": "Sets the time interval to run pg_partman's scheduled tasks", + "minimum": 3600, + "maximum": 604800 + }, + "pg_partman_bgw.role": { + "example": "myrolename", + "maxLength": 64, + "pattern": "^[_A-Za-z0-9][-._A-Za-z0-9]{0,63}$", + "title": "pg_partman_bgw.role", + "type": "string", + "user_error": "Must consist of alpha-numeric characters, dots, underscores or dashes, may not start with dash or dot, max 64 characters", + "description": "Controls which role to use for pg_partman's scheduled background tasks." + }, + "pg_read_replica": { + "example": true, + "title": "Should the service which is being forked be a read replica", + "type": [ + "boolean", + "null" + ], + "description": "This setting is deprecated. Use read-replica service integration instead." + }, + "pg_service_to_fork_from": { + "createOnly": true, + "example": "anotherservicename", + "maxLength": 63, + "title": "Name of the PG Service from which to fork (deprecated, use service_to_fork_from). This has effect only when a new service is being created.", + "type": [ + "string", + "null" + ] + }, + "pg_stat_statements.track": { + "title": "pg_stat_statements.track", + "type": [ + "string" + ], + "description": "Controls which statements are counted. Specify top to track top-level statements (those issued directly by clients), all to also track nested statements (such as statements invoked within functions), or none to disable statement statistics collection. The default value is top.", + "enum": [ + "all", + "top", + "none" + ] + }, + "pgbouncer": { + "title": "PGBouncer connection pooling settings", + "type": "object", + "properties": { + "autodb_idle_timeout": { + "example": 3600, + "title": "If the automatically created database pools have been unused this many seconds, they are freed. If 0 then timeout is disabled. [seconds]", + "type": "integer", + "minimum": 0, + "maximum": 86400 + }, + "autodb_max_db_connections": { + "example": 0, + "title": "Do not allow more than this many server connections per database (regardless of user). Setting it to 0 means unlimited.", + "type": "integer", + "minimum": 0, + "maximum": 2147483647 + }, + "autodb_pool_mode": { + "example": "session", + "title": "PGBouncer pool mode", + "type": "string", + "enum": [ + "session", + "transaction", + "statement" + ] + }, + "autodb_pool_size": { + "example": 0, + "title": "If non-zero then create automatically a pool of that size per user when a pool doesn't exist.", + "type": "integer", + "minimum": 0, + "maximum": 10000 + }, + "ignore_startup_parameters": { + "example": [ + "extra_float_digits", + "search_path" + ], + "title": "List of parameters to ignore when given in startup packet", + "type": "array", + "items": { + "title": "Enum of parameters to ignore when given in startup packet", + "type": "string" + }, + "maxItems": 32 + }, + "min_pool_size": { + "example": 0, + "title": "Add more server connections to pool if below this number. Improves behavior when usual load comes suddenly back after period of total inactivity. The value is effectively capped at the pool size.", + "type": "integer", + "minimum": 0, + "maximum": 10000 + }, + "server_idle_timeout": { + "example": 600, + "title": "If a server connection has been idle more than this many seconds it will be dropped. If 0 then timeout is disabled. [seconds]", + "type": "integer", + "minimum": 0, + "maximum": 86400 + }, + "server_lifetime": { + "example": 3600, + "title": "The pooler will close an unused server connection that has been connected longer than this. [seconds]", + "type": "integer", + "minimum": 60, + "maximum": 86400 + }, + "server_reset_query_always": { + "example": false, + "title": "Run server_reset_query (DISCARD ALL) in all pooling modes", + "type": "boolean" + } + } + }, + "pglookout": { + "default": { + "max_failover_replication_time_lag": 60 + }, + "title": "PGLookout settings", + "type": "object", + "properties": { + "max_failover_replication_time_lag": { + "default": 60, + "title": "max_failover_replication_time_lag", + "type": "integer", + "description": "Number of seconds of master unavailability before triggering database failover to standby", + "minimum": 10, + "maximum": 9223372036854776000 + } + } + }, + "public_access": { + "default": false, + "title": "Public Access", + "type": "boolean", + "description": "Allow access to the service from the public Internet" + }, + "shared_buffers_percentage": { + "example": 41.5, + "title": "shared_buffers_percentage", + "type": "number", + "description": "Percentage of total RAM that the database server uses for shared memory buffers. Valid range is 20-60 (float), which corresponds to 20% - 60%. This setting adjusts the shared_buffers configuration value.", + "minimum": 20, + "maximum": 60 + }, + "synchronous_replication": { + "example": "off", + "title": "Synchronous replication type. Note that the service plan also needs to support synchronous replication.", + "type": "string", + "enum": [ + "quorum", + "off" + ] + }, + "temp_file_limit": { + "example": 5000000, + "title": "temp_file_limit", + "type": "integer", + "description": "PostgreSQL temporary file limit in KiB, -1 for unlimited", + "minimum": -1, + "maximum": 2147483647 + }, + "timescaledb": { + "title": "TimescaleDB extension configuration values", + "type": "object", + "properties": { + "max_background_workers": { + "example": 8, + "title": "timescaledb.max_background_workers", + "type": "integer", + "description": "The number of background workers for timescaledb operations. You should configure this setting to the sum of your number of databases and the total number of concurrent background workers you want running at any given point in time.", + "minimum": 1, + "maximum": 4096 + } + } + }, + "timezone": { + "example": "Europe/Helsinki", + "maxLength": 64, + "title": "timezone", + "type": "string", + "description": "PostgreSQL service timezone" + }, + "track_activity_query_size": { + "example": 1024, + "title": "track_activity_query_size", + "type": "integer", + "description": "Specifies the number of bytes reserved to track the currently executing command for each active session.", + "minimum": 1024, + "maximum": 10240 + }, + "track_commit_timestamp": { + "example": "off", + "title": "track_commit_timestamp", + "type": "string", + "description": "Record commit time of transactions.", + "enum": [ + "off", + "on" + ] + }, + "track_functions": { + "title": "track_functions", + "type": "string", + "description": "Enables tracking of function call counts and time used.", + "enum": [ + "all", + "pl", + "none" + ] + }, + "track_io_timing": { + "example": "off", + "title": "track_io_timing", + "type": "string", + "description": "Enables timing of database I/O calls. This parameter is off by default, because it will repeatedly query the operating system for the current time, which may cause significant overhead on some platforms.", + "enum": [ + "off", + "on" + ] + }, + "variant": { + "example": "aiven", + "title": "Variant of the PostgreSQL service, may affect the features that are exposed by default", + "type": [ + "string", + "null" + ], + "enum": [ + "aiven", + "timescale" + ] + }, + "version": { + "title": "PostgreSQL major version", + "type": [ + "string", + "null" + ], + "enum": [ + "12", + "13" + ] + }, + "wal_sender_timeout": { + "example": 60000, + "title": "wal_sender_timeout", + "type": "integer", + "user_error": "Must be either 0 or between 5000 and 10800000.", + "description": "Terminate replication connections that are inactive for longer than this amount of time, in milliseconds. Setting this value to zero disables the timeout." + }, + "wal_writer_delay": { + "example": 50, + "title": "wal_writer_delay", + "type": "integer", + "description": "WAL flush interval in milliseconds. Note that setting this value to lower than the default 200ms may negatively impact performance", + "minimum": 10, + "maximum": 200 + }, + "work_mem": { + "example": 4, + "title": "work_mem", + "type": "integer", + "description": "Sets the maximum amount of memory to be used by a query operation (such as a sort or hash table) before writing to temporary disk files, in MB. Default is 1MB + 0.075% of total RAM (up to 32MB).", + "minimum": 1, + "maximum": 1024 + } + } + } +} \ No newline at end of file diff --git a/upcloud/upcloudschema/terraform.go b/upcloud/upcloudschema/terraform.go new file mode 100644 index 00000000..d2b5b3be --- /dev/null +++ b/upcloud/upcloudschema/terraform.go @@ -0,0 +1,93 @@ +package upcloudschema + +import ( + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// FnGenerateTerraformSchemaOverride can be used to override the schema generation of GenerateTerraformSchemaFromJSONSchema. +// As a schema can contain multiple nested sub-schemas, passing a function with this signature allows one to inspect the +// current key and the proposed schema. The function can then change the proposedSchema as they see fit. +type FnGenerateTerraformSchemaOverride func(keyPath []string, proposedSchema *schema.Schema, source map[string]interface{}) + +// GenerateTerraformSchemaFromJSONSchema generates a Terraform schema from a subset of JSON schema +// The root json schema must be of type "object" and this function expects you to pass the "properties" field of the +// schema object. +func GenerateTerraformSchemaFromJSONSchema(objectProperties map[string]interface{}, override FnGenerateTerraformSchemaOverride) map[string]*schema.Schema { + return generateTerraformSchemaFromJSONSchema([]string{}, objectProperties, override) +} + +func generateTerraformSchemaFromJSONSchema(keyPath []string, objectProperties map[string]interface{}, override FnGenerateTerraformSchemaOverride) map[string]*schema.Schema { + keyReplacer := strings.NewReplacer(".", "_") + r := make(map[string]*schema.Schema) + for k, v := range objectProperties { + k = keyReplacer.Replace(k) + newKeyPath := append([]string{}, keyPath...) + newKeyPath = append(newKeyPath, k) + r[k] = terraformSchemaForSingle(newKeyPath, v.(map[string]interface{}), override) + } + return r +} + +func terraformSchemaForSingle(keyPath []string, jsonSchema map[string]interface{}, override FnGenerateTerraformSchemaOverride) *schema.Schema { + typeForJSONSchemaType := func(jsonSchemaType interface{}) (r string) { + switch v := jsonSchemaType.(type) { + case string: + r = v + case []interface{}: + for _, jsonType := range v { + if _, ok := jsonType.(string); !ok { + panic(fmt.Sprintf("invalid json type value %T (%v)", jsonType, jsonSchema)) + } + if jsonType == "null" { + continue + } + r = jsonType.(string) + break + } + default: + panic(fmt.Sprintf("invalid json type value %T (%v) (key %s)", v, v, strings.Join(keyPath, "."))) + } + return r + } + + r := &schema.Schema{ + Description: jsonSchema["title"].(string), + } + + valueType := typeForJSONSchemaType(jsonSchema["type"]) + switch valueType { + case "number": + r.Type = schema.TypeFloat + case "integer": + r.Type = schema.TypeInt + case "string": + r.Type = schema.TypeString + case "boolean": + r.Type = schema.TypeBool + case "object": + r.Type = schema.TypeList + r.MaxItems = 1 + r.Elem = &schema.Resource{Schema: generateTerraformSchemaFromJSONSchema(keyPath, jsonSchema["properties"].(map[string]interface{}), override)} + case "array": + r.Type = schema.TypeList + if v, ok := jsonSchema["maxItems"]; ok { + r.MaxItems = int(v.(float64)) + } + itemValueSchema := jsonSchema["items"].(map[string]interface{}) + if itemValueSchema["type"] == "object" { + r.Elem = &schema.Resource{Schema: generateTerraformSchemaFromJSONSchema(keyPath, itemValueSchema["properties"].(map[string]interface{}), override)} + } else { + r.Elem = terraformSchemaForSingle(nil, itemValueSchema, nil) + } + default: + panic(fmt.Sprintf("non-supported json schema type %v (key %s)", valueType, strings.Join(keyPath, "."))) + } + + if override != nil { + override(keyPath, r, jsonSchema) + } + return r +} diff --git a/upcloud/upcloudschema/terraform_test.go b/upcloud/upcloudschema/terraform_test.go new file mode 100644 index 00000000..a4ff3cf2 --- /dev/null +++ b/upcloud/upcloudschema/terraform_test.go @@ -0,0 +1,28 @@ +package upcloudschema + +import ( + "testing" + + "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/stretchr/testify/assert" +) + +func TestGenerateTerraformSchemaFromJSONSchema(t *testing.T) { + for _, serviceType := range []upcloud.ManagedDatabaseServiceType{ + upcloud.ManagedDatabaseServiceTypePostgreSQL, + upcloud.ManagedDatabaseServiceTypeMySQL, + } { + t.Run(string(serviceType), func(t *testing.T) { + jsonSchema := ManagedDatabaseServicePropertiesSchema(serviceType) + tfSchema := GenerateTerraformSchemaFromJSONSchema(jsonSchema, func(keyPath []string, proposedSchema *schema.Schema, source map[string]interface{}) { + assert.NotEmpty(t, keyPath) + assert.NotNil(t, proposedSchema) + assert.NotNil(t, source) + }) + if !assert.NotEmpty(t, tfSchema) { + return + } + }) + } +} From 3a852fc7dbd21d39e3928ceb43c8cdd649f4201e Mon Sep 17 00:00:00 2001 From: Pekka Nurmi Date: Fri, 17 Dec 2021 13:06:19 +0200 Subject: [PATCH 2/6] feat(resource): implement managed database resource types Wait managed database to be fully created and running before shutdown. --- upcloud/resource_upcloud_managed_database.go | 69 +++++++++++++------ .../resource_upcloud_managed_database_test.go | 30 +++++++- 2 files changed, 78 insertions(+), 21 deletions(-) diff --git a/upcloud/resource_upcloud_managed_database.go b/upcloud/resource_upcloud_managed_database.go index 9a0915d9..61a70eab 100644 --- a/upcloud/resource_upcloud_managed_database.go +++ b/upcloud/resource_upcloud_managed_database.go @@ -2,6 +2,7 @@ package upcloud import ( "context" + "errors" "fmt" "log" "strings" @@ -291,9 +292,27 @@ func resourceUpCloudManagedDatabaseCreate(serviceType upcloud.ManagedDatabaseSer return diag.FromErr(err) } d.SetId(details.UUID) + log.Printf("[INFO] managed database %v (%v) created", details.UUID, d.Get("name")) - return resourceUpCloudManagedDatabaseUpdate(ctx, d, meta) + if !d.Get("powered").(bool) { + if err = waitManagedDatabaseFullyCreated(ctx, client, details); err != nil { + // return warning so that next apply will only shutdown database instead of recreating it + d := resourceUpCloudManagedDatabaseRead(ctx, d, meta) + d = append(d, diag.Diagnostic{ + Severity: diag.Warning, + Summary: err.Error(), + }) + return d + } + + _, err := client.ShutdownManagedDatabase(&request.ShutdownManagedDatabaseRequest{UUID: d.Id()}) + if err != nil { + return diag.FromErr(err) + } + log.Printf("[INFO] managed database %v (%v) is powered off", d.Id(), d.Get("name")) + } + return resourceUpCloudManagedDatabaseRead(ctx, d, meta) } } @@ -312,10 +331,7 @@ func resourceUpCloudManagedDatabaseRead(ctx context.Context, d *schema.ResourceD func resourceUpCloudManagedDatabaseUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { client := meta.(*service.Service) - var updated bool - - // Skip modifying these changes if are creating a new resource - if !d.IsNewResource() && d.HasChanges("plan", "title", "zone", + if d.HasChanges("plan", "title", "zone", "maintenance_window_dow", "maintenance_window_time", "properties.0") { req := request.ModifyManagedDatabaseRequest{UUID: d.Id()} req.Plan = d.Get("plan").(string) @@ -339,36 +355,25 @@ func resourceUpCloudManagedDatabaseUpdate(ctx context.Context, d *schema.Resourc if err != nil { return diag.FromErr(err) } - updated = true + log.Printf("[INFO] managed database %v (%v) updated", d.Id(), d.Get("name")) } - // Weirdly, if this is a new resource then we need to evaluate the power state as the defaults are ignored - // in terraform in create phase. - if d.IsNewResource() || d.HasChange("powered") { - doPowerOn := !d.IsNewResource() && d.Get("powered").(bool) - doPowerOff := !d.Get("powered").(bool) - if doPowerOn { + if d.HasChange("powered") { + if d.Get("powered").(bool) { _, err := client.StartManagedDatabase(&request.StartManagedDatabaseRequest{UUID: d.Id()}) if err != nil { return diag.FromErr(err) } log.Printf("[INFO] managed database %v (%v) is powered on", d.Id(), d.Get("name")) - updated = true - } - if doPowerOff { + } else { _, err := client.ShutdownManagedDatabase(&request.ShutdownManagedDatabaseRequest{UUID: d.Id()}) if err != nil { return diag.FromErr(err) } log.Printf("[INFO] managed database %v (%v) is powered off", d.Id(), d.Get("name")) - updated = true } } - if updated { - log.Printf("[INFO] managed database %v (%v) updated", d.Id(), d.Get("name")) - } - return resourceUpCloudManagedDatabaseRead(ctx, d, meta) } @@ -692,3 +697,27 @@ func buildManagedDatabasePropertiesSchema(serviceType upcloud.ManagedDatabaseSer ) return upcloudschema.GenerateTerraformSchemaFromJSONSchema(jsonSchema, overrides) } + +func waitManagedDatabaseFullyCreated(ctx context.Context, client *service.Service, db *upcloud.ManagedDatabase) error { + const maxRetries int = 100 + var err error + for i := 0; i <= maxRetries; i++ { + select { + case <-ctx.Done(): + return ctx.Err() + default: + if db, err = client.GetManagedDatabase(&request.GetManagedDatabaseRequest{UUID: db.UUID}); err != nil { + return err + } + if isManagedDatabaseFullyCreated(db) { + return nil + } + } + time.Sleep(5 * time.Second) + } + return errors.New("max retries reached while waiting for managed database instance to be created") +} + +func isManagedDatabaseFullyCreated(db *upcloud.ManagedDatabase) bool { + return db.State == upcloud.ManagedDatabaseStateRunning && len(db.Backups) > 0 && len(db.Users) > 0 +} diff --git a/upcloud/resource_upcloud_managed_database_test.go b/upcloud/resource_upcloud_managed_database_test.go index 508d0d02..e8886f22 100644 --- a/upcloud/resource_upcloud_managed_database_test.go +++ b/upcloud/resource_upcloud_managed_database_test.go @@ -98,7 +98,7 @@ func TestAccUpcloudManagedDatabasePostgreSQL_CreateAsPoweredOff(t *testing.T) { plan = "1x1xCPU-2GB-25GB" title = "testtitle" zone = "fi-hel1" - powered = "true" + powered = true }`, rName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceIdentifier, "powered", "true"), @@ -137,3 +137,31 @@ func TestAccUpcloudManagedDatabaseMySQL_Create(t *testing.T) { }, }) } + +func TestIsManagedDatabaseFullyCreated(t *testing.T) { + db := &upcloud.ManagedDatabase{ + Backups: make([]upcloud.ManagedDatabaseBackup, 0), + State: upcloud.ManagedDatabaseStatePoweroff, + Users: make([]upcloud.ManagedDatabaseUser, 0), + } + if isManagedDatabaseFullyCreated(db) { + t.Errorf("isManagedDatabaseFullyCreated failed want false got true %+v", db) + } + + db.State = upcloud.ManagedDatabaseStateRunning + db.Backups = append(db.Backups, upcloud.ManagedDatabaseBackup{}) + if isManagedDatabaseFullyCreated(db) { + t.Errorf("isManagedDatabaseFullyCreated failed want false got true %+v", db) + } + + db.Users = append(db.Users, upcloud.ManagedDatabaseUser{}) + db.Backups = make([]upcloud.ManagedDatabaseBackup, 0) + if isManagedDatabaseFullyCreated(db) { + t.Errorf("isManagedDatabaseFullyCreated failed want false got true %+v", db) + } + + db.Backups = append(db.Backups, upcloud.ManagedDatabaseBackup{}) + if !isManagedDatabaseFullyCreated(db) { + t.Errorf("isManagedDatabaseFullyCreated failed want true got false %+v", db) + } +} From 3b1f97118f493253006df22022f468b8a370673e Mon Sep 17 00:00:00 2001 From: Pekka Nurmi Date: Wed, 22 Dec 2021 16:56:13 +0200 Subject: [PATCH 3/6] chore(docs): Add DBaaS documents and examples --- .../managed_database_logical_database.md | 43 ++++ docs/resources/managed_database_mysql.md | 155 +++++++++++++ docs/resources/managed_database_postgresql.md | 206 ++++++++++++++++++ docs/resources/managed_database_user.md | 47 ++++ .../resource.tf | 11 + .../resource.tf | 30 +++ .../resource.tf | 20 ++ .../upcloud_managed_database_user/resource.tf | 12 + 8 files changed, 524 insertions(+) create mode 100644 docs/resources/managed_database_logical_database.md create mode 100644 docs/resources/managed_database_mysql.md create mode 100644 docs/resources/managed_database_postgresql.md create mode 100644 docs/resources/managed_database_user.md create mode 100644 examples/resources/upcloud_managed_database_logical_database/resource.tf create mode 100644 examples/resources/upcloud_managed_database_mysql/resource.tf create mode 100644 examples/resources/upcloud_managed_database_postgresql/resource.tf create mode 100644 examples/resources/upcloud_managed_database_user/resource.tf diff --git a/docs/resources/managed_database_logical_database.md b/docs/resources/managed_database_logical_database.md new file mode 100644 index 00000000..e3c931a6 --- /dev/null +++ b/docs/resources/managed_database_logical_database.md @@ -0,0 +1,43 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "upcloud_managed_database_logical_database Resource - terraform-provider-upcloud" +subcategory: "" +description: |- + +--- + +# upcloud_managed_database_logical_database (Resource) + + + +## Example Usage + +```terraform +resource "upcloud_managed_database_postgresql" "example" { + name = "postgres" + plan = "1x1xCPU-2GB-25GB" + title = "postgres" + zone = "fi-hel1" +} + +resource "upcloud_managed_database_logical_database" "example_db" { + service = upcloud_managed_database_postgresql.example.id + name = "example_db" +} +``` + + +## Schema + +### Required + +- **name** (String) Name of the logical database +- **service** (String) Service's UUID for which this user belongs to + +### Optional + +- **character_set** (String) Default character set for the database (LC_CTYPE) +- **collation** (String) Default collation for the database (LC_COLLATE) +- **id** (String) The ID of this resource. + + diff --git a/docs/resources/managed_database_mysql.md b/docs/resources/managed_database_mysql.md new file mode 100644 index 00000000..8a1e32e9 --- /dev/null +++ b/docs/resources/managed_database_mysql.md @@ -0,0 +1,155 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "upcloud_managed_database_mysql Resource - terraform-provider-upcloud" +subcategory: "" +description: |- + +--- + +# upcloud_managed_database_mysql (Resource) + + + +## Example Usage + +```terraform +# Minimal config +resource "upcloud_managed_database_mysql" "example_1" { + name = "mysql-1" + plan = "1x1xCPU-2GB-25GB" + zone = "fi-hel1" +} + +# Shutdown instance after creation +resource "upcloud_managed_database_mysql" "example_2" { + name = "mysql-2" + plan = "1x1xCPU-2GB-25GB" + zone = "fi-hel1" + powered = false +} + +# Service with custom properties +# Note that this basically sets strict mode off which is not normally recommended +resource "upcloud_managed_database_mysql" "example_3" { + name = "mysql-3" + plan = "1x1xCPU-2GB-25GB" + zone = "fi-hel1" + properties { + sql_mode = "NO_ENGINE_SUBSTITUTION" + wait_timeout = 300 + sort_buffer_size = 4e+6 # 4MB + max_allowed_packet = 16e+6 # 16MB + admin_username = "admin" + admin_password = "" + } +} +``` + + +## Schema + +### Required + +- **name** (String) Name of the service. The name is used as a prefix for the logical hostname. Must be unique within an account +- **plan** (String) Service plan to use. This determines how much resources the instance will have +- **zone** (String) Zone where the instance resides + +### Optional + +- **id** (String) The ID of this resource. +- **maintenance_window_dow** (String) Maintenance window day of week. Lower case weekday name (monday, tuesday, ...) +- **maintenance_window_time** (String) Maintenance window UTC time in hh:mm:ss format +- **powered** (Boolean) The administrative power state of the service +- **properties** (Block List, Max: 1) Database Engine properties for MySQL (see [below for nested schema](#nestedblock--properties)) +- **title** (String) Title of a managed database instance + +### Read-Only + +- **components** (List of Object) Service component information (see [below for nested schema](#nestedatt--components)) +- **node_states** (List of Object) Information about nodes providing the managed service (see [below for nested schema](#nestedatt--node_states)) +- **primary_database** (String) Primary database name +- **service_host** (String) Hostname to the service instance +- **service_password** (String, Sensitive) Primary username's password to the service instance +- **service_port** (String) Port to the service instance +- **service_uri** (String, Sensitive) URI to the service instance +- **service_username** (String) Primary username to the service instance +- **state** (String) State of the service +- **type** (String) Type of the service + + +### Nested Schema for `properties` + +Optional: + +- **admin_password** (String, Sensitive) Custom password for admin user. Defaults to random string. This must be set only when a new service is being created. +- **admin_username** (String) Custom username for admin user. This must be set only when a new service is being created. +- **automatic_utility_network_ip_filter** (Boolean) Automatic utility network IP Filter +- **backup_hour** (Number) The hour of day (in UTC) when backup for the service is started. New backup is only started if previous backup has already completed. +- **backup_minute** (Number) The minute of an hour when backup for the service is started. New backup is only started if previous backup has already completed. +- **binlog_retention_period** (Number) The minimum amount of time in seconds to keep binlog entries before deletion. This may be extended for services that require binlog entries for longer than the default for example if using the MySQL Debezium Kafka connector. +- **connect_timeout** (Number) connect_timeout +- **default_time_zone** (String) default_time_zone +- **group_concat_max_len** (Number) group_concat_max_len +- **information_schema_stats_expiry** (Number) information_schema_stats_expiry +- **innodb_ft_min_token_size** (Number) innodb_ft_min_token_size +- **innodb_ft_server_stopword_table** (String) innodb_ft_server_stopword_table +- **innodb_lock_wait_timeout** (Number) innodb_lock_wait_timeout +- **innodb_log_buffer_size** (Number) innodb_log_buffer_size +- **innodb_online_alter_log_max_size** (Number) innodb_online_alter_log_max_size +- **innodb_print_all_deadlocks** (Boolean) innodb_print_all_deadlocks +- **innodb_rollback_on_timeout** (Boolean) innodb_rollback_on_timeout +- **interactive_timeout** (Number) interactive_timeout +- **internal_tmp_mem_storage_engine** (String) internal_tmp_mem_storage_engine +- **ip_filter** (List of String) IP filter +- **long_query_time** (Number) long_query_time +- **max_allowed_packet** (Number) max_allowed_packet +- **max_heap_table_size** (Number) max_heap_table_size +- **migration** (Block List, Max: 1) Migrate data from existing server (see [below for nested schema](#nestedblock--properties--migration)) +- **net_read_timeout** (Number) net_read_timeout +- **net_write_timeout** (Number) net_write_timeout +- **public_access** (Boolean) Public Access +- **slow_query_log** (Boolean) slow_query_log +- **sort_buffer_size** (Number) sort_buffer_size +- **sql_mode** (String) sql_mode +- **sql_require_primary_key** (Boolean) sql_require_primary_key +- **tmp_table_size** (Number) tmp_table_size +- **version** (String) MySQL major version +- **wait_timeout** (Number) wait_timeout + + +### Nested Schema for `properties.migration` + +Optional: + +- **dbname** (String) Database name for bootstrapping the initial connection +- **host** (String) Hostname or IP address of the server where to migrate data from +- **ignore_dbs** (String) Comma-separated list of databases, which should be ignored during migration (supported by MySQL only at the moment) +- **password** (String, Sensitive) Password for authentication with the server where to migrate data from +- **port** (Number) Port number of the server where to migrate data from +- **ssl** (Boolean) The server where to migrate data from is secured with SSL +- **username** (String) User name for authentication with the server where to migrate data from + + + + +### Nested Schema for `components` + +Read-Only: + +- **component** (String) +- **host** (String) +- **port** (Number) +- **route** (String) +- **usage** (String) + + + +### Nested Schema for `node_states` + +Read-Only: + +- **name** (String) +- **role** (String) +- **state** (String) + + diff --git a/docs/resources/managed_database_postgresql.md b/docs/resources/managed_database_postgresql.md new file mode 100644 index 00000000..3a74826b --- /dev/null +++ b/docs/resources/managed_database_postgresql.md @@ -0,0 +1,206 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "upcloud_managed_database_postgresql Resource - terraform-provider-upcloud" +subcategory: "" +description: |- + +--- + +# upcloud_managed_database_postgresql (Resource) + + + +## Example Usage + +```terraform +# Minimal config +resource "upcloud_managed_database_postgresql" "example_1" { + name = "postgres-1" + plan = "1x1xCPU-2GB-25GB" + title = "postgres" + zone = "fi-hel1" +} + +# Service with custom properties +resource "upcloud_managed_database_postgresql" "example_2" { + name = "postgres-2" + plan = "1x1xCPU-2GB-25GB" + title = "postgres" + zone = "fi-hel1" + properties { + timezone = "Europe/Helsinki" + admin_username = "admin" + admin_password = "" + } +} +``` + + +## Schema + +### Required + +- **name** (String) Name of the service. The name is used as a prefix for the logical hostname. Must be unique within an account +- **plan** (String) Service plan to use. This determines how much resources the instance will have +- **zone** (String) Zone where the instance resides + +### Optional + +- **id** (String) The ID of this resource. +- **maintenance_window_dow** (String) Maintenance window day of week. Lower case weekday name (monday, tuesday, ...) +- **maintenance_window_time** (String) Maintenance window UTC time in hh:mm:ss format +- **powered** (Boolean) The administrative power state of the service +- **properties** (Block List, Max: 1) Database Engine properties for PostgreSQL (see [below for nested schema](#nestedblock--properties)) +- **title** (String) Title of a managed database instance + +### Read-Only + +- **components** (List of Object) Service component information (see [below for nested schema](#nestedatt--components)) +- **node_states** (List of Object) Information about nodes providing the managed service (see [below for nested schema](#nestedatt--node_states)) +- **primary_database** (String) Primary database name +- **service_host** (String) Hostname to the service instance +- **service_password** (String, Sensitive) Primary username's password to the service instance +- **service_port** (String) Port to the service instance +- **service_uri** (String, Sensitive) URI to the service instance +- **service_username** (String) Primary username to the service instance +- **sslmode** (String) SSL Connection Mode for PostgreSQL +- **state** (String) State of the service +- **type** (String) Type of the service + + +### Nested Schema for `properties` + +Optional: + +- **admin_password** (String, Sensitive) Custom password for admin user. Defaults to random string. This must be set only when a new service is being created. +- **admin_username** (String) Custom username for admin user. This must be set only when a new service is being created. +- **automatic_utility_network_ip_filter** (Boolean) Automatic utility network IP Filter +- **autovacuum_analyze_scale_factor** (Number) autovacuum_analyze_scale_factor +- **autovacuum_analyze_threshold** (Number) autovacuum_analyze_threshold +- **autovacuum_freeze_max_age** (Number) autovacuum_freeze_max_age +- **autovacuum_max_workers** (Number) autovacuum_max_workers +- **autovacuum_naptime** (Number) autovacuum_naptime +- **autovacuum_vacuum_cost_delay** (Number) autovacuum_vacuum_cost_delay +- **autovacuum_vacuum_cost_limit** (Number) autovacuum_vacuum_cost_limit +- **autovacuum_vacuum_scale_factor** (Number) autovacuum_vacuum_scale_factor +- **autovacuum_vacuum_threshold** (Number) autovacuum_vacuum_threshold +- **backup_hour** (Number) The hour of day (in UTC) when backup for the service is started. New backup is only started if previous backup has already completed. +- **backup_minute** (Number) The minute of an hour when backup for the service is started. New backup is only started if previous backup has already completed. +- **bgwriter_delay** (Number) bgwriter_delay +- **bgwriter_flush_after** (Number) bgwriter_flush_after +- **bgwriter_lru_maxpages** (Number) bgwriter_lru_maxpages +- **bgwriter_lru_multiplier** (Number) bgwriter_lru_multiplier +- **deadlock_timeout** (Number) deadlock_timeout +- **idle_in_transaction_session_timeout** (Number) idle_in_transaction_session_timeout +- **ip_filter** (List of String) IP filter +- **jit** (Boolean) jit +- **log_autovacuum_min_duration** (Number) log_autovacuum_min_duration +- **log_error_verbosity** (String) log_error_verbosity +- **log_line_prefix** (String) log_line_prefix +- **log_min_duration_statement** (Number) log_min_duration_statement +- **max_files_per_process** (Number) max_files_per_process +- **max_locks_per_transaction** (Number) max_locks_per_transaction +- **max_logical_replication_workers** (Number) max_logical_replication_workers +- **max_parallel_workers** (Number) max_parallel_workers +- **max_parallel_workers_per_gather** (Number) max_parallel_workers_per_gather +- **max_pred_locks_per_transaction** (Number) max_pred_locks_per_transaction +- **max_prepared_transactions** (Number) max_prepared_transactions +- **max_replication_slots** (Number) max_replication_slots +- **max_stack_depth** (Number) max_stack_depth +- **max_standby_archive_delay** (Number) max_standby_archive_delay +- **max_standby_streaming_delay** (Number) max_standby_streaming_delay +- **max_wal_senders** (Number) max_wal_senders +- **max_worker_processes** (Number) max_worker_processes +- **migration** (Block List, Max: 1) Migrate data from existing server (see [below for nested schema](#nestedblock--properties--migration)) +- **pg_partman_bgw_interval** (Number) pg_partman_bgw.interval +- **pg_partman_bgw_role** (String) pg_partman_bgw.role +- **pg_read_replica** (Boolean) Should the service which is being forked be a read replica +- **pg_service_to_fork_from** (String) Name of the PG Service from which to fork (deprecated, use service_to_fork_from). This has effect only when a new service is being created. +- **pg_stat_statements_track** (String) pg_stat_statements.track +- **pgbouncer** (Block List, Max: 1) PGBouncer connection pooling settings (see [below for nested schema](#nestedblock--properties--pgbouncer)) +- **pglookout** (Block List, Max: 1) PGLookout settings (see [below for nested schema](#nestedblock--properties--pglookout)) +- **public_access** (Boolean) Public Access +- **shared_buffers_percentage** (Number) shared_buffers_percentage +- **synchronous_replication** (String) Synchronous replication type. Note that the service plan also needs to support synchronous replication. +- **temp_file_limit** (Number) temp_file_limit +- **timescaledb** (Block List, Max: 1) TimescaleDB extension configuration values (see [below for nested schema](#nestedblock--properties--timescaledb)) +- **timezone** (String) timezone +- **track_activity_query_size** (Number) track_activity_query_size +- **track_commit_timestamp** (String) track_commit_timestamp +- **track_functions** (String) track_functions +- **track_io_timing** (String) track_io_timing +- **variant** (String) Variant of the PostgreSQL service, may affect the features that are exposed by default +- **version** (String) PostgreSQL major version +- **wal_sender_timeout** (Number) wal_sender_timeout +- **wal_writer_delay** (Number) wal_writer_delay +- **work_mem** (Number) work_mem + + +### Nested Schema for `properties.migration` + +Optional: + +- **dbname** (String) Database name for bootstrapping the initial connection +- **host** (String) Hostname or IP address of the server where to migrate data from +- **ignore_dbs** (String) Comma-separated list of databases, which should be ignored during migration (supported by MySQL only at the moment) +- **password** (String, Sensitive) Password for authentication with the server where to migrate data from +- **port** (Number) Port number of the server where to migrate data from +- **ssl** (Boolean) The server where to migrate data from is secured with SSL +- **username** (String) User name for authentication with the server where to migrate data from + + + +### Nested Schema for `properties.pgbouncer` + +Optional: + +- **autodb_idle_timeout** (Number) If the automatically created database pools have been unused this many seconds, they are freed. If 0 then timeout is disabled. [seconds] +- **autodb_max_db_connections** (Number) Do not allow more than this many server connections per database (regardless of user). Setting it to 0 means unlimited. +- **autodb_pool_mode** (String) PGBouncer pool mode +- **autodb_pool_size** (Number) If non-zero then create automatically a pool of that size per user when a pool doesn't exist. +- **ignore_startup_parameters** (List of String) List of parameters to ignore when given in startup packet +- **min_pool_size** (Number) Add more server connections to pool if below this number. Improves behavior when usual load comes suddenly back after period of total inactivity. The value is effectively capped at the pool size. +- **server_idle_timeout** (Number) If a server connection has been idle more than this many seconds it will be dropped. If 0 then timeout is disabled. [seconds] +- **server_lifetime** (Number) The pooler will close an unused server connection that has been connected longer than this. [seconds] +- **server_reset_query_always** (Boolean) Run server_reset_query (DISCARD ALL) in all pooling modes + + + +### Nested Schema for `properties.pglookout` + +Optional: + +- **max_failover_replication_time_lag** (Number) max_failover_replication_time_lag + + + +### Nested Schema for `properties.timescaledb` + +Optional: + +- **max_background_workers** (Number) timescaledb.max_background_workers + + + + +### Nested Schema for `components` + +Read-Only: + +- **component** (String) +- **host** (String) +- **port** (Number) +- **route** (String) +- **usage** (String) + + + +### Nested Schema for `node_states` + +Read-Only: + +- **name** (String) +- **role** (String) +- **state** (String) + + diff --git a/docs/resources/managed_database_user.md b/docs/resources/managed_database_user.md new file mode 100644 index 00000000..931e5d79 --- /dev/null +++ b/docs/resources/managed_database_user.md @@ -0,0 +1,47 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "upcloud_managed_database_user Resource - terraform-provider-upcloud" +subcategory: "" +description: |- + +--- + +# upcloud_managed_database_user (Resource) + + + +## Example Usage + +```terraform +resource "upcloud_managed_database_postgresql" "example" { + name = "postgres" + plan = "1x1xCPU-2GB-25GB" + title = "postgres" + zone = "fi-hel1" +} + +resource "upcloud_managed_database_user" "example_user" { + service = upcloud_managed_database_postgresql.example.id + username = "example_user" + password = "" +} +``` + + +## Schema + +### Required + +- **service** (String) Service's UUID for which this user belongs to +- **username** (String) Name of the database user + +### Optional + +- **id** (String) The ID of this resource. +- **password** (String, Sensitive) Password for the database user. Defaults to a random value + +### Read-Only + +- **type** (String) Type of the user. Only normal type users can be created + + diff --git a/examples/resources/upcloud_managed_database_logical_database/resource.tf b/examples/resources/upcloud_managed_database_logical_database/resource.tf new file mode 100644 index 00000000..1b735d80 --- /dev/null +++ b/examples/resources/upcloud_managed_database_logical_database/resource.tf @@ -0,0 +1,11 @@ +resource "upcloud_managed_database_postgresql" "example" { + name = "postgres" + plan = "1x1xCPU-2GB-25GB" + title = "postgres" + zone = "fi-hel1" +} + +resource "upcloud_managed_database_logical_database" "example_db" { + service = upcloud_managed_database_postgresql.example.id + name = "example_db" +} diff --git a/examples/resources/upcloud_managed_database_mysql/resource.tf b/examples/resources/upcloud_managed_database_mysql/resource.tf new file mode 100644 index 00000000..e97d5e21 --- /dev/null +++ b/examples/resources/upcloud_managed_database_mysql/resource.tf @@ -0,0 +1,30 @@ +# Minimal config +resource "upcloud_managed_database_mysql" "example_1" { + name = "mysql-1" + plan = "1x1xCPU-2GB-25GB" + zone = "fi-hel1" +} + +# Shutdown instance after creation +resource "upcloud_managed_database_mysql" "example_2" { + name = "mysql-2" + plan = "1x1xCPU-2GB-25GB" + zone = "fi-hel1" + powered = false +} + +# Service with custom properties +# Note that this basically sets strict mode off which is not normally recommended +resource "upcloud_managed_database_mysql" "example_3" { + name = "mysql-3" + plan = "1x1xCPU-2GB-25GB" + zone = "fi-hel1" + properties { + sql_mode = "NO_ENGINE_SUBSTITUTION" + wait_timeout = 300 + sort_buffer_size = 4e+6 # 4MB + max_allowed_packet = 16e+6 # 16MB + admin_username = "admin" + admin_password = "" + } +} diff --git a/examples/resources/upcloud_managed_database_postgresql/resource.tf b/examples/resources/upcloud_managed_database_postgresql/resource.tf new file mode 100644 index 00000000..9d1f5225 --- /dev/null +++ b/examples/resources/upcloud_managed_database_postgresql/resource.tf @@ -0,0 +1,20 @@ +# Minimal config +resource "upcloud_managed_database_postgresql" "example_1" { + name = "postgres-1" + plan = "1x1xCPU-2GB-25GB" + title = "postgres" + zone = "fi-hel1" +} + +# Service with custom properties +resource "upcloud_managed_database_postgresql" "example_2" { + name = "postgres-2" + plan = "1x1xCPU-2GB-25GB" + title = "postgres" + zone = "fi-hel1" + properties { + timezone = "Europe/Helsinki" + admin_username = "admin" + admin_password = "" + } +} diff --git a/examples/resources/upcloud_managed_database_user/resource.tf b/examples/resources/upcloud_managed_database_user/resource.tf new file mode 100644 index 00000000..88eadd45 --- /dev/null +++ b/examples/resources/upcloud_managed_database_user/resource.tf @@ -0,0 +1,12 @@ +resource "upcloud_managed_database_postgresql" "example" { + name = "postgres" + plan = "1x1xCPU-2GB-25GB" + title = "postgres" + zone = "fi-hel1" +} + +resource "upcloud_managed_database_user" "example_user" { + service = upcloud_managed_database_postgresql.example.id + username = "example_user" + password = "" +} From a98c96ff6cedc1e8ad28f0fb782dde91fc52865d Mon Sep 17 00:00:00 2001 From: Pekka Nurmi Date: Mon, 27 Dec 2021 11:25:42 +0200 Subject: [PATCH 4/6] feat(database): wait managed database to be fully created Wait managed database to be running and service name to be propagated before continuing execution. Add UpCloud Managed Databases to CHANGELOG. --- CHANGELOG.md | 2 +- upcloud/resource_upcloud_managed_database.go | 55 +++++++++++++++---- .../resource_upcloud_managed_database_test.go | 21 +++++++ 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b30967d6..a4e0e06c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ See updating [Changelog example here](https://keepachangelog.com/en/1.0.0/) ### Added - server: validate plan and zone field values before executing API commands - +- Support for UpCloud Managed Databases - Support for debuggers like Delve ### Fixed diff --git a/upcloud/resource_upcloud_managed_database.go b/upcloud/resource_upcloud_managed_database.go index 61a70eab..3b9f0ec1 100644 --- a/upcloud/resource_upcloud_managed_database.go +++ b/upcloud/resource_upcloud_managed_database.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "log" + "net" "strings" "time" @@ -295,23 +296,33 @@ func resourceUpCloudManagedDatabaseCreate(serviceType upcloud.ManagedDatabaseSer log.Printf("[INFO] managed database %v (%v) created", details.UUID, d.Get("name")) - if !d.Get("powered").(bool) { - if err = waitManagedDatabaseFullyCreated(ctx, client, details); err != nil { - // return warning so that next apply will only shutdown database instead of recreating it - d := resourceUpCloudManagedDatabaseRead(ctx, d, meta) - d = append(d, diag.Diagnostic{ - Severity: diag.Warning, - Summary: err.Error(), - }) - return d - } + if err = waitManagedDatabaseFullyCreated(ctx, client, details); err != nil { + d := resourceUpCloudManagedDatabaseRead(ctx, d, meta) + d = append(d, diag.Diagnostic{ + Severity: diag.Warning, + Summary: err.Error(), + }) + return d + } + if !d.Get("powered").(bool) { _, err := client.ShutdownManagedDatabase(&request.ShutdownManagedDatabaseRequest{UUID: d.Id()}) if err != nil { return diag.FromErr(err) } log.Printf("[INFO] managed database %v (%v) is powered off", d.Id(), d.Get("name")) } + + if err = waitServiceNameToPropagate(ctx, details.ServiceURIParams.Host); err != nil { + // return warning if DNS name is not yet available + d := resourceUpCloudManagedDatabaseRead(ctx, d, meta) + d = append(d, diag.Diagnostic{ + Severity: diag.Warning, + Summary: err.Error(), + }) + return d + } + return resourceUpCloudManagedDatabaseRead(ctx, d, meta) } } @@ -721,3 +732,27 @@ func waitManagedDatabaseFullyCreated(ctx context.Context, client *service.Servic func isManagedDatabaseFullyCreated(db *upcloud.ManagedDatabase) bool { return db.State == upcloud.ManagedDatabaseStateRunning && len(db.Backups) > 0 && len(db.Users) > 0 } + +func waitServiceNameToPropagate(ctx context.Context, name string) (err error) { + const maxRetries int = 12 + var ips []net.IPAddr + for i := 0; i <= maxRetries; i++ { + if ips, err = net.DefaultResolver.LookupIPAddr(ctx, name); err != nil { + switch e := err.(type) { + case *net.DNSError: + if !e.IsNotFound && !e.IsTemporary { + return err + } + default: + return err + } + } + + if len(ips) > 0 { + return nil + } + + time.Sleep(5 * time.Second) + } + return errors.New("max retries reached while waiting for service name to propagate") +} diff --git a/upcloud/resource_upcloud_managed_database_test.go b/upcloud/resource_upcloud_managed_database_test.go index e8886f22..1801c487 100644 --- a/upcloud/resource_upcloud_managed_database_test.go +++ b/upcloud/resource_upcloud_managed_database_test.go @@ -1,8 +1,10 @@ package upcloud import ( + "context" "fmt" "testing" + "time" "github.com/UpCloudLtd/upcloud-go-api/v4/upcloud" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" @@ -165,3 +167,22 @@ func TestIsManagedDatabaseFullyCreated(t *testing.T) { t.Errorf("isManagedDatabaseFullyCreated failed want true got false %+v", db) } } + +func TestWaitServiceNameToPropagate(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) + defer cancel() + name := "upcloud.com" + if err := waitServiceNameToPropagate(ctx, name); err != nil { + t.Errorf("waitServiceNameToPropagate failed %+v", err) + } +} + +func TestWaitServiceNameToPropagateContextTimeout(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 1) + defer cancel() + name := "upcloud.com" + if err := waitServiceNameToPropagate(ctx, name); err == nil { + d, _ := ctx.Deadline() + t.Errorf("waitServiceNameToPropagate failed didn't timeout before deadline %s", d.Format(time.RFC3339)) + } +} From aca62ca57e7b0a7bc701050b3049b624d68ebf43 Mon Sep 17 00:00:00 2001 From: Pekka Nurmi Date: Tue, 11 Jan 2022 15:35:55 +0200 Subject: [PATCH 5/6] chore(docs): Add DBaaS resouce descriptions and update examples --- .../managed_database_logical_database.md | 17 +++++++++++++++-- docs/resources/managed_database_mysql.md | 4 ++-- docs/resources/managed_database_postgresql.md | 4 ++-- docs/resources/managed_database_user.md | 4 ++-- .../resource.tf | 13 +++++++++++++ upcloud/resource_upcloud_managed_database.go | 2 ++ ...upcloud_managed_database_logical_database.go | 1 + .../resource_upcloud_managed_database_user.go | 1 + 8 files changed, 38 insertions(+), 8 deletions(-) diff --git a/docs/resources/managed_database_logical_database.md b/docs/resources/managed_database_logical_database.md index e3c931a6..832881dc 100644 --- a/docs/resources/managed_database_logical_database.md +++ b/docs/resources/managed_database_logical_database.md @@ -3,16 +3,17 @@ page_title: "upcloud_managed_database_logical_database Resource - terraform-provider-upcloud" subcategory: "" description: |- - + This resource represents a logical database in managed database --- # upcloud_managed_database_logical_database (Resource) - +This resource represents a logical database in managed database ## Example Usage ```terraform +# PostgreSQL managed database with additional logical database: example_db resource "upcloud_managed_database_postgresql" "example" { name = "postgres" plan = "1x1xCPU-2GB-25GB" @@ -24,6 +25,18 @@ resource "upcloud_managed_database_logical_database" "example_db" { service = upcloud_managed_database_postgresql.example.id name = "example_db" } + +# MySQL managed database with additional logical database: example2_db +resource "upcloud_managed_database_mysql" "example" { + name = "mymysql" + plan = "1x1xCPU-2GB-25GB" + zone = "fi-hel1" +} + +resource "upcloud_managed_database_logical_database" "example2_db" { + service = upcloud_managed_database_mysql.example.id + name = "example2_db" +} ``` diff --git a/docs/resources/managed_database_mysql.md b/docs/resources/managed_database_mysql.md index 8a1e32e9..1c1e4dc0 100644 --- a/docs/resources/managed_database_mysql.md +++ b/docs/resources/managed_database_mysql.md @@ -3,12 +3,12 @@ page_title: "upcloud_managed_database_mysql Resource - terraform-provider-upcloud" subcategory: "" description: |- - + This resource represents MySQL managed database --- # upcloud_managed_database_mysql (Resource) - +This resource represents MySQL managed database ## Example Usage diff --git a/docs/resources/managed_database_postgresql.md b/docs/resources/managed_database_postgresql.md index 3a74826b..af09af56 100644 --- a/docs/resources/managed_database_postgresql.md +++ b/docs/resources/managed_database_postgresql.md @@ -3,12 +3,12 @@ page_title: "upcloud_managed_database_postgresql Resource - terraform-provider-upcloud" subcategory: "" description: |- - + This resource represents PostgreSQL managed database --- # upcloud_managed_database_postgresql (Resource) - +This resource represents PostgreSQL managed database ## Example Usage diff --git a/docs/resources/managed_database_user.md b/docs/resources/managed_database_user.md index 931e5d79..e6567806 100644 --- a/docs/resources/managed_database_user.md +++ b/docs/resources/managed_database_user.md @@ -3,12 +3,12 @@ page_title: "upcloud_managed_database_user Resource - terraform-provider-upcloud" subcategory: "" description: |- - + This resource represents a user in managed database --- # upcloud_managed_database_user (Resource) - +This resource represents a user in managed database ## Example Usage diff --git a/examples/resources/upcloud_managed_database_logical_database/resource.tf b/examples/resources/upcloud_managed_database_logical_database/resource.tf index 1b735d80..c5707de2 100644 --- a/examples/resources/upcloud_managed_database_logical_database/resource.tf +++ b/examples/resources/upcloud_managed_database_logical_database/resource.tf @@ -1,3 +1,4 @@ +# PostgreSQL managed database with additional logical database: example_db resource "upcloud_managed_database_postgresql" "example" { name = "postgres" plan = "1x1xCPU-2GB-25GB" @@ -9,3 +10,15 @@ resource "upcloud_managed_database_logical_database" "example_db" { service = upcloud_managed_database_postgresql.example.id name = "example_db" } + +# MySQL managed database with additional logical database: example2_db +resource "upcloud_managed_database_mysql" "example" { + name = "mymysql" + plan = "1x1xCPU-2GB-25GB" + zone = "fi-hel1" +} + +resource "upcloud_managed_database_logical_database" "example2_db" { + service = upcloud_managed_database_mysql.example.id + name = "example2_db" +} diff --git a/upcloud/resource_upcloud_managed_database.go b/upcloud/resource_upcloud_managed_database.go index 3b9f0ec1..e053692e 100644 --- a/upcloud/resource_upcloud_managed_database.go +++ b/upcloud/resource_upcloud_managed_database.go @@ -33,6 +33,7 @@ var resourceUpcloudManagedDatabaseModifiableStates = []upcloud.ManagedDatabaseSt func resourceUpCloudManagedDatabasePostgreSQL() *schema.Resource { return &schema.Resource{ + Description: "This resource represents PostgreSQL managed database", CreateContext: resourceUpCloudManagedDatabaseCreate(managedDatabaseTypePostgreSQL), ReadContext: resourceUpCloudManagedDatabaseRead, UpdateContext: resourceUpCloudManagedDatabaseUpdate, @@ -46,6 +47,7 @@ func resourceUpCloudManagedDatabasePostgreSQL() *schema.Resource { func resourceUpCloudManagedDatabaseMySQL() *schema.Resource { return &schema.Resource{ + Description: "This resource represents MySQL managed database", CreateContext: resourceUpCloudManagedDatabaseCreate(managedDatabaseTypeMySQL), ReadContext: resourceUpCloudManagedDatabaseRead, UpdateContext: resourceUpCloudManagedDatabaseUpdate, diff --git a/upcloud/resource_upcloud_managed_database_logical_database.go b/upcloud/resource_upcloud_managed_database_logical_database.go index f64ce9dc..1129dba8 100644 --- a/upcloud/resource_upcloud_managed_database_logical_database.go +++ b/upcloud/resource_upcloud_managed_database_logical_database.go @@ -16,6 +16,7 @@ import ( func resourceUpCloudManagedDatabaseLogicalDatabase() *schema.Resource { return &schema.Resource{ + Description: "This resource represents a logical database in managed database", CreateContext: resourceUpCloudManagedDatabaseLogicalDatabaseCreate, ReadContext: resourceUpCloudManagedDatabaseLogicalDatabaseRead, DeleteContext: resourceUpCloudManagedDatabaseLogicalDatabaseDelete, diff --git a/upcloud/resource_upcloud_managed_database_user.go b/upcloud/resource_upcloud_managed_database_user.go index 92dd2b5f..6b443776 100644 --- a/upcloud/resource_upcloud_managed_database_user.go +++ b/upcloud/resource_upcloud_managed_database_user.go @@ -15,6 +15,7 @@ import ( func resourceUpCloudManagedDatabaseUser() *schema.Resource { return &schema.Resource{ + Description: "This resource represents a user in managed database", CreateContext: resourceUpCloudManagedDatabaseUserCreate, ReadContext: resourceUpCloudManagedDatabaseUserRead, UpdateContext: resourceUpCloudManagedDatabaseUserUpdate, From 9bf701459804cb39cc47a0606483320cd40c8947 Mon Sep 17 00:00:00 2001 From: Pekka Nurmi Date: Tue, 11 Jan 2022 17:04:19 +0200 Subject: [PATCH 6/6] fix(dbaas): Maintenance window settings Maintenance window properties were ignored when creating instances and also in updates if only one of two properties (maintenance_window_dow, maintenance_window_time) were changed. --- upcloud/resource_upcloud_managed_database.go | 12 +++++++++--- upcloud/resource_upcloud_managed_database_test.go | 10 ++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/upcloud/resource_upcloud_managed_database.go b/upcloud/resource_upcloud_managed_database.go index e053692e..66affbdc 100644 --- a/upcloud/resource_upcloud_managed_database.go +++ b/upcloud/resource_upcloud_managed_database.go @@ -290,6 +290,13 @@ func resourceUpCloudManagedDatabaseCreate(serviceType upcloud.ManagedDatabaseSer ) } + if d.HasChange("maintenance_window_dow") || d.HasChange("maintenance_window_time") { + req.Maintenance = request.ManagedDatabaseMaintenanceTimeRequest{ + DayOfWeek: d.Get("maintenance_window_dow").(string), + Time: d.Get("maintenance_window_time").(string), + } + } + details, err := client.CreateManagedDatabase(&req) if err != nil { return diag.FromErr(err) @@ -350,12 +357,11 @@ func resourceUpCloudManagedDatabaseUpdate(ctx context.Context, d *schema.Resourc req.Plan = d.Get("plan").(string) req.Title = d.Get("title").(string) req.Zone = d.Get("zone").(string) - if d.HasChange("maintenance_window_dow") { + if d.HasChange("maintenance_window_dow") || d.HasChange("maintenance_window_time") { req.Maintenance.DayOfWeek = d.Get("maintenance_window_dow").(string) - } - if d.HasChange("maintenance_window_time") { req.Maintenance.Time = d.Get("maintenance_window_time").(string) } + if d.HasChange("properties.0") { req.Properties = buildManagedDatabasePropertiesFromResourceData( d, diff --git a/upcloud/resource_upcloud_managed_database_test.go b/upcloud/resource_upcloud_managed_database_test.go index 1801c487..c5ccac4f 100644 --- a/upcloud/resource_upcloud_managed_database_test.go +++ b/upcloud/resource_upcloud_managed_database_test.go @@ -27,7 +27,8 @@ func TestAccUpcloudManagedDatabasePostgreSQL_CreateUpdate(t *testing.T) { plan = "1x1xCPU-2GB-25GB" title = "testtitle" zone = "fi-hel1" - + maintenance_window_time = "10:00:00" + maintenance_window_dow = "friday" properties { public_access = true ip_filter = ["10.0.0.1/32"] @@ -39,6 +40,8 @@ func TestAccUpcloudManagedDatabasePostgreSQL_CreateUpdate(t *testing.T) { resource.TestCheckResourceAttr(resourceIdentifier, "title", "testtitle"), resource.TestCheckResourceAttr(resourceIdentifier, "zone", "fi-hel1"), resource.TestCheckResourceAttr(resourceIdentifier, "powered", "true"), + resource.TestCheckResourceAttr(resourceIdentifier, "maintenance_window_time", "10:00:00"), + resource.TestCheckResourceAttr(resourceIdentifier, "maintenance_window_dow", "friday"), resource.TestCheckResourceAttr(resourceIdentifier, "properties.0.ip_filter.0", "10.0.0.1/32"), resource.TestCheckResourceAttr(resourceIdentifier, "type", string(upcloud.ManagedDatabaseServiceTypePostgreSQL)), resource.TestCheckResourceAttrSet(resourceIdentifier, "service_uri"), @@ -51,13 +54,16 @@ func TestAccUpcloudManagedDatabasePostgreSQL_CreateUpdate(t *testing.T) { plan = "1x1xCPU-2GB-25GB" title = "testtitle modified" zone = "fi-hel1" - + maintenance_window_time = "11:00:00" + maintenance_window_dow = "friday" properties { ip_filter = [] } }`, rName), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceIdentifier, "title", "testtitle modified"), + resource.TestCheckResourceAttr(resourceIdentifier, "maintenance_window_time", "11:00:00"), + resource.TestCheckResourceAttr(resourceIdentifier, "maintenance_window_dow", "friday"), resource.TestCheckResourceAttr(resourceIdentifier, "properties.0.public_access", "false"), resource.TestCheckResourceAttr(resourceIdentifier, "properties.0.ip_filter.#", "0"), ),