From d5efd56dae906c90bd089d22870ad620bb9ba5bc Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Mon, 29 Jan 2024 19:30:16 +0100 Subject: [PATCH] [stable-8] terraform: support diff for resource_changes (#7896) (#7914) terraform: support diff for resource_changes (#7896) (cherry picked from commit 0dc891bf37c1284bb07a71a4dabbbcd70751e956) Co-authored-by: Parsa Yousefi --- .../7896-add-terraform-diff-mode.yml | 2 + plugins/modules/terraform.py | 65 ++++++++++++++++++- 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 changelogs/fragments/7896-add-terraform-diff-mode.yml diff --git a/changelogs/fragments/7896-add-terraform-diff-mode.yml b/changelogs/fragments/7896-add-terraform-diff-mode.yml new file mode 100644 index 0000000000..7c0834efa5 --- /dev/null +++ b/changelogs/fragments/7896-add-terraform-diff-mode.yml @@ -0,0 +1,2 @@ +minor_changes: + - terraform - add support for ``diff_mode`` for terraform resource_changes (https://github.com/ansible-collections/community.general/pull/7896). diff --git a/plugins/modules/terraform.py b/plugins/modules/terraform.py index f6190493d1..5081640d94 100644 --- a/plugins/modules/terraform.py +++ b/plugins/modules/terraform.py @@ -21,7 +21,8 @@ attributes: check_mode: support: full diff_mode: - support: none + support: full + version_added: 8.3.0 options: state: choices: ['planned', 'present', 'absent'] @@ -428,6 +429,42 @@ def build_plan(command, project_path, variables_args, state_file, targets, state )) +def get_diff(diff_output): + def get_tf_resource_address(e): + return e['resource'] + + diff_json_output = json.loads(diff_output) + tf_reosource_changes = diff_json_output['resource_changes'] + diff_after = [] + diff_before = [] + changed = False + for item in tf_reosource_changes: + item_change = item['change'] + tf_before_state = {'resource': item['address'], 'change': item['change']['before']} + tf_after_state = {'resource': item['address'], 'change': item['change']['after']} + + if item_change['actions'] == ['update'] or item_change['actions'] == ['delete', 'create']: + diff_before.append(tf_before_state) + diff_after.append(tf_after_state) + changed = True + + if item_change['actions'] == ['delete']: + diff_before.append(tf_before_state) + changed = True + + if item_change['actions'] == ['create']: + diff_after.append(tf_after_state) + changed = True + + diff_before.sort(key=get_tf_resource_address) + diff_after.sort(key=get_tf_resource_address) + + return changed, dict( + before=({'data': diff_before}), + after=({'data': diff_after}), + ) + + def main(): global module module = AnsibleModule( @@ -619,6 +656,19 @@ def main(): "Consider switching the 'check_destroy' to false to suppress this error") command.append(plan_file) + result_diff = dict() + if module._diff or module.check_mode: + diff_command = [command[0], 'show', '-json', plan_file] + rc, diff_output, err = module.run_command(diff_command, check_rc=False, cwd=project_path) + changed, result_diff = get_diff(diff_output) + if rc != 0: + if workspace_ctx["current"] != workspace: + select_workspace(command[0], project_path, workspace_ctx["current"]) + module.fail_json(msg=err.rstrip(), rc=rc, stdout=out, + stdout_lines=out.splitlines(), stderr=err, + stderr_lines=err.splitlines(), + cmd=' '.join(command)) + if needs_application and not module.check_mode and state != 'planned': rc, out, err = module.run_command(command, check_rc=False, cwd=project_path) if rc != 0: @@ -651,7 +701,18 @@ def main(): if state == 'absent' and workspace != 'default' and purge_workspace is True: remove_workspace(command[0], project_path, workspace) - module.exit_json(changed=changed, state=state, workspace=workspace, outputs=outputs, stdout=out, stderr=err, command=' '.join(command)) + result = { + 'state': state, + 'workspace': workspace, + 'outputs': outputs, + 'stdout': out, + 'stderr': err, + 'command': ' '.join(command), + 'changed': changed, + 'diff': result_diff, + } + + module.exit_json(**result) if __name__ == '__main__':