generated from ansible-collections/collection_template
-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New module plan_stash #113
Merged
abikouo
merged 8 commits into
ansible-collections:main
from
abikouo:plan_stash_20240201
Feb 23, 2024
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
f5857bd
Using an action plugin
abikouo fea7b57
add integration tests for the plan_stash module
abikouo 8c7d701
Update default plan stash variable to terraform_plan
abikouo b7f155e
Update action module with ansible arg specs validation using validate…
abikouo 303a595
add note to suggest use of no_log: true
abikouo a2d53e2
Add new feature allowing to load the terraform file
abikouo 2689553
Fix sanity tests
abikouo 767aeee
minor refactoring
abikouo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
# Copyright: Contributors to the Ansible project | ||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
|
||
|
||
from ansible.plugins.action import ActionBase | ||
from ansible.utils.vars import isidentifier | ||
from ansible_collections.cloud.terraform.plugins.module_utils.plan_stash_args import PLAN_STASH_ARG_SPEC | ||
|
||
|
||
class ActionModule(ActionBase): # type: ignore # mypy ignore | ||
def run(self, tmp=None, task_vars=None): # type: ignore # mypy ignore | ||
if task_vars is None: | ||
task_vars = dict() | ||
|
||
result = super(ActionModule, self).run(tmp, task_vars) | ||
del tmp # tmp no longer has any effect | ||
|
||
validation_result, new_module_args = self.validate_argument_spec(PLAN_STASH_ARG_SPEC) | ||
|
||
# Validate that 'var_name' is a valid variable name | ||
var_name = new_module_args.get("var_name") | ||
binary_data = new_module_args.get("binary_data") | ||
if var_name: | ||
if not isidentifier(var_name): | ||
result["failed"] = True | ||
result["msg"] = ( | ||
"The variable name '%s' is not valid. Variables must start with a letter or underscore character, and contain only " | ||
"letters, numbers and underscores." % var_name | ||
) | ||
return result | ||
|
||
state = new_module_args.get("state") | ||
if state == "load": | ||
if var_name is not None and binary_data is not None: | ||
result["failed"] = True | ||
result["msg"] = "You cannot specify both 'var_name' and 'binary_data' to load the terraform plan file." | ||
return result | ||
|
||
if binary_data is None: | ||
var_name = new_module_args.get("var_name") or "terraform_plan" | ||
try: | ||
value = task_vars[var_name] | ||
except KeyError: | ||
try: | ||
value = task_vars["hostvars"][task_vars["inventory_hostname"]][var_name] | ||
except KeyError: | ||
result["failed"] = True | ||
result["msg"] = "No variable found with this name: %s" % var_name | ||
return result | ||
|
||
new_module_args.pop("var_name") | ||
new_module_args["binary_data"] = value | ||
elif state == "stash": | ||
var_name = new_module_args.get("var_name") or "terraform_plan" | ||
new_module_args.update({"var_name": var_name}) | ||
|
||
# Execute the plan_stash module. | ||
module_return = self._execute_module( | ||
module_name=self._task.action, | ||
module_args=new_module_args, | ||
task_vars=task_vars, | ||
) | ||
|
||
result.update(module_return) | ||
return result |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
PLAN_STASH_ARG_SPEC = { | ||
"path": {"required": True, "type": "path"}, | ||
"var_name": {}, | ||
"per_host": {"type": "bool", "default": False}, | ||
"state": {"choices": ["stash", "load"], "default": "stash"}, | ||
"binary_data": {"type": "raw"}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
#!/usr/bin/python | ||
# -*- coding: utf-8 -*- | ||
# Copyright (c) 2024, Aubin Bikouo <[email protected]> | ||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
DOCUMENTATION = r""" | ||
--- | ||
module: plan_stash | ||
version_added: 2.1.0 | ||
short_description: Handle the base64 encoding or decoding of a terraform plan file | ||
description: | ||
abikouo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
- This module performs base64-encoding of a terraform plan file and saves it into playbook execution stats similar | ||
to M(ansible.builtin.set_stats) module. | ||
- The module also performs base64-decoding of a terraform plan file from a variable defined into ansible facts and writes them | ||
into a file specified by the user. | ||
author: | ||
- "Aubin Bikouo (@abikouo)" | ||
options: | ||
state: | ||
description: | ||
- "O(state=stash): base64-encodes the terraform plan file and saves it into ansible stats like using the M(ansible.builtin.set_stats) module." | ||
- "O(state=load): base64-decodes data from variable specified in O(var_name) and writes them into terraform plan file." | ||
choices: [stash, load] | ||
default: stash | ||
type: str | ||
path: | ||
description: | ||
- The path to the terraform plan file. | ||
type: path | ||
required: true | ||
var_name: | ||
description: | ||
- When O(state=stash), this parameter defines the variable name to be set into stats. | ||
- When O(state=load), this parameter defines the variable from ansible facts containing | ||
the base64-encoded data of the terraform plan file. | ||
- Variables must start with a letter or underscore character, and contain only letters, | ||
numbers and underscores. | ||
- The module will use V(terraform_plan) as default variable name if not specified. | ||
type: str | ||
binary_data: | ||
description: | ||
- When O(state=load), this parameter defines the base64-encoded data of the terraform plan file. | ||
- Mutually exclusive with V(var_name). | ||
- Ignored when O(state=stash). | ||
type: raw | ||
per_host: | ||
description: | ||
- Whether the stats are per host or for all hosts in the run. | ||
- Ignored when O(state=load). | ||
type: bool | ||
default: false | ||
notes: | ||
- For security reasons, this module should be used with I(no_log=true) and I(register) functionalities | ||
as the plan file can contain unencrypted secrets. | ||
""" | ||
|
||
EXAMPLES = r""" | ||
abikouo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# Encode terraform plan file into default variable 'terraform_plan' | ||
- name: Encode a terraform plan file into terraform_plan variable | ||
cloud.terraform.plan_stash: | ||
path: /path/to/terraform_plan_file | ||
state: stash | ||
no_log: true | ||
|
||
# Encode terraform plan file into variable 'stashed_plan' | ||
- name: Encode a terraform plan file into terraform_plan variable | ||
cloud.terraform.plan_stash: | ||
path: /path/to/terraform_plan_file | ||
var_name: stashed_plan | ||
state: stash | ||
no_log: true | ||
|
||
# Load terraform plan file from variable 'stashed_plan' | ||
- name: Load a terraform plan file data from variable 'stashed_plan' into file 'tfplan' | ||
cloud.terraform.plan_stash: | ||
path: tfplan | ||
var_name: stashed_plan | ||
state: load | ||
no_log: true | ||
|
||
# Load terraform plan file from binary data | ||
- name: Load a terraform plan file data from binary data | ||
cloud.terraform.plan_stash: | ||
path: tfplan | ||
binary_data: "{{ terraform_binary_data }}" | ||
state: load | ||
no_log: true | ||
""" | ||
|
||
RETURN = r""" | ||
""" | ||
|
||
import base64 | ||
|
||
from ansible.module_utils.basic import AnsibleModule | ||
from ansible_collections.cloud.terraform.plugins.module_utils.plan_stash_args import PLAN_STASH_ARG_SPEC | ||
|
||
|
||
def read_file_content(file_path: str, module: AnsibleModule, failed_on_error: bool = True) -> bytes: | ||
data = b"" | ||
try: | ||
with open(file_path, "rb") as f: | ||
data = f.read() | ||
except FileNotFoundError: | ||
if failed_on_error: | ||
module.fail_json(msg="The following file '{0}' does not exist.".format(file_path)) | ||
return data | ||
|
||
|
||
def main() -> None: | ||
module = AnsibleModule( | ||
argument_spec=PLAN_STASH_ARG_SPEC, | ||
supports_check_mode=True, | ||
) | ||
|
||
terrafom_plan_file = module.params.get("path") | ||
var_name = module.params.get("var_name") | ||
per_host = module.params.get("per_host") | ||
state = module.params.get("state") | ||
|
||
result = {} | ||
if state == "stash": | ||
# Stash: base64-encode the terraform plan file and set stats | ||
data = read_file_content(terrafom_plan_file, module) | ||
# encode binary data | ||
try: | ||
encoded_data = base64.b64encode(data) | ||
except Exception as e: | ||
module.fail_json(msg="Cannot encode data from file {0} due to: {1}".format(terrafom_plan_file, e)) | ||
|
||
stats = {"data": {var_name: encoded_data}, "per_host": per_host} | ||
result = {"ansible_stats": stats, "changed": False} | ||
else: | ||
# Load: Decodes the data from the variable name and write into terraform plan file | ||
binary_data = module.params.get("binary_data") | ||
try: | ||
data = base64.b64decode(binary_data) | ||
except Exception as e: | ||
module.fail_json(msg="Failed to decode binary data due to: {0}".format(e)) | ||
|
||
current_content = read_file_content(terrafom_plan_file, module, failed_on_error=False) | ||
changed = False | ||
if current_content != data: | ||
changed = True | ||
if not module.check_mode: | ||
try: | ||
with open(terrafom_plan_file, "wb") as f: | ||
f.write(data) | ||
result.update({"msg": "data successfully decoded into file %s" % terrafom_plan_file}) | ||
except Exception as e: | ||
module.fail_json(msg="Failed to write data into file due to: {0}".format(e)) | ||
|
||
result.update({"changed": changed}) | ||
|
||
module.exit_json(**result) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
terraform { | ||
required_providers { | ||
random = { | ||
source = "hashicorp/random" | ||
version = "3.6.0" | ||
} | ||
} | ||
} | ||
|
||
resource "random_string" "random" { | ||
length = 16 | ||
special = true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
- name: Validate plan_stash module | ||
ansible.builtin.include_tasks: 'tasks/{{ item }}.yml' | ||
with_items: | ||
- validate_args | ||
- run |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using this, I'm realizing we may want to make this module work both for stashing and for loading the plan file. The only way to safely write the b64 encoded data to a zip file would be for the user to have a shell task that calls out to base64, which is kind of ugly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gravesm it makes sense to support the load of the plan file, but currently, a shell task is not the only way to do that, we can do it using
ansible.builtin.copy
moduleThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's unclear to me that this is always going to work, though. The documentation for the b64decode filter pretty clearly says this is likely to corrupt the binary data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok I am going to update the module to work like this
Stash the terraform plan file
Load the terraform plan file
WDYT ? @gravesm @hakbailey
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this is along the lines of what I'm thinking. My only suggestion would be to use
state
instead ofmode
.