-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add a feature to lookup a file, and get it. #10917
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
base: main
Are you sure you want to change the base?
Changes from all commits
5441e64
f6307b4
a0f7184
5db79a1
7939349
194ac69
e9e5284
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
minor_changes: | ||
- bitwarden lookup plugin - add availbility to get attachment file from bitwarden (https://github.com/ansible-collections/community.general/pull/10917). | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,8 +31,17 @@ | |
default: name | ||
version_added: 5.7.0 | ||
field: | ||
description: Field to fetch. Leave unset to fetch whole response. | ||
description: | ||
- Field to fetch. Leave unset to fetch whole response. | ||
- Mutually exclusive with O(attachment). | ||
type: str | ||
attachment: | ||
description: | ||
- Name of the attachment to download from the item. | ||
- When set, the plugin will download the attachment content in raw format. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does "raw format" mean? Do you mean "without further processing"? What if the file is binary data (Ansible doesn't like to handle binary data, one usually encodes it as Base64 because of that)? |
||
- Mutually exclusive with O(field). | ||
type: str | ||
version_added: 12.0.0 | ||
collection_id: | ||
description: | ||
- Collection ID to filter results by collection. Leave unset to skip filtering. | ||
|
@@ -72,6 +81,25 @@ | |
msg: >- | ||
{{ lookup('community.general.bitwarden', 'bafba515-af11-47e6-abe3-af1200cd18b2', search='id', field='password') | first }} | ||
|
||
- name: "Get attachment 'vpn-server.key' from Bitwarden record named 'VPN Config'" | ||
ansible.builtin.debug: | ||
msg: >- | ||
{{ lookup('community.general.bitwarden', 'VPN Config', attachment='vpn-server.key') }} | ||
|
||
- name: "Save attachment to file" | ||
ansible.builtin.copy: | ||
content: "{{ lookup('community.general.bitwarden', 'VPN Config', attachment='vpn-server.key') | first }}" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While this isn't the first time |
||
dest: /etc/vpn/server.key | ||
mode: '0600' | ||
Comment on lines
+91
to
+93
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not necessarily a problem, but I can't help remembering that the lookup plugins run on the controller, whilst the That begin said, when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A comment was putted just now. Don't sure that store file from bitwarden is the better idea; but works for now, with couple of stored files. For network traffic, from Ansible computer to target, any copy will generate network traffic. In this case we'll add from bitwarden/vaultwarden to ansible computer. |
||
# Be aware, as the lookup run into the Ansible computer, it can generate important network traffic. | ||
# Once from bitwarden/vaultwarden to the Ansible computer; | ||
# Twice (as for locally stored files) from Ansible computer to the Ansible target. | ||
|
||
- name: "Get attachment from item by ID" | ||
ansible.builtin.debug: | ||
msg: >- | ||
{{ lookup('community.general.bitwarden', 'bafba515-af11-47e6-abe3-af1200cd18b2', search='id', attachment='cert.pem') | first }} | ||
|
||
- name: "Get 'password' from all Bitwarden records named 'a_test' from collection" | ||
ansible.builtin.debug: | ||
msg: >- | ||
|
@@ -114,6 +142,7 @@ | |
- A one-element list that contains a list of requested fields or JSON objects of matches. | ||
- If you use C(query), you get a list of lists. If you use C(lookup) without C(wantlist=true), this always gets reduced | ||
to a list of field values or JSON objects. | ||
- When O(attachment) is specified, returns the raw content of the attachment(s). | ||
type: list | ||
elements: list | ||
""" | ||
|
@@ -232,6 +261,33 @@ def get_field(self, field, search_value, search_field="name", collection_id=None | |
|
||
return field_matches | ||
|
||
def get_attachment(self, attachment_name, search_value, search_field="name", collection_id=None, organization_id=None): | ||
"""Download attachment from records whose search_field match search_value. | ||
Returns a list of attachment contents (as raw bytes converted to text) for each matching item. | ||
""" | ||
matches = self._get_matches(search_value, search_field, collection_id, organization_id) | ||
|
||
if not matches: | ||
raise AnsibleError(f"No item found matching {search_field}={search_value}") | ||
|
||
attachment_contents = [] | ||
for match in matches: | ||
item_id = match.get('id') | ||
if not item_id: | ||
raise AnsibleError(f"Item {match.get('name', 'unknown')} has no ID") | ||
|
||
try: | ||
params = ['get', 'attachment', attachment_name, '--itemid', item_id, '--raw'] | ||
out, err = self._run(params) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
(Can Bitwarden actually store binary files, or must all files be UTF-8 encoded text?) |
||
attachment_contents.append(out) | ||
except BitwardenException as e: | ||
# Provide more context about which item failed | ||
item_name = match.get('name', item_id) | ||
raise AnsibleError( | ||
f"Failed to get attachment '{attachment_name}' from item '{item_name}' (ID: {item_id}): {str(e)}" | ||
) | ||
return attachment_contents | ||
|
||
def get_collection_ids(self, collection_name: str, organization_id=None) -> list[str]: | ||
"""Return matching IDs of collections whose name is equal to collection_name.""" | ||
|
||
|
@@ -256,6 +312,7 @@ class LookupModule(LookupBase): | |
def run(self, terms=None, variables=None, **kwargs): | ||
self.set_options(var_options=variables, direct=kwargs) | ||
field = self.get_option('field') | ||
attachment = self.get_option('attachment') | ||
search_field = self.get_option('search') | ||
collection_id = self.get_option('collection_id') | ||
collection_name = self.get_option('collection_name') | ||
|
@@ -266,6 +323,10 @@ def run(self, terms=None, variables=None, **kwargs): | |
if not _bitwarden.unlocked: | ||
raise AnsibleError("Bitwarden Vault locked. Run 'bw unlock'.") | ||
|
||
# Validate mutually exclusive options | ||
if field and attachment: | ||
raise AnsibleOptionsError("'field' and 'attachment' are mutually exclusive!") | ||
|
||
if not terms: | ||
terms = [None] | ||
|
||
|
@@ -278,11 +339,19 @@ def run(self, terms=None, variables=None, **kwargs): | |
else: | ||
collection_ids = [collection_id] | ||
|
||
results = [ | ||
_bitwarden.get_field(field, term, search_field, collection_id, organization_id) | ||
for collection_id in collection_ids | ||
for term in terms | ||
] | ||
# Choose the appropriate method based on what's requested | ||
if attachment: | ||
results = [ | ||
_bitwarden.get_attachment(attachment, term, search_field, collection_id, organization_id) | ||
for collection_id in collection_ids | ||
for term in terms | ||
] | ||
else: | ||
results = [ | ||
_bitwarden.get_field(field, term, search_field, collection_id, organization_id) | ||
for collection_id in collection_ids | ||
for term in terms | ||
] | ||
|
||
for result in results: | ||
if result_count is not None and len(result) != result_count: | ||
|
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.
Do you mean "ability" or "availability" (an "a" is missing for that)? I think the former makes more sense:
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.
It is a bit odd as "availability", indeed.