From 99c30fab099499d20b5d3ef38cf7ae1d6ced260e Mon Sep 17 00:00:00 2001 From: Valery Vetrov Date: Thu, 13 Jun 2024 17:34:50 +0300 Subject: [PATCH 01/12] Draft --- docs/ansible/coding-conventions.md | 669 +++++++++++++++++++++++++++++ 1 file changed, 669 insertions(+) create mode 100644 docs/ansible/coding-conventions.md diff --git a/docs/ansible/coding-conventions.md b/docs/ansible/coding-conventions.md new file mode 100644 index 0000000..d455f2b --- /dev/null +++ b/docs/ansible/coding-conventions.md @@ -0,0 +1,669 @@ +# Justcoded Ansible Coding Style and Conventions + +## Index +- [1. General conventions](#1-general-conventions) + * [Coding conventions]() + * [Best practices](#best-practices) + * [The beginning of a file](#the-beginning-of-a-file) + * [The end of a file](#the-end-of-the-file) + * [Always name tasks](#always-name-tasks) + * [Boolean variables](#boolean-variables) + * [Key/value pairs](#keyvalue-pairs) + * [Order in playbook](#order-in-playbook) + * [Order in task declaration](#order-in-task-declaration) + * [Air and tabulators](#air-and-tabulators) + * [Variable names](#variable-names) + * [Content Organization](#content-organization) + * [Directory Layout](#directory-layout) + * [Alternative Directory Layout](#alternative-directory-layout) + * [Group And Host Variables](#group-and-host-variables) + * [Top Level Playbooks Are Separated By Role](#top-level-playbooks-are-separated-by-role) + * [Always Mention The State](#always-mention-the-state) + * [Bundling Ansible Modules With Playbooks](#bundling-ansible-modules-with-playbooks) + * [Whitespace and Comments](#whitespace-and-comments) + +## Best practices + +Follow [Sample Ansible setup](https://docs.ansible.com/ansible/latest/tips_tricks/sample_setup.html) and use Ansible Lint, see [documentation](https://ansible.readthedocs.io/projects/lint/). + +### Why do this? + +The guys and girls who created Ansible have a good understanding how playbooks work and where files should reside. +You'll avoid a lot of your own ingenious pitfalls following their best practices. + +### So why doesn't my code look like Ansible examples? + +Examples in Ansible documentation are inconsistent. +The main reason for this style guide is to have one consistent way of writing Ansible which is easy to read for you and others. + +## The beginning of a file + +Always start your file with the YAML heading which is `---`. + +Please add comments above these lines with descriptions of what this playbook is and an example of how to run it. + + + +```yaml +# This playbook playbook changes state of user foo +# Example: ansible-playbook -e state=stopped playbook.yml +--- +- name: Change status of user foo + ansible.builtin.service: + enabled: true + name: foo + state: '{{ state }}' + become: true +``` +❌ ***Bad*** + +```yaml +- name: Change status of user foo + ansible.builtin.service: + enabled: true + name: foo + state: '{{ state }}' + become: true +``` + +### Why do this ? + +This makes it quick to find out what the playbook does. +Either with opening the file or just using the `head` command. + +## The end of the file + +Always end the file with a line shift. + +### Why do this? + +It's just Unix best practices. +It avoids messing up your prompt when you `cat` a file. + +## Always name tasks + +It is possible to leave off the ‘name’ for a given task, though it is recommended to provide a description about why something is being done instead. This name is shown when the playbook is run. + + +## Boolean variables + +Always use "false" and "true" values for boolean. + +✅ ***Good*** +```yaml +--- +- name: start chrony NTP daemon + ansible.builtin.service: + name: chrony + state: started + enabled: true + become: true +``` +❌ ***Bad*** + +```yaml +--- +- name: start chrony NTP daemon + ansible.builtin.service: + name: chrony + state: started + enabled: 1 + become: yes +``` + Why do this? + +Boolean variables may be expressed in a myriad of ways. +`0/1`, `False/True`, `no/yes` and `false/true`. +Even if you have a choice it's good to have a standard: `false/true`. There are a lot of scripting languages who have standardized on the `false/true` variant. Keep to it. + + +## Key/value pairs + +Only use on space after colon when you define key value pair. + +✅ ***Good*** + +```yaml +--- +- name: start chrony NTP daemon + ansible.builtin.service: + name: chrony + state: started + enabled: true + become: true +``` + +❌ ***Bad*** + +```yaml +--- +- name: start chrony NTP daemon + ansible.builtin.service: + name : chrony + state : started + enabled : true + become : true +``` + +Keep to only one standard. In our case **always use the "map" syntax**. This is regardless of how many key/value pairs that exist in a map. + +✅ ***Good*** + +```yaml +--- +- name: disable ntpd + ansible.builtin.service: + name: '{{ ntp_service }}' + state: stopped + enabled: false + become: true + +``` + +❌ ***Bad*** + +```yaml +--- +- name: disable ntpd + ansible.builtin.service: name='{{ ntp_service }}' state=stopped enabled=false + become: true +``` + Why do this? + +It's **soooo** much easier to read, and not more work to do. As the writer of this document is dyslectic, think of him and others in the same situation. In addition to the readability, it decreases the chance for a merge conflict. + +## Variables in Task Names + +Include as much information as necessary to explain the purpose of a task. +Make usage of variables inside a task name to create dynamic output messages. + +✅ ***Good*** + +```yaml +- name: 'Change status of httpd to {{ state }}' + service: + enabled: true + name: 'httpd' + state: '{{ state }}' + become: true + Reason + This will help to easily understand log outputs of playbooks. +``` + +❌ ***Bad*** + +```yaml +- name: 'Change status' + service: + enabled: true + name: 'httpd' + state: '{{ state }}' + become: true +``` + +## Omitting Unnecessary Information +While name tasks in a playbook, do not include the name of the role which is currently executed, since Ansible will do this automatically. + +**Reason:** +Avoiding the same output twice on the console will prevent confusions. + +## Names + +All the newly created Ansible roles should follow the name convention using dashes if necessary: +`[company]-[action]-[function/technology]` + +✅ ***Good*** + +```yaml +# good +mycompany-setup-lvm +``` + +❌ ***Bad*** +```yaml +# bad +lvm +``` + + +## Use Modules instead of command or shell +Before using the `command` or `shell` module, verify if there is already a module available which can avoid the usage of raw shell command. + +✅ ***Good*** + +```yaml +# good +- name: install packages + tasks: + - name: 'install httpd' + yum: + name: 'httpd' + state: 'present' +``` + +❌ ***Bad*** +```yaml +# bad +- name: install httpd + tasks: + - command: "yum install httpd" +``` + +**Reason:** While raw command could be seen as a security risk in general, another reason to avoid them is the loss of immutability of the ansible playbooks or roles. Ansible cannot verify if a command has been already executed before or not and will therefore execute it every time the playbook is running. + +## 🔴 Spacing addons + +# Comparing +Do not compare to literal True/False. +✅ ***Good*** + +```yaml +when: var +``` +```yaml +when: not var +``` + +❌ ***Bad*** +```yaml +when: var == True +``` +```yaml +when: var == Yes +``` +```yaml +when: var != "" +``` + +## Playbook File Extension + +All Ansible Yaml files should have a .yml extension (and NOT .YML, .yaml etc). + +##Vaults + +All Ansible Vault files should have a .vault extension (and NOT .yml, .YML, .yaml etc). + +## Use Module synchronize Instead of copy for Large Files + +## Use Block-Module + +Block can help to organize the code and can enable rollbacks. +```yaml +- block: + copy: + src: critical.conf + dest: /etc/critical/crit.conf + service: + name: critical + state: restarted + rescue: + command: shutdown -h now +``` + +## Order in playbook + +Playbook definitions should follow this order. + +* `name` +* `hosts` +* `remote_user` +* `become` +* `vars` +* `pre_tasks` +* `roles` +* `tasks` + +```yaml +--- +- name: update root authorized_keys for all machines + hosts: + - all + become: true + + vars: + root_keys_users: + - user: user1 + key: | + ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHkCVF05JvfkrfOOESivOxV4N8+A/EMEkF7/nCQMRoQg + enabled: true + + pre_tasks: + - name: add custom root users + set_fact: + root_keys_users: "{{ root_keys_users | union( root_keys_users_custom|default([]) ) }}" + + roles: + - role: root-keys + + tasks: + - name: print out debug message + ansible.builtin.debug: + msg: "fee foo faa" +``` + + Why do this? + +A common order makes playbooks consistent and easier to read for your dear colleagues. Think of them when you write. + +## Order in task declaration + +A task should be declared in this order. + +* `name` +* module and the arguments for the module +* arguments for the task in alphabetic order +* `tags` at the bottom + +```yaml +--- +- name: unhold packages + ansible.builtin.shell: 'apt-mark -s unhold {{ item }} && apt-mark unhold {{ item }}' + args: + executable: /bin/bash + changed_when: apt_mark_unhold.rc == 0 and "already" not in apt_mark_unhold.stdout + failed_when: false + loop: '{{ packages_ubuntu_unhold }}' + register: apt_mark_unhold + tags: + - apt_unhold +--- +``` + +### Why do this? + +Reason is the same as "order of playbook". To make tasks more consistent and easier to read. Help your colleagues. + +## Air and tabulators + +Air, one of the **most important thing** for humans and **for code**! It must be an empty line before `vars`, `pre_tasks`, `roles` and `tasks`, and before each task in the tasks definition. Tabulator stops must be set to two, `2`, spaces. + +### Why do this? + +This creates a pretty and tidy code which is easy to read, both for dyslectic and non dyslectic people. There is no excuse not to do this. + +## Variable names + +Always use `snake_case` for variable names. + +✅ ***Good*** + +```yaml +--- +- name: set my variables + ansible.builtin.set_fact: + a_boolean: false + an_int: 101 + a_string: bar +``` +❌ ***Bad*** + +```yaml +--- +- name: set my variables + ansible.builtin.set_fact: + aBoolean: false + anint: 101 + A_STRING: bar +``` + Why do this? + +Ansible already uses `snake_case` for variables in it's examples. Consistent naming of variables keeps the code tidy and gives better readability. + +## Content Organization + +Your usage of Ansible should fit your needs, however, not ours, so feel free to modify this approach and organize as you see fit. + +Always try to use Ansible’s “roles” to organize your playbook content organization feature. + +## Directory Layout + +The top level of the directory would contain files and directories like so: + +```text +production # inventory file for production servers +staging # inventory file for staging environment + +group_vars/ + group1.yml # here we assign variables to particular groups + group2.yml +host_vars/ + hostname1.yml # here we assign variables to particular systems + hostname2.yml + +library/ # if any custom modules, put them here (optional) + module_utils/ # if any custom module_utils to support modules, put them here (optional) + filter_plugins/ # if any custom filter plugins, put them here (optional) + +site.yml # master playbook +webservers.yml # playbook for webserver tier +dbservers.yml # playbook for dbserver tier + + roles/ + common/ # this hierarchy represents a "role" + tasks/ # + main.yml # <-- tasks file can include smaller files if warranted + handlers/ # + main.yml # <-- handlers file + templates/ # <-- files for use with the template resource + ntp.conf.j2 # <------- templates end in .j2 + files/ # + bar.txt # <-- files for use with the copy resource + foo.sh # <-- script files for use with the script resource + vars/ # + main.yml # <-- variables associated with this role + defaults/ # + main.yml # <-- default lower priority variables for this role + meta/ # + main.yml # <-- role dependencies + library/ # roles can also include custom modules + module_utils/ # roles can also include custom module_utils + lookup_plugins/ # or other types of plugins, like lookup in this case + + webtier/ # same kind of structure as "common" was above, done for the webtier role + monitoring/ # "" + fooapp/ # "" +``` + +## Alternative Directory Layout + +Alternatively you can put each inventory file with its group_vars/host_vars in a separate directory. This is particularly useful if your group_vars/host_vars don’t have that much in common in different environments. The layout could look something like this: + +```text +inventories/ + production/ + hosts # inventory file for production servers + group_vars/ + group1.yml # here we assign variables to particular groups + group2.yml + host_vars/ + hostname1.yml # here we assign variables to particular systems + hostname2.yml + + staging/ + hosts # inventory file for staging environment + group_vars/ + group1.yml # here we assign variables to particular groups + group2.yml + host_vars/ + stagehost1.yml # here we assign variables to particular systems + stagehost2.yml + +library/ +module_utils/ +filter_plugins/ + +site.yml +webservers.yml +dbservers.yml + +roles/ + common/ + webtier/ + monitoring/ + fooapp/ +``` + +This layout gives you more flexibility for larger environments, as well as a total separation of inventory variables between different environments. The downside is that it is harder to maintain, because there are more files. + +## Group And Host Variables + +Groups are nice for organization, but that’s not all groups are good for. +You can also assign variables to them! For instance, atlanta has its own NTP servers, so when setting up ntp.conf, we should use them. +Let’s set those now: + +```yaml +--- +# file: group_vars/atlanta +ntp: ntp-atlanta.example.com +backup: backup-atlanta.example.com +``` + +Variables aren’t just for geographic information either! Maybe the webservers have some configuration that doesn’t make sense for the database servers: + +```yaml +--- +# file: group_vars/webservers +apacheMaxRequestsPerChild: 3000 +apacheMaxClients: 900 +``` + +If we had any default values, or values that were universally true, we would put them in a file called group_vars/all: + +```yaml +--- +# file: group_vars/all +ntp: ntp-boston.example.com +backup: backup-boston.example.com +``` + +We can define specific hardware variance in systems in a host_vars file, but avoid doing this unless you need to: + +```yaml +--- +# file: host_vars/db-bos-1.example.com +foo_agent_port: 86 +bar_agent_port: 99 +``` + +Again, if we are using dynamic inventory sources, many dynamic groups are automatically created. +So a tag like “class:webserver” would load in variables from the file “group_vars/ec2_tag_class_webserver” automatically. + +## Top Level Playbooks Are Separated By Role + +In site.yml, we import a playbook that defines our entire infrastructure. This is a very short example, because it’s just importing some other playbooks: + +```yaml +--- +# file: site.yml +- import_playbook: webservers.yml +- import_playbook: dbservers.yml +``` + +In a file like webservers.yml (also at the top level), we map the configuration of the webservers group to the roles performed by the webservers group: + +```yaml +--- +# file: webservers.yml +- hosts: webservers + roles: + - common + - webtier +``` + +The idea here is that we can choose to configure our whole infrastructure by “running” site.yml or we could just choose to run a subset by running webservers.yml. +This is analogous to the “–limit” parameter to ansible but a little more explicit: + +```yaml +ansible-playbook site.yml --limit webservers +ansible-playbook webservers.yml +``` + +## Always Mention The State + +The ‘state’ parameter is optional to a lot of modules. +Whether ‘state=present’ or ‘state=absent’, it’s always best to leave that parameter in your playbooks to make it clear, especially as some modules support additional states. + +## Bundling Ansible Modules With Playbooks + +If a playbook has a ./library directory relative to its YAML file, this directory can be used to add ansible modules that will automatically be in the ansible module path. +This is a great way to keep modules that go with a playbook together. +This is shown in the directory structure example at the start of this section. + +## Whitespace and Comments + +Generous use of whitespace to break things up, and use of comments (which start with ‘#’), is encouraged. + +## Optimize Playbook Execution +Disable facts gathering if it is not required. + +Try not to use: ansible_facts[‘hostname’] (or ‘nodename’) + +Try to use inventory_hostname and inventory_hostname_short instead + +It is possible to selectively gather facts (gather_subnet) + +Consider caching facts + +Increase Parallelism + +Ansible runs 1st task on every host, then 2nd task on every host … + +Use “forks” (default is 5) to control how many connections can be active (load will increase, try first with conservative values) + +While testing forks analize Enable CPU and Memory Profiling + +If you use the module "copy" for large files: do not use “copy” module. Use “synchronize” module instead (based on rsync) + +Use lists when ever a modules support it (i.e. yum): + + +✅ ***Good*** +```yaml +tasks: +- name: Ensure the packages are installed + yum: + state: present + name: + - httpd + - mod_ssl + - httpd-tools + - mariadb-server + - mariadb + - php + - php-mysqlnd +``` + +***Bad*** +```yaml +tasks: + - name: Ensure the packages are installed + yum: + name: "{{ item }}" + state: present + loop: + - httpd + - mod_ssl + - httpd-tools + - mariadb-server + - mariadb + - php + - php-mysqlnd +``` + +## Optimize SSH Connections + +in `ansible.cfg` set + +* ControlMaster +* ControlPersist +* PreferredAuthentications + + +## Enabling Pipelining + +! **Not enabled by default** + +reduces number of SSH operations + +requires to disable `requiretty` in sudo options + From cf5992eaaad6ee6467cd570df9b50e26f588df4c Mon Sep 17 00:00:00 2001 From: Valery Vetrov Date: Mon, 17 Jun 2024 13:29:19 +0300 Subject: [PATCH 02/12] Multiple fixes --- docs/ansible/coding-conventions.md | 745 +++++++++++++++++------------ 1 file changed, 427 insertions(+), 318 deletions(-) diff --git a/docs/ansible/coding-conventions.md b/docs/ansible/coding-conventions.md index d455f2b..7c9236a 100644 --- a/docs/ansible/coding-conventions.md +++ b/docs/ansible/coding-conventions.md @@ -1,40 +1,58 @@ # Justcoded Ansible Coding Style and Conventions -## Index -- [1. General conventions](#1-general-conventions) - * [Coding conventions]() - * [Best practices](#best-practices) - * [The beginning of a file](#the-beginning-of-a-file) - * [The end of a file](#the-end-of-the-file) - * [Always name tasks](#always-name-tasks) - * [Boolean variables](#boolean-variables) +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" +in this document are to be interpreted as described in [RFC 2119](https://datatracker.ietf.org/doc/html/rfc2119). + + +* [Coding style](#coding-style) + * [Playbook File Extension](#playbook-file-extension) + * [So why doesn't my code look like Ansible examples?](#so-why-doesnt-my-code-look-like-ansible-examples) + * [The beginning of a file](#the-beginning-of-a-file) + * [The end of the file](#the-end-of-the-file) + * [Spaces and alignment](#spaces-and-alignment) * [Key/value pairs](#keyvalue-pairs) - * [Order in playbook](#order-in-playbook) - * [Order in task declaration](#order-in-task-declaration) + * [Whitespace and Comments](#whitespace-and-comments) * [Air and tabulators](#air-and-tabulators) - * [Variable names](#variable-names) + * [Order in playbook](#order-in-playbook) + * [Order in task declaration](#order-in-task-declaration) + * [Task names](#task-names) + * [Names](#names) + * [Always name tasks](#always-name-tasks) + * [Format for task names](#format-for-task-names) + * [Variables in Task Names](#variables-in-task-names) + * [Omitting Unnecessary Information](#omitting-unnecessary-information) + * [Variable names](#variable-names) + * [Always use `snake_case` for variable names.](#always-use-snakecase-for-variable-names) + * [The prefix should contain the name of the role.](#the-prefix-should-contain-the-name-of-the-role) + * [Define temporary variables using unique prefix](#define-temporary-variables-using-unique-prefix) + * [Define paths without trailing slash](#define-paths-without-trailing-slash) + * [Boolean variables](#boolean-variables) + * [Comparing](#comparing) + * [Use Modules instead of command or shell](#use-modules-instead-of-command-or-shell) + * [🔴 Spacing addons](#-spacing-addons) + * [Always Mention The State](#always-mention-the-state) + * [Use Block-Module](#use-block-module) + * [Use lists](#use-lists) +* [Coding conventions](#coding-conventions) * [Content Organization](#content-organization) * [Directory Layout](#directory-layout) * [Alternative Directory Layout](#alternative-directory-layout) * [Group And Host Variables](#group-and-host-variables) * [Top Level Playbooks Are Separated By Role](#top-level-playbooks-are-separated-by-role) - * [Always Mention The State](#always-mention-the-state) * [Bundling Ansible Modules With Playbooks](#bundling-ansible-modules-with-playbooks) - * [Whitespace and Comments](#whitespace-and-comments) - -## Best practices - -Follow [Sample Ansible setup](https://docs.ansible.com/ansible/latest/tips_tricks/sample_setup.html) and use Ansible Lint, see [documentation](https://ansible.readthedocs.io/projects/lint/). - -### Why do this? + * [Playbooks optimization](#playbooks-optimization) + * [Optimize Playbook Execution](#optimize-playbook-execution) + * [Use Module synchronize Instead of copy for Large Files](#use-module-synchronize-instead-of-copy-for-large-files) + * [Optimize SSH Connections](#optimize-ssh-connections) + * [Enabling Pipelining](#enabling-pipelining) +* [Best practices](#best-practices) + -The guys and girls who created Ansible have a good understanding how playbooks work and where files should reside. -You'll avoid a lot of your own ingenious pitfalls following their best practices. +# Coding style -### So why doesn't my code look like Ansible examples? +## Playbook File Extension -Examples in Ansible documentation are inconsistent. -The main reason for this style guide is to have one consistent way of writing Ansible which is easy to read for you and others. +All Ansible Yaml files should have a **.yml** extension (and NOT **.YML**, **.yaml** etc). ## The beginning of a file @@ -42,8 +60,7 @@ Always start your file with the YAML heading which is `---`. Please add comments above these lines with descriptions of what this playbook is and an example of how to run it. - - +✅ ***Good*** ```yaml # This playbook playbook changes state of user foo # Example: ansible-playbook -e state=stopped playbook.yml @@ -56,7 +73,6 @@ Please add comments above these lines with descriptions of what this playbook is become: true ``` ❌ ***Bad*** - ```yaml - name: Change status of user foo ansible.builtin.service: @@ -66,7 +82,7 @@ Please add comments above these lines with descriptions of what this playbook is become: true ``` -### Why do this ? +#### Why do this ? This makes it quick to find out what the playbook does. Either with opening the file or just using the `head` command. @@ -75,49 +91,14 @@ Either with opening the file or just using the `head` command. Always end the file with a line shift. -### Why do this? +#### Why do this ? It's just Unix best practices. It avoids messing up your prompt when you `cat` a file. -## Always name tasks +## Spaces and alignment -It is possible to leave off the ‘name’ for a given task, though it is recommended to provide a description about why something is being done instead. This name is shown when the playbook is run. - - -## Boolean variables - -Always use "false" and "true" values for boolean. - -✅ ***Good*** -```yaml ---- -- name: start chrony NTP daemon - ansible.builtin.service: - name: chrony - state: started - enabled: true - become: true -``` -❌ ***Bad*** - -```yaml ---- -- name: start chrony NTP daemon - ansible.builtin.service: - name: chrony - state: started - enabled: 1 - become: yes -``` - Why do this? - -Boolean variables may be expressed in a myriad of ways. -`0/1`, `False/True`, `no/yes` and `false/true`. -Even if you have a choice it's good to have a standard: `false/true`. There are a lot of scripting languages who have standardized on the `false/true` variant. Keep to it. - - -## Key/value pairs +### Key/value pairs Only use on space after colon when you define key value pair. @@ -145,7 +126,8 @@ Only use on space after colon when you define key value pair. become : true ``` -Keep to only one standard. In our case **always use the "map" syntax**. This is regardless of how many key/value pairs that exist in a map. +Keep to only one standard. In our case **always use the "map" syntax**. +This is regardless of how many key/value pairs that exist in a map. ✅ ***Good*** @@ -168,137 +150,26 @@ Keep to only one standard. In our case **always use the "map" syntax**. This is ansible.builtin.service: name='{{ ntp_service }}' state=stopped enabled=false become: true ``` - Why do this? +#### Why do this ? It's **soooo** much easier to read, and not more work to do. As the writer of this document is dyslectic, think of him and others in the same situation. In addition to the readability, it decreases the chance for a merge conflict. -## Variables in Task Names - -Include as much information as necessary to explain the purpose of a task. -Make usage of variables inside a task name to create dynamic output messages. - -✅ ***Good*** - -```yaml -- name: 'Change status of httpd to {{ state }}' - service: - enabled: true - name: 'httpd' - state: '{{ state }}' - become: true - Reason - This will help to easily understand log outputs of playbooks. -``` - -❌ ***Bad*** - -```yaml -- name: 'Change status' - service: - enabled: true - name: 'httpd' - state: '{{ state }}' - become: true -``` - -## Omitting Unnecessary Information -While name tasks in a playbook, do not include the name of the role which is currently executed, since Ansible will do this automatically. - -**Reason:** -Avoiding the same output twice on the console will prevent confusions. - -## Names - -All the newly created Ansible roles should follow the name convention using dashes if necessary: -`[company]-[action]-[function/technology]` - -✅ ***Good*** - -```yaml -# good -mycompany-setup-lvm -``` - -❌ ***Bad*** -```yaml -# bad -lvm -``` - - -## Use Modules instead of command or shell -Before using the `command` or `shell` module, verify if there is already a module available which can avoid the usage of raw shell command. - -✅ ***Good*** - -```yaml -# good -- name: install packages - tasks: - - name: 'install httpd' - yum: - name: 'httpd' - state: 'present' -``` - -❌ ***Bad*** -```yaml -# bad -- name: install httpd - tasks: - - command: "yum install httpd" -``` - -**Reason:** While raw command could be seen as a security risk in general, another reason to avoid them is the loss of immutability of the ansible playbooks or roles. Ansible cannot verify if a command has been already executed before or not and will therefore execute it every time the playbook is running. - -## 🔴 Spacing addons - -# Comparing -Do not compare to literal True/False. -✅ ***Good*** - -```yaml -when: var -``` -```yaml -when: not var -``` -❌ ***Bad*** -```yaml -when: var == True -``` -```yaml -when: var == Yes -``` -```yaml -when: var != "" -``` +### Whitespace and Comments -## Playbook File Extension +Generous use of whitespace to break things up, and use of comments (which start with ‘#’), is encouraged. -All Ansible Yaml files should have a .yml extension (and NOT .YML, .yaml etc). +### Air and tabulators -##Vaults +Air, one of the **most important thing** for humans and **for code**! +It must be an empty line before `vars`, `pre_tasks`, `roles` and `tasks`, and before each task in the tasks definition. +Tabulator stops must be set to two, `2`, spaces. -All Ansible Vault files should have a .vault extension (and NOT .yml, .YML, .yaml etc). +#### Why do this ? -## Use Module synchronize Instead of copy for Large Files +This creates a pretty and tidy code which is easy to read, both for dyslectic and non dyslectic people. There is no excuse not to do this. -## Use Block-Module -Block can help to organize the code and can enable rollbacks. -```yaml -- block: - copy: - src: critical.conf - dest: /etc/critical/crit.conf - service: - name: critical - state: restarted - rescue: - command: shutdown -h now -``` ## Order in playbook @@ -341,7 +212,7 @@ Playbook definitions should follow this order. msg: "fee foo faa" ``` - Why do this? +#### Why do this ? A common order makes playbooks consistent and easier to read for your dear colleagues. Think of them when you write. @@ -369,21 +240,91 @@ A task should be declared in this order. --- ``` -### Why do this? +## Task names -Reason is the same as "order of playbook". To make tasks more consistent and easier to read. Help your colleagues. +### Names -## Air and tabulators +All the newly created Ansible roles should follow the name convention using dashes if necessary: +`--[function/technology]` -Air, one of the **most important thing** for humans and **for code**! It must be an empty line before `vars`, `pre_tasks`, `roles` and `tasks`, and before each task in the tasks definition. Tabulator stops must be set to two, `2`, spaces. +✅ ***Good*** -### Why do this? +```yaml +# good +jc-setup-lvm +``` -This creates a pretty and tidy code which is easy to read, both for dyslectic and non dyslectic people. There is no excuse not to do this. +❌ ***Bad*** +```yaml +# bad +lvm +``` + +### Always name tasks + +It is possible to leave off the ‘name’ for a given task, though it is recommended to provide a description about why something is being done instead. +This name is shown when the playbook is run. + +### Format for task names + +Always use the following format in task names: + + ` | ` + +✅ ***Good*** + +```yaml +- name: 'SwitchHttpdStatus | Change status of httpd to {{ state }}' +``` + +❌ ***Bad*** + +```yaml +- name: 'Change status of httpd to {{ state }}' +``` + +#### Why do this? + +It will always be clear what task was performed when analyzing the log. + +### Variables in Task Names + +Include as much information as necessary to explain the purpose of a task. +Make usage of variables inside a task name to create dynamic output messages. + +✅ ***Good*** + +```yaml +- name: 'Change status of httpd to {{ state }}' + service: + enabled: true + name: 'httpd' + state: '{{ state }}' + become: true +``` + +❌ ***Bad*** + +```yaml +- name: 'Change status' + service: + enabled: true + name: 'httpd' + state: '{{ state }}' + become: true +``` +#### Why do this ? +This will help to easily understand log outputs of playbooks. + +### Omitting Unnecessary Information +While name tasks in a playbook, do not include the name of the role which is currently executed, since Ansible will do this automatically. + +#### Why do this ? +Avoiding the same output twice on the console will prevent confusions. ## Variable names -Always use `snake_case` for variable names. +### Always use `snake_case` for variable names. ✅ ***Good*** @@ -405,105 +346,273 @@ Always use `snake_case` for variable names. anint: 101 A_STRING: bar ``` - Why do this? + +#### Why do this ? Ansible already uses `snake_case` for variables in it's examples. Consistent naming of variables keeps the code tidy and gives better readability. +### The prefix should contain the name of the role. + +✅ ***Good*** + +```yaml +--- +- name: 'set some facts' + set_fact: + rolename_my_boolean: true + rolename_my_int: 20 + rolename_my_string: 'test' +``` + +❌ ***Bad*** + +```yaml +--- +- name: 'set some facts' + set_fact: + myBoolean: true + int: 20 + MY_STRING: 'test' +``` + +### Define temporary variables using unique prefix + +Registered variables and facts set using set_fact module are often defined for temporary usage. +In order to avoid variable collision and make it clear to the reader that these variables are only for temporary usage, +it is good practice to use a unique prefix. +You could use r_ for registered variables and f_ for facts. + +```yaml +- name: Collect information from external system + uri: + url: http://www.example.com + return_content: yes + register: r_results + failed_when: "'AWESOME' not in r_results.content" + +- name: Set some facts for temporary usage + set_fact: + f_username: "{{ r_results.username }}" +``` + +### Define paths without trailing slash + +Variables that define paths should never have a trailing slash. +Also when concatenating paths, follow the same convention. + +✅ ***Good*** + +```yaml +app_root: /foo +app_bar: "{{ app_root }}/bar" +``` + +❌ ***Bad*** + +```yaml +app_root: /foo/ +app_bar: "{{ app_root }}bar" +``` + +## Boolean variables + +Always use **false** and **true** values for boolean. + +✅ ***Good*** +```yaml +--- +- name: start chrony NTP daemon + ansible.builtin.service: + name: chrony + state: started + enabled: true + become: true +``` +❌ ***Bad*** + +```yaml +--- +- name: start chrony NTP daemon + ansible.builtin.service: + name: chrony + state: started + enabled: 1 + become: yes +``` + +### Comparing +Do not compare to literal True/False. + +✅ ***Good*** + +```yaml +when: var +``` +```yaml +when: not var +``` + +❌ ***Bad*** +```yaml +when: var == True +``` +```yaml +when: var == Yes +``` +```yaml +when: var != "" +``` + + +## Use Modules instead of command or shell +Before using the `command` or `shell` module, verify if there is already a module available which can avoid the usage of raw shell command. + +✅ ***Good*** + +```yaml +# good +- name: install packages + tasks: + - name: 'install httpd' + yum: + name: 'httpd' + state: 'present' +``` + +❌ ***Bad*** +```yaml +# bad +- name: install httpd + tasks: + - command: "yum install httpd" +``` + +#### Why do this ? +While raw command could be seen as a security risk in general, another reason to avoid them is the loss of immutability of the ansible playbooks or roles. Ansible cannot verify if a command has been already executed before or not and will therefore execute it every time the playbook is running. + +## 🔴 Spacing addons + +## Always Mention The State + +The ‘state’ parameter is optional to a lot of modules. +Whether ‘state=present’ or ‘state=absent’, it’s always best to leave that parameter in your playbooks to make it clear, especially as some modules support additional states. + +## Use Block-Module + +Block can help to organize the code and can enable rollbacks. +```yaml +- block: + copy: + src: critical.conf + dest: /etc/critical/crit.conf + service: + name: critical + state: restarted + rescue: + command: shutdown -h now +``` + +Also blocks allow for logical grouping of tasks and in play error handling. +Most of what you can apply to a single task (with the exception of loops) can be applied at the block level, +which also makes it much easier to set data or directives common to the tasks. This does not mean the directive affects the block itself, +but is inherited by the tasks enclosed by a block. +i.e. a when will be applied to the tasks, not the block itself. + +```yaml +tasks: + - name: Install, configure, and start Apache + block: + - name: install httpd and memcached + yum: + name: + - httpd + - memcached + state: present + + - name: apply the foo config template + template: + src: templates/src.j2 + dest: /etc/foo.conf + - name: start service bar and enable it + service: + name: bar + state: started + enabled: True + when: ansible_facts['distribution'] == 'CentOS' + become: true + become_user: root + ignore_errors: yes +``` +## Use lists +Use lists when ever a modules support it (i.e. yum): + + +✅ ***Good*** +```yaml +tasks: +- name: Ensure the packages are installed + yum: + state: present + name: + - httpd + - mod_ssl + - httpd-tools + - mariadb-server + - mariadb + - php + - php-mysqlnd +``` + +***Bad*** +```yaml +tasks: + - name: Ensure the packages are installed + yum: + name: "{{ item }}" + state: present + loop: + - httpd + - mod_ssl + - httpd-tools + - mariadb-server + - mariadb + - php + - php-mysqlnd +``` +# Coding conventions + +## Vaults + +All Ansible Vault files should have a **.vault** extension (and NOT **.yml**, **.YML**, **.yaml** etc). + ## Content Organization Your usage of Ansible should fit your needs, however, not ours, so feel free to modify this approach and organize as you see fit. Always try to use Ansible’s “roles” to organize your playbook content organization feature. -## Directory Layout -The top level of the directory would contain files and directories like so: -```text -production # inventory file for production servers -staging # inventory file for staging environment - -group_vars/ - group1.yml # here we assign variables to particular groups - group2.yml -host_vars/ - hostname1.yml # here we assign variables to particular systems - hostname2.yml - -library/ # if any custom modules, put them here (optional) - module_utils/ # if any custom module_utils to support modules, put them here (optional) - filter_plugins/ # if any custom filter plugins, put them here (optional) - -site.yml # master playbook -webservers.yml # playbook for webserver tier -dbservers.yml # playbook for dbserver tier - - roles/ - common/ # this hierarchy represents a "role" - tasks/ # - main.yml # <-- tasks file can include smaller files if warranted - handlers/ # - main.yml # <-- handlers file - templates/ # <-- files for use with the template resource - ntp.conf.j2 # <------- templates end in .j2 - files/ # - bar.txt # <-- files for use with the copy resource - foo.sh # <-- script files for use with the script resource - vars/ # - main.yml # <-- variables associated with this role - defaults/ # - main.yml # <-- default lower priority variables for this role - meta/ # - main.yml # <-- role dependencies - library/ # roles can also include custom modules - module_utils/ # roles can also include custom module_utils - lookup_plugins/ # or other types of plugins, like lookup in this case - - webtier/ # same kind of structure as "common" was above, done for the webtier role - monitoring/ # "" - fooapp/ # "" -``` - -## Alternative Directory Layout - -Alternatively you can put each inventory file with its group_vars/host_vars in a separate directory. This is particularly useful if your group_vars/host_vars don’t have that much in common in different environments. The layout could look something like this: +## Directory Layout + +The top level of the directory should contain files and directories like so: +This is the required preset for recognizing playbooks, tasks and variables by [OrchidE plugin](https://www.orchide.dev/pages/dokumentation). ```text -inventories/ - production/ - hosts # inventory file for production servers - group_vars/ - group1.yml # here we assign variables to particular groups - group2.yml - host_vars/ - hostname1.yml # here we assign variables to particular systems - hostname2.yml - - staging/ - hosts # inventory file for staging environment - group_vars/ - group1.yml # here we assign variables to particular groups - group2.yml - host_vars/ - stagehost1.yml # here we assign variables to particular systems - stagehost2.yml - -library/ -module_utils/ -filter_plugins/ - -site.yml -webservers.yml -dbservers.yml - -roles/ - common/ - webtier/ - monitoring/ - fooapp/ -``` - -This layout gives you more flexibility for larger environments, as well as a total separation of inventory variables between different environments. The downside is that it is harder to maintain, because there are more files. + project + |-- inventory + | |-- hosts + | |-- group_vars + | | |-- webservers + | | | |-- default.yml + |-- playbooks + | |-- webservers + | | -- main.yml + |-- roles + | |-- webserver + | | |-- tasks + | | | |-- main.yml +``` ## Group And Host Variables @@ -578,10 +687,6 @@ ansible-playbook site.yml --limit webservers ansible-playbook webservers.yml ``` -## Always Mention The State - -The ‘state’ parameter is optional to a lot of modules. -Whether ‘state=present’ or ‘state=absent’, it’s always best to leave that parameter in your playbooks to make it clear, especially as some modules support additional states. ## Bundling Ansible Modules With Playbooks @@ -589,11 +694,12 @@ If a playbook has a ./library directory relative to its YAML file, this director This is a great way to keep modules that go with a playbook together. This is shown in the directory structure example at the start of this section. -## Whitespace and Comments +## Playbooks optimization -Generous use of whitespace to break things up, and use of comments (which start with ‘#’), is encouraged. +### Import vs Include -## Optimize Playbook Execution + +### Optimize Playbook Execution Disable facts gathering if it is not required. Try not to use: ansible_facts[‘hostname’] (or ‘nodename’) @@ -612,44 +718,39 @@ Use “forks” (default is 5) to control how many connections can be active (lo While testing forks analize Enable CPU and Memory Profiling -If you use the module "copy" for large files: do not use “copy” module. Use “synchronize” module instead (based on rsync) - -Use lists when ever a modules support it (i.e. yum): +### Use Module synchronize Instead of copy for Large Files +If you use the module "copy" for large files: do not use “copy” module. +Use “synchronize” module instead (based on rsync) ✅ ***Good*** + ```yaml -tasks: -- name: Ensure the packages are installed - yum: - state: present - name: - - httpd - - mod_ssl - - httpd-tools - - mariadb-server - - mariadb - - php - - php-mysqlnd +- hosts: test_01 + + tasks: + - name: "copy big file" + + - package: + name: rsync + + - synchronize: + src: /tmp/bigfile.tar.gz + dest: /tmp/test ``` -***Bad*** +❌ ***Bad*** ```yaml -tasks: - - name: Ensure the packages are installed - yum: - name: "{{ item }}" - state: present - loop: - - httpd - - mod_ssl - - httpd-tools - - mariadb-server - - mariadb - - php - - php-mysqlnd -``` +- hosts: test_01 + + tasks: + - name: "copy big file" + - copy: + src: /tmp/bigfile.tar.gz + dest: /tmp/test + +``` ## Optimize SSH Connections in `ansible.cfg` set @@ -667,3 +768,11 @@ reduces number of SSH operations requires to disable `requiretty` in sudo options +## Best practices + +Follow [Sample Ansible setup](https://docs.ansible.com/ansible/latest/tips_tricks/sample_setup.html) and use Ansible Lint, see [documentation](https://ansible.readthedocs.io/projects/lint/). + +#### Why do this ? + +The guys and girls who created Ansible have a good understanding how playbooks work and where files should reside. +You'll avoid a lot of your own ingenious pitfalls following their best practices. From 613175978e62f0c47bf435701cd242bcad565c24 Mon Sep 17 00:00:00 2001 From: Valery Vetrov Date: Mon, 17 Jun 2024 14:12:37 +0300 Subject: [PATCH 03/12] Added dsome examples --- docs/ansible/coding-conventions.md | 69 +++++++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/docs/ansible/coding-conventions.md b/docs/ansible/coding-conventions.md index 7c9236a..6796cac 100644 --- a/docs/ansible/coding-conventions.md +++ b/docs/ansible/coding-conventions.md @@ -41,10 +41,12 @@ in this document are to be interpreted as described in [RFC 2119](https://datatr * [Top Level Playbooks Are Separated By Role](#top-level-playbooks-are-separated-by-role) * [Bundling Ansible Modules With Playbooks](#bundling-ansible-modules-with-playbooks) * [Playbooks optimization](#playbooks-optimization) + * [Import vs Include](#import-vs-include) * [Optimize Playbook Execution](#optimize-playbook-execution) * [Use Module synchronize Instead of copy for Large Files](#use-module-synchronize-instead-of-copy-for-large-files) - * [Optimize SSH Connections](#optimize-ssh-connections) - * [Enabling Pipelining](#enabling-pipelining) + * [Configuration conventions](#configuration-conventions) + * [Optimize SSH Connections](#optimize-ssh-connections) + * [Enabling Pipelining](#enabling-pipelining) * [Best practices](#best-practices) @@ -698,6 +700,44 @@ This is shown in the directory structure example at the start of this section. ### Import vs Include +Alway try to break up playbooks into smaller logically separated parts. + +Includes and imports allow users to do this, which can be used across multiple parent playbooks or even multiple times within the same Playbook. +A major difference between `import` and `include` tasks: + +- `import` tasks will be parsed at the beginning when you run your playbook +(If you use any `import*` Task (`import_playbook`, `import_tasks`, etc.), it will be static.) +- `include` tasks will be parsed at the moment Ansible hits them +(If you use any **include*** Task (**include_tasks**, **include_role**, etc.), it will be dynamic.) + + +**Warning:** The bare `include` task (which was used for both Task files and Playbook-level includes) is still available, however it is now considered deprecated. + +#### Tradeoffs and Pitfalls Between Includes and Imports +* Using `include*` vs. `import*` has some advantages as well as some tradeoffs which users should consider when choosing to use each: + +* The primary advantage of using `include*` statements is looping. When a loop is used with an include, the included tasks or role will be executed once for each item in the loop. + +* Using `include*` does have some limitations when compared to `import*` statements: + +* Tags which only exist inside a dynamic include will not show up in `--list-tags` output. + +* Tasks which only exist inside a dynamic include will not show up in `--list-tasks` output. + +* You cannot use `notify` to trigger a handler name which comes from inside a dynamic include (see note below). + +* You cannot use `--start-at-task` to begin execution at a task inside a dynamic include. + +* Using `import*` can also have some limitations when compared to dynamic includes: + +* As noted above, loops cannot be used with imports at all. + +* When using variables for the target file or role name, variables from inventory sources (**host**/**group** vars, etc.) cannot be used. + +* Handlers using **import*** will not be triggered when notified by their name, as importing overwrites the handler’s named task with the imported task list. + +**Note:** Regarding the use of `notify` for dynamic tasks: +it is still possible to trigger the dynamic include itself, which would result in all tasks within the include being run. ### Optimize Playbook Execution Disable facts gathering if it is not required. @@ -720,7 +760,7 @@ While testing forks analize Enable CPU and Memory Profiling ### Use Module synchronize Instead of copy for Large Files -If you use the module "copy" for large files: do not use “copy” module. +If you use the module `copy` for large files: do not use `copy` module. Use “synchronize” module instead (based on rsync) ✅ ***Good*** @@ -751,7 +791,10 @@ Use “synchronize” module instead (based on rsync) dest: /tmp/test ``` -## Optimize SSH Connections + +## Configuration conventions + +### Optimize SSH Connections in `ansible.cfg` set @@ -759,14 +802,26 @@ in `ansible.cfg` set * ControlPersist * PreferredAuthentications - -## Enabling Pipelining +```text +[ssh_connection] +... +ssh_args=-C -o ControlMaster=auto -o ControlPersist=1200s -o BatchMode=yes -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey +... +``` +### Enabling Pipelining ! **Not enabled by default** reduces number of SSH operations -requires to disable `requiretty` in sudo options +```text +[ssh_connection] +... +pipelining=true +... +``` + +**Info:** this requires to disable `requiretty` in sudo options ## Best practices From 6bab296ef86a149ea1eac84c8804ed425cfaf18a Mon Sep 17 00:00:00 2001 From: Valery Vetrov Date: Mon, 17 Jun 2024 15:38:52 +0300 Subject: [PATCH 04/12] Changed --- docs/ansible/coding-conventions.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/ansible/coding-conventions.md b/docs/ansible/coding-conventions.md index 6796cac..a5700f5 100644 --- a/docs/ansible/coding-conventions.md +++ b/docs/ansible/coding-conventions.md @@ -84,7 +84,7 @@ Please add comments above these lines with descriptions of what this playbook is become: true ``` -#### Why do this ? +🔎 Why do this? This makes it quick to find out what the playbook does. Either with opening the file or just using the `head` command. @@ -93,7 +93,7 @@ Either with opening the file or just using the `head` command. Always end the file with a line shift. -#### Why do this ? +🔎 Why do this? It's just Unix best practices. It avoids messing up your prompt when you `cat` a file. @@ -152,7 +152,7 @@ This is regardless of how many key/value pairs that exist in a map. ansible.builtin.service: name='{{ ntp_service }}' state=stopped enabled=false become: true ``` -#### Why do this ? +🔎 Why do this? It's **soooo** much easier to read, and not more work to do. As the writer of this document is dyslectic, think of him and others in the same situation. In addition to the readability, it decreases the chance for a merge conflict. @@ -167,7 +167,7 @@ Air, one of the **most important thing** for humans and **for code**! It must be an empty line before `vars`, `pre_tasks`, `roles` and `tasks`, and before each task in the tasks definition. Tabulator stops must be set to two, `2`, spaces. -#### Why do this ? +🔎 Why do this? This creates a pretty and tidy code which is easy to read, both for dyslectic and non dyslectic people. There is no excuse not to do this. @@ -214,7 +214,7 @@ Playbook definitions should follow this order. msg: "fee foo faa" ``` -#### Why do this ? +🔎 Why do this? A common order makes playbooks consistent and easier to read for your dear colleagues. Think of them when you write. @@ -315,13 +315,13 @@ Make usage of variables inside a task name to create dynamic output messages. state: '{{ state }}' become: true ``` -#### Why do this ? +🔎 Why do this? This will help to easily understand log outputs of playbooks. ### Omitting Unnecessary Information While name tasks in a playbook, do not include the name of the role which is currently executed, since Ansible will do this automatically. -#### Why do this ? +🔎 Why do this? Avoiding the same output twice on the console will prevent confusions. ## Variable names @@ -349,7 +349,7 @@ Avoiding the same output twice on the console will prevent confusions. A_STRING: bar ``` -#### Why do this ? +🔎 Why do this? Ansible already uses `snake_case` for variables in it's examples. Consistent naming of variables keeps the code tidy and gives better readability. @@ -489,7 +489,7 @@ Before using the `command` or `shell` module, verify if there is already a modul - command: "yum install httpd" ``` -#### Why do this ? +🔎 Why do this? While raw command could be seen as a security risk in general, another reason to avoid them is the loss of immutability of the ansible playbooks or roles. Ansible cannot verify if a command has been already executed before or not and will therefore execute it every time the playbook is running. ## 🔴 Spacing addons @@ -827,7 +827,7 @@ pipelining=true Follow [Sample Ansible setup](https://docs.ansible.com/ansible/latest/tips_tricks/sample_setup.html) and use Ansible Lint, see [documentation](https://ansible.readthedocs.io/projects/lint/). -#### Why do this ? +🔎 Why do this? The guys and girls who created Ansible have a good understanding how playbooks work and where files should reside. You'll avoid a lot of your own ingenious pitfalls following their best practices. From f6787b16b90d9fd504ebe21870126d91e6061c3b Mon Sep 17 00:00:00 2001 From: Valery Vetrov Date: Mon, 17 Jun 2024 15:45:59 +0300 Subject: [PATCH 05/12] Updated --- docs/ansible/coding-conventions.md | 62 ++++++++++++++++-------------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/docs/ansible/coding-conventions.md b/docs/ansible/coding-conventions.md index a5700f5..ced63c9 100644 --- a/docs/ansible/coding-conventions.md +++ b/docs/ansible/coding-conventions.md @@ -84,19 +84,19 @@ Please add comments above these lines with descriptions of what this playbook is become: true ``` -🔎 Why do this? - -This makes it quick to find out what the playbook does. +>**🔎 Why do this?** +> +>This makes it quick to find out what the playbook does. Either with opening the file or just using the `head` command. ## The end of the file Always end the file with a line shift. -🔎 Why do this? - -It's just Unix best practices. -It avoids messing up your prompt when you `cat` a file. +>**🔎 Why do this?** +> +>It's just Unix best practices. +>It avoids messing up your prompt when you `cat` a file. ## Spaces and alignment @@ -152,9 +152,10 @@ This is regardless of how many key/value pairs that exist in a map. ansible.builtin.service: name='{{ ntp_service }}' state=stopped enabled=false become: true ``` -🔎 Why do this? - -It's **soooo** much easier to read, and not more work to do. As the writer of this document is dyslectic, think of him and others in the same situation. In addition to the readability, it decreases the chance for a merge conflict. +>**🔎 Why do this?** +> +>It's **soooo** much easier to read, and not more work to do. +>As the writer of this document is dyslectic, think of him and others in the same situation In addition to the readability, it decreases the chance for a merge conflict. ### Whitespace and Comments @@ -167,9 +168,9 @@ Air, one of the **most important thing** for humans and **for code**! It must be an empty line before `vars`, `pre_tasks`, `roles` and `tasks`, and before each task in the tasks definition. Tabulator stops must be set to two, `2`, spaces. -🔎 Why do this? - -This creates a pretty and tidy code which is easy to read, both for dyslectic and non dyslectic people. There is no excuse not to do this. +>**🔎 Why do this?** +> +>This creates a pretty and tidy code which is easy to read, both for dyslectic and non dyslectic people. There is no excuse not to do this. @@ -214,9 +215,9 @@ Playbook definitions should follow this order. msg: "fee foo faa" ``` -🔎 Why do this? - -A common order makes playbooks consistent and easier to read for your dear colleagues. Think of them when you write. +>**🔎 Why do this?** +> +>A common order makes playbooks consistent and easier to read for your dear colleagues. Think of them when you write. ## Order in task declaration @@ -315,14 +316,16 @@ Make usage of variables inside a task name to create dynamic output messages. state: '{{ state }}' become: true ``` -🔎 Why do this? -This will help to easily understand log outputs of playbooks. +>**🔎 Why do this?** +> +>This will help to easily understand log outputs of playbooks. ### Omitting Unnecessary Information While name tasks in a playbook, do not include the name of the role which is currently executed, since Ansible will do this automatically. -🔎 Why do this? -Avoiding the same output twice on the console will prevent confusions. +>**🔎 Why do this?** +> +>Avoiding the same output twice on the console will prevent confusions. ## Variable names @@ -349,9 +352,9 @@ Avoiding the same output twice on the console will prevent confusions. A_STRING: bar ``` -🔎 Why do this? - -Ansible already uses `snake_case` for variables in it's examples. Consistent naming of variables keeps the code tidy and gives better readability. +>**🔎 Why do this?** +> +>Ansible already uses `snake_case` for variables in it's examples. Consistent naming of variables keeps the code tidy and gives better readability. ### The prefix should contain the name of the role. @@ -489,8 +492,9 @@ Before using the `command` or `shell` module, verify if there is already a modul - command: "yum install httpd" ``` -🔎 Why do this? -While raw command could be seen as a security risk in general, another reason to avoid them is the loss of immutability of the ansible playbooks or roles. Ansible cannot verify if a command has been already executed before or not and will therefore execute it every time the playbook is running. +>**🔎 Why do this?** +> +>While raw command could be seen as a security risk in general, another reason to avoid them is the loss of immutability of the ansible playbooks or roles. Ansible cannot verify if a command has been already executed before or not and will therefore execute it every time the playbook is running. ## 🔴 Spacing addons @@ -827,7 +831,7 @@ pipelining=true Follow [Sample Ansible setup](https://docs.ansible.com/ansible/latest/tips_tricks/sample_setup.html) and use Ansible Lint, see [documentation](https://ansible.readthedocs.io/projects/lint/). -🔎 Why do this? - -The guys and girls who created Ansible have a good understanding how playbooks work and where files should reside. -You'll avoid a lot of your own ingenious pitfalls following their best practices. +>**🔎 Why do this?** +> +>The guys and girls who created Ansible have a good understanding how playbooks work and where files should reside. +>You'll avoid a lot of your own ingenious pitfalls following their best practices. From 5a4f3dd4769becd37cf668516b6b102fec4694a5 Mon Sep 17 00:00:00 2001 From: Valery Vetrov Date: Mon, 17 Jun 2024 15:48:47 +0300 Subject: [PATCH 06/12] Updated TOC --- docs/ansible/coding-conventions.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/ansible/coding-conventions.md b/docs/ansible/coding-conventions.md index ced63c9..124e0b4 100644 --- a/docs/ansible/coding-conventions.md +++ b/docs/ansible/coding-conventions.md @@ -6,7 +6,6 @@ in this document are to be interpreted as described in [RFC 2119](https://datatr * [Coding style](#coding-style) * [Playbook File Extension](#playbook-file-extension) - * [So why doesn't my code look like Ansible examples?](#so-why-doesnt-my-code-look-like-ansible-examples) * [The beginning of a file](#the-beginning-of-a-file) * [The end of the file](#the-end-of-the-file) * [Spaces and alignment](#spaces-and-alignment) @@ -19,6 +18,7 @@ in this document are to be interpreted as described in [RFC 2119](https://datatr * [Names](#names) * [Always name tasks](#always-name-tasks) * [Format for task names](#format-for-task-names) + * [Why do this?](#why-do-this) * [Variables in Task Names](#variables-in-task-names) * [Omitting Unnecessary Information](#omitting-unnecessary-information) * [Variable names](#variable-names) @@ -34,20 +34,21 @@ in this document are to be interpreted as described in [RFC 2119](https://datatr * [Use Block-Module](#use-block-module) * [Use lists](#use-lists) * [Coding conventions](#coding-conventions) + * [Vaults](#vaults) * [Content Organization](#content-organization) * [Directory Layout](#directory-layout) - * [Alternative Directory Layout](#alternative-directory-layout) * [Group And Host Variables](#group-and-host-variables) * [Top Level Playbooks Are Separated By Role](#top-level-playbooks-are-separated-by-role) * [Bundling Ansible Modules With Playbooks](#bundling-ansible-modules-with-playbooks) * [Playbooks optimization](#playbooks-optimization) * [Import vs Include](#import-vs-include) + * [Tradeoffs and Pitfalls Between Includes and Imports](#tradeoffs-and-pitfalls-between-includes-and-imports) * [Optimize Playbook Execution](#optimize-playbook-execution) * [Use Module synchronize Instead of copy for Large Files](#use-module-synchronize-instead-of-copy-for-large-files) * [Configuration conventions](#configuration-conventions) * [Optimize SSH Connections](#optimize-ssh-connections) * [Enabling Pipelining](#enabling-pipelining) -* [Best practices](#best-practices) + * [Best practices](#best-practices) # Coding style From ef22117bf32bc73989af057fb2881bed6aa56f2f Mon Sep 17 00:00:00 2001 From: Valery Vetrov Date: Mon, 17 Jun 2024 15:51:49 +0300 Subject: [PATCH 07/12] Updated TOC --- docs/ansible/coding-conventions.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/ansible/coding-conventions.md b/docs/ansible/coding-conventions.md index 124e0b4..ed8bfde 100644 --- a/docs/ansible/coding-conventions.md +++ b/docs/ansible/coding-conventions.md @@ -4,6 +4,7 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S in this document are to be interpreted as described in [RFC 2119](https://datatracker.ietf.org/doc/html/rfc2119). +* [Justcoded Ansible Coding Style and Conventions](#justcoded-ansible-coding-style-and-conventions-) * [Coding style](#coding-style) * [Playbook File Extension](#playbook-file-extension) * [The beginning of a file](#the-beginning-of-a-file) @@ -18,7 +19,6 @@ in this document are to be interpreted as described in [RFC 2119](https://datatr * [Names](#names) * [Always name tasks](#always-name-tasks) * [Format for task names](#format-for-task-names) - * [Why do this?](#why-do-this) * [Variables in Task Names](#variables-in-task-names) * [Omitting Unnecessary Information](#omitting-unnecessary-information) * [Variable names](#variable-names) @@ -287,9 +287,9 @@ Always use the following format in task names: - name: 'Change status of httpd to {{ state }}' ``` -#### Why do this? - -It will always be clear what task was performed when analyzing the log. +>**🔎 Why do this?** +> +>It will always be clear what task was performed when analyzing the log. ### Variables in Task Names From fc47778a565b81b631206ae85dc6d87710ec3497 Mon Sep 17 00:00:00 2001 From: Valery Vetrov Date: Tue, 18 Jun 2024 11:05:30 +0300 Subject: [PATCH 08/12] Fixed many, added examples etc... --- docs/ansible/coding-conventions.md | 403 ++++++++++++++++++++++++++--- 1 file changed, 373 insertions(+), 30 deletions(-) diff --git a/docs/ansible/coding-conventions.md b/docs/ansible/coding-conventions.md index ed8bfde..84f3796 100644 --- a/docs/ansible/coding-conventions.md +++ b/docs/ansible/coding-conventions.md @@ -24,6 +24,7 @@ in this document are to be interpreted as described in [RFC 2119](https://datatr * [Variable names](#variable-names) * [Always use `snake_case` for variable names.](#always-use-snakecase-for-variable-names) * [The prefix should contain the name of the role.](#the-prefix-should-contain-the-name-of-the-role) + * [Variable precedence](#variable-precedence) * [Define temporary variables using unique prefix](#define-temporary-variables-using-unique-prefix) * [Define paths without trailing slash](#define-paths-without-trailing-slash) * [Boolean variables](#boolean-variables) @@ -36,10 +37,10 @@ in this document are to be interpreted as described in [RFC 2119](https://datatr * [Coding conventions](#coding-conventions) * [Vaults](#vaults) * [Content Organization](#content-organization) - * [Directory Layout](#directory-layout) - * [Group And Host Variables](#group-and-host-variables) - * [Top Level Playbooks Are Separated By Role](#top-level-playbooks-are-separated-by-role) - * [Bundling Ansible Modules With Playbooks](#bundling-ansible-modules-with-playbooks) + * [Directory Layout](#directory-layout) + * [Group And Host Variables](#group-and-host-variables) + * [Top Level Playbooks Are Separated By Role](#top-level-playbooks-are-separated-by-role) + * [Bundling Ansible Modules With Playbooks](#bundling-ansible-modules-with-playbooks) * [Playbooks optimization](#playbooks-optimization) * [Import vs Include](#import-vs-include) * [Tradeoffs and Pitfalls Between Includes and Imports](#tradeoffs-and-pitfalls-between-includes-and-imports) @@ -48,11 +49,17 @@ in this document are to be interpreted as described in [RFC 2119](https://datatr * [Configuration conventions](#configuration-conventions) * [Optimize SSH Connections](#optimize-ssh-connections) * [Enabling Pipelining](#enabling-pipelining) - * [Best practices](#best-practices) # Coding style +The main reason for this style guide is to have one consistent way of writing Ansible which is easy to read for you and others. + +First of all, follow [Sample Ansible setup](https://docs.ansible.com/ansible/latest/tips_tricks/sample_setup.html) and use Ansible Lint ... + +Use IDE plugins for autocomplete and validation, the best one is OrchidE (https://www.orchide.dev/) + + ## Playbook File Extension All Ansible Yaml files should have a **.yml** extension (and NOT **.YML**, **.yaml** etc). @@ -165,9 +172,213 @@ Generous use of whitespace to break things up, and use of comments (which start ### Air and tabulators -Air, one of the **most important thing** for humans and **for code**! -It must be an empty line before `vars`, `pre_tasks`, `roles` and `tasks`, and before each task in the tasks definition. -Tabulator stops must be set to two, `2`, spaces. +* Air, one of the **most important thing** for humans and **for code**! +* It must be an empty line before `vars`, `pre_tasks`, `roles` and `tasks`, +* and before each task in the tasks definition. +* Tabulator stops must be set to two, `2`, spaces. + +✅ ***Good*** + +```yaml +--- +- name: Install and Launch the Simple NodeJS Application + hosts: testserver + + vars_files: + - gitsecrets.yml + + vars: + - destdir: /apps/sampleapp + + pre_tasks: + - name : install dependencies before starting + become: yes + register: aptinstall + apt: + name: + - nodejs + - npm + - git + state: latest + update_cache: yes + + - name : validate the nodejs installation + debug: msg="Installation of node is Successfull" + when: aptinstall is changed + + tasks: + - name: Version of Node and NPM + shell: + "npm -v && nodejs -v" + register: versioninfo + - name: Validate if the installation is intact + assert: + that: "versioninfo is changed" + + - name: Create Dest Directory if not exists + become: yes + file: + path: "{{ destdir }}" + state: directory + owner: vagrant + group: vagrant + mode: 0755 + + - name: Download the NodeJS code from the GitRepo + become: yes + git: + repo: 'https://{{gittoken}}@github.com/AKSarav/SampleNodeApp.git' + dest: "{{ destdir }}" + + - name: Change the ownership of the directory + become: yes + file: + path: "{{destdir}}" + owner: "vagrant" + register: chgrpout + + - name: Install Dependencies with NPM install command + shell: + "npm install" + args: + chdir: "{{ destdir }}" + register: npminstlout + - name: Debug npm install command + debug: msg='{{npminstlout.stdout_lines}}' + - name: Start the App + async: 10 + poll: 0 + shell: + "(node index.js > nodesrv.log 2>&1 &)" + args: + chdir: "{{ destdir }}" + register: appstart + + - name: Validating the port is open + tags: nodevalidate + wait_for: + host: "localhost" + port: 5000 + delay: 10 + timeout: 30 + state: started + msg: "NodeJS server is not running" + + post_tasks: + - name: notify Slack that the servers have been updated + tags: slack + community.general.slack: + token: T026******PF/B02U*****N/WOa7r**********Ao0jnWn + msg: | + ### StatusUpdate ### + – ------------------------------------ + `` + `Server`: {{ansible_host}} + `Status`: NodeJS Sample Application installed successfully + – ------------------------------------ + channel: '#ansible' + color: good + username: 'Ansible on {{ inventory_hostname }}' + link_names: 0 + parse: 'none' + delegate_to: localhost +``` + +❌ ***Bad*** + +```yaml +--- +- name: Install and Launch the Simple NodeJS Application + hosts: testserver + vars_files: + - gitsecrets.yml + vars: + - destdir: /apps/sampleapp + pre_tasks: + - name : install dependencies before starting + become: yes + register: aptinstall + apt: + name: + - nodejs + - npm + - git + state: latest + update_cache: yes + - name : validate the nodejs installation + debug: msg="Installation of node is Successfull" + when: aptinstall is changed + tasks: + - name: Version of Node and NPM + shell: + "npm -v && nodejs -v" + register: versioninfo + - name: Validate if the installation is intact + assert: + that: "versioninfo is changed" + - name: Create Dest Directory if not exists + become: yes + file: + path: "{{ destdir }}" + state: directory + owner: vagrant + group: vagrant + mode: 0755 + - name: Download the NodeJS code from the GitRepo + become: yes + git: + repo: 'https://{{gittoken}}@github.com/AKSarav/SampleNodeApp.git' + dest: "{{ destdir }}" + - name: Change the ownership of the directory + become: yes + file: + path: "{{destdir}}" + owner: "vagrant" + register: chgrpout + - name: Install Dependencies with NPM install command + shell: + "npm install" + args: + chdir: "{{ destdir }}" + register: npminstlout + - name: Debug npm install command + debug: msg='{{npminstlout.stdout_lines}}' + - name: Start the App + async: 10 + poll: 0 + shell: + "(node index.js > nodesrv.log 2>&1 &)" + args: + chdir: "{{ destdir }}" + register: appstart + - name: Validating the port is open + tags: nodevalidate + wait_for: + host: "localhost" + port: 5000 + delay: 10 + timeout: 30 + state: started + msg: "NodeJS server is not running" + post_tasks: + - name: notify Slack that the servers have been updated + tags: slack + community.general.slack: + token: T026******PF/B02U*****N/WOa7r**********Ao0jnWn + msg: | + ### StatusUpdate ### + – ------------------------------------ + `` + `Server`: {{ansible_host}} + `Status`: NodeJS Sample Application installed successfully + – ------------------------------------ + channel: '#ansible' + color: good + username: 'Ansible on {{ inventory_hostname }}' + link_names: 0 + parse: 'none' + delegate_to: localhost +``` >**🔎 Why do this?** > @@ -188,6 +399,7 @@ Playbook definitions should follow this order. * `roles` * `tasks` +✅ ***Good*** ```yaml --- - name: update root authorized_keys for all machines @@ -226,15 +438,26 @@ A task should be declared in this order. * `name` * module and the arguments for the module -* arguments for the task in alphabetic order +* arguments for the task in correct order * `tags` at the bottom +✅ ***Good*** ```yaml --- - name: unhold packages ansible.builtin.shell: 'apt-mark -s unhold {{ item }} && apt-mark unhold {{ item }}' args: + #chdir: # change dir before execution + #cmd: # The command to run followed by optional arguments. + #creates: # A filename, when it already exists, this step will not be run. executable: /bin/bash + #free_form: # The shell module takes a free form command to run, as a string. + # There is no actual parameter named ‘free form’. + # See the documentation on how to use. + #removes: # A filename, when it does not exist, this step will not be run. + #stdin: # Set the stdin of the command directly to the specified value. + #stdin_add_newline: + # Whether to append a newline to stdin data. (true / false) changed_when: apt_mark_unhold.rc == 0 and "already" not in apt_mark_unhold.stdout failed_when: false loop: '{{ packages_ubuntu_unhold }}' @@ -254,13 +477,11 @@ All the newly created Ansible roles should follow the name convention using dash ✅ ***Good*** ```yaml -# good jc-setup-lvm ``` ❌ ***Bad*** ```yaml -# bad lvm ``` @@ -381,6 +602,30 @@ While name tasks in a playbook, do not include the name of the role which is cur MY_STRING: 'test' ``` +### Variable precedence + +Follow this [guide](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_variables.html#understanding-variable-precedence) when defining the variable scope. + +You should set defaults in roles to avoid undefined-variable errors. + +✅ ***Good*** +```yaml +--- +# file: roles/x/defaults/main.yml +# if no other value is supplied in inventory or as a parameter, this value will be used +http_port: 80 +``` + +Then override those values at the required level or at the command line. + +✅ ***Good*** +```yaml +--- +# file: roles/x/vars/main.yml +# this will absolutely be used in this role +http_port: 8080 +``` + ### Define temporary variables using unique prefix Registered variables and facts set using set_fact module are often defined for temporary usage. @@ -388,6 +633,8 @@ In order to avoid variable collision and make it clear to the reader that these it is good practice to use a unique prefix. You could use r_ for registered variables and f_ for facts. +✅ ***Good*** + ```yaml - name: Collect information from external system uri: @@ -476,7 +723,6 @@ Before using the `command` or `shell` module, verify if there is already a modul ✅ ***Good*** ```yaml -# good - name: install packages tasks: - name: 'install httpd' @@ -487,7 +733,6 @@ Before using the `command` or `shell` module, verify if there is already a modul ❌ ***Bad*** ```yaml -# bad - name: install httpd tasks: - command: "yum install httpd" @@ -504,8 +749,46 @@ Before using the `command` or `shell` module, verify if there is already a modul The ‘state’ parameter is optional to a lot of modules. Whether ‘state=present’ or ‘state=absent’, it’s always best to leave that parameter in your playbooks to make it clear, especially as some modules support additional states. +✅ ***Good*** + +```yaml +- name: Example Simple Playbook + hosts: all + become: yes + + tasks: + - name: Copy file example_file to /tmp with permissions + ansible.builtin.copy: + src: ./example_file + dest: /tmp/example_file + mode: '0644' + + - name: Add the user 'bob' with a specific uid + ansible.builtin.user: + name: bob + state: present + uid: 1040 + +- name: Update postgres servers + hosts: databases + become: yes + + tasks: + - name: Ensure postgres DB is at the latest version + ansible.builtin.yum: + name: postgresql + state: latest + + - name: Ensure that postgresql is started + ansible.builtin.service: + name: postgresql + state: started +``` + ## Use Block-Module +✅ ***Good*** + Block can help to organize the code and can enable rollbacks. ```yaml - block: @@ -525,6 +808,8 @@ which also makes it much easier to set data or directives common to the tasks. T but is inherited by the tasks enclosed by a block. i.e. a when will be applied to the tasks, not the block itself. +✅ ***Good*** + ```yaml tasks: - name: Install, configure, and start Apache @@ -594,13 +879,9 @@ All Ansible Vault files should have a **.vault** extension (and NOT **.yml**, ** ## Content Organization -Your usage of Ansible should fit your needs, however, not ours, so feel free to modify this approach and organize as you see fit. - Always try to use Ansible’s “roles” to organize your playbook content organization feature. - - -## Directory Layout +### Directory Layout The top level of the directory should contain files and directories like so: This is the required preset for recognizing playbooks, tasks and variables by [OrchidE plugin](https://www.orchide.dev/pages/dokumentation). @@ -621,12 +902,16 @@ This is the required preset for recognizing playbooks, tasks and variables by [O | | | |-- main.yml ``` -## Group And Host Variables +Take care, that file structure for some roles can be completely generated with ansible galaxy. + +### Group And Host Variables Groups are nice for organization, but that’s not all groups are good for. You can also assign variables to them! For instance, atlanta has its own NTP servers, so when setting up ntp.conf, we should use them. Let’s set those now: +✅ ***Good*** + ```yaml --- # file: group_vars/atlanta @@ -636,6 +921,8 @@ backup: backup-atlanta.example.com Variables aren’t just for geographic information either! Maybe the webservers have some configuration that doesn’t make sense for the database servers: +✅ ***Good*** + ```yaml --- # file: group_vars/webservers @@ -645,6 +932,8 @@ apacheMaxClients: 900 If we had any default values, or values that were universally true, we would put them in a file called group_vars/all: +✅ ***Good*** + ```yaml --- # file: group_vars/all @@ -654,6 +943,8 @@ backup: backup-boston.example.com We can define specific hardware variance in systems in a host_vars file, but avoid doing this unless you need to: +✅ ***Good*** + ```yaml --- # file: host_vars/db-bos-1.example.com @@ -664,10 +955,12 @@ bar_agent_port: 99 Again, if we are using dynamic inventory sources, many dynamic groups are automatically created. So a tag like “class:webserver” would load in variables from the file “group_vars/ec2_tag_class_webserver” automatically. -## Top Level Playbooks Are Separated By Role +### Top Level Playbooks Are Separated By Role In site.yml, we import a playbook that defines our entire infrastructure. This is a very short example, because it’s just importing some other playbooks: +✅ ***Good*** + ```yaml --- # file: site.yml @@ -677,6 +970,8 @@ In site.yml, we import a playbook that defines our entire infrastructure. This i In a file like webservers.yml (also at the top level), we map the configuration of the webservers group to the roles performed by the webservers group: +✅ ***Good*** + ```yaml --- # file: webservers.yml @@ -695,7 +990,7 @@ ansible-playbook webservers.yml ``` -## Bundling Ansible Modules With Playbooks +### Bundling Ansible Modules With Playbooks If a playbook has a ./library directory relative to its YAML file, this directory can be used to add ansible modules that will automatically be in the ansible module path. This is a great way to keep modules that go with a playbook together. @@ -744,6 +1039,59 @@ A major difference between `import` and `include` tasks: **Note:** Regarding the use of `notify` for dynamic tasks: it is still possible to trigger the dynamic include itself, which would result in all tasks within the include being run. +Here is a simple example on the ebove: + +✅ ***Good*** + +```yaml +--- +# main.yml + +- name: Include tasks + include_tasks: include_task.yml + # args: + # apply: + # tags: task + tags: task + +- name: Import tasks + import_tasks: import_task.yml + tags: task + +- name: Main task + debug: + msg: The main task + tags: main +``` +```yaml +--- +#include_task.yml: + +- name: Subtask3 + debug: + msg: Subtask3 include + tags: task1 + +- name: Subtask4 + debug: + msg: Subtask4 include + tags: task2 +``` +```yaml +--- +#import_task.yml: + +- name: Subtask1 + debug: + msg: Subtask1 import + tags: task1 + +- name: Subtask2 + debug: + msg: Subtask2 import + tags: task2 +``` + ### Optimize Playbook Execution Disable facts gathering if it is not required. @@ -807,6 +1155,8 @@ in `ansible.cfg` set * ControlPersist * PreferredAuthentications +✅ ***Good*** + ```text [ssh_connection] ... @@ -819,6 +1169,8 @@ ssh_args=-C -o ControlMaster=auto -o ControlPersist=1200s -o BatchMode=yes -o Pr reduces number of SSH operations +✅ ***Good*** + ```text [ssh_connection] ... @@ -827,12 +1179,3 @@ pipelining=true ``` **Info:** this requires to disable `requiretty` in sudo options - -## Best practices - -Follow [Sample Ansible setup](https://docs.ansible.com/ansible/latest/tips_tricks/sample_setup.html) and use Ansible Lint, see [documentation](https://ansible.readthedocs.io/projects/lint/). - ->**🔎 Why do this?** -> ->The guys and girls who created Ansible have a good understanding how playbooks work and where files should reside. ->You'll avoid a lot of your own ingenious pitfalls following their best practices. From 9548a13c6aeed70a3fdb4c982de2709566d2aacc Mon Sep 17 00:00:00 2001 From: Valery Vetrov Date: Tue, 18 Jun 2024 14:17:52 +0300 Subject: [PATCH 09/12] Removed already added todo like heading --- docs/ansible/coding-conventions.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/ansible/coding-conventions.md b/docs/ansible/coding-conventions.md index 84f3796..2e5a9df 100644 --- a/docs/ansible/coding-conventions.md +++ b/docs/ansible/coding-conventions.md @@ -30,7 +30,6 @@ in this document are to be interpreted as described in [RFC 2119](https://datatr * [Boolean variables](#boolean-variables) * [Comparing](#comparing) * [Use Modules instead of command or shell](#use-modules-instead-of-command-or-shell) - * [🔴 Spacing addons](#-spacing-addons) * [Always Mention The State](#always-mention-the-state) * [Use Block-Module](#use-block-module) * [Use lists](#use-lists) @@ -742,8 +741,6 @@ Before using the `command` or `shell` module, verify if there is already a modul > >While raw command could be seen as a security risk in general, another reason to avoid them is the loss of immutability of the ansible playbooks or roles. Ansible cannot verify if a command has been already executed before or not and will therefore execute it every time the playbook is running. -## 🔴 Spacing addons - ## Always Mention The State The ‘state’ parameter is optional to a lot of modules. From 138ba1510336ba0bf029c0defe0f2920a935b2ba Mon Sep 17 00:00:00 2001 From: Alex Prokopenko Date: Tue, 18 Jun 2024 18:58:25 +0300 Subject: [PATCH 10/12] Update coding-conventions.md --- docs/ansible/coding-conventions.md | 98 +++--------------------------- 1 file changed, 8 insertions(+), 90 deletions(-) diff --git a/docs/ansible/coding-conventions.md b/docs/ansible/coding-conventions.md index 2e5a9df..734e409 100644 --- a/docs/ansible/coding-conventions.md +++ b/docs/ansible/coding-conventions.md @@ -171,10 +171,14 @@ Generous use of whitespace to break things up, and use of comments (which start ### Air and tabulators -* Air, one of the **most important thing** for humans and **for code**! -* It must be an empty line before `vars`, `pre_tasks`, `roles` and `tasks`, -* and before each task in the tasks definition. -* Tabulator stops must be set to two, `2`, spaces. +:memo: Tabulator stops must be set to two, `2`, spaces. + +:memo: Air, one of the **most important thing** for humans and **for code**! + +It must be an empty line: +* before `vars`, `pre_tasks`, `roles` and `tasks`, +* before each task in the tasks definition. + ✅ ***Good*** @@ -901,92 +905,6 @@ This is the required preset for recognizing playbooks, tasks and variables by [O Take care, that file structure for some roles can be completely generated with ansible galaxy. -### Group And Host Variables - -Groups are nice for organization, but that’s not all groups are good for. -You can also assign variables to them! For instance, atlanta has its own NTP servers, so when setting up ntp.conf, we should use them. -Let’s set those now: - -✅ ***Good*** - -```yaml ---- -# file: group_vars/atlanta -ntp: ntp-atlanta.example.com -backup: backup-atlanta.example.com -``` - -Variables aren’t just for geographic information either! Maybe the webservers have some configuration that doesn’t make sense for the database servers: - -✅ ***Good*** - -```yaml ---- -# file: group_vars/webservers -apacheMaxRequestsPerChild: 3000 -apacheMaxClients: 900 -``` - -If we had any default values, or values that were universally true, we would put them in a file called group_vars/all: - -✅ ***Good*** - -```yaml ---- -# file: group_vars/all -ntp: ntp-boston.example.com -backup: backup-boston.example.com -``` - -We can define specific hardware variance in systems in a host_vars file, but avoid doing this unless you need to: - -✅ ***Good*** - -```yaml ---- -# file: host_vars/db-bos-1.example.com -foo_agent_port: 86 -bar_agent_port: 99 -``` - -Again, if we are using dynamic inventory sources, many dynamic groups are automatically created. -So a tag like “class:webserver” would load in variables from the file “group_vars/ec2_tag_class_webserver” automatically. - -### Top Level Playbooks Are Separated By Role - -In site.yml, we import a playbook that defines our entire infrastructure. This is a very short example, because it’s just importing some other playbooks: - -✅ ***Good*** - -```yaml ---- -# file: site.yml -- import_playbook: webservers.yml -- import_playbook: dbservers.yml -``` - -In a file like webservers.yml (also at the top level), we map the configuration of the webservers group to the roles performed by the webservers group: - -✅ ***Good*** - -```yaml ---- -# file: webservers.yml -- hosts: webservers - roles: - - common - - webtier -``` - -The idea here is that we can choose to configure our whole infrastructure by “running” site.yml or we could just choose to run a subset by running webservers.yml. -This is analogous to the “–limit” parameter to ansible but a little more explicit: - -```yaml -ansible-playbook site.yml --limit webservers -ansible-playbook webservers.yml -``` - - ### Bundling Ansible Modules With Playbooks If a playbook has a ./library directory relative to its YAML file, this directory can be used to add ansible modules that will automatically be in the ansible module path. From 14e688828f670b921411e73d7349bc6c7d9beb33 Mon Sep 17 00:00:00 2001 From: Valery Vetrov Date: Wed, 19 Jun 2024 12:36:18 +0300 Subject: [PATCH 11/12] Took care about all suggestions --- docs/ansible/coding-conventions.md | 109 +++++++++++++++-------------- 1 file changed, 58 insertions(+), 51 deletions(-) diff --git a/docs/ansible/coding-conventions.md b/docs/ansible/coding-conventions.md index 2e5a9df..70721be 100644 --- a/docs/ansible/coding-conventions.md +++ b/docs/ansible/coding-conventions.md @@ -6,7 +6,9 @@ in this document are to be interpreted as described in [RFC 2119](https://datatr * [Justcoded Ansible Coding Style and Conventions](#justcoded-ansible-coding-style-and-conventions-) * [Coding style](#coding-style) - * [Playbook File Extension](#playbook-file-extension) + * [File extensions](#file-extensions) + * [Playbook File Extension](#playbook-file-extension) + * [Vault File Extension](#vault-file-extension) * [The beginning of a file](#the-beginning-of-a-file) * [The end of the file](#the-end-of-the-file) * [Spaces and alignment](#spaces-and-alignment) @@ -15,15 +17,15 @@ in this document are to be interpreted as described in [RFC 2119](https://datatr * [Air and tabulators](#air-and-tabulators) * [Order in playbook](#order-in-playbook) * [Order in task declaration](#order-in-task-declaration) + * [Role names](#role-names) * [Task names](#task-names) - * [Names](#names) * [Always name tasks](#always-name-tasks) * [Format for task names](#format-for-task-names) * [Variables in Task Names](#variables-in-task-names) * [Omitting Unnecessary Information](#omitting-unnecessary-information) * [Variable names](#variable-names) * [Always use `snake_case` for variable names.](#always-use-snakecase-for-variable-names) - * [The prefix should contain the name of the role.](#the-prefix-should-contain-the-name-of-the-role) + * [The prefix should contain the name of the role or if it is longer than few chars - abbreviation of role name or first letter of it.](#the-prefix-should-contain-the-name-of-the-role-or-if-it-is-longer-than-few-chars---abbreviation-of-role-name-or-first-letter-of-it) * [Variable precedence](#variable-precedence) * [Define temporary variables using unique prefix](#define-temporary-variables-using-unique-prefix) * [Define paths without trailing slash](#define-paths-without-trailing-slash) @@ -34,7 +36,6 @@ in this document are to be interpreted as described in [RFC 2119](https://datatr * [Use Block-Module](#use-block-module) * [Use lists](#use-lists) * [Coding conventions](#coding-conventions) - * [Vaults](#vaults) * [Content Organization](#content-organization) * [Directory Layout](#directory-layout) * [Group And Host Variables](#group-and-host-variables) @@ -59,10 +60,16 @@ First of all, follow [Sample Ansible setup](https://docs.ansible.com/ansible/lat Use IDE plugins for autocomplete and validation, the best one is OrchidE (https://www.orchide.dev/) -## Playbook File Extension +## File extensions + +### Playbook File Extension All Ansible Yaml files should have a **.yml** extension (and NOT **.YML**, **.yaml** etc). +### Vault File Extension + +All Ansible Vault files should have a **.vault** extension (and NOT **.yml**, **.YML**, **.yaml** etc). + ## The beginning of a file Always start your file with the YAML heading which is `---`. @@ -74,7 +81,7 @@ Please add comments above these lines with descriptions of what this playbook is # This playbook playbook changes state of user foo # Example: ansible-playbook -e state=stopped playbook.yml --- -- name: Change status of user foo +- name: main | Change status of user foo ansible.builtin.service: enabled: true name: foo @@ -109,13 +116,13 @@ Always end the file with a line shift. ### Key/value pairs -Only use on space after colon when you define key value pair. +Only use one space after colon when you define key value pair. ✅ ***Good*** ```yaml --- -- name: start chrony NTP daemon +- name: main | start chrony NTP daemon ansible.builtin.service: name: chrony state: started @@ -142,7 +149,7 @@ This is regardless of how many key/value pairs that exist in a map. ```yaml --- -- name: disable ntpd +- name: main | disable ntpd ansible.builtin.service: name: '{{ ntp_service }}' state: stopped @@ -180,7 +187,7 @@ Generous use of whitespace to break things up, and use of comments (which start ```yaml --- -- name: Install and Launch the Simple NodeJS Application +- name: main | Install and Launch the Simple NodeJS Application hosts: testserver vars_files: @@ -401,7 +408,7 @@ Playbook definitions should follow this order. ✅ ***Good*** ```yaml --- -- name: update root authorized_keys for all machines +- name: main | update root authorized_keys for all machines hosts: - all become: true @@ -443,7 +450,7 @@ A task should be declared in this order. ✅ ***Good*** ```yaml --- -- name: unhold packages +- name: main | unhold packages ansible.builtin.shell: 'apt-mark -s unhold {{ item }} && apt-mark unhold {{ item }}' args: #chdir: # change dir before execution @@ -465,10 +472,7 @@ A task should be declared in this order. - apt_unhold --- ``` - -## Task names - -### Names +## Role names All the newly created Ansible roles should follow the name convention using dashes if necessary: `--[function/technology]` @@ -484,13 +488,18 @@ jc-setup-lvm lvm ``` +## Task names + ### Always name tasks It is possible to leave off the ‘name’ for a given task, though it is recommended to provide a description about why something is being done instead. This name is shown when the playbook is run. + ### Format for task names +**🔴 TODO:** Add the file name conventions + Always use the following format in task names: ` | ` @@ -498,7 +507,7 @@ Always use the following format in task names: ✅ ***Good*** ```yaml -- name: 'SwitchHttpdStatus | Change status of httpd to {{ state }}' +- name: 'switch_httpd_status | Change status of httpd to {{ state }}' ``` ❌ ***Bad*** @@ -519,7 +528,7 @@ Make usage of variables inside a task name to create dynamic output messages. ✅ ***Good*** ```yaml -- name: 'Change status of httpd to {{ state }}' +- name: 'main | Change status of httpd to {{ state }}' service: enabled: true name: 'httpd' @@ -556,7 +565,7 @@ While name tasks in a playbook, do not include the name of the role which is cur ```yaml --- -- name: set my variables +- name: main | set my variables ansible.builtin.set_fact: a_boolean: false an_int: 101 @@ -577,19 +586,21 @@ While name tasks in a playbook, do not include the name of the role which is cur > >Ansible already uses `snake_case` for variables in it's examples. Consistent naming of variables keeps the code tidy and gives better readability. -### The prefix should contain the name of the role. +### The prefix should contain the name of the role or if it is longer than few chars - abbreviation of role name or first letter of it. ✅ ***Good*** ```yaml --- -- name: 'set some facts' +- name: 'main | set some facts' set_fact: - rolename_my_boolean: true - rolename_my_int: 20 - rolename_my_string: 'test' + # in a role named test_role + tr_my_boolean: true + tr_my_int: 20 + tr_my_string: 'test' ``` + ❌ ***Bad*** ```yaml @@ -630,12 +641,12 @@ http_port: 8080 Registered variables and facts set using set_fact module are often defined for temporary usage. In order to avoid variable collision and make it clear to the reader that these variables are only for temporary usage, it is good practice to use a unique prefix. -You could use r_ for registered variables and f_ for facts. +You could use `r_` for registered variables and `f_` for facts. ✅ ***Good*** ```yaml -- name: Collect information from external system +- name: main | Collect information from external system uri: url: http://www.example.com return_content: yes @@ -673,7 +684,7 @@ Always use **false** and **true** values for boolean. ✅ ***Good*** ```yaml --- -- name: start chrony NTP daemon +- name: main | start chrony NTP daemon ansible.builtin.service: name: chrony state: started @@ -722,7 +733,7 @@ Before using the `command` or `shell` module, verify if there is already a modul ✅ ***Good*** ```yaml -- name: install packages +- name: main | install packages tasks: - name: 'install httpd' yum: @@ -749,7 +760,7 @@ Whether ‘state=present’ or ‘state=absent’, it’s always best to leave t ✅ ***Good*** ```yaml -- name: Example Simple Playbook +- name: main | Example Simple Playbook hosts: all become: yes @@ -766,7 +777,7 @@ Whether ‘state=present’ or ‘state=absent’, it’s always best to leave t state: present uid: 1040 -- name: Update postgres servers +- name: main | Update postgres servers hosts: databases become: yes @@ -839,17 +850,17 @@ Use lists when ever a modules support it (i.e. yum): ✅ ***Good*** ```yaml tasks: -- name: Ensure the packages are installed +- name: main | Ensure the packages are installed yum: - state: present - name: - - httpd - - mod_ssl - - httpd-tools - - mariadb-server - - mariadb - - php - - php-mysqlnd + state: present + name: + - httpd + - mod_ssl + - httpd-tools + - mariadb-server + - mariadb + - php + - php-mysqlnd ``` ***Bad*** @@ -870,10 +881,6 @@ tasks: ``` # Coding conventions -## Vaults - -All Ansible Vault files should have a **.vault** extension (and NOT **.yml**, **.YML**, **.yaml** etc). - ## Content Organization Always try to use Ansible’s “roles” to organize your playbook content organization feature. @@ -1044,18 +1051,18 @@ Here is a simple example on the ebove: --- # main.yml -- name: Include tasks +- name: main | Include tasks include_tasks: include_task.yml # args: # apply: # tags: task tags: task -- name: Import tasks +- name: main | Import tasks import_tasks: import_task.yml tags: task -- name: Main task +- name: main | Main task debug: msg: The main task tags: main @@ -1064,12 +1071,12 @@ Here is a simple example on the ebove: --- #include_task.yml: -- name: Subtask3 +- name: main | Subtask3 debug: msg: Subtask3 include tags: task1 -- name: Subtask4 +- name: main | Subtask4 debug: msg: Subtask4 include tags: task2 @@ -1078,12 +1085,12 @@ Here is a simple example on the ebove: --- #import_task.yml: -- name: Subtask1 +- name: main | Subtask1 debug: msg: Subtask1 import tags: task1 -- name: Subtask2 +- name: main | Subtask2 debug: msg: Subtask2 import tags: task2 From 63579a906582bc07a36b8a9fee50ba735f45c489 Mon Sep 17 00:00:00 2001 From: Alex Prokopenko Date: Wed, 19 Jun 2024 13:54:02 +0300 Subject: [PATCH 12/12] Update coding-conventions.md --- docs/ansible/coding-conventions.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/ansible/coding-conventions.md b/docs/ansible/coding-conventions.md index e8e36b6..c2d7bcf 100644 --- a/docs/ansible/coding-conventions.md +++ b/docs/ansible/coding-conventions.md @@ -479,12 +479,12 @@ A task should be declared in this order. ## Role names All the newly created Ansible roles should follow the name convention using dashes if necessary: -`--[function/technology]` +`_[_function/technology]` ✅ ***Good*** ```yaml -jc-setup-lvm +jc_setup_lvm ``` ❌ ***Bad*** @@ -494,16 +494,21 @@ lvm ## Task names +### File names + +**🔴 TODO:** Add the file name conventions + +Draft idea: using snake case, like it's shown in examples of official ansible documentation (may be find in examples of using import_tasks/include_tasks) + + ### Always name tasks -It is possible to leave off the ‘name’ for a given task, though it is recommended to provide a description about why something is being done instead. +It is possible to leave off the `name` for a given task, though it is recommended to provide a description about why something is being done instead. This name is shown when the playbook is run. ### Format for task names -**🔴 TODO:** Add the file name conventions - Always use the following format in task names: ` | ` @@ -867,7 +872,7 @@ tasks: - php-mysqlnd ``` -***Bad*** +❌ ***Bad*** ```yaml tasks: - name: Ensure the packages are installed @@ -1015,6 +1020,9 @@ Here is a simple example on the ebove: ``` ### Optimize Playbook Execution + +**🔴 TODO:** Update all items here with examples if they are appropriate + Disable facts gathering if it is not required. Try not to use: ansible_facts[‘hostname’] (or ‘nodename’)