diff --git a/README.md b/README.md index d300c13..1739d39 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,9 @@ all_runners_in_same_repo: true # GitHub Enterprise name # github_enterprise: "yourenterprise" +# Runner user Windows password - the logon password for the service user when running on windows. +# runner_user_win_password: "{{ lookup('env', 'PASS') }}" + # Configuring a custom .env file # custom_env: | # http_proxy=YOUR_URL_HERE diff --git a/defaults/main.yml b/defaults/main.yml index 2c30cbc..78d532e 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -3,7 +3,7 @@ runner_user: "{{ lookup('env', 'USER') }}" # Directory where the local runner will be installed -runner_dir: /opt/actions-runner +runner_dir: "{{ 'C:\\actions-runner' if ansible_facts.system == 'Win32NT' else '/opt/actions-runner' }}" # Version of the GitHub Actions Runner runner_version: "latest" @@ -42,7 +42,7 @@ runner_group: "" runner_download_repository: "actions/runner" # Extra arguments to pass to `config.sh`. -# Several arguments muste be set as one string (i.e. "--ephemeral --my_special_fork") +# Several arguments must be set as one string (i.e. "--ephemeral --my_special_fork") runner_extra_config_args: "" # Name to assign to this runner in GitHub (System hostname as default) @@ -63,6 +63,9 @@ all_runners_in_same_repo: true # GitHub Enterprise name # github_enterprise: "yourenterprise" +# Runner user Windows password - the logon password for the service user when running on windows. +# runner_user_win_password: "{{ lookup('env', 'PASS') }}" + # Configuring a custom .env file # custom_env: | # http_proxy=YOUR_URL_HERE diff --git a/tasks/assert.yml b/tasks/assert.yml index 2657ca5..6abf982 100644 --- a/tasks/assert.yml +++ b/tasks/assert.yml @@ -31,3 +31,11 @@ fail_msg: "github_repo was not found or is using an invalid format." run_once: true when: not runner_org and github_enterprise is not defined + +- name: Check runner_user_win_password (RUN ONCE) + ansible.builtin.assert: + that: + - runner_user_win_password is defined + fail_msg: "runner_user_win_password was not defined, but it is required on a windows system" + run_once: true + when: github_actions_system == "win" diff --git a/tasks/collect_info.yml b/tasks/collect_info.yml index f017e27..a08bf5d 100644 --- a/tasks/collect_info.yml +++ b/tasks/collect_info.yml @@ -27,6 +27,8 @@ status_code: 201 force_basic_auth: true register: registration + delegate_to: localhost + become: false run_once: true - name: "Check currently registered runners for repo {{ '(RUN ONCE)' if all_runners_in_same_repo else '' }}" @@ -42,21 +44,21 @@ status_code: 200 force_basic_auth: true register: registered_runners + delegate_to: localhost + become: false run_once: "{{ all_runners_in_same_repo }}" - name: Get Runner User IDs ansible.builtin.command: id -u "{{ runner_user }}" changed_when: false register: runner_user_id + when: github_actions_system != "win" - name: Get Runner Group IDs ansible.builtin.command: id -g "{{ runner_user }}" changed_when: false register: runner_user_group_id - - - name: Set runner_system variable - ansible.builtin.set_fact: - runner_system: "{{ 'osx' if ansible_facts.system == 'Darwin' else 'linux' }}" + when: github_actions_system != "win" - name: Find the latest runner version (RUN ONCE) ansible.builtin.uri: @@ -77,4 +79,4 @@ - name: Get systemd service facts ansible.builtin.service_facts: register: service_facts - when: ansible_facts.system == "Linux" + when: github_actions_system == "linux" diff --git a/tasks/install_runner.yml b/tasks/install_runner_unix.yml similarity index 98% rename from tasks/install_runner.yml rename to tasks/install_runner_unix.yml index ded230d..b398de4 100644 --- a/tasks/install_runner.yml +++ b/tasks/install_runner_unix.yml @@ -22,7 +22,7 @@ - name: Unarchive runner package ansible.builtin.unarchive: src: "https://github.com/{{ runner_download_repository }}/releases/download/v{{ runner_version }}/\ - actions-runner-{{ runner_system }}-{{ github_actions_architecture }}-{{ runner_version }}.tar.gz" + actions-runner-{{ github_actions_system }}-{{ github_actions_architecture }}-{{ runner_version }}.tar.gz" dest: "{{ runner_dir }}/" owner: "{{ runner_user_id.stdout }}" group: "{{ runner_user_group_id.stdout }}" diff --git a/tasks/install_runner_win.yml b/tasks/install_runner_win.yml new file mode 100644 index 0000000..68cdc6b --- /dev/null +++ b/tasks/install_runner_win.yml @@ -0,0 +1,142 @@ +--- +- name: Create directory + ansible.windows.win_file: + path: "{{ runner_dir }}" + state: directory + +- name: Set owner of directory + ansible.windows.win_owner: + path: "{{ runner_dir }}" + user: "{{ runner_user }}" + +- name: Set runner_version variable (If latest) + ansible.builtin.set_fact: + runner_version: "{{ api_response.json.tag_name | regex_replace('^v', '') }}" + when: runner_version == "latest" + +- name: Check if desired version already installed + ansible.windows.win_command: "grep -i {{ runner_version }} {{ runner_dir }}\\bin\\Runner.Listener.deps.json" + register: runner_installed + check_mode: false + changed_when: false + ignore_errors: true + +- name: Download runner package + ansible.windows.win_get_url: + url: "https://github.com/{{ runner_download_repository }}/releases/download/v{{ runner_version }}/\ + actions-runner-{{ github_actions_system }}-{{ github_actions_architecture }}-{{ runner_version }}.zip" + dest: "%TEMP%\\actions-runner-{{ github_actions_system }}-{{ github_actions_architecture }}-{{ runner_version }}.zip" + when: runner_version not in runner_installed.stdout or reinstall_runner + +- name: Unarchive runner package + community.windows.win_unzip: + src: "%TEMP%\\actions-runner-{{ github_actions_system }}-{{ github_actions_architecture }}-{{ runner_version }}.zip" + dest: "{{ runner_dir }}\\" + delete_archive: yes + when: runner_version not in runner_installed.stdout or reinstall_runner + +- name: Configure custom env file if required + randrej.windows.win_blockinfile: + path: "{{ runner_dir }}\\.env" + block: "{{ custom_env }}" + create: true + marker: "# {mark} ANSIBLE MANAGED BLOCK" + marker_begin: BEGIN + marker_end: END + when: custom_env is defined + +- name: Check if runner service name file exist + ansible.windows.win_stat: + path: "{{ runner_dir }}/.service" + register: runner_service_file_path + +- name: Set complete GitHub url for repo runner + ansible.builtin.set_fact: + github_full_url: "{{ github_url }}/{{ github_owner | default(github_account) }}/{{ github_repo }}" + when: not runner_org and github_enterprise is not defined + +- name: Set complete GitHub url for org runner + ansible.builtin.set_fact: + github_full_url: "{{ github_url }}/{{ github_owner | default(github_account) }}" + when: runner_org | bool and github_enterprise is not defined + +- name: Set complete GitHub url for enterprise runner + ansible.builtin.set_fact: + github_full_url: "{{ github_url }}/enterprises/{{ github_enterprise }}" + when: github_enterprise is defined + +- name: Register runner # noqa no-changed-when + environment: + RUNNER_ALLOW_RUNASROOT: "1" + ansible.windows.win_command: + "{{ runner_dir }}\\config.cmd \ + --url {{ github_full_url }} \ + --token {{ registration.json.token }} \ + --name {{ runner_name }} \ + --labels {{ runner_labels | join(',') }} \ + --runnergroup {{ runner_group }} \ + --runasservice \ + --windowslogonaccount {{ runner_user }} \ + --windowslogonpassword {{ runner_user_win_password }} \ + --unattended \ + {{ runner_extra_config_args }}" + args: + chdir: "{{ runner_dir }}" + changed_when: true + become_method: runas + become_user: "{{ runner_user }}" + become: true + no_log: "{{ hide_sensitive_logs | bool }}" + when: runner_name not in registered_runners.json.runners|map(attribute='name')|list + +- name: Replace registered runner # noqa no-changed-when + environment: + RUNNER_ALLOW_RUNASROOT: "1" + ansible.windows.win_command: + "{{ runner_dir }}\\config.cmd \ + --url {{ github_full_url }} \ + --token {{ registration.json.token }} \ + --name {{ runner_name }} \ + --labels {{ runner_labels | join(',') }} \ + --runasservice \ + --windowslogonaccount {{ runner_user }} \ + --windowslogonpassword {{ runner_user_win_password }} \ + --unattended \ + {{ runner_extra_config_args }} \ + --replace" + args: + chdir: "{{ runner_dir }}" + changed_when: true + become_method: runas + become_user: "{{ runner_user }}" + become: true + no_log: "{{ hide_sensitive_logs | bool }}" + when: > + runner_name in registered_runners.json.runners|map(attribute='name')|list and + reinstall_runner + +- name: Read service name from file + ansible.windows.win_command: "cat {{ runner_dir }}\\.service" + register: runner_service + changed_when: false + +- name: START and enable Github Actions Runner service + ansible.windows.win_service: + name: "{{ runner_service.stdout }}" + start_mode: auto + state: started + when: runner_state|lower == "started" + +- name: STOP and disable Github Actions Runner service + ansible.windows.win_service: + name: "{{ runner_service.stdout }}" + start_mode: manual + state: stopped + when: runner_state|lower == "stopped" + +- name: Version changed - RESTART Github Actions Runner service + ansible.windows.win_service: + name: "{{ runner_service.stdout }}" + start_mode: auto + state: restarted + when: runner_version not in runner_installed.stdout and not runner_state|lower == "stopped" diff --git a/tasks/main.yml b/tasks/main.yml index 9884999..b546592 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -5,7 +5,7 @@ - install - uninstall -- name: Include Information collection taks +- name: Include Information collection tasks ansible.builtin.include_tasks: collect_info.yml tags: - install @@ -13,18 +13,40 @@ - name: Include tasks to install dependencies ansible.builtin.include_tasks: install_deps.yml - when: runner_state|lower == "started" or runner_state|lower == "stopped" + when: + - runner_state|lower == "started" or runner_state|lower == "stopped" + - github_actions_system == "linux" tags: - install -- name: Include tasks to uninstall runner - ansible.builtin.include_tasks: uninstall_runner.yml - when: reinstall_runner or runner_state|lower == "absent" +- name: Include tasks to uninstall runner (UNIX-like) + ansible.builtin.include_tasks: uninstall_runner_unix.yml + when: + - reinstall_runner or runner_state|lower == "absent" + - github_actions_system == "linux" or github_actions_system == "osx" tags: - uninstall -- name: Include tasks to install runner - ansible.builtin.include_tasks: install_runner.yml - when: runner_state|lower == "started" or runner_state|lower == "stopped" +- name: Include tasks to uninstall runner (Windows) + ansible.builtin.include_tasks: uninstall_runner_win.yml + when: + - reinstall_runner or runner_state|lower == "absent" + - github_actions_system == "win" + tags: + - uninstall + +- name: Include tasks to install runner (UNIX-like) + ansible.builtin.include_tasks: install_runner_unix.yml + when: + - runner_state|lower == "started" or runner_state|lower == "stopped" + - github_actions_system == "linux" or github_actions_system == "osx" + tags: + - install + +- name: Include tasks to install runner (Windows) + ansible.builtin.include_tasks: install_runner_win.yml + when: + - runner_state|lower == "started" or runner_state|lower == "stopped" + - github_actions_system == "win" tags: - install diff --git a/tasks/uninstall_runner.yml b/tasks/uninstall_runner_unix.yml similarity index 100% rename from tasks/uninstall_runner.yml rename to tasks/uninstall_runner_unix.yml diff --git a/tasks/uninstall_runner_win.yml b/tasks/uninstall_runner_win.yml new file mode 100644 index 0000000..54fd14f --- /dev/null +++ b/tasks/uninstall_runner_win.yml @@ -0,0 +1,47 @@ +--- +- name: Check if runner service name file exist + ansible.windows.win_stat: + path: "{{ runner_dir }}/.service" + register: runner_service_file_path + +- name: Read service name from file + ansible.windows.win_command: "cat {{ runner_dir }}\\.service" + register: runner_service + changed_when: false + when: runner_service_file_path.stat.exists + +- name: Uninstall service runner + ansible.windows.win_shell: | + if(Get-Service {{ runner_service.stdout }} -ErrorAction SilentlyContinue) { + Write-Host "Stopping and removing service: {{ runner_service.stdout }}" + sc.exe stop "{{ runner_service.stdout }}" + sc.exe delete "{{ runner_service.stdout }}" + } + args: + chdir: "{{ runner_dir }}" + register: uninstall_service_runner + changed_when: "'Stopping and removing service:' in uninstall_service_runner.stdout" + when: runner_service_file_path.stat.exists + +- name: Check GitHub Actions runner file + ansible.windows.win_stat: + path: "{{ runner_dir }}/.runner" + register: runner_file + +- name: Unregister runner from the GitHub + environment: + RUNNER_ALLOW_RUNASROOT: "1" + ansible.windows.win_command: "config.cmd remove --token {{ registration.json.token }} --name {{ runner_name }} --unattended" + args: + chdir: "{{ runner_dir }}" + become: true + become_method: runas + become_user: "{{ runner_user }}" + no_log: "{{ hide_sensitive_logs | bool }}" + changed_when: true + when: runner_name in registered_runners.json.runners|map(attribute='name')|list and runner_file.stat.exists + +- name: Delete runner directory + ansible.windows.win_file: + path: "{{ runner_dir }}" + state: absent diff --git a/vars/main.yml b/vars/main.yml index 4236c1a..4c31c34 100644 --- a/vars/main.yml +++ b/vars/main.yml @@ -3,7 +3,13 @@ github_actions_architecture_map: amd64: x64 x86_64: x64 + 64-bit: x64 armv7l: arm aarch64: arm64 arm64: arm64 github_actions_architecture: "{{ github_actions_architecture_map[ansible_facts.architecture] }}" +github_actions_system_map: + Darwin: osx + Linux: linux + Win32NT: win +github_actions_system: "{{ github_actions_system_map[ansible_facts.system] }}"