diff --git a/docs/data-sources/vm.md b/docs/data-sources/vm.md
index eaaea0f..9e19718 100644
--- a/docs/data-sources/vm.md
+++ b/docs/data-sources/vm.md
@@ -14,8 +14,20 @@ Virtual Machine Data Source
```terraform
data "parallels-desktop_vm" "example" {
- host = "http://example.com:8080"
+ # You can only use one of the following options
+ # Use the host if you need to connect directly to a host
+ host = "http:#example.com:8080"
+ # Use the orchestrator if you need to connect to a Parallels Orchestrator
+ orchestrator = "https:#orchestrator.example.com:443"
+
+ # The authenticator block for authenticating to the API, either to the host or orchestrator
+ authenticator {
+ username = "john.doe"
+ password = "my-password"
+ }
+
+ # The filter block to filter the VMs
filter {
field_name = "name"
value = "exampe-vm"
@@ -26,14 +38,12 @@ data "parallels-desktop_vm" "example" {
## Schema
-### Required
-
-- `host` (String)
-
### Optional
- `authenticator` (Block, Optional) Authenticator block, this is used to authenticate with the Parallels Desktop API, if empty it will try to use the root password (see [below for nested schema](#nestedblock--authenticator))
- `filter` (Block, Optional) Filter block, this is used to filter data sources (see [below for nested schema](#nestedblock--filter))
+- `host` (String) Parallels Desktop DevOps Host
+- `orchestrator` (String) Parallels Desktop DevOps Orchestrator
### Read-Only
@@ -67,10 +77,13 @@ Optional:
Read-Only:
-- `description` (String)
-- `home` (String)
-- `host_ip` (String)
-- `id` (String)
-- `name` (String)
-- `os_type` (String)
-- `state` (String)
+- `description` (String) The description of the virtual machine
+- `external_ip` (String) VM external IP address
+- `home` (String) The path to the virtual machine home directory
+- `host_ip` (String) The IP address of the host machine
+- `id` (String) The unique identifier of the virtual machine
+- `internal_ip` (String) VM internal IP address
+- `name` (String) The name of the virtual machine
+- `orchestrator_host_id` (String) Orchestrator Host Id if the VM is running in an orchestrator
+- `os_type` (String) The type of the operating system installed on the virtual machine
+- `state` (String) The state of the virtual machine
diff --git a/docs/index.md b/docs/index.md
index 7095140..aeb5b46 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -20,6 +20,9 @@ You can also join our community on [Discord](https://discord.gg/aFsrjbkN) channe
```terraform
provider "parallels-desktop" {
license = "xxxx-xxxx-xxxx-xxxx"
+ # Optional, will disable TLS validation when doing calls to the API using HTTPS
+ # this is useful when the API is using a self-signed certificate
+ disable_tls_validation = true
}
```
diff --git a/docs/resources/clone_vm.md b/docs/resources/clone_vm.md
index 962f573..b6ce282 100644
--- a/docs/resources/clone_vm.md
+++ b/docs/resources/clone_vm.md
@@ -14,7 +14,12 @@ Parallels Desktop Clone VM resource
```terraform
data "parallels-desktop_vm" "example" {
- host = "https://example.com:8080"
+ host = "https:#example.com:8080"
+
+ authenticator {
+ username = "john.doe"
+ password = "my-password"
+ }
filter {
field_name = "name"
@@ -24,16 +29,29 @@ data "parallels-desktop_vm" "example" {
}
resource "parallels-desktop_clone_vm" "example" {
- host = "https://example.com:8080"
+ # You can only use one of the following options
+
+ # Use the host if you need to connect directly to a host
+ host = "http://example.com:8080"
+ # Use the orchestrator if you need to connect to a Parallels Orchestrator
+ orchestrator = "https://orchestrator.example.com:443"
+
name = "example-vm"
owner = "example"
base_vm_id = data.parallels-desktop_vm.example.machines[count.index].id
path = "/some/folder/path"
+ # The authenticator block for authenticating to the API, either to the host or orchestrator
+ # in this case we are using the API key
authenticator {
api_key = "some api key"
}
+ # The configuration for the VM
+ config {
+ start_headless = true
+ }
+
# this will allow you to fine grain the configuration of the VM
# you can pass any command that is compatible with the prlctl command
# directly to the VM
@@ -56,6 +74,24 @@ resource "parallels-desktop_clone_vm" "example" {
force_changes = true
+ # this flag will set the desired state for the VM
+ # if it is set to true it will keep the VM running otherwise it will stop it
+ # by default it is set to true, so all VMs will be running
+ keep_running = true
+
+ # This will contain the configuration for the port forwarding reverse proxy
+ # in this case we are opening a port to any part in the host, it will not be linked to any
+ # specific vm or container. by default it will listen on 0.0.0.0 (all interfaces)
+ # and the target host will also be 0.0.0.0 (all interfaces) so it will be open to the world
+ # use
+ reverse_proxy_host {
+ port = "2022"
+
+ tcp_route {
+ target_port = "22"
+ }
+ }
+
# This will contain the configuration for the shared folders
shared_folder {
name = "user_download_folder"
@@ -66,7 +102,7 @@ resource "parallels-desktop_clone_vm" "example" {
# allowing you to run any command on the VM after it has been deployed
# you can have multiple lines and they will be executed in order
post_processor_script {
- // Retry the script 4 times with 10 seconds between each attempt
+ # Retry the script 4 times with 10 seconds between each attempt
retry {
attempts = 4
wait_between_attempts = "10s"
@@ -80,7 +116,7 @@ resource "parallels-desktop_clone_vm" "example" {
# This is a special block that will allow you to undo any changes your scripts have done
# if you are destroying a VM, like unregistering from a service where the VM was registered
on_destroy_script {
- // Retry the script 4 times with 10 seconds between each attempt
+ # Retry the script 4 times with 10 seconds between each attempt
retry {
attempts = 4
wait_between_attempts = "10s"
@@ -113,14 +149,18 @@ resource "parallels-desktop_clone_vm" "example" {
- `owner` (String) Virtual Machine owner
- `post_processor_script` (Block List) Run any script after the virtual machine is created (see [below for nested schema](#nestedblock--post_processor_script))
- `prlctl` (Block List) Virtual Machine config block, this is used set some of the most common settings for a VM (see [below for nested schema](#nestedblock--prlctl))
-- `run_after_create` (Boolean) Run after create, this will make the VM to run after creation
+- `reverse_proxy_host` (Block List) Parallels Desktop DevOps Reverse Proxy configuration (see [below for nested schema](#nestedblock--reverse_proxy_host))
+- `run_after_create` (Boolean, Deprecated) Run after create, this will make the VM to run after creation
- `shared_folder` (Block List) Shared Folders Block, this is used to share folders with the virtual machine (see [below for nested schema](#nestedblock--shared_folder))
- `specs` (Block, Optional) Virtual Machine Specs block, this is used to set the specs of the virtual machine (see [below for nested schema](#nestedblock--specs))
- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts))
### Read-Only
+- `external_ip` (String) VM external IP address
- `id` (String) Virtual Machine Id
+- `internal_ip` (String) VM internal IP address
+- `keep_running` (Boolean) This will keep the VM running after the terraform apply
- `os_type` (String) Virtual Machine OS type
@@ -231,6 +271,72 @@ Optional:
+
+### Nested Schema for `reverse_proxy_host`
+
+Required:
+
+- `port` (String) Reverse proxy port
+
+Optional:
+
+- `cors` (Block, Optional) Parallels Desktop DevOps Reverse Proxy Http Route CORS configuration (see [below for nested schema](#nestedblock--reverse_proxy_host--cors))
+- `host` (String) Reverse proxy host
+- `http_routes` (Block List) Parallels Desktop DevOps Reverse Proxy Http Route CORS configuration (see [below for nested schema](#nestedblock--reverse_proxy_host--http_routes))
+- `tcp_route` (Block, Optional) Parallels Desktop DevOps Reverse Proxy TCP Route configuration (see [below for nested schema](#nestedblock--reverse_proxy_host--tcp_route))
+- `tls` (Block, Optional) Parallels Desktop DevOps Reverse Proxy Http Route TLS configuration (see [below for nested schema](#nestedblock--reverse_proxy_host--tls))
+
+Read-Only:
+
+- `id` (String) Reverse proxy Host id
+
+
+### Nested Schema for `reverse_proxy_host.cors`
+
+Optional:
+
+- `allowed_headers` (List of String) Allowed headers
+- `allowed_methods` (List of String) Allowed methods
+- `allowed_origins` (List of String) Allowed origins
+- `enabled` (Boolean) Enable CORS
+
+
+
+### Nested Schema for `reverse_proxy_host.http_routes`
+
+Optional:
+
+- `path` (String) Reverse proxy HTTP Route path
+- `pattern` (String) Reverse proxy HTTP Route pattern
+- `request_headers` (Map of String) Reverse proxy HTTP Route request headers
+- `response_headers` (Map of String) Reverse proxy HTTP Route response headers
+- `schema` (String) Reverse proxy HTTP Route schema
+- `target_host` (String) Reverse proxy HTTP Route target host
+- `target_port` (String) Reverse proxy HTTP Route target port
+- `target_vm_id` (String) Reverse proxy HTTP Route target VM id
+
+
+
+### Nested Schema for `reverse_proxy_host.tcp_route`
+
+Optional:
+
+- `target_host` (String) Reverse proxy host
+- `target_port` (String) Reverse proxy port
+- `target_vm_id` (String) Reverse proxy target VM ID
+
+
+
+### Nested Schema for `reverse_proxy_host.tls`
+
+Optional:
+
+- `certificate` (String) TLS Certificate
+- `enabled` (Boolean) Enable TLS
+- `private_key` (String) TLS Private Key
+
+
+
### Nested Schema for `shared_folder`
diff --git a/docs/resources/deploy.md b/docs/resources/deploy.md
index cac7e01..d0fe16b 100644
--- a/docs/resources/deploy.md
+++ b/docs/resources/deploy.md
@@ -17,25 +17,59 @@ resource "parallels-desktop_deploy" "example" {
# This will contain the configuration for the Parallels Desktop API
api_config {
- port = "8080"
- prefix = "/api"
- log_level = "info"
- mode = "api"
- devops_version = "latest"
- root_password = "VerySecretPassword"
- hmac_secret = "VerySecretLongStringForHMAC"
- encryption_rsa_key = "base64 encoded rsa key"
- enable_tls = true
- tls_port = "8443"
- tls_certificate = "base64 encoded tls cert"
- tls_private_key = "base64 encoded tls key"
- disable_catalog_caching = false
+ port = "8080"
+ prefix = "/api"
+ # This will set the log level for the API
+ log_level = "info"
+ # This will enable logging for the API
+ enable_logging = true
+ # This will set the mode for the API, you can use either api or orchestrator. by default it will be api
+ mode = "api"
+ # you can force any version of the devops api, if you leave it empty it will use the latest version
+ # but it will not automatically update to the latest version, that would need a manual step
+ devops_version = "latest"
+ # This will set the password for the default root user
+ root_password = "VerySecretPassword"
+ # This will enable the api to use the hmac secret for the authentication
+ hmac_secret = "VerySecretLongStringForHMAC"
+ # This will enable the api to use the rsa key for encryption of the database file
+ # we strongly advise you to use this feature for security reasons
+ encryption_rsa_key = "base64 encoded rsa key"
+ # This will enable the api to use the tls certificate
+ enable_tls = true
+ # This will enable the tls port
+ tls_port = "8443"
+ # This will enable the tls certificate
+ tls_certificate = "base64 encoded tls cert"
+ # This will enable the tls private key
+ tls_private_key = "base64 encoded tls key"
+ # This will enable the catalog caching, this will cache the catalog in the host
+ disable_catalog_caching = false
+ # This will enable the orchestrator resources, this will enable the host to use the orchestrator
use_orchestrator_resources = false
+ # This will enable the port forwarding reverse proxy in the host, you will need to set the
+ # port_forwarding block to configure the ports in the deploy or any other provider
+ enable_port_forwarding = false
+ # This will allow more fine tune of the api configuration, you can pass any compatible environment
+ # variable
environment_variables = {
"key" = "value"
}
}
+ # This will contain the configuration for the port forwarding reverse proxy
+ # in this case we are opening a port to any part in the host, it will not be linked to any
+ # specific vm or container. by default it will listen on 0.0.0.0 (all interfaces)
+ # and the target host will also be 0.0.0.0 (all interfaces) so it will be open to the world
+ # use
+ reverse_proxy_host {
+ port = "2022"
+
+ tcp_route {
+ target_port = "22"
+ }
+ }
+
# This will contain the configuration for the Parallels Desktop Orchestrator
# and how to register this instance with it
orchestrator_registration {
@@ -78,6 +112,7 @@ resource "parallels-desktop_deploy" "example" {
- `api_config` (Block, Optional) Parallels Desktop DevOps configuration (see [below for nested schema](#nestedblock--api_config))
- `install_local` (Boolean) Deploy Parallels Desktop in the local machine, this will ignore the need to connect to a remote machine
- `orchestrator_registration` (Block, Optional) Orchestrator connection details (see [below for nested schema](#nestedblock--orchestrator_registration))
+- `reverse_proxy_host` (Block List) Parallels Desktop DevOps Reverse Proxy configuration (see [below for nested schema](#nestedblock--reverse_proxy_host))
- `ssh_connection` (Block, Optional) Host connection details (see [below for nested schema](#nestedblock--ssh_connection))
### Read-Only
@@ -87,8 +122,12 @@ resource "parallels-desktop_deploy" "example" {
- `current_packer_version` (String) Current version of Hashicorp Packer
- `current_vagrant_version` (String) Current version of Hashicorp Vagrant
- `current_version` (String) Current version of Parallels Desktop
+- `external_ip` (String) External IP address
- `installed_dependencies` (List of String) List of installed dependencies
+- `is_registered_in_orchestrator` (Boolean) Is this host registered in the orchestrator
- `license` (Object) Parallels Desktop license (see [below for nested schema](#nestedatt--license))
+- `orchestrator_host` (String) Orchestrator host ID
+- `orchestrator_host_id` (String) Orchestrator host ID
### Nested Schema for `api_config`
@@ -98,6 +137,7 @@ Optional:
- `devops_version` (String) Parallels Desktop DevOps version to install, if empty the latest will be installed
- `disable_catalog_caching` (Boolean) Disable catalog caching, this will disable the ability to cache catalog items that are pulled from a remote catalog
- `enable_logging` (Boolean) Enable logging
+- `enable_port_forwarding` (Boolean) Enable inbuilt reverse proxy for port forwarding
- `enable_tls` (Boolean) Parallels Desktop DevOps enable TLS
- `encryption_rsa_key` (String, Sensitive) Parallels Desktop DevOps RSA key, this is used to encrypt database file on rest
- `environment_variables` (Map of String) Environment variables that can be used in the DevOps service, please see documentation to see which variables are available
@@ -114,6 +154,7 @@ Optional:
- `tls_port` (String) Parallels Desktop DevOps TLS port
- `tls_private_key` (String, Sensitive) Parallels Desktop DevOps TLS private key, this should be a PEM base64 encoded private key string
- `token_duration_minutes` (String) JWT Token duration in minutes
+- `use_latest_beta` (Boolean) Enables the use of the latest beta
- `use_orchestrator_resources` (Boolean) Use orchestrator resources
@@ -166,6 +207,72 @@ Optional:
+
+### Nested Schema for `reverse_proxy_host`
+
+Required:
+
+- `port` (String) Reverse proxy port
+
+Optional:
+
+- `cors` (Block, Optional) Parallels Desktop DevOps Reverse Proxy Http Route CORS configuration (see [below for nested schema](#nestedblock--reverse_proxy_host--cors))
+- `host` (String) Reverse proxy host
+- `http_routes` (Block List) Parallels Desktop DevOps Reverse Proxy Http Route CORS configuration (see [below for nested schema](#nestedblock--reverse_proxy_host--http_routes))
+- `tcp_route` (Block, Optional) Parallels Desktop DevOps Reverse Proxy TCP Route configuration (see [below for nested schema](#nestedblock--reverse_proxy_host--tcp_route))
+- `tls` (Block, Optional) Parallels Desktop DevOps Reverse Proxy Http Route TLS configuration (see [below for nested schema](#nestedblock--reverse_proxy_host--tls))
+
+Read-Only:
+
+- `id` (String) Reverse proxy Host id
+
+
+### Nested Schema for `reverse_proxy_host.cors`
+
+Optional:
+
+- `allowed_headers` (List of String) Allowed headers
+- `allowed_methods` (List of String) Allowed methods
+- `allowed_origins` (List of String) Allowed origins
+- `enabled` (Boolean) Enable CORS
+
+
+
+### Nested Schema for `reverse_proxy_host.http_routes`
+
+Optional:
+
+- `path` (String) Reverse proxy HTTP Route path
+- `pattern` (String) Reverse proxy HTTP Route pattern
+- `request_headers` (Map of String) Reverse proxy HTTP Route request headers
+- `response_headers` (Map of String) Reverse proxy HTTP Route response headers
+- `schema` (String) Reverse proxy HTTP Route schema
+- `target_host` (String) Reverse proxy HTTP Route target host
+- `target_port` (String) Reverse proxy HTTP Route target port
+- `target_vm_id` (String) Reverse proxy HTTP Route target VM id
+
+
+
+### Nested Schema for `reverse_proxy_host.tcp_route`
+
+Optional:
+
+- `target_host` (String) Reverse proxy host
+- `target_port` (String) Reverse proxy port
+- `target_vm_id` (String) Reverse proxy target VM ID
+
+
+
+### Nested Schema for `reverse_proxy_host.tls`
+
+Optional:
+
+- `certificate` (String) TLS Certificate
+- `enabled` (Boolean) Enable TLS
+- `private_key` (String) TLS Private Key
+
+
+
### Nested Schema for `ssh_connection`
diff --git a/docs/resources/remote_vm.md b/docs/resources/remote_vm.md
index eaa124e..3a7ce66 100644
--- a/docs/resources/remote_vm.md
+++ b/docs/resources/remote_vm.md
@@ -14,13 +14,25 @@ Parallels Virtual Machine State Resource
```terraform
resource "parallels-desktop_remote_vm" "example_box" {
- host = "https://example.com:8080"
- name = "example-vm"
- owner = "example"
- catalog_id = "example-catalog-id"
- version = "v1"
- host_connection = "host=user:VerySecretPassword@example.com"
- path = "/Users/example/Parallels"
+ # You can only use one of the following options
+
+ # Use the host if you need to connect directly to a host
+ host = "http://example.com:8080"
+ # Use the orchestrator if you need to connect to a Parallels Orchestrator
+ orchestrator = "https://orchestrator.example.com:443"
+
+ # The name of the VM
+ name = "example-vm"
+ # The owner of the VM, otherwise it will be set as root
+ owner = "example"
+ # The catalog id of the VM from the catalog provider
+ catalog_id = "example-catalog-id"
+ # The version of the VM from the catalog provider
+ version = "v1"
+ # The connection to the catalog provider
+ catalog_connection = "host=user:VerySecretPassword@example.com"
+ # The path where the VM will be stored
+ path = "/Users/example/Parallels"
# This will tell how should we authenticate with the host API
# you can either use it or leave it empty, if left empty then
@@ -44,6 +56,24 @@ resource "parallels-desktop_remote_vm" "example_box" {
memory_size = "2048"
}
+ # this flag will set the desired state for the VM
+ # if it is set to true it will keep the VM running otherwise it will stop it
+ # by default it is set to true, so all VMs will be running
+ keep_running = true
+
+ # This will contain the configuration for the port forwarding reverse proxy
+ # in this case we are opening a port to any part in the host, it will not be linked to any
+ # specific vm or container. by default it will listen on 0.0.0.0 (all interfaces)
+ # and the target host will also be 0.0.0.0 (all interfaces) so it will be open to the world
+ # use
+ reverse_proxy_host {
+ port = "2022"
+
+ tcp_route {
+ target_port = "22"
+ }
+ }
+
# this will allow you to fine grain the configuration of the VM
# you can pass any command that is compatible with the prlctl command
# directly to the VM
@@ -120,12 +150,14 @@ resource "parallels-desktop_remote_vm" "example_box" {
- `config` (Block, Optional) Virtual Machine config block, this is used set some of the most common settings for a VM (see [below for nested schema](#nestedblock--config))
- `force_changes` (Boolean) Force changes, this will force the VM to be stopped and started again
- `host` (String) Parallels Desktop DevOps Host
+- `keep_running` (Boolean) This will keep the VM running after the terraform apply
- `on_destroy_script` (Block List) Run any script after the virtual machine is created (see [below for nested schema](#nestedblock--on_destroy_script))
- `orchestrator` (String) Parallels Desktop DevOps Orchestrator
- `owner` (String) Virtual Machine owner
- `post_processor_script` (Block List) Run any script after the virtual machine is created (see [below for nested schema](#nestedblock--post_processor_script))
- `prlctl` (Block List) Virtual Machine config block, this is used set some of the most common settings for a VM (see [below for nested schema](#nestedblock--prlctl))
-- `run_after_create` (Boolean) Run after create, this will make the VM to run after creation
+- `reverse_proxy_host` (Block List) Parallels Desktop DevOps Reverse Proxy configuration (see [below for nested schema](#nestedblock--reverse_proxy_host))
+- `run_after_create` (Boolean, Deprecated) Run after create, this will make the VM to run after creation
- `shared_folder` (Block List) Shared Folders Block, this is used to share folders with the virtual machine (see [below for nested schema](#nestedblock--shared_folder))
- `specs` (Block, Optional) Virtual Machine Specs block, this is used to set the specs of the virtual machine (see [below for nested schema](#nestedblock--specs))
- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts))
@@ -133,7 +165,10 @@ resource "parallels-desktop_remote_vm" "example_box" {
### Read-Only
+- `external_ip` (String) VM external IP address
- `id` (String) Virtual Machine Id
+- `internal_ip` (String) VM internal IP address
+- `orchestrator_host_id` (String) Orchestrator Host Id if the VM is running in an orchestrator
- `os_type` (String) Virtual Machine OS type
@@ -244,6 +279,72 @@ Optional:
+
+### Nested Schema for `reverse_proxy_host`
+
+Required:
+
+- `port` (String) Reverse proxy port
+
+Optional:
+
+- `cors` (Block, Optional) Parallels Desktop DevOps Reverse Proxy Http Route CORS configuration (see [below for nested schema](#nestedblock--reverse_proxy_host--cors))
+- `host` (String) Reverse proxy host
+- `http_routes` (Block List) Parallels Desktop DevOps Reverse Proxy Http Route CORS configuration (see [below for nested schema](#nestedblock--reverse_proxy_host--http_routes))
+- `tcp_route` (Block, Optional) Parallels Desktop DevOps Reverse Proxy TCP Route configuration (see [below for nested schema](#nestedblock--reverse_proxy_host--tcp_route))
+- `tls` (Block, Optional) Parallels Desktop DevOps Reverse Proxy Http Route TLS configuration (see [below for nested schema](#nestedblock--reverse_proxy_host--tls))
+
+Read-Only:
+
+- `id` (String) Reverse proxy Host id
+
+
+### Nested Schema for `reverse_proxy_host.cors`
+
+Optional:
+
+- `allowed_headers` (List of String) Allowed headers
+- `allowed_methods` (List of String) Allowed methods
+- `allowed_origins` (List of String) Allowed origins
+- `enabled` (Boolean) Enable CORS
+
+
+
+### Nested Schema for `reverse_proxy_host.http_routes`
+
+Optional:
+
+- `path` (String) Reverse proxy HTTP Route path
+- `pattern` (String) Reverse proxy HTTP Route pattern
+- `request_headers` (Map of String) Reverse proxy HTTP Route request headers
+- `response_headers` (Map of String) Reverse proxy HTTP Route response headers
+- `schema` (String) Reverse proxy HTTP Route schema
+- `target_host` (String) Reverse proxy HTTP Route target host
+- `target_port` (String) Reverse proxy HTTP Route target port
+- `target_vm_id` (String) Reverse proxy HTTP Route target VM id
+
+
+
+### Nested Schema for `reverse_proxy_host.tcp_route`
+
+Optional:
+
+- `target_host` (String) Reverse proxy host
+- `target_port` (String) Reverse proxy port
+- `target_vm_id` (String) Reverse proxy target VM ID
+
+
+
+### Nested Schema for `reverse_proxy_host.tls`
+
+Optional:
+
+- `certificate` (String) TLS Certificate
+- `enabled` (Boolean) Enable TLS
+- `private_key` (String) TLS Private Key
+
+
+
### Nested Schema for `shared_folder`
diff --git a/docs/resources/vagrant_box.md b/docs/resources/vagrant_box.md
index 65c39be..685c9e0 100644
--- a/docs/resources/vagrant_box.md
+++ b/docs/resources/vagrant_box.md
@@ -49,6 +49,24 @@ resource "parallels-desktop_remote_vm" "example_vagrant_file" {
memory_size = "2048"
}
+ # this flag will set the desired state for the VM
+ # if it is set to true it will keep the VM running otherwise it will stop it
+ # by default it is set to true, so all VMs will be running
+ keep_running = true
+
+ # This will contain the configuration for the port forwarding reverse proxy
+ # in this case we are opening a port to any part in the host, it will not be linked to any
+ # specific vm or container. by default it will listen on 0.0.0.0 (all interfaces)
+ # and the target host will also be 0.0.0.0 (all interfaces) so it will be open to the world
+ # use
+ reverse_proxy_host {
+ port = "2022"
+
+ tcp_route {
+ target_port = "22"
+ }
+ }
+
# this will allow you to fine grain the configuration of the VM
# you can pass any command that is compatible with the prlctl command
# directly to the VM
@@ -126,12 +144,14 @@ resource "parallels-desktop_remote_vm" "example_vagrant_file" {
- `custom_vagrant_config` (String) Custom Vagrant config
- `force_changes` (Boolean) Force changes, this will force the VM to be stopped and started again
- `host` (String) Parallels Desktop DevOps Host
+- `keep_running` (Boolean) This will keep the VM running after the terraform apply
- `on_destroy_script` (Block List) Run any script after the virtual machine is created (see [below for nested schema](#nestedblock--on_destroy_script))
- `orchestrator` (String) Parallels Desktop DevOps Orchestrator
- `owner` (String) Virtual Machine owner
- `post_processor_script` (Block List) Run any script after the virtual machine is created (see [below for nested schema](#nestedblock--post_processor_script))
- `prlctl` (Block List) Virtual Machine config block, this is used set some of the most common settings for a VM (see [below for nested schema](#nestedblock--prlctl))
-- `run_after_create` (Boolean) Run after create
+- `reverse_proxy_host` (Block List) Parallels Desktop DevOps Reverse Proxy configuration (see [below for nested schema](#nestedblock--reverse_proxy_host))
+- `run_after_create` (Boolean, Deprecated) Run after create
- `shared_folder` (Block List) Shared Folders Block, this is used to share folders with the virtual machine (see [below for nested schema](#nestedblock--shared_folder))
- `specs` (Block, Optional) Virtual Machine Specs block, this is used to set the specs of the virtual machine (see [below for nested schema](#nestedblock--specs))
- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts))
@@ -139,7 +159,9 @@ resource "parallels-desktop_remote_vm" "example_vagrant_file" {
### Read-Only
+- `external_ip` (String) VM external IP address
- `id` (String) Virtual Machine Id
+- `internal_ip` (String) VM internal IP address
- `os_type` (String) Virtual Machine OS type
@@ -250,6 +272,72 @@ Optional:
+
+### Nested Schema for `reverse_proxy_host`
+
+Required:
+
+- `port` (String) Reverse proxy port
+
+Optional:
+
+- `cors` (Block, Optional) Parallels Desktop DevOps Reverse Proxy Http Route CORS configuration (see [below for nested schema](#nestedblock--reverse_proxy_host--cors))
+- `host` (String) Reverse proxy host
+- `http_routes` (Block List) Parallels Desktop DevOps Reverse Proxy Http Route CORS configuration (see [below for nested schema](#nestedblock--reverse_proxy_host--http_routes))
+- `tcp_route` (Block, Optional) Parallels Desktop DevOps Reverse Proxy TCP Route configuration (see [below for nested schema](#nestedblock--reverse_proxy_host--tcp_route))
+- `tls` (Block, Optional) Parallels Desktop DevOps Reverse Proxy Http Route TLS configuration (see [below for nested schema](#nestedblock--reverse_proxy_host--tls))
+
+Read-Only:
+
+- `id` (String) Reverse proxy Host id
+
+
+### Nested Schema for `reverse_proxy_host.cors`
+
+Optional:
+
+- `allowed_headers` (List of String) Allowed headers
+- `allowed_methods` (List of String) Allowed methods
+- `allowed_origins` (List of String) Allowed origins
+- `enabled` (Boolean) Enable CORS
+
+
+
+### Nested Schema for `reverse_proxy_host.http_routes`
+
+Optional:
+
+- `path` (String) Reverse proxy HTTP Route path
+- `pattern` (String) Reverse proxy HTTP Route pattern
+- `request_headers` (Map of String) Reverse proxy HTTP Route request headers
+- `response_headers` (Map of String) Reverse proxy HTTP Route response headers
+- `schema` (String) Reverse proxy HTTP Route schema
+- `target_host` (String) Reverse proxy HTTP Route target host
+- `target_port` (String) Reverse proxy HTTP Route target port
+- `target_vm_id` (String) Reverse proxy HTTP Route target VM id
+
+
+
+### Nested Schema for `reverse_proxy_host.tcp_route`
+
+Optional:
+
+- `target_host` (String) Reverse proxy host
+- `target_port` (String) Reverse proxy port
+- `target_vm_id` (String) Reverse proxy target VM ID
+
+
+
+### Nested Schema for `reverse_proxy_host.tls`
+
+Optional:
+
+- `certificate` (String) TLS Certificate
+- `enabled` (Boolean) Enable TLS
+- `private_key` (String) TLS Private Key
+
+
+
### Nested Schema for `shared_folder`
diff --git a/examples/data-sources/parallels-desktop_vm/data-source.tf b/examples/data-sources/parallels-desktop_vm/data-source.tf
index 5ecb6db..8789b52 100644
--- a/examples/data-sources/parallels-desktop_vm/data-source.tf
+++ b/examples/data-sources/parallels-desktop_vm/data-source.tf
@@ -1,8 +1,20 @@
data "parallels-desktop_vm" "example" {
- host = "http://example.com:8080"
+ # You can only use one of the following options
+ # Use the host if you need to connect directly to a host
+ host = "http:#example.com:8080"
+ # Use the orchestrator if you need to connect to a Parallels Orchestrator
+ orchestrator = "https:#orchestrator.example.com:443"
+
+ # The authenticator block for authenticating to the API, either to the host or orchestrator
+ authenticator {
+ username = "john.doe"
+ password = "my-password"
+ }
+
+ # The filter block to filter the VMs
filter {
field_name = "name"
value = "exampe-vm"
}
-}
\ No newline at end of file
+}
diff --git a/examples/provider/provider.tf b/examples/provider/provider.tf
index 2c69192..bad96d0 100644
--- a/examples/provider/provider.tf
+++ b/examples/provider/provider.tf
@@ -1,3 +1,6 @@
provider "parallels-desktop" {
license = "xxxx-xxxx-xxxx-xxxx"
+ # Optional, will disable TLS validation when doing calls to the API using HTTPS
+ # this is useful when the API is using a self-signed certificate
+ disable_tls_validation = true
}
diff --git a/examples/resources/parallels-desktop_clone_vm/resource.tf b/examples/resources/parallels-desktop_clone_vm/resource.tf
index 192c956..512a137 100644
--- a/examples/resources/parallels-desktop_clone_vm/resource.tf
+++ b/examples/resources/parallels-desktop_clone_vm/resource.tf
@@ -1,5 +1,10 @@
data "parallels-desktop_vm" "example" {
- host = "https://example.com:8080"
+ host = "https:#example.com:8080"
+
+ authenticator {
+ username = "john.doe"
+ password = "my-password"
+ }
filter {
field_name = "name"
@@ -9,16 +14,29 @@ data "parallels-desktop_vm" "example" {
}
resource "parallels-desktop_clone_vm" "example" {
- host = "https://example.com:8080"
+ # You can only use one of the following options
+
+ # Use the host if you need to connect directly to a host
+ host = "http://example.com:8080"
+ # Use the orchestrator if you need to connect to a Parallels Orchestrator
+ orchestrator = "https://orchestrator.example.com:443"
+
name = "example-vm"
owner = "example"
base_vm_id = data.parallels-desktop_vm.example.machines[count.index].id
path = "/some/folder/path"
+ # The authenticator block for authenticating to the API, either to the host or orchestrator
+ # in this case we are using the API key
authenticator {
api_key = "some api key"
}
+ # The configuration for the VM
+ config {
+ start_headless = true
+ }
+
# this will allow you to fine grain the configuration of the VM
# you can pass any command that is compatible with the prlctl command
# directly to the VM
@@ -41,6 +59,24 @@ resource "parallels-desktop_clone_vm" "example" {
force_changes = true
+ # this flag will set the desired state for the VM
+ # if it is set to true it will keep the VM running otherwise it will stop it
+ # by default it is set to true, so all VMs will be running
+ keep_running = true
+
+ # This will contain the configuration for the port forwarding reverse proxy
+ # in this case we are opening a port to any part in the host, it will not be linked to any
+ # specific vm or container. by default it will listen on 0.0.0.0 (all interfaces)
+ # and the target host will also be 0.0.0.0 (all interfaces) so it will be open to the world
+ # use
+ reverse_proxy_host {
+ port = "2022"
+
+ tcp_route {
+ target_port = "22"
+ }
+ }
+
# This will contain the configuration for the shared folders
shared_folder {
name = "user_download_folder"
@@ -51,7 +87,7 @@ resource "parallels-desktop_clone_vm" "example" {
# allowing you to run any command on the VM after it has been deployed
# you can have multiple lines and they will be executed in order
post_processor_script {
- // Retry the script 4 times with 10 seconds between each attempt
+ # Retry the script 4 times with 10 seconds between each attempt
retry {
attempts = 4
wait_between_attempts = "10s"
@@ -65,7 +101,7 @@ resource "parallels-desktop_clone_vm" "example" {
# This is a special block that will allow you to undo any changes your scripts have done
# if you are destroying a VM, like unregistering from a service where the VM was registered
on_destroy_script {
- // Retry the script 4 times with 10 seconds between each attempt
+ # Retry the script 4 times with 10 seconds between each attempt
retry {
attempts = 4
wait_between_attempts = "10s"
@@ -75,4 +111,4 @@ resource "parallels-desktop_clone_vm" "example" {
"rm -rf /tmp/*"
]
}
-}
\ No newline at end of file
+}
diff --git a/examples/resources/parallels-desktop_deploy/resource.tf b/examples/resources/parallels-desktop_deploy/resource.tf
index 16f5169..545e90a 100644
--- a/examples/resources/parallels-desktop_deploy/resource.tf
+++ b/examples/resources/parallels-desktop_deploy/resource.tf
@@ -2,25 +2,59 @@ resource "parallels-desktop_deploy" "example" {
# This will contain the configuration for the Parallels Desktop API
api_config {
- port = "8080"
- prefix = "/api"
- log_level = "info"
- mode = "api"
- devops_version = "latest"
- root_password = "VerySecretPassword"
- hmac_secret = "VerySecretLongStringForHMAC"
- encryption_rsa_key = "base64 encoded rsa key"
- enable_tls = true
- tls_port = "8443"
- tls_certificate = "base64 encoded tls cert"
- tls_private_key = "base64 encoded tls key"
- disable_catalog_caching = false
+ port = "8080"
+ prefix = "/api"
+ # This will set the log level for the API
+ log_level = "info"
+ # This will enable logging for the API
+ enable_logging = true
+ # This will set the mode for the API, you can use either api or orchestrator. by default it will be api
+ mode = "api"
+ # you can force any version of the devops api, if you leave it empty it will use the latest version
+ # but it will not automatically update to the latest version, that would need a manual step
+ devops_version = "latest"
+ # This will set the password for the default root user
+ root_password = "VerySecretPassword"
+ # This will enable the api to use the hmac secret for the authentication
+ hmac_secret = "VerySecretLongStringForHMAC"
+ # This will enable the api to use the rsa key for encryption of the database file
+ # we strongly advise you to use this feature for security reasons
+ encryption_rsa_key = "base64 encoded rsa key"
+ # This will enable the api to use the tls certificate
+ enable_tls = true
+ # This will enable the tls port
+ tls_port = "8443"
+ # This will enable the tls certificate
+ tls_certificate = "base64 encoded tls cert"
+ # This will enable the tls private key
+ tls_private_key = "base64 encoded tls key"
+ # This will enable the catalog caching, this will cache the catalog in the host
+ disable_catalog_caching = false
+ # This will enable the orchestrator resources, this will enable the host to use the orchestrator
use_orchestrator_resources = false
+ # This will enable the port forwarding reverse proxy in the host, you will need to set the
+ # port_forwarding block to configure the ports in the deploy or any other provider
+ enable_port_forwarding = false
+ # This will allow more fine tune of the api configuration, you can pass any compatible environment
+ # variable
environment_variables = {
"key" = "value"
}
}
+ # This will contain the configuration for the port forwarding reverse proxy
+ # in this case we are opening a port to any part in the host, it will not be linked to any
+ # specific vm or container. by default it will listen on 0.0.0.0 (all interfaces)
+ # and the target host will also be 0.0.0.0 (all interfaces) so it will be open to the world
+ # use
+ reverse_proxy_host {
+ port = "2022"
+
+ tcp_route {
+ target_port = "22"
+ }
+ }
+
# This will contain the configuration for the Parallels Desktop Orchestrator
# and how to register this instance with it
orchestrator_registration {
diff --git a/examples/resources/parallels-desktop_remote_vm/resource.tf b/examples/resources/parallels-desktop_remote_vm/resource.tf
index ed41576..0ec0bc9 100644
--- a/examples/resources/parallels-desktop_remote_vm/resource.tf
+++ b/examples/resources/parallels-desktop_remote_vm/resource.tf
@@ -1,11 +1,23 @@
resource "parallels-desktop_remote_vm" "example_box" {
- host = "https://example.com:8080"
- name = "example-vm"
- owner = "example"
- catalog_id = "example-catalog-id"
- version = "v1"
- host_connection = "host=user:VerySecretPassword@example.com"
- path = "/Users/example/Parallels"
+ # You can only use one of the following options
+
+ # Use the host if you need to connect directly to a host
+ host = "http://example.com:8080"
+ # Use the orchestrator if you need to connect to a Parallels Orchestrator
+ orchestrator = "https://orchestrator.example.com:443"
+
+ # The name of the VM
+ name = "example-vm"
+ # The owner of the VM, otherwise it will be set as root
+ owner = "example"
+ # The catalog id of the VM from the catalog provider
+ catalog_id = "example-catalog-id"
+ # The version of the VM from the catalog provider
+ version = "v1"
+ # The connection to the catalog provider
+ catalog_connection = "host=user:VerySecretPassword@example.com"
+ # The path where the VM will be stored
+ path = "/Users/example/Parallels"
# This will tell how should we authenticate with the host API
# you can either use it or leave it empty, if left empty then
@@ -29,6 +41,24 @@ resource "parallels-desktop_remote_vm" "example_box" {
memory_size = "2048"
}
+ # this flag will set the desired state for the VM
+ # if it is set to true it will keep the VM running otherwise it will stop it
+ # by default it is set to true, so all VMs will be running
+ keep_running = true
+
+ # This will contain the configuration for the port forwarding reverse proxy
+ # in this case we are opening a port to any part in the host, it will not be linked to any
+ # specific vm or container. by default it will listen on 0.0.0.0 (all interfaces)
+ # and the target host will also be 0.0.0.0 (all interfaces) so it will be open to the world
+ # use
+ reverse_proxy_host {
+ port = "2022"
+
+ tcp_route {
+ target_port = "22"
+ }
+ }
+
# this will allow you to fine grain the configuration of the VM
# you can pass any command that is compatible with the prlctl command
# directly to the VM
@@ -85,4 +115,4 @@ resource "parallels-desktop_remote_vm" "example_box" {
"rm -rf /tmp/*"
]
}
-}
\ No newline at end of file
+}
diff --git a/examples/resources/parallels-desktop_vagrant_box/resource.tf b/examples/resources/parallels-desktop_vagrant_box/resource.tf
index 45c94a5..9275e7c 100644
--- a/examples/resources/parallels-desktop_vagrant_box/resource.tf
+++ b/examples/resources/parallels-desktop_vagrant_box/resource.tf
@@ -34,6 +34,24 @@ resource "parallels-desktop_remote_vm" "example_vagrant_file" {
memory_size = "2048"
}
+ # this flag will set the desired state for the VM
+ # if it is set to true it will keep the VM running otherwise it will stop it
+ # by default it is set to true, so all VMs will be running
+ keep_running = true
+
+ # This will contain the configuration for the port forwarding reverse proxy
+ # in this case we are opening a port to any part in the host, it will not be linked to any
+ # specific vm or container. by default it will listen on 0.0.0.0 (all interfaces)
+ # and the target host will also be 0.0.0.0 (all interfaces) so it will be open to the world
+ # use
+ reverse_proxy_host {
+ port = "2022"
+
+ tcp_route {
+ target_port = "22"
+ }
+ }
+
# this will allow you to fine grain the configuration of the VM
# you can pass any command that is compatible with the prlctl command
# directly to the VM
@@ -91,4 +109,4 @@ resource "parallels-desktop_remote_vm" "example_vagrant_file" {
]
}
-}
\ No newline at end of file
+}
diff --git a/internal/apiclient/apimodels/create_reverse_proxy_host.go b/internal/apiclient/apimodels/create_reverse_proxy_host.go
new file mode 100644
index 0000000..f2fd112
--- /dev/null
+++ b/internal/apiclient/apimodels/create_reverse_proxy_host.go
@@ -0,0 +1,17 @@
+package apimodels
+
+type ReverseProxyHostCreateRequest struct {
+ Host string `json:"host"`
+ Port string `json:"port"`
+ Tls *ReverseProxyHostTls `json:"tls,omitempty"`
+ Cors *ReverseProxyHostCors `json:"cors,omitempty"`
+ HttpRoutes []*ReverseProxyHostHttpRoute `json:"http_routes,omitempty"`
+ TcpRoute *ReverseProxyHostTcpRoute `json:"tcp_route,omitempty"`
+}
+
+type ReverseProxyHostUpdateRequest struct {
+ Host string `json:"host"`
+ Port string `json:"port"`
+ Tls *ReverseProxyHostTls `json:"tls,omitempty"`
+ Cors *ReverseProxyHostCors `json:"cors,omitempty"`
+}
diff --git a/internal/apiclient/apimodels/orchestrator_host.go b/internal/apiclient/apimodels/orchestrator_host.go
index c9a3b05..de25c0e 100644
--- a/internal/apiclient/apimodels/orchestrator_host.go
+++ b/internal/apiclient/apimodels/orchestrator_host.go
@@ -26,19 +26,39 @@ type OrchestratorHostResponse struct {
}
type OrchestratorHost struct {
- ID string `json:"id"`
- Enabled bool `json:"enabled"`
- Host string `json:"host"`
- Architecture string `json:"architecture"`
- CPUModel string `json:"cpu_model"`
- Description string `json:"description"`
- Tags []string `json:"tags"`
- State string `json:"state"`
- Resources OrchestratorHostResources `json:"resources"`
+ ID string `json:"id"`
+ Enabled bool `json:"enabled"`
+ Host string `json:"host"`
+ Architecture string `json:"architecture"`
+ CpuModel string `json:"cpu_model"`
+ OsVersion string `json:"os_version,omitempty"`
+ OsName string `json:"os_name,omitempty"`
+ ExternalIpAddress string `json:"external_ip_address,omitempty"`
+ DevOpsVersion string `json:"devops_version,omitempty"`
+ Description string `json:"description,omitempty"`
+ Tags []string `json:"tags,omitempty"`
+ State string `json:"state,omitempty"`
+ ParallelsDesktopVersion string `json:"parallels_desktop_version,omitempty"`
+ ParallelsDesktopLicensed bool `json:"parallels_desktop_licensed,omitempty"`
+ IsReverseProxyEnabled bool `json:"is_reverse_proxy_enabled"`
+ ReverseProxy *HostReverseProxy `json:"reverse_proxy,omitempty"`
+ Resources OrchestratorHostResources `json:"resources"`
+ RequiredClaims []string `json:"required_claims,omitempty"`
+ RequiredRoles []string `json:"required_roles,omitempty"`
}
type OrchestratorHostResources struct {
- LogicalCPUCount int64 `json:"logical_cpu_count"`
- MemorySize int64 `json:"memory_size"`
- DiskSize int64 `json:"disk_size"`
+ TotalAppleVms int64 `json:"total_apple_vms,omitempty"`
+ PhysicalCpuCount int64 `json:"physical_cpu_count,omitempty"`
+ LogicalCpuCount int64 `json:"logical_cpu_count"`
+ MemorySize float64 `json:"memory_size,omitempty"`
+ DiskSize float64 `json:"disk_size,omitempty"`
+ FreeDiskSize float64 `json:"free_disk_size,omitempty"`
+}
+
+type HostReverseProxy struct {
+ Enabled bool `json:"enabled,omitempty"`
+ Host string `json:"host,omitempty"`
+ Port string `json:"port,omitempty"`
+ Hosts []ReverseProxyHost `json:"hosts,omitempty"`
}
diff --git a/internal/apiclient/apimodels/reverse_proxy.go b/internal/apiclient/apimodels/reverse_proxy.go
new file mode 100644
index 0000000..a291d5b
--- /dev/null
+++ b/internal/apiclient/apimodels/reverse_proxy.go
@@ -0,0 +1,43 @@
+package apimodels
+
+type ReverseProxyHost struct {
+ ID string `json:"id"`
+ Host string `json:"host"`
+ Port string `json:"port"`
+ Tls *ReverseProxyHostTls `json:"tls,omitempty"`
+ Cors *ReverseProxyHostCors `json:"cors,omitempty"`
+ HttpRoutes []*ReverseProxyHostHttpRoute `json:"http_routes,omitempty"`
+ TcpRoute *ReverseProxyHostTcpRoute `json:"tcp_route,omitempty"`
+}
+
+type ReverseProxyHostTls struct {
+ Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty"`
+ Cert string `json:"cert,omitempty" yaml:"cert,omitempty"`
+ Key string `json:"key,omitempty" yaml:"key,omitempty"`
+}
+
+type ReverseProxyHostTcpRoute struct {
+ ID string `json:"id,omitempty" yaml:"id,omitempty"`
+ TargetPort string `json:"target_port,omitempty" yaml:"target_port,omitempty"`
+ TargetHost string `json:"target_host,omitempty" yaml:"target_host,omitempty"`
+ TargetVmId string `json:"target_vm_id,omitempty" yaml:"target_vm_id,omitempty"`
+}
+
+type ReverseProxyHostCors struct {
+ Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty"`
+ AllowedOrigins []string `json:"allowed_origins,omitempty" yaml:"allowed_origins,omitempty"`
+ AllowedMethods []string `json:"allowed_methods,omitempty" yaml:"allowed_methods,omitempty"`
+ AllowedHeaders []string `json:"allowed_headers,omitempty" yaml:"allowed_headers,omitempty"`
+}
+
+type ReverseProxyHostHttpRoute struct {
+ ID string `json:"id,omitempty" yaml:"id,omitempty"`
+ Path string `json:"path,omitempty" yaml:"path,omitempty"`
+ TargetVmId string `json:"target_vm_id,omitempty" yaml:"target_vm_id,omitempty"`
+ TargetHost string `json:"target_host,omitempty" yaml:"target_host,omitempty"`
+ TargetPort string `json:"target_port,omitempty" yaml:"target_port,omitempty"`
+ Schema string `json:"schema,omitempty" yaml:"scheme,omitempty"`
+ Pattern string `json:"pattern,omitempty" yaml:"pattern,omitempty"`
+ RequestHeaders map[string]string `json:"request_headers,omitempty" yaml:"request_headers,omitempty"`
+ ResponseHeaders map[string]string `json:"response_headers,omitempty" yaml:"response_headers,omitempty"`
+}
diff --git a/internal/apiclient/apimodels/system_usage.go b/internal/apiclient/apimodels/system_usage.go
index f33d1ea..78a69e0 100644
--- a/internal/apiclient/apimodels/system_usage.go
+++ b/internal/apiclient/apimodels/system_usage.go
@@ -1,15 +1,43 @@
package apimodels
type SystemUsageResponse struct {
- CpuType string `json:"cpu_type,omitempty"`
- TotalAvailable SystemUsageItem `json:"total_available,omitempty"`
- TotalInUse SystemUsageItem `json:"total_in_use,omitempty"`
- TotalReserved SystemUsageItem `json:"total_reserved,omitempty"`
+ CpuType string `json:"cpu_type,omitempty"`
+ CpuBrand string `json:"cpu_brand,omitempty"`
+ DevOpsVersion string `json:"devops_version,omitempty"`
+ OsName string `json:"os_name,omitempty"`
+ OsVersion string `json:"os_version,omitempty"`
+ ParallelsDesktopVersion string `json:"parallels_desktop_version,omitempty"`
+ ParallelsDesktopLicensed bool `json:"parallels_desktop_licensed,omitempty"`
+ ExternalIpAddress string `json:"external_ip_address,omitempty"`
+ IsReverseProxyEnabled bool `json:"is_reverse_proxy_enabled"`
+ ReverseProxy *SystemReverseProxy `json:"reverse_proxy,omitempty"`
+ SystemReserved *SystemUsageItem `json:"system_reserved,omitempty"`
+ Total *SystemUsageItem `json:"total,omitempty"`
+ TotalAvailable *SystemUsageItem `json:"total_available,omitempty"`
+ TotalInUse *SystemUsageItem `json:"total_in_use,omitempty"`
+ TotalReserved *SystemUsageItem `json:"total_reserved,omitempty"`
}
type SystemUsageItem struct {
PhysicalCpuCount int64 `json:"physical_cpu_count,omitempty"`
- LogicalCpuCount int64 `json:"logical_cpu_count,omitempty"`
+ LogicalCpuCount int64 `json:"logical_cpu_count"`
+ MemorySize float64 `json:"memory_size"`
+ DiskSize float64 `json:"disk_count"`
+}
+
+type SystemHardwareInfo struct {
+ CpuType string `json:"cpu_type,omitempty"`
+ CpuBrand string `json:"cpu_brand,omitempty"`
+ PhysicalCpuCount int `json:"physical_cpu_count,omitempty"`
+ LogicalCpuCount int `json:"logical_cpu_count,omitempty"`
MemorySize float64 `json:"memory_size,omitempty"`
- DiskSize float64 `json:"disk_count,omitempty"`
+ DiskSize float64 `json:"disk_size,omitempty"`
+ FreeDiskSize float64 `json:"free_disk_size,omitempty"`
+}
+
+type SystemReverseProxy struct {
+ Enabled bool `json:"enabled,omitempty"`
+ Host string `json:"host,omitempty"`
+ Port string `json:"port,omitempty"`
+ Hosts []ReverseProxyHost `json:"hosts,omitempty"`
}
diff --git a/internal/apiclient/apimodels/virtual_machine.go b/internal/apiclient/apimodels/virtual_machine.go
index 6484fdc..59566d7 100644
--- a/internal/apiclient/apimodels/virtual_machine.go
+++ b/internal/apiclient/apimodels/virtual_machine.go
@@ -3,6 +3,9 @@ package apimodels
type VirtualMachine struct {
User string `json:"user"`
ID string `json:"ID"`
+ HostId string `json:"host_id"`
+ HostExternalIpAddress string `json:"host_external_ip_address"`
+ InternalIpAddress string `json:"internal_ip_address"`
Name string `json:"Name"`
Description string `json:"Description"`
Type string `json:"Type"`
diff --git a/internal/apiclient/create_reverse_proxy_host.go b/internal/apiclient/create_reverse_proxy_host.go
new file mode 100644
index 0000000..5dc3269
--- /dev/null
+++ b/internal/apiclient/create_reverse_proxy_host.go
@@ -0,0 +1,45 @@
+package apiclient
+
+import (
+ "context"
+ "fmt"
+
+ "terraform-provider-parallels-desktop/internal/apiclient/apimodels"
+ "terraform-provider-parallels-desktop/internal/helpers"
+ "terraform-provider-parallels-desktop/internal/schemas/authenticator"
+
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+)
+
+func CreateReverseProxyHost(ctx context.Context, config HostConfig, request apimodels.ReverseProxyHost) (*apimodels.ReverseProxyHost, diag.Diagnostics) {
+ diagnostics := diag.Diagnostics{}
+ var response apimodels.ReverseProxyHost
+
+ tflog.Info(ctx, "Creating reverse proxy host "+request.Host+" with port "+request.Port)
+ urlHost := helpers.GetHostUrl(config.Host)
+ var url string
+ if config.IsOrchestrator {
+ url = fmt.Sprintf("%s/orchestrator/hosts/%s/reverse-proxy/hosts", helpers.GetHostApiVersionedBaseUrl(urlHost), config.HostId)
+ } else {
+ url = fmt.Sprintf("%s/reverse-proxy/hosts", helpers.GetHostApiVersionedBaseUrl(urlHost))
+ }
+
+ auth, err := authenticator.GetAuthenticator(ctx, urlHost, config.License, config.Authorization, config.DisableTlsValidation)
+ if err != nil {
+ diagnostics.AddError("There was an error getting the authenticator", err.Error())
+ return nil, diagnostics
+ }
+
+ client := helpers.NewHttpCaller(ctx, config.DisableTlsValidation)
+ if clientResponse, err := client.PostDataToClient(url, nil, request, auth, &response); err != nil {
+ if clientResponse != nil && clientResponse.ApiError != nil {
+ tflog.Error(ctx, fmt.Sprintf("Error creating reverse proxy: %v, api message: %s", err, clientResponse.ApiError.Message))
+ }
+
+ diagnostics.AddError("There was an error creating the reverse proxy", err.Error())
+ return nil, diagnostics
+ }
+
+ return &response, diagnostics
+}
diff --git a/internal/apiclient/delete_reverse_proxy_host.go b/internal/apiclient/delete_reverse_proxy_host.go
new file mode 100644
index 0000000..cdb5e1e
--- /dev/null
+++ b/internal/apiclient/delete_reverse_proxy_host.go
@@ -0,0 +1,44 @@
+package apiclient
+
+import (
+ "context"
+ "fmt"
+
+ "terraform-provider-parallels-desktop/internal/helpers"
+ "terraform-provider-parallels-desktop/internal/schemas/authenticator"
+
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+)
+
+func DeleteReverseProxyHost(ctx context.Context, config HostConfig, host string) diag.Diagnostics {
+ diagnostic := diag.Diagnostics{}
+ urlHost := helpers.GetHostUrl(config.Host)
+ if host == "" {
+ diagnostic.AddError("There was an error deleting the reverse proxy host", "host is empty")
+ return diagnostic
+ }
+
+ var url string
+ if config.IsOrchestrator {
+ url = fmt.Sprintf("%s/orchestrator/hosts/%s/reverse-proxy/hosts/%s", helpers.GetHostApiVersionedBaseUrl(urlHost), config.HostId, host)
+ } else {
+ url = fmt.Sprintf("%s/reverse-proxy/hosts/%s", helpers.GetHostApiVersionedBaseUrl(urlHost), host)
+ }
+
+ auth, err := authenticator.GetAuthenticator(ctx, urlHost, config.License, config.Authorization, config.DisableTlsValidation)
+ if err != nil {
+ diagnostic.AddError("There was an error getting the authenticator", err.Error())
+ return diagnostic
+ }
+
+ client := helpers.NewHttpCaller(ctx, config.DisableTlsValidation)
+ if _, err := client.DeleteDataFromClient(url, nil, auth, nil); err != nil {
+ diagnostic.AddError("There was an error deleting the reverse proxy host", err.Error())
+ return diagnostic
+ }
+
+ tflog.Info(ctx, "Deleted reverse proxy host "+host)
+
+ return diagnostic
+}
diff --git a/internal/apiclient/get_orchestrator_host.go b/internal/apiclient/get_orchestrator_host.go
index a89aa5b..a7754aa 100644
--- a/internal/apiclient/get_orchestrator_host.go
+++ b/internal/apiclient/get_orchestrator_host.go
@@ -45,3 +45,33 @@ func GetOrchestratorHost(ctx context.Context, config HostConfig, hostId string)
return &response, diagnostics
}
+
+func GetOrchestratorHosts(ctx context.Context, config HostConfig) ([]apimodels.OrchestratorHost, diag.Diagnostics) {
+ diagnostics := diag.Diagnostics{}
+ var response []apimodels.OrchestratorHost
+ urlHost := helpers.GetHostUrl(config.Host)
+
+ url := fmt.Sprintf("%s/orchestrator/hosts", helpers.GetHostApiVersionedBaseUrl(urlHost))
+
+ auth, err := authenticator.GetAuthenticator(ctx, urlHost, config.License, config.Authorization, config.DisableTlsValidation)
+ if err != nil {
+ diagnostics.AddError("There was an error getting the authenticator", err.Error())
+ return nil, diagnostics
+ }
+
+ client := helpers.NewHttpCaller(ctx, config.DisableTlsValidation)
+ if clientResponse, err := client.GetDataFromClient(url, nil, auth, &response); err != nil {
+ if clientResponse != nil && clientResponse.ApiError != nil {
+ if clientResponse.ApiError.Code == 404 {
+ return nil, diagnostics
+ }
+ tflog.Error(ctx, fmt.Sprintf("Error getting orchestrator hosts: %v, api message: %s", err, clientResponse.ApiError.Message))
+ }
+ diagnostics.AddError("There was an error getting the orchestrator hosts", err.Error())
+ return nil, diagnostics
+ }
+
+ tflog.Info(ctx, fmt.Sprintf("Got %v orchestrators ", len(response)))
+
+ return response, diagnostics
+}
diff --git a/internal/apiclient/get_reverse_proxy_host.go b/internal/apiclient/get_reverse_proxy_host.go
new file mode 100644
index 0000000..11f44ec
--- /dev/null
+++ b/internal/apiclient/get_reverse_proxy_host.go
@@ -0,0 +1,52 @@
+package apiclient
+
+import (
+ "context"
+ "fmt"
+
+ "terraform-provider-parallels-desktop/internal/apiclient/apimodels"
+ "terraform-provider-parallels-desktop/internal/helpers"
+ "terraform-provider-parallels-desktop/internal/schemas/authenticator"
+
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+)
+
+func GetReverseProxyHost(ctx context.Context, config HostConfig, host string) (*apimodels.ReverseProxyHost, diag.Diagnostics) {
+ diagnostic := diag.Diagnostics{}
+ urlHost := helpers.GetHostUrl(config.Host)
+ if host == "" {
+ diagnostic.AddError("There was an error getting the reverse proxy host", "host is empty")
+ return nil, diagnostic
+ }
+
+ var url string
+ if config.IsOrchestrator {
+ url = fmt.Sprintf("%s/orchestrator/hosts/%s/reverse-proxy/hosts/%s", helpers.GetHostApiVersionedBaseUrl(urlHost), config.HostId, host)
+ } else {
+ url = fmt.Sprintf("%s/reverse-proxy/hosts/%s", helpers.GetHostApiVersionedBaseUrl(urlHost), host)
+ }
+
+ auth, err := authenticator.GetAuthenticator(ctx, urlHost, config.License, config.Authorization, config.DisableTlsValidation)
+ if err != nil {
+ diagnostic.AddError("There was an error getting the authenticator", err.Error())
+ return nil, diagnostic
+ }
+
+ var response apimodels.ReverseProxyHost
+ client := helpers.NewHttpCaller(ctx, config.DisableTlsValidation)
+ if clientResponse, err := client.GetDataFromClient(url, nil, auth, &response); err != nil {
+ if clientResponse != nil && clientResponse.ApiError != nil {
+ if clientResponse.ApiError.Code == 404 {
+ return nil, diagnostic
+ }
+ tflog.Error(ctx, fmt.Sprintf("Error getting claims: %v, api message: %s", err, clientResponse.ApiError.Message))
+ }
+ diagnostic.AddError("There was an error getting the claims", err.Error())
+ return nil, diagnostic
+ }
+
+ tflog.Info(ctx, "Got the reverse proxy host "+host)
+
+ return &response, diagnostic
+}
diff --git a/internal/apiclient/get_system_usage.go b/internal/apiclient/get_system_usage.go
index 70b3dc4..f4b7638 100644
--- a/internal/apiclient/get_system_usage.go
+++ b/internal/apiclient/get_system_usage.go
@@ -17,7 +17,12 @@ func GetSystemUsage(ctx context.Context, config HostConfig) (*apimodels.SystemUs
var response apimodels.SystemUsageResponse
urlHost := helpers.GetHostUrl(config.Host)
- url := fmt.Sprintf("%s/config/hardware", helpers.GetHostApiVersionedBaseUrl(urlHost))
+ var url string
+ if config.IsOrchestrator {
+ url = fmt.Sprintf("%s/orchestrator/hosts/%s/hardware", helpers.GetHostApiVersionedBaseUrl(urlHost), config.HostId)
+ } else {
+ url = fmt.Sprintf("%s/config/hardware", helpers.GetHostApiVersionedBaseUrl(urlHost))
+ }
auth, err := authenticator.GetAuthenticator(ctx, urlHost, config.License, config.Authorization, config.DisableTlsValidation)
if err != nil {
diff --git a/internal/apiclient/host_config.go b/internal/apiclient/host_config.go
index c410afa..db63239 100644
--- a/internal/apiclient/host_config.go
+++ b/internal/apiclient/host_config.go
@@ -7,6 +7,7 @@ import (
type HostConfig struct {
IsOrchestrator bool `json:"is_orchestrator"`
Host string `json:"host"`
+ HostId string `json:"host_id"`
MachineId string `json:"machine_id"`
License string `json:"license"`
DisableTlsValidation bool `json:"disable_tls_validation"`
diff --git a/internal/clone_vm/resource_models.go b/internal/clone_vm/models/resource_models_v0.go
similarity index 94%
rename from internal/clone_vm/resource_models.go
rename to internal/clone_vm/models/resource_models_v0.go
index 0527e4f..68fb24a 100644
--- a/internal/clone_vm/resource_models.go
+++ b/internal/clone_vm/models/resource_models_v0.go
@@ -1,4 +1,4 @@
-package clonevm
+package models
import (
"terraform-provider-parallels-desktop/internal/schemas/authenticator"
@@ -12,8 +12,8 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
)
-// CloneVmResourceModel describes the resource data model.
-type CloneVmResourceModel struct {
+// CloneVmResourceModelV0 describes the resource data model.
+type CloneVmResourceModelV0 struct {
Authenticator *authenticator.Authentication `tfsdk:"authenticator"`
Host types.String `tfsdk:"host"`
Orchestrator types.String `tfsdk:"orchestrator"`
diff --git a/internal/clone_vm/models/resource_models_v1.go b/internal/clone_vm/models/resource_models_v1.go
new file mode 100644
index 0000000..dc14e71
--- /dev/null
+++ b/internal/clone_vm/models/resource_models_v1.go
@@ -0,0 +1,40 @@
+package models
+
+import (
+ "terraform-provider-parallels-desktop/internal/schemas/authenticator"
+ "terraform-provider-parallels-desktop/internal/schemas/postprocessorscript"
+ "terraform-provider-parallels-desktop/internal/schemas/prlctl"
+ "terraform-provider-parallels-desktop/internal/schemas/reverseproxy"
+ "terraform-provider-parallels-desktop/internal/schemas/sharedfolder"
+ "terraform-provider-parallels-desktop/internal/schemas/vmconfig"
+ "terraform-provider-parallels-desktop/internal/schemas/vmspecs"
+
+ "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+// CloneVmResourceModelV0 describes the resource data model.
+type CloneVmResourceModelV1 struct {
+ Authenticator *authenticator.Authentication `tfsdk:"authenticator"`
+ Host types.String `tfsdk:"host"`
+ Orchestrator types.String `tfsdk:"orchestrator"`
+ ID types.String `tfsdk:"id"`
+ OsType types.String `tfsdk:"os_type"`
+ BaseVmId types.String `tfsdk:"base_vm_id"`
+ ExternalIp types.String `tfsdk:"external_ip"`
+ InternalIp types.String `tfsdk:"internal_ip"`
+ Name types.String `tfsdk:"name"`
+ Owner types.String `tfsdk:"owner"`
+ Path types.String `tfsdk:"path"`
+ Specs *vmspecs.VmSpecs `tfsdk:"specs"`
+ PostProcessorScripts []*postprocessorscript.PostProcessorScript `tfsdk:"post_processor_script"`
+ OnDestroyScript []*postprocessorscript.PostProcessorScript `tfsdk:"on_destroy_script"`
+ SharedFolder []*sharedfolder.SharedFolder `tfsdk:"shared_folder"`
+ Config *vmconfig.VmConfig `tfsdk:"config"`
+ PrlCtl []*prlctl.PrlCtlCmd `tfsdk:"prlctl"`
+ RunAfterCreate types.Bool `tfsdk:"run_after_create"`
+ Timeouts timeouts.Value `tfsdk:"timeouts"`
+ ForceChanges types.Bool `tfsdk:"force_changes"`
+ KeepRunning types.Bool `tfsdk:"keep_running"`
+ ReverseProxyHosts []*reverseproxy.ReverseProxyHost `tfsdk:"reverse_proxy_host"`
+}
diff --git a/internal/clone_vm/resource.go b/internal/clone_vm/resource.go
index edce3d0..fe3166a 100644
--- a/internal/clone_vm/resource.go
+++ b/internal/clone_vm/resource.go
@@ -7,12 +7,16 @@ import (
"terraform-provider-parallels-desktop/internal/apiclient"
"terraform-provider-parallels-desktop/internal/apiclient/apimodels"
+ resource_models "terraform-provider-parallels-desktop/internal/clone_vm/models"
+ "terraform-provider-parallels-desktop/internal/clone_vm/schemas"
"terraform-provider-parallels-desktop/internal/common"
"terraform-provider-parallels-desktop/internal/models"
"terraform-provider-parallels-desktop/internal/schemas/postprocessorscript"
+ "terraform-provider-parallels-desktop/internal/schemas/reverseproxy"
"terraform-provider-parallels-desktop/internal/telemetry"
"github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
@@ -39,7 +43,7 @@ func (r *CloneVmResource) Metadata(ctx context.Context, req resource.MetadataReq
}
func (r *CloneVmResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
- resp.Schema = getSchema(ctx)
+ resp.Schema = schemas.GetCloneVmSchemaV1(ctx)
}
func (r *CloneVmResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
@@ -60,7 +64,7 @@ func (r *CloneVmResource) Configure(ctx context.Context, req resource.ConfigureR
}
func (r *CloneVmResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
- var data CloneVmResourceModel
+ var data resource_models.CloneVmResourceModelV1
telemetrySvc := telemetry.Get(ctx)
telemetryEvent := telemetry.NewTelemetryItem(
@@ -252,25 +256,92 @@ func (r *CloneVmResource) Create(ctx context.Context, req resource.CreateRequest
return
}
- // Starting the vm if requested
- if data.RunAfterCreate.ValueBool() {
+ if len(data.ReverseProxyHosts) > 0 {
+ rpHostConfig := hostConfig
+ rpHostConfig.HostId = stoppedVm.HostId
+ rpHosts, updateDiag := updateReverseProxyHostsTarget(ctx, &data, rpHostConfig, stoppedVm)
+ if updateDiag.HasError() {
+ resp.Diagnostics.Append(updateDiag...)
+ return
+ }
+
+ result, createDiag := reverseproxy.Create(ctx, rpHostConfig, rpHosts)
+ if createDiag.HasError() {
+ resp.Diagnostics.Append(createDiag...)
+
+ if diag := reverseproxy.Delete(ctx, rpHostConfig, rpHosts); diag.HasError() {
+ tflog.Error(ctx, "Error deleting reverse proxy hosts")
+ }
+
+ if data.ID.ValueString() != "" {
+ // If we have an ID, we need to delete the machine
+ apiclient.SetMachineState(ctx, rpHostConfig, data.ID.ValueString(), apiclient.MachineStateOpStop)
+ if _, diag := common.EnsureMachineStopped(ctx, rpHostConfig, stoppedVm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
+
+ apiclient.DeleteVm(ctx, rpHostConfig, data.ID.ValueString())
+ }
+ return
+ }
+
+ for i := range result {
+ data.ReverseProxyHosts[i].ID = result[i].ID
+ }
+ }
+
+ // Starting the vm by default, otherwise we will stop the VM from being created
+ if data.RunAfterCreate.ValueBool() || data.KeepRunning.ValueBool() || (data.RunAfterCreate.IsUnknown() && data.KeepRunning.IsUnknown()) {
if _, diag := common.EnsureMachineRunning(ctx, hostConfig, stoppedVm); diag.HasError() {
resp.Diagnostics.Append(diag...)
if data.ID.ValueString() != "" {
// If we have an ID, we need to delete the machine
apiclient.SetMachineState(ctx, hostConfig, data.ID.ValueString(), apiclient.MachineStateOpStop)
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, stoppedVm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
apiclient.DeleteVm(ctx, hostConfig, data.ID.ValueString())
}
return
}
- _, diag := apiclient.GetVm(ctx, hostConfig, clonedVm.ID)
+ _, diag := apiclient.GetVm(ctx, hostConfig, vm.ID)
if diag.HasError() {
resp.Diagnostics.Append(diag...)
return
}
+ } else {
+ // If we are not starting the machine, we will stop it
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, stoppedVm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ if data.ID.ValueString() != "" {
+ // If we have an ID, we need to delete the machine
+ apiclient.SetMachineState(ctx, hostConfig, data.ID.ValueString(), apiclient.MachineStateOpStop)
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, stoppedVm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
+ apiclient.DeleteVm(ctx, hostConfig, data.ID.ValueString())
+ }
+ return
+ }
+ }
+
+ externalIp := ""
+ internalIp := ""
+ refreshVm, refreshDiag := apiclient.GetVm(ctx, hostConfig, vm.ID)
+ if refreshDiag.HasError() {
+ resp.Diagnostics.Append(refreshDiag...)
+ return
+ } else {
+ externalIp = refreshVm.HostExternalIpAddress
+ internalIp = refreshVm.InternalIpAddress
}
+ data.ExternalIp = types.StringValue(externalIp)
+ data.InternalIp = types.StringValue(internalIp)
data.OsType = types.StringValue(clonedVm.OS)
if data.OnDestroyScript != nil {
for _, script := range data.OnDestroyScript {
@@ -311,7 +382,7 @@ func (r *CloneVmResource) Create(ctx context.Context, req resource.CreateRequest
}
func (r *CloneVmResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
- var data CloneVmResourceModel
+ var data resource_models.CloneVmResourceModelV1
telemetrySvc := telemetry.Get(ctx)
telemetryEvent := telemetry.NewTelemetryItem(
@@ -383,8 +454,8 @@ func (r *CloneVmResource) Read(ctx context.Context, req resource.ReadRequest, re
}
func (r *CloneVmResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
- var data CloneVmResourceModel
- var currentData CloneVmResourceModel
+ var data resource_models.CloneVmResourceModelV1
+ var currentData resource_models.CloneVmResourceModelV1
telemetrySvc := telemetry.Get(ctx)
telemetryEvent := telemetry.NewTelemetryItem(
@@ -532,8 +603,82 @@ func (r *CloneVmResource) Update(ctx context.Context, req resource.UpdateRequest
}
}
- data.ID = types.StringValue(vm.ID)
- data.OsType = types.StringValue(vm.OS)
+ if reverseproxy.ReverseProxyHostsDiff(data.ReverseProxyHosts, currentData.ReverseProxyHosts) {
+ copyCurrentRpHosts := reverseproxy.CopyReverseProxyHosts(currentData.ReverseProxyHosts)
+ copyRpHosts := reverseproxy.CopyReverseProxyHosts(data.ReverseProxyHosts)
+
+ results, updateDiag := reverseproxy.Update(ctx, hostConfig, copyCurrentRpHosts, copyRpHosts)
+ if updateDiag.HasError() {
+ resp.Diagnostics.Append(updateDiag...)
+ revertResults, _ := reverseproxy.Revert(ctx, hostConfig, copyCurrentRpHosts, copyRpHosts)
+ for i := range revertResults {
+ data.ReverseProxyHosts[i].ID = revertResults[i].ID
+ }
+ return
+ }
+
+ for i := range results {
+ data.ReverseProxyHosts[i].ID = results[i].ID
+ }
+ } else {
+ for i := range currentData.ReverseProxyHosts {
+ data.ReverseProxyHosts[i].ID = currentData.ReverseProxyHosts[i].ID
+ }
+ }
+
+ // Starting the vm by default, otherwise we will stop the VM from being created
+ if data.RunAfterCreate.ValueBool() || data.KeepRunning.ValueBool() || (data.RunAfterCreate.IsUnknown() && data.KeepRunning.IsUnknown()) {
+ if _, diag := common.EnsureMachineRunning(ctx, hostConfig, vm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ if data.ID.ValueString() != "" {
+ // If we have an ID, we need to delete the machine
+ apiclient.SetMachineState(ctx, hostConfig, data.ID.ValueString(), apiclient.MachineStateOpStop)
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, vm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
+ apiclient.DeleteVm(ctx, hostConfig, data.ID.ValueString())
+ }
+ return
+ }
+
+ _, diag := apiclient.GetVm(ctx, hostConfig, vm.ID)
+ if diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
+ } else {
+ // If we are not starting the machine, we will stop it
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, vm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ if data.ID.ValueString() != "" {
+ // If we have an ID, we need to delete the machine
+ apiclient.SetMachineState(ctx, hostConfig, data.ID.ValueString(), apiclient.MachineStateOpStop)
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, vm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
+ apiclient.DeleteVm(ctx, hostConfig, data.ID.ValueString())
+ }
+ return
+ }
+ }
+
+ externalIp := ""
+ internalIp := ""
+ refreshVm, refreshDiag := apiclient.GetVm(ctx, hostConfig, vm.ID)
+ if refreshDiag.HasError() {
+ resp.Diagnostics.Append(refreshDiag...)
+ return
+ } else {
+ externalIp = refreshVm.HostExternalIpAddress
+ internalIp = refreshVm.InternalIpAddress
+ }
+
+ data.ID = types.StringValue(refreshVm.ID)
+ data.OsType = types.StringValue(refreshVm.OS)
+ data.ExternalIp = types.StringValue(externalIp)
+ data.InternalIp = types.StringValue(internalIp)
if data.OnDestroyScript != nil {
for _, script := range data.OnDestroyScript {
elements := make([]attr.Value, 0)
@@ -569,7 +714,7 @@ func (r *CloneVmResource) Update(ctx context.Context, req resource.UpdateRequest
}
func (r *CloneVmResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
- var data CloneVmResourceModel
+ var data resource_models.CloneVmResourceModelV1
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
@@ -661,3 +806,119 @@ func (r *CloneVmResource) Delete(ctx context.Context, req resource.DeleteRequest
func (r *CloneVmResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
}
+
+func (r *CloneVmResource) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader {
+ v0Schema := schemas.GetCloneVmSchemaV0(ctx)
+ return map[int64]resource.StateUpgrader{
+ 0: {
+ PriorSchema: &v0Schema,
+ StateUpgrader: UpgradeStateToV1,
+ },
+ }
+}
+
+func UpgradeStateToV1(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) {
+ var priorStateData resource_models.CloneVmResourceModelV0
+ resp.Diagnostics.Append(req.State.Get(ctx, &priorStateData)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ upgradedStateData := resource_models.CloneVmResourceModelV1{
+ Authenticator: priorStateData.Authenticator,
+ Host: priorStateData.Host,
+ Orchestrator: priorStateData.Orchestrator,
+ ID: priorStateData.ID,
+ OsType: priorStateData.OsType,
+ BaseVmId: priorStateData.BaseVmId,
+ ExternalIp: types.StringUnknown(),
+ InternalIp: types.StringUnknown(),
+ Name: priorStateData.Name,
+ Owner: priorStateData.Owner,
+ Path: priorStateData.Path,
+ Specs: priorStateData.Specs,
+ PostProcessorScripts: priorStateData.PostProcessorScripts,
+ OnDestroyScript: priorStateData.OnDestroyScript,
+ SharedFolder: priorStateData.SharedFolder,
+ Config: priorStateData.Config,
+ PrlCtl: priorStateData.PrlCtl,
+ RunAfterCreate: priorStateData.RunAfterCreate,
+ Timeouts: priorStateData.Timeouts,
+ ForceChanges: priorStateData.ForceChanges,
+ KeepRunning: types.BoolValue(true),
+ ReverseProxyHosts: make([]*reverseproxy.ReverseProxyHost, 0),
+ }
+
+ println(fmt.Sprintf("Upgrading state from version %v", upgradedStateData))
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &upgradedStateData)...)
+}
+
+func updateReverseProxyHostsTarget(ctx context.Context, data *resource_models.CloneVmResourceModelV1, hostConfig apiclient.HostConfig, targetVm *apimodels.VirtualMachine) ([]reverseproxy.ReverseProxyHost, diag.Diagnostics) {
+ resultDiagnostic := diag.Diagnostics{}
+ var refreshedVm *apimodels.VirtualMachine
+ var rpDiag diag.Diagnostics
+ refreshedVm, rpDiag = common.EnsureMachineHasInternalIp(ctx, hostConfig, targetVm)
+ if rpDiag.HasError() {
+ resultDiagnostic.Append(rpDiag...)
+ if data.ID.ValueString() != "" {
+ // If we have an ID, we need to delete the machine
+ apiclient.SetMachineState(ctx, hostConfig, data.ID.ValueString(), apiclient.MachineStateOpStop)
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, refreshedVm); diag.HasError() {
+ return nil, diag
+ }
+ apiclient.DeleteVm(ctx, hostConfig, data.ID.ValueString())
+ }
+ return nil, resultDiagnostic
+ }
+
+ modifiedHosts := make([]reverseproxy.ReverseProxyHost, len(data.ReverseProxyHosts))
+ for i := range data.ReverseProxyHosts {
+ host := reverseproxy.ReverseProxyHost{}
+ host.Host = data.ReverseProxyHosts[i].Host
+ host.Port = data.ReverseProxyHosts[i].Port
+ internalIp := refreshedVm.InternalIpAddress
+ emptyString := ""
+
+ if data.ReverseProxyHosts[i].Cors != nil {
+ host.Cors = &reverseproxy.ReverseProxyCors{}
+ host.Cors.AllowedOrigins = data.ReverseProxyHosts[i].Cors.AllowedOrigins
+ host.Cors.AllowedMethods = data.ReverseProxyHosts[i].Cors.AllowedMethods
+ host.Cors.AllowedHeaders = data.ReverseProxyHosts[i].Cors.AllowedHeaders
+ host.Cors.Enabled = data.ReverseProxyHosts[i].Cors.Enabled
+ }
+ if data.ReverseProxyHosts[i].Tls != nil {
+ host.Tls = &reverseproxy.ReverseProxyTls{}
+ host.Tls.Certificate = data.ReverseProxyHosts[i].Tls.Certificate
+ host.Tls.PrivateKey = data.ReverseProxyHosts[i].Tls.PrivateKey
+ host.Tls.Enabled = data.ReverseProxyHosts[i].Tls.Enabled
+ }
+ if data.ReverseProxyHosts[i].TcpRoute != nil {
+ host.TcpRoute = &reverseproxy.ReverseProxyHostTcpRoute{}
+ host.TcpRoute.TargetPort = data.ReverseProxyHosts[i].TcpRoute.TargetPort
+ host.TcpRoute.TargetHost = types.StringValue(internalIp)
+ host.TcpRoute.TargetVmId = types.StringValue(emptyString)
+ }
+
+ if len(data.ReverseProxyHosts[i].HttpRoute) > 0 {
+ host.HttpRoute = make([]*reverseproxy.ReverseProxyHttpRoute, len(data.ReverseProxyHosts[i].HttpRoute))
+ for j := range modifiedHosts[i].HttpRoute {
+ httpRoute := reverseproxy.ReverseProxyHttpRoute{}
+ httpRoute.Path = data.ReverseProxyHosts[i].HttpRoute[j].Path
+ httpRoute.TargetHost = types.StringValue(internalIp)
+ httpRoute.TargetPort = data.ReverseProxyHosts[i].HttpRoute[j].TargetPort
+ httpRoute.TargetVmId = types.StringValue(emptyString)
+ httpRoute.Pattern = data.ReverseProxyHosts[i].HttpRoute[j].Pattern
+ httpRoute.Schema = data.ReverseProxyHosts[i].HttpRoute[j].Schema
+ httpRoute.RequestHeaders = data.ReverseProxyHosts[i].HttpRoute[j].RequestHeaders
+ httpRoute.ResponseHeaders = data.ReverseProxyHosts[i].HttpRoute[j].ResponseHeaders
+ host.HttpRoute[j] = &httpRoute
+ }
+ }
+
+ modifiedHosts[i] = host
+ }
+
+ return modifiedHosts, resultDiagnostic
+}
diff --git a/internal/clone_vm/resource_schema.go b/internal/clone_vm/schemas/resource_schema_v0.go
similarity index 98%
rename from internal/clone_vm/resource_schema.go
rename to internal/clone_vm/schemas/resource_schema_v0.go
index e7cd317..b9a7a24 100644
--- a/internal/clone_vm/resource_schema.go
+++ b/internal/clone_vm/schemas/resource_schema_v0.go
@@ -1,4 +1,4 @@
-package clonevm
+package schemas
import (
"context"
@@ -19,7 +19,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)
-func getSchema(ctx context.Context) schema.Schema {
+func GetCloneVmSchemaV0(ctx context.Context) schema.Schema {
return schema.Schema{
// This description is used by the documentation generator and the language server.
MarkdownDescription: "Parallels Desktop Clone VM resource",
diff --git a/internal/clone_vm/schemas/resource_schema_v1.go b/internal/clone_vm/schemas/resource_schema_v1.go
new file mode 100644
index 0000000..7dd728b
--- /dev/null
+++ b/internal/clone_vm/schemas/resource_schema_v1.go
@@ -0,0 +1,123 @@
+package schemas
+
+import (
+ "context"
+
+ "terraform-provider-parallels-desktop/internal/schemas/authenticator"
+ "terraform-provider-parallels-desktop/internal/schemas/postprocessorscript"
+ "terraform-provider-parallels-desktop/internal/schemas/prlctl"
+ "terraform-provider-parallels-desktop/internal/schemas/reverseproxy"
+ "terraform-provider-parallels-desktop/internal/schemas/sharedfolder"
+ "terraform-provider-parallels-desktop/internal/schemas/vmconfig"
+ "terraform-provider-parallels-desktop/internal/schemas/vmspecs"
+
+ "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+)
+
+func GetCloneVmSchemaV1(ctx context.Context) schema.Schema {
+ return schema.Schema{
+ // This description is used by the documentation generator and the language server.
+ MarkdownDescription: "Parallels Desktop Clone VM resource",
+ Blocks: map[string]schema.Block{
+ authenticator.SchemaName: authenticator.SchemaBlock,
+ vmspecs.SchemaName: vmspecs.SchemaBlock,
+ postprocessorscript.SchemaName: postprocessorscript.SchemaBlock,
+ "on_destroy_script": postprocessorscript.SchemaBlock,
+ sharedfolder.SchemaName: sharedfolder.SchemaBlock,
+ vmconfig.SchemaName: vmconfig.SchemaBlock,
+ prlctl.SchemaName: prlctl.SchemaBlock,
+ reverseproxy.SchemaName: reverseproxy.HostBlockV0,
+ },
+ Attributes: map[string]schema.Attribute{
+ "timeouts": timeouts.Attributes(ctx, timeouts.Opts{
+ Create: true,
+ }),
+ "force_changes": schema.BoolAttribute{
+ MarkdownDescription: "Force changes, this will force the VM to be stopped and started again",
+ Optional: true,
+ },
+ "host": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps Host",
+ Optional: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ Validators: []validator.String{
+ stringvalidator.AtLeastOneOf(path.Expressions{
+ path.MatchRoot("orchestrator"),
+ path.MatchRoot("host"),
+ }...),
+ },
+ },
+ "orchestrator": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps Orchestrator",
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.AtLeastOneOf(path.Expressions{
+ path.MatchRoot("orchestrator"),
+ path.MatchRoot("host"),
+ }...),
+ },
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "id": schema.StringAttribute{
+ MarkdownDescription: "Virtual Machine Id",
+ Computed: true,
+ },
+ "os_type": schema.StringAttribute{
+ MarkdownDescription: "Virtual Machine OS type",
+ Computed: true,
+ },
+ "base_vm_id": schema.StringAttribute{
+ MarkdownDescription: "Base Virtual Machine Id to clone",
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "name": schema.StringAttribute{
+ MarkdownDescription: "Virtual Machine name to create, this needs to be unique in the host",
+ Required: true,
+ },
+ "owner": schema.StringAttribute{
+ MarkdownDescription: "Virtual Machine owner",
+ Optional: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "path": schema.StringAttribute{
+ MarkdownDescription: "Path",
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "run_after_create": schema.BoolAttribute{
+ MarkdownDescription: "Run after create, this will make the VM to run after creation",
+ Optional: true,
+ DeprecationMessage: "Use the `keep_running` attribute instead",
+ },
+ "external_ip": schema.StringAttribute{
+ MarkdownDescription: "VM external IP address",
+ Computed: true,
+ },
+ "internal_ip": schema.StringAttribute{
+ MarkdownDescription: "VM internal IP address",
+ Computed: true,
+ },
+ "keep_running": schema.BoolAttribute{
+ MarkdownDescription: "This will keep the VM running after the terraform apply",
+ Computed: true,
+ },
+ },
+ }
+}
diff --git a/internal/common/basetype_helpers.go b/internal/common/basetype_helpers.go
new file mode 100644
index 0000000..5f681c1
--- /dev/null
+++ b/internal/common/basetype_helpers.go
@@ -0,0 +1,37 @@
+package common
+
+import (
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+func IsTrue(c types.Bool) bool {
+ if c.IsNull() {
+ return false
+ }
+ if c.IsUnknown() {
+ return false
+ }
+ if c.ValueBool() {
+ return true
+ }
+ return false
+}
+
+func GetString(c types.String) string {
+ if c.IsNull() {
+ return ""
+ }
+ if c.IsUnknown() {
+ return ""
+ }
+ return c.ValueString()
+}
+
+func CopyPointer[T any](src *T) *T {
+ if src == nil {
+ return nil
+ }
+ dst := new(T)
+ *dst = *src
+ return dst
+}
diff --git a/internal/common/check_required_specs.go b/internal/common/check_required_specs.go
index e8c981f..a8e3890 100644
--- a/internal/common/check_required_specs.go
+++ b/internal/common/check_required_specs.go
@@ -74,7 +74,7 @@ func CheckIfEnoughSpecs(ctx context.Context, hostConfig apiclient.HostConfig, sp
diagnostics.AddError("error converting cpu count", err.Error())
return diagnostics
}
- if hardwareInfo.TotalAvailable.LogicalCpuCount-int64(updateCpuValueInt) <= 0 {
+ if hardwareInfo.TotalAvailable.LogicalCpuCount-int64(updateCpuValueInt) < 0 {
diagnostics.AddError("not enough cpus", "not enough cpus")
return diagnostics
}
@@ -88,7 +88,7 @@ func CheckIfEnoughSpecs(ctx context.Context, hostConfig apiclient.HostConfig, sp
diagnostics.AddError("error converting memory size", err.Error())
return diagnostics
}
- if hardwareInfo.TotalAvailable.MemorySize-float64(updateMemoryValueInt) <= 0 {
+ if hardwareInfo.TotalAvailable.MemorySize-float64(updateMemoryValueInt) < 0 {
diagnostics.AddError("not enough memory", "not enough memory")
return diagnostics
}
diff --git a/internal/common/ensure_machine_as_internal_ip.go b/internal/common/ensure_machine_as_internal_ip.go
new file mode 100644
index 0000000..9961205
--- /dev/null
+++ b/internal/common/ensure_machine_as_internal_ip.go
@@ -0,0 +1,55 @@
+package common
+
+import (
+ "context"
+ "time"
+
+ "terraform-provider-parallels-desktop/internal/apiclient"
+ "terraform-provider-parallels-desktop/internal/apiclient/apimodels"
+
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+)
+
+func EnsureMachineHasInternalIp(ctx context.Context, hostConfig apiclient.HostConfig, vm *apimodels.VirtualMachine) (*apimodels.VirtualMachine, diag.Diagnostics) {
+ diagnostics := diag.Diagnostics{}
+
+ refreshVm, ensureRunningDiag := EnsureMachineRunning(ctx, hostConfig, vm)
+ if ensureRunningDiag.HasError() {
+ diagnostics.Append(ensureRunningDiag...)
+ return nil, diagnostics
+ }
+
+ maxRetries := 10
+ retryCount := 0
+ for {
+ diagnostics = diag.Diagnostics{}
+ retryCount += 1
+ if refreshVm.InternalIpAddress == "" || refreshVm.InternalIpAddress == "-" {
+ updatedVm, checkVmDiag := apiclient.GetVm(ctx, hostConfig, refreshVm.ID)
+ if checkVmDiag.HasError() {
+ diagnostics.Append(checkVmDiag...)
+ }
+
+ // If we have the internal IP, break out of the loop
+ if updatedVm.InternalIpAddress != "" && updatedVm.InternalIpAddress != "-" {
+ tflog.Info(ctx, "Machine "+updatedVm.Name+" is running")
+ diagnostics = diag.Diagnostics{}
+ refreshVm = updatedVm
+ break
+ }
+
+ // We have run out of retries, add an error and break out of the loop
+ if retryCount >= maxRetries {
+ diagnostics.AddError("error getting vm Internal IP", "We could not get the internal IP of the machine")
+ break
+ }
+
+ time.Sleep(10 * time.Second)
+ } else {
+ break
+ }
+ }
+
+ return refreshVm, diagnostics
+}
diff --git a/internal/common/ensure_machine_running.go b/internal/common/ensure_machine_running.go
index 8aba238..d5d082f 100644
--- a/internal/common/ensure_machine_running.go
+++ b/internal/common/ensure_machine_running.go
@@ -44,12 +44,20 @@ func EnsureMachineRunning(ctx context.Context, hostConfig apiclient.HostConfig,
diagnostics.Append(checkVmDiag...)
}
- // All if good, break out of the loop
+ // The machine is running, lets check if we have the tools initialized
if updatedVm.State == "running" {
- tflog.Info(ctx, "Machine "+returnVm.Name+" is running")
- diagnostics = diag.Diagnostics{}
- returnVm = updatedVm
- break
+ echoHelloCommand := apimodels.PostScriptItem{
+ Command: "echo 'I am running'",
+ VirtualMachineId: updatedVm.ID,
+ }
+
+ // Only breaking out of the loop if the script executes successfully
+ if _, execDiag := apiclient.ExecuteScript(ctx, hostConfig, echoHelloCommand); !execDiag.HasError() {
+ tflog.Info(ctx, "Machine "+returnVm.Name+" is running")
+ diagnostics = diag.Diagnostics{}
+ returnVm = updatedVm
+ break
+ }
}
// We have run out of retries, add an error and break out of the loop
diff --git a/internal/common/ensure_machine_stopped.go b/internal/common/ensure_machine_stopped.go
index 24f0106..3e04c59 100644
--- a/internal/common/ensure_machine_stopped.go
+++ b/internal/common/ensure_machine_stopped.go
@@ -21,7 +21,7 @@ func EnsureMachineStopped(ctx context.Context, hostConfig apiclient.HostConfig,
return vm, diagnostics
}
- maxRetries := 10
+ maxRetries := 30
retryCount := 0
for {
diagnostics = diag.Diagnostics{}
diff --git a/internal/common/specs.go b/internal/common/specs.go
index 1f438e0..0964823 100644
--- a/internal/common/specs.go
+++ b/internal/common/specs.go
@@ -8,7 +8,6 @@ import (
"terraform-provider-parallels-desktop/internal/apiclient"
"terraform-provider-parallels-desktop/internal/apiclient/apimodels"
- "terraform-provider-parallels-desktop/internal/helpers"
"terraform-provider-parallels-desktop/internal/schemas/vmspecs"
"github.com/hashicorp/terraform-plugin-framework/diag"
@@ -46,15 +45,8 @@ func SpecsBlockOnUpdate(ctx context.Context, hostConfig apiclient.HostConfig, vm
changes := apimodels.NewVmConfigRequest(vm.User)
if vm.State == "running" {
- // Because this is an update we need to take into account the already existing cpu and add it to the total available
- // if the vm is running, otherwise we already added that value to the reserved resources
- hardwareInfo.TotalAvailable.LogicalCpuCount = hardwareInfo.TotalAvailable.LogicalCpuCount + vm.Hardware.CPU.Cpus
- currentMemoryUsage, err := helpers.GetSizeByteFromString(vm.Hardware.Memory.Size)
- if err != nil {
- diagnostics.AddError("error getting memory size", err.Error())
- return diagnostics
- }
- hardwareInfo.TotalAvailable.MemorySize = hardwareInfo.TotalAvailable.MemorySize + helpers.ConvertByteToMegabyte(currentMemoryUsage)
+ diagnostics.AddError("cannot update vm", "vm is running")
+ return diagnostics
}
if planSpecs.CpuCount.ValueString() != fmt.Sprintf("%v", vm.Hardware.CPU.Cpus) {
diff --git a/internal/deploy/api_config_schema.go b/internal/deploy/api_config_schema.go
deleted file mode 100644
index 6b7b82c..0000000
--- a/internal/deploy/api_config_schema.go
+++ /dev/null
@@ -1,232 +0,0 @@
-package deploy
-
-import (
- "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
- "github.com/hashicorp/terraform-plugin-framework/resource/schema"
- "github.com/hashicorp/terraform-plugin-framework/schema/validator"
- "github.com/hashicorp/terraform-plugin-framework/types"
-)
-
-var (
- ApiConfigSchemaName = "api_config"
- apiConfigSchemaBlockV0 = schema.SingleNestedBlock{
- MarkdownDescription: "Parallels Desktop DevOps configuration",
- Description: "Parallels Desktop DevOps configuration",
- Attributes: map[string]schema.Attribute{
- "port": schema.StringAttribute{
- MarkdownDescription: "Parallels Desktop DevOps port",
- Description: "Parallels Desktop DevOps port",
- Optional: true,
- },
- "prefix": schema.StringAttribute{
- MarkdownDescription: "Parallels Desktop DevOps port",
- Description: "Parallels Desktop DevOps port",
- Optional: true,
- },
- "devops_version": schema.StringAttribute{
- MarkdownDescription: "Parallels Desktop DevOps version to install, if empty the latest will be installed",
- Description: "Parallels Desktop DevOps version to install, if empty the latest will be installed",
- Optional: true,
- },
- "root_password": schema.StringAttribute{
- MarkdownDescription: "Parallels Desktop DevOps root password",
- Description: "Parallels Desktop DevOps root password",
- Optional: true,
- Sensitive: true,
- },
- "hmac_secret": schema.StringAttribute{
- MarkdownDescription: "Parallels Desktop DevOps HMAC secret, this is used to sign the JWT tokens",
- Description: "Parallels Desktop DevOps HMAC secret, this is used to sign the JWT tokens",
- Optional: true,
- Sensitive: true,
- },
- "encryption_rsa_key": schema.StringAttribute{
- MarkdownDescription: "Parallels Desktop DevOps RSA key, this is used to encrypt database file on rest",
- Description: "Parallels Desktop DevOps RSA key, this is used to encrypt database file on rest",
- Optional: true,
- Sensitive: true,
- },
- "log_level": schema.StringAttribute{
- MarkdownDescription: "Parallels Desktop DevOps log level, you can choose between debug, info, warn, error",
- Description: "Parallels Desktop DevOps log level, you can choose between debug, info, warn, error",
- Optional: true,
- Validators: []validator.String{
- stringvalidator.OneOf("debug", "info", "warn", "error"),
- },
- },
- "enable_tls": schema.BoolAttribute{
- MarkdownDescription: "Parallels Desktop DevOps enable TLS",
- Description: "Parallels Desktop DevOps enable TLS",
- Optional: true,
- },
- "tls_port": schema.StringAttribute{
- MarkdownDescription: "Parallels Desktop DevOps TLS port",
- Description: "Parallels Desktop DevOps TLS port",
- Optional: true,
- },
- "tls_certificate": schema.StringAttribute{
- MarkdownDescription: "Parallels Desktop DevOps TLS certificate, this should be a PEM base64 encoded certificate string",
- Description: "Parallels Desktop DevOps TLS certificate, this should be a PEM base64 encoded certificate string",
- Optional: true,
- Sensitive: true,
- },
- "tls_private_key": schema.StringAttribute{
- MarkdownDescription: "Parallels Desktop DevOps TLS private key, this should be a PEM base64 encoded private key string",
- Description: "Parallels Desktop DevOps TLS private key, this should be a PEM base64 encoded private key string",
- Optional: true,
- Sensitive: true,
- },
- "disable_catalog_caching": schema.BoolAttribute{
- MarkdownDescription: "Disable catalog caching, this will disable the ability to cache catalog items that are pulled from a remote catalog",
- Description: "Disable catalog caching, this will disable the ability to cache catalog items that are pulled from a remote catalog",
- Optional: true,
- },
- "token_duration_minutes": schema.StringAttribute{
- MarkdownDescription: "JWT Token duration in minutes",
- Description: "JWT Token duration in minutes",
- Optional: true,
- },
- "mode": schema.StringAttribute{
- MarkdownDescription: "API Operation mode, either orchestrator or catalog",
- Optional: true,
- Sensitive: true,
- Validators: []validator.String{
- stringvalidator.OneOf("orchestrator", "catalog", "api"),
- },
- },
- "use_orchestrator_resources": schema.BoolAttribute{
- MarkdownDescription: "Use orchestrator resources",
- Optional: true,
- },
- "system_reserved_memory": schema.StringAttribute{
- MarkdownDescription: "System reserved memory in MB",
- Optional: true,
- },
- "system_reserved_cpu": schema.StringAttribute{
- MarkdownDescription: "System reserved CPU in %",
- Optional: true,
- },
- "system_reserved_disk": schema.StringAttribute{
- MarkdownDescription: "System reserved disk in MB",
- Optional: true,
- },
- "enable_logging": schema.BoolAttribute{
- MarkdownDescription: "Enable logging",
- Optional: true,
- },
- },
- }
-
- apiConfigSchemaBlockV1 = schema.SingleNestedBlock{
- MarkdownDescription: "Parallels Desktop DevOps configuration",
- Description: "Parallels Desktop DevOps configuration",
- Attributes: map[string]schema.Attribute{
- "port": schema.StringAttribute{
- MarkdownDescription: "Parallels Desktop DevOps port",
- Description: "Parallels Desktop DevOps port",
- Optional: true,
- },
- "prefix": schema.StringAttribute{
- MarkdownDescription: "Parallels Desktop DevOps port",
- Description: "Parallels Desktop DevOps port",
- Optional: true,
- },
- "devops_version": schema.StringAttribute{
- MarkdownDescription: "Parallels Desktop DevOps version to install, if empty the latest will be installed",
- Description: "Parallels Desktop DevOps version to install, if empty the latest will be installed",
- Optional: true,
- },
- "root_password": schema.StringAttribute{
- MarkdownDescription: "Parallels Desktop DevOps root password",
- Description: "Parallels Desktop DevOps root password",
- Optional: true,
- Sensitive: true,
- },
- "hmac_secret": schema.StringAttribute{
- MarkdownDescription: "Parallels Desktop DevOps HMAC secret, this is used to sign the JWT tokens",
- Description: "Parallels Desktop DevOps HMAC secret, this is used to sign the JWT tokens",
- Optional: true,
- Sensitive: true,
- },
- "encryption_rsa_key": schema.StringAttribute{
- MarkdownDescription: "Parallels Desktop DevOps RSA key, this is used to encrypt database file on rest",
- Description: "Parallels Desktop DevOps RSA key, this is used to encrypt database file on rest",
- Optional: true,
- Sensitive: true,
- },
- "log_level": schema.StringAttribute{
- MarkdownDescription: "Parallels Desktop DevOps log level, you can choose between debug, info, warn, error",
- Description: "Parallels Desktop DevOps log level, you can choose between debug, info, warn, error",
- Optional: true,
- Validators: []validator.String{
- stringvalidator.OneOf("debug", "info", "warn", "error"),
- },
- },
- "enable_tls": schema.BoolAttribute{
- MarkdownDescription: "Parallels Desktop DevOps enable TLS",
- Description: "Parallels Desktop DevOps enable TLS",
- Optional: true,
- },
- "tls_port": schema.StringAttribute{
- MarkdownDescription: "Parallels Desktop DevOps TLS port",
- Description: "Parallels Desktop DevOps TLS port",
- Optional: true,
- },
- "tls_certificate": schema.StringAttribute{
- MarkdownDescription: "Parallels Desktop DevOps TLS certificate, this should be a PEM base64 encoded certificate string",
- Description: "Parallels Desktop DevOps TLS certificate, this should be a PEM base64 encoded certificate string",
- Optional: true,
- Sensitive: true,
- },
- "tls_private_key": schema.StringAttribute{
- MarkdownDescription: "Parallels Desktop DevOps TLS private key, this should be a PEM base64 encoded private key string",
- Description: "Parallels Desktop DevOps TLS private key, this should be a PEM base64 encoded private key string",
- Optional: true,
- Sensitive: true,
- },
- "disable_catalog_caching": schema.BoolAttribute{
- MarkdownDescription: "Disable catalog caching, this will disable the ability to cache catalog items that are pulled from a remote catalog",
- Description: "Disable catalog caching, this will disable the ability to cache catalog items that are pulled from a remote catalog",
- Optional: true,
- },
- "token_duration_minutes": schema.StringAttribute{
- MarkdownDescription: "JWT Token duration in minutes",
- Description: "JWT Token duration in minutes",
- Optional: true,
- },
- "mode": schema.StringAttribute{
- MarkdownDescription: "API Operation mode, either orchestrator or catalog",
- Optional: true,
- Sensitive: true,
- Validators: []validator.String{
- stringvalidator.OneOf("orchestrator", "catalog", "api"),
- },
- },
- "use_orchestrator_resources": schema.BoolAttribute{
- MarkdownDescription: "Use orchestrator resources",
- Optional: true,
- },
- "system_reserved_memory": schema.StringAttribute{
- MarkdownDescription: "System reserved memory in MB",
- Optional: true,
- },
- "system_reserved_cpu": schema.StringAttribute{
- MarkdownDescription: "System reserved CPU in %",
- Optional: true,
- },
- "system_reserved_disk": schema.StringAttribute{
- MarkdownDescription: "System reserved disk in MB",
- Optional: true,
- },
- "enable_logging": schema.BoolAttribute{
- MarkdownDescription: "Enable logging",
- Optional: true,
- },
- "environment_variables": schema.MapAttribute{
- MarkdownDescription: "Environment variables that can be used in the DevOps service, please see documentation to see which variables are available",
- Optional: true,
- ElementType: types.StringType,
- },
- },
- }
-)
diff --git a/internal/deploy/devops_service.go b/internal/deploy/devops_service.go
index 293e324..7f71f59 100644
--- a/internal/deploy/devops_service.go
+++ b/internal/deploy/devops_service.go
@@ -9,6 +9,7 @@ import (
"strings"
"terraform-provider-parallels-desktop/internal/clientmodels"
+ "terraform-provider-parallels-desktop/internal/deploy/models"
"terraform-provider-parallels-desktop/internal/interfaces"
"terraform-provider-parallels-desktop/internal/localclient"
@@ -350,7 +351,7 @@ func (c *DevOpsServiceClient) UninstallParallelsDesktop() error {
return nil
}
-func (c *DevOpsServiceClient) GetLicense() (*ParallelsDesktopLicense, error) {
+func (c *DevOpsServiceClient) GetLicense() (*models.ParallelsDesktopLicense, error) {
cmd := c.findPath("prlsrvctl")
arguments := []string{"info", "--json"}
output, err := c.client.RunCommand(cmd, arguments)
@@ -368,7 +369,7 @@ func (c *DevOpsServiceClient) GetLicense() (*ParallelsDesktopLicense, error) {
return nil, err
}
- parallelsLicense := ParallelsDesktopLicense{}
+ parallelsLicense := models.ParallelsDesktopLicense{}
parallelsLicense.FromClientModel(parallelsInfo.License)
return ¶llelsLicense, nil
}
@@ -447,16 +448,19 @@ func (c *DevOpsServiceClient) CompareLicenses(license string) (bool, error) {
return false, nil
}
-func (c *DevOpsServiceClient) InstallDevOpsService(license string, config ParallelsDesktopDevopsConfigV1) (string, error) {
+func (c *DevOpsServiceClient) InstallDevOpsService(license string, config models.ParallelsDesktopDevopsConfigV2) (string, error) {
// Installing DevOps Service
devopsPath := c.findPath("prldevops")
if devopsPath == "" {
cmd := "/bin/bash"
arguments := []string{"-c", "\"$(curl -fsSL https://raw.githubusercontent.com/Parallels/prl-devops-service/main/scripts/install.sh)\"", "-", "--no-service"}
- if config.DevOpsVersion.ValueString() != "" && config.DevOpsVersion.ValueString() != "latest" {
+ if config.DevOpsVersion.ValueString() != "" && config.DevOpsVersion.ValueString() != "latest" && !config.UseLatestBeta.ValueBool() {
arguments = append(arguments, "--version", config.DevOpsVersion.ValueString())
}
+ if config.UseLatestBeta.ValueBool() {
+ arguments = append(arguments, "--pre-release")
+ }
_, err := c.client.RunCommand(cmd, arguments)
if err != nil {
return "", errors.New("Error running devops install command, error: " + err.Error())
@@ -483,6 +487,11 @@ func (c *DevOpsServiceClient) InstallDevOpsService(license string, config Parall
configFile.EnvironmentVariables[key] = envVar.ValueString()
}
+ // Setting the environment variables for the prldevops service port forwarding
+ if config.EnablePortForwarding.ValueBool() {
+ configFile.EnvironmentVariables["ENABLE_REVERSE_PROXY"] = "true"
+ }
+
yamlConfig, err := yaml.Marshal(configFile)
if err != nil {
return "", err
@@ -634,7 +643,7 @@ func (c *DevOpsServiceClient) GenerateDefaultRootPassword() (string, error) {
return encoded, nil
}
-func (c *DevOpsServiceClient) generateConfigFile(config ParallelsDesktopDevopsConfigV1) (string, error) {
+func (c *DevOpsServiceClient) generateConfigFile(config models.ParallelsDesktopDevopsConfigV2) (string, error) {
configPath := "/tmp/service_config.json"
configMap := make(map[string]interface{})
if config.Port.ValueString() != "" {
diff --git a/internal/deploy/models/common.go b/internal/deploy/models/common.go
new file mode 100644
index 0000000..9a32f59
--- /dev/null
+++ b/internal/deploy/models/common.go
@@ -0,0 +1,178 @@
+package models
+
+import (
+ "context"
+
+ "terraform-provider-parallels-desktop/internal/clientmodels"
+
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
+)
+
+type DeployResourceSshConnection struct {
+ Host types.String `tfsdk:"host"`
+ HostPort types.String `tfsdk:"host_port"`
+ User types.String `tfsdk:"user"`
+ Password types.String `tfsdk:"password"`
+ PrivateKey types.String `tfsdk:"private_key"`
+}
+
+type ParallelsDesktopLicense struct {
+ State types.String `tfsdk:"state"`
+ Key types.String `tfsdk:"key"`
+ Restricted types.Bool `tfsdk:"restricted"`
+}
+
+func (p *ParallelsDesktopLicense) FromClientModel(value clientmodels.ParallelsServerLicense) {
+ p.State = types.StringValue(value.State)
+ p.Key = types.StringValue(value.Key)
+ p.Restricted = types.BoolValue(value.Restricted == "true")
+}
+
+func (p *ParallelsDesktopLicense) MapObject() basetypes.ObjectValue {
+ attributeTypes := make(map[string]attr.Type)
+ attributeTypes["state"] = types.StringType
+ attributeTypes["key"] = types.StringType
+ attributeTypes["restricted"] = types.BoolType
+
+ attrs := map[string]attr.Value{}
+ attrs["state"] = p.State
+ attrs["key"] = p.Key
+ attrs["restricted"] = p.Restricted
+
+ return types.ObjectValueMust(attributeTypes, attrs)
+}
+
+type ParallelsDesktopDevOps struct {
+ Version types.String `tfsdk:"version"`
+ Protocol types.String `tfsdk:"protocol"`
+ Host types.String `tfsdk:"host"`
+ Port types.String `tfsdk:"port"`
+ User types.String `tfsdk:"user"`
+ Password types.String `tfsdk:"password"`
+}
+
+func (p *ParallelsDesktopDevOps) MapObject() basetypes.ObjectValue {
+ attributeTypes := make(map[string]attr.Type)
+ attributeTypes["version"] = types.StringType
+ attributeTypes["protocol"] = types.StringType
+ attributeTypes["host"] = types.StringType
+ attributeTypes["port"] = types.StringType
+ attributeTypes["user"] = types.StringType
+ attributeTypes["password"] = types.StringType
+
+ attrs := map[string]attr.Value{}
+ attrs["version"] = p.Version
+ attrs["protocol"] = p.Protocol
+ attrs["host"] = p.Host
+ attrs["port"] = p.Port
+ attrs["user"] = p.User
+ attrs["password"] = p.Password
+
+ return types.ObjectValueMust(attributeTypes, attrs)
+}
+
+func ApiConfigHasChanges(context context.Context, planState, currentState *ParallelsDesktopDevopsConfigV2) bool {
+ if planState == nil && currentState == nil {
+ return false
+ }
+
+ if planState != nil && currentState == nil {
+ return true
+ }
+
+ if planState == nil && currentState != nil {
+ return true
+ }
+
+ if planState.Port != currentState.Port {
+ return true
+ }
+
+ if planState.Prefix != currentState.Prefix {
+ return true
+ }
+
+ if planState.DevOpsVersion != currentState.DevOpsVersion {
+ return true
+ }
+
+ if planState.RootPassword != currentState.RootPassword {
+ return true
+ }
+
+ if planState.HmacSecret != currentState.HmacSecret {
+ return true
+ }
+
+ if planState.EncryptionRsaKey != currentState.EncryptionRsaKey {
+ return true
+ }
+
+ if planState.LogLevel != currentState.LogLevel {
+ return true
+ }
+
+ if planState.EnableTLS != currentState.EnableTLS {
+ return true
+ }
+
+ if planState.TLSPort != currentState.TLSPort {
+ return true
+ }
+
+ if planState.TLSCertificate != currentState.TLSCertificate {
+ return true
+ }
+
+ if planState.TLSPrivateKey != currentState.TLSPrivateKey {
+ return true
+ }
+
+ if planState.DisableCatalogCaching != currentState.DisableCatalogCaching {
+ return true
+ }
+
+ if planState.TokenDurationMinutes != currentState.TokenDurationMinutes {
+ return true
+ }
+
+ if planState.Mode != currentState.Mode {
+ return true
+ }
+
+ if planState.UseOrchestratorResources != currentState.UseOrchestratorResources {
+ return true
+ }
+
+ if planState.SystemReservedMemory != currentState.SystemReservedMemory {
+ return true
+ }
+
+ if planState.SystemReservedCpu != currentState.SystemReservedCpu {
+ return true
+ }
+
+ if planState.SystemReservedDisk != currentState.SystemReservedDisk {
+ return true
+ }
+
+ if planState.EnableLogging != currentState.EnableLogging {
+ return true
+ }
+
+ if len(planState.EnvironmentVariables) != len(currentState.EnvironmentVariables) {
+ return true
+ }
+
+ if len(planState.EnvironmentVariables) != 0 {
+ for k, v := range planState.EnvironmentVariables {
+ if currentState.EnvironmentVariables[k] != v {
+ return true
+ }
+ }
+ }
+
+ return false
+}
diff --git a/internal/deploy/models/resource_models_v0.go b/internal/deploy/models/resource_models_v0.go
new file mode 100644
index 0000000..3058f3f
--- /dev/null
+++ b/internal/deploy/models/resource_models_v0.go
@@ -0,0 +1,45 @@
+package models
+
+import (
+ "terraform-provider-parallels-desktop/internal/schemas/orchestrator"
+
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+// DeployResourceModel describes the resource data model.
+
+type DeployResourceModelV0 struct {
+ SshConnection *DeployResourceSshConnection `tfsdk:"ssh_connection"`
+ CurrentVersion types.String `tfsdk:"current_version"`
+ CurrentPackerVersion types.String `tfsdk:"current_packer_version"`
+ CurrentVagrantVersion types.String `tfsdk:"current_vagrant_version"`
+ CurrentGitVersion types.String `tfsdk:"current_git_version"`
+ License types.Object `tfsdk:"license"`
+ Orchestrator *orchestrator.OrchestratorRegistration `tfsdk:"orchestrator_registration"`
+ ApiConfig *ParallelsDesktopDevopsConfigV0 `tfsdk:"api_config"`
+ Api types.Object `tfsdk:"api"`
+ InstalledDependencies types.List `tfsdk:"installed_dependencies"`
+ InstallLocal types.Bool `tfsdk:"install_local"`
+}
+
+type ParallelsDesktopDevopsConfigV0 struct {
+ Port types.String `tfsdk:"port" json:"port,omitempty"`
+ Prefix types.String `tfsdk:"prefix" json:"prefix,omitempty"`
+ DevOpsVersion types.String `tfsdk:"devops_version" json:"devops_version,omitempty"`
+ RootPassword types.String `tfsdk:"root_password" json:"root_password,omitempty"`
+ HmacSecret types.String `tfsdk:"hmac_secret" json:"hmac_secret,omitempty"`
+ EncryptionRsaKey types.String `tfsdk:"encryption_rsa_key" json:"encryption_rsa_key,omitempty"`
+ LogLevel types.String `tfsdk:"log_level" json:"log_level,omitempty"`
+ EnableTLS types.Bool `tfsdk:"enable_tls" json:"enable_tls,omitempty"`
+ TLSPort types.String `tfsdk:"tls_port" json:"tls_port,omitempty"`
+ TLSCertificate types.String `tfsdk:"tls_certificate" json:"tls_certificate,omitempty"`
+ TLSPrivateKey types.String `tfsdk:"tls_private_key" json:"tls_private_key,omitempty"`
+ DisableCatalogCaching types.Bool `tfsdk:"disable_catalog_caching" json:"disable_catalog_caching,omitempty"`
+ TokenDurationMinutes types.String `tfsdk:"token_duration_minutes" json:"token_duration_minutes,omitempty"`
+ Mode types.String `tfsdk:"mode" json:"mode,omitempty"`
+ UseOrchestratorResources types.Bool `tfsdk:"use_orchestrator_resources"`
+ SystemReservedMemory types.String `tfsdk:"system_reserved_memory"`
+ SystemReservedCpu types.String `tfsdk:"system_reserved_cpu"`
+ SystemReservedDisk types.String `tfsdk:"system_reserved_disk"`
+ EnableLogging types.Bool `tfsdk:"enable_logging"`
+}
diff --git a/internal/deploy/models/resource_models_v1.go b/internal/deploy/models/resource_models_v1.go
new file mode 100644
index 0000000..2c7e06d
--- /dev/null
+++ b/internal/deploy/models/resource_models_v1.go
@@ -0,0 +1,99 @@
+package models
+
+import (
+ "terraform-provider-parallels-desktop/internal/schemas/orchestrator"
+
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
+)
+
+// DeployResourceModel describes the resource data model.
+
+type DeployResourceModelV1 struct {
+ SshConnection *DeployResourceSshConnection `tfsdk:"ssh_connection"`
+ CurrentVersion types.String `tfsdk:"current_version"`
+ CurrentPackerVersion types.String `tfsdk:"current_packer_version"`
+ CurrentVagrantVersion types.String `tfsdk:"current_vagrant_version"`
+ CurrentGitVersion types.String `tfsdk:"current_git_version"`
+ License types.Object `tfsdk:"license"`
+ Orchestrator *orchestrator.OrchestratorRegistration `tfsdk:"orchestrator_registration"`
+ ApiConfig *ParallelsDesktopDevopsConfigV1 `tfsdk:"api_config"`
+ Api types.Object `tfsdk:"api"`
+ InstalledDependencies types.List `tfsdk:"installed_dependencies"`
+ InstallLocal types.Bool `tfsdk:"install_local"`
+}
+
+type ParallelsDesktopDevopsConfigV1 struct {
+ Port types.String `tfsdk:"port" json:"port,omitempty"`
+ Prefix types.String `tfsdk:"prefix" json:"prefix,omitempty"`
+ DevOpsVersion types.String `tfsdk:"devops_version" json:"devops_version,omitempty"`
+ RootPassword types.String `tfsdk:"root_password" json:"root_password,omitempty"`
+ HmacSecret types.String `tfsdk:"hmac_secret" json:"hmac_secret,omitempty"`
+ EncryptionRsaKey types.String `tfsdk:"encryption_rsa_key" json:"encryption_rsa_key,omitempty"`
+ LogLevel types.String `tfsdk:"log_level" json:"log_level,omitempty"`
+ EnableTLS types.Bool `tfsdk:"enable_tls" json:"enable_tls,omitempty"`
+ TLSPort types.String `tfsdk:"tls_port" json:"tls_port,omitempty"`
+ TLSCertificate types.String `tfsdk:"tls_certificate" json:"tls_certificate,omitempty"`
+ TLSPrivateKey types.String `tfsdk:"tls_private_key" json:"tls_private_key,omitempty"`
+ DisableCatalogCaching types.Bool `tfsdk:"disable_catalog_caching" json:"disable_catalog_caching,omitempty"`
+ TokenDurationMinutes types.String `tfsdk:"token_duration_minutes" json:"token_duration_minutes,omitempty"`
+ Mode types.String `tfsdk:"mode" json:"mode,omitempty"`
+ UseOrchestratorResources types.Bool `tfsdk:"use_orchestrator_resources"`
+ SystemReservedMemory types.String `tfsdk:"system_reserved_memory"`
+ SystemReservedCpu types.String `tfsdk:"system_reserved_cpu"`
+ SystemReservedDisk types.String `tfsdk:"system_reserved_disk"`
+ EnableLogging types.Bool `tfsdk:"enable_logging"`
+ EnvironmentVariables map[string]types.String `tfsdk:"environment_variables"`
+}
+
+func (p *ParallelsDesktopDevopsConfigV1) MapObject() basetypes.ObjectValue {
+ attributeTypes := make(map[string]attr.Type)
+ attributeTypes["port"] = types.StringType
+ attributeTypes["devops_version"] = types.StringType
+ attributeTypes["root_password"] = types.StringType
+ attributeTypes["hmac_secret"] = types.StringType
+ attributeTypes["encryption_rsa_key"] = types.StringType
+ attributeTypes["log_level"] = types.StringType
+ attributeTypes["enable_tls"] = types.BoolType
+ attributeTypes["tls_port"] = types.StringType
+ attributeTypes["tls_certificate"] = types.StringType
+ attributeTypes["tls_private_key"] = types.StringType
+ attributeTypes["disable_catalog_caching"] = types.BoolType
+ attributeTypes["token_duration_minutes"] = types.StringType
+ attributeTypes["mode"] = types.StringType
+ attributeTypes["use_orchestrator_resources"] = types.BoolType
+ attributeTypes["system_reserved_memory"] = types.StringType
+ attributeTypes["system_reserved_cpu"] = types.StringType
+ attributeTypes["system_reserved_disk"] = types.StringType
+ attributeTypes["enable_logging"] = types.BoolType
+ attributeTypes["environment_variables"] = types.MapType{}
+
+ attrs := map[string]attr.Value{}
+ attrs["api_port"] = p.Port
+ attrs["devops_version"] = p.DevOpsVersion
+ attrs["root_password"] = p.RootPassword
+ attrs["hmac_secret"] = p.HmacSecret
+ attrs["encryption_rsa_key"] = p.EncryptionRsaKey
+ attrs["log_level"] = p.LogLevel
+ attrs["enable_tls"] = p.EnableTLS
+ attrs["host_tls_port"] = p.TLSPort
+ attrs["tls_certificate"] = p.TLSCertificate
+ attrs["tls_private_key"] = p.TLSPrivateKey
+ attrs["disable_catalog_caching"] = p.DisableCatalogCaching
+ attrs["token_duration_minutes"] = p.TokenDurationMinutes
+ attrs["mode"] = p.Mode
+ attrs["use_orchestrator_resources"] = p.UseOrchestratorResources
+ attrs["system_reserved_memory"] = p.SystemReservedMemory
+ attrs["system_reserved_cpu"] = p.SystemReservedCpu
+ attrs["system_reserved_disk"] = p.SystemReservedDisk
+ attrs["enable_logging"] = p.EnableLogging
+
+ envVars := make(map[string]attr.Value)
+ for k, v := range p.EnvironmentVariables {
+ envVars[k] = v
+ }
+ attrs["environment_variables"] = types.MapValueMust(types.StringType, envVars)
+
+ return types.ObjectValueMust(attributeTypes, attrs)
+}
diff --git a/internal/deploy/models/resource_models_v2.go b/internal/deploy/models/resource_models_v2.go
new file mode 100644
index 0000000..85cf8f5
--- /dev/null
+++ b/internal/deploy/models/resource_models_v2.go
@@ -0,0 +1,142 @@
+package models
+
+import (
+ "strings"
+
+ "terraform-provider-parallels-desktop/internal/apiclient"
+ "terraform-provider-parallels-desktop/internal/models"
+ "terraform-provider-parallels-desktop/internal/schemas/authenticator"
+ "terraform-provider-parallels-desktop/internal/schemas/orchestrator"
+ "terraform-provider-parallels-desktop/internal/schemas/reverseproxy"
+
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
+)
+
+// DeployResourceModel describes the resource data model.
+
+type DeployResourceModelV2 struct {
+ SshConnection *DeployResourceSshConnection `tfsdk:"ssh_connection"`
+ CurrentVersion types.String `tfsdk:"current_version"`
+ CurrentPackerVersion types.String `tfsdk:"current_packer_version"`
+ CurrentVagrantVersion types.String `tfsdk:"current_vagrant_version"`
+ CurrentGitVersion types.String `tfsdk:"current_git_version"`
+ ExternalIp types.String `tfsdk:"external_ip"`
+ License types.Object `tfsdk:"license"`
+ Orchestrator *orchestrator.OrchestratorRegistration `tfsdk:"orchestrator_registration"`
+ ReverseProxyHosts []*reverseproxy.ReverseProxyHost `tfsdk:"reverse_proxy_host"`
+ ApiConfig *ParallelsDesktopDevopsConfigV2 `tfsdk:"api_config"`
+ Api types.Object `tfsdk:"api"`
+ InstalledDependencies types.List `tfsdk:"installed_dependencies"`
+ InstallLocal types.Bool `tfsdk:"install_local"`
+ IsRegisteredInOrchestrator types.Bool `tfsdk:"is_registered_in_orchestrator"`
+ OrchestratorHost types.String `tfsdk:"orchestrator_host"`
+ OrchestratorHostId types.String `tfsdk:"orchestrator_host_id"`
+}
+
+type ParallelsDesktopDevopsConfigV2 struct {
+ Port types.String `tfsdk:"port" json:"port,omitempty"`
+ Prefix types.String `tfsdk:"prefix" json:"prefix,omitempty"`
+ DevOpsVersion types.String `tfsdk:"devops_version" json:"devops_version,omitempty"`
+ RootPassword types.String `tfsdk:"root_password" json:"root_password,omitempty"`
+ HmacSecret types.String `tfsdk:"hmac_secret" json:"hmac_secret,omitempty"`
+ EncryptionRsaKey types.String `tfsdk:"encryption_rsa_key" json:"encryption_rsa_key,omitempty"`
+ LogLevel types.String `tfsdk:"log_level" json:"log_level,omitempty"`
+ EnableTLS types.Bool `tfsdk:"enable_tls" json:"enable_tls,omitempty"`
+ TLSPort types.String `tfsdk:"tls_port" json:"tls_port,omitempty"`
+ TLSCertificate types.String `tfsdk:"tls_certificate" json:"tls_certificate,omitempty"`
+ TLSPrivateKey types.String `tfsdk:"tls_private_key" json:"tls_private_key,omitempty"`
+ DisableCatalogCaching types.Bool `tfsdk:"disable_catalog_caching" json:"disable_catalog_caching,omitempty"`
+ TokenDurationMinutes types.String `tfsdk:"token_duration_minutes" json:"token_duration_minutes,omitempty"`
+ Mode types.String `tfsdk:"mode" json:"mode,omitempty"`
+ UseOrchestratorResources types.Bool `tfsdk:"use_orchestrator_resources"`
+ SystemReservedMemory types.String `tfsdk:"system_reserved_memory"`
+ SystemReservedCpu types.String `tfsdk:"system_reserved_cpu"`
+ SystemReservedDisk types.String `tfsdk:"system_reserved_disk"`
+ EnableLogging types.Bool `tfsdk:"enable_logging"`
+ EnablePortForwarding types.Bool `tfsdk:"enable_port_forwarding"`
+ UseLatestBeta types.Bool `tfsdk:"use_latest_beta"`
+ EnvironmentVariables map[string]types.String `tfsdk:"environment_variables"`
+}
+
+func (p *ParallelsDesktopDevopsConfigV2) MapObject() basetypes.ObjectValue {
+ attributeTypes := make(map[string]attr.Type)
+ attributeTypes["port"] = types.StringType
+ attributeTypes["devops_version"] = types.StringType
+ attributeTypes["root_password"] = types.StringType
+ attributeTypes["hmac_secret"] = types.StringType
+ attributeTypes["encryption_rsa_key"] = types.StringType
+ attributeTypes["log_level"] = types.StringType
+ attributeTypes["enable_tls"] = types.BoolType
+ attributeTypes["tls_port"] = types.StringType
+ attributeTypes["tls_certificate"] = types.StringType
+ attributeTypes["tls_private_key"] = types.StringType
+ attributeTypes["disable_catalog_caching"] = types.BoolType
+ attributeTypes["token_duration_minutes"] = types.StringType
+ attributeTypes["mode"] = types.StringType
+ attributeTypes["use_orchestrator_resources"] = types.BoolType
+ attributeTypes["system_reserved_memory"] = types.StringType
+ attributeTypes["system_reserved_cpu"] = types.StringType
+ attributeTypes["system_reserved_disk"] = types.StringType
+ attributeTypes["enable_logging"] = types.BoolType
+ attributeTypes["enable_port_forwarding"] = types.BoolType
+ attributeTypes["use_latest_beta"] = types.BoolType
+ attributeTypes["environment_variables"] = types.MapType{}
+
+ attrs := map[string]attr.Value{}
+ attrs["api_port"] = p.Port
+ attrs["devops_version"] = p.DevOpsVersion
+ attrs["root_password"] = p.RootPassword
+ attrs["hmac_secret"] = p.HmacSecret
+ attrs["encryption_rsa_key"] = p.EncryptionRsaKey
+ attrs["log_level"] = p.LogLevel
+ attrs["enable_tls"] = p.EnableTLS
+ attrs["host_tls_port"] = p.TLSPort
+ attrs["tls_certificate"] = p.TLSCertificate
+ attrs["tls_private_key"] = p.TLSPrivateKey
+ attrs["disable_catalog_caching"] = p.DisableCatalogCaching
+ attrs["token_duration_minutes"] = p.TokenDurationMinutes
+ attrs["mode"] = p.Mode
+ attrs["use_orchestrator_resources"] = p.UseOrchestratorResources
+ attrs["system_reserved_memory"] = p.SystemReservedMemory
+ attrs["system_reserved_cpu"] = p.SystemReservedCpu
+ attrs["system_reserved_disk"] = p.SystemReservedDisk
+ attrs["enable_logging"] = p.EnableLogging
+ attrs["enable_port_forwarding"] = p.EnablePortForwarding
+ attrs["use_latest_beta"] = p.UseLatestBeta
+
+ envVars := make(map[string]attr.Value)
+ for k, v := range p.EnvironmentVariables {
+ envVars[k] = v
+ }
+ attrs["environment_variables"] = types.MapValueMust(types.StringType, envVars)
+
+ return types.ObjectValueMust(attributeTypes, attrs)
+}
+
+func (o *DeployResourceModelV2) GenerateApiHostConfig(provider *models.ParallelsProviderModel) apiclient.HostConfig {
+ hostConfig := apiclient.HostConfig{
+ IsOrchestrator: false,
+ Host: strings.ReplaceAll(o.SshConnection.Host.String(), "\"", ""),
+ License: provider.License.ValueString(),
+ Authorization: &authenticator.Authentication{
+ Username: types.StringValue(strings.ReplaceAll(o.Api.Attributes()["user"].String(), "\"", "")),
+ Password: types.StringValue(strings.ReplaceAll(o.Api.Attributes()["password"].String(), "\"", "")),
+ },
+
+ DisableTlsValidation: provider.DisableTlsValidation.ValueBool(),
+ }
+ api_port := strings.ReplaceAll(o.ApiConfig.Port.ValueString(), "\"", "")
+ api_schema := "http"
+
+ if api_port != "" {
+ hostConfig.Host = hostConfig.Host + ":" + api_port
+ }
+ if o.ApiConfig.EnableTLS.ValueBool() {
+ api_schema = "https"
+ }
+ hostConfig.Host = api_schema + "://" + hostConfig.Host
+
+ return hostConfig
+}
diff --git a/internal/deploy/resource.go b/internal/deploy/resource.go
index 770cc11..554d138 100644
--- a/internal/deploy/resource.go
+++ b/internal/deploy/resource.go
@@ -6,11 +6,15 @@ import (
"fmt"
"strings"
+ "terraform-provider-parallels-desktop/internal/common"
+ deploy_models "terraform-provider-parallels-desktop/internal/deploy/models"
+ "terraform-provider-parallels-desktop/internal/deploy/schemas"
"terraform-provider-parallels-desktop/internal/interfaces"
"terraform-provider-parallels-desktop/internal/localclient"
"terraform-provider-parallels-desktop/internal/models"
"terraform-provider-parallels-desktop/internal/schemas/authenticator"
"terraform-provider-parallels-desktop/internal/schemas/orchestrator"
+ "terraform-provider-parallels-desktop/internal/schemas/reverseproxy"
"terraform-provider-parallels-desktop/internal/ssh"
"terraform-provider-parallels-desktop/internal/telemetry"
@@ -44,7 +48,7 @@ func (r *DeployResource) Metadata(ctx context.Context, req resource.MetadataRequ
}
func (r *DeployResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
- resp.Schema = deployResourceSchemaV1
+ resp.Schema = schemas.DeployResourceSchemaV2
}
func (r *DeployResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
@@ -67,7 +71,7 @@ func (r *DeployResource) Configure(ctx context.Context, req resource.ConfigureRe
}
func (r *DeployResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
- var data DeployResourceModelV1
+ var data deploy_models.DeployResourceModelV2
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
telemetrySvc := telemetry.Get(ctx)
@@ -161,28 +165,8 @@ func (r *DeployResource) Create(ctx context.Context, req resource.CreateRequest,
// Register with orchestrator if needed
if data.Orchestrator != nil {
- apiData := data.Api
- host := strings.ReplaceAll(apiData.Attributes()["host"].String(), "\"", "")
- protocol := strings.ReplaceAll(apiData.Attributes()["protocol"].String(), "\"", "")
- port := strings.ReplaceAll(apiData.Attributes()["port"].String(), "\"", "")
- user := strings.ReplaceAll(apiData.Attributes()["user"].String(), "\"", "")
- password := strings.ReplaceAll(apiData.Attributes()["password"].String(), "\"", "")
-
- orchestratorConfig := orchestrator.OrchestratorRegistration{
- HostId: data.Orchestrator.HostId,
- Schema: types.StringValue(protocol),
- Host: types.StringValue(host),
- Port: types.StringValue(port),
- Description: data.Orchestrator.Description,
- Tags: data.Orchestrator.Tags,
- HostCredentials: &authenticator.Authentication{
- Username: types.StringValue(user),
- Password: types.StringValue(password),
- },
- Orchestrator: data.Orchestrator.Orchestrator,
- }
- id, diag := orchestrator.RegisterWithHost(ctx, orchestratorConfig, r.provider.DisableTlsValidation.ValueBool())
+ diag := r.registerWithOrchestrator(ctx, &data, nil)
if diag.HasError() {
if uninstallErrors := parallelsClient.UninstallDependencies(dependencies); len(uninstallErrors) > 0 {
for _, uninstallError := range uninstallErrors {
@@ -195,23 +179,14 @@ func (r *DeployResource) Create(ctx context.Context, req resource.CreateRequest,
if err := parallelsClient.UninstallDevOpsService(); err != nil {
resp.Diagnostics.AddError("Error uninstalling parallels DevOps service", err.Error())
}
- isRegistered, diags := orchestrator.IsAlreadyRegistered(ctx, orchestratorConfig, r.provider.DisableTlsValidation.ValueBool())
+ diags := r.unregisterWithOrchestrator(ctx, &data)
if diags.HasError() {
resp.Diagnostics.Append(diags...)
return
}
-
- if isRegistered {
- if diag := orchestrator.UnregisterWithHost(ctx, orchestratorConfig, r.provider.DisableTlsValidation.ValueBool()); diag.HasError() {
- resp.Diagnostics.Append(diag...)
- }
- }
-
resp.Diagnostics.Append(diag...)
return
}
-
- data.Orchestrator.HostId = types.StringValue(id)
}
var installedDependencies []attr.Value
@@ -230,12 +205,32 @@ func (r *DeployResource) Create(ctx context.Context, req resource.CreateRequest,
}
data.InstalledDependencies = installDependenciesListValue
+ hostConfig := data.GenerateApiHostConfig(r.provider)
+
+ if len(data.ReverseProxyHosts) > 0 {
+ rpHostsCopy := reverseproxy.CopyReverseProxyHosts(data.ReverseProxyHosts)
+ result, createDiag := reverseproxy.Create(ctx, hostConfig, rpHostsCopy)
+ if createDiag.HasError() {
+ resp.Diagnostics.Append(createDiag...)
+ if diag := reverseproxy.Delete(ctx, hostConfig, rpHostsCopy); diag.HasError() {
+ tflog.Error(ctx, "Error deleting reverse proxy hosts")
+ }
+ return
+ }
+
+ for i := range result {
+ data.ReverseProxyHosts[i].ID = result[i].ID
+ }
+ }
+
+ data.ExternalIp = types.StringValue(strings.ReplaceAll(data.SshConnection.Host.String(), "\"", ""))
+
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *DeployResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
- var data DeployResourceModelV1
+ var data deploy_models.DeployResourceModelV2
telemetrySvc := telemetry.Get(ctx)
telemetryEvent := telemetry.NewTelemetryItem(
ctx,
@@ -285,7 +280,7 @@ func (r *DeployResource) Read(ctx context.Context, req resource.ReadRequest, res
// Getting parallels latest api version
if version, err := parallelsClient.GetDevOpsVersion(); err != nil {
- planVersion := ParallelsDesktopDevOps{}
+ planVersion := deploy_models.ParallelsDesktopDevOps{}
if !data.Api.IsNull() {
if diags := data.Api.As(ctx, &planVersion, basetypes.ObjectAsOptions{}); diags.HasError() {
resp.Diagnostics.Append(diags...)
@@ -298,7 +293,7 @@ func (r *DeployResource) Read(ctx context.Context, req resource.ReadRequest, res
data.Api = planVersion.MapObject()
}
} else {
- planVersion := ParallelsDesktopDevOps{}
+ planVersion := deploy_models.ParallelsDesktopDevOps{}
if !data.Api.IsNull() {
if diags := data.Api.As(ctx, &planVersion, basetypes.ObjectAsOptions{}); diags.HasError() {
resp.Diagnostics.Append(diags...)
@@ -322,8 +317,8 @@ func (r *DeployResource) Read(ctx context.Context, req resource.ReadRequest, res
}
func (r *DeployResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
- var data DeployResourceModelV1
- var currentData DeployResourceModelV1
+ var data deploy_models.DeployResourceModelV2
+ var currentData deploy_models.DeployResourceModelV2
telemetrySvc := telemetry.Get(ctx)
telemetryEvent := telemetry.NewTelemetryItem(
@@ -375,7 +370,7 @@ func (r *DeployResource) Update(ctx context.Context, req resource.UpdateRequest,
}
// Check if the API config has changed
- if ApiConfigHasChanges(ctx, data.ApiConfig, currentData.ApiConfig) {
+ if deploy_models.ApiConfigHasChanges(ctx, data.ApiConfig, currentData.ApiConfig) {
if err := parallelsClient.UninstallDevOpsService(); err != nil {
resp.Diagnostics.AddError("Error uninstalling parallels DevOps service", err.Error())
return
@@ -384,6 +379,24 @@ func (r *DeployResource) Update(ctx context.Context, req resource.UpdateRequest,
resp.Diagnostics.Append(diag...)
return
}
+
+ if diag := r.registerWithOrchestrator(ctx, &data, ¤tData); diag.HasError() {
+ if uninstallErrors := parallelsClient.UninstallDependencies(dependencies); len(uninstallErrors) > 0 {
+ for _, uninstallError := range uninstallErrors {
+ diag.AddError("Error uninstalling dependencies", uninstallError.Error())
+ }
+ }
+ if err := parallelsClient.UninstallParallelsDesktop(); err != nil {
+ resp.Diagnostics.AddError("Error uninstalling dependencies", err.Error())
+ }
+ if err := parallelsClient.UninstallDevOpsService(); err != nil {
+ resp.Diagnostics.AddError("Error uninstalling parallels DevOps service", err.Error())
+ }
+ resp.Diagnostics.Append(diag...)
+ return
+ }
+
+ tflog.Info(ctx, "Changes in DevOps service, restarting parallels service")
}
// restart parallels service
@@ -485,71 +498,6 @@ func (r *DeployResource) Update(ctx context.Context, req resource.UpdateRequest,
data.InstalledDependencies = currentData.InstalledDependencies
}
- hasChangesInDevOpsService := false
- if data.ApiConfig != nil {
- if currentData.ApiConfig == nil {
- hasChangesInDevOpsService = true
- } else {
- if data.ApiConfig.Port.ValueString() != currentData.ApiConfig.Port.ValueString() {
- hasChangesInDevOpsService = true
- }
- if data.ApiConfig.TLSPort.ValueString() != currentData.ApiConfig.TLSPort.ValueString() {
- hasChangesInDevOpsService = true
- }
- if data.ApiConfig.RootPassword.ValueString() != currentData.ApiConfig.RootPassword.ValueString() {
- hasChangesInDevOpsService = true
- }
- if data.ApiConfig.EncryptionRsaKey.ValueString() != currentData.ApiConfig.EncryptionRsaKey.ValueString() {
- hasChangesInDevOpsService = true
- }
- if data.ApiConfig.HmacSecret.ValueString() != currentData.ApiConfig.HmacSecret.ValueString() {
- hasChangesInDevOpsService = true
- }
- if data.ApiConfig.LogLevel.ValueString() != currentData.ApiConfig.LogLevel.ValueString() {
- hasChangesInDevOpsService = true
- }
- if data.ApiConfig.EnableTLS.ValueBool() != currentData.ApiConfig.EnableTLS.ValueBool() {
- hasChangesInDevOpsService = true
- }
- if data.ApiConfig.TLSPort.ValueString() != currentData.ApiConfig.TLSPort.ValueString() {
- hasChangesInDevOpsService = true
- }
- if data.ApiConfig.TLSCertificate.ValueString() != currentData.ApiConfig.TLSCertificate.ValueString() {
- hasChangesInDevOpsService = true
- }
- if data.ApiConfig.TLSPrivateKey.ValueString() != currentData.ApiConfig.TLSPrivateKey.ValueString() {
- hasChangesInDevOpsService = true
- }
- if data.ApiConfig.DisableCatalogCaching.ValueBool() != currentData.ApiConfig.DisableCatalogCaching.ValueBool() {
- hasChangesInDevOpsService = true
- }
- if data.ApiConfig.TokenDurationMinutes.ValueString() != currentData.ApiConfig.TokenDurationMinutes.ValueString() {
- hasChangesInDevOpsService = true
- }
- if data.ApiConfig.Mode.ValueString() != currentData.ApiConfig.Mode.ValueString() {
- hasChangesInDevOpsService = true
- }
- if data.ApiConfig.UseOrchestratorResources.ValueBool() != currentData.ApiConfig.UseOrchestratorResources.ValueBool() {
- hasChangesInDevOpsService = true
- }
- }
- }
-
- if hasChangesInDevOpsService {
- err := parallelsClient.UninstallDevOpsService()
- if err != nil {
- resp.Diagnostics.AddError("Error uninstalling parallels DevOps service", err.Error())
- return
- }
- if _, diag := r.installDevOpsService(&data, dependencies, parallelsClient); diag.HasError() {
- resp.Diagnostics.Append(diag...)
- return
- }
- tflog.Info(ctx, "Changes in DevOps service, restarting parallels service")
- } else {
- tflog.Info(ctx, "No changes in DevOps service")
- }
-
installedVersion, getVersionError := parallelsClient.GetDevOpsVersion()
if getVersionError != nil {
if getVersionError.Error() == "Parallels Desktop DevOps Service not found" {
@@ -588,53 +536,30 @@ func (r *DeployResource) Update(ctx context.Context, req resource.UpdateRequest,
if data.Orchestrator != nil {
if orchestrator.HasChanges(ctx, data.Orchestrator, currentData.Orchestrator) {
- apiData := data.Api
- host := strings.ReplaceAll(apiData.Attributes()["host"].String(), "\"", "")
- protocol := strings.ReplaceAll(apiData.Attributes()["protocol"].String(), "\"", "")
- port := strings.ReplaceAll(apiData.Attributes()["port"].String(), "\"", "")
- user := strings.ReplaceAll(apiData.Attributes()["user"].String(), "\"", "")
- password := strings.ReplaceAll(apiData.Attributes()["password"].String(), "\"", "")
-
- // checking if we already registered with orchestrator
- if currentData.Orchestrator != nil && currentData.Orchestrator.HostId.ValueString() != "" {
- if diag := orchestrator.UnregisterWithHost(ctx, *currentData.Orchestrator, r.provider.DisableTlsValidation.ValueBool()); diag.HasError() {
- resp.Diagnostics.Append(diag...)
- return
+ if diag := r.registerWithOrchestrator(ctx, &data, ¤tData); diag.HasError() {
+ if uninstallErrors := parallelsClient.UninstallDependencies(dependencies); len(uninstallErrors) > 0 {
+ for _, uninstallError := range uninstallErrors {
+ diag.AddError("Error uninstalling dependencies", uninstallError.Error())
+ }
}
- }
-
- orchestratorConfig := orchestrator.OrchestratorRegistration{
- HostId: data.Orchestrator.HostId,
- Schema: types.StringValue(protocol),
- Host: types.StringValue(host),
- Port: types.StringValue(port),
- Description: data.Orchestrator.Description,
- Tags: data.Orchestrator.Tags,
- HostCredentials: &authenticator.Authentication{
- Username: types.StringValue(user),
- Password: types.StringValue(password),
- },
- Orchestrator: data.Orchestrator.Orchestrator,
- }
-
- isRegistered, diags := orchestrator.IsAlreadyRegistered(ctx, orchestratorConfig, r.provider.DisableTlsValidation.ValueBool())
- if diags.HasError() {
- resp.Diagnostics.Append(diags...)
- return
- }
- if !isRegistered {
- id, diag := orchestrator.RegisterWithHost(ctx, orchestratorConfig, r.provider.DisableTlsValidation.ValueBool())
- if diag.HasError() {
- resp.Diagnostics.Append(diag...)
- return
+ if err := parallelsClient.UninstallParallelsDesktop(); err != nil {
+ resp.Diagnostics.AddError("Error uninstalling dependencies", err.Error())
}
-
- data.Orchestrator.HostId = types.StringValue(id)
- } else {
- tflog.Info(ctx, "Already registered with orchestrator, skipping registration")
+ if err := parallelsClient.UninstallDevOpsService(); err != nil {
+ resp.Diagnostics.AddError("Error uninstalling parallels DevOps service", err.Error())
+ }
+ resp.Diagnostics.Append(diag...)
+ return
}
} else {
data.Orchestrator.HostId = currentData.Orchestrator.HostId
+ data.IsRegisteredInOrchestrator = types.BoolValue(true)
+ data.OrchestratorHost = currentData.OrchestratorHost
+ if common.GetString(data.OrchestratorHostId) != "" {
+ data.OrchestratorHostId = currentData.OrchestratorHostId
+ } else {
+ data.OrchestratorHostId = currentData.Orchestrator.HostId
+ }
}
} else if currentData.Orchestrator != nil {
if currentData.Orchestrator.HostId.ValueString() != "" {
@@ -645,6 +570,36 @@ func (r *DeployResource) Update(ctx context.Context, req resource.UpdateRequest,
}
}
+ hostConfig := data.GenerateApiHostConfig(r.provider)
+
+ if reverseproxy.ReverseProxyHostsDiff(data.ReverseProxyHosts, currentData.ReverseProxyHosts) {
+ copyCurrentRpHosts := reverseproxy.CopyReverseProxyHosts(currentData.ReverseProxyHosts)
+ copyRpHosts := reverseproxy.CopyReverseProxyHosts(data.ReverseProxyHosts)
+
+ results, updateDiag := reverseproxy.Update(ctx, hostConfig, copyCurrentRpHosts, copyRpHosts)
+ if updateDiag.HasError() {
+ resp.Diagnostics.Append(updateDiag...)
+ revertResults, _ := reverseproxy.Revert(ctx, hostConfig, copyCurrentRpHosts, copyRpHosts)
+ for i := range revertResults {
+ data.ReverseProxyHosts[i].ID = revertResults[i].ID
+ }
+ return
+ }
+
+ for i := range results {
+ data.ReverseProxyHosts[i].ID = results[i].ID
+ }
+ } else {
+ for i := range currentData.ReverseProxyHosts {
+ data.ReverseProxyHosts[i].ID = currentData.ReverseProxyHosts[i].ID
+ }
+ }
+
+ if currentData.ExternalIp.ValueString() == "" ||
+ strings.ReplaceAll(currentData.ExternalIp.ValueString(), "\"", "") != strings.ReplaceAll(data.ExternalIp.ValueString(), "\"", "") {
+ data.ExternalIp = types.StringValue(strings.ReplaceAll(data.SshConnection.Host.String(), "\"", ""))
+ }
+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
@@ -652,7 +607,7 @@ func (r *DeployResource) Update(ctx context.Context, req resource.UpdateRequest,
}
func (r *DeployResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
- var data DeployResourceModelV1
+ var data deploy_models.DeployResourceModelV2
telemetrySvc := telemetry.Get(ctx)
telemetryEvent := telemetry.NewTelemetryItem(
@@ -728,11 +683,21 @@ func (r *DeployResource) Delete(ctx context.Context, req resource.DeleteRequest,
})
if data.Orchestrator != nil {
- if diag := orchestrator.UnregisterWithHost(ctx, *data.Orchestrator, r.provider.DisableTlsValidation.ValueBool()); diag.HasError() {
+ if diag := r.unregisterWithOrchestrator(ctx, &data); diag.HasError() {
resp.Diagnostics.Append(diag...)
}
}
+ hostConfig := data.GenerateApiHostConfig(r.provider)
+
+ if len(data.ReverseProxyHosts) > 0 {
+ rpHostsCopy := reverseproxy.CopyReverseProxyHosts(data.ReverseProxyHosts)
+ if diag := reverseproxy.Delete(ctx, hostConfig, rpHostsCopy); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
+ }
+
if resp.Diagnostics.HasError() {
return
}
@@ -745,21 +710,25 @@ func (r *DeployResource) ImportState(ctx context.Context, req resource.ImportSta
func (r *DeployResource) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader {
return map[int64]resource.StateUpgrader{
0: {
- PriorSchema: &deployResourceSchemaV0,
- StateUpgrader: UpgradeState,
+ PriorSchema: &schemas.DeployResourceSchemaV0,
+ StateUpgrader: UpgradeStateToV1,
+ },
+ 1: {
+ PriorSchema: &schemas.DeployResourceSchemaV1,
+ StateUpgrader: UpgradeStateToV2,
},
}
}
-func UpgradeState(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) {
- var priorStateData DeployResourceModelV0
+func UpgradeStateToV1(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) {
+ var priorStateData deploy_models.DeployResourceModelV0
resp.Diagnostics.Append(req.State.Get(ctx, &priorStateData)...)
if resp.Diagnostics.HasError() {
return
}
- upgradedStateData := DeployResourceModelV1{
+ upgradedStateData := deploy_models.DeployResourceModelV1{
SshConnection: priorStateData.SshConnection,
CurrentVersion: priorStateData.CurrentVersion,
CurrentPackerVersion: priorStateData.CurrentPackerVersion,
@@ -767,7 +736,7 @@ func UpgradeState(ctx context.Context, req resource.UpgradeStateRequest, resp *r
CurrentGitVersion: priorStateData.CurrentGitVersion,
License: priorStateData.License,
Orchestrator: priorStateData.Orchestrator,
- ApiConfig: &ParallelsDesktopDevopsConfigV1{
+ ApiConfig: &deploy_models.ParallelsDesktopDevopsConfigV1{
Port: priorStateData.ApiConfig.Port,
Prefix: priorStateData.ApiConfig.Prefix,
DevOpsVersion: priorStateData.ApiConfig.DevOpsVersion,
@@ -799,7 +768,58 @@ func UpgradeState(ctx context.Context, req resource.UpgradeStateRequest, resp *r
resp.Diagnostics.Append(resp.State.Set(ctx, &upgradedStateData)...)
}
-func (r *DeployResource) getSshClient(data DeployResourceModelV1) (*ssh.SshClient, error) {
+func UpgradeStateToV2(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) {
+ var priorStateData deploy_models.DeployResourceModelV1
+ resp.Diagnostics.Append(req.State.Get(ctx, &priorStateData)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ upgradedStateData := deploy_models.DeployResourceModelV2{
+ SshConnection: priorStateData.SshConnection,
+ CurrentVersion: priorStateData.CurrentVersion,
+ CurrentPackerVersion: priorStateData.CurrentPackerVersion,
+ CurrentVagrantVersion: priorStateData.CurrentVagrantVersion,
+ CurrentGitVersion: priorStateData.CurrentGitVersion,
+ License: priorStateData.License,
+ Orchestrator: priorStateData.Orchestrator,
+ ApiConfig: &deploy_models.ParallelsDesktopDevopsConfigV2{
+ Port: priorStateData.ApiConfig.Port,
+ Prefix: priorStateData.ApiConfig.Prefix,
+ DevOpsVersion: priorStateData.ApiConfig.DevOpsVersion,
+ RootPassword: priorStateData.ApiConfig.RootPassword,
+ HmacSecret: priorStateData.ApiConfig.HmacSecret,
+ EncryptionRsaKey: priorStateData.ApiConfig.EncryptionRsaKey,
+ LogLevel: priorStateData.ApiConfig.LogLevel,
+ EnableTLS: priorStateData.ApiConfig.EnableTLS,
+ TLSPort: priorStateData.ApiConfig.TLSPort,
+ TLSCertificate: priorStateData.ApiConfig.TLSCertificate,
+ TLSPrivateKey: priorStateData.ApiConfig.TLSPrivateKey,
+ DisableCatalogCaching: priorStateData.ApiConfig.DisableCatalogCaching,
+ TokenDurationMinutes: priorStateData.ApiConfig.TokenDurationMinutes,
+ Mode: priorStateData.ApiConfig.Mode,
+ UseOrchestratorResources: priorStateData.ApiConfig.UseOrchestratorResources,
+ SystemReservedMemory: priorStateData.ApiConfig.SystemReservedMemory,
+ SystemReservedCpu: priorStateData.ApiConfig.SystemReservedCpu,
+ SystemReservedDisk: priorStateData.ApiConfig.SystemReservedDisk,
+ EnableLogging: priorStateData.ApiConfig.EnableLogging,
+ EnvironmentVariables: priorStateData.ApiConfig.EnvironmentVariables,
+ EnablePortForwarding: basetypes.NewBoolValue(false),
+ UseLatestBeta: basetypes.NewBoolValue(false),
+ },
+ ReverseProxyHosts: make([]*reverseproxy.ReverseProxyHost, 0),
+ Api: priorStateData.Api,
+ InstalledDependencies: priorStateData.InstalledDependencies,
+ InstallLocal: priorStateData.InstallLocal,
+ }
+
+ println(fmt.Sprintf("Upgrading state from version %v", upgradedStateData))
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &upgradedStateData)...)
+}
+
+func (r *DeployResource) getSshClient(data deploy_models.DeployResourceModelV2) (*ssh.SshClient, error) {
if data.SshConnection.Host.IsNull() {
return nil, errors.New("host is required")
}
@@ -898,16 +918,16 @@ func (r *DeployResource) installParallelsDesktop(parallelsClient *DevOpsServiceC
return installed_dependencies, diag
}
-func (r *DeployResource) installDevOpsService(data *DeployResourceModelV1, dependencies []string, parallelsClient *DevOpsServiceClient) (*ParallelsDesktopDevOps, diag.Diagnostics) {
+func (r *DeployResource) installDevOpsService(data *deploy_models.DeployResourceModelV2, dependencies []string, parallelsClient *DevOpsServiceClient) (*deploy_models.ParallelsDesktopDevOps, diag.Diagnostics) {
diag := diag.Diagnostics{}
targetPort := "8080"
targetTlsPort := "8443"
apiVersion := "latest"
// Installing parallels DevOps service
- var config ParallelsDesktopDevopsConfigV1
+ var config deploy_models.ParallelsDesktopDevopsConfigV2
if data.ApiConfig == nil {
- config = ParallelsDesktopDevopsConfigV1{
+ config = deploy_models.ParallelsDesktopDevopsConfigV2{
DevOpsVersion: types.StringValue(apiVersion),
Port: types.StringValue(targetPort),
TLSPort: types.StringValue(targetTlsPort),
@@ -955,7 +975,7 @@ func (r *DeployResource) installDevOpsService(data *DeployResourceModelV1, depen
return nil, diag
}
- apiData := ParallelsDesktopDevOps{
+ apiData := deploy_models.ParallelsDesktopDevOps{
Version: types.StringValue(currentVersion),
Host: types.StringValue(data.SshConnection.Host.ValueString()),
Port: types.StringValue(targetPort),
@@ -977,3 +997,130 @@ func (r *DeployResource) installDevOpsService(data *DeployResourceModelV1, depen
return &apiData, diag
}
+
+func (r *DeployResource) registerWithOrchestrator(ctx context.Context, data, currentData *deploy_models.DeployResourceModelV2) diag.Diagnostics {
+ diagnostic := diag.Diagnostics{}
+ if data.Orchestrator == nil {
+ return diagnostic
+ }
+
+ host := strings.ReplaceAll(data.SshConnection.Host.String(), "\"", "")
+ port := strings.ReplaceAll(data.ApiConfig.Port.String(), "\"", "")
+ user := "root@localhost"
+ schema := "http"
+ password := strings.ReplaceAll(data.ApiConfig.RootPassword.String(), "\"", "")
+ if data.ApiConfig.EnableTLS.ValueBool() {
+ schema = "https"
+ }
+
+ if currentData != nil {
+ currentRegistration := *currentData.Orchestrator
+ if common.GetString(currentData.OrchestratorHostId) != "" {
+ currentRegistration.HostId = currentData.OrchestratorHostId
+ }
+ if currentRegistration.HostId.ValueString() != "" &&
+ currentData.Orchestrator != nil &&
+ currentData.Orchestrator.HostId.ValueString() != "" {
+ currentRegistration.HostId = currentData.Orchestrator.HostId
+ }
+
+ // checking if we already registered with orchestrator
+ isRegistered, item, diags := orchestrator.IsAlreadyRegistered(ctx, currentRegistration, r.provider.DisableTlsValidation.ValueBool())
+ if diags.HasError() {
+ diagnostic.Append(diags...)
+ return diagnostic
+ }
+ if isRegistered {
+ currentRegistration.HostId = types.StringValue(item.ID)
+ if diag := orchestrator.UnregisterWithHost(ctx, currentRegistration, r.provider.DisableTlsValidation.ValueBool()); diag.HasError() {
+ diag.Append(diag...)
+ return diag
+ }
+ }
+ }
+
+ // New registration details
+ orchestratorConfig := orchestrator.OrchestratorRegistration{
+ HostId: data.Orchestrator.HostId,
+ Schema: types.StringValue(schema),
+ Host: types.StringValue(host),
+ Port: types.StringValue(port),
+ Description: data.Orchestrator.Description,
+ Tags: data.Orchestrator.Tags,
+ HostCredentials: &authenticator.Authentication{
+ Username: types.StringValue(user),
+ Password: types.StringValue(password),
+ },
+ Orchestrator: data.Orchestrator.Orchestrator,
+ }
+
+ isRegistered, item, diags := orchestrator.IsAlreadyRegistered(ctx, orchestratorConfig, r.provider.DisableTlsValidation.ValueBool())
+ if diags.HasError() {
+ diagnostic.Append(diags...)
+ data.IsRegisteredInOrchestrator = types.BoolValue(true)
+ data.OrchestratorHostId = types.StringValue(item.ID)
+ data.OrchestratorHost = types.StringValue(item.Host)
+ return diagnostic
+ }
+
+ if !isRegistered {
+ id, diag := orchestrator.RegisterWithHost(ctx, orchestratorConfig, r.provider.DisableTlsValidation.ValueBool())
+ if diag.HasError() {
+ diagnostic.Append(diag...)
+ return diagnostic
+ }
+
+ if data.Orchestrator != nil {
+ data.Orchestrator.HostId = types.StringValue(id)
+ }
+ data.IsRegisteredInOrchestrator = types.BoolValue(true)
+ data.OrchestratorHostId = types.StringValue(id)
+ data.OrchestratorHost = types.StringValue(orchestratorConfig.GetHost())
+ } else {
+ tflog.Info(ctx, "Already registered with orchestrator, skipping registration")
+ if data.Orchestrator != nil {
+ data.Orchestrator.HostId = types.StringValue(item.ID)
+ }
+ data.IsRegisteredInOrchestrator = types.BoolValue(true)
+ data.OrchestratorHostId = types.StringValue(item.ID)
+ data.OrchestratorHost = types.StringValue(item.Host)
+ }
+
+ return diagnostic
+}
+
+func (r *DeployResource) unregisterWithOrchestrator(ctx context.Context, data *deploy_models.DeployResourceModelV2) diag.Diagnostics {
+ diagnostic := diag.Diagnostics{}
+ if data.Orchestrator == nil {
+ return diagnostic
+ }
+
+ currentRegistration := *data.Orchestrator
+ if common.GetString(data.OrchestratorHostId) != "" {
+ currentRegistration.HostId = data.OrchestratorHostId
+ }
+
+ isRegistered, item, diags := orchestrator.IsAlreadyRegistered(ctx, currentRegistration, r.provider.DisableTlsValidation.ValueBool())
+ if diags.HasError() {
+ diagnostic.Append(diags...)
+ return diagnostic
+ }
+
+ if isRegistered {
+ // checking if we already registered with orchestrator
+ currentRegistration.HostId = types.StringValue(item.ID)
+ if diag := orchestrator.UnregisterWithHost(ctx, currentRegistration, r.provider.DisableTlsValidation.ValueBool()); diag.HasError() {
+ diag.Append(diag...)
+ return diag
+ }
+ }
+ if data.Orchestrator != nil {
+ data.Orchestrator.HostId = types.StringValue("")
+ }
+
+ data.IsRegisteredInOrchestrator = types.BoolValue(false)
+ data.OrchestratorHostId = types.StringValue("")
+ data.OrchestratorHost = types.StringValue("")
+
+ return diagnostic
+}
diff --git a/internal/deploy/resource_models.go b/internal/deploy/resource_models.go
deleted file mode 100644
index 0e44837..0000000
--- a/internal/deploy/resource_models.go
+++ /dev/null
@@ -1,305 +0,0 @@
-package deploy
-
-import (
- "context"
-
- "terraform-provider-parallels-desktop/internal/clientmodels"
- "terraform-provider-parallels-desktop/internal/schemas/orchestrator"
-
- "github.com/hashicorp/terraform-plugin-framework/attr"
- "github.com/hashicorp/terraform-plugin-framework/types"
- "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
-)
-
-// DeployResourceModel describes the resource data model.
-
-type DeployResourceModelV0 struct {
- SshConnection *DeployResourceSshConnection `tfsdk:"ssh_connection"`
- CurrentVersion types.String `tfsdk:"current_version"`
- CurrentPackerVersion types.String `tfsdk:"current_packer_version"`
- CurrentVagrantVersion types.String `tfsdk:"current_vagrant_version"`
- CurrentGitVersion types.String `tfsdk:"current_git_version"`
- License types.Object `tfsdk:"license"`
- Orchestrator *orchestrator.OrchestratorRegistration `tfsdk:"orchestrator_registration"`
- ApiConfig *ParallelsDesktopDevopsConfigV0 `tfsdk:"api_config"`
- Api types.Object `tfsdk:"api"`
- InstalledDependencies types.List `tfsdk:"installed_dependencies"`
- InstallLocal types.Bool `tfsdk:"install_local"`
-}
-
-type DeployResourceModelV1 struct {
- SshConnection *DeployResourceSshConnection `tfsdk:"ssh_connection"`
- CurrentVersion types.String `tfsdk:"current_version"`
- CurrentPackerVersion types.String `tfsdk:"current_packer_version"`
- CurrentVagrantVersion types.String `tfsdk:"current_vagrant_version"`
- CurrentGitVersion types.String `tfsdk:"current_git_version"`
- License types.Object `tfsdk:"license"`
- Orchestrator *orchestrator.OrchestratorRegistration `tfsdk:"orchestrator_registration"`
- ApiConfig *ParallelsDesktopDevopsConfigV1 `tfsdk:"api_config"`
- Api types.Object `tfsdk:"api"`
- InstalledDependencies types.List `tfsdk:"installed_dependencies"`
- InstallLocal types.Bool `tfsdk:"install_local"`
-}
-
-type DeployResourceSshConnection struct {
- Host types.String `tfsdk:"host"`
- HostPort types.String `tfsdk:"host_port"`
- User types.String `tfsdk:"user"`
- Password types.String `tfsdk:"password"`
- PrivateKey types.String `tfsdk:"private_key"`
-}
-
-type ParallelsDesktopLicense struct {
- State types.String `tfsdk:"state"`
- Key types.String `tfsdk:"key"`
- Restricted types.Bool `tfsdk:"restricted"`
-}
-
-func (p *ParallelsDesktopLicense) FromClientModel(value clientmodels.ParallelsServerLicense) {
- p.State = types.StringValue(value.State)
- p.Key = types.StringValue(value.Key)
- p.Restricted = types.BoolValue(value.Restricted == "true")
-}
-
-func (p *ParallelsDesktopLicense) MapObject() basetypes.ObjectValue {
- attributeTypes := make(map[string]attr.Type)
- attributeTypes["state"] = types.StringType
- attributeTypes["key"] = types.StringType
- attributeTypes["restricted"] = types.BoolType
-
- attrs := map[string]attr.Value{}
- attrs["state"] = p.State
- attrs["key"] = p.Key
- attrs["restricted"] = p.Restricted
-
- return types.ObjectValueMust(attributeTypes, attrs)
-}
-
-type ParallelsDesktopDevOps struct {
- Version types.String `tfsdk:"version"`
- Protocol types.String `tfsdk:"protocol"`
- Host types.String `tfsdk:"host"`
- Port types.String `tfsdk:"port"`
- User types.String `tfsdk:"user"`
- Password types.String `tfsdk:"password"`
-}
-
-func (p *ParallelsDesktopDevOps) MapObject() basetypes.ObjectValue {
- attributeTypes := make(map[string]attr.Type)
- attributeTypes["version"] = types.StringType
- attributeTypes["protocol"] = types.StringType
- attributeTypes["host"] = types.StringType
- attributeTypes["port"] = types.StringType
- attributeTypes["user"] = types.StringType
- attributeTypes["password"] = types.StringType
-
- attrs := map[string]attr.Value{}
- attrs["version"] = p.Version
- attrs["protocol"] = p.Protocol
- attrs["host"] = p.Host
- attrs["port"] = p.Port
- attrs["user"] = p.User
- attrs["password"] = p.Password
-
- return types.ObjectValueMust(attributeTypes, attrs)
-}
-
-type ParallelsDesktopDevopsConfigV0 struct {
- Port types.String `tfsdk:"port" json:"port,omitempty"`
- Prefix types.String `tfsdk:"prefix" json:"prefix,omitempty"`
- DevOpsVersion types.String `tfsdk:"devops_version" json:"devops_version,omitempty"`
- RootPassword types.String `tfsdk:"root_password" json:"root_password,omitempty"`
- HmacSecret types.String `tfsdk:"hmac_secret" json:"hmac_secret,omitempty"`
- EncryptionRsaKey types.String `tfsdk:"encryption_rsa_key" json:"encryption_rsa_key,omitempty"`
- LogLevel types.String `tfsdk:"log_level" json:"log_level,omitempty"`
- EnableTLS types.Bool `tfsdk:"enable_tls" json:"enable_tls,omitempty"`
- TLSPort types.String `tfsdk:"tls_port" json:"tls_port,omitempty"`
- TLSCertificate types.String `tfsdk:"tls_certificate" json:"tls_certificate,omitempty"`
- TLSPrivateKey types.String `tfsdk:"tls_private_key" json:"tls_private_key,omitempty"`
- DisableCatalogCaching types.Bool `tfsdk:"disable_catalog_caching" json:"disable_catalog_caching,omitempty"`
- TokenDurationMinutes types.String `tfsdk:"token_duration_minutes" json:"token_duration_minutes,omitempty"`
- Mode types.String `tfsdk:"mode" json:"mode,omitempty"`
- UseOrchestratorResources types.Bool `tfsdk:"use_orchestrator_resources"`
- SystemReservedMemory types.String `tfsdk:"system_reserved_memory"`
- SystemReservedCpu types.String `tfsdk:"system_reserved_cpu"`
- SystemReservedDisk types.String `tfsdk:"system_reserved_disk"`
- EnableLogging types.Bool `tfsdk:"enable_logging"`
-}
-
-type ParallelsDesktopDevopsConfigV1 struct {
- Port types.String `tfsdk:"port" json:"port,omitempty"`
- Prefix types.String `tfsdk:"prefix" json:"prefix,omitempty"`
- DevOpsVersion types.String `tfsdk:"devops_version" json:"devops_version,omitempty"`
- RootPassword types.String `tfsdk:"root_password" json:"root_password,omitempty"`
- HmacSecret types.String `tfsdk:"hmac_secret" json:"hmac_secret,omitempty"`
- EncryptionRsaKey types.String `tfsdk:"encryption_rsa_key" json:"encryption_rsa_key,omitempty"`
- LogLevel types.String `tfsdk:"log_level" json:"log_level,omitempty"`
- EnableTLS types.Bool `tfsdk:"enable_tls" json:"enable_tls,omitempty"`
- TLSPort types.String `tfsdk:"tls_port" json:"tls_port,omitempty"`
- TLSCertificate types.String `tfsdk:"tls_certificate" json:"tls_certificate,omitempty"`
- TLSPrivateKey types.String `tfsdk:"tls_private_key" json:"tls_private_key,omitempty"`
- DisableCatalogCaching types.Bool `tfsdk:"disable_catalog_caching" json:"disable_catalog_caching,omitempty"`
- TokenDurationMinutes types.String `tfsdk:"token_duration_minutes" json:"token_duration_minutes,omitempty"`
- Mode types.String `tfsdk:"mode" json:"mode,omitempty"`
- UseOrchestratorResources types.Bool `tfsdk:"use_orchestrator_resources"`
- SystemReservedMemory types.String `tfsdk:"system_reserved_memory"`
- SystemReservedCpu types.String `tfsdk:"system_reserved_cpu"`
- SystemReservedDisk types.String `tfsdk:"system_reserved_disk"`
- EnableLogging types.Bool `tfsdk:"enable_logging"`
- EnvironmentVariables map[string]types.String `tfsdk:"environment_variables"`
-}
-
-func (p *ParallelsDesktopDevopsConfigV1) MapObject() basetypes.ObjectValue {
- attributeTypes := make(map[string]attr.Type)
- attributeTypes["port"] = types.StringType
- attributeTypes["devops_version"] = types.StringType
- attributeTypes["root_password"] = types.StringType
- attributeTypes["hmac_secret"] = types.StringType
- attributeTypes["encryption_rsa_key"] = types.StringType
- attributeTypes["log_level"] = types.StringType
- attributeTypes["enable_tls"] = types.BoolType
- attributeTypes["tls_port"] = types.StringType
- attributeTypes["tls_certificate"] = types.StringType
- attributeTypes["tls_private_key"] = types.StringType
- attributeTypes["disable_catalog_caching"] = types.BoolType
- attributeTypes["token_duration_minutes"] = types.StringType
- attributeTypes["mode"] = types.StringType
- attributeTypes["use_orchestrator_resources"] = types.BoolType
- attributeTypes["system_reserved_memory"] = types.StringType
- attributeTypes["system_reserved_cpu"] = types.StringType
- attributeTypes["system_reserved_disk"] = types.StringType
- attributeTypes["enable_logging"] = types.BoolType
- attributeTypes["environment_variables"] = types.MapType{}
-
- attrs := map[string]attr.Value{}
- attrs["api_port"] = p.Port
- attrs["devops_version"] = p.DevOpsVersion
- attrs["root_password"] = p.RootPassword
- attrs["hmac_secret"] = p.HmacSecret
- attrs["encryption_rsa_key"] = p.EncryptionRsaKey
- attrs["log_level"] = p.LogLevel
- attrs["enable_tls"] = p.EnableTLS
- attrs["host_tls_port"] = p.TLSPort
- attrs["tls_certificate"] = p.TLSCertificate
- attrs["tls_private_key"] = p.TLSPrivateKey
- attrs["disable_catalog_caching"] = p.DisableCatalogCaching
- attrs["token_duration_minutes"] = p.TokenDurationMinutes
- attrs["mode"] = p.Mode
- attrs["use_orchestrator_resources"] = p.UseOrchestratorResources
- attrs["system_reserved_memory"] = p.SystemReservedMemory
- attrs["system_reserved_cpu"] = p.SystemReservedCpu
- attrs["system_reserved_disk"] = p.SystemReservedDisk
- attrs["enable_logging"] = p.EnableLogging
-
- envVars := make(map[string]attr.Value)
- for k, v := range p.EnvironmentVariables {
- envVars[k] = v
- }
- attrs["environment_variables"] = types.MapValueMust(types.StringType, envVars)
-
- return types.ObjectValueMust(attributeTypes, attrs)
-}
-
-func ApiConfigHasChanges(context context.Context, planState, currentState *ParallelsDesktopDevopsConfigV1) bool {
- if planState == nil && currentState == nil {
- return false
- }
-
- if planState != nil && currentState == nil {
- return true
- }
-
- if planState == nil && currentState != nil {
- return true
- }
-
- if planState.Port != currentState.Port {
- return true
- }
-
- if planState.Prefix != currentState.Prefix {
- return true
- }
-
- if planState.DevOpsVersion != currentState.DevOpsVersion {
- return true
- }
-
- if planState.RootPassword != currentState.RootPassword {
- return true
- }
-
- if planState.HmacSecret != currentState.HmacSecret {
- return true
- }
-
- if planState.EncryptionRsaKey != currentState.EncryptionRsaKey {
- return true
- }
-
- if planState.LogLevel != currentState.LogLevel {
- return true
- }
-
- if planState.EnableTLS != currentState.EnableTLS {
- return true
- }
-
- if planState.TLSPort != currentState.TLSPort {
- return true
- }
-
- if planState.TLSCertificate != currentState.TLSCertificate {
- return true
- }
-
- if planState.TLSPrivateKey != currentState.TLSPrivateKey {
- return true
- }
-
- if planState.DisableCatalogCaching != currentState.DisableCatalogCaching {
- return true
- }
-
- if planState.TokenDurationMinutes != currentState.TokenDurationMinutes {
- return true
- }
-
- if planState.Mode != currentState.Mode {
- return true
- }
-
- if planState.UseOrchestratorResources != currentState.UseOrchestratorResources {
- return true
- }
-
- if planState.SystemReservedMemory != currentState.SystemReservedMemory {
- return true
- }
-
- if planState.SystemReservedCpu != currentState.SystemReservedCpu {
- return true
- }
-
- if planState.SystemReservedDisk != currentState.SystemReservedDisk {
- return true
- }
-
- if planState.EnableLogging != currentState.EnableLogging {
- return true
- }
-
- if len(planState.EnvironmentVariables) != len(currentState.EnvironmentVariables) {
- return true
- }
-
- if len(planState.EnvironmentVariables) != 0 {
- for k, v := range planState.EnvironmentVariables {
- if currentState.EnvironmentVariables[k] != v {
- return true
- }
- }
- }
-
- return false
-}
diff --git a/internal/deploy/schemas/api_config_schema_v0.go b/internal/deploy/schemas/api_config_schema_v0.go
new file mode 100644
index 0000000..a89d867
--- /dev/null
+++ b/internal/deploy/schemas/api_config_schema_v0.go
@@ -0,0 +1,115 @@
+package schemas
+
+import (
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+)
+
+var ApiConfigSchemaBlockV0 = schema.SingleNestedBlock{
+ MarkdownDescription: "Parallels Desktop DevOps configuration",
+ Description: "Parallels Desktop DevOps configuration",
+ Attributes: map[string]schema.Attribute{
+ "port": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps port",
+ Description: "Parallels Desktop DevOps port",
+ Optional: true,
+ },
+ "prefix": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps port",
+ Description: "Parallels Desktop DevOps port",
+ Optional: true,
+ },
+ "devops_version": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps version to install, if empty the latest will be installed",
+ Description: "Parallels Desktop DevOps version to install, if empty the latest will be installed",
+ Optional: true,
+ },
+ "root_password": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps root password",
+ Description: "Parallels Desktop DevOps root password",
+ Optional: true,
+ Sensitive: true,
+ },
+ "hmac_secret": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps HMAC secret, this is used to sign the JWT tokens",
+ Description: "Parallels Desktop DevOps HMAC secret, this is used to sign the JWT tokens",
+ Optional: true,
+ Sensitive: true,
+ },
+ "encryption_rsa_key": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps RSA key, this is used to encrypt database file on rest",
+ Description: "Parallels Desktop DevOps RSA key, this is used to encrypt database file on rest",
+ Optional: true,
+ Sensitive: true,
+ },
+ "log_level": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps log level, you can choose between debug, info, warn, error",
+ Description: "Parallels Desktop DevOps log level, you can choose between debug, info, warn, error",
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("debug", "info", "warn", "error"),
+ },
+ },
+ "enable_tls": schema.BoolAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps enable TLS",
+ Description: "Parallels Desktop DevOps enable TLS",
+ Optional: true,
+ },
+ "tls_port": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps TLS port",
+ Description: "Parallels Desktop DevOps TLS port",
+ Optional: true,
+ },
+ "tls_certificate": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps TLS certificate, this should be a PEM base64 encoded certificate string",
+ Description: "Parallels Desktop DevOps TLS certificate, this should be a PEM base64 encoded certificate string",
+ Optional: true,
+ Sensitive: true,
+ },
+ "tls_private_key": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps TLS private key, this should be a PEM base64 encoded private key string",
+ Description: "Parallels Desktop DevOps TLS private key, this should be a PEM base64 encoded private key string",
+ Optional: true,
+ Sensitive: true,
+ },
+ "disable_catalog_caching": schema.BoolAttribute{
+ MarkdownDescription: "Disable catalog caching, this will disable the ability to cache catalog items that are pulled from a remote catalog",
+ Description: "Disable catalog caching, this will disable the ability to cache catalog items that are pulled from a remote catalog",
+ Optional: true,
+ },
+ "token_duration_minutes": schema.StringAttribute{
+ MarkdownDescription: "JWT Token duration in minutes",
+ Description: "JWT Token duration in minutes",
+ Optional: true,
+ },
+ "mode": schema.StringAttribute{
+ MarkdownDescription: "API Operation mode, either orchestrator or catalog",
+ Optional: true,
+ Sensitive: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("orchestrator", "catalog", "api"),
+ },
+ },
+ "use_orchestrator_resources": schema.BoolAttribute{
+ MarkdownDescription: "Use orchestrator resources",
+ Optional: true,
+ },
+ "system_reserved_memory": schema.StringAttribute{
+ MarkdownDescription: "System reserved memory in MB",
+ Optional: true,
+ },
+ "system_reserved_cpu": schema.StringAttribute{
+ MarkdownDescription: "System reserved CPU in %",
+ Optional: true,
+ },
+ "system_reserved_disk": schema.StringAttribute{
+ MarkdownDescription: "System reserved disk in MB",
+ Optional: true,
+ },
+ "enable_logging": schema.BoolAttribute{
+ MarkdownDescription: "Enable logging",
+ Optional: true,
+ },
+ },
+}
diff --git a/internal/deploy/schemas/api_config_schema_v1.go b/internal/deploy/schemas/api_config_schema_v1.go
new file mode 100644
index 0000000..4bab6d5
--- /dev/null
+++ b/internal/deploy/schemas/api_config_schema_v1.go
@@ -0,0 +1,121 @@
+package schemas
+
+import (
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+var ApiConfigSchemaBlockV1 = schema.SingleNestedBlock{
+ MarkdownDescription: "Parallels Desktop DevOps configuration",
+ Description: "Parallels Desktop DevOps configuration",
+ Attributes: map[string]schema.Attribute{
+ "port": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps port",
+ Description: "Parallels Desktop DevOps port",
+ Optional: true,
+ },
+ "prefix": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps port",
+ Description: "Parallels Desktop DevOps port",
+ Optional: true,
+ },
+ "devops_version": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps version to install, if empty the latest will be installed",
+ Description: "Parallels Desktop DevOps version to install, if empty the latest will be installed",
+ Optional: true,
+ },
+ "root_password": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps root password",
+ Description: "Parallels Desktop DevOps root password",
+ Optional: true,
+ Sensitive: true,
+ },
+ "hmac_secret": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps HMAC secret, this is used to sign the JWT tokens",
+ Description: "Parallels Desktop DevOps HMAC secret, this is used to sign the JWT tokens",
+ Optional: true,
+ Sensitive: true,
+ },
+ "encryption_rsa_key": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps RSA key, this is used to encrypt database file on rest",
+ Description: "Parallels Desktop DevOps RSA key, this is used to encrypt database file on rest",
+ Optional: true,
+ Sensitive: true,
+ },
+ "log_level": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps log level, you can choose between debug, info, warn, error",
+ Description: "Parallels Desktop DevOps log level, you can choose between debug, info, warn, error",
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("debug", "info", "warn", "error"),
+ },
+ },
+ "enable_tls": schema.BoolAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps enable TLS",
+ Description: "Parallels Desktop DevOps enable TLS",
+ Optional: true,
+ },
+ "tls_port": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps TLS port",
+ Description: "Parallels Desktop DevOps TLS port",
+ Optional: true,
+ },
+ "tls_certificate": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps TLS certificate, this should be a PEM base64 encoded certificate string",
+ Description: "Parallels Desktop DevOps TLS certificate, this should be a PEM base64 encoded certificate string",
+ Optional: true,
+ Sensitive: true,
+ },
+ "tls_private_key": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps TLS private key, this should be a PEM base64 encoded private key string",
+ Description: "Parallels Desktop DevOps TLS private key, this should be a PEM base64 encoded private key string",
+ Optional: true,
+ Sensitive: true,
+ },
+ "disable_catalog_caching": schema.BoolAttribute{
+ MarkdownDescription: "Disable catalog caching, this will disable the ability to cache catalog items that are pulled from a remote catalog",
+ Description: "Disable catalog caching, this will disable the ability to cache catalog items that are pulled from a remote catalog",
+ Optional: true,
+ },
+ "token_duration_minutes": schema.StringAttribute{
+ MarkdownDescription: "JWT Token duration in minutes",
+ Description: "JWT Token duration in minutes",
+ Optional: true,
+ },
+ "mode": schema.StringAttribute{
+ MarkdownDescription: "API Operation mode, either orchestrator or catalog",
+ Optional: true,
+ Sensitive: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("orchestrator", "catalog", "api"),
+ },
+ },
+ "use_orchestrator_resources": schema.BoolAttribute{
+ MarkdownDescription: "Use orchestrator resources",
+ Optional: true,
+ },
+ "system_reserved_memory": schema.StringAttribute{
+ MarkdownDescription: "System reserved memory in MB",
+ Optional: true,
+ },
+ "system_reserved_cpu": schema.StringAttribute{
+ MarkdownDescription: "System reserved CPU in %",
+ Optional: true,
+ },
+ "system_reserved_disk": schema.StringAttribute{
+ MarkdownDescription: "System reserved disk in MB",
+ Optional: true,
+ },
+ "enable_logging": schema.BoolAttribute{
+ MarkdownDescription: "Enable logging",
+ Optional: true,
+ },
+ "environment_variables": schema.MapAttribute{
+ MarkdownDescription: "Environment variables that can be used in the DevOps service, please see documentation to see which variables are available",
+ Optional: true,
+ ElementType: types.StringType,
+ },
+ },
+}
diff --git a/internal/deploy/schemas/api_config_schema_v2.go b/internal/deploy/schemas/api_config_schema_v2.go
new file mode 100644
index 0000000..4c03e24
--- /dev/null
+++ b/internal/deploy/schemas/api_config_schema_v2.go
@@ -0,0 +1,129 @@
+package schemas
+
+import (
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+var ApiConfigSchemaBlockV2 = schema.SingleNestedBlock{
+ MarkdownDescription: "Parallels Desktop DevOps configuration",
+ Description: "Parallels Desktop DevOps configuration",
+ Attributes: map[string]schema.Attribute{
+ "port": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps port",
+ Description: "Parallels Desktop DevOps port",
+ Optional: true,
+ },
+ "prefix": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps port",
+ Description: "Parallels Desktop DevOps port",
+ Optional: true,
+ },
+ "devops_version": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps version to install, if empty the latest will be installed",
+ Description: "Parallels Desktop DevOps version to install, if empty the latest will be installed",
+ Optional: true,
+ },
+ "use_latest_beta": schema.BoolAttribute{
+ MarkdownDescription: "Enables the use of the latest beta",
+ Optional: true,
+ },
+ "root_password": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps root password",
+ Description: "Parallels Desktop DevOps root password",
+ Optional: true,
+ Sensitive: true,
+ },
+ "hmac_secret": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps HMAC secret, this is used to sign the JWT tokens",
+ Description: "Parallels Desktop DevOps HMAC secret, this is used to sign the JWT tokens",
+ Optional: true,
+ Sensitive: true,
+ },
+ "encryption_rsa_key": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps RSA key, this is used to encrypt database file on rest",
+ Description: "Parallels Desktop DevOps RSA key, this is used to encrypt database file on rest",
+ Optional: true,
+ Sensitive: true,
+ },
+ "log_level": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps log level, you can choose between debug, info, warn, error",
+ Description: "Parallels Desktop DevOps log level, you can choose between debug, info, warn, error",
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("debug", "info", "warn", "error"),
+ },
+ },
+ "enable_tls": schema.BoolAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps enable TLS",
+ Description: "Parallels Desktop DevOps enable TLS",
+ Optional: true,
+ },
+ "tls_port": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps TLS port",
+ Description: "Parallels Desktop DevOps TLS port",
+ Optional: true,
+ },
+ "tls_certificate": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps TLS certificate, this should be a PEM base64 encoded certificate string",
+ Description: "Parallels Desktop DevOps TLS certificate, this should be a PEM base64 encoded certificate string",
+ Optional: true,
+ Sensitive: true,
+ },
+ "tls_private_key": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps TLS private key, this should be a PEM base64 encoded private key string",
+ Description: "Parallels Desktop DevOps TLS private key, this should be a PEM base64 encoded private key string",
+ Optional: true,
+ Sensitive: true,
+ },
+ "disable_catalog_caching": schema.BoolAttribute{
+ MarkdownDescription: "Disable catalog caching, this will disable the ability to cache catalog items that are pulled from a remote catalog",
+ Description: "Disable catalog caching, this will disable the ability to cache catalog items that are pulled from a remote catalog",
+ Optional: true,
+ },
+ "token_duration_minutes": schema.StringAttribute{
+ MarkdownDescription: "JWT Token duration in minutes",
+ Description: "JWT Token duration in minutes",
+ Optional: true,
+ },
+ "mode": schema.StringAttribute{
+ MarkdownDescription: "API Operation mode, either orchestrator or catalog",
+ Optional: true,
+ Sensitive: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("orchestrator", "catalog", "api"),
+ },
+ },
+ "use_orchestrator_resources": schema.BoolAttribute{
+ MarkdownDescription: "Use orchestrator resources",
+ Optional: true,
+ },
+ "system_reserved_memory": schema.StringAttribute{
+ MarkdownDescription: "System reserved memory in MB",
+ Optional: true,
+ },
+ "system_reserved_cpu": schema.StringAttribute{
+ MarkdownDescription: "System reserved CPU in %",
+ Optional: true,
+ },
+ "system_reserved_disk": schema.StringAttribute{
+ MarkdownDescription: "System reserved disk in MB",
+ Optional: true,
+ },
+ "enable_logging": schema.BoolAttribute{
+ MarkdownDescription: "Enable logging",
+ Optional: true,
+ },
+ "environment_variables": schema.MapAttribute{
+ MarkdownDescription: "Environment variables that can be used in the DevOps service, please see documentation to see which variables are available",
+ Optional: true,
+ ElementType: types.StringType,
+ },
+ "enable_port_forwarding": schema.BoolAttribute{
+ MarkdownDescription: "Enable inbuilt reverse proxy for port forwarding",
+ Optional: true,
+ },
+ },
+}
diff --git a/internal/deploy/schemas/resource_schema_v0.go b/internal/deploy/schemas/resource_schema_v0.go
new file mode 100644
index 0000000..59ff307
--- /dev/null
+++ b/internal/deploy/schemas/resource_schema_v0.go
@@ -0,0 +1,86 @@
+package schemas
+
+import (
+ "terraform-provider-parallels-desktop/internal/schemas/orchestrator"
+ "terraform-provider-parallels-desktop/internal/schemas/sshconnection"
+
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+var DeployResourceSchemaV0 = schema.Schema{
+ // This description is used by the documentation generator and the language server.
+ MarkdownDescription: "Parallels Virtual Machine Deployment Resource",
+ Blocks: map[string]schema.Block{
+ ApiConfigSchemaName: ApiConfigSchemaBlockV0,
+ sshconnection.SchemaName: sshconnection.SchemaBlockV0,
+ orchestrator.SchemaName: orchestrator.SchemaBlockV0,
+ },
+ Version: 1,
+ Attributes: map[string]schema.Attribute{
+ "current_version": schema.StringAttribute{
+ MarkdownDescription: "Current version of Parallels Desktop",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "current_packer_version": schema.StringAttribute{
+ MarkdownDescription: "Current version of Hashicorp Packer",
+ Description: "Current version of Hashicorp Packer",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "current_vagrant_version": schema.StringAttribute{
+ MarkdownDescription: "Current version of Hashicorp Vagrant",
+ Description: "Current version of Hashicorp Vagrant",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "current_git_version": schema.StringAttribute{
+ MarkdownDescription: "Current version of Git",
+ Description: "Current version of Git",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "license": schema.ObjectAttribute{
+ MarkdownDescription: "Parallels Desktop license",
+ Computed: true,
+ AttributeTypes: map[string]attr.Type{
+ "state": types.StringType,
+ "key": types.StringType,
+ "restricted": types.BoolType,
+ },
+ },
+ "api": schema.ObjectAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps Service",
+ Computed: true,
+ AttributeTypes: map[string]attr.Type{
+ "version": types.StringType,
+ "protocol": types.StringType,
+ "host": types.StringType,
+ "port": types.StringType,
+ "user": types.StringType,
+ "password": types.StringType,
+ },
+ },
+ "installed_dependencies": schema.ListAttribute{
+ MarkdownDescription: "List of installed dependencies",
+ Computed: true,
+ ElementType: types.StringType,
+ },
+ "install_local": schema.BoolAttribute{
+ MarkdownDescription: "Deploy Parallels Desktop in the local machine, this will ignore the need to connect to a remote machine",
+ Optional: true,
+ },
+ },
+}
diff --git a/internal/deploy/schemas/resource_schema_v1.go b/internal/deploy/schemas/resource_schema_v1.go
new file mode 100644
index 0000000..b4ba953
--- /dev/null
+++ b/internal/deploy/schemas/resource_schema_v1.go
@@ -0,0 +1,86 @@
+package schemas
+
+import (
+ "terraform-provider-parallels-desktop/internal/schemas/orchestrator"
+ "terraform-provider-parallels-desktop/internal/schemas/sshconnection"
+
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+var DeployResourceSchemaV1 = schema.Schema{
+ // This description is used by the documentation generator and the language server.
+ MarkdownDescription: "Parallels Virtual Machine Deployment Resource",
+ Blocks: map[string]schema.Block{
+ ApiConfigSchemaName: ApiConfigSchemaBlockV1,
+ sshconnection.SchemaName: sshconnection.SchemaBlockV0,
+ orchestrator.SchemaName: orchestrator.SchemaBlockV0,
+ },
+ Version: 1,
+ Attributes: map[string]schema.Attribute{
+ "current_version": schema.StringAttribute{
+ MarkdownDescription: "Current version of Parallels Desktop",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "current_packer_version": schema.StringAttribute{
+ MarkdownDescription: "Current version of Hashicorp Packer",
+ Description: "Current version of Hashicorp Packer",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "current_vagrant_version": schema.StringAttribute{
+ MarkdownDescription: "Current version of Hashicorp Vagrant",
+ Description: "Current version of Hashicorp Vagrant",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "current_git_version": schema.StringAttribute{
+ MarkdownDescription: "Current version of Git",
+ Description: "Current version of Git",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "license": schema.ObjectAttribute{
+ MarkdownDescription: "Parallels Desktop license",
+ Computed: true,
+ AttributeTypes: map[string]attr.Type{
+ "state": types.StringType,
+ "key": types.StringType,
+ "restricted": types.BoolType,
+ },
+ },
+ "api": schema.ObjectAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps Service",
+ Computed: true,
+ AttributeTypes: map[string]attr.Type{
+ "version": types.StringType,
+ "protocol": types.StringType,
+ "host": types.StringType,
+ "port": types.StringType,
+ "user": types.StringType,
+ "password": types.StringType,
+ },
+ },
+ "installed_dependencies": schema.ListAttribute{
+ MarkdownDescription: "List of installed dependencies",
+ Computed: true,
+ ElementType: types.StringType,
+ },
+ "install_local": schema.BoolAttribute{
+ MarkdownDescription: "Deploy Parallels Desktop in the local machine, this will ignore the need to connect to a remote machine",
+ Optional: true,
+ },
+ },
+}
diff --git a/internal/deploy/resource_schema.go b/internal/deploy/schemas/resource_schema_v2.go
similarity index 54%
rename from internal/deploy/resource_schema.go
rename to internal/deploy/schemas/resource_schema_v2.go
index 9871d17..fd2eee8 100644
--- a/internal/deploy/resource_schema.go
+++ b/internal/deploy/schemas/resource_schema_v2.go
@@ -1,7 +1,8 @@
-package deploy
+package schemas
import (
"terraform-provider-parallels-desktop/internal/schemas/orchestrator"
+ "terraform-provider-parallels-desktop/internal/schemas/reverseproxy"
"terraform-provider-parallels-desktop/internal/schemas/sshconnection"
"github.com/hashicorp/terraform-plugin-framework/attr"
@@ -11,15 +12,16 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
)
-var deployResourceSchemaV0 = schema.Schema{
+var DeployResourceSchemaV2 = schema.Schema{
// This description is used by the documentation generator and the language server.
MarkdownDescription: "Parallels Virtual Machine Deployment Resource",
Blocks: map[string]schema.Block{
- ApiConfigSchemaName: apiConfigSchemaBlockV0,
- sshconnection.SchemaName: sshconnection.SchemaBlock,
- orchestrator.SchemaName: orchestrator.SchemaBlock,
+ ApiConfigSchemaName: ApiConfigSchemaBlockV2,
+ reverseproxy.SchemaName: reverseproxy.HostBlockV0,
+ sshconnection.SchemaName: sshconnection.SchemaBlockV0,
+ orchestrator.SchemaName: orchestrator.SchemaBlockV0,
},
- Version: 1,
+ Version: 2,
Attributes: map[string]schema.Attribute{
"current_version": schema.StringAttribute{
MarkdownDescription: "Current version of Parallels Desktop",
@@ -61,6 +63,14 @@ var deployResourceSchemaV0 = schema.Schema{
"restricted": types.BoolType,
},
},
+ "external_ip": schema.StringAttribute{
+ MarkdownDescription: "External IP address",
+ Description: "External IP address",
+ Computed: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
"api": schema.ObjectAttribute{
MarkdownDescription: "Parallels Desktop DevOps Service",
Computed: true,
@@ -82,79 +92,17 @@ var deployResourceSchemaV0 = schema.Schema{
MarkdownDescription: "Deploy Parallels Desktop in the local machine, this will ignore the need to connect to a remote machine",
Optional: true,
},
- },
-}
-
-var deployResourceSchemaV1 = schema.Schema{
- // This description is used by the documentation generator and the language server.
- MarkdownDescription: "Parallels Virtual Machine Deployment Resource",
- Blocks: map[string]schema.Block{
- ApiConfigSchemaName: apiConfigSchemaBlockV1,
- sshconnection.SchemaName: sshconnection.SchemaBlock,
- orchestrator.SchemaName: orchestrator.SchemaBlock,
- },
- Version: 1,
- Attributes: map[string]schema.Attribute{
- "current_version": schema.StringAttribute{
- MarkdownDescription: "Current version of Parallels Desktop",
- Computed: true,
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.UseStateForUnknown(),
- },
- },
- "current_packer_version": schema.StringAttribute{
- MarkdownDescription: "Current version of Hashicorp Packer",
- Description: "Current version of Hashicorp Packer",
- Computed: true,
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.UseStateForUnknown(),
- },
- },
- "current_vagrant_version": schema.StringAttribute{
- MarkdownDescription: "Current version of Hashicorp Vagrant",
- Description: "Current version of Hashicorp Vagrant",
- Computed: true,
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.UseStateForUnknown(),
- },
- },
- "current_git_version": schema.StringAttribute{
- MarkdownDescription: "Current version of Git",
- Description: "Current version of Git",
+ "is_registered_in_orchestrator": schema.BoolAttribute{
+ MarkdownDescription: "Is this host registered in the orchestrator",
Computed: true,
- PlanModifiers: []planmodifier.String{
- stringplanmodifier.UseStateForUnknown(),
- },
- },
- "license": schema.ObjectAttribute{
- MarkdownDescription: "Parallels Desktop license",
- Computed: true,
- AttributeTypes: map[string]attr.Type{
- "state": types.StringType,
- "key": types.StringType,
- "restricted": types.BoolType,
- },
},
- "api": schema.ObjectAttribute{
- MarkdownDescription: "Parallels Desktop DevOps Service",
+ "orchestrator_host": schema.StringAttribute{
+ MarkdownDescription: "Orchestrator host ID",
Computed: true,
- AttributeTypes: map[string]attr.Type{
- "version": types.StringType,
- "protocol": types.StringType,
- "host": types.StringType,
- "port": types.StringType,
- "user": types.StringType,
- "password": types.StringType,
- },
},
- "installed_dependencies": schema.ListAttribute{
- MarkdownDescription: "List of installed dependencies",
+ "orchestrator_host_id": schema.StringAttribute{
+ MarkdownDescription: "Orchestrator host ID",
Computed: true,
- ElementType: types.StringType,
- },
- "install_local": schema.BoolAttribute{
- MarkdownDescription: "Deploy Parallels Desktop in the local machine, this will ignore the need to connect to a remote machine",
- Optional: true,
},
},
}
diff --git a/internal/deploy/schemas/schema_names.go b/internal/deploy/schemas/schema_names.go
new file mode 100644
index 0000000..973708b
--- /dev/null
+++ b/internal/deploy/schemas/schema_names.go
@@ -0,0 +1,3 @@
+package schemas
+
+var ApiConfigSchemaName = "api_config"
diff --git a/internal/helpers/client.go b/internal/helpers/client.go
index ef4883a..036a54b 100644
--- a/internal/helpers/client.go
+++ b/internal/helpers/client.go
@@ -12,6 +12,7 @@ import (
"os"
"reflect"
"strings"
+ "time"
"terraform-provider-parallels-desktop/internal/clientmodels"
@@ -95,6 +96,8 @@ func (c *HttpCaller) RequestDataToClient(verb HttpCallerVerb, url string, header
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
+ // Set a timeout for the client for 15 seconds to avoid hanging
+ Timeout: 60 * time.Second,
}
}
var req *http.Request
diff --git a/internal/remoteimage/resource_models.go b/internal/remoteimage/models/resource_models_v0.go
similarity index 97%
rename from internal/remoteimage/resource_models.go
rename to internal/remoteimage/models/resource_models_v0.go
index 3c29509..ef9dc2b 100644
--- a/internal/remoteimage/resource_models.go
+++ b/internal/remoteimage/models/resource_models_v0.go
@@ -1,4 +1,4 @@
-package remoteimage
+package models
import (
"terraform-provider-parallels-desktop/internal/schemas/authenticator"
@@ -13,7 +13,7 @@ import (
)
// VirtualMachineStateResourceModel describes the resource data model.
-type RemoteVmResourceModel struct {
+type RemoteVmResourceModelV0 struct {
Authenticator *authenticator.Authentication `tfsdk:"authenticator"`
Host types.String `tfsdk:"host"`
Orchestrator types.String `tfsdk:"orchestrator"`
diff --git a/internal/remoteimage/models/resource_models_v1.go b/internal/remoteimage/models/resource_models_v1.go
new file mode 100644
index 0000000..1728218
--- /dev/null
+++ b/internal/remoteimage/models/resource_models_v1.go
@@ -0,0 +1,44 @@
+package models
+
+import (
+ "terraform-provider-parallels-desktop/internal/schemas/authenticator"
+ "terraform-provider-parallels-desktop/internal/schemas/postprocessorscript"
+ "terraform-provider-parallels-desktop/internal/schemas/prlctl"
+ "terraform-provider-parallels-desktop/internal/schemas/reverseproxy"
+ "terraform-provider-parallels-desktop/internal/schemas/sharedfolder"
+ "terraform-provider-parallels-desktop/internal/schemas/vmconfig"
+ "terraform-provider-parallels-desktop/internal/schemas/vmspecs"
+
+ "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+// VirtualMachineStateResourceModel describes the resource data model.
+type RemoteVmResourceModelV1 struct {
+ Authenticator *authenticator.Authentication `tfsdk:"authenticator"`
+ Host types.String `tfsdk:"host"`
+ Orchestrator types.String `tfsdk:"orchestrator"`
+ ID types.String `tfsdk:"id"`
+ ExternalIp types.String `tfsdk:"external_ip"`
+ InternalIp types.String `tfsdk:"internal_ip"`
+ OrchestratorHostId types.String `tfsdk:"orchestrator_host_id"`
+ OsType types.String `tfsdk:"os_type"`
+ CatalogId types.String `tfsdk:"catalog_id"`
+ Version types.String `tfsdk:"version"`
+ Architecture types.String `tfsdk:"architecture"`
+ Name types.String `tfsdk:"name"`
+ Owner types.String `tfsdk:"owner"`
+ CatalogConnection types.String `tfsdk:"catalog_connection"`
+ Path types.String `tfsdk:"path"`
+ Specs *vmspecs.VmSpecs `tfsdk:"specs"`
+ PostProcessorScripts []*postprocessorscript.PostProcessorScript `tfsdk:"post_processor_script"`
+ OnDestroyScript []*postprocessorscript.PostProcessorScript `tfsdk:"on_destroy_script"`
+ SharedFolder []*sharedfolder.SharedFolder `tfsdk:"shared_folder"`
+ Config *vmconfig.VmConfig `tfsdk:"config"`
+ PrlCtl []*prlctl.PrlCtlCmd `tfsdk:"prlctl"`
+ RunAfterCreate types.Bool `tfsdk:"run_after_create"`
+ Timeouts timeouts.Value `tfsdk:"timeouts"`
+ ForceChanges types.Bool `tfsdk:"force_changes"`
+ KeepRunning types.Bool `tfsdk:"keep_running"`
+ ReverseProxyHosts []*reverseproxy.ReverseProxyHost `tfsdk:"reverse_proxy_host"`
+}
diff --git a/internal/remoteimage/resource.go b/internal/remoteimage/resource.go
index f1d472f..a144522 100644
--- a/internal/remoteimage/resource.go
+++ b/internal/remoteimage/resource.go
@@ -8,11 +8,15 @@ import (
"terraform-provider-parallels-desktop/internal/apiclient"
"terraform-provider-parallels-desktop/internal/apiclient/apimodels"
"terraform-provider-parallels-desktop/internal/common"
- "terraform-provider-parallels-desktop/internal/models"
+ common_models "terraform-provider-parallels-desktop/internal/models"
+ "terraform-provider-parallels-desktop/internal/remoteimage/models"
+ "terraform-provider-parallels-desktop/internal/remoteimage/schemas"
"terraform-provider-parallels-desktop/internal/schemas/postprocessorscript"
+ "terraform-provider-parallels-desktop/internal/schemas/reverseproxy"
"terraform-provider-parallels-desktop/internal/telemetry"
"github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
@@ -31,7 +35,7 @@ func NewRemoteVmResource() resource.Resource {
// RemoteVmResource defines the resource implementation.
type RemoteVmResource struct {
- provider *models.ParallelsProviderModel
+ provider *common_models.ParallelsProviderModel
}
func (r *RemoteVmResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
@@ -39,7 +43,7 @@ func (r *RemoteVmResource) Metadata(ctx context.Context, req resource.MetadataRe
}
func (r *RemoteVmResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
- resp.Schema = getSchema(ctx)
+ resp.Schema = schemas.GetRemoteImageSchemaV1(ctx)
}
func (r *RemoteVmResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
@@ -47,11 +51,11 @@ func (r *RemoteVmResource) Configure(ctx context.Context, req resource.Configure
return
}
- data, ok := req.ProviderData.(*models.ParallelsProviderModel)
+ data, ok := req.ProviderData.(*common_models.ParallelsProviderModel)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
- fmt.Sprintf("Expected *models.ParallelsProviderModel, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+ fmt.Sprintf("Expected *common_modesl.ParallelsProviderModel, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)
return
}
@@ -60,7 +64,7 @@ func (r *RemoteVmResource) Configure(ctx context.Context, req resource.Configure
}
func (r *RemoteVmResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
- var data RemoteVmResourceModel
+ var data models.RemoteVmResourceModelV1
telemetrySvc := telemetry.Get(ctx)
telemetryEvent := telemetry.NewTelemetryItem(
@@ -118,8 +122,8 @@ func (r *RemoteVmResource) Create(ctx context.Context, req resource.CreateReques
return
}
- catalogManifest, diag := apiclient.GetCatalogManifest(ctx, *catalogHostConfig, data.CatalogId.ValueString(), data.Version.ValueString(), data.Architecture.ValueString())
- if diag.HasError() {
+ catalogManifest, catalogManifestDiag := apiclient.GetCatalogManifest(ctx, *catalogHostConfig, data.CatalogId.ValueString(), data.Version.ValueString(), data.Architecture.ValueString())
+ if catalogManifestDiag.HasError() {
resp.Diagnostics.AddError("Catalog Not Found", fmt.Sprintf("Catalog %s was not found on %s", data.CatalogId.ValueString(), catalogHostConfig.Host))
return
}
@@ -130,9 +134,9 @@ func (r *RemoteVmResource) Create(ctx context.Context, req resource.CreateReques
}
// Checking if the VM already exists in the host
- vm, diag := apiclient.GetVms(ctx, hostConfig, "Name", data.Name.String())
- if diag.HasError() {
- resp.Diagnostics.Append(diag...)
+ vm, vmDiag := apiclient.GetVms(ctx, hostConfig, "Name", data.Name.String())
+ if vmDiag.HasError() {
+ resp.Diagnostics.Append(vmDiag...)
return
}
@@ -145,8 +149,8 @@ func (r *RemoteVmResource) Create(ctx context.Context, req resource.CreateReques
architecture = catalogManifest.Architecture
}
- if diags := common.CheckIfEnoughSpecs(ctx, hostConfig, data.Specs, architecture); diags.HasError() {
- resp.Diagnostics.Append(diags...)
+ if specsDiag := common.CheckIfEnoughSpecs(ctx, hostConfig, data.Specs, architecture); specsDiag.HasError() {
+ resp.Diagnostics.Append(specsDiag...)
return
}
}
@@ -183,27 +187,29 @@ func (r *RemoteVmResource) Create(ctx context.Context, req resource.CreateReques
createMachineRequest.Owner = data.Owner.ValueString()
}
- response, diag := apiclient.CreateVm(ctx, hostConfig, createMachineRequest)
- if diag.HasError() {
- resp.Diagnostics.Append(diag...)
+ response, vmDiag := apiclient.CreateVm(ctx, hostConfig, createMachineRequest)
+ if vmDiag.HasError() {
+ resp.Diagnostics.Append(vmDiag...)
return
}
data.ID = types.StringValue(response.ID)
tflog.Info(ctx, "Created vm with id "+data.ID.ValueString())
- createdVM, diag := apiclient.GetVm(ctx, hostConfig, response.ID)
- if diag.HasError() {
- resp.Diagnostics.Append(diag...)
+ createdVM, vmDiag := apiclient.GetVm(ctx, hostConfig, response.ID)
+ if vmDiag.HasError() {
+ resp.Diagnostics.Append(vmDiag...)
return
}
+ hostConfig.HostId = createdVM.HostId
+
// stopping the machine as it might need some operations where the machine needs to be stopped
// add anything here in sequence that needs to be done before the machine is started
// so we do not loose time waiting for the machine to stop
- stoppedVm, diag := common.EnsureMachineStopped(ctx, hostConfig, createdVM)
- if diag.HasError() {
- resp.Diagnostics.Append(diag...)
+ stoppedVm, vmDiag := common.EnsureMachineStopped(ctx, hostConfig, createdVM)
+ if vmDiag.HasError() {
+ resp.Diagnostics.Append(vmDiag...)
}
// Applying the Specs block
@@ -213,6 +219,10 @@ func (r *RemoteVmResource) Create(ctx context.Context, req resource.CreateReques
if data.ID.ValueString() != "" {
// If we have an ID, we need to delete the machine
apiclient.SetMachineState(ctx, hostConfig, data.ID.ValueString(), apiclient.MachineStateOpStop)
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, stoppedVm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
apiclient.DeleteVm(ctx, hostConfig, data.ID.ValueString())
}
return
@@ -220,56 +230,111 @@ func (r *RemoteVmResource) Create(ctx context.Context, req resource.CreateReques
}
// Configuring the machine if there is any configuration
- if diag := common.VmConfigBlockOnCreate(ctx, hostConfig, stoppedVm, data.Config); diag.HasError() {
- resp.Diagnostics.Append(diag...)
+ if vmBlockDiag := common.VmConfigBlockOnCreate(ctx, hostConfig, stoppedVm, data.Config); vmBlockDiag.HasError() {
+ resp.Diagnostics.Append(vmBlockDiag...)
if data.ID.ValueString() != "" {
// If we have an ID, we need to delete the machine
apiclient.SetMachineState(ctx, hostConfig, data.ID.ValueString(), apiclient.MachineStateOpStop)
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, stoppedVm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
apiclient.DeleteVm(ctx, hostConfig, data.ID.ValueString())
}
return
}
// Applying any prlctl commands
- if diag := common.PrlCtlBlockOnCreate(ctx, hostConfig, stoppedVm, data.PrlCtl); diag.HasError() {
- resp.Diagnostics.Append(diag...)
+ if prlctlDiag := common.PrlCtlBlockOnCreate(ctx, hostConfig, stoppedVm, data.PrlCtl); prlctlDiag.HasError() {
+ resp.Diagnostics.Append(prlctlDiag...)
if data.ID.ValueString() != "" {
// If we have an ID, we need to delete the machine
apiclient.SetMachineState(ctx, hostConfig, data.ID.ValueString(), apiclient.MachineStateOpStop)
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, stoppedVm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
apiclient.DeleteVm(ctx, hostConfig, data.ID.ValueString())
}
return
}
// Processing shared folders
- if diag := common.SharedFoldersBlockOnCreate(ctx, hostConfig, stoppedVm, data.SharedFolder); diag.HasError() {
- resp.Diagnostics.Append(diag...)
+ if sharedFolderDiag := common.SharedFoldersBlockOnCreate(ctx, hostConfig, stoppedVm, data.SharedFolder); sharedFolderDiag.HasError() {
+ resp.Diagnostics.Append(sharedFolderDiag...)
if data.ID.ValueString() != "" {
// If we have an ID, we need to delete the machine
apiclient.SetMachineState(ctx, hostConfig, data.ID.ValueString(), apiclient.MachineStateOpStop)
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, stoppedVm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
apiclient.DeleteVm(ctx, hostConfig, data.ID.ValueString())
}
return
}
// Running the post processor scripts
- if diag := common.RunPostProcessorScript(ctx, hostConfig, stoppedVm, data.PostProcessorScripts); diag.HasError() {
- resp.Diagnostics.Append(diag...)
+ if postProcessDiag := common.RunPostProcessorScript(ctx, hostConfig, stoppedVm, data.PostProcessorScripts); postProcessDiag.HasError() {
+ resp.Diagnostics.Append(postProcessDiag...)
if data.ID.ValueString() != "" {
// If we have an ID, we need to delete the machine
apiclient.SetMachineState(ctx, hostConfig, data.ID.ValueString(), apiclient.MachineStateOpStop)
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, stoppedVm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
apiclient.DeleteVm(ctx, hostConfig, data.ID.ValueString())
}
return
}
- // Starting the vm if requested
- if data.RunAfterCreate.ValueBool() {
+ if len(data.ReverseProxyHosts) > 0 {
+ rpHostConfig := hostConfig
+ rpHostConfig.HostId = stoppedVm.HostId
+ rpHosts, updateDiag := updateReverseProxyHostsTarget(ctx, &data, rpHostConfig, stoppedVm)
+ if updateDiag.HasError() {
+ resp.Diagnostics.Append(updateDiag...)
+ return
+ }
+
+ result, createDiag := reverseproxy.Create(ctx, rpHostConfig, rpHosts)
+ if createDiag.HasError() {
+ resp.Diagnostics.Append(createDiag...)
+
+ if diag := reverseproxy.Delete(ctx, rpHostConfig, rpHosts); diag.HasError() {
+ tflog.Error(ctx, "Error deleting reverse proxy hosts")
+ }
+
+ if data.ID.ValueString() != "" {
+ // If we have an ID, we need to delete the machine
+ apiclient.SetMachineState(ctx, rpHostConfig, data.ID.ValueString(), apiclient.MachineStateOpStop)
+ if _, diag := common.EnsureMachineStopped(ctx, rpHostConfig, stoppedVm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
+
+ apiclient.DeleteVm(ctx, rpHostConfig, data.ID.ValueString())
+ }
+ return
+ }
+
+ for i := range result {
+ data.ReverseProxyHosts[i].ID = result[i].ID
+ }
+ }
+
+ // Starting the vm by default, otherwise we will stop the VM from being created
+ if data.RunAfterCreate.ValueBool() || data.KeepRunning.ValueBool() || (data.RunAfterCreate.IsUnknown() && data.KeepRunning.IsUnknown()) {
if _, diag := common.EnsureMachineRunning(ctx, hostConfig, stoppedVm); diag.HasError() {
resp.Diagnostics.Append(diag...)
if data.ID.ValueString() != "" {
// If we have an ID, we need to delete the machine
apiclient.SetMachineState(ctx, hostConfig, data.ID.ValueString(), apiclient.MachineStateOpStop)
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, stoppedVm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
apiclient.DeleteVm(ctx, hostConfig, data.ID.ValueString())
}
return
@@ -280,9 +345,38 @@ func (r *RemoteVmResource) Create(ctx context.Context, req resource.CreateReques
resp.Diagnostics.Append(diag...)
return
}
+ } else {
+ // If we are not starting the machine, we will stop it
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, stoppedVm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ if data.ID.ValueString() != "" {
+ // If we have an ID, we need to delete the machine
+ apiclient.SetMachineState(ctx, hostConfig, data.ID.ValueString(), apiclient.MachineStateOpStop)
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, stoppedVm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
+ apiclient.DeleteVm(ctx, hostConfig, data.ID.ValueString())
+ }
+ return
+ }
+ }
+
+ externalIp := ""
+ internalIp := ""
+ refreshVm, refreshDiag := apiclient.GetVm(ctx, hostConfig, response.ID)
+ if refreshDiag.HasError() {
+ resp.Diagnostics.Append(refreshDiag...)
+ return
+ } else {
+ externalIp = refreshVm.HostExternalIpAddress
+ internalIp = refreshVm.InternalIpAddress
}
data.OsType = types.StringValue(createdVM.OS)
+ data.ExternalIp = types.StringValue(externalIp)
+ data.InternalIp = types.StringValue(internalIp)
+ data.OrchestratorHostId = types.StringValue(refreshVm.HostId)
if data.OnDestroyScript != nil {
for _, script := range data.OnDestroyScript {
elements := make([]attr.Value, 0)
@@ -309,11 +403,23 @@ func (r *RemoteVmResource) Create(ctx context.Context, req resource.CreateReques
}
}
+ // Setting the state of the vm to running if that is required
+ if (data.RunAfterCreate.ValueBool() || data.RunAfterCreate.IsUnknown() || data.RunAfterCreate.IsNull()) && (refreshVm.State == "stopped") {
+ if _, diag := common.EnsureMachineRunning(ctx, hostConfig, refreshVm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
+ }
+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
if resp.Diagnostics.HasError() {
if data.ID.ValueString() != "" {
// If we have an ID, we need to delete the machine
apiclient.SetMachineState(ctx, hostConfig, data.ID.ValueString(), apiclient.MachineStateOpStop)
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, stoppedVm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
apiclient.DeleteVm(ctx, hostConfig, data.ID.ValueString())
}
return
@@ -321,7 +427,7 @@ func (r *RemoteVmResource) Create(ctx context.Context, req resource.CreateReques
}
func (r *RemoteVmResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
- var data RemoteVmResourceModel
+ var data models.RemoteVmResourceModelV1
telemetrySvc := telemetry.Get(ctx)
telemetryEvent := telemetry.NewTelemetryItem(
@@ -393,8 +499,8 @@ func (r *RemoteVmResource) Read(ctx context.Context, req resource.ReadRequest, r
}
func (r *RemoteVmResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
- var data RemoteVmResourceModel
- var currentData RemoteVmResourceModel
+ var data models.RemoteVmResourceModelV1
+ var currentData models.RemoteVmResourceModelV1
telemetrySvc := telemetry.Get(ctx)
telemetryEvent := telemetry.NewTelemetryItem(
@@ -456,6 +562,8 @@ func (r *RemoteVmResource) Update(ctx context.Context, req resource.UpdateReques
return
}
+ hostConfig.HostId = vm.HostId
+
nameChanges := apimodels.NewVmConfigRequest(vm.User)
currentState := vm.State
needsRestart := false
@@ -505,6 +613,12 @@ func (r *RemoteVmResource) Update(ctx context.Context, req resource.UpdateReques
// Applying the Specs block
if specsChanges {
+ // We need to stop the machine if it is not stopped
+ if vm.State != "stopped" && !data.ForceChanges.ValueBool() {
+ resp.Diagnostics.AddError("vm must be stopped before updating", "Virtual Machine "+vm.Name+" must be stopped before updating, currently "+vm.State)
+ return
+ }
+
if diags := common.SpecsBlockOnUpdate(ctx, hostConfig, vm, data.Specs, currentData.Specs); diags.HasError() {
resp.Diagnostics.Append(diags...)
return
@@ -553,8 +667,84 @@ func (r *RemoteVmResource) Update(ctx context.Context, req resource.UpdateReques
data.PostProcessorScripts = currentData.PostProcessorScripts
}
+ if reverseproxy.ReverseProxyHostsDiff(data.ReverseProxyHosts, currentData.ReverseProxyHosts) {
+ copyCurrentRpHosts := reverseproxy.CopyReverseProxyHosts(currentData.ReverseProxyHosts)
+ copyRpHosts := reverseproxy.CopyReverseProxyHosts(data.ReverseProxyHosts)
+
+ results, updateDiag := reverseproxy.Update(ctx, hostConfig, copyCurrentRpHosts, copyRpHosts)
+ if updateDiag.HasError() {
+ resp.Diagnostics.Append(updateDiag...)
+ revertResults, _ := reverseproxy.Revert(ctx, hostConfig, copyCurrentRpHosts, copyRpHosts)
+ for i := range revertResults {
+ data.ReverseProxyHosts[i].ID = revertResults[i].ID
+ }
+ return
+ }
+
+ for i := range results {
+ data.ReverseProxyHosts[i].ID = results[i].ID
+ }
+ } else {
+ for i := range currentData.ReverseProxyHosts {
+ data.ReverseProxyHosts[i].ID = currentData.ReverseProxyHosts[i].ID
+ }
+ }
+
+ // Starting the vm by default, otherwise we will stop the VM from being created
+ if data.RunAfterCreate.ValueBool() || data.KeepRunning.ValueBool() || (data.RunAfterCreate.IsUnknown() && data.KeepRunning.IsUnknown()) {
+ if _, diag := common.EnsureMachineRunning(ctx, hostConfig, vm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ if data.ID.ValueString() != "" {
+ // If we have an ID, we need to delete the machine
+ apiclient.SetMachineState(ctx, hostConfig, data.ID.ValueString(), apiclient.MachineStateOpStop)
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, vm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
+ apiclient.DeleteVm(ctx, hostConfig, data.ID.ValueString())
+ }
+ return
+ }
+
+ _, diag := apiclient.GetVm(ctx, hostConfig, vm.ID)
+ if diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
+ } else {
+ // If we are not starting the machine, we will stop it
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, vm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ if data.ID.ValueString() != "" {
+ // If we have an ID, we need to delete the machine
+ apiclient.SetMachineState(ctx, hostConfig, data.ID.ValueString(), apiclient.MachineStateOpStop)
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, vm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
+ apiclient.DeleteVm(ctx, hostConfig, data.ID.ValueString())
+ }
+ return
+ }
+ }
+
+ externalIp := ""
+ internalIp := ""
+ refreshVm, refreshDiag := apiclient.GetVm(ctx, hostConfig, vm.ID)
+ if refreshDiag.HasError() {
+ resp.Diagnostics.Append(refreshDiag...)
+ return
+ } else {
+ externalIp = refreshVm.HostExternalIpAddress
+ internalIp = refreshVm.InternalIpAddress
+ }
+
data.ID = types.StringValue(vm.ID)
- data.OsType = types.StringValue(vm.OS)
+ data.OsType = types.StringValue(refreshVm.OS)
+ data.ExternalIp = types.StringValue(externalIp)
+ data.InternalIp = types.StringValue(internalIp)
+ data.OrchestratorHostId = types.StringValue(refreshVm.HostId)
+
if data.OnDestroyScript != nil {
for _, script := range data.OnDestroyScript {
elements := make([]attr.Value, 0)
@@ -581,6 +771,13 @@ func (r *RemoteVmResource) Update(ctx context.Context, req resource.UpdateReques
}
}
+ if (data.RunAfterCreate.ValueBool() || data.RunAfterCreate.IsUnknown() || data.RunAfterCreate.IsNull()) && (vm.State == "stopped") {
+ if _, diag := common.EnsureMachineRunning(ctx, hostConfig, vm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
+ }
+
tflog.Info(ctx, "Updated vm with id "+data.ID.ValueString()+" and name "+data.Name.ValueString())
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
@@ -590,7 +787,7 @@ func (r *RemoteVmResource) Update(ctx context.Context, req resource.UpdateReques
}
func (r *RemoteVmResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
- var data RemoteVmResourceModel
+ var data models.RemoteVmResourceModelV1
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
@@ -653,6 +850,8 @@ func (r *RemoteVmResource) Delete(ctx context.Context, req resource.DeleteReques
if vm == nil {
resp.Diagnostics.Append(req.State.Set(ctx, &data)...)
return
+ } else {
+ hostConfig.HostId = vm.HostId
}
// Running cleanup script if any
@@ -693,6 +892,16 @@ func (r *RemoteVmResource) Delete(ctx context.Context, req resource.DeleteReques
time.Sleep(10 * time.Second)
}
+ if len(data.ReverseProxyHosts) > 0 {
+ rpHostConfig := hostConfig
+ rpHostConfig.HostId = vm.HostId
+ rpHostsCopy := reverseproxy.CopyReverseProxyHosts(data.ReverseProxyHosts)
+ if diag := reverseproxy.Delete(ctx, rpHostConfig, rpHostsCopy); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
+ }
+
resp.Diagnostics.Append(req.State.Set(ctx, &data)...)
if resp.Diagnostics.HasError() {
@@ -703,3 +912,123 @@ func (r *RemoteVmResource) Delete(ctx context.Context, req resource.DeleteReques
func (r *RemoteVmResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
}
+
+func (r *RemoteVmResource) UpgradeState(ctx context.Context) map[int64]resource.StateUpgrader {
+ v0Schema := schemas.GetRemoteImageSchemaV0(ctx)
+ return map[int64]resource.StateUpgrader{
+ 0: {
+ PriorSchema: &v0Schema,
+ StateUpgrader: UpgradeStateToV1,
+ },
+ }
+}
+
+func UpgradeStateToV1(ctx context.Context, req resource.UpgradeStateRequest, resp *resource.UpgradeStateResponse) {
+ var priorStateData models.RemoteVmResourceModelV0
+ resp.Diagnostics.Append(req.State.Get(ctx, &priorStateData)...)
+
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ upgradedStateData := models.RemoteVmResourceModelV1{
+ Authenticator: priorStateData.Authenticator,
+ Host: priorStateData.Host,
+ Orchestrator: priorStateData.Orchestrator,
+ ID: priorStateData.ID,
+ OsType: priorStateData.OsType,
+ ExternalIp: types.StringUnknown(),
+ InternalIp: types.StringUnknown(),
+ OrchestratorHostId: types.StringUnknown(),
+ CatalogId: priorStateData.CatalogId,
+ Version: priorStateData.Version,
+ Architecture: priorStateData.Architecture,
+ Name: priorStateData.Name,
+ Owner: priorStateData.Owner,
+ CatalogConnection: priorStateData.CatalogConnection,
+ Path: priorStateData.Path,
+ Specs: priorStateData.Specs,
+ PostProcessorScripts: priorStateData.PostProcessorScripts,
+ OnDestroyScript: priorStateData.OnDestroyScript,
+ SharedFolder: priorStateData.SharedFolder,
+ Config: priorStateData.Config,
+ PrlCtl: priorStateData.PrlCtl,
+ RunAfterCreate: priorStateData.RunAfterCreate,
+ Timeouts: priorStateData.Timeouts,
+ ForceChanges: priorStateData.ForceChanges,
+ KeepRunning: types.BoolValue(true),
+ ReverseProxyHosts: make([]*reverseproxy.ReverseProxyHost, 0),
+ }
+
+ println(fmt.Sprintf("Upgrading state from version %v", upgradedStateData))
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &upgradedStateData)...)
+}
+
+func updateReverseProxyHostsTarget(ctx context.Context, data *models.RemoteVmResourceModelV1, hostConfig apiclient.HostConfig, targetVm *apimodels.VirtualMachine) ([]reverseproxy.ReverseProxyHost, diag.Diagnostics) {
+ resultDiagnostic := diag.Diagnostics{}
+ var refreshedVm *apimodels.VirtualMachine
+ var rpDiag diag.Diagnostics
+ refreshedVm, rpDiag = common.EnsureMachineHasInternalIp(ctx, hostConfig, targetVm)
+ if rpDiag.HasError() {
+ resultDiagnostic.Append(rpDiag...)
+ if data.ID.ValueString() != "" {
+ // If we have an ID, we need to delete the machine
+ apiclient.SetMachineState(ctx, hostConfig, data.ID.ValueString(), apiclient.MachineStateOpStop)
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, refreshedVm); diag.HasError() {
+ return nil, diag
+ }
+ apiclient.DeleteVm(ctx, hostConfig, data.ID.ValueString())
+ }
+ return nil, resultDiagnostic
+ }
+
+ modifiedHosts := make([]reverseproxy.ReverseProxyHost, len(data.ReverseProxyHosts))
+ for i := range data.ReverseProxyHosts {
+ host := reverseproxy.ReverseProxyHost{}
+ host.Host = data.ReverseProxyHosts[i].Host
+ host.Port = data.ReverseProxyHosts[i].Port
+ internalIp := refreshedVm.InternalIpAddress
+ emptyString := ""
+
+ if data.ReverseProxyHosts[i].Cors != nil {
+ host.Cors = &reverseproxy.ReverseProxyCors{}
+ host.Cors.AllowedOrigins = data.ReverseProxyHosts[i].Cors.AllowedOrigins
+ host.Cors.AllowedMethods = data.ReverseProxyHosts[i].Cors.AllowedMethods
+ host.Cors.AllowedHeaders = data.ReverseProxyHosts[i].Cors.AllowedHeaders
+ host.Cors.Enabled = data.ReverseProxyHosts[i].Cors.Enabled
+ }
+ if data.ReverseProxyHosts[i].Tls != nil {
+ host.Tls = &reverseproxy.ReverseProxyTls{}
+ host.Tls.Certificate = data.ReverseProxyHosts[i].Tls.Certificate
+ host.Tls.PrivateKey = data.ReverseProxyHosts[i].Tls.PrivateKey
+ host.Tls.Enabled = data.ReverseProxyHosts[i].Tls.Enabled
+ }
+ if data.ReverseProxyHosts[i].TcpRoute != nil {
+ host.TcpRoute = &reverseproxy.ReverseProxyHostTcpRoute{}
+ host.TcpRoute.TargetPort = data.ReverseProxyHosts[i].TcpRoute.TargetPort
+ host.TcpRoute.TargetHost = types.StringValue(internalIp)
+ host.TcpRoute.TargetVmId = types.StringValue(emptyString)
+ }
+
+ if len(data.ReverseProxyHosts[i].HttpRoute) > 0 {
+ host.HttpRoute = make([]*reverseproxy.ReverseProxyHttpRoute, len(data.ReverseProxyHosts[i].HttpRoute))
+ for j := range modifiedHosts[i].HttpRoute {
+ httpRoute := reverseproxy.ReverseProxyHttpRoute{}
+ httpRoute.Path = data.ReverseProxyHosts[i].HttpRoute[j].Path
+ httpRoute.TargetHost = types.StringValue(internalIp)
+ httpRoute.TargetPort = data.ReverseProxyHosts[i].HttpRoute[j].TargetPort
+ httpRoute.TargetVmId = types.StringValue(emptyString)
+ httpRoute.Pattern = data.ReverseProxyHosts[i].HttpRoute[j].Pattern
+ httpRoute.Schema = data.ReverseProxyHosts[i].HttpRoute[j].Schema
+ httpRoute.RequestHeaders = data.ReverseProxyHosts[i].HttpRoute[j].RequestHeaders
+ httpRoute.ResponseHeaders = data.ReverseProxyHosts[i].HttpRoute[j].ResponseHeaders
+ host.HttpRoute[j] = &httpRoute
+ }
+ }
+
+ modifiedHosts[i] = host
+ }
+
+ return modifiedHosts, resultDiagnostic
+}
diff --git a/internal/remoteimage/resource_schema.go b/internal/remoteimage/schemas/resource_schema_v0.go
similarity index 97%
rename from internal/remoteimage/resource_schema.go
rename to internal/remoteimage/schemas/resource_schema_v0.go
index 53926a0..5450090 100644
--- a/internal/remoteimage/resource_schema.go
+++ b/internal/remoteimage/schemas/resource_schema_v0.go
@@ -1,4 +1,4 @@
-package remoteimage
+package schemas
import (
"context"
@@ -19,7 +19,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)
-func getSchema(ctx context.Context) schema.Schema {
+func GetRemoteImageSchemaV0(ctx context.Context) schema.Schema {
return schema.Schema{
// This description is used by the documentation generator and the language server.
MarkdownDescription: "Parallels Virtual Machine State Resource",
@@ -32,6 +32,7 @@ func getSchema(ctx context.Context) schema.Schema {
vmconfig.SchemaName: vmconfig.SchemaBlock,
prlctl.SchemaName: prlctl.SchemaBlock,
},
+ Version: 0,
Attributes: map[string]schema.Attribute{
"timeouts": timeouts.Attributes(ctx, timeouts.Opts{
Create: true,
diff --git a/internal/remoteimage/schemas/resource_schema_v1.go b/internal/remoteimage/schemas/resource_schema_v1.go
new file mode 100644
index 0000000..7429608
--- /dev/null
+++ b/internal/remoteimage/schemas/resource_schema_v1.go
@@ -0,0 +1,146 @@
+package schemas
+
+import (
+ "context"
+
+ "terraform-provider-parallels-desktop/internal/schemas/authenticator"
+ "terraform-provider-parallels-desktop/internal/schemas/postprocessorscript"
+ "terraform-provider-parallels-desktop/internal/schemas/prlctl"
+ "terraform-provider-parallels-desktop/internal/schemas/reverseproxy"
+ "terraform-provider-parallels-desktop/internal/schemas/sharedfolder"
+ "terraform-provider-parallels-desktop/internal/schemas/vmconfig"
+ "terraform-provider-parallels-desktop/internal/schemas/vmspecs"
+
+ "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+)
+
+func GetRemoteImageSchemaV1(ctx context.Context) schema.Schema {
+ return schema.Schema{
+ // This description is used by the documentation generator and the language server.
+ MarkdownDescription: "Parallels Virtual Machine State Resource",
+ Blocks: map[string]schema.Block{
+ authenticator.SchemaName: authenticator.SchemaBlock,
+ vmspecs.SchemaName: vmspecs.SchemaBlock,
+ postprocessorscript.SchemaName: postprocessorscript.SchemaBlock,
+ "on_destroy_script": postprocessorscript.SchemaBlock,
+ sharedfolder.SchemaName: sharedfolder.SchemaBlock,
+ vmconfig.SchemaName: vmconfig.SchemaBlock,
+ prlctl.SchemaName: prlctl.SchemaBlock,
+ reverseproxy.SchemaName: reverseproxy.HostBlockV0,
+ },
+ Version: 1,
+ Attributes: map[string]schema.Attribute{
+ "timeouts": timeouts.Attributes(ctx, timeouts.Opts{
+ Create: true,
+ }),
+ "force_changes": schema.BoolAttribute{
+ MarkdownDescription: "Force changes, this will force the VM to be stopped and started again",
+ Optional: true,
+ },
+ "host": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps Host",
+ Optional: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ Validators: []validator.String{
+ stringvalidator.AtLeastOneOf(path.Expressions{
+ path.MatchRoot("orchestrator"),
+ path.MatchRoot("host"),
+ }...),
+ },
+ },
+ "orchestrator": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps Orchestrator",
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.AtLeastOneOf(path.Expressions{
+ path.MatchRoot("orchestrator"),
+ path.MatchRoot("host"),
+ }...),
+ },
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "id": schema.StringAttribute{
+ MarkdownDescription: "Virtual Machine Id",
+ Computed: true,
+ },
+ "orchestrator_host_id": schema.StringAttribute{
+ MarkdownDescription: "Orchestrator Host Id if the VM is running in an orchestrator",
+ Computed: true,
+ },
+ "os_type": schema.StringAttribute{
+ MarkdownDescription: "Virtual Machine OS type",
+ Computed: true,
+ },
+ "catalog_id": schema.StringAttribute{
+ MarkdownDescription: "Catalog Id to pull",
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "version": schema.StringAttribute{
+ MarkdownDescription: "Catalog version to pull, if empty will pull the 'latest' version",
+ Optional: true,
+ },
+ "architecture": schema.StringAttribute{
+ MarkdownDescription: "Virtual Machine architecture",
+ Optional: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "name": schema.StringAttribute{
+ MarkdownDescription: "Virtual Machine name to create, this needs to be unique in the host",
+ Required: true,
+ },
+ "owner": schema.StringAttribute{
+ MarkdownDescription: "Virtual Machine owner",
+ Optional: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "catalog_connection": schema.StringAttribute{
+ MarkdownDescription: "Parallels DevOps Catalog Connection",
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "path": schema.StringAttribute{
+ MarkdownDescription: "Path",
+ Required: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "run_after_create": schema.BoolAttribute{
+ MarkdownDescription: "Run after create, this will make the VM to run after creation",
+ Optional: true,
+ DeprecationMessage: "Use the `keep_running` attribute instead",
+ },
+ "external_ip": schema.StringAttribute{
+ MarkdownDescription: "VM external IP address",
+ Computed: true,
+ },
+ "internal_ip": schema.StringAttribute{
+ MarkdownDescription: "VM internal IP address",
+ Computed: true,
+ },
+ "keep_running": schema.BoolAttribute{
+ MarkdownDescription: "This will keep the VM running after the terraform apply",
+ Optional: true,
+ },
+ },
+ }
+}
diff --git a/internal/schemas/orchestrator/main.go b/internal/schemas/orchestrator/main.go
index 01d3922..96f8657 100644
--- a/internal/schemas/orchestrator/main.go
+++ b/internal/schemas/orchestrator/main.go
@@ -2,6 +2,7 @@ package orchestrator
import (
"context"
+ "strings"
"terraform-provider-parallels-desktop/internal/apiclient"
"terraform-provider-parallels-desktop/internal/apiclient/apimodels"
@@ -75,8 +76,11 @@ func RegisterWithHost(context context.Context, plan OrchestratorRegistration, di
return "", diagnostics
}
-func IsAlreadyRegistered(context context.Context, data OrchestratorRegistration, disableTlsValidation bool) (bool, diag.Diagnostics) {
+func IsAlreadyRegistered(context context.Context, data OrchestratorRegistration, disableTlsValidation bool) (bool, *apimodels.OrchestratorHost, diag.Diagnostics) {
diagnostics := diag.Diagnostics{}
+ if data.Orchestrator == nil {
+ return false, nil, diagnostics
+ }
hostConfig := apiclient.HostConfig{
Host: data.Orchestrator.GetHost(),
@@ -90,16 +94,25 @@ func IsAlreadyRegistered(context context.Context, data OrchestratorRegistration,
currentHostId := data.HostId.ValueString()
currentHostUrl := helpers.GetHostApiBaseUrl(data.GetHost())
- response, _ := apiclient.GetOrchestratorHost(context, hostConfig, data.HostId.ValueString())
+ currentHostDescription := data.Description.ValueString()
+ response, _ := apiclient.GetOrchestratorHosts(context, hostConfig)
if response == nil {
- return false, diagnostics
+ return false, nil, diagnostics
}
- if currentHostId == response.ID || currentHostUrl == response.Host {
- return true, diagnostics
+ if len(response) == 0 {
+ return false, nil, diagnostics
+ }
+
+ for _, host := range response {
+ if strings.EqualFold(currentHostId, host.ID) ||
+ strings.EqualFold(currentHostUrl, host.Host) ||
+ strings.EqualFold(currentHostDescription, host.Description) {
+ return true, &host, diagnostics
+ }
}
- return false, diagnostics
+ return false, nil, diagnostics
}
func UnregisterWithHost(context context.Context, data OrchestratorRegistration, disableTlsValidation bool) diag.Diagnostics {
diff --git a/internal/schemas/orchestrator/schema.go b/internal/schemas/orchestrator/schema.go
index d012fb9..bc5c088 100644
--- a/internal/schemas/orchestrator/schema.go
+++ b/internal/schemas/orchestrator/schema.go
@@ -9,41 +9,43 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types"
)
-var SchemaName = "orchestrator_registration"
-var SchemaBlock = schema.SingleNestedBlock{
- MarkdownDescription: "Orchestrator connection details",
- Blocks: map[string]schema.Block{
- "host_credentials": authenticator.SchemaBlock,
- OrchestratorSchemaName: OrchestratorSchemaBlock,
- },
- Attributes: map[string]schema.Attribute{
- "schema": schema.StringAttribute{
- MarkdownDescription: "Host Schema",
- Optional: true,
+var (
+ SchemaName = "orchestrator_registration"
+ SchemaBlockV0 = schema.SingleNestedBlock{
+ MarkdownDescription: "Orchestrator connection details",
+ Blocks: map[string]schema.Block{
+ "host_credentials": authenticator.SchemaBlock,
+ OrchestratorSchemaName: OrchestratorSchemaBlock,
},
- "host": schema.StringAttribute{
- MarkdownDescription: "Host address",
- Optional: true,
- Validators: []validator.String{
- stringvalidator.LengthAtLeast(1),
+ Attributes: map[string]schema.Attribute{
+ "schema": schema.StringAttribute{
+ MarkdownDescription: "Host Schema",
+ Optional: true,
+ },
+ "host": schema.StringAttribute{
+ MarkdownDescription: "Host address",
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.LengthAtLeast(1),
+ },
+ },
+ "port": schema.StringAttribute{
+ MarkdownDescription: "Host port",
+ Optional: true,
+ },
+ "tags": schema.ListAttribute{
+ MarkdownDescription: "Host tags",
+ Optional: true,
+ ElementType: types.StringType,
+ },
+ "description": schema.StringAttribute{
+ MarkdownDescription: "Host description",
+ Optional: true,
+ },
+ "host_id": schema.StringAttribute{
+ MarkdownDescription: "Host Orchestrator ID",
+ Computed: true,
},
},
- "port": schema.StringAttribute{
- MarkdownDescription: "Host port",
- Optional: true,
- },
- "tags": schema.ListAttribute{
- MarkdownDescription: "Host tags",
- Optional: true,
- ElementType: types.StringType,
- },
- "description": schema.StringAttribute{
- MarkdownDescription: "Host description",
- Optional: true,
- },
- "host_id": schema.StringAttribute{
- MarkdownDescription: "Host Orchestrator ID",
- Computed: true,
- },
- },
-}
+ }
+)
diff --git a/internal/schemas/reverseproxy/common.go b/internal/schemas/reverseproxy/common.go
new file mode 100644
index 0000000..5188c95
--- /dev/null
+++ b/internal/schemas/reverseproxy/common.go
@@ -0,0 +1,3 @@
+package reverseproxy
+
+var SchemaName = "reverse_proxy_host"
diff --git a/internal/schemas/reverseproxy/cors_schema_v0.go b/internal/schemas/reverseproxy/cors_schema_v0.go
new file mode 100644
index 0000000..f50f6be
--- /dev/null
+++ b/internal/schemas/reverseproxy/cors_schema_v0.go
@@ -0,0 +1,48 @@
+package reverseproxy
+
+import (
+ "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
+
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+var CorsSchemaBlockV0 = schema.SingleNestedBlock{
+ MarkdownDescription: "Parallels Desktop DevOps Reverse Proxy Http Route CORS configuration",
+ Description: "Parallels Desktop DevOps Reverse Proxy Http Route CORS configuration",
+ Attributes: map[string]schema.Attribute{
+ "enabled": schema.BoolAttribute{
+ MarkdownDescription: "Enable CORS",
+ Description: "Enable CORS",
+ Optional: true,
+ },
+ "allowed_origins": schema.ListAttribute{
+ MarkdownDescription: "Allowed origins",
+ Description: "Allowed origins",
+ Optional: true,
+ ElementType: types.StringType,
+ Validators: []validator.List{
+ listvalidator.SizeAtLeast(1),
+ },
+ },
+ "allowed_methods": schema.ListAttribute{
+ MarkdownDescription: "Allowed methods",
+ Description: "Allowed methods",
+ Optional: true,
+ ElementType: types.StringType,
+ Validators: []validator.List{
+ listvalidator.SizeAtLeast(1),
+ },
+ },
+ "allowed_headers": schema.ListAttribute{
+ MarkdownDescription: "Allowed headers",
+ Description: "Allowed headers",
+ Optional: true,
+ ElementType: types.StringType,
+ Validators: []validator.List{
+ listvalidator.SizeAtLeast(1),
+ },
+ },
+ },
+}
diff --git a/internal/schemas/reverseproxy/host_schema_v0.go b/internal/schemas/reverseproxy/host_schema_v0.go
new file mode 100644
index 0000000..051ea33
--- /dev/null
+++ b/internal/schemas/reverseproxy/host_schema_v0.go
@@ -0,0 +1,33 @@
+package reverseproxy
+
+import "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+
+var HostBlockV0 = schema.ListNestedBlock{
+ MarkdownDescription: "Parallels Desktop DevOps Reverse Proxy configuration",
+ Description: "Parallels Desktop DevOps Reverse Proxy configuration",
+ NestedObject: schema.NestedBlockObject{
+ Blocks: map[string]schema.Block{
+ "tcp_route": TcpRouteSchemaBlockV0,
+ "cors": CorsSchemaBlockV0,
+ "tls": TlsSchemaBlockV0,
+ "http_routes": HttpRouteSchemaBlockV0,
+ },
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ MarkdownDescription: "Reverse proxy Host id",
+ Description: "Reverse proxy Host id",
+ Computed: true,
+ },
+ "host": schema.StringAttribute{
+ MarkdownDescription: "Reverse proxy host",
+ Description: "Reverse proxy host",
+ Optional: true,
+ },
+ "port": schema.StringAttribute{
+ MarkdownDescription: "Reverse proxy port",
+ Description: "Reverse proxy port",
+ Required: true,
+ },
+ },
+ },
+}
diff --git a/internal/schemas/reverseproxy/http_route_schema_v0.go b/internal/schemas/reverseproxy/http_route_schema_v0.go
new file mode 100644
index 0000000..5f48230
--- /dev/null
+++ b/internal/schemas/reverseproxy/http_route_schema_v0.go
@@ -0,0 +1,84 @@
+package reverseproxy
+
+import (
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+var HttpRouteSchemaBlockV0 = schema.ListNestedBlock{
+ MarkdownDescription: "Parallels Desktop DevOps Reverse Proxy Http Route CORS configuration",
+ Description: "Parallels Desktop DevOps Reverse Proxy Http Route CORS configuration",
+ NestedObject: schema.NestedBlockObject{
+ Attributes: map[string]schema.Attribute{
+ "path": schema.StringAttribute{
+ MarkdownDescription: "Reverse proxy HTTP Route path",
+ Description: "Reverse proxy HTTP Route path",
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.ConflictsWith(path.Expressions{
+ path.MatchRelative().AtName("path").AtParent(),
+ path.MatchRelative().AtName("pattern").AtParent(),
+ }...),
+ },
+ },
+ "target_host": schema.StringAttribute{
+ MarkdownDescription: "Reverse proxy HTTP Route target host",
+ Description: "Reverse proxy HTTP Route target host",
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.ConflictsWith(path.Expressions{
+ path.MatchRelative().AtName("target_host").AtParent(),
+ path.MatchRelative().AtName("target_vm_id").AtParent(),
+ }...),
+ },
+ },
+ "target_vm_id": schema.StringAttribute{
+ MarkdownDescription: "Reverse proxy HTTP Route target VM id",
+ Description: "Reverse proxy HTTP Route target VM id",
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.ConflictsWith(path.Expressions{
+ path.MatchRelative().AtName("target_host").AtParent(),
+ path.MatchRelative().AtName("target_vm_id").AtParent(),
+ }...),
+ },
+ },
+ "target_port": schema.StringAttribute{
+ MarkdownDescription: "Reverse proxy HTTP Route target port",
+ Description: "Reverse proxy HTTP Route target port",
+ Optional: true,
+ },
+ "schema": schema.StringAttribute{
+ MarkdownDescription: "Reverse proxy HTTP Route schema",
+ Description: "Reverse proxy HTTP Route schema",
+ Optional: true,
+ },
+ "pattern": schema.StringAttribute{
+ MarkdownDescription: "Reverse proxy HTTP Route pattern",
+ Description: "Reverse proxy HTTP Route pattern",
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.ConflictsWith(path.Expressions{
+ path.MatchRelative().AtName("path").AtParent(),
+ path.MatchRelative().AtName("pattern").AtParent(),
+ }...),
+ },
+ },
+ "request_headers": schema.MapAttribute{
+ MarkdownDescription: "Reverse proxy HTTP Route request headers",
+ Description: "Reverse proxy HTTP Route request headers",
+ Optional: true,
+ ElementType: types.StringType,
+ },
+ "response_headers": schema.MapAttribute{
+ MarkdownDescription: "Reverse proxy HTTP Route response headers",
+ Description: "Reverse proxy HTTP Route response headers",
+ Optional: true,
+ ElementType: types.StringType,
+ },
+ },
+ },
+}
diff --git a/internal/schemas/reverseproxy/models.go b/internal/schemas/reverseproxy/models.go
new file mode 100644
index 0000000..c63cd74
--- /dev/null
+++ b/internal/schemas/reverseproxy/models.go
@@ -0,0 +1,372 @@
+package reverseproxy
+
+import (
+ "terraform-provider-parallels-desktop/internal/common"
+
+ "github.com/hashicorp/terraform-plugin-framework/types/basetypes"
+)
+
+type ReverseProxyHost struct {
+ Index int `tfsdk:"-"`
+ ID basetypes.StringValue `tfsdk:"id"`
+ Host basetypes.StringValue `tfsdk:"host"`
+ Port string `tfsdk:"port"`
+ Cors *ReverseProxyCors `tfsdk:"cors"`
+ Tls *ReverseProxyTls `tfsdk:"tls"`
+ HttpRoute []*ReverseProxyHttpRoute `tfsdk:"http_routes"`
+ TcpRoute *ReverseProxyHostTcpRoute `tfsdk:"tcp_route"`
+}
+
+func (o *ReverseProxyHost) Copy() ReverseProxyHost {
+ result := ReverseProxyHost{}
+ if o == nil {
+ return result
+ }
+
+ result.ID = o.ID
+ result.Host = o.Host
+ result.Port = o.Port
+ if o.Cors != nil {
+ cors := o.Cors.Copy()
+ result.Cors = &cors
+ }
+ if o.Tls != nil {
+ tls := o.Tls.Copy()
+ result.Tls = &tls
+ }
+ if len(o.HttpRoute) > 0 {
+ result.HttpRoute = make([]*ReverseProxyHttpRoute, len(o.HttpRoute))
+ for i, v := range o.HttpRoute {
+ route := v.Copy()
+ result.HttpRoute[i] = &route
+ }
+ }
+ if o.TcpRoute != nil {
+ tcpRoute := o.TcpRoute.Copy()
+ result.TcpRoute = &tcpRoute
+ }
+
+ return result
+}
+
+func (o *ReverseProxyHost) GetHost() string {
+ if o == nil {
+ return ""
+ }
+
+ host := common.GetString(o.Host)
+ if host == "" {
+ host = allAddress
+ }
+
+ if o.Port != "" {
+ return host + ":" + o.Port
+ }
+
+ return host
+}
+
+func (o *ReverseProxyHost) Diff(other *ReverseProxyHost) bool {
+ if o == nil && other == nil {
+ return false
+ }
+ if o == nil && other != nil {
+ return true
+ }
+ if o != nil && other == nil {
+ return true
+ }
+ if common.GetString(o.ID) != common.GetString(other.ID) {
+ return true
+ }
+ if common.GetString(o.Host) != common.GetString(other.Host) {
+ return true
+ }
+ if o.Port != other.Port {
+ return true
+ }
+ if o.Cors.Diff(other.Cors) {
+ return true
+ }
+ if o.Tls.Diff(other.Tls) {
+ return true
+ }
+ if len(o.HttpRoute) != len(other.HttpRoute) {
+ return true
+ }
+ for i, v := range o.HttpRoute {
+ if v.Diff(other.HttpRoute[i]) {
+ return true
+ }
+ }
+
+ return o.TcpRoute.Diff(other.TcpRoute)
+}
+
+type ReverseProxyHostTcpRoute struct {
+ TargetPort basetypes.StringValue `tfsdk:"target_port"`
+ TargetHost basetypes.StringValue `tfsdk:"target_host"`
+ TargetVmId basetypes.StringValue `tfsdk:"target_vm_id"`
+}
+
+func (o *ReverseProxyHostTcpRoute) GetHost() string {
+ host := common.GetString(o.TargetHost)
+ port := common.GetString(o.TargetPort)
+ vmId := common.GetString(o.TargetVmId)
+
+ if host == "" && vmId == "" {
+ host = allAddress
+ }
+ if host == "" && vmId != "" {
+ host = vmId
+ }
+
+ if port != "" {
+ host += ":" + port
+ }
+
+ return host
+}
+
+func (o *ReverseProxyHostTcpRoute) Copy() ReverseProxyHostTcpRoute {
+ result := ReverseProxyHostTcpRoute{}
+ if o == nil {
+ return result
+ }
+
+ result.TargetPort = o.TargetPort
+ result.TargetHost = o.TargetHost
+ result.TargetVmId = o.TargetVmId
+
+ return result
+}
+
+func (o *ReverseProxyHostTcpRoute) Diff(other *ReverseProxyHostTcpRoute) bool {
+ if o == nil && other == nil {
+ return false
+ }
+ if o == nil && other != nil {
+ return true
+ }
+ if o != nil && other == nil {
+ return true
+ }
+ if common.GetString(o.TargetPort) != common.GetString(other.TargetPort) {
+ return true
+ }
+ if common.GetString(o.TargetHost) != common.GetString(other.TargetHost) {
+ return true
+ }
+ if common.GetString(o.TargetVmId) != common.GetString(other.TargetVmId) {
+ return true
+ }
+ return false
+}
+
+type ReverseProxyCors struct {
+ Enabled bool `tfsdk:"enabled"`
+ AllowedOrigins []string `tfsdk:"allowed_origins"`
+ AllowedMethods []string `tfsdk:"allowed_methods"`
+ AllowedHeaders []string `tfsdk:"allowed_headers"`
+}
+
+func (o *ReverseProxyCors) Copy() ReverseProxyCors {
+ if o == nil {
+ return ReverseProxyCors{}
+ }
+ c := *o
+ return c
+}
+
+func (o *ReverseProxyCors) Diff(other *ReverseProxyCors) bool {
+ if o == nil && other == nil {
+ return false
+ }
+ if o == nil && other != nil {
+ return true
+ }
+ if o != nil && other == nil {
+ return true
+ }
+ if o.Enabled != other.Enabled {
+ return true
+ }
+ if len(o.AllowedOrigins) != len(other.AllowedOrigins) {
+ return true
+ }
+ for i, v := range o.AllowedOrigins {
+ if other.AllowedOrigins[i] != v {
+ return true
+ }
+ }
+ if len(o.AllowedMethods) != len(other.AllowedMethods) {
+ return true
+ }
+ for i, v := range o.AllowedMethods {
+ if other.AllowedMethods[i] != v {
+ return true
+ }
+ }
+ if len(o.AllowedHeaders) != len(other.AllowedHeaders) {
+ return true
+ }
+ for i, v := range o.AllowedHeaders {
+ if other.AllowedHeaders[i] != v {
+ return true
+ }
+ }
+ return false
+}
+
+type ReverseProxyTls struct {
+ Enabled bool `tfsdk:"enabled"`
+ Certificate string `tfsdk:"certificate"`
+ PrivateKey string `tfsdk:"private_key"`
+}
+
+func (o *ReverseProxyTls) Copy() ReverseProxyTls {
+ if o == nil {
+ return ReverseProxyTls{}
+ }
+ c := *o
+ return c
+}
+
+func (o *ReverseProxyTls) Diff(other *ReverseProxyTls) bool {
+ if o == nil && other == nil {
+ return false
+ }
+ if o == nil && other != nil {
+ return true
+ }
+ if o != nil && other == nil {
+ return true
+ }
+ if o.Enabled != other.Enabled {
+ return true
+ }
+ if o.Certificate != other.Certificate {
+ return true
+ }
+ if o.PrivateKey != other.PrivateKey {
+ return true
+ }
+ return false
+}
+
+type ReverseProxyHttpRoute struct {
+ TargetPort basetypes.StringValue `tfsdk:"target_port"`
+ TargetHost basetypes.StringValue `tfsdk:"target_host"`
+ TargetVmId basetypes.StringValue `tfsdk:"target_vm_id"`
+ Path string `tfsdk:"path"`
+ Pattern string `tfsdk:"pattern"`
+ Schema string `tfsdk:"schema"`
+ RequestHeaders map[string]string `tfsdk:"request_headers"`
+ ResponseHeaders map[string]string `tfsdk:"response_headers"`
+}
+
+func (o *ReverseProxyHttpRoute) GetHost() string {
+ host := common.GetString(o.TargetHost)
+ port := common.GetString(o.TargetPort)
+ vmId := common.GetString(o.TargetVmId)
+
+ if host == "" && vmId == "" {
+ host = allAddress
+ }
+ if host == "" && vmId != "" {
+ host = vmId
+ }
+
+ if port != "" {
+ host += ":" + port
+ }
+
+ if o.Schema != "" {
+ host = o.Schema + "://" + host
+ }
+ return host
+}
+
+func (o *ReverseProxyHttpRoute) Copy() ReverseProxyHttpRoute {
+ result := ReverseProxyHttpRoute{}
+ result.Path = o.Path
+ result.Pattern = o.Pattern
+ result.TargetHost = o.TargetHost
+ result.TargetPort = o.TargetPort
+ result.TargetVmId = o.TargetVmId
+ result.Schema = o.Schema
+ result.RequestHeaders = o.RequestHeaders
+ result.ResponseHeaders = o.ResponseHeaders
+
+ return result
+}
+
+func (o *ReverseProxyHttpRoute) Diff(other *ReverseProxyHttpRoute) bool {
+ if o == nil && other == nil {
+ return false
+ }
+ if o == nil && other != nil {
+ return true
+ }
+ if o != nil && other == nil {
+ return true
+ }
+ if o.Path != other.Path {
+ return true
+ }
+ if o.Pattern != other.Pattern {
+ return true
+ }
+ if common.GetString(o.TargetHost) != common.GetString(other.TargetHost) {
+ return true
+ }
+ if common.GetString(o.TargetPort) != common.GetString(other.TargetPort) {
+ return true
+ }
+ if common.GetString(o.TargetVmId) != common.GetString(other.TargetVmId) {
+ return true
+ }
+ if o.Schema != other.Schema {
+ return true
+ }
+ if len(o.RequestHeaders) != len(other.RequestHeaders) {
+ return true
+ }
+ for k, v := range o.RequestHeaders {
+ if other.RequestHeaders[k] != v {
+ return true
+ }
+ }
+ if len(o.ResponseHeaders) != len(other.ResponseHeaders) {
+ return true
+ }
+ for k, v := range o.ResponseHeaders {
+ if other.ResponseHeaders[k] != v {
+ return true
+ }
+ }
+ return false
+}
+
+func ReverseProxyHostsDiff(a, b []*ReverseProxyHost) bool {
+ if len(a) != len(b) {
+ return true
+ }
+ for i, v := range a {
+ if v.Diff(b[i]) {
+ return true
+ }
+ }
+ return false
+}
+
+func CopyReverseProxyHosts(a []*ReverseProxyHost) []ReverseProxyHost {
+ if a == nil {
+ return nil
+ }
+ b := make([]ReverseProxyHost, len(a))
+ for i, v := range a {
+ b[i] = v.Copy()
+ }
+ return b
+}
diff --git a/internal/schemas/reverseproxy/operations.go b/internal/schemas/reverseproxy/operations.go
new file mode 100644
index 0000000..5f4936b
--- /dev/null
+++ b/internal/schemas/reverseproxy/operations.go
@@ -0,0 +1,341 @@
+package reverseproxy
+
+import (
+ "context"
+ "strings"
+
+ "terraform-provider-parallels-desktop/internal/apiclient"
+ "terraform-provider-parallels-desktop/internal/apiclient/apimodels"
+ "terraform-provider-parallels-desktop/internal/common"
+
+ "github.com/hashicorp/terraform-plugin-framework/diag"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/hashicorp/terraform-plugin-log/tflog"
+)
+
+var allAddress = "0.0.0.0"
+
+func Read() diag.Diagnostics {
+ diagnostic := diag.Diagnostics{}
+ return diagnostic
+}
+
+func Create(ctx context.Context, config apiclient.HostConfig, request []ReverseProxyHost) ([]ReverseProxyHost, diag.Diagnostics) {
+ diagnostic := diag.Diagnostics{}
+ for _, host := range request {
+ if h, diag := createHost(ctx, config, host); diag.HasError() {
+ diagnostic = append(diagnostic, diag...)
+ } else {
+ host.ID = h.ID
+ }
+ }
+
+ return request, diagnostic
+}
+
+func Revert(ctx context.Context, config apiclient.HostConfig, currentHosts []ReverseProxyHost, requestHosts []ReverseProxyHost) ([]ReverseProxyHost, diag.Diagnostics) {
+ diagnostic := diag.Diagnostics{}
+
+ // we will delete all the hosts that are in the request as we do not know what was changed
+ for _, host := range requestHosts {
+ if exists, _ := apiclient.GetReverseProxyHost(ctx, config, host.GetHost()); exists != nil {
+ if diag := deleteHost(ctx, config, host); diag.HasError() {
+ tflog.Error(ctx, "Error deleting host "+host.GetHost())
+ }
+ }
+ }
+
+ // we will create all the hosts that are in the currentHosts as we do not know what was changed
+ for i, host := range currentHosts {
+ if exists, _ := apiclient.GetReverseProxyHost(ctx, config, host.GetHost()); exists == nil {
+ if r, diag := createHost(ctx, config, host); diag.HasError() {
+ diagnostic = append(diagnostic, diag...)
+ } else {
+ requestHosts[i].ID = r.ID
+ }
+ }
+ }
+
+ return requestHosts, diagnostic
+}
+
+func Update(ctx context.Context, config apiclient.HostConfig, currentHosts []ReverseProxyHost, requestHosts []ReverseProxyHost) ([]ReverseProxyHost, diag.Diagnostics) {
+ diagnostic := diag.Diagnostics{}
+ hasChanges := diff(currentHosts, requestHosts)
+ if !hasChanges {
+ return nil, diagnostic
+ }
+
+ // Getting a list of reverse proxy hosts to delete as they exist in the currentHosts and not in the requestHosts
+ toDelete := getHostsToDelete(currentHosts, requestHosts)
+ toCreate := getHostsToCreate(currentHosts, requestHosts)
+ toUpdate := getHostsToUpdate(currentHosts, requestHosts)
+
+ for _, host := range toDelete {
+ if diag := deleteHost(ctx, config, host); diag.HasError() {
+ diagnostic = append(diagnostic, diag...)
+ }
+ }
+
+ for _, host := range toCreate {
+ h, createDiag := createHost(ctx, config, host)
+ if createDiag.HasError() {
+ diagnostic = append(diagnostic, createDiag...)
+ }
+ requestHosts[host.Index].ID = h.ID
+ }
+
+ for _, host := range toUpdate {
+ currentHost := currentHosts[host.Index]
+ h, updateDiag := updateHost(ctx, config, currentHost, host)
+ if updateDiag.HasError() {
+ diagnostic = append(diagnostic, updateDiag...)
+ }
+
+ requestHosts[host.Index].ID = h.ID
+ }
+
+ return requestHosts, diagnostic
+}
+
+func Delete(ctx context.Context, config apiclient.HostConfig, request []ReverseProxyHost) diag.Diagnostics {
+ diagnostic := diag.Diagnostics{}
+ for _, host := range request {
+ if diag := deleteHost(ctx, config, host); diag.HasError() {
+ diagnostic = append(diagnostic, diag...)
+ }
+ }
+ return diagnostic
+}
+
+func diff(a, b []ReverseProxyHost) bool {
+ if len(a) != len(b) {
+ return true
+ }
+ for i, v := range a {
+ if v.Diff(&b[i]) {
+ return true
+ }
+ }
+
+ return false
+}
+
+func getHostsToDelete(currentHosts, requestHosts []ReverseProxyHost) []ReverseProxyHost {
+ toDelete := make([]ReverseProxyHost, 0)
+ if len(currentHosts) > 0 && len(requestHosts) == 0 {
+ for i, v := range currentHosts {
+ v.Index = i
+ toDelete = append(toDelete, v)
+ }
+ return toDelete
+ }
+
+ for i, v := range currentHosts {
+ found := false
+ vHost := v.GetHost()
+ for _, r := range requestHosts {
+ rHost := r.GetHost()
+ if strings.EqualFold(vHost, rHost) {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ toDelete = append(toDelete, currentHosts[i])
+ }
+ }
+
+ return toDelete
+}
+
+func getHostsToUpdate(currentHosts, requestHosts []ReverseProxyHost) []ReverseProxyHost {
+ toUpdate := make([]ReverseProxyHost, 0)
+ if len(requestHosts) == 0 || len(currentHosts) == 0 {
+ return toUpdate
+ }
+
+ for i, v := range requestHosts {
+ vHost := v.GetHost()
+ for _, r := range currentHosts {
+ rHost := r.GetHost()
+ if strings.EqualFold(vHost, rHost) {
+ if currentHosts[i].Diff(&v) {
+ requestHosts[i].Index = i
+ toUpdate = append(toUpdate, requestHosts[i])
+ }
+ break
+ }
+ }
+ }
+
+ return toUpdate
+}
+
+func getHostsToCreate(currentHosts, requestHosts []ReverseProxyHost) []ReverseProxyHost {
+ toCreate := make([]ReverseProxyHost, 0)
+ if len(requestHosts) == 0 {
+ return toCreate
+ }
+
+ if len(currentHosts) == 0 {
+ for i, v := range requestHosts {
+ v.Index = i
+ toCreate = append(toCreate, v)
+ }
+ return toCreate
+ }
+
+ for i, v := range requestHosts {
+ found := false
+ vHost := v.GetHost()
+ for _, r := range currentHosts {
+ rHost := r.GetHost()
+ if strings.EqualFold(vHost, rHost) {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ requestHosts[i].Index = i
+ toCreate = append(toCreate, requestHosts[i])
+ }
+ }
+
+ return toCreate
+}
+
+func mapReverseProxyToApiModel(v ReverseProxyHost) apimodels.ReverseProxyHost {
+ requestHost := apimodels.ReverseProxyHost{
+ Port: v.Port,
+ }
+
+ if common.GetString(v.ID) != "" {
+ requestHost.ID = common.GetString(v.ID)
+ }
+
+ if common.GetString(v.Host) == "" {
+ requestHost.Host = allAddress
+ } else {
+ requestHost.Host = common.GetString(v.Host)
+ }
+
+ if v.Tls != nil {
+ requestHost.Tls = &apimodels.ReverseProxyHostTls{
+ Cert: v.Tls.Certificate,
+ Key: v.Tls.PrivateKey,
+ Enabled: v.Tls.Enabled,
+ }
+ }
+ if v.Cors != nil {
+ requestHost.Cors = &apimodels.ReverseProxyHostCors{
+ Enabled: v.Cors.Enabled,
+ AllowedOrigins: v.Cors.AllowedOrigins,
+ AllowedMethods: v.Cors.AllowedMethods,
+ AllowedHeaders: v.Cors.AllowedHeaders,
+ }
+ }
+ if v.TcpRoute != nil {
+ requestHost.TcpRoute = &apimodels.ReverseProxyHostTcpRoute{}
+ if common.GetString(v.TcpRoute.TargetHost) != "" {
+ requestHost.TcpRoute.TargetHost = common.GetString(v.TcpRoute.TargetHost)
+ }
+ if common.GetString(v.TcpRoute.TargetPort) != "" {
+ requestHost.TcpRoute.TargetPort = common.GetString(v.TcpRoute.TargetPort)
+ }
+ if common.GetString(v.TcpRoute.TargetVmId) != "" {
+ requestHost.TcpRoute.TargetVmId = common.GetString(v.TcpRoute.TargetVmId)
+ }
+ if common.GetString(v.TcpRoute.TargetHost) == "" && common.GetString(v.TcpRoute.TargetVmId) == "" {
+ requestHost.TcpRoute.TargetHost = allAddress
+ }
+ }
+
+ if len(v.HttpRoute) > 0 {
+ requestHost.HttpRoutes = make([]*apimodels.ReverseProxyHostHttpRoute, 0)
+ for _, route := range v.HttpRoute {
+ httpRoute := apimodels.ReverseProxyHostHttpRoute{
+ TargetHost: common.GetString(route.TargetHost),
+ TargetPort: common.GetString(route.TargetPort),
+ TargetVmId: common.GetString(route.TargetVmId),
+ Path: route.Path,
+ Schema: route.Schema,
+ Pattern: route.Pattern,
+ RequestHeaders: route.RequestHeaders,
+ ResponseHeaders: route.ResponseHeaders,
+ }
+ if common.GetString(route.TargetHost) == "" && common.GetString(route.TargetVmId) == "" {
+ httpRoute.TargetHost = allAddress
+ }
+
+ requestHost.HttpRoutes = append(requestHost.HttpRoutes, &httpRoute)
+ }
+ }
+
+ return requestHost
+}
+
+func deleteHost(ctx context.Context, config apiclient.HostConfig, host ReverseProxyHost) diag.Diagnostics {
+ diagnostic := diag.Diagnostics{}
+
+ if common.GetString(host.Host) == "" {
+ host.Host = types.StringValue(allAddress)
+ }
+
+ if exists, _ := apiclient.GetReverseProxyHost(ctx, config, host.GetHost()); exists != nil {
+ if diag := apiclient.DeleteReverseProxyHost(ctx, config, host.GetHost()); diag.HasError() {
+ diagnostic = append(diagnostic, diag...)
+ }
+ }
+
+ return diagnostic
+}
+
+func createHost(ctx context.Context, config apiclient.HostConfig, host ReverseProxyHost) (ReverseProxyHost, diag.Diagnostics) {
+ diagnostic := diag.Diagnostics{}
+
+ // if the host id is not set, we will check if the host exists and delete it
+ if exists, _ := apiclient.GetReverseProxyHost(ctx, config, host.GetHost()); exists != nil {
+ if diag := apiclient.DeleteReverseProxyHost(ctx, config, host.GetHost()); diag.HasError() {
+ diagnostic = append(diagnostic, diag...)
+ }
+ }
+
+ r, createDiag := apiclient.CreateReverseProxyHost(ctx, config, mapReverseProxyToApiModel(host))
+ if createDiag.HasError() || r == nil {
+ diagnostic = append(diagnostic, createDiag...)
+ return host, diagnostic
+ }
+
+ host.ID = types.StringValue(r.ID)
+ return host, diagnostic
+}
+
+func updateHost(ctx context.Context, config apiclient.HostConfig, currentHost ReverseProxyHost, requestHost ReverseProxyHost) (ReverseProxyHost, diag.Diagnostics) {
+ diagnostic := diag.Diagnostics{}
+
+ if exists, _ := apiclient.GetReverseProxyHost(ctx, config, currentHost.GetHost()); exists != nil {
+ if diag := apiclient.DeleteReverseProxyHost(ctx, config, currentHost.GetHost()); diag.HasError() {
+ diagnostic = append(diagnostic, diag...)
+ }
+ }
+
+ if exists, diag := apiclient.GetReverseProxyHost(ctx, config, requestHost.GetHost()); exists != nil {
+ if diag.HasError() {
+ diagnostic = append(diagnostic, diag...)
+ }
+
+ requestHost.ID = types.StringValue(exists.ID)
+ return requestHost, diagnostic
+ }
+
+ r, createDiag := apiclient.CreateReverseProxyHost(ctx, config, mapReverseProxyToApiModel(requestHost))
+ if createDiag.HasError() {
+ diagnostic = append(diagnostic, createDiag...)
+ }
+
+ requestHost.ID = types.StringValue(r.ID)
+ return requestHost, diagnostic
+}
diff --git a/internal/schemas/reverseproxy/tcp_route_schema_v0.go b/internal/schemas/reverseproxy/tcp_route_schema_v0.go
new file mode 100644
index 0000000..8b65a5a
--- /dev/null
+++ b/internal/schemas/reverseproxy/tcp_route_schema_v0.go
@@ -0,0 +1,42 @@
+package reverseproxy
+
+import (
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+)
+
+var TcpRouteSchemaBlockV0 = schema.SingleNestedBlock{
+ MarkdownDescription: "Parallels Desktop DevOps Reverse Proxy TCP Route configuration",
+ Description: "Parallels Desktop DevOps Reverse Proxy TCP Route configuration",
+ Attributes: map[string]schema.Attribute{
+ "target_host": schema.StringAttribute{
+ MarkdownDescription: "Reverse proxy host",
+ Description: "Reverse proxy host",
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.ConflictsWith(path.Expressions{
+ path.MatchRelative().AtName("target_host").AtParent(),
+ path.MatchRelative().AtName("target_vm_id").AtParent(),
+ }...),
+ },
+ },
+ "target_port": schema.StringAttribute{
+ MarkdownDescription: "Reverse proxy port",
+ Description: "reverse proxy port",
+ Optional: true,
+ },
+ "target_vm_id": schema.StringAttribute{
+ MarkdownDescription: "Reverse proxy target VM ID",
+ Description: "Reverse proxy target VM ID",
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.ConflictsWith(path.Expressions{
+ path.MatchRelative().AtName("target_host").AtParent(),
+ path.MatchRelative().AtName("target_vm_id").AtParent(),
+ }...),
+ },
+ },
+ },
+}
diff --git a/internal/schemas/reverseproxy/tls_schema_v0.go b/internal/schemas/reverseproxy/tls_schema_v0.go
new file mode 100644
index 0000000..e8f84a3
--- /dev/null
+++ b/internal/schemas/reverseproxy/tls_schema_v0.go
@@ -0,0 +1,27 @@
+package reverseproxy
+
+import (
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+)
+
+var TlsSchemaBlockV0 = schema.SingleNestedBlock{
+ MarkdownDescription: "Parallels Desktop DevOps Reverse Proxy Http Route TLS configuration",
+ Description: "Parallels Desktop DevOps Reverse Proxy Http Route TLS configuration",
+ Attributes: map[string]schema.Attribute{
+ "enabled": schema.BoolAttribute{
+ MarkdownDescription: "Enable TLS",
+ Description: "Enable TLS",
+ Optional: true,
+ },
+ "certificate": schema.StringAttribute{
+ MarkdownDescription: "TLS Certificate",
+ Description: "TLS Certificate",
+ Optional: true,
+ },
+ "private_key": schema.StringAttribute{
+ MarkdownDescription: "TLS Private Key",
+ Description: "TLS Private Key",
+ Optional: true,
+ },
+ },
+}
diff --git a/internal/schemas/sshconnection/schema.go b/internal/schemas/sshconnection/schema.go
index 9d1b83c..ca246c0 100644
--- a/internal/schemas/sshconnection/schema.go
+++ b/internal/schemas/sshconnection/schema.go
@@ -6,37 +6,39 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)
-var SchemaName = "ssh_connection"
-var SchemaBlock = schema.SingleNestedBlock{
- MarkdownDescription: "Host connection details",
- Attributes: map[string]schema.Attribute{
- "host": schema.StringAttribute{
- MarkdownDescription: "Host Machine address",
- Optional: true,
- Validators: []validator.String{
- stringvalidator.LengthAtLeast(1),
+var (
+ SchemaName = "ssh_connection"
+ SchemaBlockV0 = schema.SingleNestedBlock{
+ MarkdownDescription: "Host connection details",
+ Attributes: map[string]schema.Attribute{
+ "host": schema.StringAttribute{
+ MarkdownDescription: "Host Machine address",
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.LengthAtLeast(1),
+ },
},
- },
- "host_port": schema.StringAttribute{
- MarkdownDescription: "Host Machine port",
- Optional: true,
- },
- "user": schema.StringAttribute{
- MarkdownDescription: "Host Machine user",
- Optional: true,
- Validators: []validator.String{
- stringvalidator.LengthAtLeast(1),
+ "host_port": schema.StringAttribute{
+ MarkdownDescription: "Host Machine port",
+ Optional: true,
+ },
+ "user": schema.StringAttribute{
+ MarkdownDescription: "Host Machine user",
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.LengthAtLeast(1),
+ },
+ },
+ "password": schema.StringAttribute{
+ MarkdownDescription: "Host Machine password",
+ Optional: true,
+ Sensitive: true,
+ },
+ "private_key": schema.StringAttribute{
+ MarkdownDescription: "Host Machine RSA private key",
+ Optional: true,
+ Sensitive: true,
},
},
- "password": schema.StringAttribute{
- MarkdownDescription: "Host Machine password",
- Optional: true,
- Sensitive: true,
- },
- "private_key": schema.StringAttribute{
- MarkdownDescription: "Host Machine RSA private key",
- Optional: true,
- Sensitive: true,
- },
- },
-}
+ }
+)
diff --git a/internal/vagrantbox/resource_models.go b/internal/vagrantbox/models/resource_models_v0.go
similarity index 97%
rename from internal/vagrantbox/resource_models.go
rename to internal/vagrantbox/models/resource_models_v0.go
index 81e1a19..0faef4b 100644
--- a/internal/vagrantbox/resource_models.go
+++ b/internal/vagrantbox/models/resource_models_v0.go
@@ -1,4 +1,4 @@
-package vagrantbox
+package models
import (
"terraform-provider-parallels-desktop/internal/schemas/authenticator"
@@ -13,7 +13,7 @@ import (
)
// VirtualMachineStateResourceModel describes the resource data model.
-type VagrantBoxResourceModel struct {
+type VagrantBoxResourceModelV0 struct {
Authenticator *authenticator.Authentication `tfsdk:"authenticator"`
Host types.String `tfsdk:"host"`
Orchestrator types.String `tfsdk:"orchestrator"`
diff --git a/internal/vagrantbox/models/resource_models_v1.go b/internal/vagrantbox/models/resource_models_v1.go
new file mode 100644
index 0000000..ca06bb1
--- /dev/null
+++ b/internal/vagrantbox/models/resource_models_v1.go
@@ -0,0 +1,43 @@
+package models
+
+import (
+ "terraform-provider-parallels-desktop/internal/schemas/authenticator"
+ "terraform-provider-parallels-desktop/internal/schemas/postprocessorscript"
+ "terraform-provider-parallels-desktop/internal/schemas/prlctl"
+ "terraform-provider-parallels-desktop/internal/schemas/reverseproxy"
+ "terraform-provider-parallels-desktop/internal/schemas/sharedfolder"
+ "terraform-provider-parallels-desktop/internal/schemas/vmconfig"
+ "terraform-provider-parallels-desktop/internal/schemas/vmspecs"
+
+ "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+// VirtualMachineStateResourceModel describes the resource data model.
+type VagrantBoxResourceModelV1 struct {
+ Authenticator *authenticator.Authentication `tfsdk:"authenticator"`
+ Host types.String `tfsdk:"host"`
+ Orchestrator types.String `tfsdk:"orchestrator"`
+ ID types.String `tfsdk:"id"`
+ ExternalIp types.String `tfsdk:"external_ip"`
+ InternalIp types.String `tfsdk:"internal_ip"`
+ OsType types.String `tfsdk:"os_type"`
+ BoxName types.String `tfsdk:"box_name"`
+ BoxVersion types.String `tfsdk:"box_version"`
+ VagrantFilePath types.String `tfsdk:"vagrant_file_path"`
+ CustomVagrantConfig types.String `tfsdk:"custom_vagrant_config"`
+ CustomParallelsConfig types.String `tfsdk:"custom_parallels_config"`
+ Name types.String `tfsdk:"name"`
+ Owner types.String `tfsdk:"owner"`
+ RunAfterCreate types.Bool `tfsdk:"run_after_create"`
+ Timeouts timeouts.Value `tfsdk:"timeouts"`
+ Specs *vmspecs.VmSpecs `tfsdk:"specs"`
+ PostProcessorScripts []*postprocessorscript.PostProcessorScript `tfsdk:"post_processor_script"`
+ OnDestroyScript []*postprocessorscript.PostProcessorScript `tfsdk:"on_destroy_script"`
+ SharedFolder []*sharedfolder.SharedFolder `tfsdk:"shared_folder"`
+ ForceChanges types.Bool `tfsdk:"force_changes"`
+ Config *vmconfig.VmConfig `tfsdk:"config"`
+ PrlCtl []*prlctl.PrlCtlCmd `tfsdk:"prlctl"`
+ KeepRunning types.Bool `tfsdk:"keep_running"`
+ ReverseProxyHosts []*reverseproxy.ReverseProxyHost `tfsdk:"reverse_proxy_host"`
+}
diff --git a/internal/vagrantbox/resource.go b/internal/vagrantbox/resource.go
index 66b6de5..9621e8a 100644
--- a/internal/vagrantbox/resource.go
+++ b/internal/vagrantbox/resource.go
@@ -10,9 +10,13 @@ import (
"terraform-provider-parallels-desktop/internal/common"
"terraform-provider-parallels-desktop/internal/models"
"terraform-provider-parallels-desktop/internal/schemas/postprocessorscript"
+ "terraform-provider-parallels-desktop/internal/schemas/reverseproxy"
"terraform-provider-parallels-desktop/internal/telemetry"
+ resource_models "terraform-provider-parallels-desktop/internal/vagrantbox/models"
+ "terraform-provider-parallels-desktop/internal/vagrantbox/schemas"
"github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
@@ -39,7 +43,7 @@ func (r *VagrantBoxResource) Metadata(ctx context.Context, req resource.Metadata
}
func (r *VagrantBoxResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
- resp.Schema = getSchema(ctx)
+ resp.Schema = schemas.GetResourceSchemaV1(ctx)
}
func (r *VagrantBoxResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
@@ -60,7 +64,7 @@ func (r *VagrantBoxResource) Configure(ctx context.Context, req resource.Configu
}
func (r *VagrantBoxResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
- var data VagrantBoxResourceModel
+ var data resource_models.VagrantBoxResourceModelV1
telemetrySvc := telemetry.Get(ctx)
telemetryEvent := telemetry.NewTelemetryItem(
@@ -234,13 +238,52 @@ func (r *VagrantBoxResource) Create(ctx context.Context, req resource.CreateRequ
return
}
- // Starting the vm if requested
- if data.RunAfterCreate.ValueBool() {
+ if len(data.ReverseProxyHosts) > 0 {
+ rpHostConfig := hostConfig
+ rpHostConfig.HostId = stoppedVm.HostId
+ rpHosts, updateDiag := updateReverseProxyHostsTarget(ctx, &data, rpHostConfig, stoppedVm)
+ if updateDiag.HasError() {
+ resp.Diagnostics.Append(updateDiag...)
+ return
+ }
+
+ result, createDiag := reverseproxy.Create(ctx, rpHostConfig, rpHosts)
+ if createDiag.HasError() {
+ resp.Diagnostics.Append(createDiag...)
+
+ if diag := reverseproxy.Delete(ctx, rpHostConfig, rpHosts); diag.HasError() {
+ tflog.Error(ctx, "Error deleting reverse proxy hosts")
+ }
+
+ if data.ID.ValueString() != "" {
+ // If we have an ID, we need to delete the machine
+ apiclient.SetMachineState(ctx, rpHostConfig, data.ID.ValueString(), apiclient.MachineStateOpStop)
+ if _, diag := common.EnsureMachineStopped(ctx, rpHostConfig, stoppedVm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
+
+ apiclient.DeleteVm(ctx, rpHostConfig, data.ID.ValueString())
+ }
+ return
+ }
+
+ for i := range result {
+ data.ReverseProxyHosts[i].ID = result[i].ID
+ }
+ }
+
+ // Starting the vm by default, otherwise we will stop the VM from being created
+ if data.RunAfterCreate.ValueBool() || data.KeepRunning.ValueBool() || (data.RunAfterCreate.IsUnknown() && data.KeepRunning.IsUnknown()) {
if _, diag := common.EnsureMachineRunning(ctx, hostConfig, stoppedVm); diag.HasError() {
resp.Diagnostics.Append(diag...)
if data.ID.ValueString() != "" {
// If we have an ID, we need to delete the machine
apiclient.SetMachineState(ctx, hostConfig, data.ID.ValueString(), apiclient.MachineStateOpStop)
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, stoppedVm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
apiclient.DeleteVm(ctx, hostConfig, data.ID.ValueString())
}
return
@@ -251,8 +294,37 @@ func (r *VagrantBoxResource) Create(ctx context.Context, req resource.CreateRequ
resp.Diagnostics.Append(diag...)
return
}
+ } else {
+ // If we are not starting the machine, we will stop it
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, stoppedVm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ if data.ID.ValueString() != "" {
+ // If we have an ID, we need to delete the machine
+ apiclient.SetMachineState(ctx, hostConfig, data.ID.ValueString(), apiclient.MachineStateOpStop)
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, stoppedVm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
+ apiclient.DeleteVm(ctx, hostConfig, data.ID.ValueString())
+ }
+ return
+ }
}
+ externalIp := ""
+ internalIp := ""
+ refreshVm, refreshDiag := apiclient.GetVm(ctx, hostConfig, response.ID)
+ if refreshDiag.HasError() {
+ resp.Diagnostics.Append(refreshDiag...)
+ return
+ } else {
+ externalIp = refreshVm.HostExternalIpAddress
+ internalIp = refreshVm.InternalIpAddress
+ }
+
+ data.ExternalIp = types.StringValue(externalIp)
+ data.InternalIp = types.StringValue(internalIp)
+
data.OsType = types.StringValue(createdVM.OS)
if data.OnDestroyScript != nil {
for _, script := range data.OnDestroyScript {
@@ -292,7 +364,7 @@ func (r *VagrantBoxResource) Create(ctx context.Context, req resource.CreateRequ
}
func (r *VagrantBoxResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
- var data VagrantBoxResourceModel
+ var data resource_models.VagrantBoxResourceModelV1
telemetrySvc := telemetry.Get(ctx)
telemetryEvent := telemetry.NewTelemetryItem(
@@ -362,8 +434,8 @@ func (r *VagrantBoxResource) Read(ctx context.Context, req resource.ReadRequest,
}
func (r *VagrantBoxResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
- var data VagrantBoxResourceModel
- var currentData VagrantBoxResourceModel
+ var data resource_models.VagrantBoxResourceModelV1
+ var currentData resource_models.VagrantBoxResourceModelV1
telemetrySvc := telemetry.Get(ctx)
telemetryEvent := telemetry.NewTelemetryItem(
@@ -510,6 +582,81 @@ func (r *VagrantBoxResource) Update(ctx context.Context, req resource.UpdateRequ
}
}
+ if reverseproxy.ReverseProxyHostsDiff(data.ReverseProxyHosts, currentData.ReverseProxyHosts) {
+ copyCurrentRpHosts := reverseproxy.CopyReverseProxyHosts(currentData.ReverseProxyHosts)
+ copyRpHosts := reverseproxy.CopyReverseProxyHosts(data.ReverseProxyHosts)
+
+ results, updateDiag := reverseproxy.Update(ctx, hostConfig, copyCurrentRpHosts, copyRpHosts)
+ if updateDiag.HasError() {
+ resp.Diagnostics.Append(updateDiag...)
+ revertResults, _ := reverseproxy.Revert(ctx, hostConfig, copyCurrentRpHosts, copyRpHosts)
+ for i := range revertResults {
+ data.ReverseProxyHosts[i].ID = revertResults[i].ID
+ }
+ return
+ }
+
+ for i := range results {
+ data.ReverseProxyHosts[i].ID = results[i].ID
+ }
+ } else {
+ for i := range currentData.ReverseProxyHosts {
+ data.ReverseProxyHosts[i].ID = currentData.ReverseProxyHosts[i].ID
+ }
+ }
+
+ // Starting the vm by default, otherwise we will stop the VM from being created
+ if data.RunAfterCreate.ValueBool() || data.KeepRunning.ValueBool() || (data.RunAfterCreate.IsUnknown() && data.KeepRunning.IsUnknown()) {
+ if _, diag := common.EnsureMachineRunning(ctx, hostConfig, vm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ if data.ID.ValueString() != "" {
+ // If we have an ID, we need to delete the machine
+ apiclient.SetMachineState(ctx, hostConfig, data.ID.ValueString(), apiclient.MachineStateOpStop)
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, vm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
+ apiclient.DeleteVm(ctx, hostConfig, data.ID.ValueString())
+ }
+ return
+ }
+
+ _, diag := apiclient.GetVm(ctx, hostConfig, vm.ID)
+ if diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
+ } else {
+ // If we are not starting the machine, we will stop it
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, vm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ if data.ID.ValueString() != "" {
+ // If we have an ID, we need to delete the machine
+ apiclient.SetMachineState(ctx, hostConfig, data.ID.ValueString(), apiclient.MachineStateOpStop)
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, vm); diag.HasError() {
+ resp.Diagnostics.Append(diag...)
+ return
+ }
+ apiclient.DeleteVm(ctx, hostConfig, data.ID.ValueString())
+ }
+ return
+ }
+ }
+
+ externalIp := ""
+ internalIp := ""
+ refreshVm, refreshDiag := apiclient.GetVm(ctx, hostConfig, vm.ID)
+ if refreshDiag.HasError() {
+ resp.Diagnostics.Append(refreshDiag...)
+ return
+ } else {
+ externalIp = refreshVm.HostExternalIpAddress
+ internalIp = refreshVm.InternalIpAddress
+ }
+
+ data.ExternalIp = types.StringValue(externalIp)
+ data.InternalIp = types.StringValue(internalIp)
+
data.ID = types.StringValue(vm.ID)
if data.OnDestroyScript != nil {
for _, script := range data.OnDestroyScript {
@@ -546,7 +693,7 @@ func (r *VagrantBoxResource) Update(ctx context.Context, req resource.UpdateRequ
}
func (r *VagrantBoxResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
- var data VagrantBoxResourceModel
+ var data resource_models.VagrantBoxResourceModelV1
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
@@ -660,3 +807,71 @@ func (r *VagrantBoxResource) Delete(ctx context.Context, req resource.DeleteRequ
func (r *VagrantBoxResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
}
+
+func updateReverseProxyHostsTarget(ctx context.Context, data *resource_models.VagrantBoxResourceModelV1, hostConfig apiclient.HostConfig, targetVm *apimodels.VirtualMachine) ([]reverseproxy.ReverseProxyHost, diag.Diagnostics) {
+ resultDiagnostic := diag.Diagnostics{}
+ var refreshedVm *apimodels.VirtualMachine
+ var rpDiag diag.Diagnostics
+ refreshedVm, rpDiag = common.EnsureMachineHasInternalIp(ctx, hostConfig, targetVm)
+ if rpDiag.HasError() {
+ resultDiagnostic.Append(rpDiag...)
+ if data.ID.ValueString() != "" {
+ // If we have an ID, we need to delete the machine
+ apiclient.SetMachineState(ctx, hostConfig, data.ID.ValueString(), apiclient.MachineStateOpStop)
+ if _, diag := common.EnsureMachineStopped(ctx, hostConfig, refreshedVm); diag.HasError() {
+ return nil, diag
+ }
+ apiclient.DeleteVm(ctx, hostConfig, data.ID.ValueString())
+ }
+ return nil, resultDiagnostic
+ }
+
+ modifiedHosts := make([]reverseproxy.ReverseProxyHost, len(data.ReverseProxyHosts))
+ for i := range data.ReverseProxyHosts {
+ host := reverseproxy.ReverseProxyHost{}
+ host.Host = data.ReverseProxyHosts[i].Host
+ host.Port = data.ReverseProxyHosts[i].Port
+ internalIp := refreshedVm.InternalIpAddress
+ emptyString := ""
+
+ if data.ReverseProxyHosts[i].Cors != nil {
+ host.Cors = &reverseproxy.ReverseProxyCors{}
+ host.Cors.AllowedOrigins = data.ReverseProxyHosts[i].Cors.AllowedOrigins
+ host.Cors.AllowedMethods = data.ReverseProxyHosts[i].Cors.AllowedMethods
+ host.Cors.AllowedHeaders = data.ReverseProxyHosts[i].Cors.AllowedHeaders
+ host.Cors.Enabled = data.ReverseProxyHosts[i].Cors.Enabled
+ }
+ if data.ReverseProxyHosts[i].Tls != nil {
+ host.Tls = &reverseproxy.ReverseProxyTls{}
+ host.Tls.Certificate = data.ReverseProxyHosts[i].Tls.Certificate
+ host.Tls.PrivateKey = data.ReverseProxyHosts[i].Tls.PrivateKey
+ host.Tls.Enabled = data.ReverseProxyHosts[i].Tls.Enabled
+ }
+ if data.ReverseProxyHosts[i].TcpRoute != nil {
+ host.TcpRoute = &reverseproxy.ReverseProxyHostTcpRoute{}
+ host.TcpRoute.TargetPort = data.ReverseProxyHosts[i].TcpRoute.TargetPort
+ host.TcpRoute.TargetHost = types.StringValue(internalIp)
+ host.TcpRoute.TargetVmId = types.StringValue(emptyString)
+ }
+
+ if len(data.ReverseProxyHosts[i].HttpRoute) > 0 {
+ host.HttpRoute = make([]*reverseproxy.ReverseProxyHttpRoute, len(data.ReverseProxyHosts[i].HttpRoute))
+ for j := range modifiedHosts[i].HttpRoute {
+ httpRoute := reverseproxy.ReverseProxyHttpRoute{}
+ httpRoute.Path = data.ReverseProxyHosts[i].HttpRoute[j].Path
+ httpRoute.TargetHost = types.StringValue(internalIp)
+ httpRoute.TargetPort = data.ReverseProxyHosts[i].HttpRoute[j].TargetPort
+ httpRoute.TargetVmId = types.StringValue(emptyString)
+ httpRoute.Pattern = data.ReverseProxyHosts[i].HttpRoute[j].Pattern
+ httpRoute.Schema = data.ReverseProxyHosts[i].HttpRoute[j].Schema
+ httpRoute.RequestHeaders = data.ReverseProxyHosts[i].HttpRoute[j].RequestHeaders
+ httpRoute.ResponseHeaders = data.ReverseProxyHosts[i].HttpRoute[j].ResponseHeaders
+ host.HttpRoute[j] = &httpRoute
+ }
+ }
+
+ modifiedHosts[i] = host
+ }
+
+ return modifiedHosts, resultDiagnostic
+}
diff --git a/internal/vagrantbox/resource_schema.go b/internal/vagrantbox/schemas/resource_schema_v0.go
similarity index 98%
rename from internal/vagrantbox/resource_schema.go
rename to internal/vagrantbox/schemas/resource_schema_v0.go
index abcdccf..1d6211d 100644
--- a/internal/vagrantbox/resource_schema.go
+++ b/internal/vagrantbox/schemas/resource_schema_v0.go
@@ -1,4 +1,4 @@
-package vagrantbox
+package schemas
import (
"context"
@@ -19,7 +19,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)
-func getSchema(ctx context.Context) schema.Schema {
+func GetResourceSchemaV0(ctx context.Context) schema.Schema {
return schema.Schema{
// This description is used by the documentation generator and the language server.
MarkdownDescription: "Parallels Virtual Machine State Resource",
diff --git a/internal/vagrantbox/schemas/resource_schema_v1.go b/internal/vagrantbox/schemas/resource_schema_v1.go
new file mode 100644
index 0000000..c558392
--- /dev/null
+++ b/internal/vagrantbox/schemas/resource_schema_v1.go
@@ -0,0 +1,151 @@
+package schemas
+
+import (
+ "context"
+
+ "terraform-provider-parallels-desktop/internal/schemas/authenticator"
+ "terraform-provider-parallels-desktop/internal/schemas/postprocessorscript"
+ "terraform-provider-parallels-desktop/internal/schemas/prlctl"
+ "terraform-provider-parallels-desktop/internal/schemas/reverseproxy"
+ "terraform-provider-parallels-desktop/internal/schemas/sharedfolder"
+ "terraform-provider-parallels-desktop/internal/schemas/vmconfig"
+ "terraform-provider-parallels-desktop/internal/schemas/vmspecs"
+
+ "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+)
+
+func GetResourceSchemaV1(ctx context.Context) schema.Schema {
+ return schema.Schema{
+ // This description is used by the documentation generator and the language server.
+ MarkdownDescription: "Parallels Virtual Machine State Resource",
+ Blocks: map[string]schema.Block{
+ authenticator.SchemaName: authenticator.SchemaBlock,
+ vmspecs.SchemaName: vmspecs.SchemaBlock,
+ postprocessorscript.SchemaName: postprocessorscript.SchemaBlock,
+ "on_destroy_script": postprocessorscript.SchemaBlock,
+ sharedfolder.SchemaName: sharedfolder.SchemaBlock,
+ vmconfig.SchemaName: vmconfig.SchemaBlock,
+ prlctl.SchemaName: prlctl.SchemaBlock,
+ reverseproxy.SchemaName: reverseproxy.HostBlockV0,
+ },
+ Attributes: map[string]schema.Attribute{
+ "timeouts": timeouts.Attributes(ctx, timeouts.Opts{
+ Create: true,
+ }),
+ "force_changes": schema.BoolAttribute{
+ MarkdownDescription: "Force changes, this will force the VM to be stopped and started again",
+ Description: "Force changes, this will force the VM to be stopped and started again",
+ Optional: true,
+ },
+ "host": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps Host",
+ Optional: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ Validators: []validator.String{
+ stringvalidator.AtLeastOneOf(path.Expressions{
+ path.MatchRoot("orchestrator"),
+ path.MatchRoot("host"),
+ }...),
+ },
+ },
+ "orchestrator": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps Orchestrator",
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.AtLeastOneOf(path.Expressions{
+ path.MatchRoot("orchestrator"),
+ path.MatchRoot("host"),
+ }...),
+ },
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "id": schema.StringAttribute{
+ MarkdownDescription: "Virtual Machine Id",
+ Computed: true,
+ },
+ "os_type": schema.StringAttribute{
+ MarkdownDescription: "Virtual Machine OS type",
+ Computed: true,
+ },
+ "box_name": schema.StringAttribute{
+ MarkdownDescription: "Vagrant box name",
+ Optional: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ Validators: []validator.String{
+ stringvalidator.ConflictsWith(path.MatchRoot("vagrant_file_path")),
+ },
+ },
+ "vagrant_file_path": schema.StringAttribute{
+ MarkdownDescription: "Vagrant file path",
+ Optional: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ Validators: []validator.String{
+ stringvalidator.ConflictsWith(path.MatchRoot("vagrant_file_path")),
+ },
+ },
+ "box_version": schema.StringAttribute{
+ MarkdownDescription: "Vagrant box version",
+ Optional: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "custom_vagrant_config": schema.StringAttribute{
+ MarkdownDescription: "Custom Vagrant config",
+ Optional: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "custom_parallels_config": schema.StringAttribute{
+ MarkdownDescription: "Custom Parallels config",
+ Optional: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "name": schema.StringAttribute{
+ MarkdownDescription: "Virtual Machine name",
+ Required: true,
+ },
+ "owner": schema.StringAttribute{
+ MarkdownDescription: "Virtual Machine owner",
+ Optional: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "run_after_create": schema.BoolAttribute{
+ MarkdownDescription: "Run after create",
+ Optional: true,
+ DeprecationMessage: "Use the `keep_running` attribute instead",
+ },
+ "external_ip": schema.StringAttribute{
+ MarkdownDescription: "VM external IP address",
+ Computed: true,
+ },
+ "internal_ip": schema.StringAttribute{
+ MarkdownDescription: "VM internal IP address",
+ Computed: true,
+ },
+ "keep_running": schema.BoolAttribute{
+ MarkdownDescription: "This will keep the VM running after the terraform apply",
+ Optional: true,
+ },
+ },
+ }
+}
diff --git a/internal/virtualmachine/datasource.go b/internal/virtualmachine/datasource.go
index f5d51a7..a3ee11c 100644
--- a/internal/virtualmachine/datasource.go
+++ b/internal/virtualmachine/datasource.go
@@ -6,6 +6,8 @@ import (
"terraform-provider-parallels-desktop/internal/apiclient"
"terraform-provider-parallels-desktop/internal/models"
+ data_models "terraform-provider-parallels-desktop/internal/virtualmachine/models"
+ "terraform-provider-parallels-desktop/internal/virtualmachine/schemas"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/types"
@@ -46,24 +48,35 @@ func (d *VirtualMachinesDataSource) Metadata(_ context.Context, req datasource.M
}
func (d *VirtualMachinesDataSource) Schema(_ context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
- resp.Schema = virtualMachineDataSourceSchema
+ resp.Schema = schemas.VirtualMachineDataSourceSchemaV1
}
func (d *VirtualMachinesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
- var data virtualMachinesDataSourceModel
+ var data data_models.VirtualMachinesDataSourceModelV1
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
- if data.Host.ValueString() == "" {
+ // selecting if this is a standalone host or an orchestrator
+ isOrchestrator := false
+ var host string
+ if data.Orchestrator.ValueString() != "" {
+ isOrchestrator = true
+ host = data.Orchestrator.ValueString()
+ } else {
+ host = data.Host.ValueString()
+ }
+
+ if host == "" {
resp.Diagnostics.AddError("host cannot be empty", "Host cannot be null")
return
}
hostConfig := apiclient.HostConfig{
- Host: data.Host.ValueString(),
+ Host: host,
+ IsOrchestrator: isOrchestrator,
License: d.provider.License.ValueString(),
Authorization: data.Authenticator,
DisableTlsValidation: d.provider.DisableTlsValidation.ValueBool(),
@@ -76,21 +89,24 @@ func (d *VirtualMachinesDataSource) Read(ctx context.Context, req datasource.Rea
}
for _, machine := range vms {
- stateMachine := virtualMachineModel{
- HostIP: types.StringValue("-"),
- ID: types.StringValue(machine.ID),
- Name: types.StringValue(machine.Name),
- Description: types.StringValue(machine.Description),
- OSType: types.StringValue(machine.OS),
- State: types.StringValue(machine.State),
- Home: types.StringValue(machine.Home),
+ stateMachine := data_models.VirtualMachineModelV1{
+ HostIP: types.StringValue("-"),
+ ID: types.StringValue(machine.ID),
+ Name: types.StringValue(machine.Name),
+ Description: types.StringValue(machine.Description),
+ OSType: types.StringValue(machine.OS),
+ State: types.StringValue(machine.State),
+ Home: types.StringValue(machine.Home),
+ ExternalIp: types.StringValue(machine.HostExternalIpAddress),
+ InternalIp: types.StringValue(machine.InternalIpAddress),
+ OrchestratorHostId: types.StringValue(machine.HostId),
}
data.Machines = append(data.Machines, stateMachine)
}
if data.Machines == nil {
- data.Machines = make([]virtualMachineModel, 0)
+ data.Machines = make([]data_models.VirtualMachineModelV1, 0)
}
diags := resp.State.Set(ctx, &data)
diff --git a/internal/virtualmachine/datasource_models.go b/internal/virtualmachine/models/datasource_models_v0.go
similarity index 88%
rename from internal/virtualmachine/datasource_models.go
rename to internal/virtualmachine/models/datasource_models_v0.go
index 64c2efd..6eff8ce 100644
--- a/internal/virtualmachine/datasource_models.go
+++ b/internal/virtualmachine/models/datasource_models_v0.go
@@ -1,4 +1,4 @@
-package virtualmachine
+package models
import (
"terraform-provider-parallels-desktop/internal/schemas/authenticator"
@@ -8,15 +8,15 @@ import (
)
// virtualMachinesDataSourceModel represents the data source schema for the virtual_machines data source.
-type virtualMachinesDataSourceModel struct {
+type VirtualMachinesDataSourceModelV0 struct {
Authenticator *authenticator.Authentication `tfsdk:"authenticator"`
Host types.String `tfsdk:"host"`
Filter *filter.Filter `tfsdk:"filter"`
- Machines []virtualMachineModel `tfsdk:"machines"`
+ Machines []VirtualMachineModelV0 `tfsdk:"machines"`
}
// virtualMachineModel represents a virtual machine model with its properties.
-type virtualMachineModel struct {
+type VirtualMachineModelV0 struct {
HostIP types.String `tfsdk:"host_ip"` // The IP address of the host machine.
ID types.String `tfsdk:"id"` // The unique identifier of the virtual machine.
Name types.String `tfsdk:"name"` // The name of the virtual machine.
diff --git a/internal/virtualmachine/models/datasource_models_v1.go b/internal/virtualmachine/models/datasource_models_v1.go
new file mode 100644
index 0000000..3a654e5
--- /dev/null
+++ b/internal/virtualmachine/models/datasource_models_v1.go
@@ -0,0 +1,31 @@
+package models
+
+import (
+ "terraform-provider-parallels-desktop/internal/schemas/authenticator"
+ "terraform-provider-parallels-desktop/internal/schemas/filter"
+
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+// virtualMachinesDataSourceModel represents the data source schema for the virtual_machines data source.
+type VirtualMachinesDataSourceModelV1 struct {
+ Authenticator *authenticator.Authentication `tfsdk:"authenticator"`
+ Host types.String `tfsdk:"host"`
+ Orchestrator types.String `tfsdk:"orchestrator"`
+ Filter *filter.Filter `tfsdk:"filter"`
+ Machines []VirtualMachineModelV1 `tfsdk:"machines"`
+}
+
+// virtualMachineModel represents a virtual machine model with its properties.
+type VirtualMachineModelV1 struct {
+ HostIP types.String `tfsdk:"host_ip"` // The IP address of the host machine.
+ ID types.String `tfsdk:"id"` // The unique identifier of the virtual machine.
+ ExternalIp types.String `tfsdk:"external_ip"` // The external IP address of the virtual machine.
+ InternalIp types.String `tfsdk:"internal_ip"` // The internal IP address of the virtual machine.
+ OrchestratorHostId types.String `tfsdk:"orchestrator_host_id"` // The unique identifier of the orchestrator host.
+ Name types.String `tfsdk:"name"` // The name of the virtual machine.
+ Description types.String `tfsdk:"description"` // The description of the virtual machine.
+ OSType types.String `tfsdk:"os_type"` // The type of the operating system installed on the virtual machine.
+ State types.String `tfsdk:"state"` // The state of the virtual machine.
+ Home types.String `tfsdk:"home"` // The path to the virtual machine home directory.
+}
diff --git a/internal/virtualmachine/data_source_schema.go b/internal/virtualmachine/schemas/datasource_schema_v0.go
similarity index 55%
rename from internal/virtualmachine/data_source_schema.go
rename to internal/virtualmachine/schemas/datasource_schema_v0.go
index 859c5a2..d9afc98 100644
--- a/internal/virtualmachine/data_source_schema.go
+++ b/internal/virtualmachine/schemas/datasource_schema_v0.go
@@ -1,4 +1,4 @@
-package virtualmachine
+package schemas
import (
"terraform-provider-parallels-desktop/internal/schemas/authenticator"
@@ -7,7 +7,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
)
-var virtualMachineDataSourceSchema = schema.Schema{
+var VirtualMachineDataSourceSchemaV0 = schema.Schema{
MarkdownDescription: "Virtual Machine Data Source",
Blocks: map[string]schema.Block{
authenticator.SchemaName: authenticator.SchemaBlock,
@@ -22,25 +22,32 @@ var virtualMachineDataSourceSchema = schema.Schema{
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"host_ip": schema.StringAttribute{
- Computed: true,
+ MarkdownDescription: "The IP address of the host machine",
+ Computed: true,
},
"id": schema.StringAttribute{
- Computed: true,
+ MarkdownDescription: "The unique identifier of the virtual machine",
+ Computed: true,
},
"name": schema.StringAttribute{
- Computed: true,
+ MarkdownDescription: "The name of the virtual machine",
+ Computed: true,
},
"description": schema.StringAttribute{
- Computed: true,
+ MarkdownDescription: "The description of the virtual machine",
+ Computed: true,
},
"os_type": schema.StringAttribute{
- Computed: true,
+ MarkdownDescription: "The type of the operating system installed on the virtual machine",
+ Computed: true,
},
"state": schema.StringAttribute{
- Computed: true,
+ MarkdownDescription: "The state of the virtual machine",
+ Computed: true,
},
"home": schema.StringAttribute{
- Computed: true,
+ MarkdownDescription: "The path to the virtual machine home directory",
+ Computed: true,
},
},
},
diff --git a/internal/virtualmachine/schemas/datasource_schema_v1.go b/internal/virtualmachine/schemas/datasource_schema_v1.go
new file mode 100644
index 0000000..11872ae
--- /dev/null
+++ b/internal/virtualmachine/schemas/datasource_schema_v1.go
@@ -0,0 +1,88 @@
+package schemas
+
+import (
+ "terraform-provider-parallels-desktop/internal/schemas/authenticator"
+ "terraform-provider-parallels-desktop/internal/schemas/filter"
+
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+)
+
+var VirtualMachineDataSourceSchemaV1 = schema.Schema{
+ MarkdownDescription: "Virtual Machine Data Source",
+ Blocks: map[string]schema.Block{
+ authenticator.SchemaName: authenticator.SchemaBlock,
+ filter.SchemaName: filter.SchemaBlock,
+ },
+ Attributes: map[string]schema.Attribute{
+ "host": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps Host",
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.AtLeastOneOf(path.Expressions{
+ path.MatchRoot("orchestrator"),
+ path.MatchRoot("host"),
+ }...),
+ },
+ },
+ "orchestrator": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps Orchestrator",
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.AtLeastOneOf(path.Expressions{
+ path.MatchRoot("orchestrator"),
+ path.MatchRoot("host"),
+ }...),
+ },
+ },
+ "machines": schema.ListNestedAttribute{
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "host_ip": schema.StringAttribute{
+ MarkdownDescription: "The IP address of the host machine",
+ Computed: true,
+ },
+ "id": schema.StringAttribute{
+ MarkdownDescription: "The unique identifier of the virtual machine",
+ Computed: true,
+ },
+ "name": schema.StringAttribute{
+ MarkdownDescription: "The name of the virtual machine",
+ Computed: true,
+ },
+ "description": schema.StringAttribute{
+ MarkdownDescription: "The description of the virtual machine",
+ Computed: true,
+ },
+ "os_type": schema.StringAttribute{
+ MarkdownDescription: "The type of the operating system installed on the virtual machine",
+ Computed: true,
+ },
+ "state": schema.StringAttribute{
+ MarkdownDescription: "The state of the virtual machine",
+ Computed: true,
+ },
+ "home": schema.StringAttribute{
+ MarkdownDescription: "The path to the virtual machine home directory",
+ Computed: true,
+ },
+ "orchestrator_host_id": schema.StringAttribute{
+ MarkdownDescription: "Orchestrator Host Id if the VM is running in an orchestrator",
+ Computed: true,
+ },
+ "external_ip": schema.StringAttribute{
+ MarkdownDescription: "VM external IP address",
+ Computed: true,
+ },
+ "internal_ip": schema.StringAttribute{
+ MarkdownDescription: "VM internal IP address",
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+}
diff --git a/internal/virtualmachinestate/resource_models.go b/internal/virtualmachinestate/models/resource_model_v0.go
similarity index 87%
rename from internal/virtualmachinestate/resource_models.go
rename to internal/virtualmachinestate/models/resource_model_v0.go
index 70129e5..a3d3ae8 100644
--- a/internal/virtualmachinestate/resource_models.go
+++ b/internal/virtualmachinestate/models/resource_model_v0.go
@@ -1,4 +1,4 @@
-package virtualmachinestate
+package models
import (
"terraform-provider-parallels-desktop/internal/schemas/authenticator"
@@ -7,7 +7,7 @@ import (
)
// VirtualMachineStateResourceModel describes the resource data model.
-type VirtualMachineStateResourceModel struct {
+type VirtualMachineStateResourceModelV0 struct {
Authenticator *authenticator.Authentication `tfsdk:"authenticator"`
Host types.String `tfsdk:"host"`
ID types.String `tfsdk:"id"`
diff --git a/internal/virtualmachinestate/models/resource_model_v1.go b/internal/virtualmachinestate/models/resource_model_v1.go
new file mode 100644
index 0000000..c9ea0ed
--- /dev/null
+++ b/internal/virtualmachinestate/models/resource_model_v1.go
@@ -0,0 +1,17 @@
+package models
+
+import (
+ "terraform-provider-parallels-desktop/internal/schemas/authenticator"
+
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+// VirtualMachineStateResourceModel describes the resource data model.
+type VirtualMachineStateResourceModelV1 struct {
+ Authenticator *authenticator.Authentication `tfsdk:"authenticator"`
+ Orchestrator types.String `tfsdk:"orchestrator"`
+ Host types.String `tfsdk:"host"`
+ ID types.String `tfsdk:"id"`
+ Operation types.String `tfsdk:"operation"`
+ CurrentState types.String `tfsdk:"current_state"`
+}
diff --git a/internal/virtualmachinestate/resource.go b/internal/virtualmachinestate/resource.go
index ab09fa7..cbc3c24 100644
--- a/internal/virtualmachinestate/resource.go
+++ b/internal/virtualmachinestate/resource.go
@@ -7,6 +7,8 @@ import (
"terraform-provider-parallels-desktop/internal/apiclient"
"terraform-provider-parallels-desktop/internal/models"
+ resource_models "terraform-provider-parallels-desktop/internal/virtualmachinestate/models"
+ "terraform-provider-parallels-desktop/internal/virtualmachinestate/schemas"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
@@ -34,7 +36,7 @@ func (r *VirtualMachineStateResource) Metadata(ctx context.Context, req resource
}
func (r *VirtualMachineStateResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
- resp.Schema = virtualMachineStateResourceSchema
+ resp.Schema = schemas.VirtualMachineStateResourceSchemaV1
}
func (r *VirtualMachineStateResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
@@ -55,19 +57,32 @@ func (r *VirtualMachineStateResource) Configure(ctx context.Context, req resourc
}
func (r *VirtualMachineStateResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
- var data VirtualMachineStateResourceModel
+ var data resource_models.VirtualMachineStateResourceModelV1
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
- if data.Host.ValueString() == "" {
+
+ // selecting if this is a standalone host or an orchestrator
+ isOrchestrator := false
+ var host string
+ if data.Orchestrator.ValueString() != "" {
+ isOrchestrator = true
+ host = data.Orchestrator.ValueString()
+ } else {
+ host = data.Host.ValueString()
+ }
+
+ if host == "" {
resp.Diagnostics.AddError("host cannot be empty", "Host cannot be null")
return
}
+
hostConfig := apiclient.HostConfig{
- Host: data.Host.ValueString(),
+ Host: host,
+ IsOrchestrator: isOrchestrator,
License: r.provider.License.ValueString(),
Authorization: data.Authenticator,
DisableTlsValidation: r.provider.DisableTlsValidation.ValueBool(),
@@ -103,7 +118,7 @@ func (r *VirtualMachineStateResource) Create(ctx context.Context, req resource.C
}
func (r *VirtualMachineStateResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
- var data VirtualMachineStateResourceModel
+ var data resource_models.VirtualMachineStateResourceModelV1
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
@@ -111,13 +126,24 @@ func (r *VirtualMachineStateResource) Read(ctx context.Context, req resource.Rea
return
}
- if data.Host.ValueString() == "" {
+ // selecting if this is a standalone host or an orchestrator
+ isOrchestrator := false
+ var host string
+ if data.Orchestrator.ValueString() != "" {
+ isOrchestrator = true
+ host = data.Orchestrator.ValueString()
+ } else {
+ host = data.Host.ValueString()
+ }
+
+ if host == "" {
resp.Diagnostics.AddError("host cannot be empty", "Host cannot be null")
return
}
hostConfig := apiclient.HostConfig{
- Host: data.Host.ValueString(),
+ Host: host,
+ IsOrchestrator: isOrchestrator,
License: r.provider.License.ValueString(),
Authorization: data.Authenticator,
DisableTlsValidation: r.provider.DisableTlsValidation.ValueBool(),
@@ -143,7 +169,7 @@ func (r *VirtualMachineStateResource) Read(ctx context.Context, req resource.Rea
}
func (r *VirtualMachineStateResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
- var data VirtualMachineStateResourceModel
+ var data resource_models.VirtualMachineStateResourceModelV1
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
@@ -151,18 +177,29 @@ func (r *VirtualMachineStateResource) Update(ctx context.Context, req resource.U
return
}
- if data.Host.ValueString() == "" {
- resp.Diagnostics.AddError("host cannot be empty", "Host cannot be null")
+ if data.ID.IsNull() {
+ resp.Diagnostics.AddError("Id is required", "Id is required")
return
}
- if data.ID.IsNull() {
- resp.Diagnostics.AddError("Id is required", "Id is required")
+ // selecting if this is a standalone host or an orchestrator
+ isOrchestrator := false
+ var host string
+ if data.Orchestrator.ValueString() != "" {
+ isOrchestrator = true
+ host = data.Orchestrator.ValueString()
+ } else {
+ host = data.Host.ValueString()
+ }
+
+ if host == "" {
+ resp.Diagnostics.AddError("host cannot be empty", "Host cannot be null")
return
}
hostConfig := apiclient.HostConfig{
- Host: data.Host.ValueString(),
+ Host: host,
+ IsOrchestrator: isOrchestrator,
License: r.provider.License.ValueString(),
Authorization: data.Authenticator,
DisableTlsValidation: r.provider.DisableTlsValidation.ValueBool(),
@@ -217,7 +254,7 @@ func (r *VirtualMachineStateResource) Update(ctx context.Context, req resource.U
}
func (r *VirtualMachineStateResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
- var data VirtualMachineStateResourceModel
+ var data resource_models.VirtualMachineStateResourceModelV1
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
diff --git a/internal/virtualmachinestate/resource_schema.go b/internal/virtualmachinestate/schemas/resource_schema_v0.go
similarity index 94%
rename from internal/virtualmachinestate/resource_schema.go
rename to internal/virtualmachinestate/schemas/resource_schema_v0.go
index 89fc0b8..f430af3 100644
--- a/internal/virtualmachinestate/resource_schema.go
+++ b/internal/virtualmachinestate/schemas/resource_schema_v0.go
@@ -1,4 +1,4 @@
-package virtualmachinestate
+package schemas
import (
"terraform-provider-parallels-desktop/internal/schemas/authenticator"
@@ -10,7 +10,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)
-var virtualMachineStateResourceSchema = schema.Schema{
+var VirtualMachineStateResourceSchemaV0 = schema.Schema{
MarkdownDescription: "Parallels Virtual Machine State Resource\n Use this to set a virtual machine to a desired state.",
Blocks: map[string]schema.Block{
authenticator.SchemaName: authenticator.SchemaBlock,
diff --git a/internal/virtualmachinestate/schemas/resource_schema_v1.go b/internal/virtualmachinestate/schemas/resource_schema_v1.go
new file mode 100644
index 0000000..75d51a0
--- /dev/null
+++ b/internal/virtualmachinestate/schemas/resource_schema_v1.go
@@ -0,0 +1,65 @@
+package schemas
+
+import (
+ "terraform-provider-parallels-desktop/internal/schemas/authenticator"
+
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/path"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+ "github.com/hashicorp/terraform-plugin-framework/schema/validator"
+)
+
+var VirtualMachineStateResourceSchemaV1 = schema.Schema{
+ MarkdownDescription: "Parallels Virtual Machine State Resource\n Use this to set a virtual machine to a desired state.",
+ Blocks: map[string]schema.Block{
+ authenticator.SchemaName: authenticator.SchemaBlock,
+ },
+ Attributes: map[string]schema.Attribute{
+ "host": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps Host",
+ Optional: true,
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ Validators: []validator.String{
+ stringvalidator.AtLeastOneOf(path.Expressions{
+ path.MatchRoot("orchestrator"),
+ path.MatchRoot("host"),
+ }...),
+ },
+ },
+ "orchestrator": schema.StringAttribute{
+ MarkdownDescription: "Parallels Desktop DevOps Orchestrator",
+ Optional: true,
+ Validators: []validator.String{
+ stringvalidator.AtLeastOneOf(path.Expressions{
+ path.MatchRoot("orchestrator"),
+ path.MatchRoot("host"),
+ }...),
+ },
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ },
+ "id": schema.StringAttribute{
+ MarkdownDescription: "Virtual Machine Id",
+ Required: true,
+ },
+ "operation": schema.StringAttribute{
+ Required: true,
+ MarkdownDescription: "Virtual Machine desired state",
+ Validators: []validator.String{
+ stringvalidator.OneOf("start", "stop", "suspend", "pause", "resume", "restart"),
+ },
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.UseStateForUnknown(),
+ },
+ },
+ "current_state": schema.StringAttribute{
+ Computed: true,
+ MarkdownDescription: "Virtual Machine current state",
+ },
+ },
+}