diff --git a/snippets/ansible/tasks.yml b/snippets/ansible/tasks.yml index 07f127d..1cf3c7f 100644 --- a/snippets/ansible/tasks.yml +++ b/snippets/ansible/tasks.yml @@ -1,445 +1,5 @@ --- -- name: Data manipulation - tags: - - data_manipulation - - never - hosts: localhost - connection: local - gather_facts: false - tasks: - - name: Return the input data's type - tags: type_return - ansible.builtin.set_fact: - should_return_str: "{{ 'this' | type_debug }}" - - name: Test types - tags: type_test - ansible.builtin.set_fact: - # strings are classified as 'string', 'iterable' and 'sequence', but not 'mapping' - aa_is_string: "{{ 'aa' is string }}" - aa_is_iterable: "{{ 'aa' is iterable }}" - aa_is_sequence: "{{ 'aa' is sequence }}" - # numbers are classified as 'numbers', with 'integer' and 'float' being subclasses - i42_is_number: "{{ 42 is number }}" - i5_is_integer: "{{ 5 is integer }}" - f21_34_is_number: "{{ 21.34 is number }}" - f12_1_is_float: "{{ 12.1 is float }}" - # lists are classified as 'iterable' and 'sequence', but not as 'string' nor 'mapping' - list_is_iterable: "{{ ['list'] is iterable }}" - list_is_sequence: "{{ ['list'] is sequence }}" - list_is_string: "{{ ['list'] is string }}" - list_is_mapping: "{{ ['list'] is mapping }}" - # dictionaries are classified as 'iterable', 'sequence' and 'mapping', but not as 'string' - dict_is_iterable: "{{ {'a': 'dict'} is iterable }}" - dict_is_sequence: "{{ {'a': 'dict'} is sequence }}" - dict_is_mapping: "{{ {'a': 'dict'} is mapping }}" - dict_is_string: "{{ {'a': 'dict'} is string }}" - # native booleans - true_is_boolean: "{{ true is boolean }}" - upper_true_is_boolean: "{{ True is boolean }}" - false_is_boolean: "{{ false is boolean }}" - upper_false_is_boolean: "{{ False is boolean }}" - # null is None in ansible - aa_is_not_null: "{{ 'aa' != None }}" - aa_is_not_null_nor_empty: "{{ 'aa' not in [ None, '' ] }}" - - name: Convert between types - tags: type_conversion - ansible.builtin.set_fact: - string_to_int_is_integer: "{{ 'string' | int is integer }}" - string_to_float_is_float: "{{ 'string' | float is float }}" - integer_to_float_is_float: "{{ 12 | float is float }}" - float_to_int_is_integer: "{{ 21.02 | int is integer }}" - integer_to_string_is_string: "{{ 43 | string is string }}" - float_to_string_is_string: "{{ 74.93 | string is string }}" - integer_to_bool_is_boolean: "{{ 4 | bool is boolean }}" - - name: Test truthfulness - tags: truthfulness - ansible.builtin.set_fact: - this_is_true: true - this_is_false: false - this_is_true_again: "{{ not false }}" - true_is_truthy: "{{ true is truthy }}" - false_is_falsy: "{{ false is falsy }}" - - name: Elvis operator - tags: - - elvis_operator - - ternary - # (condition) | ternary(value_for_true_condition, value_for_false_condition, optional_value_for_null_condition) - ansible.builtin.set_fact: - acme_directory: >- - {{ - this_is_a_test_run - | default(true) - | bool - | ternary( - 'https://acme-staging-v02.api.letsencrypt.org/directory', - 'https://acme-v02.api.letsencrypt.org/directory' - ) - }} - - name: Manipulate strings - tags: string_manipulation - vars: - module_output: >- - u001b]0;@smth:/u0007{ - "failed": 0, "started": 1, "finished": 0, "ansible_job_id": "j968817333249.114504", - "results_file": "/home/ssm-user/.ansible_async/j968817333249.114504", "_ansible_suppress_tmpdir_delete": true - }\r\r - pattern: >- - {{ '"failed": 0, "started": 1, "finished": 0' | regex_escape() }} - ansible.builtin.set_fact: - first_letter_to_uppercase: "{{ 'all_lowercase' | capitalize }}" - something_replaced: "{{ 'dots.to.dashes' | replace('.','-') }}" - split_string: "{{ 'testMe@example.com' | split('@') | first }}" - pattern_replaced: >- - {{ '*.domain.com...' | regex_replace('*' | regex_escape, 'star') | regex_replace('\.+$', '') }} - pattern_is_anywhere_in_module_output: "{{ module_output is search(pattern) }}" - pattern_is_at_the_beginning_of_string: "{{ 'sator arepo tenet opera rotas' is match('sator arepo') }}" - regex_is_anywhere_in_string: "{{ 'sator arepo tenet opera rotas' is regex('\\stenet\\s') }}" - first_substr_matching_regex: "{{ 'sator arepo tenet opera rotas' | regex_search('\\stenet\\s') }}" - password_obfuscated: "{{ 'sensitiveString' | regex_replace('^(.{2}).*(.{2})$', '\\1…\\2') }}" - value_from_json_string_in_module_output: >- - {{ 'ansible_job_id' | extract(module_output | regex_search('{.*}') | from_json) }} - base64_encoded_string: "{{ 'some string' | ansible.builtin.b64encode }}" - base64_decoded_string: "{{ 'c29tZSBzdHJpbmc=' | ansible.builtin.b64decode }}" - - name: Manipulate lists - tags: list_manipulation - block: - - name: Add elements to lists - vars: - programming_languages: - - C - - Python - ansible.builtin.set_fact: - programming_languages: "{{ programming_languages + ['Ruby'] }}" - - name: Remove elements from lists - vars: - dbs_list: ['primary', 'sales'] - ansible.builtin.set_fact: - list_without_items: "{{ dbs_list | difference(['template0','template1','postgres','rdsadmin']) }}" - - name: Get elements - ansible.builtin.set_fact: - random_item: "{{ ['a','b','c'] | random }}" - last_item_array_mode: "{{ ['a','b','c'][-1] }}" - last_item_filter: "{{ ['a','b','c'] | last }}" - first_item_filter: "{{ ['a','b','c'] | first }}" - - name: Sort dict elements in lists by attribute - tags: order_by - vars: - snapshots: - - name: sales - create_time: '2024-06-25T00:52:55.127000+00:00' - - name: test - create_time: '2024-05-17T01:53:12.103220+00:00' - ansible.builtin.set_fact: - snapshot_latest: "{{ snapshots | sort(attribute='create_time') | last }}" - - name: Give back the first not null value - tags: coalesce - vars: - list_with_null_values: - - null - - null - - something - - something else - ansible.builtin.set_fact: - first_non_null_value: "{{ list_with_null_values | select | first }}" - - name: Get values for a specific attribute in a list of dictionaries - ansible.builtin.set_fact: - vpc_security_group_ids: >- - {{ instance_information.vpc_security_groups | map(attribute='vpc_security_group_id') }} - volume_ids: >- - {{ instances_information.instances[0].block_device_mappings | map(attribute='ebs.volume_id') }} - - name: Return only elements with specific attributes matching a filter - ansible.builtin.set_fact: - available_rds_snapshots: "{{ snapshots_list | selectattr('status', 'equalto', 'available') }}" - mounts_with_path: "{{ ansible_facts.mounts | selectattr('mount', 'in', path) }}" - - name: Return all elements *but* the ones with specific attributes matching a filter - ansible.builtin.set_fact: - available_rds_snapshots: "{{ snapshots_list | rejectattr('status', 'equalto', 'creating') }}" - mounts_without_path: "{{ ansible_facts.mounts | rejectattr('mount', 'in', path) }}" - - name: Remove lines about RDS protected users and permissions from a dump file - # remove empty lines - # remove comments - # remove creation of the master user - # remove anything involving 'rdsadmin' - # remove changes to protected RDS users - # remove protected 'superuser' and 'replication' assignments - vars: - # **Hack notice**: Ansible has issues with splitting on new lines if this template is quoted differently - permissions_dump_content_as_lines: "{{ dump_file.content | ansible.builtin.b64decode | split('\n') }}" - master_username: postgresql - ansible.builtin.set_fact: - permissions_commands: >- - {{ - permissions_dump_content_as_lines - | reject('match', '^$') - | reject('match', '^--') - | reject('match', '^CREATE ROLE ' + master_username) - | reject('match', '.*rdsadmin.*') - | reject('match', '^(CREATE|ALTER) ROLE rds_') - | map('regex_replace', '(NO)(SUPERUSER|REPLICATION)\s?', '') - }} - - name: Manipulate dictionaries - tags: dictionary_manipulation - vars: - organization: - address: 123 common lane - id: 123abc - block: - - name: Add keys to dictionaries - ansible.builtin.set_fact: - organization: "{{ organization | combine({ 'name': 'ExampleOrg' }) }}" - - name: Sort keys in dictionaries - ansible.builtin.set_fact: - organization: "{{ organization | dictsort }}" - - name: Pretty print dictionaries - ansible.builtin.set_fact: - organization: "{{ organization | to_nice_json }}" - - name: Merge dictionaries - vars: - dict_1: - a: 43 - b: some string - dict_2: - y: true - z: - - 4 - - test - ansible.builtin.set_fact: - merged_dict: "{{ dict1 | ansible.builtin.combine(dict_2, {'z':'new_value','w':[44]}) }}" - recursively_merged_dict: >- - {{ {'rest':'test'} | ansible.builtin.combine({'z':'newValue','w':[44]}, dict_1, dict_2, recursive=true) }} - - name: Register the list of extensions per DB - vars: - db_extensions: {} - ansible.builtin.set_fact: - db_extensions: >- - {{ - db_extensions - | combine({ - item.item: item.query_result | map(attribute='extname') - }) - }} - with_items: "{{ db_extensions_query.results }}" - - name: Register the list of extensions per DB as 'db:extensions[]' pairs - vars: - db_extensions: - sales: - - pgaudit - - plpgsql - countries: - - pgcrypto - - postgis - - pg_stat_statements - ansible.builtin.set_fact: - db_extension_pairs: - # Refer https://jinja.palletsprojects.com/en/3.0.x/templates/#assignments for the namespace object's - # reason - >- - {%- set ns = namespace(output = []) -%} - {%- for db in db_extensions.keys() -%} - {%- for extension in db_extensions[db] -%} - {{- ns.output.append({'db':db, 'extension': extension}) -}} - {%- endfor -%} - {%- endfor -%} - {{- ns.output -}} - - name: Get the device name and last snapshot id for all block devices in an EC2 instance - # Useful to create AMIs from instance snapshots - tags: - - aws - - ec2 - - snapshot - - ami - ansible.builtin.set_fact: - last_snap_for_device: - # Refer https://jinja.palletsprojects.com/en/3.0.x/templates/#assignments for the namespace object's - # reason - >- - {%- set ns = namespace(devices_list = []) -%} - {%- for result in current_instance_snapshots.results -%} - {%- for device in current_instance_information.instances[0].block_device_mappings - | selectattr('ebs.volume_id', 'equalto', result.volume_id) -%} - {{- - ns.devices_list.append({ - 'device_name': device.device_name, - 'snapshot_id': result.snapshots - | sort(attribute='start_time') | last - | json_query('snapshot_id'), - }) - -}} - {%- endfor -%} - {%- endfor -%} - {{ ns.devices_list }} - -- name: Flow control - tags: - - flow_control - - never - hosts: localhost - connection: local - gather_facts: false - ignore_errors: true - tasks: - - name: Take pauses - tags: - - pause - - sleep - ansible.builtin.pause: - seconds: 1 - - name: Only run in check mode - tags: check_mode_only - when: ansible_check_mode is truthy - ansible.builtin.set_fact: - check_mode_active: ansible_check_mode - - name: Enforce failures - tags: failure - ansible.builtin.fail: - msg: Manually enforced failure - - name: Fail task on any non-compliance - tags: assertion - vars: - installation_method: package - url: https://www.google.com/ - ansible.builtin.assert: - that: - - installation_method in ['container', 'package'] - - "'https://www.google.com/' is ansible.builtin.url" - - "'domain.example.com' is community.general.fqdn_valid(min_labels=2)" - - url is regex('\w\.com/') - fail_msg: What to say if any of the above conditions fail - success_msg: What to say if all of the above conditions succeed - - name: Execute notified handlers now - tags: handler - ansible.builtin.meta: flush_handlers - - name: Do nothing - tags: noop - ansible.builtin.meta: noop - - name: Retry failing tasks - tags: - - failure - - retry - changed_when: false - ansible.builtin.command: /usr/bin/false - retries: 3 - delay: 1 - register: command_result - until: command_result is not failed - - name: Error handling in blocks - tags: error_handling - block: - - name: This executes normally - ansible.builtin.debug: - msg: I execute normally - - name: This errors out - changed_when: false - ansible.builtin.command: /bin/false - - name: This never executes - ansible.builtin.debug: - msg: I never execute due to the above task failing - rescue: - - name: This executes if any errors arose in the block - ansible.builtin.debug: - msg: I caught an error and can do stuff here to fix it - always: - - name: This always executes - ansible.builtin.debug: - msg: I always execute - - name: Long-running tasks - tags: long-running - vars: - ansible_async_dir: /tmp/.ansible/async # defaults to '~/.ansible_async' - block: - - name: Long-running task with integrated poll - tags: async_with_self_poll - when: ansible_check_mode is falsy # check mode and async cannot be used on same task - ansible.builtin.command: /bin/sleep 15 - changed_when: false - async: 45 # run max 45s - poll: 5 # check once every 5s - - name: Long-running task with external poll - tags: async_with_external_poll - block: - - name: Long-running task with external poll - when: ansible_check_mode is falsy # check mode and async cannot be used on same task - ansible.builtin.command: /bin/sleep 15 - changed_when: false - async: 45 # run max 45s - poll: 0 # fire and forget - register: long_running_task_with_external_poll - - name: Check on long_running_task_with_external_poll - when: long_running_task_with_external_poll is not skipped - ansible.builtin.async_status: - jid: "{{ long_running_task_with_external_poll.ansible_job_id }}" - register: job_result - until: job_result.finished - retries: 9 - delay: 5 -- name: Run this play on up to 3 hosts at a time - hosts: large_group_of_hosts - serial: 3 - tasks: [] -- name: Run this play on a batch of 4 hosts, then a batch of 8, then the rest - hosts: large_group_of_hosts - serial: - - 4 - - 8 - - 100% - strategy: linear - tasks: - - name: Limit this task to 3 workers (or up to the current batch if lower) - throttle: 3 - ansible.builtin.set_fact: - greetings: hi from {{ ansible_hostname }} - # - name: Run this task only on one single host - # run_once: true - # ansible.builtin.set_fact: - # confirm: only run on {{ ansible_hostname }} - -- name: Debugging - tags: - - debug - - never - hosts: localhost - connection: local - gather_facts: false - ignore_errors: true - tasks: - - name: Output messages - ansible.builtin.debug: - msg: I always display! - - name: Pretty print messages - vars: - install_method: package - supported_install_methods: ['package'] - ansible.builtin.debug: - msg: >- - {{ - dict([ - [ 'install_method', install_method ], - [ 'install_method in supported_install_methods', install_method in supported_install_methods ], - ]) - }} - - name: Output variables' values - vars: - install_method: package - ansible.builtin.debug: - var: install_method - - name: Print all available variables - ansible.builtin.debug: - var: vars # magic variable - - name: Output messages depending on the verbosity level - ansible.builtin.debug: - msg: I only display with 'ansible-playbook -vvv' or with more 'v's - verbosity: 3 - - name: Print the run's shell environment - ansible.builtin.shell: printenv | sort - - name: Start the debugger on failure - # print all variables at this point => p task_vars - # continue => c - # abort and quit => q - debugger: on_failed - ansible.builtin.fail: - msg: Manually enforced failure - - name: Prompt for vars tags: - prompt @@ -458,24 +18,27 @@ default: whatever tasks: [] -- name: Run tasks on different targets than the current one - # Only *single* target patterns allowed - # No groups - tags: - - never - hosts: localhost - connection: local - gather_facts: false +- name: Run this play on up to 3 hosts at a time + hosts: large_group_of_hosts + serial: 3 + tasks: [] + +- name: Run this play on a batch of 4 hosts, then a batch of 8, then the rest + hosts: large_group_of_hosts + serial: + - 4 + - 8 + - 100% + strategy: linear tasks: - - name: Run on the controller - delegate_to: 127.0.0.1 - connection: local - changed_when: false - ansible.builtin.command: hostname - - name: Run on other targets - delegate_to: copernicus - changed_when: false - ansible.builtin.command: hostname + - name: Limit this task to 3 workers (or up to the current batch if lower) + throttle: 3 + ansible.builtin.set_fact: + greetings: hi from {{ ansible_hostname }} + # - name: Run this task only on one single host + # run_once: true + # ansible.builtin.set_fact: + # confirm: only run on {{ ansible_hostname }} - name: Reuse tasks tags: @@ -506,11 +69,12 @@ # ansible.builtin.import_tasks: # file: "{{ filename }}" -# - name: Reuse playbooks -# # only works at playbook level -# vars: -# var_for_playbook_1: value1 -# ansible.builtin.import_playbook: path/to/playbook.yml +- name: Reuse playbooks + # only works at playbook level + tags: never + vars: + var_for_playbook_1: value1 + ansible.builtin.import_playbook: path/to/playbook.yml # - name: Apply roles # hosts: localhost diff --git a/snippets/ansible/tasks/control flows.yml b/snippets/ansible/tasks/control flows.yml new file mode 100644 index 0000000..82469d1 --- /dev/null +++ b/snippets/ansible/tasks/control flows.yml @@ -0,0 +1,89 @@ +--- + +- name: Do nothing + tags: noop + ansible.builtin.meta: noop + +- name: Execute long-running tasks + tags: long-running + vars: + ansible_async_dir: /tmp/.ansible/async # defaults to '~/.ansible_async' + block: + - name: Long-running task with integrated poll + tags: async_with_self_poll + when: ansible_check_mode is falsy # check mode and async cannot be used on same task + ansible.builtin.command: /bin/sleep 15 + changed_when: false + async: 45 # run max 45s + poll: 5 # check once every 5s + - name: Long-running task with external poll + tags: async_with_external_poll + block: + - name: Long-running task with external poll + when: ansible_check_mode is falsy # check mode and async cannot be used on same task + ansible.builtin.command: /bin/sleep 15 + changed_when: false + async: 45 # run max 45s + poll: 0 # fire and forget + register: long_running_task_with_external_poll + - name: Check on long_running_task_with_external_poll + when: long_running_task_with_external_poll is not skipped + ansible.builtin.async_status: + jid: "{{ long_running_task_with_external_poll.ansible_job_id }}" + register: job_result + until: job_result.finished + retries: 9 + delay: 5 + +- name: Fail task on any non-compliance + tags: assertion + vars: + installation_method: package + url: https://www.google.com/ + ansible.builtin.assert: + that: + - installation_method in ['container', 'package'] + - "'https://www.google.com/' is ansible.builtin.url" + - "'domain.example.com' is community.general.fqdn_valid(min_labels=2)" + - url is regex('\w\.com/') + fail_msg: What to say if any of the above conditions fail + success_msg: What to say if all of the above conditions succeed + +- name: Force execution of *notified* handlers + tags: force_handlers + ansible.builtin.meta: flush_handlers + +- name: Force failures + tags: force_failure + ansible.builtin.fail: + msg: Manually enforced failure + +- name: Only run in check mode + tags: check_mode_only + when: ansible_check_mode is truthy + ansible.builtin.set_fact: + check_mode_active: ansible_check_mode + +- name: Pause + tags: + - pause + - sleep + ansible.builtin.pause: + seconds: 1 + +- name: Ternary A.K.A. Elvis operator + # (condition) | ternary(value_for_true_condition, value_for_false_condition, optional_value_for_null_condition) + tags: + - elvis_operator + - ternary + ansible.builtin.set_fact: + acme_directory: >- + {{ + this_is_a_test_run + | default(true) + | bool + | ternary( + 'https://acme-staging-v02.api.letsencrypt.org/directory', + 'https://acme-v02.api.letsencrypt.org/directory' + ) + }} diff --git a/snippets/ansible/tasks/debug.yml b/snippets/ansible/tasks/debug.yml new file mode 100644 index 0000000..2e5a6a5 --- /dev/null +++ b/snippets/ansible/tasks/debug.yml @@ -0,0 +1,46 @@ +--- + + +- name: Output messages + ansible.builtin.debug: + msg: I always display! + +- name: Pretty print messages + vars: + install_method: package + supported_install_methods: ['package'] + ansible.builtin.debug: + msg: >- + {{ + dict([ + [ 'install_method', install_method ], + [ 'install_method in supported_install_methods', install_method in supported_install_methods ], + ]) + }} + +- name: Output variables' values + vars: + install_method: package + ansible.builtin.debug: + var: install_method + +- name: Print all available variables + ansible.builtin.debug: + var: vars # magic variable + +- name: Output messages depending on the verbosity level + ansible.builtin.debug: + msg: I only display with 'ansible-playbook -vvv' or with more 'v's + verbosity: 3 + +- name: Print the run's shell environment + ansible.builtin.shell: printenv | sort + +- name: Start the debugger on failure + tags: never + # print all variables at this point => p task_vars + # continue => c + # abort and quit => q + debugger: on_failed + ansible.builtin.fail: + msg: Manually enforced failure diff --git a/snippets/ansible/tasks/delegate tasks to other hosts.yml b/snippets/ansible/tasks/delegate tasks to other hosts.yml new file mode 100644 index 0000000..7ca69ee --- /dev/null +++ b/snippets/ansible/tasks/delegate tasks to other hosts.yml @@ -0,0 +1,15 @@ +--- + +# Only *single* target patterns allowed +# No groups + +- name: Forcefully run on the controller + delegate_to: 127.0.0.1 + connection: local + changed_when: false + ansible.builtin.command: hostname + +- name: Forcefully run on a target from the inventory + delegate_to: copernicus + changed_when: false + ansible.builtin.command: hostname diff --git a/snippets/ansible/tasks/handle errors.yml b/snippets/ansible/tasks/handle errors.yml new file mode 100644 index 0000000..6e96572 --- /dev/null +++ b/snippets/ansible/tasks/handle errors.yml @@ -0,0 +1,36 @@ +--- + + +- name: Recover from errors + tags: + - error_handling + - recover_from_errors + block: + - name: This executes normally + ansible.builtin.debug: + msg: I execute normally + - name: This errors out + changed_when: false + ansible.builtin.command: /bin/false + - name: This never executes + ansible.builtin.debug: + msg: I never execute due to the above task failing + rescue: + - name: This executes if any errors arose in the block + ansible.builtin.debug: + msg: I caught an error and can do stuff here to fix it + always: + - name: This always executes + ansible.builtin.debug: + msg: I always execute + +- name: Retry tasks on failure + tags: + - error_handling + - retry_on_failure + changed_when: false + ansible.builtin.command: /usr/bin/false + retries: 3 + delay: 1 + register: command_result + until: command_result is not failed diff --git a/snippets/ansible/tasks/manipulate data.yml b/snippets/ansible/tasks/manipulate data.yml new file mode 100644 index 0000000..a5c1865 --- /dev/null +++ b/snippets/ansible/tasks/manipulate data.yml @@ -0,0 +1,310 @@ +--- + +- name: Convert between types + tags: + - convert_types + - type_conversion + ansible.builtin.set_fact: + string_to_int_is_integer: "{{ 'string' | int is integer }}" + string_to_float_is_float: "{{ 'string' | float is float }}" + integer_to_float_is_float: "{{ 12 | float is float }}" + float_to_int_is_integer: "{{ 21.02 | int is integer }}" + integer_to_string_is_string: "{{ 43 | string is string }}" + float_to_string_is_string: "{{ 74.93 | string is string }}" + integer_to_bool_is_boolean: "{{ 4 | bool is boolean }}" + +- name: Manipulate dictionaries + tags: + - dictionary_manipulation + - manipulate_dictionaries + vars: + organization: + address: 123 common lane + id: 123abc + block: + - name: Add keys to dictionaries + ansible.builtin.set_fact: + organization: "{{ organization | combine({ 'name': 'ExampleOrg' }) }}" + - name: Sort keys in dictionaries + ansible.builtin.set_fact: + organization: "{{ organization | dictsort }}" + - name: Pretty print dictionaries + ansible.builtin.set_fact: + organization: "{{ organization | to_nice_json }}" + - name: Merge dictionaries + vars: + dict_1: + a: 43 + b: some string + dict_2: + y: true + z: + - 4 + - test + ansible.builtin.set_fact: + merged_dict: "{{ dict_1 | ansible.builtin.combine(dict_2, {'z':'new_value','w':[44]}) }}" + recursively_merged_dict: >- + {{ {'rest':'test'} | ansible.builtin.combine({'z':'newValue','w':[44]}, dict_1, dict_2, recursive=true) }} + - name: Register the list of extensions per DB + tags: never + ansible.builtin.set_fact: + db_extensions: >- + {{ + db_extensions + | combine({ + item.item: item.query_result | map(attribute='extname') + }) + }} + with_items: "{{ db_extensions_query.results }}" + - name: Register the list of extensions per DB as 'db:extensions[]' pairs + vars: + db_extensions: + sales: + - pgaudit + - plpgsql + countries: + - pgcrypto + - postgis + - pg_stat_statements + ansible.builtin.set_fact: + db_extension_pairs: + # Refer https://jinja.palletsprojects.com/en/3.0.x/templates/#assignments for the namespace object's + # reason + >- + {%- set ns = namespace(output = []) -%} + {%- for db in db_extensions.keys() -%} + {%- for extension in db_extensions[db] -%} + {{- ns.output.append({'db':db, 'extension': extension}) -}} + {%- endfor -%} + {%- endfor -%} + {{- ns.output -}} + - name: Get the device name and last snapshot id for all block devices in an EC2 instance + # Useful to create AMIs from instance snapshots + tags: never + ansible.builtin.set_fact: + last_snap_for_device: + # Refer https://jinja.palletsprojects.com/en/3.0.x/templates/#assignments for the namespace object's + # reason + >- + {%- set ns = namespace(devices_list = []) -%} + {%- for result in current_instance_snapshots.results -%} + {%- for device in current_instance_information.instances[0].block_device_mappings + | selectattr('ebs.volume_id', 'equalto', result.volume_id) -%} + {{- + ns.devices_list.append({ + 'device_name': device.device_name, + 'snapshot_id': result.snapshots + | sort(attribute='start_time') | last + | json_query('snapshot_id'), + }) + -}} + {%- endfor -%} + {%- endfor -%} + {{ ns.devices_list }} + +- name: Manipulate lists + tags: + - list_manipulation + - manipulate_lists + vars: + mounts: + - block_available: 237681860 + block_size: 4096 + block_total: 239859712 + block_used: 2177852 + device: /dev/mapper/vg0-root + fstype: btrfs + inode_available: 0 + inode_total: 0 + inode_used: 0 + mount: / + options: rw,relatime,compress-force=zstd:15,ssd,space_cache,autodefrag,subvolid=256,subvol=/os,bind + size_available: 973544898560 + size_total: 982465380352 + uuid: 6fc42685-8f3f-43a3-b698-a135b2b59ac5 + - block_available: 110948 + block_size: 4096 + block_total: 130812 + block_used: 19864 + device: /dev/sda1 + fstype: vfat + inode_available: 0 + inode_total: 0 + inode_used: 0 + mount: /boot + options: rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro + size_available: 454443008 + size_total: 535805952 + uuid: 8ACA-ADB8 + snapshots_list: + - name: some_available_snapshot + status: available + - name: some_unavailable_snapshot + status: creating + block: + - name: Add elements to lists + vars: + programming_languages: + - C + - Python + ansible.builtin.set_fact: + programming_languages: "{{ programming_languages + ['Ruby'] }}" + - name: Remove elements from lists + vars: + dbs_list: ['primary', 'sales'] + ansible.builtin.set_fact: + list_without_items: "{{ dbs_list | difference(['template0','template1','postgres','rdsadmin']) }}" + - name: Get elements + ansible.builtin.set_fact: + random_item: "{{ ['a','b','c'] | random }}" + last_item_array_mode: "{{ ['a','b','c'][-1] }}" + last_item_filter: "{{ ['a','b','c'] | last }}" + first_item_filter: "{{ ['a','b','c'] | first }}" + - name: Sort dict elements in lists by attribute + tags: order_by + vars: + snapshots: + - name: sales + create_time: '2024-06-25T00:52:55.127000+00:00' + - name: test + create_time: '2024-05-17T01:53:12.103220+00:00' + ansible.builtin.set_fact: + snapshot_latest: "{{ snapshots | sort(attribute='create_time') | last }}" + - name: Give back the first not null value + tags: coalesce + vars: + list_with_null_values: + - null + - null + - something + - something else + ansible.builtin.set_fact: + first_non_null_value: "{{ list_with_null_values | select | first }}" + - name: Get values for a specific attribute in a list of dictionaries + vars: + instances_information: + instances: + - block_device_mappings: + - ebs: + volume_id: vol-0123456 + vpc_security_groups: + - vpc_security_group_id: sg-0123456789abcdef + instance_information: "{{ instances_information.instances[0] }}" + ansible.builtin.set_fact: + vpc_security_group_ids: >- + {{ instance_information.vpc_security_groups | map(attribute='vpc_security_group_id') }} + volume_ids: >- + {{ instances_information.instances[0].block_device_mappings | map(attribute='ebs.volume_id') }} + - name: Return only elements with specific attributes matching a filter + ansible.builtin.set_fact: + available_rds_snapshots: "{{ snapshots_list | selectattr('status', 'equalto', 'available') }}" + mounts_with_path: "{{ mounts | selectattr('mount', 'in', ['/boot']) }}" + - name: Return all elements *but* the ones with specific attributes matching a filter + ansible.builtin.set_fact: + available_rds_snapshots: "{{ snapshots_list | rejectattr('status', 'equalto', 'creating') }}" + mounts_without_path: "{{ mounts | rejectattr('mount', 'in', ['/boot']) }}" + - name: Remove lines about RDS protected users and permissions from a dump file + tags: never + # remove empty lines + # remove comments + # remove creation of the master user + # remove anything involving 'rdsadmin' + # remove changes to protected RDS users + # remove protected 'superuser' and 'replication' assignments + vars: + # **Hack notice**: Ansible has issues with splitting on new lines if this template is quoted differently + permissions_dump_content_as_lines: "{{ dump_file.content | ansible.builtin.b64decode | split('\n') }}" + master_username: postgresql + ansible.builtin.set_fact: + permissions_commands: >- + {{ + permissions_dump_content_as_lines + | reject('match', '^$') + | reject('match', '^--') + | reject('match', '^CREATE ROLE ' + master_username) + | reject('match', '.*rdsadmin.*') + | reject('match', '^(CREATE|ALTER) ROLE rds_') + | map('regex_replace', '(NO)(SUPERUSER|REPLICATION)\s?', '') + }} + +- name: Manipulate strings + tags: + - manipulate_strings + - string_manipulation + vars: + module_output: >- + u001b]0;@smth:/u0007{ + "failed": 0, "started": 1, "finished": 0, "ansible_job_id": "j968817333249.114504", + "results_file": "/home/ssm-user/.ansible_async/j968817333249.114504", "_ansible_suppress_tmpdir_delete": true + }\r\r + pattern: >- + {{ '"failed": 0, "started": 1, "finished": 0' | regex_escape() }} + ansible.builtin.set_fact: + first_letter_to_uppercase: "{{ 'all_lowercase' | capitalize }}" + something_replaced: "{{ 'dots.to.dashes' | replace('.','-') }}" + split_string: "{{ 'testMe@example.com' | split('@') | first }}" + pattern_replaced: >- + {{ '*.domain.com...' | regex_replace('*' | regex_escape, 'star') | regex_replace('\.+$', '') }} + pattern_is_anywhere_in_module_output: "{{ module_output is search(pattern) }}" + pattern_is_at_the_beginning_of_string: "{{ 'sator arepo tenet opera rotas' is match('sator arepo') }}" + regex_is_anywhere_in_string: "{{ 'sator arepo tenet opera rotas' is regex('\\stenet\\s') }}" + first_substr_matching_regex: "{{ 'sator arepo tenet opera rotas' | regex_search('\\stenet\\s') }}" + password_obfuscated: "{{ 'sensitiveString' | regex_replace('^(.{2}).*(.{2})$', '\\1…\\2') }}" + value_from_json_string_in_module_output: >- + {{ 'ansible_job_id' | extract(module_output | regex_search('{.*}') | from_json) }} + base64_encoded_string: "{{ 'some string' | ansible.builtin.b64encode }}" + base64_decoded_string: "{{ 'c29tZSBzdHJpbmc=' | ansible.builtin.b64decode }}" + +- name: Return data types + tags: + - return_type + - type_return + ansible.builtin.set_fact: + returns_AnsibleUndefined: "{{ null | type_debug }}" + returns_int: "{{ 3 | type_debug }}" + returns_NoneType: "{{ None | type_debug }}" + returns_str: "{{ 'this' | type_debug }}" + +- name: Test data types + tags: + - test_types + - type_test + ansible.builtin.set_fact: + # strings are classified as 'string', 'iterable' and 'sequence', but not 'mapping' + aa_is_string: "{{ 'aa' is string }}" + aa_is_iterable: "{{ 'aa' is iterable }}" + aa_is_sequence: "{{ 'aa' is sequence }}" + # numbers are classified as 'numbers', with 'integer' and 'float' being subclasses + i42_is_number: "{{ 42 is number }}" + i5_is_integer: "{{ 5 is integer }}" + f21_34_is_number: "{{ 21.34 is number }}" + f12_1_is_float: "{{ 12.1 is float }}" + # lists are classified as 'iterable' and 'sequence', but not as 'string' nor 'mapping' + list_is_iterable: "{{ ['list'] is iterable }}" + list_is_sequence: "{{ ['list'] is sequence }}" + list_is_string: "{{ ['list'] is string }}" + list_is_mapping: "{{ ['list'] is mapping }}" + # dictionaries are classified as 'iterable', 'sequence' and 'mapping', but not as 'string' + dict_is_iterable: "{{ {'a': 'dict'} is iterable }}" + dict_is_sequence: "{{ {'a': 'dict'} is sequence }}" + dict_is_mapping: "{{ {'a': 'dict'} is mapping }}" + dict_is_string: "{{ {'a': 'dict'} is string }}" + # native booleans + true_is_boolean: "{{ true is boolean }}" + upper_true_is_boolean: "{{ True is boolean }}" + false_is_boolean: "{{ false is boolean }}" + upper_false_is_boolean: "{{ False is boolean }}" + # null is not really the same as None in ansible + aa_is_not_null: "{{ 'aa' != None }}" + aa_is_not_null_nor_empty: "{{ 'aa' not in [ None, '' ] }}" + +- name: Test truthfulness + tags: + - test_truthfulness + - truthfulness + ansible.builtin.set_fact: + this_is_true: true + this_is_false: false + this_is_true_again: "{{ not false }}" + true_is_truthy: "{{ true is truthy }}" + false_is_falsy: "{{ false is falsy }}" diff --git a/snippets/ansible/tasks/use tasks from other files.yml b/snippets/ansible/tasks/use tasks from other files.yml new file mode 100644 index 0000000..3635d53 --- /dev/null +++ b/snippets/ansible/tasks/use tasks from other files.yml @@ -0,0 +1,18 @@ +--- + +# +# + +- name: Use tasks from other files + hosts: localhost + connection: local + gather_facts: false + tasks: + - ansible.builtin.import_tasks: debug.yml + - ansible.builtin.import_tasks: + file: control flows.yml + # - ansible.builtin.include_tasks: manipulate data.yml + - ansible.builtin.include_tasks: + file: manipulate data.yml + apply: + tags: data_manipulation