diff --git a/docs/index.md b/docs/index.md index e983aff7..57823d5f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -70,6 +70,16 @@ provider "proxmox" { } ``` +## Enable proxy server support + +You can send all api calls from the provider api client to a proxy server and then to proxmox itself to see whats appening and debug more easily. One nice proxy server is mitmproxy + +```hcl +provider "proxmox" { + pm_proxy_server = "http://proxyurl:proxyport +} +``` + ## Argument Reference The following arguments are supported in the provider block: @@ -87,6 +97,7 @@ The following arguments are supported in the provider block: - `pm_log_file` - (Optional; defaults to "terraform-plugin-proxmox.log") If logging is enabled, the log file the provider will write logs to. - `pm_timeout` - (Optional; defaults to 300) Timeout value (seconds) for proxmox API calls. - `pm_debug` - (Optional; defaults to false) Enable verbose output in proxmox-api-go +- `pm_proxy_server` - (Optional; defaults to nil) Send provider api call to a proxy server for easy debugging Additionally, one can set the `PM_OTP_PROMPT` environment variable to prompt for OTP 2FA code (if required). diff --git a/go.mod b/go.mod index 7422821e..5479f739 100644 --- a/go.mod +++ b/go.mod @@ -3,28 +3,29 @@ module github.com/Telmate/terraform-provider-proxmox go 1.16 require ( - github.com/Telmate/proxmox-api-go v0.0.0-20211022153530-57ee1e4bd869 + github.com/Telmate/proxmox-api-go v0.0.0-20211101191122-8bbe30eb7171 github.com/agext/levenshtein v1.2.3 // indirect github.com/fatih/color v1.13.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/go-hclog v1.0.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.4.3 // indirect github.com/hashicorp/go-uuid v1.0.2 // indirect github.com/hashicorp/hcl/v2 v2.10.1 // indirect github.com/hashicorp/terraform-plugin-sdk/v2 v2.8.0 - github.com/hashicorp/yamux v0.0.0-20210826001029-26ff87cf9493 // indirect + github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect github.com/mattn/go-colorable v0.1.11 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.4.2 // indirect github.com/oklog/run v1.1.0 // indirect - github.com/rs/zerolog v1.25.0 - github.com/zclconf/go-cty v1.9.1 // indirect - golang.org/x/net v0.0.0-20211020060615-d418f374d309 // indirect - golang.org/x/sys v0.0.0-20211020174200-9d6173849985 // indirect + github.com/rs/zerolog v1.26.0 + github.com/zclconf/go-cty v1.10.0 // indirect + golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 // indirect + golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 // indirect golang.org/x/text v0.3.7 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20211021150943-2b146023228c // indirect - google.golang.org/grpc v1.41.0 // indirect + google.golang.org/genproto v0.0.0-20211102202547-e9cf271f7f2c // indirect + google.golang.org/grpc v1.42.0 // indirect ) diff --git a/go.sum b/go.sum index 5f8f99f6..9e58b8da 100644 --- a/go.sum +++ b/go.sum @@ -45,6 +45,12 @@ github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C6 github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/Telmate/proxmox-api-go v0.0.0-20211022153530-57ee1e4bd869 h1:IcMtti6HuXLZwUUkv+sEQSKwia+OCaeaBt5HFfDi668= github.com/Telmate/proxmox-api-go v0.0.0-20211022153530-57ee1e4bd869/go.mod h1:keBhXWLa+UBajvf79xvKcfiqeIc7vZL9wOqxuy1CBGw= +github.com/Telmate/proxmox-api-go v0.0.0-20211023205737-5ce2d5fbf04e h1:cL8732QTN0+DqhOiHAL4aDSD1hwrIvOPl9c1mpCJbco= +github.com/Telmate/proxmox-api-go v0.0.0-20211023205737-5ce2d5fbf04e/go.mod h1:keBhXWLa+UBajvf79xvKcfiqeIc7vZL9wOqxuy1CBGw= +github.com/Telmate/proxmox-api-go v0.0.0-20211025150448-0cc61e17eed6 h1:NOMF/RS2IMr4ljhKUK0q8xi16a3K5tMi0mAwCzdfp90= +github.com/Telmate/proxmox-api-go v0.0.0-20211025150448-0cc61e17eed6/go.mod h1:keBhXWLa+UBajvf79xvKcfiqeIc7vZL9wOqxuy1CBGw= +github.com/Telmate/proxmox-api-go v0.0.0-20211101191122-8bbe30eb7171 h1:XHB+u94AWr9xDbQXqzCIeuGvq7devG5FS8OmAL3clQM= +github.com/Telmate/proxmox-api-go v0.0.0-20211101191122-8bbe30eb7171/go.mod h1:keBhXWLa+UBajvf79xvKcfiqeIc7vZL9wOqxuy1CBGw= 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= @@ -82,8 +88,11 @@ 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/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 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= @@ -228,6 +237,8 @@ github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKe github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20210826001029-26ff87cf9493 h1:brI5vBRUlAlM34VFmnLPwjnCL/FxAJp9XvOdX6Zt+XE= github.com/hashicorp/yamux v0.0.0-20210826001029-26ff87cf9493/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I= +github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= @@ -313,6 +324,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.25.0 h1:Rj7XygbUHKUlDPcVdoLyR91fJBsduXj5fRxyqIQj/II= github.com/rs/zerolog v1.25.0/go.mod h1:7KHcEGe0QZPOm2IE4Kpb5rTh6n1h2hIgS5OOnu1rUaI= +github.com/rs/zerolog v1.26.0 h1:ORM4ibhEZeTeQlCojCK2kPz1ogAY4bGs4tD+SaAdGaE= +github.com/rs/zerolog v1.26.0/go.mod h1:yBiM87lvSqX8h0Ww4sdzNSkVYZ8dL2xjZJG1lAuGZEo= 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= @@ -343,12 +356,15 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zclconf/go-cty v1.8.4/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zclconf/go-cty v1.9.1 h1:viqrgQwFl5UpSxc046qblj78wZXVDFnSOufaOTER+cc= github.com/zclconf/go-cty v1.9.1/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +github.com/zclconf/go-cty v1.10.0 h1:mp9ZXQeIcN8kAwuqorjH+Q+njbJKjLrvB2yIh4q7U+0= +github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -432,8 +448,11 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211020060615-d418f374d309 h1:A0lJIi+hcTR6aajJH4YqKWwohY4aW9RO7oRMcdv+HKI= golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 h1:VrJZAjbekhoRn7n5FBujY31gboH+iB3pdLxn3gE9FjU= +golang.org/x/net v0.0.0-20211101193420-4a448f8816b3/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -487,9 +506,18 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211020174200-9d6173849985 h1:LOlKVhfDyahgmqa97awczplwkjzNaELFg3zRIJ13RYo= golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70 h1:SeSEfdIxyvwGJliREIJhRPPXvW6sDlLT+UQ3B0hD0NA= +golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025112917-711f33c9992c h1:i4MLwL3EbCgobekQtkVW94UBSPLMadfEGtKq+CAFsEU= +golang.org/x/sys v0.0.0-20211025112917-711f33c9992c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 h1:2B5p2L5IfGiD7+b9BOoRMC6DgObAVZV+Fsp050NqXik= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 h1:7zYaz3tjChtpayGDzu6H0hDAUM5zIGA2XW7kRNgQ0jc= +golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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= @@ -544,6 +572,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.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= 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= @@ -604,6 +634,10 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200711021454-869866162049/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20211021150943-2b146023228c h1:FqrtZMB5Wr+/RecOM3uPJNPfWR8Upb5hAPnt7PU6i4k= google.golang.org/genproto v0.0.0-20211021150943-2b146023228c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211026145609-4688e4c4e024 h1:aePO4E0x+Urj9V5NQHjqOpaNG4oMeHQq0l2ob05z5tI= +google.golang.org/genproto v0.0.0-20211026145609-4688e4c4e024/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211102202547-e9cf271f7f2c h1:UQDUEuW1R2dcciOjiFmuzE4skW4n/zGGNMU0RhU3hQI= +google.golang.org/genproto v0.0.0-20211102202547-e9cf271f7f2c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -622,6 +656,8 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.41.0 h1:f+PlOh7QV4iIJkPrx5NQ7qaNGFQ3OTse67yaDHfju4E= google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= +google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/proxmox/provider.go b/proxmox/provider.go index cc88d976..8d90cdde 100644 --- a/proxmox/provider.go +++ b/proxmox/provider.go @@ -4,7 +4,6 @@ import ( "crypto/tls" "fmt" "os" - "regexp" "strconv" "strings" "sync" @@ -105,9 +104,10 @@ func Provider() *schema.Provider { Description: "Write logs to this specific file", }, "pm_timeout": { - Type: schema.TypeInt, - Optional: true, - Default: 120, + Type: schema.TypeInt, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("PM_TIMEOUT", defaultTimeout), + Description: "How much second to wait for operations for both provider and api-client, default is 300s", }, "pm_dangerously_ignore_unknown_attributes": { Type: schema.TypeBool, @@ -121,6 +121,12 @@ func Provider() *schema.Provider { DefaultFunc: schema.EnvDefaultFunc("PM_DEBUG", false), Description: "Enable or disable the verbose debug output from proxmox api", }, + "pm_proxy_server": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("PM_PROXY", nil), + Description: "Proxy Server passed to Api client(useful for debugging). Syntax: http://proxy:port", + }, "pm_otp": &pmOTPprompt, }, @@ -149,6 +155,7 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { d.Get("pm_tls_insecure").(bool), d.Get("pm_timeout").(int), d.Get("pm_debug").(bool), + d.Get("pm_proxy_server").(string), ) if err != nil { return nil, err @@ -161,7 +168,7 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { if ok { logLevels[logger] = levelAsString } else { - return nil, fmt.Errorf("Invalid logging level %v for %v. Be sure to use a string.", level, logger) + return nil, fmt.Errorf("invalid logging level %v for %v. Be sure to use a string", level, logger) } } @@ -195,7 +202,9 @@ func getClient(pm_api_url string, pm_otp string, pm_tls_insecure bool, pm_timeout int, - pm_debug bool) (*pxapi.Client, error) { + pm_debug bool, + pm_proxy_server string) (*pxapi.Client, error) { + tlsconf := &tls.Config{InsecureSkipVerify: true} if !pm_tls_insecure { tlsconf = nil @@ -204,22 +213,22 @@ func getClient(pm_api_url string, var err error if pm_password != "" && pm_api_token_secret != "" { - err = fmt.Errorf("Password and API token secret both exist, choose one or the other.") + err = fmt.Errorf("password and API token secret both exist, choose one or the other") } if pm_password == "" && pm_api_token_secret == "" { - err = fmt.Errorf("Password and API token do not exist, one of these must exist.") + err = fmt.Errorf("password and API token do not exist, one of these must exist") } if strings.Contains(pm_user, "!") && pm_password != "" { - err = fmt.Errorf("You appear to be using an API TokenID username with your password.") + err = fmt.Errorf("you appear to be using an API TokenID username with your password") } if !strings.Contains(pm_api_token_id, "!") { - err = fmt.Errorf("Your API TokenID username should contain a !, check your API credentials.") + err = fmt.Errorf("your API TokenID username should contain a !, check your API credentials") } - client, _ := pxapi.NewClient(pm_api_url, nil, tlsconf, pm_timeout) + client, _ := pxapi.NewClient(pm_api_url, nil, tlsconf, pm_proxy_server, pm_timeout) *pxapi.Debug = pm_debug // User+Pass authentication @@ -294,11 +303,9 @@ func resourceId(targetNode string, resType string, vmId int) string { return fmt.Sprintf("%s/%s/%d", targetNode, resType, vmId) } -var rxRsId = regexp.MustCompile("([^/]+)/([^/]+)/(\\d+)") - func parseResourceId(resId string) (targetNode string, resType string, vmId int, err error) { if !rxRsId.MatchString(resId) { - return "", "", -1, fmt.Errorf("Invalid resource format: %s. Must be node/type/vmId", resId) + return "", "", -1, fmt.Errorf("invalid resource format: %s. Must be node/type/vmId", resId) } idMatch := rxRsId.FindStringSubmatch(resId) targetNode = idMatch[1] @@ -311,11 +318,9 @@ func clusterResourceId(resType string, resId string) string { return fmt.Sprintf("%s/%s", resType, resId) } -var rxClusterRsId = regexp.MustCompile("([^/]+)/([^/]+)") - func parseClusterResourceId(resId string) (resType string, id string, err error) { if !rxClusterRsId.MatchString(resId) { - return "", "", fmt.Errorf("Invalid resource format: %s. Must be type/resId", resId) + return "", "", fmt.Errorf("invalid resource format: %s. Must be type/resId", resId) } idMatch := rxClusterRsId.FindStringSubmatch(resId) return idMatch[1], idMatch[2], nil diff --git a/proxmox/provider_test.go b/proxmox/provider_test.go index 43b49245..f382fd9c 100644 --- a/proxmox/provider_test.go +++ b/proxmox/provider_test.go @@ -34,7 +34,7 @@ func TestParseClusteResources(t *testing.T) { name: "invalid resource", input: "storage", output: ParseClusterResourceTestResult{ - Error: errors.New("Invalid resource format: storage. Must be type/resId"), + Error: errors.New("invalid resource format: storage. Must be type/resId"), }, }} diff --git a/proxmox/resource_lxc.go b/proxmox/resource_lxc.go index ca24a3a7..9173e354 100644 --- a/proxmox/resource_lxc.go +++ b/proxmox/resource_lxc.go @@ -20,7 +20,7 @@ func resourceLxc() *schema.Resource { Update: resourceLxcUpdate, Delete: resourceVmQemuDelete, Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, + StateContext: schema.ImportStatePassthroughContext, }, Schema: map[string]*schema.Schema{ @@ -195,7 +195,7 @@ func resourceLxc() *schema.Resource { ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { v := val.(string) if !(strings.Contains(v, "G") || strings.Contains(v, "M") || strings.Contains(v, "n")) { - errs = append(errs, fmt.Errorf("Disk size must end in G, M, or K, got %s", v)) + errs = append(errs, fmt.Errorf("disk size must end in G, M, or K, got %s", v)) } return }, @@ -316,23 +316,23 @@ func resourceLxc() *schema.Resource { Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "storage": &schema.Schema{ + "storage": { Type: schema.TypeString, ForceNew: true, Required: true, }, - "size": &schema.Schema{ + "size": { Type: schema.TypeString, Required: true, ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { v := val.(string) if !(strings.Contains(v, "G") || strings.Contains(v, "M") || strings.Contains(v, "n")) { - errs = append(errs, fmt.Errorf("Disk size must end in G, M, or K, got %s", v)) + errs = append(errs, fmt.Errorf("disk size must end in G, M, or K, got %s", v)) } return }, }, - "volume": &schema.Schema{ + "volume": { Type: schema.TypeString, Computed: true, }, @@ -382,7 +382,7 @@ func resourceLxc() *schema.Resource { }, "unused": { Type: schema.TypeList, - Optional: true, + Computed: true, Elem: &schema.Schema{ Type: schema.TypeString, }, @@ -457,14 +457,6 @@ func resourceLxcCreate(d *schema.ResourceData, meta interface{}) error { config.Tty = d.Get("tty").(int) config.Unique = d.Get("unique").(bool) config.Unprivileged = d.Get("unprivileged").(bool) - // proxmox api allows to specify unused volumes - // even if it is recommended not to change them manually - unusedVolumes := d.Get("unused").([]interface{}) - var volumes []string - for _, v := range unusedVolumes { - volumes = append(volumes, v.(string)) - } - config.Unused = volumes targetNode := d.Get("target_node").(string) @@ -543,7 +535,7 @@ func resourceLxcCreate(d *schema.ResourceData, meta interface{}) error { // The existence of a non-blank ID is what tells Terraform that a resource was created d.SetId(resourceId(targetNode, "lxc", vmr.VmId())) - return _resourceLxcRead(d, meta) + return resourceLxcRead(d, meta) } @@ -605,14 +597,6 @@ func resourceLxcUpdate(d *schema.ResourceData, meta interface{}) error { config.Tty = d.Get("tty").(int) config.Unique = d.Get("unique").(bool) config.Unprivileged = d.Get("unprivileged").(bool) - // proxmox api allows to specify unused volumes - // even if it is recommended not to change them manually - unusedVolumes := d.Get("unused").([]interface{}) - var volumes []string - for _, v := range unusedVolumes { - volumes = append(volumes, v.(string)) - } - config.Unused = volumes if d.HasChange("network") { // TODO Delete extra networks @@ -667,7 +651,7 @@ func resourceLxcUpdate(d *schema.ResourceData, meta interface{}) error { } } - return _resourceLxcRead(d, meta) + return resourceLxcRead(d, meta) } func resourceLxcRead(d *schema.ResourceData, meta interface{}) error { @@ -766,6 +750,13 @@ func _resourceLxcRead(d *schema.ResourceData, meta interface{}) error { } } + //_, err = client.ReadVMHA(vmr) + if err != nil { + return err + } + d.Set("hastate", vmr.HaState()) + d.Set("hagroup", vmr.HaGroup()) + // Read Misc d.Set("arch", config.Arch) d.Set("bwlimit", config.BWLimit) @@ -776,8 +767,7 @@ func _resourceLxcRead(d *schema.ResourceData, meta interface{}) error { d.Set("cpuunits", config.CPUUnits) d.Set("description", config.Description) d.Set("force", config.Force) - d.Set("hastate", config.HaState) - d.Set("hagroup", config.HaGroup) + d.Set("hookscript", config.Hookscript) d.Set("hostname", config.Hostname) d.Set("ignore_unpack_errors", config.IgnoreUnpackErrors) diff --git a/proxmox/resource_lxc_disk.go b/proxmox/resource_lxc_disk.go index 7db57a7c..5cb19eed 100644 --- a/proxmox/resource_lxc_disk.go +++ b/proxmox/resource_lxc_disk.go @@ -16,7 +16,7 @@ func resourceLxcDisk() *schema.Resource { Delete: resourceLxcDiskDelete, Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, + StateContext: schema.ImportStatePassthroughContext, }, Schema: map[string]*schema.Schema{ @@ -68,7 +68,7 @@ func resourceLxcDisk() *schema.Resource { ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { v := val.(string) if !(strings.Contains(v, "G") || strings.Contains(v, "M") || strings.Contains(v, "n")) { - errs = append(errs, fmt.Errorf("Disk size must end in G, M, or K, got %s", v)) + errs = append(errs, fmt.Errorf("disk size must end in G, M, or K, got %s", v)) } return }, @@ -142,7 +142,7 @@ func resourceLxcDiskCreate(d *schema.ResourceData, meta interface{}) error { params[mpName] = pxapi.FormatDiskParam(disk) exitStatus, err := pconf.Client.SetLxcConfig(vmr, params) if err != nil { - return fmt.Errorf("Error updating LXC Mountpoint: %v, error status: %s (params: %v)", err, exitStatus, params) + return fmt.Errorf("error updating LXC Mountpoint: %v, error status: %s (params: %v)", err, exitStatus, params) } if err = _resourceLxcDiskRead(d, meta); err != nil { @@ -177,7 +177,7 @@ func resourceLxcDiskUpdate(d *schema.ResourceData, meta interface{}) error { // Apply Changes err = processLxcDiskChanges(DeviceToMap(oldDisk, 0), DeviceToMap(newDisk, 0), pconf, vmr) if err != nil { - return fmt.Errorf("Error updating LXC Mountpoint: %v", err) + return fmt.Errorf("error updating LXC Mountpoint: %v", err) } return _resourceLxcDiskRead(d, meta) @@ -251,7 +251,7 @@ func resourceLxcDiskDelete(d *schema.ResourceData, meta interface{}) error { params := map[string]interface{}{} params["delete"] = fmt.Sprintf("mp%v", d.Get("slot").(int)) if exitStatus, err := pconf.Client.SetLxcConfig(vmr, params); err != nil { - return fmt.Errorf("Error deleting LXC Mountpoint: %v, error status: %s (params: %v)", err, exitStatus, params) + return fmt.Errorf("error deleting LXC Mountpoint: %v, error status: %s (params: %v)", err, exitStatus, params) } return nil diff --git a/proxmox/resource_pool.go b/proxmox/resource_pool.go index 70c1393a..4cc925d7 100644 --- a/proxmox/resource_pool.go +++ b/proxmox/resource_pool.go @@ -15,7 +15,7 @@ func resourcePool() *schema.Resource { Update: resourcePoolUpdate, Delete: resourcePoolDelete, Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, + StateContext: schema.ImportStatePassthroughContext, }, Schema: map[string]*schema.Schema{ @@ -68,7 +68,7 @@ func _resourcePoolRead(d *schema.ResourceData, meta interface{}) error { _, poolID, err := parseClusterResourceId(d.Id()) if err != nil { d.SetId("") - return fmt.Errorf("Unexpected error when trying to read and parse resource id: %v", err) + return fmt.Errorf("unexpected error when trying to read and parse resource id: %v", err) } logger, _ := CreateSubLogger("resource_pool_read") @@ -125,6 +125,9 @@ func resourcePoolDelete(d *schema.ResourceData, meta interface{}) error { client := pconf.Client _, poolID, err := parseClusterResourceId(d.Id()) + if err != nil { + return err + } err = client.DeletePool(poolID) if err != nil { return err diff --git a/proxmox/resource_vm_qemu.go b/proxmox/resource_vm_qemu.go index 43dd0106..3017b654 100755 --- a/proxmox/resource_vm_qemu.go +++ b/proxmox/resource_vm_qemu.go @@ -26,24 +26,23 @@ func resourceVmQemu() *schema.Resource { Update: resourceVmQemuUpdate, Delete: resourceVmQemuDelete, Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, + StateContext: schema.ImportStatePassthroughContext, }, Schema: map[string]*schema.Schema{ "vmid": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - ForceNew: true, + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + ValidateDiagFunc: VMIDValidator(), + Description: "The VM identifier in proxmox (100-999999999)", }, "name": { - Type: schema.TypeString, - Required: true, - }, - "define_connection_info": { // by default define SSH for provisioner info - Type: schema.TypeBool, - Optional: true, - Default: true, + Type: schema.TypeString, + Optional: true, + Default: "", + Description: "The VM name", }, "desc": { Type: schema.TypeString, @@ -51,30 +50,38 @@ func resourceVmQemu() *schema.Resource { DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { return strings.TrimSpace(old) == strings.TrimSpace(new) }, + Default: "", + Description: "The VM description", }, "target_node": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + Description: "The node where VM goes to", }, "bios": { - Type: schema.TypeString, - Optional: true, - Default: "seabios", + Type: schema.TypeString, + Optional: true, + Default: "seabios", + Description: "The VM bios, it can be seabios or ovmf", + ValidateFunc: BIOSValidator(), }, "onboot": { - Type: schema.TypeBool, - Optional: true, - Default: true, + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "VM autostart on boot", }, "tablet": { - Type: schema.TypeBool, - Optional: true, - Default: true, + Type: schema.TypeBool, + Optional: true, + Default: true, + Description: "Enable tablet mode in the VM", }, "boot": { - Type: schema.TypeString, - Optional: true, - Default: "cdn", + Type: schema.TypeString, + Optional: true, + Default: "c", + Description: "Boot order of the VM", }, "bootdisk": { Type: schema.TypeString, @@ -86,12 +93,6 @@ func resourceVmQemu() *schema.Resource { Optional: true, Default: 0, }, - "guest_agent_ready_timeout": { - Type: schema.TypeInt, - Deprecated: "Use custom per-resource timeout instead. See https://www.terraform.io/docs/language/resources/syntax.html#operation-timeouts", - Optional: true, - Default: 100, - }, "iso": { Type: schema.TypeString, Optional: true, @@ -189,7 +190,7 @@ func resourceVmQemu() *schema.Resource { Optional: true, Computed: true, }, - "vga": &schema.Schema{ + "vga": { Type: schema.TypeSet, Optional: true, Elem: &schema.Resource{ @@ -206,53 +207,48 @@ func resourceVmQemu() *schema.Resource { }, }, }, - "network": &schema.Schema{ + "network": { Type: schema.TypeList, Optional: true, ConflictsWith: []string{"nic", "bridge", "vlan", "mac"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - //"id": &schema.Schema{ - // Type: schema.TypeInt, - // Deprecated: "Id is no longer required. The order of the network blocks is used for the Id.", - // Optional: true, - //}, - "model": &schema.Schema{ + "model": { Type: schema.TypeString, Required: true, }, - "macaddr": &schema.Schema{ + "macaddr": { Type: schema.TypeString, Optional: true, Computed: true, }, - "bridge": &schema.Schema{ + "bridge": { Type: schema.TypeString, Optional: true, Default: "nat", }, - "tag": &schema.Schema{ + "tag": { Type: schema.TypeInt, Optional: true, Description: "VLAN tag.", Default: -1, }, - "firewall": &schema.Schema{ + "firewall": { Type: schema.TypeBool, Optional: true, Default: false, }, - "rate": &schema.Schema{ + "rate": { Type: schema.TypeInt, Optional: true, Computed: true, }, - "queues": &schema.Schema{ + "queues": { Type: schema.TypeInt, Optional: true, Computed: true, }, - "link_down": &schema.Schema{ + "link_down": { Type: schema.TypeBool, Optional: true, Default: false, @@ -260,85 +256,85 @@ func resourceVmQemu() *schema.Resource { }, }, }, - "unused_disk": &schema.Schema{ + "unused_disk": { Type: schema.TypeList, Computed: true, //Optional: true, Description: "Record unused disks in proxmox. This is intended to be read-only for now.", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "storage": &schema.Schema{ + "storage": { Type: schema.TypeString, Computed: true, }, - "slot": &schema.Schema{ + "slot": { Type: schema.TypeInt, Computed: true, }, - "file": &schema.Schema{ + "file": { Type: schema.TypeString, Computed: true, }, }, }, }, - "disk": &schema.Schema{ + "disk": { Type: schema.TypeList, Optional: true, ConflictsWith: []string{"disk_gb", "storage", "storage_type"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "type": &schema.Schema{ + "type": { Type: schema.TypeString, Required: true, }, - "storage": &schema.Schema{ + "storage": { Type: schema.TypeString, Required: true, }, - "size": &schema.Schema{ + "size": { Type: schema.TypeString, Required: true, ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { v := val.(string) if !(strings.Contains(v, "G") || strings.Contains(v, "M") || strings.Contains(v, "K")) { - errs = append(errs, fmt.Errorf("Disk size must end in G, M, or K, got %s", v)) + errs = append(errs, fmt.Errorf("disk size must end in G, M, or K, got %s", v)) } return }, }, - "format": &schema.Schema{ + "format": { Type: schema.TypeString, Optional: true, Computed: true, }, - "cache": &schema.Schema{ + "cache": { Type: schema.TypeString, Optional: true, Default: "none", }, - "backup": &schema.Schema{ + "backup": { Type: schema.TypeInt, Optional: true, Default: 0, }, - "iothread": &schema.Schema{ + "iothread": { Type: schema.TypeInt, Optional: true, Default: 0, }, - "replicate": &schema.Schema{ + "replicate": { Type: schema.TypeInt, Optional: true, Default: 0, }, //SSD emulation - "ssd": &schema.Schema{ + "ssd": { Type: schema.TypeInt, Optional: true, Default: 0, }, - "discard": &schema.Schema{ + "discard": { Type: schema.TypeString, Optional: true, ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { @@ -350,38 +346,38 @@ func resourceVmQemu() *schema.Resource { }, }, //Maximum r/w speed in megabytes per second - "mbps": &schema.Schema{ + "mbps": { Type: schema.TypeInt, Optional: true, Default: 0, }, - "mbps_rd": &schema.Schema{ + "mbps_rd": { Type: schema.TypeInt, Optional: true, Default: 0, }, - "mbps_rd_max": &schema.Schema{ + "mbps_rd_max": { Type: schema.TypeInt, Optional: true, Default: 0, }, - "mbps_wr": &schema.Schema{ + "mbps_wr": { Type: schema.TypeInt, Optional: true, Default: 0, }, - "mbps_wr_max": &schema.Schema{ + "mbps_wr_max": { Type: schema.TypeInt, Optional: true, Default: 0, }, // Misc - "file": &schema.Schema{ + "file": { Type: schema.TypeString, Optional: true, Computed: true, }, - "media": &schema.Schema{ + "media": { Type: schema.TypeString, Optional: true, Computed: true, @@ -396,7 +392,7 @@ func resourceVmQemu() *schema.Resource { Optional: true, Computed: true, }, - "storage_type": &schema.Schema{ + "storage_type": { Type: schema.TypeString, Required: false, Computed: true, @@ -462,16 +458,16 @@ func resourceVmQemu() *schema.Resource { }, }, // Other - "serial": &schema.Schema{ + "serial": { Type: schema.TypeSet, Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "id": &schema.Schema{ + "id": { Type: schema.TypeInt, Required: true, }, - "type": &schema.Schema{ + "type": { Type: schema.TypeString, Required: true, }, @@ -546,10 +542,11 @@ func resourceVmQemu() *schema.Resource { Optional: true, Sensitive: true, DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - if new == "**********" { - return true // api returns astericks instead of password so can't diff - } - return false + return new == "**********" + // if new == "**********" { + // return true // api returns astericks instead of password so can't diff + // } + // return false }, }, "cicustom": { @@ -637,6 +634,17 @@ func resourceVmQemu() *schema.Resource { Computed: true, Description: "Use to track vm ipv4 address", }, + "define_connection_info": { // by default define SSH for provisioner info + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "guest_agent_ready_timeout": { + Type: schema.TypeInt, + Deprecated: "Use custom per-resource timeout instead. See https://www.terraform.io/docs/language/resources/syntax.html#operation-timeouts", + Optional: true, + Default: 100, + }, }, Timeouts: resourceTimeouts(), } @@ -728,9 +736,9 @@ func resourceVmQemuCreate(d *schema.ResourceData, meta interface{}) error { pool := d.Get("pool").(string) if dupVmr != nil && forceCreate { - return fmt.Errorf("Duplicate VM name (%s) with vmId: %d. Set force_create=false to recycle", vmName, dupVmr.VmId()) + return fmt.Errorf("duplicate VM name (%s) with vmId: %d. Set force_create=false to recycle", vmName, dupVmr.VmId()) } else if dupVmr != nil && dupVmr.Node() != targetNode { - return fmt.Errorf("Duplicate VM name (%s) with vmId: %d on different target_node=%s", vmName, dupVmr.VmId(), dupVmr.Node()) + return fmt.Errorf("duplicate VM name (%s) with vmId: %d on different target_node=%s", vmName, dupVmr.VmId(), dupVmr.Node()) } vmr := dupVmr @@ -784,23 +792,28 @@ func resourceVmQemuCreate(d *schema.ResourceData, meta interface{}) error { // Waiting for the clone to become ready and // read back all the current disk configurations from proxmox // this allows us to receive updates on the post-clone state of the vm we're building - // log.Print("[DEBUG][QemuVmCreate] Waiting for clone becoming ready") - // var config_post_clone *pxapi.ConfigQemu - // for { - // // Wait until we can actually retrieve the config from the cloned machine - // config_post_clone, err = pxapi.NewConfigQemuFromApi(vmr, client) - // if config_post_clone != nil { - // break - // // to prevent an infinite loop we check for any other error - // // this error is actually fine because the clone is not ready yet - // } else if err.Error() != "vm locked, could not obtain config" { - // return err - // } - // time.Sleep(5 * time.Second) - // log.Print("[DEBUG] Clone still not ready, checking again") - // } - - config_post_clone, err := pxapi.NewConfigQemuFromApi(vmr, client) + log.Print("[DEBUG][QemuVmCreate] Waiting for clone becoming ready") + var config_post_clone *pxapi.ConfigQemu + cloneTimeout := d.Timeout(schema.TimeoutCreate) + cloneWaitEnd := time.Now().Add(time.Duration(cloneTimeout)) + log.Printf("[DEBUG][clone] retrying for at most %v minutes before giving up\n", cloneTimeout) + log.Printf("[DEBUG][clone] retries will end at %s\n", cloneWaitEnd) + + for time.Now().Before(cloneWaitEnd) { + // // Wait until we can actually retrieve the config from the cloned machine + config_post_clone, err = pxapi.NewConfigQemuFromApi(vmr, client) + if config_post_clone != nil { + break + // to prevent an infinite loop we check for any other error + // this error is actually fine because the clone is not ready yet + } else if err.Error() != "[DEBUG][clone] vm locked, could not obtain config" { + return err + } + time.Sleep(5 * time.Second) + log.Print("[DEBUG][clone] Clone still not ready, checking again") + } + + config_post_clone, err = pxapi.NewConfigQemuFromApi(vmr, client) if err != nil { return err } @@ -845,7 +858,7 @@ func resourceVmQemuCreate(d *schema.ResourceData, meta interface{}) error { return err } } else { - return fmt.Errorf("Either clone or iso must be set") + return fmt.Errorf("either clone or iso must be set") } } else { log.Printf("[DEBUG][QemuVmCreate] recycling VM vmId: %d", vmr.VmId()) @@ -935,7 +948,7 @@ func resourceVmQemuUpdate(d *schema.ResourceData, meta interface{}) error { qemuNetworks, err := ExpandDevicesList(d.Get("network").([]interface{})) if err != nil { - return fmt.Errorf("Error while processing Network configuration: %v", err) + return fmt.Errorf("error while processing Network configuration: %v", err) } logger.Debug().Int("vmid", vmID).Msgf("Processed NetworkSet into qemuNetworks as %+v", qemuNetworks) @@ -1074,17 +1087,17 @@ func resourceVmQemuUpdate(d *schema.ResourceData, meta interface{}) error { } // reboot is only required when memory hotplug is disabled - if d.HasChange("memory") && strings.Contains(d.Get("hotplug").(string), "memory") == false { + if d.HasChange("memory") && !strings.Contains(d.Get("hotplug").(string), "memory") { d.Set("reboot_required", true) } // reboot is only required when cpu hotplug is disabled - if d.HasChanges("sockets", "cores", "vcpus") && strings.Contains(d.Get("hotplug").(string), "cpu") == false { + if d.HasChanges("sockets", "cores", "vcpus") && !strings.Contains(d.Get("hotplug").(string), "cpu") { d.Set("reboot_required", true) } // if network hot(un)plug is not enabled, then check if some of the "critical" parameters have changes - if d.HasChange("network") && strings.Contains(d.Get("hotplug").(string), "network") == false { + if d.HasChange("network") && !strings.Contains(d.Get("hotplug").(string), "network") { oldValuesRaw, newValuesRaw := d.GetChange("network") oldValues := oldValuesRaw.([]interface{}) newValues := newValuesRaw.([]interface{}) @@ -1112,7 +1125,7 @@ func resourceVmQemuUpdate(d *schema.ResourceData, meta interface{}) error { oldValuesRaw, newValuesRaw := d.GetChange("disk") oldValues := oldValuesRaw.([]interface{}) newValues := newValuesRaw.([]interface{}) - if len(oldValues) != len(newValues) && strings.Contains(d.Get("hotplug").(string), "disk") == false { + if len(oldValues) != len(newValues) && !strings.Contains(d.Get("hotplug").(string), "disk") { // disk added or removed AND there is no disk hot(un)plug d.Set("reboot_required", true) } else { @@ -1139,7 +1152,7 @@ func resourceVmQemuUpdate(d *schema.ResourceData, meta interface{}) error { d.Set("reboot_required", true) } // these paramater changes only require reboot if disk hotplug is disabled - if strings.Contains(d.Get("hotplug").(string), "disk") == false { + if !strings.Contains(d.Get("hotplug").(string), "disk") { if oldValues[i].(map[string]interface{})["type"] != newValues[i].(map[string]interface{})["type"] { // note: changing type does not remove the old disk d.Set("reboot_required", true) @@ -1158,6 +1171,9 @@ func resourceVmQemuUpdate(d *schema.ResourceData, meta interface{}) error { if err != nil { log.Print("[DEBUG][QemuVmUpdate] shutdown failed, stopping VM forcefully") _, err = client.StopVm(vmr) + if err != nil { + return err + } } } else if err != nil { return err @@ -1168,6 +1184,9 @@ func resourceVmQemuUpdate(d *schema.ResourceData, meta interface{}) error { if err == nil && vmState["status"] == "stopped" { log.Print("[DEBUG][QemuVmUpdate] starting VM") _, err = client.StartVm(vmr) + if err != nil { + return err + } } else if err != nil { return err } @@ -1189,7 +1208,7 @@ func _resourceVmQemuRead(d *schema.ResourceData, meta interface{}) error { _, _, vmID, err := parseResourceId(d.Id()) if err != nil { d.SetId("") - return fmt.Errorf("Unexpected error when trying to read and parse the resource: %v", err) + return fmt.Errorf("unexpected error when trying to read and parse the resource: %v", err) } // create a logger for this function @@ -1270,13 +1289,13 @@ func _resourceVmQemuRead(d *schema.ResourceData, meta interface{}) error { // add an explicit check that the keys in the config.QemuDisks map are a strict subset of // the keys in our resource schema. if they aren't things fail in a very weird and hidden way for _, diskEntry := range config.QemuDisks { - for key, _ := range diskEntry { + for key := range diskEntry { if _, ok := thisResource.Schema["disk"].Elem.(*schema.Resource).Schema[key]; !ok { if key == "id" { // we purposely ignore id here as that is implied by the order in the TypeList/QemuDevice(list) continue } if !pconf.DangerouslyIgnoreUnknownAttributes { - return fmt.Errorf("Proxmox Provider Error: proxmox API returned new disk parameter '%v' we cannot process", key) + return fmt.Errorf("proxmox Provider Error: proxmox API returned new disk parameter '%v' we cannot process", key) } } } @@ -1324,12 +1343,12 @@ func _resourceVmQemuRead(d *schema.ResourceData, meta interface{}) error { if networkEntry["tag"] == "" || networkEntry["tag"] == nil { networkEntry["tag"] = thisResource.Schema["network"].Elem.(*schema.Resource).Schema["tag"].Default } - for key, _ := range networkEntry { + for key := range networkEntry { if _, ok := thisResource.Schema["network"].Elem.(*schema.Resource).Schema[key]; !ok { if key == "id" { // we purposely ignore id here as that is implied by the order in the TypeList/QemuDevice(list) continue } - return fmt.Errorf("Proxmox Provider Error: proxmox API returned new network parameter '%v' we cannot process", key) + return fmt.Errorf("proxmox Provider Error: proxmox API returned new network parameter '%v' we cannot process", key) } } } @@ -1453,7 +1472,7 @@ func prepareDiskSize( } else if diskSize == clonedDiskSize { logger.Debug().Int("diskId", diskID).Msgf("Disk is same size as before, skipping resize. Original '%+v', New '%+v'", diskSize, clonedDiskSize) } else { - return fmt.Errorf("Proxmox does not support decreasing disk size. Disk '%v' wanted to go from '%vG' to '%vG'", diskName, clonedDiskSize, diskSize) + return fmt.Errorf("proxmox does not support decreasing disk size. Disk '%v' wanted to go from '%vG' to '%vG'", diskName, clonedDiskSize, diskSize) } } @@ -1474,7 +1493,7 @@ func DevicesSetToMap(devicesSet *schema.Set) (pxapi.QemuDevices, error) { if _, ok := devicesMap[setID]; !ok { devicesMap[setID] = setMap } else { - return nil, fmt.Errorf("Unable to process set, received a duplicate ID '%v' check your configuration file", setID) + return nil, fmt.Errorf("unable to process set, received a duplicate ID '%v' check your configuration file", setID) } } } @@ -1654,7 +1673,7 @@ func initConnInfo( time.Sleep(10 * time.Second) } if lasterr != nil { - return fmt.Errorf("Error from PVE: \"%s\"\n, QEMU Agent is enabled in you configuration but non installed/not working on your vm", lasterr) + return fmt.Errorf("error from PVE: \"%s\"\n, QEMU Agent is enabled in you configuration but non installed/not working on your vm", lasterr) } vmConfig, err := client.GetVmConfig(vmr) if err != nil { @@ -1732,7 +1751,7 @@ func initConnInfo( // Done with proxmox API, end parallel and do the SSH things lock.unlock() if sshHost == "" { - return fmt.Errorf("Cannot find any IP address") + return fmt.Errorf("cannot find any IP address") } // Optional convience attributes for provisioners diff --git a/proxmox/timeouts.go b/proxmox/timeouts.go index c25a5ab2..31453b3f 100644 --- a/proxmox/timeouts.go +++ b/proxmox/timeouts.go @@ -9,10 +9,10 @@ import ( ) func resourceTimeouts() *schema.ResourceTimeout { - resourceCreateTimeout := 120 - resourceReadTimeout := 30 - resourceUpdateTimeout := 90 - resourceDeleteTimeout := 90 + resourceCreateTimeout := defaultTimeout + resourceReadTimeout := 180 + resourceUpdateTimeout := 180 + resourceDeleteTimeout := 180 if v, ok := os.LookupEnv("PM_TIMEOUT"); ok { resourceCreateTimeout, _ = strconv.Atoi(v) @@ -23,6 +23,6 @@ func resourceTimeouts() *schema.ResourceTimeout { Read: schema.DefaultTimeout(time.Duration(resourceReadTimeout) * time.Second), Update: schema.DefaultTimeout(time.Duration(resourceUpdateTimeout) * time.Second), Delete: schema.DefaultTimeout(time.Duration(resourceDeleteTimeout) * time.Second), - Default: schema.DefaultTimeout(120 * time.Second), + Default: schema.DefaultTimeout(defaultTimeout * time.Second), } } diff --git a/proxmox/util.go b/proxmox/util.go index 12bf8de8..2f0ee30a 100644 --- a/proxmox/util.go +++ b/proxmox/util.go @@ -14,9 +14,15 @@ import ( "github.com/rs/zerolog" ) -var rxIPconfig = regexp.MustCompile("ip6?=([0-9a-fA-F:\\.]+)") +const defaultTimeout = 300 -var macAddressRegex = regexp.MustCompile("([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}") +var rxRsId = regexp.MustCompile(`([^/]+)/([^/]+)/(\d+)`) + +var rxClusterRsId = regexp.MustCompile(`([^/]+)/([^/]+)`) + +var rxIPconfig = regexp.MustCompile(`ip6?=([0-9a-fA-F:\\.]+)`) + +var macAddressRegex = regexp.MustCompile(`([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}`) // given a string, return the appropriate zerolog level func levelStringToZerologLevel(logLevel string) (zerolog.Level, error) { @@ -32,7 +38,7 @@ func levelStringToZerologLevel(logLevel string) (zerolog.Level, error) { foundResult, ok := conversionMap[logLevel] if !ok { - return zerolog.Disabled, fmt.Errorf("Unable to find level %v", logLevel) + return zerolog.Disabled, fmt.Errorf("unable to find level %v", logLevel) } return foundResult, nil } @@ -89,6 +95,9 @@ func ConfigureLogger(enableOutput bool, logPath string, inputLogLevels map[strin // Create the log file if doesn't exist. And append to it if it already exists. // TODO log to stderr so at least terraform's TF_LOG can capture an issue if this file isn't created f, err := os.OpenFile(logPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) + if err != nil { + return + } // using a multi-writer here so we can easily add additional log destination (like a json file) // for now though using just the console writer because it makes pretty logs @@ -215,12 +224,12 @@ func UpdateDeviceConfDefaults( defaultDeviceConf *schema.Set, ) *schema.Set { defaultDeviceConfMap := defaultDeviceConf.List()[0].(map[string]interface{}) - for key, _ := range defaultDeviceConfMap { + for key := range defaultDeviceConfMap { if deviceConfigValue, ok := activeDeviceConf[key]; ok { defaultDeviceConfMap[key] = deviceConfigValue - switch deviceConfigValue.(type) { + switch deviceConfigValue := deviceConfigValue.(type) { case int: - sValue := strconv.Itoa(deviceConfigValue.(int)) + sValue := strconv.Itoa(deviceConfigValue) bValue, err := strconv.ParseBool(sValue) if err == nil { defaultDeviceConfMap[key] = bValue @@ -285,12 +294,12 @@ func AssertNoNonSchemaValues( // add an explicit check that the keys in the config.QemuNetworks map are a strict subset of // the keys in our resource schema. if they aren't things fail in a very weird and hidden way for _, deviceEntry := range devices { - for key, _ := range deviceEntry { + for key := range deviceEntry { if _, ok := schemaDef.Elem.(*schema.Resource).Schema[key]; !ok { if key == "id" { // we purposely ignore id here as that is implied by the order in the TypeList/QemuDevice(list) continue } - return fmt.Errorf("Proxmox Provider Error: proxmox API returned new parameter '%v' we cannot process", key) + return fmt.Errorf("proxmox provider error: proxmox API returned new parameter '%v' we cannot process", key) } } } @@ -310,11 +319,11 @@ func adaptDeviceToConf( // a boolean could be used in ".tf" files. switch conf[key].(type) { case bool: - switch value.(type) { + switch value := value.(type) { // If the key is bool and value is int (which comes from Proxmox API), // should be converted to bool (as in ".tf" conf). case int: - sValue := strconv.Itoa(value.(int)) + sValue := strconv.Itoa(value) bValue, err := strconv.ParseBool(sValue) if err == nil { conf[key] = bValue diff --git a/proxmox/validators.go b/proxmox/validators.go new file mode 100644 index 00000000..0671ef17 --- /dev/null +++ b/proxmox/validators.go @@ -0,0 +1,37 @@ +package proxmox + +import ( + "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" +) + +func VMIDValidator() schema.SchemaValidateDiagFunc { + return func(i interface{}, k cty.Path) diag.Diagnostics { + min := 100 + max := 999999999 + + val, ok := i.(int) + + if !ok { + return diag.Errorf("expected type of %v to be int", k) + + } + + if val != -1 { + if val < min || val > max { + return diag.Errorf("proxmox %s must be in the range (%d - %d), got %d", k, min, max, val) + } + } + + return nil + } +} + +func BIOSValidator() schema.SchemaValidateFunc { + return validation.StringInSlice([]string{ + "ovmf", + "seabios", + }, false) +}