mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-08-22 19:38:35 +00:00
Initial support for custom cloud-init user data
This commit is contained in:
parent
4780318bd4
commit
1176ef9ee4
22
README.md
22
README.md
@ -240,13 +240,22 @@ This data source doesn't accept arguments.
|
|||||||
##### File (proxmox_virtual_environment_file)
|
##### File (proxmox_virtual_environment_file)
|
||||||
|
|
||||||
###### Arguments
|
###### Arguments
|
||||||
* `content_type` - (Optional) The content type (`backup`, `images`, `iso` or `vztmpl`)
|
* `content_type` - (Optional) The content type
|
||||||
|
* `backup`
|
||||||
|
* `iso`
|
||||||
|
* `snippets`
|
||||||
|
* `vztmpl`
|
||||||
* `datastore_id` - (Required) The datastore id
|
* `datastore_id` - (Required) The datastore id
|
||||||
* `node_name` - (Required) The node name
|
* `node_name` - (Required) The node name
|
||||||
* `override_file_name` - (Optional) The file name to use instead of the source file name
|
* `source_file` - (Optional) The source file (conflicts with `source_raw`)
|
||||||
* `source` - (Required) A path to a local file or a URL
|
* `checksum` - (Optional) The SHA256 checksum of the source file
|
||||||
* `source_checksum` - (Optional) The SHA256 checksum of the source file
|
* `file_name` - (Optional) The file name to use instead of the source file name
|
||||||
* `source_insecure` - (Optional) Whether to skip the TLS verification step for HTTPS sources (defaults to `false`)
|
* `insecure` - (Optional) Whether to skip the TLS verification step for HTTPS sources (defaults to `false`)
|
||||||
|
* `path` - (Required) A path to a local file or a URL
|
||||||
|
* `source_raw` - (Optional) The raw source (conflicts with `source_file`)
|
||||||
|
* `data` - (Required) The raw data
|
||||||
|
* `file_name` - (Required) The file name
|
||||||
|
* `resize` - (Optional) The number of bytes to resize the file to
|
||||||
|
|
||||||
###### Attributes
|
###### Attributes
|
||||||
* `file_modification_date` - The file modification date (RFC 3339)
|
* `file_modification_date` - The file modification date (RFC 3339)
|
||||||
@ -334,10 +343,11 @@ This resource doesn't expose any additional attributes.
|
|||||||
* `ipv6` - (Optional) The IPv4 configuration
|
* `ipv6` - (Optional) The IPv4 configuration
|
||||||
* `address` - (Optional) The IPv6 address (use `dhcp` for autodiscovery)
|
* `address` - (Optional) The IPv6 address (use `dhcp` for autodiscovery)
|
||||||
* `gateway` - (Optional) The IPv6 gateway (must be omitted when `dhcp` is used as the address)
|
* `gateway` - (Optional) The IPv6 gateway (must be omitted when `dhcp` is used as the address)
|
||||||
* `user_account` - (Required) The user account configuration
|
* `user_account` - (Required) The user account configuration (conflicts with `user_data_file_id`)
|
||||||
* `keys` - (Required) The SSH keys
|
* `keys` - (Required) The SSH keys
|
||||||
* `password` - (Optional) The SSH password
|
* `password` - (Optional) The SSH password
|
||||||
* `username` - (Required) The SSH username
|
* `username` - (Required) The SSH username
|
||||||
|
* `user_data_file_id` - (Optional) The ID of a file containing custom user data (conflicts with `user_account`)
|
||||||
* `cpu` - (Optional) The CPU configuration
|
* `cpu` - (Optional) The CPU configuration
|
||||||
* `cores` - (Optional) The number of CPU cores (defaults to `1`)
|
* `cores` - (Optional) The number of CPU cores (defaults to `1`)
|
||||||
* `hotplugged` - (Optional) The number of hotplugged vCPUs (defaults to `0`)
|
* `hotplugged` - (Optional) The number of hotplugged vCPUs (defaults to `0`)
|
||||||
|
@ -1,8 +1,40 @@
|
|||||||
resource "proxmox_virtual_environment_file" "ubuntu_cloud_image" {
|
resource "proxmox_virtual_environment_file" "ubuntu_cloud_image" {
|
||||||
content_type = "iso"
|
content_type = "iso"
|
||||||
datastore_id = "${element(data.proxmox_virtual_environment_datastores.example.datastore_ids, index(data.proxmox_virtual_environment_datastores.example.datastore_ids, "local"))}"
|
datastore_id = "${element(data.proxmox_virtual_environment_datastores.example.datastore_ids, index(data.proxmox_virtual_environment_datastores.example.datastore_ids, "local"))}"
|
||||||
node_name = "${data.proxmox_virtual_environment_datastores.example.node_name}"
|
node_name = "${data.proxmox_virtual_environment_datastores.example.node_name}"
|
||||||
source = "https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64.img"
|
|
||||||
|
source_file {
|
||||||
|
path = "https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64.img"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "proxmox_virtual_environment_file" "cloud_init_config" {
|
||||||
|
content_type = "snippets"
|
||||||
|
datastore_id = "${element(data.proxmox_virtual_environment_datastores.example.datastore_ids, index(data.proxmox_virtual_environment_datastores.example.datastore_ids, "local"))}"
|
||||||
|
node_name = "${data.proxmox_virtual_environment_datastores.example.node_name}"
|
||||||
|
|
||||||
|
source_raw {
|
||||||
|
data = <<EOF
|
||||||
|
#cloud-config
|
||||||
|
chpasswd:
|
||||||
|
list: |
|
||||||
|
ubuntu:example
|
||||||
|
expire: False
|
||||||
|
hostname: terraform-provider-proxmox-example
|
||||||
|
packages:
|
||||||
|
- qemu-guest-agent
|
||||||
|
users:
|
||||||
|
- default
|
||||||
|
- name: ubuntu
|
||||||
|
groups: sudo
|
||||||
|
shell: /bin/bash
|
||||||
|
ssh-authorized-keys:
|
||||||
|
- ${trimspace(tls_private_key.example.public_key_openssh)}
|
||||||
|
sudo: ALL=(ALL) NOPASSWD:ALL
|
||||||
|
EOF
|
||||||
|
|
||||||
|
file_name = "terraform-provider-proxmox-example-cloud-init.yaml"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
output "resource_proxmox_virtual_environment_file_ubuntu_cloud_image_content_type" {
|
output "resource_proxmox_virtual_environment_file_ubuntu_cloud_image_content_type" {
|
||||||
@ -37,6 +69,6 @@ output "resource_proxmox_virtual_environment_file_ubuntu_cloud_image_node_name"
|
|||||||
value = "${proxmox_virtual_environment_file.ubuntu_cloud_image.node_name}"
|
value = "${proxmox_virtual_environment_file.ubuntu_cloud_image.node_name}"
|
||||||
}
|
}
|
||||||
|
|
||||||
output "resource_proxmox_virtual_environment_file_ubuntu_cloud_image_source" {
|
output "resource_proxmox_virtual_environment_file_ubuntu_cloud_image_source_file" {
|
||||||
value = "${proxmox_virtual_environment_file.ubuntu_cloud_image.source}"
|
value = "${proxmox_virtual_environment_file.ubuntu_cloud_image.source_file}"
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
resource "proxmox_virtual_environment_vm" "example" {
|
resource "proxmox_virtual_environment_vm" "example" {
|
||||||
|
agent {
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
|
|
||||||
cloud_init {
|
cloud_init {
|
||||||
dns {
|
dns {
|
||||||
server = "1.1.1.1"
|
server = "1.1.1.1"
|
||||||
@ -15,6 +19,8 @@ resource "proxmox_virtual_environment_vm" "example" {
|
|||||||
password = "proxmoxtf"
|
password = "proxmoxtf"
|
||||||
username = "ubuntu"
|
username = "ubuntu"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user_data_file_id = "${proxmox_virtual_environment_file.cloud_init_config.id}"
|
||||||
}
|
}
|
||||||
|
|
||||||
disk {
|
disk {
|
||||||
|
4
go.mod
4
go.mod
@ -5,5 +5,7 @@ go 1.13
|
|||||||
require (
|
require (
|
||||||
github.com/google/go-querystring v1.0.0
|
github.com/google/go-querystring v1.0.0
|
||||||
github.com/hashicorp/terraform v0.12.18
|
github.com/hashicorp/terraform v0.12.18
|
||||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
|
github.com/pkg/sftp v1.10.1
|
||||||
|
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586
|
||||||
)
|
)
|
||||||
|
13
go.sum
13
go.sum
@ -77,7 +77,6 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
|
|||||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
|
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
|
||||||
@ -195,8 +194,11 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
|
|||||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||||
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M=
|
github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M=
|
||||||
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.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||||
|
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
@ -254,6 +256,10 @@ github.com/packer-community/winrmcp v0.0.0-20180102160824-81144009af58/go.mod h1
|
|||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
|
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/sftp v1.10.1 h1:VasscCm72135zRysgrJDKsntdmPN+OuU3+nnHYA9wyc=
|
||||||
|
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||||
github.com/posener/complete v1.2.1 h1:LrvDIY//XNo65Lq84G/akBuMGlawHvGBABv8f/ZN6DI=
|
github.com/posener/complete v1.2.1 h1:LrvDIY//XNo65Lq84G/akBuMGlawHvGBABv8f/ZN6DI=
|
||||||
@ -281,6 +287,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d/go.mod h1:BSTlc8jOjh0niykqEGVXOLXdi9o0r0kR8tCYiMvjFgw=
|
github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d/go.mod h1:BSTlc8jOjh0niykqEGVXOLXdi9o0r0kR8tCYiMvjFgw=
|
||||||
github.com/terraform-providers/terraform-provider-openstack v1.15.0/go.mod h1:2aQ6n/BtChAl1y2S60vebhyJyZXBsuAI5G4+lHrT1Ew=
|
github.com/terraform-providers/terraform-provider-openstack v1.15.0/go.mod h1:2aQ6n/BtChAl1y2S60vebhyJyZXBsuAI5G4+lHrT1Ew=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
@ -310,8 +317,9 @@ golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f/go.mod h1:6SG95UA2DQfeDnf
|
|||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
|
||||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0=
|
||||||
|
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
@ -365,7 +373,6 @@ golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/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-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-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa h1:KIDDMLT1O0Nr7TSxp8xM5tJcdn8tgyAONntO829og1M=
|
|
||||||
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
@ -177,20 +177,7 @@ func (c *VirtualEnvironmentClient) ValidateResponseCode(res *http.Response) erro
|
|||||||
status = fmt.Sprintf("%s (%s)", status, strings.Join(errList, " - "))
|
status = fmt.Sprintf("%s (%s)", status, strings.Join(errList, " - "))
|
||||||
}
|
}
|
||||||
|
|
||||||
switch res.StatusCode {
|
return fmt.Errorf("Received an HTTP %d response - Reason: %s", res.StatusCode, status)
|
||||||
case 400, 500:
|
|
||||||
return fmt.Errorf("Received an HTTP %d response - Reason: %s", res.StatusCode, status)
|
|
||||||
case 401:
|
|
||||||
return fmt.Errorf("Received an HTTP %d response - Please verify that the specified credentials are valid", res.StatusCode)
|
|
||||||
case 403:
|
|
||||||
return fmt.Errorf("Received an HTTP %d response - Please verify that the user account has the necessary permissions", res.StatusCode)
|
|
||||||
case 404:
|
|
||||||
return fmt.Errorf("Received an HTTP %d response - Please verify that the endpoint refers to a supported version of the Proxmox Virtual Environment API", res.StatusCode)
|
|
||||||
case 501, 502, 503:
|
|
||||||
return fmt.Errorf("Received an HTTP %d response - Please verify that the Proxmox Virtual Environment API is healthy", res.StatusCode)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("Received an HTTP %d response", res.StatusCode)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -13,6 +13,9 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/sftp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DeleteDatastoreFile deletes a file in a datastore.
|
// DeleteDatastoreFile deletes a file in a datastore.
|
||||||
@ -68,80 +71,155 @@ func (c *VirtualEnvironmentClient) ListDatastores(nodeName string, d *VirtualEnv
|
|||||||
|
|
||||||
// UploadFileToDatastore uploads a file to a datastore.
|
// UploadFileToDatastore uploads a file to a datastore.
|
||||||
func (c *VirtualEnvironmentClient) UploadFileToDatastore(d *VirtualEnvironmentDatastoreUploadRequestBody) (*VirtualEnvironmentDatastoreUploadResponseBody, error) {
|
func (c *VirtualEnvironmentClient) UploadFileToDatastore(d *VirtualEnvironmentDatastoreUploadRequestBody) (*VirtualEnvironmentDatastoreUploadResponseBody, error) {
|
||||||
r, w := io.Pipe()
|
switch d.ContentType {
|
||||||
|
case "iso", "vztmpl":
|
||||||
|
r, w := io.Pipe()
|
||||||
|
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
|
|
||||||
m := multipart.NewWriter(w)
|
m := multipart.NewWriter(w)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer w.Close()
|
defer w.Close()
|
||||||
defer m.Close()
|
defer m.Close()
|
||||||
|
|
||||||
m.WriteField("content", d.ContentType)
|
m.WriteField("content", d.ContentType)
|
||||||
|
|
||||||
part, err := m.CreateFormFile("filename", d.FileName)
|
part, err := m.CreateFormFile("filename", d.FileName)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(part, d.FileReader)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// We need to store the multipart content in a temporary file to avoid using high amounts of memory.
|
||||||
|
// This is necessary due to Proxmox VE not supporting chunked transfers in v6.1 and earlier versions.
|
||||||
|
tempMultipartFile, err := ioutil.TempFile("", "multipart")
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = io.Copy(part, d.FileReader)
|
tempMultipartFileName := tempMultipartFile.Name()
|
||||||
|
|
||||||
|
io.Copy(tempMultipartFile, r)
|
||||||
|
|
||||||
|
err = tempMultipartFile.Close()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
|
|
||||||
// We need to store the multipart content in a temporary file to avoid using high amounts of memory.
|
defer os.Remove(tempMultipartFileName)
|
||||||
// This is necessary due to Proxmox VE not supporting chunked transfers in v6.1 and earlier versions.
|
|
||||||
tempMultipartFile, err := ioutil.TempFile("", "multipart")
|
|
||||||
|
|
||||||
if err != nil {
|
// Now that the multipart data is stored in a file, we can go ahead and do a HTTP POST request.
|
||||||
return nil, err
|
fileReader, err := os.Open(tempMultipartFileName)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer fileReader.Close()
|
||||||
|
|
||||||
|
fileInfo, err := fileReader.Stat()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fileSize := fileInfo.Size()
|
||||||
|
|
||||||
|
reqBody := &VirtualEnvironmentMultiPartData{
|
||||||
|
Boundary: m.Boundary(),
|
||||||
|
Reader: fileReader,
|
||||||
|
Size: &fileSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
resBody := &VirtualEnvironmentDatastoreUploadResponseBody{}
|
||||||
|
err = c.DoRequest(hmPOST, fmt.Sprintf("nodes/%s/storage/%s/upload", url.PathEscape(d.NodeName), url.PathEscape(d.DatastoreID)), reqBody, resBody)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resBody, nil
|
||||||
|
default:
|
||||||
|
// We need to upload all other files using SFTP due to API limitations.
|
||||||
|
// Hopefully, this will not be required in future releases of Proxmox VE.
|
||||||
|
sshClient, err := c.OpenNodeShell(d.NodeName)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer sshClient.Close()
|
||||||
|
|
||||||
|
sshSession, err := sshClient.NewSession()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := sshSession.CombinedOutput(
|
||||||
|
fmt.Sprintf(`grep -Pzo ': %s\s+path\s+[^\s]+' /etc/pve/storage.cfg | grep -Pzo '/[^\s]*' | tr -d '\000'`, d.DatastoreID),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
sshSession.Close()
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sshSession.Close()
|
||||||
|
|
||||||
|
datastorePath := strings.Trim(string(buf), "\000")
|
||||||
|
|
||||||
|
if datastorePath == "" {
|
||||||
|
return nil, errors.New("Failed to determine the datastore path")
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteFileDir := datastorePath
|
||||||
|
|
||||||
|
switch d.ContentType {
|
||||||
|
default:
|
||||||
|
remoteFileDir += fmt.Sprintf("/%s", d.ContentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteFilePath := fmt.Sprintf("%s/%s", remoteFileDir, d.FileName)
|
||||||
|
sftpClient, err := sftp.NewClient(sshClient)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer sftpClient.Close()
|
||||||
|
|
||||||
|
err = sftpClient.MkdirAll(remoteFileDir)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteFile, err := sftpClient.Create(remoteFilePath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer remoteFile.Close()
|
||||||
|
|
||||||
|
_, err = remoteFile.ReadFrom(d.FileReader)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &VirtualEnvironmentDatastoreUploadResponseBody{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tempMultipartFileName := tempMultipartFile.Name()
|
|
||||||
|
|
||||||
io.Copy(tempMultipartFile, r)
|
|
||||||
|
|
||||||
err = tempMultipartFile.Close()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer os.Remove(tempMultipartFileName)
|
|
||||||
|
|
||||||
// Now that the multipart data is stored in a file, we can go ahead and do a HTTP POST request.
|
|
||||||
fileReader, err := os.Open(tempMultipartFileName)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer fileReader.Close()
|
|
||||||
|
|
||||||
fileInfo, err := fileReader.Stat()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fileSize := fileInfo.Size()
|
|
||||||
|
|
||||||
reqBody := &VirtualEnvironmentMultiPartData{
|
|
||||||
Boundary: m.Boundary(),
|
|
||||||
Reader: fileReader,
|
|
||||||
Size: &fileSize,
|
|
||||||
}
|
|
||||||
|
|
||||||
resBody := &VirtualEnvironmentDatastoreUploadResponseBody{}
|
|
||||||
err = c.DoRequest(hmPOST, fmt.Sprintf("nodes/%s/storage/%s/upload", url.PathEscape(d.NodeName), url.PathEscape(d.DatastoreID)), reqBody, resBody)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return resBody, nil
|
|
||||||
}
|
}
|
||||||
|
@ -16,37 +16,7 @@ import (
|
|||||||
|
|
||||||
// ExecuteNodeCommands executes commands on a given node.
|
// ExecuteNodeCommands executes commands on a given node.
|
||||||
func (c *VirtualEnvironmentClient) ExecuteNodeCommands(nodeName string, commands []string) error {
|
func (c *VirtualEnvironmentClient) ExecuteNodeCommands(nodeName string, commands []string) error {
|
||||||
// We must first retrieve the IP address of the node as we need to bypass the API and use SSH instead.
|
sshClient, err := c.OpenNodeShell(nodeName)
|
||||||
networkDevices, err := c.ListNodeNetworkDevices(nodeName)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeAddress := ""
|
|
||||||
|
|
||||||
for _, d := range networkDevices {
|
|
||||||
if d.Address != nil {
|
|
||||||
nodeAddress = *d.Address
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if nodeAddress == "" {
|
|
||||||
return fmt.Errorf("Failed to determine the IP address of node \"%s\"", nodeName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can now go ahead and execute the commands using SSH.
|
|
||||||
// Hopefully, the developers will add this feature to the REST API at some point.
|
|
||||||
ur := strings.Split(c.Username, "@")
|
|
||||||
|
|
||||||
sshConfig := &ssh.ClientConfig{
|
|
||||||
User: ur[0],
|
|
||||||
Auth: []ssh.AuthMethod{ssh.Password(c.Password)},
|
|
||||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
|
||||||
}
|
|
||||||
|
|
||||||
sshClient, err := ssh.Dial("tcp", nodeAddress+":22", sshConfig)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -62,7 +32,7 @@ func (c *VirtualEnvironmentClient) ExecuteNodeCommands(nodeName string, commands
|
|||||||
|
|
||||||
defer sshSession.Close()
|
defer sshSession.Close()
|
||||||
|
|
||||||
_, err = sshSession.CombinedOutput(
|
output, err := sshSession.CombinedOutput(
|
||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
"/bin/bash -c '%s'",
|
"/bin/bash -c '%s'",
|
||||||
strings.ReplaceAll(strings.Join(commands, " && "), "'", "'\"'\"'"),
|
strings.ReplaceAll(strings.Join(commands, " && "), "'", "'\"'\"'"),
|
||||||
@ -70,12 +40,36 @@ func (c *VirtualEnvironmentClient) ExecuteNodeCommands(nodeName string, commands
|
|||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.New(string(output))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetNodeIP retrieves the IP address of a node.
|
||||||
|
func (c *VirtualEnvironmentClient) GetNodeIP(nodeName string) (*string, error) {
|
||||||
|
networkDevices, err := c.ListNodeNetworkDevices(nodeName)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeAddress := ""
|
||||||
|
|
||||||
|
for _, d := range networkDevices {
|
||||||
|
if d.Address != nil {
|
||||||
|
nodeAddress = *d.Address
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if nodeAddress == "" {
|
||||||
|
return nil, fmt.Errorf("Failed to determine the IP address of node \"%s\"", nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &nodeAddress, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ListNodeNetworkDevices retrieves a list of network devices for a specific nodes.
|
// ListNodeNetworkDevices retrieves a list of network devices for a specific nodes.
|
||||||
func (c *VirtualEnvironmentClient) ListNodeNetworkDevices(nodeName string) ([]*VirtualEnvironmentNodeNetworkDeviceListResponseData, error) {
|
func (c *VirtualEnvironmentClient) ListNodeNetworkDevices(nodeName string) ([]*VirtualEnvironmentNodeNetworkDeviceListResponseData, error) {
|
||||||
resBody := &VirtualEnvironmentNodeNetworkDeviceListResponseBody{}
|
resBody := &VirtualEnvironmentNodeNetworkDeviceListResponseBody{}
|
||||||
@ -115,3 +109,28 @@ func (c *VirtualEnvironmentClient) ListNodes() ([]*VirtualEnvironmentNodeListRes
|
|||||||
|
|
||||||
return resBody.Data, nil
|
return resBody.Data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenNodeShell establishes a new SSH connection to a node.
|
||||||
|
func (c *VirtualEnvironmentClient) OpenNodeShell(nodeName string) (*ssh.Client, error) {
|
||||||
|
nodeAddress, err := c.GetNodeIP(nodeName)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ur := strings.Split(c.Username, "@")
|
||||||
|
|
||||||
|
sshConfig := &ssh.ClientConfig{
|
||||||
|
User: ur[0],
|
||||||
|
Auth: []ssh.AuthMethod{ssh.Password(c.Password)},
|
||||||
|
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||||
|
}
|
||||||
|
|
||||||
|
sshClient, err := ssh.Dial("tcp", *nodeAddress+":22", sshConfig)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sshClient, nil
|
||||||
|
}
|
||||||
|
@ -5,9 +5,9 @@
|
|||||||
package proxmoxtf
|
package proxmoxtf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -24,11 +24,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
dvResourceVirtualEnvironmentFileContentType = ""
|
dvResourceVirtualEnvironmentFileContentType = ""
|
||||||
dvResourceVirtualEnvironmentFileOverrideFileName = ""
|
dvResourceVirtualEnvironmentFileSourceData = ""
|
||||||
dvResourceVirtualEnvironmentFileSourceChanged = false
|
dvResourceVirtualEnvironmentFileSourceFileChanged = false
|
||||||
dvResourceVirtualEnvironmentFileSourceChecksum = ""
|
dvResourceVirtualEnvironmentFileSourceFileChecksum = ""
|
||||||
dvResourceVirtualEnvironmentFileSourceInsecure = false
|
dvResourceVirtualEnvironmentFileSourceFileFileName = ""
|
||||||
|
dvResourceVirtualEnvironmentFileSourceFileInsecure = false
|
||||||
|
dvResourceVirtualEnvironmentFileSourceRawResize = 0
|
||||||
|
|
||||||
mkResourceVirtualEnvironmentFileContentType = "content_type"
|
mkResourceVirtualEnvironmentFileContentType = "content_type"
|
||||||
mkResourceVirtualEnvironmentFileDatastoreID = "datastore_id"
|
mkResourceVirtualEnvironmentFileDatastoreID = "datastore_id"
|
||||||
@ -36,23 +38,29 @@ const (
|
|||||||
mkResourceVirtualEnvironmentFileFileName = "file_name"
|
mkResourceVirtualEnvironmentFileFileName = "file_name"
|
||||||
mkResourceVirtualEnvironmentFileFileSize = "file_size"
|
mkResourceVirtualEnvironmentFileFileSize = "file_size"
|
||||||
mkResourceVirtualEnvironmentFileFileTag = "file_tag"
|
mkResourceVirtualEnvironmentFileFileTag = "file_tag"
|
||||||
mkResourceVirtualEnvironmentFileOverrideFileName = "override_file_name"
|
|
||||||
mkResourceVirtualEnvironmentFileNodeName = "node_name"
|
mkResourceVirtualEnvironmentFileNodeName = "node_name"
|
||||||
mkResourceVirtualEnvironmentFileSource = "source"
|
mkResourceVirtualEnvironmentFileSourceFile = "source_file"
|
||||||
mkResourceVirtualEnvironmentFileSourceChanged = "source_changed"
|
mkResourceVirtualEnvironmentFileSourceFilePath = "path"
|
||||||
mkResourceVirtualEnvironmentFileSourceChecksum = "source_checksum"
|
mkResourceVirtualEnvironmentFileSourceFileChanged = "changed"
|
||||||
mkResourceVirtualEnvironmentFileSourceInsecure = "source_insecure"
|
mkResourceVirtualEnvironmentFileSourceFileChecksum = "checksum"
|
||||||
|
mkResourceVirtualEnvironmentFileSourceFileFileName = "file_name"
|
||||||
|
mkResourceVirtualEnvironmentFileSourceFileInsecure = "insecure"
|
||||||
|
mkResourceVirtualEnvironmentFileSourceRaw = "source_raw"
|
||||||
|
mkResourceVirtualEnvironmentFileSourceRawData = "data"
|
||||||
|
mkResourceVirtualEnvironmentFileSourceRawFileName = "file_name"
|
||||||
|
mkResourceVirtualEnvironmentFileSourceRawResize = "resize"
|
||||||
)
|
)
|
||||||
|
|
||||||
func resourceVirtualEnvironmentFile() *schema.Resource {
|
func resourceVirtualEnvironmentFile() *schema.Resource {
|
||||||
return &schema.Resource{
|
return &schema.Resource{
|
||||||
Schema: map[string]*schema.Schema{
|
Schema: map[string]*schema.Schema{
|
||||||
mkResourceVirtualEnvironmentFileContentType: &schema.Schema{
|
mkResourceVirtualEnvironmentFileContentType: &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Description: "The content type",
|
Description: "The content type",
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
Default: dvResourceVirtualEnvironmentFileContentType,
|
Default: dvResourceVirtualEnvironmentFileContentType,
|
||||||
|
ValidateFunc: getContentTypeValidator(),
|
||||||
},
|
},
|
||||||
mkResourceVirtualEnvironmentFileDatastoreID: &schema.Schema{
|
mkResourceVirtualEnvironmentFileDatastoreID: &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
@ -83,45 +91,94 @@ func resourceVirtualEnvironmentFile() *schema.Resource {
|
|||||||
Computed: true,
|
Computed: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
mkResourceVirtualEnvironmentFileOverrideFileName: &schema.Schema{
|
|
||||||
Type: schema.TypeString,
|
|
||||||
Description: "The file name to use instead of the source file name",
|
|
||||||
Optional: true,
|
|
||||||
ForceNew: true,
|
|
||||||
Default: dvResourceVirtualEnvironmentFileOverrideFileName,
|
|
||||||
},
|
|
||||||
mkResourceVirtualEnvironmentFileNodeName: &schema.Schema{
|
mkResourceVirtualEnvironmentFileNodeName: &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Description: "The node name",
|
Description: "The node name",
|
||||||
Required: true,
|
Required: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
},
|
},
|
||||||
mkResourceVirtualEnvironmentFileSource: &schema.Schema{
|
mkResourceVirtualEnvironmentFileSourceFile: &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeList,
|
||||||
Description: "A path to a local file or a URL",
|
Description: "The source file",
|
||||||
Required: true,
|
|
||||||
ForceNew: true,
|
|
||||||
},
|
|
||||||
mkResourceVirtualEnvironmentFileSourceChanged: &schema.Schema{
|
|
||||||
Type: schema.TypeBool,
|
|
||||||
Description: "Whether the source has changed since the last run",
|
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
Default: dvResourceVirtualEnvironmentFileSourceChanged,
|
DefaultFunc: func() (interface{}, error) {
|
||||||
|
return make([]interface{}, 1), nil
|
||||||
|
},
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
mkResourceVirtualEnvironmentFileSourceFilePath: &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Description: "A path to a local file or a URL",
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
mkResourceVirtualEnvironmentFileSourceFileChanged: &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Description: "Whether the source file has changed since the last run",
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Default: dvResourceVirtualEnvironmentFileSourceFileChanged,
|
||||||
|
},
|
||||||
|
mkResourceVirtualEnvironmentFileSourceFileChecksum: &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Description: "The SHA256 checksum of the source file",
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Default: dvResourceVirtualEnvironmentFileSourceFileChecksum,
|
||||||
|
},
|
||||||
|
mkResourceVirtualEnvironmentFileSourceFileFileName: &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Description: "The file name to use instead of the source file name",
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Default: dvResourceVirtualEnvironmentFileSourceFileFileName,
|
||||||
|
},
|
||||||
|
mkResourceVirtualEnvironmentFileSourceFileInsecure: &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Description: "Whether to skip the TLS verification step for HTTPS sources",
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Default: dvResourceVirtualEnvironmentFileSourceFileInsecure,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MaxItems: 1,
|
||||||
|
MinItems: 0,
|
||||||
},
|
},
|
||||||
mkResourceVirtualEnvironmentFileSourceChecksum: &schema.Schema{
|
mkResourceVirtualEnvironmentFileSourceRaw: &schema.Schema{
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeList,
|
||||||
Description: "The SHA256 checksum of the source file",
|
Description: "The raw source",
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ForceNew: true,
|
ForceNew: true,
|
||||||
Default: dvResourceVirtualEnvironmentFileSourceChecksum,
|
DefaultFunc: func() (interface{}, error) {
|
||||||
},
|
return make([]interface{}, 1), nil
|
||||||
mkResourceVirtualEnvironmentFileSourceInsecure: &schema.Schema{
|
},
|
||||||
Type: schema.TypeBool,
|
Elem: &schema.Resource{
|
||||||
Description: "Whether to skip the TLS verification step for HTTPS sources",
|
Schema: map[string]*schema.Schema{
|
||||||
Optional: true,
|
mkResourceVirtualEnvironmentFileSourceRawData: &schema.Schema{
|
||||||
ForceNew: true,
|
Type: schema.TypeString,
|
||||||
Default: dvResourceVirtualEnvironmentFileSourceInsecure,
|
Description: "The raw data",
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
mkResourceVirtualEnvironmentFileSourceRawFileName: &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Description: "The file name",
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
mkResourceVirtualEnvironmentFileSourceRawResize: &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Description: "The number of bytes to resize the file to",
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Default: dvResourceVirtualEnvironmentFileSourceRawResize,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MaxItems: 1,
|
||||||
|
MinItems: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Create: resourceVirtualEnvironmentFileCreate,
|
Create: resourceVirtualEnvironmentFileCreate,
|
||||||
@ -152,87 +209,144 @@ func resourceVirtualEnvironmentFileCreate(d *schema.ResourceData, m interface{})
|
|||||||
}
|
}
|
||||||
|
|
||||||
nodeName := d.Get(mkResourceVirtualEnvironmentFileNodeName).(string)
|
nodeName := d.Get(mkResourceVirtualEnvironmentFileNodeName).(string)
|
||||||
source := d.Get(mkResourceVirtualEnvironmentFileSource).(string)
|
sourceFile := d.Get(mkResourceVirtualEnvironmentFileSourceFile).([]interface{})
|
||||||
sourceChecksum := d.Get(mkResourceVirtualEnvironmentFileSourceChecksum).(string)
|
sourceRaw := d.Get(mkResourceVirtualEnvironmentFileSourceRaw).([]interface{})
|
||||||
sourceInsecure := d.Get(mkResourceVirtualEnvironmentFileSourceInsecure).(bool)
|
|
||||||
|
|
||||||
sourceFile := ""
|
sourceFilePathLocal := ""
|
||||||
|
|
||||||
// Download the source file, if it's not available locally.
|
// Determine if both source_data and source_file is specified as this is not supported.
|
||||||
if resourceVirtualEnvironmentFileIsURL(d, m) {
|
if len(sourceFile) > 0 && len(sourceRaw) > 0 {
|
||||||
log.Printf("[DEBUG] Downloading file from '%s'", source)
|
return fmt.Errorf(
|
||||||
|
"Please specify \"%s.%s\" or \"%s\" - not both",
|
||||||
httpClient := http.Client{
|
mkResourceVirtualEnvironmentFileSourceFile,
|
||||||
Transport: &http.Transport{
|
mkResourceVirtualEnvironmentFileSourceFilePath,
|
||||||
TLSClientConfig: &tls.Config{
|
mkResourceVirtualEnvironmentFileSourceRaw,
|
||||||
InsecureSkipVerify: sourceInsecure,
|
)
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := httpClient.Get(source)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer res.Body.Close()
|
|
||||||
|
|
||||||
tempDownloadedFile, err := ioutil.TempFile("", "download")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tempDownloadedFileName := tempDownloadedFile.Name()
|
|
||||||
|
|
||||||
_, err = io.Copy(tempDownloadedFile, res.Body)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
tempDownloadedFile.Close()
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tempDownloadedFile.Close()
|
|
||||||
|
|
||||||
defer os.Remove(tempDownloadedFileName)
|
|
||||||
|
|
||||||
sourceFile = tempDownloadedFileName
|
|
||||||
} else {
|
|
||||||
sourceFile = source
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the checksum of the source file now that it's available locally.
|
// Determine if we're dealing with raw file data or a reference to a file or URL.
|
||||||
if sourceChecksum != "" {
|
// In case of a URL, we must first download the file before proceeding.
|
||||||
file, err := os.Open(sourceFile)
|
// This is due to lack of support for chunked transfers in the Proxmox VE API.
|
||||||
|
if len(sourceFile) > 0 {
|
||||||
|
sourceFileBlock := sourceFile[0].(map[string]interface{})
|
||||||
|
sourceFilePath := sourceFileBlock[mkResourceVirtualEnvironmentFileSourceFilePath].(string)
|
||||||
|
sourceFileChecksum := sourceFileBlock[mkResourceVirtualEnvironmentFileSourceFileChecksum].(string)
|
||||||
|
sourceFileInsecure := sourceFileBlock[mkResourceVirtualEnvironmentFileSourceFileInsecure].(bool)
|
||||||
|
|
||||||
if err != nil {
|
if resourceVirtualEnvironmentFileIsURL(d, m) {
|
||||||
return err
|
log.Printf("[DEBUG] Downloading file from '%s'", sourceFilePath)
|
||||||
|
|
||||||
|
httpClient := http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: sourceFileInsecure,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := httpClient.Get(sourceFilePath)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
tempDownloadedFile, err := ioutil.TempFile("", "download")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tempDownloadedFileName := tempDownloadedFile.Name()
|
||||||
|
_, err = io.Copy(tempDownloadedFile, res.Body)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
tempDownloadedFile.Close()
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tempDownloadedFile.Close()
|
||||||
|
|
||||||
|
defer os.Remove(tempDownloadedFileName)
|
||||||
|
|
||||||
|
sourceFilePathLocal = tempDownloadedFileName
|
||||||
|
} else {
|
||||||
|
sourceFilePathLocal = sourceFilePath
|
||||||
}
|
}
|
||||||
|
|
||||||
h := sha256.New()
|
// Calculate the checksum of the source file now that it's available locally.
|
||||||
_, err = io.Copy(h, file)
|
if sourceFileChecksum != "" {
|
||||||
|
file, err := os.Open(sourceFilePathLocal)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
h := sha256.New()
|
||||||
|
_, err = io.Copy(h, file)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
file.Close()
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
file.Close()
|
file.Close()
|
||||||
|
|
||||||
|
calculatedChecksum := fmt.Sprintf("%x", h.Sum(nil))
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] The calculated SHA256 checksum for source \"%s\" is \"%s\"", sourceFilePath, calculatedChecksum)
|
||||||
|
|
||||||
|
if sourceFileChecksum != calculatedChecksum {
|
||||||
|
return fmt.Errorf("The calculated SHA256 checksum \"%s\" does not match source checksum \"%s\"", calculatedChecksum, sourceFileChecksum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if len(sourceRaw) > 0 {
|
||||||
|
sourceRawBlock := sourceRaw[0].(map[string]interface{})
|
||||||
|
sourceRawData := sourceRawBlock[mkResourceVirtualEnvironmentFileSourceRawData].(string)
|
||||||
|
sourceRawResize := sourceRawBlock[mkResourceVirtualEnvironmentFileSourceRawResize].(int)
|
||||||
|
|
||||||
|
if sourceRawResize > 0 {
|
||||||
|
if len(sourceRawData) <= sourceRawResize {
|
||||||
|
sourceRawData = fmt.Sprintf(fmt.Sprintf("%%-%dv", sourceRawResize), sourceRawData)
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Cannot resize %d bytes to %d bytes", len(sourceRawData), sourceRawResize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tempRawFile, err := ioutil.TempFile("", "raw")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
file.Close()
|
tempRawFileName := tempRawFile.Name()
|
||||||
|
_, err = io.Copy(tempRawFile, bytes.NewBufferString(sourceRawData))
|
||||||
|
|
||||||
calculatedChecksum := fmt.Sprintf("%x", h.Sum(nil))
|
if err != nil {
|
||||||
|
tempRawFile.Close()
|
||||||
|
|
||||||
log.Printf("[DEBUG] The calculated SHA256 checksum for source \"%s\" is \"%s\"", source, calculatedChecksum)
|
return err
|
||||||
|
|
||||||
if sourceChecksum != calculatedChecksum {
|
|
||||||
return fmt.Errorf("The calculated SHA256 checksum \"%s\" does not match source checksum \"%s\"", calculatedChecksum, sourceChecksum)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tempRawFile.Close()
|
||||||
|
|
||||||
|
defer os.Remove(tempRawFileName)
|
||||||
|
|
||||||
|
sourceFilePathLocal = tempRawFileName
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"Please specify either \"%s.%s\" or \"%s\"",
|
||||||
|
mkResourceVirtualEnvironmentFileSourceFile,
|
||||||
|
mkResourceVirtualEnvironmentFileSourceFilePath,
|
||||||
|
mkResourceVirtualEnvironmentFileSourceRaw,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the source file for reading in order to upload it.
|
// Open the source file for reading in order to upload it.
|
||||||
file, err := os.Open(sourceFile)
|
file, err := os.Open(sourceFilePathLocal)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -267,69 +381,101 @@ func resourceVirtualEnvironmentFileCreate(d *schema.ResourceData, m interface{})
|
|||||||
|
|
||||||
func resourceVirtualEnvironmentFileGetContentType(d *schema.ResourceData, m interface{}) (*string, error) {
|
func resourceVirtualEnvironmentFileGetContentType(d *schema.ResourceData, m interface{}) (*string, error) {
|
||||||
contentType := d.Get(mkResourceVirtualEnvironmentFileContentType).(string)
|
contentType := d.Get(mkResourceVirtualEnvironmentFileContentType).(string)
|
||||||
source := d.Get(mkResourceVirtualEnvironmentFileSource).(string)
|
sourceFile := d.Get(mkResourceVirtualEnvironmentFileSourceFile).([]interface{})
|
||||||
supportedTypes := []string{"backup", "images", "iso", "vztmpl"}
|
sourceRaw := d.Get(mkResourceVirtualEnvironmentFileSourceRaw).([]interface{})
|
||||||
|
|
||||||
|
sourceFilePath := ""
|
||||||
|
|
||||||
|
if len(sourceFile) > 0 {
|
||||||
|
sourceFileBlock := sourceFile[0].(map[string]interface{})
|
||||||
|
sourceFilePath = sourceFileBlock[mkResourceVirtualEnvironmentFileSourceFilePath].(string)
|
||||||
|
} else if len(sourceRaw) > 0 {
|
||||||
|
sourceRawBlock := sourceRaw[0].(map[string]interface{})
|
||||||
|
sourceFilePath = sourceRawBlock[mkResourceVirtualEnvironmentFileSourceRawFileName].(string)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"Missing argument \"%s.%s\" or \"%s\"",
|
||||||
|
mkResourceVirtualEnvironmentFileSourceFile,
|
||||||
|
mkResourceVirtualEnvironmentFileSourceFilePath,
|
||||||
|
mkResourceVirtualEnvironmentFileSourceRaw,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if contentType == "" {
|
if contentType == "" {
|
||||||
if strings.HasSuffix(source, ".tar.xz") {
|
if strings.HasSuffix(sourceFilePath, ".tar.xz") {
|
||||||
contentType = "vztmpl"
|
contentType = "vztmpl"
|
||||||
} else {
|
} else {
|
||||||
ext := strings.TrimLeft(strings.ToLower(filepath.Ext(source)), ".")
|
ext := strings.TrimLeft(strings.ToLower(filepath.Ext(sourceFilePath)), ".")
|
||||||
|
|
||||||
switch ext {
|
switch ext {
|
||||||
case "img", "iso":
|
case "img", "iso":
|
||||||
contentType = "iso"
|
contentType = "iso"
|
||||||
|
case "yaml", "yml":
|
||||||
|
contentType = "snippets"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if contentType == "" {
|
if contentType == "" {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"Cannot determine the content type of source \"%s\" - Please manually define the \"%s\" argument (supported: %s)",
|
"Cannot determine the content type of source \"%s\" - Please manually define the \"%s\" argument",
|
||||||
source,
|
sourceFilePath,
|
||||||
mkResourceVirtualEnvironmentFileContentType,
|
mkResourceVirtualEnvironmentFileContentType,
|
||||||
strings.Join(supportedTypes, " or "),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range supportedTypes {
|
ctValidator := getContentTypeValidator()
|
||||||
if v == contentType {
|
_, errs := ctValidator(contentType, mkResourceVirtualEnvironmentFileContentType)
|
||||||
return &contentType, nil
|
|
||||||
}
|
if len(errs) > 0 {
|
||||||
|
return nil, errs[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf(
|
return &contentType, nil
|
||||||
"Unsupported content type \"%s\" for source \"%s\" (supported: %s)",
|
|
||||||
contentType,
|
|
||||||
source,
|
|
||||||
strings.Join(supportedTypes, " or "),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceVirtualEnvironmentFileGetFileName(d *schema.ResourceData, m interface{}) (*string, error) {
|
func resourceVirtualEnvironmentFileGetFileName(d *schema.ResourceData, m interface{}) (*string, error) {
|
||||||
fileName := d.Get(mkResourceVirtualEnvironmentFileOverrideFileName).(string)
|
sourceFile := d.Get(mkResourceVirtualEnvironmentFileSourceFile).([]interface{})
|
||||||
source := d.Get(mkResourceVirtualEnvironmentFileSource).(string)
|
sourceRaw := d.Get(mkResourceVirtualEnvironmentFileSourceRaw).([]interface{})
|
||||||
|
|
||||||
if fileName == "" {
|
sourceFileFileName := ""
|
||||||
|
sourceFilePath := ""
|
||||||
|
|
||||||
|
if len(sourceFile) > 0 {
|
||||||
|
sourceFileBlock := sourceFile[0].(map[string]interface{})
|
||||||
|
sourceFileFileName = sourceFileBlock[mkResourceVirtualEnvironmentFileSourceFileFileName].(string)
|
||||||
|
sourceFilePath = sourceFileBlock[mkResourceVirtualEnvironmentFileSourceFilePath].(string)
|
||||||
|
} else if len(sourceRaw) > 0 {
|
||||||
|
sourceRawBlock := sourceRaw[0].(map[string]interface{})
|
||||||
|
sourceFileFileName = sourceRawBlock[mkResourceVirtualEnvironmentFileSourceRawFileName].(string)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"Missing argument \"%s.%s\"",
|
||||||
|
mkResourceVirtualEnvironmentFileSourceFile,
|
||||||
|
mkResourceVirtualEnvironmentFileSourceFilePath,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sourceFileFileName == "" {
|
||||||
if resourceVirtualEnvironmentFileIsURL(d, m) {
|
if resourceVirtualEnvironmentFileIsURL(d, m) {
|
||||||
downloadURL, err := url.ParseRequestURI(source)
|
downloadURL, err := url.ParseRequestURI(sourceFilePath)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
path := strings.Split(downloadURL.Path, "/")
|
path := strings.Split(downloadURL.Path, "/")
|
||||||
fileName = path[len(path)-1]
|
sourceFileFileName = path[len(path)-1]
|
||||||
|
|
||||||
if fileName == "" {
|
if sourceFileFileName == "" {
|
||||||
return nil, errors.New("Failed to determine file name from source URL")
|
return nil, fmt.Errorf("Failed to determine file name from the URL \"%s\"", sourceFilePath)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fileName = filepath.Base(source)
|
sourceFileFileName = filepath.Base(sourceFilePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &fileName, nil
|
return &sourceFileFileName, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceVirtualEnvironmentFileGetVolumeID(d *schema.ResourceData, m interface{}) (*string, error) {
|
func resourceVirtualEnvironmentFileGetVolumeID(d *schema.ResourceData, m interface{}) (*string, error) {
|
||||||
@ -352,9 +498,17 @@ func resourceVirtualEnvironmentFileGetVolumeID(d *schema.ResourceData, m interfa
|
|||||||
}
|
}
|
||||||
|
|
||||||
func resourceVirtualEnvironmentFileIsURL(d *schema.ResourceData, m interface{}) bool {
|
func resourceVirtualEnvironmentFileIsURL(d *schema.ResourceData, m interface{}) bool {
|
||||||
source := d.Get(mkResourceVirtualEnvironmentFileSource).(string)
|
sourceFile := d.Get(mkResourceVirtualEnvironmentFileSourceFile).([]interface{})
|
||||||
|
sourceFilePath := ""
|
||||||
|
|
||||||
return strings.HasPrefix(source, "http://") || strings.HasPrefix(source, "https://")
|
if len(sourceFile) > 0 {
|
||||||
|
sourceFileBlock := sourceFile[0].(map[string]interface{})
|
||||||
|
sourceFilePath = sourceFileBlock[mkResourceVirtualEnvironmentFileSourceFilePath].(string)
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.HasPrefix(sourceFilePath, "http://") || strings.HasPrefix(sourceFilePath, "https://")
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceVirtualEnvironmentFileRead(d *schema.ResourceData, m interface{}) error {
|
func resourceVirtualEnvironmentFileRead(d *schema.ResourceData, m interface{}) error {
|
||||||
@ -367,6 +521,15 @@ func resourceVirtualEnvironmentFileRead(d *schema.ResourceData, m interface{}) e
|
|||||||
|
|
||||||
datastoreID := d.Get(mkResourceVirtualEnvironmentFileDatastoreID).(string)
|
datastoreID := d.Get(mkResourceVirtualEnvironmentFileDatastoreID).(string)
|
||||||
nodeName := d.Get(mkResourceVirtualEnvironmentFileNodeName).(string)
|
nodeName := d.Get(mkResourceVirtualEnvironmentFileNodeName).(string)
|
||||||
|
sourceFile := d.Get(mkResourceVirtualEnvironmentFileSourceFile).([]interface{})
|
||||||
|
sourceFilePath := ""
|
||||||
|
|
||||||
|
if len(sourceFile) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceFileBlock := sourceFile[0].(map[string]interface{})
|
||||||
|
sourceFilePath = sourceFileBlock[mkResourceVirtualEnvironmentFileSourceFilePath].(string)
|
||||||
|
|
||||||
list, err := veClient.ListDatastoreFiles(nodeName, datastoreID)
|
list, err := veClient.ListDatastoreFiles(nodeName, datastoreID)
|
||||||
|
|
||||||
@ -374,17 +537,21 @@ func resourceVirtualEnvironmentFileRead(d *schema.ResourceData, m interface{}) e
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileIsURL := resourceVirtualEnvironmentFileIsURL(d, m)
|
||||||
|
fileName, err := resourceVirtualEnvironmentFileGetFileName(d, m)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
for _, v := range list {
|
for _, v := range list {
|
||||||
if v.VolumeID == d.Id() {
|
if v.VolumeID == d.Id() {
|
||||||
fileName, _ := resourceVirtualEnvironmentFileGetFileName(d, m)
|
|
||||||
source := d.Get(mkResourceVirtualEnvironmentFileSource).(string)
|
|
||||||
|
|
||||||
var fileModificationDate string
|
var fileModificationDate string
|
||||||
var fileSize int64
|
var fileSize int64
|
||||||
var fileTag string
|
var fileTag string
|
||||||
|
|
||||||
if resourceVirtualEnvironmentFileIsURL(d, m) {
|
if fileIsURL {
|
||||||
res, err := http.Head(source)
|
res, err := http.Head(sourceFilePath)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -425,7 +592,7 @@ func resourceVirtualEnvironmentFileRead(d *schema.ResourceData, m interface{}) e
|
|||||||
fileTag = ""
|
fileTag = ""
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
f, err := os.Open(source)
|
f, err := os.Open(sourceFilePath)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -452,7 +619,7 @@ func resourceVirtualEnvironmentFileRead(d *schema.ResourceData, m interface{}) e
|
|||||||
d.Set(mkResourceVirtualEnvironmentFileFileName, *fileName)
|
d.Set(mkResourceVirtualEnvironmentFileFileName, *fileName)
|
||||||
d.Set(mkResourceVirtualEnvironmentFileFileSize, fileSize)
|
d.Set(mkResourceVirtualEnvironmentFileFileSize, fileSize)
|
||||||
d.Set(mkResourceVirtualEnvironmentFileFileTag, fileTag)
|
d.Set(mkResourceVirtualEnvironmentFileFileTag, fileTag)
|
||||||
d.Set(mkResourceVirtualEnvironmentFileSourceChanged, lastFileModificationDate != fileModificationDate || lastFileSize != fileSize || lastFileTag != fileTag)
|
d.Set(mkResourceVirtualEnvironmentFileSourceFileChanged, lastFileModificationDate != fileModificationDate || lastFileSize != fileSize || lastFileTag != fileTag)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -26,15 +26,12 @@ func TestResourceVirtualEnvironmentFileSchema(t *testing.T) {
|
|||||||
testRequiredArguments(t, s, []string{
|
testRequiredArguments(t, s, []string{
|
||||||
mkResourceVirtualEnvironmentFileDatastoreID,
|
mkResourceVirtualEnvironmentFileDatastoreID,
|
||||||
mkResourceVirtualEnvironmentFileNodeName,
|
mkResourceVirtualEnvironmentFileNodeName,
|
||||||
mkResourceVirtualEnvironmentFileSource,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
testOptionalArguments(t, s, []string{
|
testOptionalArguments(t, s, []string{
|
||||||
mkResourceVirtualEnvironmentFileContentType,
|
mkResourceVirtualEnvironmentFileContentType,
|
||||||
mkResourceVirtualEnvironmentFileOverrideFileName,
|
mkResourceVirtualEnvironmentFileSourceFile,
|
||||||
mkResourceVirtualEnvironmentFileSourceChanged,
|
mkResourceVirtualEnvironmentFileSourceRaw,
|
||||||
mkResourceVirtualEnvironmentFileSourceChecksum,
|
|
||||||
mkResourceVirtualEnvironmentFileSourceInsecure,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
testComputedAttributes(t, s, []string{
|
testComputedAttributes(t, s, []string{
|
||||||
@ -51,12 +48,9 @@ func TestResourceVirtualEnvironmentFileSchema(t *testing.T) {
|
|||||||
mkResourceVirtualEnvironmentFileFileName,
|
mkResourceVirtualEnvironmentFileFileName,
|
||||||
mkResourceVirtualEnvironmentFileFileSize,
|
mkResourceVirtualEnvironmentFileFileSize,
|
||||||
mkResourceVirtualEnvironmentFileFileTag,
|
mkResourceVirtualEnvironmentFileFileTag,
|
||||||
mkResourceVirtualEnvironmentFileOverrideFileName,
|
|
||||||
mkResourceVirtualEnvironmentFileSourceChanged,
|
|
||||||
mkResourceVirtualEnvironmentFileNodeName,
|
mkResourceVirtualEnvironmentFileNodeName,
|
||||||
mkResourceVirtualEnvironmentFileSource,
|
mkResourceVirtualEnvironmentFileSourceFile,
|
||||||
mkResourceVirtualEnvironmentFileSourceChecksum,
|
mkResourceVirtualEnvironmentFileSourceRaw,
|
||||||
mkResourceVirtualEnvironmentFileSourceInsecure,
|
|
||||||
}, []schema.ValueType{
|
}, []schema.ValueType{
|
||||||
schema.TypeString,
|
schema.TypeString,
|
||||||
schema.TypeString,
|
schema.TypeString,
|
||||||
@ -65,10 +59,55 @@ func TestResourceVirtualEnvironmentFileSchema(t *testing.T) {
|
|||||||
schema.TypeInt,
|
schema.TypeInt,
|
||||||
schema.TypeString,
|
schema.TypeString,
|
||||||
schema.TypeString,
|
schema.TypeString,
|
||||||
|
schema.TypeList,
|
||||||
|
schema.TypeList,
|
||||||
|
})
|
||||||
|
|
||||||
|
sourceFileSchema := testNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentFileSourceFile)
|
||||||
|
|
||||||
|
testRequiredArguments(t, sourceFileSchema, []string{
|
||||||
|
mkResourceVirtualEnvironmentFileSourceFilePath,
|
||||||
|
})
|
||||||
|
|
||||||
|
testOptionalArguments(t, sourceFileSchema, []string{
|
||||||
|
mkResourceVirtualEnvironmentFileSourceFileChanged,
|
||||||
|
mkResourceVirtualEnvironmentFileSourceFileChecksum,
|
||||||
|
mkResourceVirtualEnvironmentFileSourceFileFileName,
|
||||||
|
mkResourceVirtualEnvironmentFileSourceFileInsecure,
|
||||||
|
})
|
||||||
|
|
||||||
|
testSchemaValueTypes(t, sourceFileSchema, []string{
|
||||||
|
mkResourceVirtualEnvironmentFileSourceFileChanged,
|
||||||
|
mkResourceVirtualEnvironmentFileSourceFileChecksum,
|
||||||
|
mkResourceVirtualEnvironmentFileSourceFileFileName,
|
||||||
|
mkResourceVirtualEnvironmentFileSourceFileInsecure,
|
||||||
|
mkResourceVirtualEnvironmentFileSourceFilePath,
|
||||||
|
}, []schema.ValueType{
|
||||||
schema.TypeBool,
|
schema.TypeBool,
|
||||||
schema.TypeString,
|
schema.TypeString,
|
||||||
schema.TypeString,
|
schema.TypeString,
|
||||||
schema.TypeString,
|
|
||||||
schema.TypeBool,
|
schema.TypeBool,
|
||||||
|
schema.TypeString,
|
||||||
|
})
|
||||||
|
|
||||||
|
sourceRawSchema := testNestedSchemaExistence(t, s, mkResourceVirtualEnvironmentFileSourceRaw)
|
||||||
|
|
||||||
|
testRequiredArguments(t, sourceRawSchema, []string{
|
||||||
|
mkResourceVirtualEnvironmentFileSourceRawData,
|
||||||
|
mkResourceVirtualEnvironmentFileSourceRawFileName,
|
||||||
|
})
|
||||||
|
|
||||||
|
testOptionalArguments(t, sourceRawSchema, []string{
|
||||||
|
mkResourceVirtualEnvironmentFileSourceRawResize,
|
||||||
|
})
|
||||||
|
|
||||||
|
testSchemaValueTypes(t, sourceRawSchema, []string{
|
||||||
|
mkResourceVirtualEnvironmentFileSourceRawData,
|
||||||
|
mkResourceVirtualEnvironmentFileSourceRawFileName,
|
||||||
|
mkResourceVirtualEnvironmentFileSourceRawResize,
|
||||||
|
}, []schema.ValueType{
|
||||||
|
schema.TypeString,
|
||||||
|
schema.TypeString,
|
||||||
|
schema.TypeInt,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ const (
|
|||||||
dvResourceVirtualEnvironmentVMCloudInitDNSDomain = ""
|
dvResourceVirtualEnvironmentVMCloudInitDNSDomain = ""
|
||||||
dvResourceVirtualEnvironmentVMCloudInitDNSServer = ""
|
dvResourceVirtualEnvironmentVMCloudInitDNSServer = ""
|
||||||
dvResourceVirtualEnvironmentVMCloudInitUserAccountPassword = ""
|
dvResourceVirtualEnvironmentVMCloudInitUserAccountPassword = ""
|
||||||
|
dvResourceVirtualEnvironmentVMCloudInitUserDataFileID = ""
|
||||||
dvResourceVirtualEnvironmentVMCPUCores = 1
|
dvResourceVirtualEnvironmentVMCPUCores = 1
|
||||||
dvResourceVirtualEnvironmentVMCPUHotplugged = 0
|
dvResourceVirtualEnvironmentVMCPUHotplugged = 0
|
||||||
dvResourceVirtualEnvironmentVMCPUSockets = 1
|
dvResourceVirtualEnvironmentVMCPUSockets = 1
|
||||||
@ -72,6 +73,7 @@ const (
|
|||||||
mkResourceVirtualEnvironmentVMCloudInitUserAccountKeys = "keys"
|
mkResourceVirtualEnvironmentVMCloudInitUserAccountKeys = "keys"
|
||||||
mkResourceVirtualEnvironmentVMCloudInitUserAccountPassword = "password"
|
mkResourceVirtualEnvironmentVMCloudInitUserAccountPassword = "password"
|
||||||
mkResourceVirtualEnvironmentVMCloudInitUserAccountUsername = "username"
|
mkResourceVirtualEnvironmentVMCloudInitUserAccountUsername = "username"
|
||||||
|
mkResourceVirtualEnvironmentVMCloudInitUserDataFileID = "user_data_file_id"
|
||||||
mkResourceVirtualEnvironmentVMCPU = "cpu"
|
mkResourceVirtualEnvironmentVMCPU = "cpu"
|
||||||
mkResourceVirtualEnvironmentVMCPUCores = "cores"
|
mkResourceVirtualEnvironmentVMCPUCores = "cores"
|
||||||
mkResourceVirtualEnvironmentVMCPUHotplugged = "hotplugged"
|
mkResourceVirtualEnvironmentVMCPUHotplugged = "hotplugged"
|
||||||
@ -318,6 +320,14 @@ func resourceVirtualEnvironmentVM() *schema.Resource {
|
|||||||
MaxItems: 1,
|
MaxItems: 1,
|
||||||
MinItems: 0,
|
MinItems: 0,
|
||||||
},
|
},
|
||||||
|
mkResourceVirtualEnvironmentVMCloudInitUserDataFileID: {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Description: "The ID of a file containing custom user data",
|
||||||
|
Default: dvResourceVirtualEnvironmentVMCloudInitUserDataFileID,
|
||||||
|
ValidateFunc: getFileIDValidator(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MaxItems: 1,
|
MaxItems: 1,
|
||||||
@ -770,6 +780,14 @@ func resourceVirtualEnvironmentVMCreate(d *schema.ResourceData, m interface{}) e
|
|||||||
|
|
||||||
cloudInitConfig.Username = &username
|
cloudInitConfig.Username = &username
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cloudInitUserDataFileID := cloudInitBlock[mkResourceVirtualEnvironmentVMCloudInitUserDataFileID].(string)
|
||||||
|
|
||||||
|
if cloudInitUserDataFileID != "" {
|
||||||
|
cloudInitConfig.Files = &proxmox.CustomCloudInitFiles{
|
||||||
|
UserVolume: &cloudInitUserDataFileID,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cpu := d.Get(mkResourceVirtualEnvironmentVMCPU).([]interface{})
|
cpu := d.Get(mkResourceVirtualEnvironmentVMCPU).([]interface{})
|
||||||
@ -1075,7 +1093,9 @@ func resourceVirtualEnvironmentVMCreateImportedDisks(d *schema.ResourceData, m i
|
|||||||
|
|
||||||
speedBlock := speed[0].(map[string]interface{})
|
speedBlock := speed[0].(map[string]interface{})
|
||||||
speedLimitRead := speedBlock[mkResourceVirtualEnvironmentVMDiskSpeedRead].(int)
|
speedLimitRead := speedBlock[mkResourceVirtualEnvironmentVMDiskSpeedRead].(int)
|
||||||
|
speedLimitReadBurstable := speedBlock[mkResourceVirtualEnvironmentVMDiskSpeedReadBurstable].(int)
|
||||||
speedLimitWrite := speedBlock[mkResourceVirtualEnvironmentVMDiskSpeedWrite].(int)
|
speedLimitWrite := speedBlock[mkResourceVirtualEnvironmentVMDiskSpeedWrite].(int)
|
||||||
|
speedLimitWriteBurstable := speedBlock[mkResourceVirtualEnvironmentVMDiskSpeedWriteBurstable].(int)
|
||||||
|
|
||||||
diskOptions := ""
|
diskOptions := ""
|
||||||
|
|
||||||
@ -1083,21 +1103,37 @@ func resourceVirtualEnvironmentVMCreateImportedDisks(d *schema.ResourceData, m i
|
|||||||
diskOptions += fmt.Sprintf(",mbps_rd=%d", speedLimitRead)
|
diskOptions += fmt.Sprintf(",mbps_rd=%d", speedLimitRead)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if speedLimitReadBurstable > 0 {
|
||||||
|
diskOptions += fmt.Sprintf(",mbps_rd_max=%d", speedLimitReadBurstable)
|
||||||
|
}
|
||||||
|
|
||||||
if speedLimitWrite > 0 {
|
if speedLimitWrite > 0 {
|
||||||
diskOptions += fmt.Sprintf(",mbps_wr=%d", speedLimitWrite)
|
diskOptions += fmt.Sprintf(",mbps_wr=%d", speedLimitWrite)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if speedLimitWriteBurstable > 0 {
|
||||||
|
diskOptions += fmt.Sprintf(",mbps_wr_max=%d", speedLimitWriteBurstable)
|
||||||
|
}
|
||||||
|
|
||||||
fileIDParts := strings.Split(fileID, ":")
|
fileIDParts := strings.Split(fileID, ":")
|
||||||
filePath := fmt.Sprintf("/var/lib/vz/template/%s", fileIDParts[1])
|
filePath := ""
|
||||||
|
|
||||||
|
if strings.HasPrefix(fileIDParts[1], "iso/") {
|
||||||
|
filePath = fmt.Sprintf("/template/%s", fileIDParts[1])
|
||||||
|
} else {
|
||||||
|
filePath = fmt.Sprintf("/%s", fileIDParts[1])
|
||||||
|
}
|
||||||
|
|
||||||
filePathTmp := fmt.Sprintf("/tmp/vm-%d-disk-%d.%s", vmID, diskCount+importedDiskCount, fileFormat)
|
filePathTmp := fmt.Sprintf("/tmp/vm-%d-disk-%d.%s", vmID, diskCount+importedDiskCount, fileFormat)
|
||||||
|
|
||||||
commands = append(
|
commands = append(
|
||||||
commands,
|
commands,
|
||||||
fmt.Sprintf("cp %s %s", filePath, filePathTmp),
|
`set -e`,
|
||||||
fmt.Sprintf("qemu-img resize %s %dG", filePathTmp, size),
|
fmt.Sprintf(`cp "$(grep -Pzo ': %s\s+path\s+[^\s]+' /etc/pve/storage.cfg | grep -Pzo '/[^\s]*' | tr -d '\000')%s" %s`, fileIDParts[0], filePath, filePathTmp),
|
||||||
fmt.Sprintf("qm importdisk %d %s %s -format qcow2", vmID, filePathTmp, datastoreID),
|
fmt.Sprintf(`qemu-img resize %s %dG`, filePathTmp, size),
|
||||||
fmt.Sprintf("qm set %d -scsi%d %s:vm-%d-disk-%d%s", vmID, i, datastoreID, vmID, diskCount+importedDiskCount, diskOptions),
|
fmt.Sprintf(`qm importdisk %d %s %s -format qcow2`, vmID, filePathTmp, datastoreID),
|
||||||
fmt.Sprintf("rm -f %s", filePathTmp),
|
fmt.Sprintf(`qm set %d -scsi%d %s:vm-%d-disk-%d%s`, vmID, i, datastoreID, vmID, diskCount+importedDiskCount, diskOptions),
|
||||||
|
fmt.Sprintf(`rm -f %s`, filePathTmp),
|
||||||
)
|
)
|
||||||
|
|
||||||
importedDiskCount++
|
importedDiskCount++
|
||||||
|
@ -13,8 +13,21 @@ import (
|
|||||||
"github.com/hashicorp/terraform/helper/validation"
|
"github.com/hashicorp/terraform/helper/validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func getContentTypeValidator() schema.SchemaValidateFunc {
|
||||||
|
return validation.StringInSlice([]string{
|
||||||
|
"backup",
|
||||||
|
"iso",
|
||||||
|
"snippets",
|
||||||
|
"vztmpl",
|
||||||
|
}, false)
|
||||||
|
}
|
||||||
|
|
||||||
func getFileFormatValidator() schema.SchemaValidateFunc {
|
func getFileFormatValidator() schema.SchemaValidateFunc {
|
||||||
return validation.StringInSlice([]string{"qcow2", "raw", "vmdk"}, false)
|
return validation.StringInSlice([]string{
|
||||||
|
"qcow2",
|
||||||
|
"raw",
|
||||||
|
"vmdk",
|
||||||
|
}, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFileIDValidator() schema.SchemaValidateFunc {
|
func getFileIDValidator() schema.SchemaValidateFunc {
|
||||||
|
Loading…
Reference in New Issue
Block a user