--- - name: Retry tasks ansible.builtin.command: /usr/bin/false retries: 3 delay: 3 register: command_result until: command_result is not failed - name: Create directories recursively ansible.builtin.file: path: /tmp/path/to/final/dir state: directory - name: Write files from tasks ansible.builtin.copy: dest: "{{ ansible_user_dir }}/.tmux.conf" mode: u=rw,go=r content: | … - name: Show input data type set_fact: should_be_string: "{{ 'this' | type_debug }}" - name: Run locally delegate_to: 127.0.0.1 # 'localhost' works too command: hostname - name: Import tasks block: - name: By using absolute paths and special variables (preferred) ansible.builtin.import_tasks: file: "{{ role_path }}/tasks/install/{{ install_method }}.yml" - name: By using paths relative to the including file ansible.builtin.import_tasks: file: pre-flight.yml - name: Conditionally include tasks block: - name: by leveraging the 'with_fileglob' loop filter (preferred) ansible.builtin.include_tasks: file: "{{ item }}" with_fileglob: "{{ install_method }}.yml" - name: by checking the files' existence vars: filename: "{{ install_method }}.yml" when: lookup('ansible.builtin.fileglob', filename) != [] ansible.builtin.import_tasks: file: "{{ filename }}" - name: Assertions ansible.builtin.assert: that: - install_method in supported_install_methods - external_url is ansible.builtin.url 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: Pretty print information ansible.builtin.debug: msg: >- {{ dict([ [ 'install_method', install_method ], [ 'install_method in supported_install_methods', install_method in supported_install_methods ], ]) }} - name: Generate passwords block: - name: Randomly ansible.builtin.debug: msg: "{{ lookup('ansible.builtin.password', '/dev/null') }}" - name: Specifying requirements ansible.builtin.debug: msg: "{{ lookup('ansible.builtin.password', '/dev/null length=32 chars=ascii_letters,digits,punctuation') }}" - name: Random but idempotent, so it will not change at every execution ansible.builtin.debug: msg: "{{ lookup('ansible.builtin.password', '/dev/null', seed=inventory_hostname) }}" - name: Add repositories block: - name: DNF/YUM ansible.builtin.yum_repository: name: epel description: EPEL YUM repo baseurl: https://download.fedoraproject.org/pub/epel/$releasever/$basearch/ - name: Install packages block: - name: Generic module ansible.builtin.package: name: - tmux - screen - name: Via PIP ansible.builtin.pip: name: - bottle - django>1.11.0,<1.12.0 - svn+http://myrepo/svn/MyApp#egg=MyApp - git+http://myrepo/app/MyApp - file:///path/to/MyApp.tar.gz - name: Run containers block: - name: Directly community.docker.docker_container: name: gitlab image: gitlab/gitlab-ce:16.11.2-ce.0 hostname: gitlab.lan published_ports: - "8022:22" - "8080:80" - "8443:443" env: GITLAB_OMNIBUS_CONFIG: >- external_url 'http://gitlab.lan'; shm_size: 256m volumes: - ./config:/etc/gitlab:Z - ./logs:/var/log/gitlab:Z - ./data:/var/opt/gitlab:Z auto_remove: true - name: With Compose community.docker.docker_compose_v2: project_src: /home/user/flask - name: Manipulate strings ansible.builtin.set_fact: string_with_first_letter_to_uppercase: "{{ 'all_lowercase' | capitalize }}" - name: Manipulate lists block: - name: Add elements to lists set_fact: programming_languages: "{{ programming_languages + ['Ruby'] }}" - name: Remove elements from lists set_fact: list_without_items: "{{ dbs_list | difference(['template0','template1','postgres','rdsadmin']) }}" - name: Get a random element set_fact: random_item: "{{ ['a','b','c'] | random }}" - name: Sort dict elements in list 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' set_fact: snapshot_latest: "{{ snapshots | sort(attribute='create_time') | last }}" - name: Give back the first not null value (coalesce-like) vars: list_with_null_values: - null - null - something - something else set_fact: first_non_null_value: "{{ list_with_null_values | select | first }}" - name: Get values for a specific attribute in a list of dictionaries 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 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 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 block: - name: Add keys to dictionaries set_fact: organization: "{{ organization | combine({ 'name': 'ExampleOrg' }) }}" - name: Sort keys in dictionaries set_fact: organization: "{{ organization | dictsort }}" - name: Pretty print dictionaries 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 set_fact: merged_dict: "{{ dict1 | ansible.builtin.combine(dict_2, {'z':'new_value','w':[44]}) }}" recursively_merged_dict: >- {{ {'rest':'test'} | ansible.builtin.combine({'z':'new_value','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: FIXME vars: db_extensions: sales: - pgaudit - plpgsql countries: - pgcrypto - postgis - pg_stat_statements ansible.builtin.set_fact: db_extension_pairs: >- {%- set output = [] -%} {%- for db in db_extensions.keys() -%} {%- for extension in db_extensions[db] -%} {{- output.append({'db':db, 'extension': extension}) -}} {%- endfor -%} {%- endfor -%} {{- 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: >- {%- set 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) -%} {{- devices_list.append({ 'device_name': device.device_name, 'snapshot_id': result.snapshots | sort(attribute='start_time') | last | json_query('snapshot_id'), }) -}} {%- endfor -%} {%- endfor -%} {{ devices_list }} - name: "Use the users' home directory for something" block: - name: Executing commands from specified users block: - name: "Get users' homedir back" become: true become_user: "{{ item }}" become_flags: "-iH" check_mode: false command: >- echo "{{ item }}: $HOME" changed_when: false with_items: - root - ec2-user register: users_homedir_retrieve - name: Compute and register the results tags: AnsibleUnsafeText_to_Dict ansible.builtin.set_fact: users_homedir: >- {{ users_homedir_retrieve | community.general.json_query('results[].stdout') | map('from_yaml') | combine }} - name: Do your thing! become: true become_user: "{{ item.key }}" ansible.builtin.file: path: "{{ item.value }}/placeholder" state: touch with_dict: "{{ users_homedir }}" - name: "From the system's entries" block: - name: "Get raw information from the system's entries" ansible.builtin.getent: database: passwd key: "{{ item }}" split: ":" with_items: - root - ec2-user register: users_entries - name: Compute and register the results ansible.builtin.set_fact: users_info: >- {{ users_entries | community.general.json_query('results[].ansible_facts.getent_passwd[]') | combine }} - name: Do your thing! ansible.builtin.file: path: "{{ item.value[4] }}/placeholder" owner: "{{ item.key }}" state: touch with_dict: "{{ users_info }}" - name: Cronjobs block: - name: At specific times become: true ansible.builtin.cron: name: Prometheus manual data backup cron_file: prometheus-manual-data-backup # Mind this is based on the hosts' time. hour: 4 minute: 0 user: root job: # - Keep '%' characters escaped or they'll be treated as newlines. # - Archive creation returns 1 if it detects changes to read files. # Using ';' instead of '&&' to ignore. > FILENAME="/tmp/prometheus-data-$(date +'\%s-\%F-\%H-\%m-\%S').tar.gz" && tar -czf "$FILENAME" '/var/lib/prometheus/data' ; tar -tf "$FILENAME" > '/dev/null' && aws s3 cp "$FILENAME" 's3://backups/prometheus/' && rm "$FILENAME" - name: Debug tasks debugger: on_failed ansible.builtin.fail: msg: Manual, enforced failure # print all variables at this point => p task_vars # continue => c # abort and quit => q - name: Error handling in blocks block: - name: This executes normally ansible.builtin.debug: msg: I execute normally - name: This errors out 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: AWS tags: aws block: - name: Get current IP ranges # too many to be put into security group rules set_fact: ip_ranges: >- lookup('url', 'https://ip-ranges.amazonaws.com/ip-ranges.json', split_lines=False) | from_json | json_query('prefixes') | selectattr('region', 'equalto', 'eu-west-1') | selectattr('service', 'equalto', 'AMAZON') | map(attribute='ip_prefix') - name: Assume roles block: - name: Get session tokens amazon.aws.sts_assume_role: access_key: AKIA1EXAMPLE1EXAMPLE # optional if defined as environment variable secret_key: 123456789abcdefghijklmnopqrstuvwxyzABCDE # optional if defined as environment variable profile: someProfile # optional if defined as environment variable role_arn: "arn:aws:iam::123456789012:role/someRole" role_session_name: "someRoleSession" register: assumed_role - name: Use the assumed role to take action amazon.aws.ec2_tag: access_key: "{{ assumed_role.sts_creds.access_key }}" secret_key: "{{ assumed_role.sts_creds.secret_key }}" profile: null # required to use the assumed role's token, if profile is specified via environment variable session_token: "{{ assumed_role.sts_creds.session_token }}" resource: i-xyzxyz01 tags: MyNewTag: value - name: EC2 block: - name: Get running instances with 'K8S' as the 'Application' tag amazon.aws.ec2_instance_info: filters: "tag:Application": K8S "instance-state-name": ["running"] - name: Clone EC2 instances vars: source_instance_id: i-0123456789abcdef0 block: - name: Get instance information from the original instance amazon.aws.ec2_instance_info: instance_ids: - "{{ source_instance_id }}" register: source_instance_info - name: Create an AMI of the original instance amazon.aws.ec2_ami: instance_id: "{{ source_instance_id }}" no_reboot: true # remove if the instance rebooting upon AMI creation is no biggie wait: true wait_timeout: 3600 # big volumes call for bit wait times (a 200GiB volume took ) name: ami-source register: source_ami - name: Use the AMI to launch clones identical to the original when: source_ami.image_id is defined amazon.aws.ec2_instance: name: clone vpc_subnet_id: "{{ source_instance_info.instances[0].subnet_id }}" instance_type: "{{ source_instance_info.instances[0].instance_type }}" image: id: "{{ source_ami.image_id }}" - name: RDS block: - name: Create an instance's snapshot block: - name: Create the snapshot amazon.aws.rds_instance_snapshot: db_instance_identifier: "db-identifier" db_snapshot_identifier: "db-identifier-snapshot" register: snapshot_creation - name: Wait for the snapshot to be in the 'available state' when: snapshot_creation.snapshot_create_time is defined amazon.aws.rds_snapshot_info: db_snapshot_identifier: "{{ snapshot_creation.db_snapshot_identifier }}" register: snapshot_check retries: 3 delay: 120 until: snapshot_check.snapshots | selectattr("status", "equalto", "available") | length > 0 - name: "Dump roles' privileges" block: - name: Dump to file environment: PGPASSWORD: "someRandomString" vars: out_file: /tmp/instance-id_roles.sql ansible.builtin.command: >- pg_dumpall --host 'instance-id.c4v563ptr321.eu-west-1.rds.amazonaws.com' --port '5432' --user 'postgres' --database 'postgres' --no-password --roles-only --no-role-passwords --file '{{ out_file }}' changed_when: false - name: Dump to variable for later use through 'dump_execution.stdout_lines' environment: PGPASSWORD: "someRandomString" ansible.builtin.command: >- pg_dumpall -h 'instance-id.c4v563ptr321.eu-west-1.rds.amazonaws.com' -p '5432' -U 'postgres' -l 'postgres' -w -r --no-role-passwords changed_when: false register: dump_execution - name: Wait for pending changes to be applied amazon.aws.rds_instance_info: db_instance_identifier: "{{ db_instance_identifier }}" register: instance_check retries: 12 delay: 15 until: instance_check.instances[0].pending_modified_values.keys() | length == 0 - name: Wait for AWS to realize some requests have been made ansible.builtin.pause: seconds: 60 - name: Send messages to Slack channels vars: slack_notification_hook_url: https://hooks.slack.com/services/AB01CD23EF4/ABCD0123E/aBcDefGh0123456789iJKLmn block: - name: Send plain messages ansible.builtin.uri: url: "{{ slack_notification_hook_url }}" method: POST body_format: json body: text: (╥╯ᗝ╰╥) task XYZ failed - name: Send mrkdwn (Slack-specific markdown) text # FIXME: still to be tested ansible.builtin.uri: url: "{{ slack_notification_hook_url }}" method: POST body_format: json body: blocks: - type: section text: type: mrkdwn text: This is a *_fancy_* message - name: AWX environment: CONTROLLER_HOST: https://awx.example.org/ CONTROLLER_VERIFY_SSL: false CONTROLLER_USERNAME: admin CONTROLLER_PASSWORD: somethingSecret block: - name: Export all data from existing instances # At the time of writing: applications, credential_types, credentials, execution_environments, inventory, # inventory_sources, job_templates, notification_templates, organizations, projects, schedules, teams, and users. awx.awx.export: all: true register: awx_export_output