From 71c998a0fd274519d37df5202af5b9f53eca1482 Mon Sep 17 00:00:00 2001 From: Matt Pryor Date: Wed, 4 Oct 2023 13:55:21 +0100 Subject: [PATCH] Allow adopting hosts into inventory without provisioning (#10) * Rework install logic slightly * Support readonly mode for Terraform state --- roles/infra/defaults/main.yml | 3 + roles/infra/tasks/inventory_adopt.yml | 6 +- roles/infra/tasks/main.yml | 37 +++++++++- roles/install/defaults/main.yml | 7 +- roles/install/tasks/main.yml | 100 ++++++++++---------------- 5 files changed, 85 insertions(+), 68 deletions(-) diff --git a/roles/infra/defaults/main.yml b/roles/infra/defaults/main.yml index 39ec8c2..b5a01b4 100644 --- a/roles/infra/defaults/main.yml +++ b/roles/infra/defaults/main.yml @@ -1,6 +1,9 @@ # The path to the Terraform binary, if not in the PATH terraform_binary_path: +# If true, the state from the last deploy is used rather than provisioning +terraform_readonly: false + # By default, provision the infrastructure rather than tear it down terraform_state: present diff --git a/roles/infra/tasks/inventory_adopt.yml b/roles/infra/tasks/inventory_adopt.yml index e6983e1..9659c6c 100644 --- a/roles/infra/tasks/inventory_adopt.yml +++ b/roles/infra/tasks/inventory_adopt.yml @@ -2,15 +2,15 @@ - name: Set facts from Terraform outputs set_fact: - cluster_gateway_ip: "{{ terraform_provision.outputs.cluster_gateway_ip.value }}" - cluster_nodes: "{{ terraform_provision.outputs.cluster_nodes.value }}" + cluster_gateway_ip: "{{ terraform_provision_state.outputs.cluster_gateway_ip.value }}" + cluster_nodes: "{{ terraform_provision_state.outputs.cluster_nodes.value }}" # We allow the SSH private key file to be specified # If it is not, we expect a private key to be in the Terraform outputs - block: - name: Get SSH private key from Terraform output set_fact: - cluster_ssh_private_key: "{{ terraform_provision.outputs.cluster_ssh_private_key.value }}" + cluster_ssh_private_key: "{{ terraform_provision_state.outputs.cluster_ssh_private_key.value }}" - name: Get tempfile for SSH private key tempfile: register: cluster_ssh_private_key_tempfile diff --git a/roles/infra/tasks/main.yml b/roles/infra/tasks/main.yml index 962f26d..5920e57 100644 --- a/roles/infra/tasks/main.yml +++ b/roles/infra/tasks/main.yml @@ -1,6 +1,6 @@ --- -- name: Write backend configuration +- name: Write backend type configuration copy: content: | terraform { @@ -8,16 +8,47 @@ } dest: "{{ terraform_project_path }}/backend.tf" +- name: Write backend configuration options + copy: + content: "{{ terraform_backend_config | to_json }}" + dest: "{{ terraform_project_path }}/backend_config.json" + - name: Provision infrastructure using Terraform terraform: binary_path: "{{ terraform_binary_path or omit }}" project_path: "{{ terraform_project_path }}" state: "{{ terraform_state }}" - backend_config: "{{ terraform_backend_config }}" + backend_config_files: + - "{{ terraform_project_path }}/backend_config.json" force_init: yes init_reconfigure: yes variables: "{{ terraform_variables }}" - register: terraform_provision + register: terraform_provision_state + when: not terraform_readonly + +- name: Get outputs from Terraform state + block: + - name: Initialise Terraform + command: >- + {{ terraform_binary_path | default('terraform', True) }} + -chdir="{{ terraform_project_path }}" + init + -input=false + -reconfigure + -backend-config="{{ terraform_project_path }}/backend_config.json" + + - name: Pull Terraform state + command: >- + {{ terraform_binary_path | default('terraform', True) }} + -chdir="{{ terraform_project_path }}" + state + pull + register: terraform_state_pull + + - name: Set Terraform provision state fact + set_fact: + terraform_provision_state: "{{ terraform_state_pull.stdout | from_json }}" + when: terraform_readonly - name: Populate in-memory inventory include_tasks: inventory_adopt.yml diff --git a/roles/install/defaults/main.yml b/roles/install/defaults/main.yml index 636fc36..5216815 100644 --- a/roles/install/defaults/main.yml +++ b/roles/install/defaults/main.yml @@ -13,8 +13,13 @@ terraform_zip_name: terraform_{{ terraform_version_max }}_{{ terraform_os }}_{{ # The URL of the Terraform binary terraform_binary_url: https://releases.hashicorp.com/terraform/{{ terraform_version_max }}/{{ terraform_zip_name }} -# The directory to put the Terraform binary in +# The directory to put the Terraform binary in if we download it terraform_binary_directory: /usr/local/bin +# List of directories to search for a suitable Terraform +# By default, search the binary directory + the directories in the path +terraform_additional_search_directories: "{{ lookup('ansible.builtin.env', 'PATH') | split(':') | list }}" +terraform_search_directories: "{{ ([terraform_binary_directory] + terraform_additional_search_directories) | unique }}" + # Become root when downloading Terraform binary terraform_download_binary_become: false diff --git a/roles/install/tasks/main.yml b/roles/install/tasks/main.yml index 57c456c..672f490 100644 --- a/roles/install/tasks/main.yml +++ b/roles/install/tasks/main.yml @@ -1,88 +1,66 @@ --- -- name: Get directories on the PATH - set_fact: - terraform_path_dirs: "{{ terraform_path_dirs | default([]) + [item] }}" - loop: "{{ lookup('ansible.builtin.env', 'PATH') | split(':') }}" - -- name: Check if Terraform binary exists in PATH directories +- name: Check if Terraform binary exists in search directories stat: path: "{{ item }}/terraform" register: terraform_binary_paths - loop: "{{ ( [terraform_binary_directory] + terraform_path_dirs ) | unique }}" + loop: "{{ terraform_search_directories }}" -- name: Check Terraform versions available on the PATH - command: - cmd: "{{ item.stat.path }} version -json" - register: current_terraform_version +- name: Get available Terraform versions + command: "{{ item.stat.path }} version -json" + register: terraform_version_info changed_when: false - loop: "{{ terraform_binary_paths.results | selectattr('stat.exists', 'equalto', True) }}" + loop: "{{ terraform_binary_paths.results | selectattr('stat.exists') }}" loop_control: label: "{{ item.stat.path }}" -- name: Set current Terraform versions fact +- name: Set Terraform versions fact set_fact: - terraform_version_stdout: >- - {{ - terraform_version_stdout | default([]) + - [ - {'path': item.item.stat.path, 'version': item.stdout | from_json } - ] - }} - loop: "{{ current_terraform_version.results }}" - loop_control: - label: "{{ item.item.stat.path }}" + terraform_available_versions: >- + [ + {% for item in terraform_version_info.results %} + ("{{ item.item.stat.path }}", "{{ item.stdout | from_json | json_query('terraform_version') }}"), + {% endfor %} + ] -- name: Filter Terraform paths to match version requirements +- name: Filter Terraform versions set_fact: - terraform_filtered_path: >- - {{ - terraform_version_stdout | - sort(attribute='version.terraform_version') | - selectattr( - 'version.terraform_version', 'version', terraform_version_min, '>=' - ) | - selectattr( - 'version.terraform_version', 'version', terraform_version_max, '<=' - ) | - last | - default([]) + terraform_acceptable_versions: >- + {{- + terraform_available_versions | + selectattr('1', 'version', terraform_version_min, '>=') | + selectattr('1', 'version', terraform_version_max, '<=') | + sort(attribute = '1', reverse = True) }} -- name: Set current Terraform path fact +- name: Set Terraform binary path set_fact: - terraform_binary_path: "{{ terraform_filtered_path.path }}" - when: - - terraform_filtered_path | length == 1 + terraform_binary_path: "{{ terraform_acceptable_versions.0.0 }}" + terraform_detected_version: "{{ terraform_acceptable_versions.0.1 }}" + when: "terraform_acceptable_versions | length > 0" - name: Download Terraform binary if an acceptable version is not available block: - - name: Ensure Terraform bin directory exists - file: - path: "{{ terraform_binary_directory }}" - state: directory + - name: Ensure Terraform bin directory exists + file: + path: "{{ terraform_binary_directory }}" + state: directory - - name: Download Terraform binary - unarchive: - remote_src: yes - src: "{{ terraform_binary_url }}" - dest: "{{ terraform_binary_directory }}" - - - name: Set current Terraform path fact - set_fact: - terraform_binary_path: "{{ terraform_binary_directory }}/terraform" - + - name: Download Terraform binary + unarchive: + remote_src: yes + src: "{{ terraform_binary_url }}" + dest: "{{ terraform_binary_directory }}" + + - name: Set current Terraform path fact + set_fact: + terraform_binary_path: "{{ terraform_binary_directory }}/terraform" when: terraform_binary_path is not defined become: "{{ terraform_download_binary_become }}" - name: Display Terraform path and version message debug: msg: >- - Using Terraform version {{ terraform_detected_version }} + Using Terraform version + {{ terraform_detected_version | default(terraform_version_max) }} from {{ terraform_binary_path }}. - vars: - terraform_detected_version: >- - {{ terraform_filtered_path.version.terraform_version - if terraform_filtered_path | length > 0 - else terraform_version_max - }}