├── .github ├── FUNDING.yml └── workflows │ └── stale.yml ├── LICENSE ├── main.yml ├── tasks └── update-requirement.yml └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | --- 3 | github: geerlingguy 4 | patreon: geerlingguy 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jeff Geerling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | gather_facts: false 4 | connection: local 5 | 6 | vars: 7 | requirements_file_path: ../requirements.yml 8 | requirements_file_backup: true 9 | 10 | # Don't touch this variable. It's for the updated requirements list. 11 | requirements_updated: [] 12 | 13 | tasks: 14 | - name: Get all requirements from the requirements file. 15 | include_vars: 16 | file: "{{ requirements_file_path }}" 17 | name: requirements 18 | 19 | - name: Debug existing requirements list. 20 | debug: 21 | var: requirements 22 | verbosity: 2 23 | 24 | - include_tasks: tasks/update-requirement.yml 25 | loop: "{{ requirements.roles }}" 26 | loop_control: 27 | loop_var: role_info 28 | 29 | - name: Debug updated requirement list. 30 | debug: 31 | var: requirements_updated 32 | verbosity: 2 33 | 34 | - name: Write the updated requirements.yml file. 35 | copy: 36 | content: | 37 | --- 38 | roles: 39 | {{ requirements_updated | to_nice_yaml(indent=2) | trim | indent(2) }} 40 | dest: "{{ requirements_file_path }}" 41 | backup: "{{ requirements_file_backup }}" 42 | -------------------------------------------------------------------------------- /tasks/update-requirement.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # There might be a more direct route to this information via the Galaxy API, but 3 | # this works, and so far I don't notice any rate-limiting going on. 4 | - name: Get information about role via ansible-galaxy CLI. 5 | shell: > 6 | ansible-galaxy role info {{ role_info.name }} | grep -E 'id: [0-9]' | awk {'print $2'} 7 | register: galaxy_result 8 | 9 | - name: Set ID. 10 | set_fact: 11 | galaxy_id: "{{ galaxy_result.stdout }}" 12 | 13 | - name: Get role versions. 14 | uri: 15 | url: "https://galaxy.ansible.com/api/v1/roles/{{ galaxy_id }}/" 16 | return_content: true 17 | register: role_metadata 18 | 19 | - name: Set versions as fact. 20 | set_fact: 21 | role_versions: "{{ (role_metadata.content | from_json).summary_fields.versions }}" 22 | 23 | # Note: This is a pretty fragile sort. It works for strict semver... mostly, but 24 | # it might not work with more complex use cases, and Galaxy kind of allows 25 | # anything in the version field, so YMMV. It works with geerlingguy.* roles. 26 | - name: Use the ugliest hack in the world to find the highest version. 27 | set_fact: 28 | highest_role_version: >- 29 | {%- for version_data in role_versions | sort(attribute='name', reverse=True) -%} 30 | {%- if loop.first -%} 31 | {{ version_data.name }} 32 | {%- endif -%} 33 | {%- endfor -%} 34 | 35 | - name: Set up the dict to be added to requirements_updated. 36 | set_fact: 37 | requirements_role_data: 38 | - name: "{{ role_info.name }}" 39 | version: "{{ highest_role_version }}" 40 | 41 | - name: Debug the information for this role. 42 | debug: 43 | var: requirements_role_data 44 | verbosity: 2 45 | 46 | - name: Combine this role back into the requirements_updated var. 47 | set_fact: 48 | requirements_updated: "{{ requirements_updated | union(requirements_role_data) }}" 49 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Close inactive issues 3 | 'on': 4 | schedule: 5 | - cron: "55 1 * * 5" # semi-random time 6 | 7 | jobs: 8 | close-issues: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | pull-requests: write 13 | steps: 14 | - uses: actions/stale@v8 15 | with: 16 | days-before-stale: 120 17 | days-before-close: 60 18 | exempt-issue-labels: bug,pinned,security,planned 19 | exempt-pr-labels: bug,pinned,security,planned 20 | stale-issue-label: "stale" 21 | stale-pr-label: "stale" 22 | stale-issue-message: | 23 | This issue has been marked 'stale' due to lack of recent activity. If there is no further activity, the issue will be closed in another 30 days. Thank you for your contribution! 24 | 25 | Please read [this blog post](https://www.jeffgeerling.com/blog/2020/enabling-stale-issue-bot-on-my-github-repositories) to see the reasons why I mark issues as stale. 26 | close-issue-message: | 27 | This issue has been closed due to inactivity. If you feel this is in error, please reopen the issue or file a new issue with the relevant details. 28 | stale-pr-message: | 29 | This pr has been marked 'stale' due to lack of recent activity. If there is no further activity, the issue will be closed in another 30 days. Thank you for your contribution! 30 | 31 | Please read [this blog post](https://www.jeffgeerling.com/blog/2020/enabling-stale-issue-bot-on-my-github-repositories) to see the reasons why I mark issues as stale. 32 | close-pr-message: | 33 | This pr has been closed due to inactivity. If you feel this is in error, please reopen the issue or file a new issue with the relevant details. 34 | repo-token: ${{ secrets.GITHUB_TOKEN }} 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ansible Requirements Updater 2 | 3 | **Update your requirements.yml with this grisly Ansible playbook.** 4 | 5 | This is one of the gookiest Ansible playbooks I've ever written. 6 | 7 | But it does something I need, and it's a lot faster than doing it manually, and I do it every few months. So it was worth spending two hours automating the process on a Friday night while watching a Blues game. 8 | 9 | The playbook does the following: 10 | 11 | 1. Reads in a list of role requirements from an existing `requirements.yml` file (at `requirements_file_path`). 12 | 2. Loops through all the roles in that list, and gets the latest role version string from Ansible Galaxy. 13 | 3. Generates a new requirements list. 14 | 4. Writes the new list to the `requirements.yml` file. 15 | 16 | If you're really daring and had a few drinks, you can set `requirements_file_backup` to `false`, and overwrite your artisinally-handcrafted requirements file with whatever this playbook disgorges 🤮. 17 | 18 | You might be wondering at this point: "Why doesn't geerlingguy just run `ansible-galaxy update -r requirements.yml`?" Well, unfortunately, [`update` is not a thing](https://github.com/ansible/galaxy/issues/1358), and might never be, for roles on Ansible Galaxy. Collections [can be upgraded now](https://github.com/ansible/ansible/issues/65699), but unfortunately besides defining requirements ranges, you can't 'lock in' a set of specific known-working versions of collections. Until ansible supports something like a lock file, you have to do it manually, or trust your life to this playbook. I know what _I'd_ do. 19 | 20 | ## Caveats 21 | 22 | - This has only been tested with `geerlingguy.*` roles, and a few others which follow semantic versioning standards. Since Galaxy allows practically anything as a version (heck, you could probably throw a 🦑 in there and it would work), the playbook will likely break your requirements in new and interesting ways if you use roles that do strange things with versioning. 23 | - This playbook only handles requirements files with `roles` in them, not with `roles` and `collections`. If you have any `collections` listed in your requirements file, this playbook will act like they don't exist and they will go 💥. 24 | - Maybe this playbook will support collections someday, maybe not. Buy my book and I'll be at least 0.0002% more likely to fix [issue #1](https://github.com/geerlingguy/ansible-requirements-updater/issues/1). 25 | - You should never write a playbook like this. It is dumb. It is inefficient. It is against every one of the best practices I espouse in my book [Ansible for DevOps](https://www.ansiblefordevops.com) and my blog posts on Ansible. But I did, so 🤷‍♂️. 26 | 27 | ## Usage 28 | 29 | Let's assume you have a requirements file (`requirements.yml`) with contents like: 30 | 31 | ```yaml 32 | --- 33 | roles: 34 | - name: geerlingguy.ansible 35 | version: 1.2.0 36 | - name: geerlingguy.certbot 37 | version: 3.0.0 38 | - name: geerlingguy.docker 39 | version: 2.1.0 40 | ``` 41 | 42 | Run the playbook like so: 43 | 44 | ansible-playbook main.yml -e "requirements_file_path=path/to/a/requirements.yml" 45 | 46 | Go get a coffee (☕️) – it's going to take a while because of how horrifically inefficient it is to use Ansible as a programming language like this playbook does. 47 | 48 | Cross your fingers (🤞) and hope it works. 49 | 50 | > **Note**: It is _highly_ recommended you track your `requirements.yml` file in a git repository. That way if this playbook mangles it horrifically, you can `git restore requirements.yml` and act like nothing happened 🤐. 51 | 52 | ## License 53 | 54 | MIT. 55 | 56 | ## Author Information 57 | 58 | This playbook was brought into its miserable existence by [Jeff Geerling](https://www.jeffgeerling.com/), author of [Ansible for DevOps](https://www.ansiblefordevops.com/) and [Ansible for Kubernetes](https://www.ansibleforkubernetes.com). 59 | --------------------------------------------------------------------------------