From ea137cd54f324370ab591a990e5099c050380b58 Mon Sep 17 00:00:00 2001 From: William Bradford Clark Date: Thu, 8 Sep 2022 01:43:35 -0400 Subject: [PATCH] Add a git_repo role This role accepts a single complex data structure to clone a git repository with multiple remotes and branches. --- roles/git_repo/README.md | 85 +++++++++++++++++++++++++++++++++ roles/git_repo/meta/main.yml | 3 ++ roles/git_repo/tasks/branch.yml | 5 ++ roles/git_repo/tasks/clone.yml | 15 ++++++ roles/git_repo/tasks/main.yml | 32 +++++++++++++ roles/git_repo/tasks/remote.yml | 26 ++++++++++ 6 files changed, 166 insertions(+) create mode 100644 roles/git_repo/README.md create mode 100644 roles/git_repo/meta/main.yml create mode 100644 roles/git_repo/tasks/branch.yml create mode 100644 roles/git_repo/tasks/clone.yml create mode 100644 roles/git_repo/tasks/main.yml create mode 100644 roles/git_repo/tasks/remote.yml diff --git a/roles/git_repo/README.md b/roles/git_repo/README.md new file mode 100644 index 000000000..d0b850496 --- /dev/null +++ b/roles/git_repo/README.md @@ -0,0 +1,85 @@ +This role clones a set of git repositories, handling multiple remotes and managing different branches for each remote. It also provides basic support for bundle installs for Ruby projects, and pip installs within a virtual environment for Python projects. The role aims to present a single, simple interface in Ansible variables for managing a collection of locally cloned git repositories. + +Role Variables +-------------- + +The main data structure for the role is the list of `git_repositories`. Each repository in the list requires the following attributes: + +- `name`: The local name of the repository. +- `dir`: The parent directory to clone the repository from. +- `remotes`: List of repository remotes, each with `name`, `url`, and `branches`. + +Repositories may also have the following optional attributes: + +- `install_gems`: Boolean to indicate if Ruby gems should be installed. This assumes the cloned repository provides a Gemfile. +- `python_packages`: A list of python packages to install in a venv within the repository. + +Each remote requires the following attributes: + +- `name`: Name of the remote. +- `url`: URL of the remote. +- `branches`: A list of branches in the remote repository that will be created in the local clone. + +Examples +-------- + +Basic example of the `git_repositories` variable, configuring two repositories, each with a single remote and a single branch: + +```yaml +git_repositories: + - name: 'foreman' + dir: '/home/vagrant' + remotes: + - name: 'origin' + url: 'git@github.com/my_github_user/foreman.git' + branches: + - 'develop' + - name: 'katello' + dir: '/home/vagrant' + remotes: + - name: 'origin' + url: 'git@github.com/my_github_user/katello.git' + branches: + - 'master' +``` + +Example configuring a repository with multiple remotes and branches and installing gems from the project's Gemfile. The checkout will be of the first branch on the first remote when performing the bundle install: + +```yaml +git_repositories: + - name: 'foreman' + dir: '/home/vagrant' + remotes: + - name: 'myfork' + url: 'git@github.com/my_github_user/foreman.git' + branches: + - 'develop' + - 'exciting-new-feature' + - 'fix-annoying-bug' + - name: 'upstream' + url: 'git@github.com/theforeman/foreman.git' + branches: + - '3.9-stable' + - '3.8-stable' + install_gems: true +``` + +Installing Python Packages in a Virtual Environment: + +```yaml +git_repositories: + - name: 'rpm-packaging' + dir: '/home/vagrant' + remotes: + - name: 'downstream' + url: 'git@gitlab.example.com/systems_management/rpm-packaging.git' + branches: + - 'STREAM' + - 'VERSION-1' + - 'VERSION-2' + - 'VERSION-3' + python_packages: + - 'ansible' + - 'obal' + - 'tito' +``` diff --git a/roles/git_repo/meta/main.yml b/roles/git_repo/meta/main.yml new file mode 100644 index 000000000..4e537669d --- /dev/null +++ b/roles/git_repo/meta/main.yml @@ -0,0 +1,3 @@ +--- +dependencies: + - role: git diff --git a/roles/git_repo/tasks/branch.yml b/roles/git_repo/tasks/branch.yml new file mode 100644 index 000000000..2444b0bcb --- /dev/null +++ b/roles/git_repo/tasks/branch.yml @@ -0,0 +1,5 @@ +--- +- name: "Create local repository branch tracking specified remote branch" + ansible.builtin.command: + cmd: "git branch -f {{ branch }} {{ remote.name }}/{{ branch }}" + chdir: "{{ git_repo_repository.dir }}/{{ git_repo_repository.name }}" diff --git a/roles/git_repo/tasks/clone.yml b/roles/git_repo/tasks/clone.yml new file mode 100644 index 000000000..a880ed7ef --- /dev/null +++ b/roles/git_repo/tasks/clone.yml @@ -0,0 +1,15 @@ +--- +- name: "Clone repository" + ansible.builtin.git: + repo: "{{ remote.url }}" + remote: "{{ remote.name }}" + version: "{{ (remote.branches[0]) if (remote.branches is defined) else 'HEAD' }}" + dest: "{{ git_repo_repository.dir }}/{{ git_repo_repository.name }}" + accept_newhostkey: yes + +- name: "Configure additional branches for initial remote" + ansible.builtin.include_tasks: branch.yml + when: (remote.branches is defined) and (remote.branches | length > 1) + loop: "{{ remote.branches[1:] }}" + loop_control: + loop_var: branch diff --git a/roles/git_repo/tasks/main.yml b/roles/git_repo/tasks/main.yml new file mode 100644 index 000000000..f634ee432 --- /dev/null +++ b/roles/git_repo/tasks/main.yml @@ -0,0 +1,32 @@ +--- +- name: "Configure git repository" + ansible.builtin.include_tasks: "{{ 'clone.yml' if ansible_loop.first else 'remote.yml' }}" + loop: "{{ git_repo_repository.remotes }}" + loop_control: + loop_var: remote + extended: yes + label: "{{ remote.name }}" + +- name: "Local bundle config" + when: git_repo_repository.install_gems is defined and git_repo_repository.install_gems + block: + - name: "Create .bundle/gems" + ansible.builtin.file: + path: "{{ git_repo_repository.dir }}/{{ git_repo_repository.name }}/.bundle/gems" + state: directory + - name: "Configure bundler to use .bundle/gems" + ansible.builtin.command: + cmd: "bundle config --local path .bundle/gems" + chdir: "{{ git_repo_repository.dir }}/{{ git_repo_repository.name }}" + - name: "Install Gems" + community.general.bundler: + state: present + chdir: "{{ git_repo_repository.dir }}/{{ git_repo_repository.name }}" + +- name: "Create venv and install python packages" + ansible.builtin.pip: + name: "{{ git_repo_repository.python_packages }}" + virtualenv: "{{ git_repo_repository.dir }}/{{ git_repo_repository.name }}/env" + virtualenv_command: "python3 -m venv" + virtualenv_site_packages: yes + when: (git_repo_repository.python_packages is defined) and (git_repo_repository.python_packages | length >= 1) diff --git a/roles/git_repo/tasks/remote.yml b/roles/git_repo/tasks/remote.yml new file mode 100644 index 000000000..fda718e9a --- /dev/null +++ b/roles/git_repo/tasks/remote.yml @@ -0,0 +1,26 @@ +--- +- name: "Configure additional remote for repository" + block: + - name: "Configure remote url" + community.general.git_config: + scope: local + repo: "{{ git_repo_repository.dir }}/{{ git_repo_repository.name }}" + name: "remote.{{ remote.name }}.url" + value: "{{ remote.url }}" + - name: "Configure remote fetch" + community.general.git_config: + scope: local + repo: "{{ git_repo_repository.dir }}/{{ git_repo_repository.name }}" + name: "remote.{{ remote.name }}.fetch" + value: '+refs/heads/*:refs/remotes/{{ remote.name }}/*' + - name: "Fetch remote" + ansible.builtin.command: + cmd: "git fetch {{ remote.name }}" + chdir: "{{ git_repo_repository.dir }}/{{ git_repo_repository.name }}" + +- name: "Configure branches for additional remote" + ansible.builtin.include_tasks: branch.yml + when: remote.branches is defined + loop: "{{ remote.branches }}" + loop_control: + loop_var: branch