Skip to content

Commit cd2db22

Browse files
committed
vdi module - idempotence update
1 parent 579de9e commit cd2db22

24 files changed

Lines changed: 1696 additions & 900 deletions

File tree

community/modules/scripts/vdi-setup/README.md

Lines changed: 94 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
## Description
22

3-
Creates a containerised Guacamole instance. Works with the Rocky, Debian and Ubuntu images shown in blueprint example below.
3+
Creates a containerised Guacamole instance. Currently designed to work mainly with the Rocky image in the blueprint example below. Ubuntu/Debian support previously worked too but fixes may be required due to updates.
44

55
## Features
66

77
- **VDI Tool Support**: Currently supports Guacamole for web-based VDI access
8-
- **User Provisioning**: Supports local user creation with secure password management
9-
- **VNC Integration**: Configures VNC servers for desktop access
8+
- **User Provisioning**: Supports local user creation with secure password management and group management
9+
- **VNC Integration**: Configures VNC servers for desktop access with port change support
1010
- **Secret Manager Integration**: Secure password storage and retrieval for VDI users and webapp admin
11-
- **VDI Monitoring System**: Automatic monitoring and reconfiguration capabilities with password reset flags and deployment status updates
11+
- **VDI Monitoring System**: Automatic monitoring and reconfiguration capabilities with password reset flags and deployment status updates. [More info](roles/vdi_monitor/README.md).
1212
- **Debug Mode**: Comprehensive logging when enabled via the `debug` variable
13+
- **Port Change Support**: Handles both webapp port changes (container recreation) and user VNC port changes (SQL updates)
1314

1415
## Secret Manager Integration
1516

@@ -21,11 +22,31 @@ The module integrates with Google Cloud Secret Manager for secure password handl
2122
- **Reset Functionality**: Both individual user passwords (`reset_password`) and webapp admin password (`reset_webapp_admin_password`) can be forced to regenerate
2223
- **Database Updates**: Password resets update existing database records without re-initializing the entire database
2324

25+
## Port Change and User Group Management
26+
27+
The module supports dynamic configuration changes without full redeployment:
28+
29+
### **Webapp Port Changes**
30+
- **Container Recreation**: When `vdi_webapp_port` changes, the Guacamole webapp container is recreated with the new port
31+
- **Database Synchronization**: Database passwords are automatically synchronized during port changes
32+
- **Service Continuity**: VNC services remain unaffected during webapp port changes
33+
34+
### **User VNC Port Changes**
35+
- **SQL Updates**: User VNC port changes are handled via database updates without container recreation
36+
- **VNC Service Management**: VNC services are automatically stopped and restarted with new ports
37+
- **X Server Cleanup**: Old X server processes are properly terminated before starting new ones
38+
39+
### **User Group Management**
40+
- **Dynamic Group Changes**: When `vdi_user_group` changes, users are automatically migrated to the new group
41+
- **Group Removal**: Users are removed from the old group and added to the new group
42+
- **Idempotent Operations**: Group changes are safe to apply multiple times
43+
2444
## VDI Monitoring System
2545

2646
The module includes a monitoring system that:
2747

28-
- **Deployment Status**: Updates instance metadata to reflect deployment state (`available`, `reconfiguring`)
48+
- **Deployment Status**: Updates instance metadata to reflect deployment state (`available`, `reconfiguring`, `failed`)
49+
- **Targeted Updates**: Performs database-only updates for password resets and user changes without container recreation
2950
- **Password Reset Flags**: Supports `reset_password` for individual users and `reset_webapp_admin_password` for the webapp admin account
3051
- **Automatic Reconfiguration**: Detects changes and triggers reconfiguration when needed
3152
- **Status Tracking**: Maintains deployment status through instance metadata
@@ -101,14 +122,13 @@ deployment_groups:
101122
source: modules/compute/vm-instance
102123
settings:
103124
instance_image:
104-
# Several supported image families:
105125
family: hpc-rocky-linux-8
106126
project: cloud-hpc-image-public
107127
#family: debian-11
108128
#project: debian-cloud
109129
#family: ubuntu-2204-lts
110130
#project: ubuntu-os-cloud
111-
name_prefix: debian
131+
name_prefix: guacamole
112132
add_deployment_name_before_prefix: true
113133
machine_type: e2-highcpu-8
114134
tags: ["guacamole"]
@@ -156,3 +176,70 @@ After deployment, you can access the VDI in several ways:
156176
```
157177

158178
- The web interface will then be accessible from http://localhost:8080/guacamole/ (for Guacamole)
179+
180+
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
181+
## Requirements
182+
183+
| Name | Version |
184+
|------|---------|
185+
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.0 |
186+
| <a name="requirement_archive"></a> [archive](#requirement\_archive) | ~> 2.0 |
187+
| <a name="requirement_google"></a> [google](#requirement\_google) | >= 3.83 |
188+
| <a name="requirement_random"></a> [random](#requirement\_random) | ~> 3.0 |
189+
190+
## Providers
191+
192+
| Name | Version |
193+
|------|---------|
194+
| <a name="provider_archive"></a> [archive](#provider\_archive) | ~> 2.0 |
195+
| <a name="provider_google"></a> [google](#provider\_google) | >= 3.83 |
196+
| <a name="provider_random"></a> [random](#provider\_random) | ~> 3.0 |
197+
| <a name="provider_terraform"></a> [terraform](#provider\_terraform) | n/a |
198+
199+
## Modules
200+
201+
| Name | Source | Version |
202+
|------|--------|---------|
203+
| <a name="module_startup_script"></a> [startup\_script](#module\_startup\_script) | ../../../../modules/scripts/startup-script | n/a |
204+
205+
## Resources
206+
207+
| Name | Type |
208+
|------|------|
209+
| [google_storage_bucket.bucket](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/storage_bucket) | resource |
210+
| [random_id.resource_name_suffix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource |
211+
| [terraform_data.input_validation](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) | resource |
212+
| [archive_file.roles_tar](https://registry.terraform.io/providers/hashicorp/archive/latest/docs/data-sources/file) | data source |
213+
214+
## Inputs
215+
216+
| Name | Description | Type | Default | Required |
217+
|------|-------------|------|---------|:--------:|
218+
| <a name="input_debug"></a> [debug](#input\_debug) | Enable debug mode for verbose logging during VDI setup. | `bool` | `false` | no |
219+
| <a name="input_deployment_name"></a> [deployment\_name](#input\_deployment\_name) | The name of the deployment. | `string` | n/a | yes |
220+
| <a name="input_force_rerun"></a> [force\_rerun](#input\_force\_rerun) | Force complete container recreation and database re-initialization, bypassing all idempotency checks. Use only when troubleshooting or when the system is in a broken state. | `bool` | `false` | no |
221+
| <a name="input_labels"></a> [labels](#input\_labels) | Key-value pairs of labels to be added to created resources. | `map(string)` | n/a | yes |
222+
| <a name="input_project_id"></a> [project\_id](#input\_project\_id) | Project in which the HPC deployment will be created. | `string` | n/a | yes |
223+
| <a name="input_region"></a> [region](#input\_region) | Region to place bucket containing startup script. | `string` | n/a | yes |
224+
| <a name="input_reset_webapp_admin_password"></a> [reset\_webapp\_admin\_password](#input\_reset\_webapp\_admin\_password) | Force reset of the webapp admin password during reconfiguration. If true, a new password will be generated and stored in Secret Manager, even if an existing password exists. | `bool` | `false` | no |
225+
| <a name="input_user_provision"></a> [user\_provision](#input\_user\_provision) | User type to create (local\_users supported. os-login to do. | `string` | `"local_users"` | no |
226+
| <a name="input_vdi_resolution"></a> [vdi\_resolution](#input\_vdi\_resolution) | Desktop resolution for VNC sessions (e.g. 1920x1080). | `string` | `"1920x1080"` | no |
227+
| <a name="input_vdi_tool"></a> [vdi\_tool](#input\_vdi\_tool) | VDI tool to deploy (guacamole currently supported). | `string` | `"guacamole"` | no |
228+
| <a name="input_vdi_user_group"></a> [vdi\_user\_group](#input\_vdi\_user\_group) | Unix group to create/use for VDI users. | `string` | `"vdiusers"` | no |
229+
| <a name="input_vdi_users"></a> [vdi\_users](#input\_vdi\_users) | List of VDI users to configure. Passwords are handled securely by the Ansible roles: if secret\_name is provided, the password is fetched from Secret Manager; if neither password nor secret\_name is provided, a random password is generated and stored in Secret Manager. If secret\_project is provided, it specifies the GCP project where the secret is stored (defaults to the deployment project). Set reset\_password to true to trigger password regeneration for auto-generated passwords. | <pre>list(object({<br/> username = string<br/> port = number<br/> secret_name = optional(string)<br/> secret_project = optional(string)<br/> reset_password = optional(bool)<br/> }))</pre> | `[]` | no |
230+
| <a name="input_vdi_webapp_port"></a> [vdi\_webapp\_port](#input\_vdi\_webapp\_port) | Port to serve the Webapp interface from if applicable (note: containers will be recreated if changed) | `string` | `"8080"` | no |
231+
| <a name="input_vnc_flavor"></a> [vnc\_flavor](#input\_vnc\_flavor) | The VNC server flavor to use (tigervnc currently supported) | `string` | `"tigervnc"` | no |
232+
| <a name="input_vnc_port_max"></a> [vnc\_port\_max](#input\_vnc\_port\_max) | Maximum valid VNC port. | `number` | `5999` | no |
233+
| <a name="input_vnc_port_min"></a> [vnc\_port\_min](#input\_vnc\_port\_min) | Minimum valid VNC port. | `number` | `5901` | no |
234+
| <a name="input_zone"></a> [zone](#input\_zone) | Zone in which the VDI instances are created. | `string` | n/a | yes |
235+
236+
## Outputs
237+
238+
| Name | Description |
239+
|------|-------------|
240+
| <a name="output_guacamole_admin_password_secret"></a> [guacamole\_admin\_password\_secret](#output\_guacamole\_admin\_password\_secret) | The name of the Secret Manager secret containing the Guacamole admin password |
241+
| <a name="output_guacamole_admin_username"></a> [guacamole\_admin\_username](#output\_guacamole\_admin\_username) | The admin username for Guacamole |
242+
| <a name="output_startup_script"></a> [startup\_script](#output\_startup\_script) | Combined startup script that installs VDI (VNC, Guacamole, users). |
243+
| <a name="output_vdi_runner"></a> [vdi\_runner](#output\_vdi\_runner) | Shell runner wrapping Ansible playbook + roles (for custom-image or direct use). |
244+
| <a name="output_vdi_user_credentials"></a> [vdi\_user\_credentials](#output\_vdi\_user\_credentials) | Map of VDI user credentials stored in Secret Manager |
245+
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->

community/modules/scripts/vdi-setup/main.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ locals {
9999
}
100100
)
101101
destination = "/opt/vdi-setup/install.yaml"
102-
args = var.debug ? "--extra-vars @/opt/vdi-setup/vars.yaml -v --extra-vars debug=true" : "--extra-vars @/opt/vdi-setup/vars.yaml"
102+
args = var.debug ? "--extra-vars @/opt/vdi-setup/vars.yaml -v --extra-vars debug=true" : "--extra-vars @/opt/vdi-setup/vars.yaml"
103103
},
104104
# Clean up temporary directory
105105
{

community/modules/scripts/vdi-setup/roles/base_os/tasks/main.yaml

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,30 @@
2626
ansible.builtin.import_role:
2727
name: lock_manager
2828
tasks_from: check_lock
29-
register: lock_check_result
3029

3130
- name: DEBUG Finished import_role for base_os
3231
debug:
3332
msg: "Finished import_role for base_os"
3433

35-
- name: Set role_should_run from lock check result
34+
- name: Set role variables from lock manager facts
3635
ansible.builtin.set_fact:
37-
role_should_run: "{{ lock_check_result.ansible_facts.role_should_run | default(true) }}"
36+
role_should_run: "{{ role_should_run | default(false) }}"
37+
is_fresh_deployment: "{{ is_fresh_deployment | default(false) }}"
38+
deployment_hash: "{{ current_deployment_hash | default('none') }}"
39+
user_hash: "{{ current_user_secrets_hash | default('none') }}"
40+
lock_file_stat: "{{ lock_file_stat | default({}) }}"
41+
vdi_setup_status: "{{ vdi_setup_status | default({}) }}"
42+
43+
- name: Debug role variables
44+
ansible.builtin.debug:
45+
msg: |
46+
Base OS role variables:
47+
- role_should_run: {{ role_should_run }}
48+
- is_fresh_deployment: {{ is_fresh_deployment }}
49+
- deployment_hash: {{ deployment_hash }}
50+
- user_hash: {{ user_hash }}
51+
- execution_reason: {{ 'Fresh deployment' if is_fresh_deployment else ('Hash mismatch' if not role_should_run else 'Normal role execution') }}
52+
when: debug
3853

3954
# Skip all tasks if role should not run
4055
- name: Skip base_os tasks if role should not run
@@ -57,21 +72,21 @@
5772
# Include distribution-specific tasks
5873
- name: Include Ubuntu-specific tasks
5974
ansible.builtin.include_tasks: ubuntu.yaml
60-
when:
61-
- ansible_facts.distribution == 'Ubuntu'
62-
- role_should_run
75+
when:
76+
- ansible_facts.distribution == 'Ubuntu'
77+
- role_should_run
6378

6479
- name: Include Rocky Linux specific tasks
6580
ansible.builtin.include_tasks: rocky.yaml
66-
when:
67-
- ansible_facts.distribution == 'Rocky'
68-
- role_should_run
81+
when:
82+
- ansible_facts.distribution == 'Rocky'
83+
- role_should_run
6984

7085
- name: Include Debian-specific tasks
7186
ansible.builtin.include_tasks: debian.yaml
72-
when:
73-
- ansible_facts.distribution == 'Debian'
74-
- role_should_run
87+
when:
88+
- ansible_facts.distribution == 'Debian'
89+
- role_should_run
7590

7691
# Common tasks for all distributions
7792
- name: Ensure requests package is installed
@@ -92,9 +107,9 @@
92107
# GPU / vWS driver setup
93108
- name: Provision GPU setup
94109
ansible.builtin.include_tasks: gpu_driver_setup.yaml
95-
when:
96-
- nvidia_gpu_present.rc == 0
97-
- role_should_run
110+
when:
111+
- nvidia_gpu_present.rc == 0
112+
- role_should_run
98113

99114
# Mark role as completed
100115
- name: Set current role for lock manager completion
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# Lock Manager Role
2+
3+
The `lock_manager` role is the central coordination mechanism for the VDI setup module. It manages deployment state, change detection, and ensures idempotent role execution.
4+
5+
## Overview
6+
7+
The lock manager uses a hash-based change detection system to determine when roles should run, preventing unnecessary re-execution while ensuring changes are properly applied.
8+
9+
## Key Features
10+
11+
### 1. **Hash-Based Change Detection**
12+
13+
- **Deployment Hash**: Tracks changes to deployment configuration (ports, tools, etc.)
14+
- **User Hash**: Tracks changes to user configurations (additions, removals, password resets)
15+
- **Smart Detection**: Only re-runs roles when relevant changes are detected
16+
17+
### 2. **Centralized Variable Management**
18+
19+
- **User Management Variables**: Calculates and propagates user change information to all roles
20+
- **Port Change Detection**: Detects and tracks VNC port changes for existing users
21+
- **Password Reset Tracking**: Identifies users requiring password resets
22+
23+
### 3. **Targeted Updates**
24+
25+
- **Database-Only Updates**: Performs user additions/deletions/password resets without container recreation
26+
- **Port Change Handling**: Updates VNC ports and Guacamole connections without full re-initialization
27+
- **Webapp Admin Password Reset**: Updates admin password without container recreation
28+
29+
### 4. **Deployment Status Management**
30+
31+
- **Lock File**: `.vdi-lock.yaml` tracks deployment state and completed roles
32+
- **Status Tracking**: `available`, `configuring`, `reconfiguring`, `failed` states
33+
- **Role Completion**: Tracks which roles have completed successfully
34+
35+
## Variable Propagation
36+
37+
### **Variables Calculated by Lock Manager:**
38+
39+
| Variable | Description | Usage |
40+
|----------|-------------|-------|
41+
| `new_users` | Users added since last deployment | `vdi_tool`, `user_provision`, `vnc` |
42+
| `removed_users` | Users removed since last deployment | `vdi_tool`, `user_provision`, `vnc` |
43+
| `users_needing_reset` | Users with `reset_password: true` | `vdi_tool`, `secret_manager` |
44+
| `users_with_port_changes` | Users whose VNC ports changed | `vdi_tool`, `vnc` |
45+
| `webapp_port_changed` | Whether webapp port changed | `vdi_tool` |
46+
| `users_changed` | Any user-related changes detected | `vdi_tool` |
47+
| `db_needs_init` | Whether database needs re-initialization | `vdi_tool` |
48+
| `vdi_user_group` | Current VDI user group for group management | `user_provision` |
49+
50+
### **Variable Propagation Flow:**
51+
52+
1. **Lock Manager Calculation**: Variables calculated once during initial `lock_manager` run
53+
2. **Role Import**: Other roles import `lock_manager` and receive these variables
54+
3. **Defensive Programming**: Variables preserved with `default()` filters, not overwritten
55+
4. **Role Usage**: Each role uses only the variables it needs
56+
57+
## Data Flows
58+
59+
### **Initial Deployment:**
60+
61+
```mermaid
62+
1. lock_manager → Calculates all variables (empty for fresh deployment)
63+
2. base_os → OS setup
64+
3. secret_manager → Password generation and Secret Manager setup
65+
4. user_provision → Local user creation
66+
5. vnc → VNC service setup
67+
6. vdi_tool → Guacamole deployment
68+
7. vdi_monitor → Monitoring service setup
69+
```
70+
71+
### **Reconfiguration (User Changes):**
72+
73+
```mermaid
74+
1. lock_manager → Detects changes, calculates user management variables
75+
2. user_provision → Handles user additions/removals
76+
3. vnc → Handles VNC port changes and user removal
77+
4. vdi_tool → Handles database updates (targeted, no container recreation)
78+
5. vdi_monitor → No changes needed
79+
```
80+
81+
### **Reconfiguration (Port Changes):**
82+
83+
```mermaid
84+
1. lock_manager → Detects port changes
85+
2. vnc → Stops VNC services for users with port changes
86+
3. vdi_tool → Updates Guacamole connection ports in database
87+
4. user_provision → Updates local user configurations
88+
```
89+
90+
## Lock File Structure
91+
92+
```yaml
93+
vdi_setup_status:
94+
completed_roles:
95+
base_os: true
96+
secret_manager: true
97+
user_provision: true
98+
vdi_monitor: true
99+
vdi_tool: true
100+
vnc: true
101+
created_at: '2025-08-07T16:20:26Z'
102+
current_users: ['alice', 'bob', 'charlie', 'david']
103+
deployment_hash: 'abc123...'
104+
deployment_name: 'vdi-test-scott'
105+
force_rerun: false
106+
last_updated: '2025-08-07T16:28:42Z'
107+
lock_version: '1.0'
108+
setup_status: 'available'
109+
user_hash: 'def456...'
110+
user_ports:
111+
alice: 5901
112+
bob: 5902
113+
charlie: 5903
114+
david: 5904
115+
webapp_port: 8081
116+
vdi_user_group: vdiusers
117+
```
118+
119+
## Role Execution Logic
120+
121+
### **When Roles Run:**
122+
- **Fresh Deployment**: All roles run regardless of hash
123+
- **Hash Mismatch**: Only roles with changed hashes run
124+
- **Force Rerun**: All roles run when `force_rerun: true`
125+
126+
### **Role Completion:**
127+
- Roles mark themselves as completed in lock file
128+
- Lock file tracks completion status per role
129+
- Deployment status updated based on all role completions
130+
131+
## Error Handling
132+
133+
### **Failed Deployments:**
134+
- VDI monitor sets status to `"failed"` when Ansible exits with non-zero code
135+
- Lock file preserves failed state for debugging
136+
- Valid blueprint can be re-applied to fix `failed` deployments
137+
138+
### **Variable Safety:**
139+
- Defensive programming ensures all variables have safe defaults
140+
- `default()` filters prevent undefined variable errors
141+
- Variables preserved across role imports
142+
143+
## Debug Output
144+
145+
When `debug: true` is set, the lock manager provides detailed information about:
146+
- Variable calculations and propagation
147+
- Change detection results
148+
- Hash comparisons
149+
- Role execution decisions
150+
- Port change detection details

0 commit comments

Comments
 (0)