summaryrefslogtreecommitdiffstats
path: root/README.rst
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-14 20:04:50 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-14 20:04:50 +0000
commit782f8df6e41f29dce2db4970a3ad84aaeb7d8c5f (patch)
tree3a88a542cd8074743d251881131510157cfc510b /README.rst
parentInitial commit. (diff)
downloadansible-lint-782f8df6e41f29dce2db4970a3ad84aaeb7d8c5f.tar.xz
ansible-lint-782f8df6e41f29dce2db4970a3ad84aaeb7d8c5f.zip
Adding upstream version 4.3.7.upstream/4.3.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--README.rst640
1 files changed, 640 insertions, 0 deletions
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..0e061fa
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,640 @@
+.. image:: https://img.shields.io/pypi/v/ansible-lint.svg
+ :target: https://pypi.org/project/ansible-lint
+ :alt: PyPI version
+
+.. image:: https://img.shields.io/badge/Ansible--lint-rules%20table-blue.svg
+ :target: https://ansible-lint.readthedocs.io/en/latest/default_rules.html
+ :alt: Ansible-lint rules explanation
+
+.. image:: https://img.shields.io/badge/Code%20of%20Conduct-black.svg
+ :target: https://docs.ansible.com/ansible/latest/community/code_of_conduct.html
+ :alt: Ansible Code of Conduct
+
+.. image:: https://img.shields.io/badge/Mailing%20lists-Ansible-orange.svg
+ :target: https://docs.ansible.com/ansible/latest/community/communication.html#mailing-list-information
+ :alt: Ansible mailing lists
+
+.. image:: https://github.com/ansible/ansible-lint/workflows/gh/badge.svg
+ :target: https://github.com/ansible/ansible-lint/actions?query=workflow%3Agh+branch%3Amaster+event%3Apush
+ :alt: GitHub Actions CI/CD
+
+.. image:: https://img.shields.io/lgtm/grade/python/g/ansible/ansible-lint.svg?logo=lgtm&logoWidth=18
+ :target: https://lgtm.com/projects/g/ansible/ansible-lint/context:python
+ :alt: Language grade: Python
+
+.. image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white
+ :target: https://github.com/pre-commit/pre-commit
+ :alt: pre-commit
+
+
+Ansible-lint
+============
+
+``ansible-lint`` checks playbooks for practices and behaviour that could
+potentially be improved. As a community backed project ansible-lint supports
+only the last two major versions of Ansible.
+
+`Visit the Ansible Lint docs site <https://ansible-lint.readthedocs.io/en/latest/>`_
+
+Installing
+==========
+
+.. installing-docs-inclusion-marker-do-not-remove
+
+Installing on Windows is not supported because we use symlinks inside Python packages.
+
+Using Pip
+---------
+
+.. code-block:: bash
+
+ pip install ansible-lint
+
+.. _installing_from_source:
+
+From Source
+-----------
+
+**Note**: pip 19.0+ is required for installation. Please consult with the `PyPA User Guide`_
+to learn more about managing Pip versions.
+
+.. code-block:: bash
+
+ pip install git+https://github.com/ansible/ansible-lint.git
+
+.. _PyPA User Guide: https://packaging.python.org/tutorials/installing-packages/#ensure-pip-setuptools-and-wheel-are-up-to-date
+
+.. installing-docs-inclusion-marker-end-do-not-remove
+
+Usage
+=====
+
+.. usage-docs-inclusion-marker-do-not-remove
+
+Command Line Options
+--------------------
+
+The following is the output from ``ansible-lint --help``, providing an overview of the basic command line options:
+
+.. code-block::
+
+ usage: ansible-lint [-h] [-L] [-f {rich,plain,rst}] [-q] [-p] [--parseable-severity] [-r RULESDIR]
+ [-R] [--show-relpath] [-t TAGS] [-T] [-v] [-x SKIP_LIST]
+ [-w WARN_LIST [WARN_LIST ...]] [--nocolor] [--force-color]
+ [--exclude EXCLUDE_PATHS] [-c CONFIG_FILE] [--version]
+ [playbook [playbook ...]]
+
+ positional arguments:
+ playbook One or more files or paths. When missing it will enable auto-detection mode.
+
+ optional arguments:
+ -h, --help show this help message and exit
+ -L list all the rules
+ -f {rich,plain,rst} Format used rules output, (default: rich)
+ -q quieter, although not silent output
+ -p parseable output in the format of pep8
+ --parseable-severity parseable output including severity of rule
+ --progressive Return success if it detects a reduction in number of violations compared with
+ previous git commit. This feature works only on git repository clones.
+ -r RULESDIR Specify custom rule directories. Add -R to keep using embedded rules from
+ /usr/local/lib/python3.8/site-packages/ansiblelint/rules
+ -R Keep default rules when using -r
+ --show-relpath Display path relative to CWD
+ -t TAGS only check rules whose id/tags match these values
+ -T list all the tags
+ -v Increase verbosity level
+ -x SKIP_LIST only check rules whose id/tags do not match these values
+ -w WARN_LIST [WARN_LIST ...]
+ only warn about these rules
+ --nocolor disable colored output
+ --force-color Try force colored output (relying on ansible's code)
+ --exclude EXCLUDE_PATHS
+ path to directories or files to skip. This option is repeatable.
+ -c CONFIG_FILE Specify configuration file to use. Defaults to ".ansible-lint"
+ --version show program's version number and exit
+
+Progressive mode
+----------------
+
+In order to ease tool adoption, git users can enable the progressive mode using
+``--progressive`` option. This makes the linter return a success even if
+some failures are found, as long the total number of violations did not
+increase since the previous commit.
+
+As expected, this mode makes the linter run twice if it finds any violations.
+The second run is performed against a temporary git working copy that contains
+the previous commit. All the violations that were already present are removed
+from the list and the final result is displayed.
+
+The most notable benefit introduced by this mode it does not prevent merging
+new code while allowing developer to address historical violation at his own
+speed.
+
+CI/CD
+-----
+
+If execution under `Github Actions`_ is detected via the presence of
+``GITHUB_ACTIONS=true`` and ``GITHUB_WORFLOW=...`` variables, the linter will
+also print errors using their `annotation`_ format.
+
+.. _GitHub Actions: https://github.com/features/actions
+.. _annotation: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message
+
+Linting Playbooks and Roles
+---------------------------
+
+It's important to note that ``ansible-lint`` accepts a list of Ansible playbook files or a list of role directories. Starting from a directory that contains the following, the playbook file, ``playbook.yml``, or one of the role subdirectories, such as ``geerlingguy.apache``, can be passed:
+
+.. code-block::
+
+ playbook.yml
+ roles/
+ geerlingguy.apache/
+ tasks/
+ handlers/
+ files/
+ templates/
+ vars/
+ defaults/
+ meta/
+ geerlingguy.elasticsearch/
+ tasks/
+ handlers/
+ files/
+ templates/
+ vars/
+ defaults/
+ meta/
+
+The following lints the role ``geerlingguy.apache``:
+
+.. code-block::
+
+ $ ansible-lint geerlingguy.apache
+
+ [305] Use shell only when shell functionality is required
+ /Users/chouseknecht/.ansible/roles/geerlingguy.apache/tasks/main.yml:19
+ Task/Handler: Get installed version of Apache.
+
+ [502] All tasks should be named
+ /Users/chouseknecht/.ansible/roles/geerlingguy.apache/tasks/main.yml:29
+ Task/Handler: include_vars apache-22.yml
+
+ [502] All tasks should be named
+ /Users/chouseknecht/.ansible/roles/geerlingguy.apache/tasks/main.yml:32
+ Task/Handler: include_vars apache-24.yml
+
+Here's the contents of ``playbook.yml``, which references multiples roles:
+
+.. code-block:: yaml
+
+ - name: Lint multiple roles
+ hosts: all
+ tasks:
+
+ - include_role:
+ name: geerlingguy.apache
+
+ - include_role:
+ name: geerlingguy.elasticsearch
+
+The following lints ``playbook.yml``, which evaluates both the playbook and the referenced roles:
+
+.. code-block::
+
+ $ ansible-lint playbook.yml
+
+ [305] Use shell only when shell functionality is required
+ /Users/chouseknecht/roles/geerlingguy.apache/tasks/main.yml:19
+ Task/Handler: Get installed version of Apache.
+
+ [502] All tasks should be named
+ /Users/chouseknecht/roles/geerlingguy.apache/tasks/main.yml:29
+ Task/Handler: include_vars apache-22.yml
+
+ [502] All tasks should be named
+ /Users/chouseknecht/roles/geerlingguy.apache/tasks/main.yml:32
+ Task/Handler: include_vars apache-24.yml
+
+ [502] All tasks should be named
+ /Users/chouseknecht/roles/geerlingguy.elasticsearch/tasks/main.yml:17
+ Task/Handler: service state=started name=elasticsearch enabled=yes
+
+Since ``ansible-lint`` accepts a list of roles or playbooks, the following works as well, producing the same output as the example above:
+
+.. code-block::
+
+ $ ansible-lint geerlingguy.apache geerlingguy.elasticsearch
+
+ [305] Use shell only when shell functionality is required
+ /Users/chouseknecht/roles/geerlingguy.apache/tasks/main.yml:19
+ Task/Handler: Get installed version of Apache.
+
+ [502] All tasks should be named
+ /Users/chouseknecht/roles/geerlingguy.apache/tasks/main.yml:29
+ Task/Handler: include_vars apache-22.yml
+
+ [502] All tasks should be named
+ /Users/chouseknecht/roles/geerlingguy.apache/tasks/main.yml:32
+ Task/Handler: include_vars apache-24.yml
+
+ [502] All tasks should be named
+ /Users/chouseknecht/roles/geerlingguy.elasticsearch/tasks/main.yml:17
+ Task/Handler: service state=started name=elasticsearch enabled=yes
+
+Examples
+--------
+
+Included in ``ansible-lint/examples`` are some example playbooks with undesirable features. Running ansible-lint on them works, as demonstrated in the following:
+
+.. code-block::
+
+ $ ansible-lint examples/example.yml
+
+ [301] Commands should not change things if nothing needs doing
+ examples/example.yml:9
+ Task/Handler: unset variable
+
+ [206] Variables should have spaces before and after: {{ var_name }}
+ examples/example.yml:10
+ action: command echo {{thisvariable}} is not set in this playbook
+
+ [301] Commands should not change things if nothing needs doing
+ examples/example.yml:12
+ Task/Handler: trailing whitespace
+
+ [201] Trailing whitespace
+ examples/example.yml:13
+ action: command echo do nothing
+
+ [401] Git checkouts must contain explicit version
+ examples/example.yml:15
+ Task/Handler: git check
+
+ [401] Git checkouts must contain explicit version
+ examples/example.yml:18
+ Task/Handler: git check 2
+
+ [301] Commands should not change things if nothing needs doing
+ examples/example.yml:24
+ Task/Handler: executing git through command
+
+ [303] git used in place of git module
+ examples/example.yml:24
+ Task/Handler: executing git through command
+
+ [303] git used in place of git module
+ examples/example.yml:27
+ Task/Handler: executing git through command
+
+ [401] Git checkouts must contain explicit version
+ examples/example.yml:30
+ Task/Handler: using git module
+
+ [206] Variables should have spaces before and after: {{ var_name }}
+ examples/example.yml:34
+ action: debug msg="{{item}}"
+
+ [201] Trailing whitespace
+ examples/example.yml:35
+ with_items:
+
+ [403] Package installs should not use latest
+ examples/example.yml:39
+ Task/Handler: yum latest
+
+ [403] Package installs should not use latest
+ examples/example.yml:44
+ Task/Handler: apt latest
+
+ [101] Deprecated always_run
+ examples/example.yml:47
+ Task/Handler: always run
+
+
+If playbooks include other playbooks, or tasks, or handlers or roles, these are also handled:
+
+.. code-block::
+
+ $ ansible-lint examples/include.yml
+
+ [301] Commands should not change things if nothing needs doing
+ examples/play.yml:5
+ Task/Handler: a bad play
+
+ [303] service used in place of service module
+ examples/play.yml:5
+ Task/Handler: a bad play
+
+ [401] Git checkouts must contain explicit version
+ examples/roles/bobbins/tasks/main.yml:2
+ Task/Handler: test tasks
+
+ [701] No 'galaxy_info' found
+ examples/roles/hello/meta/main.yml:1
+ {'meta/main.yml': {'dependencies': [{'role': 'bobbins', '__line__': 3, '__file__': '/Users/akx/build/ansible-lint/examples/roles/hello/meta/main.yml'}], '__line__': 1, '__file__': '/Users/akx/build/ansible-lint/examples/roles/hello/meta/main.yml', 'skipped_rules': []}}
+
+ [303] service used in place of service module
+ examples/roles/morecomplex/handlers/main.yml:1
+ Task/Handler: restart service using command
+
+ [301] Commands should not change things if nothing needs doing
+ examples/roles/morecomplex/tasks/main.yml:1
+ Task/Handler: test bad command
+
+ [302] mkdir used in place of argument state=directory to file module
+ examples/roles/morecomplex/tasks/main.yml:1
+ Task/Handler: test bad command
+
+ [301] Commands should not change things if nothing needs doing
+ examples/roles/morecomplex/tasks/main.yml:4
+ Task/Handler: test bad command v2
+
+ [302] mkdir used in place of argument state=directory to file module
+ examples/roles/morecomplex/tasks/main.yml:4
+ Task/Handler: test bad command v2
+
+ [301] Commands should not change things if nothing needs doing
+ examples/roles/morecomplex/tasks/main.yml:7
+ Task/Handler: test bad local command
+
+ [305] Use shell only when shell functionality is required
+ examples/roles/morecomplex/tasks/main.yml:7
+ Task/Handler: test bad local command
+
+ [504] Do not use 'local_action', use 'delegate_to: localhost'
+ examples/roles/morecomplex/tasks/main.yml:8
+ local_action: shell touch foo
+
+ [201] Trailing whitespace
+ examples/tasks/x.yml:3
+ args:
+
+ [201] Trailing whitespace
+ examples/tasks/x.yml:3
+ args:
+
+.. usage-docs-inclusion-marker-end-do-not-remove
+
+Configuring
+===========
+
+.. configuring-docs-inclusion-marker-do-not-remove
+
+Configuration File
+------------------
+
+Ansible-lint supports local configuration via a ``.ansible-lint`` configuration file. Ansible-lint checks the working directory for the presence of this file and applies any configuration found there. The configuration file location can also be overridden via the ``-c path/to/file`` CLI flag.
+
+If a value is provided on both the command line and via a config file, the values will be merged (if a list like **exclude_paths**), or the **True** value will be preferred, in the case of something like **quiet**.
+
+The following values are supported, and function identically to their CLI counterparts:
+
+.. code-block:: yaml
+
+ exclude_paths:
+ - ./my/excluded/directory/
+ - ./my/other/excluded/directory/
+ - ./last/excluded/directory/
+ parseable: true
+ quiet: true
+ rulesdir:
+ - ./rule/directory/
+ skip_list:
+ - skip_this_tag
+ - and_this_one_too
+ - skip_this_id
+ - '401'
+ tags:
+ - run_this_tag
+ use_default_rules: true
+ verbosity: 1
+
+
+Pre-commit Setup
+----------------
+
+To use ansible-lint with `pre-commit`_, just add the following to your local repo's ``.pre-commit-config.yaml`` file. Make sure to change **rev:** to be either a git commit sha or tag of ansible-lint containing ``hooks.yaml``.
+
+.. code-block:: yaml
+
+ - repo: https://github.com/ansible/ansible-lint.git
+ rev: v4.1.0
+ hooks:
+ - id: ansible-lint
+ files: \.(yaml|yml)$
+
+.. _pre-commit: https://pre-commit.com
+
+.. configuring-docs-inclusion-marker-end-do-not-remove
+
+Rules
+=====
+
+.. rules-docs-inclusion-marker-do-not-remove
+
+Specifying Rules at Runtime
+---------------------------
+
+By default, ``ansible-lint`` uses the rules found in ``ansible-lint/lib/ansiblelint/rules``. To override this behavior and use a custom set of rules, use the ``-r /path/to/custom-rules`` option to provide a directory path containing the custom rules. For multiple rule sets, pass multiple ``-r`` options.
+
+It's also possible to use the default rules, plus custom rules. This can be done by passing the ``-R`` to indicate that the default rules are to be used, along with one or more ``-r`` options.
+
+Using Tags to Include Rules
+```````````````````````````
+
+Each rule has an associated set of one or more tags. To view the list of tags for each available rule, use the ``-T`` option.
+
+The following shows the available tags in an example set of rules, and the rules associated with each tag:
+
+.. code-block:: bash
+
+ $ ansible-lint -v -T
+
+ behaviour ['[503]']
+ bug ['[304]']
+ command-shell ['[305]', '[302]', '[304]', '[306]', '[301]', '[303]']
+ deprecated ['[105]', '[104]', '[103]', '[101]', '[102]']
+ formatting ['[104]', '[203]', '[201]', '[204]', '[206]', '[205]', '[202]']
+ idempotency ['[301]']
+ idiom ['[601]', '[602]']
+ metadata ['[701]', '[704]', '[703]', '[702]']
+ module ['[404]', '[401]', '[403]', '[402]']
+ oddity ['[501]']
+ readability ['[502]']
+ repeatability ['[401]', '[403]', '[402]']
+ resources ['[302]', '[303]']
+ safety ['[305]']
+ task ['[502]', '[503]', '[504]', '[501]']
+
+To run just the *idempotency* rules, for example, run the following:
+
+.. code-block:: bash
+
+ $ ansible-lint -t idempotency playbook.yml
+
+Excluding Rules
+```````````````
+
+To exclude rules from the available set of rules, use the ``-x SKIP_LIST`` option. For example, the following runs all of the rules except those with the tags *readability* and *safety*:
+
+.. code-block:: bash
+
+ $ ansible-lint -x readability,safety playbook.yml
+
+It's also possible to skip specific rules by passing the rule ID. For example, the following excludes rule *502*:
+
+.. code-block:: bash
+
+ $ ansible-lint -x 502 playbook.yml
+
+False Positives: Skipping Rules
+-------------------------------
+
+Some rules are a bit of a rule of thumb. Advanced *git*, *yum* or *apt* usage, for example, is typically difficult to achieve through the modules. In this case, you should mark the task so that warnings aren't produced.
+
+To skip a specific rule for a specific task, inside your ansible yaml add ``# noqa [rule_id]`` at the end of the line. If the rule is task-based (most are), add at the end of any line in the task. You can skip multiple rules via a space-separated list.
+
+.. code-block:: yaml
+
+ - name: this would typically fire GitHasVersionRule 401 and BecomeUserWithoutBecomeRule 501
+ become_user: alice # noqa 401 501
+ git: src=/path/to/git/repo dest=checkout
+
+If the rule is line-based, ``# noqa [rule_id]`` must be at the end of the particular line to be skipped
+
+.. code-block:: yaml
+
+ - name: this would typically fire LineTooLongRule 204 and VariableHasSpacesRule 206
+ get_url:
+ url: http://example.com/really_long_path/really_long_path/really_long_path/really_long_path/really_long_path/really_long_path/file.conf # noqa 204
+ dest: "{{dest_proj_path}}/foo.conf" # noqa 206
+
+
+It's also a good practice to comment the reasons why a task is being skipped.
+
+If you want skip running a rule entirely, you can use either use ``-x`` command
+line argument, or add it to ``skip_list`` inside the configuration file.
+
+A less-preferred method of skipping is to skip all task-based rules for a task (this does not skip line-based rules). There are two mechanisms for this: the ``skip_ansible_lint`` tag works with all tasks, and the ``warn`` parameter works with the *command* or *shell* modules only. Examples:
+
+.. code-block:: yaml
+
+ - name: this would typically fire CommandsInsteadOfArgumentRule 302
+ command: warn=no chmod 644 X
+
+ - name: this would typically fire CommandsInsteadOfModuleRule 303
+ command: git pull --rebase
+ args:
+ warn: False
+
+ - name: this would typically fire GitHasVersionRule 401
+ git: src=/path/to/git/repo dest=checkout
+ tags:
+ - skip_ansible_lint
+
+Creating Custom Rules
+---------------------
+
+Rules are described using a class file per rule. Default rules are named *DeprecatedVariableRule.py*, etc.
+
+Each rule definition should have the following:
+
+* ID: A unique identifier
+* Short description: Brief description of the rule
+* Description: Behaviour the rule is looking for
+* Tags: one or more tags that may be used to include or exclude the rule
+* At least one of the following methods:
+
+ * ``match`` that takes a line and returns None or False, if the line doesn't match the test, and True or a custom message, when it does. (This allows one rule to test multiple behaviours - see e.g. the *CommandsInsteadOfModulesRule*.)
+ * ``matchtask`` that operates on a single task or handler, such that tasks get standardized to always contain a *module* key and *module_arguments* key. Other common task modifiers, such as *when*, *with_items*, etc., are also available as keys, if present in the task.
+
+An example rule using ``match`` is:
+
+.. code-block:: python
+
+ from ansiblelint.rules import AnsibleLintRule
+
+ class DeprecatedVariableRule(AnsibleLintRule):
+
+ id = 'EXAMPLE002'
+ shortdesc = 'Deprecated variable declarations'
+ description = 'Check for lines that have old style ${var} ' + \
+ 'declarations'
+ tags = { 'deprecated' }
+
+ def match(self, file, line):
+ return '${' in line
+
+An example rule using ``matchtask`` is:
+
+.. code-block:: python
+
+ import ansiblelint.utils
+ from ansiblelint.rules import AnsibleLintRule
+
+ class TaskHasTag(AnsibleLintRule):
+ id = 'EXAMPLE001'
+ shortdesc = 'Tasks must have tag'
+ description = 'Tasks must have tag'
+ tags = ['productivity']
+
+ def matchtask(self, file, task):
+ # If the task include another task or make the playbook fail
+ # Don't force to have a tag
+ if not set(task.keys()).isdisjoint(['include','fail']):
+ return False
+
+ # Task should have tags
+ if not task.has_key('tags'):
+ return True
+
+ return False
+
+The task argument to ``matchtask`` contains a number of keys - the critical one is *action*. The value of *task['action']* contains the module being used, and the arguments passed, both as key-value pairs and a list of other arguments (e.g. the command used with shell).
+
+In ansible-lint 2.0.0, *task['action']['args']* was renamed *task['action']['module_arguments']* to avoid a clash when a module actually takes args as a parameter key (e.g. ec2_tag)
+
+In ansible-lint 3.0.0 *task['action']['module']* was renamed *task['action']['__ansible_module__']* to avoid a clash when a module take module as an argument. As a precaution, *task['action']['module_arguments']* was renamed *task['action']['__ansible_arguments__']*.
+
+Packaging Custom Rules
+``````````````````````
+
+Ansible-lint provides a sub directory named *custom* in its built-in rules,
+``/usr/lib/python3.8/site-packages/ansiblelint/rules/custom/`` for example, to
+install custom rules since v4.3.1. The custom rules which are packaged as an
+usual python package installed into this directory will be loaded and enabled
+automatically by ansible-lint.
+
+To make custom rules loaded automatically, you need the followings:
+
+- Packaging your custom rules as an usual python package named some descriptive ones like ``ansible_lint_custom_rules_foo``.
+- Make it installed into ``<ansible_lint_custom_rules_dir>/custom/<your_custom_rules_subdir>/``.
+
+You may accomplish the second by adding some configurations into the [options]
+section of the ``setup.cfg`` of your custom rules python package like the following.
+
+.. code-block::
+
+ [options]
+ packages =
+ ansiblelint.rules.custom.<your_custom_rules_subdir>
+ package_dir =
+ ansiblelint.rules.custom.<your_custom_rules_subdir> = <your_rules_source_code_subdir>
+
+.. rules-docs-inclusion-marker-end-do-not-remove
+
+Contributing
+============
+
+Please read `Contribution guidelines`_ if you wish to contribute.
+
+Authors
+=======
+
+ansible-lint was created by `Will Thames`_ and is now maintained as part of the `Ansible`_ by `Red Hat`_ project.
+
+.. _Contribution guidelines: https://ansible-lint.readthedocs.io/en/latest/contributing.html
+.. _Will Thames: https://github.com/willthames
+.. _Ansible: https://ansible.com
+.. _Red Hat: https://redhat.com