diff --git a/doc/source/general-roles.rst b/doc/source/general-roles.rst index e439836a6..afc009d22 100644 --- a/doc/source/general-roles.rst +++ b/doc/source/general-roles.rst @@ -21,6 +21,7 @@ General Purpose Roles .. zuul:autorole:: ensure-shake .. zuul:autorole:: fetch-markdownlint .. zuul:autorole:: git-prepare-nodecache +.. zuul:autorole:: intercept-job .. zuul:autorole:: log-inventory .. zuul:autorole:: markdownlint .. zuul:autorole:: mirror-workspace-git-repos diff --git a/roles/intercept-job/README.rst b/roles/intercept-job/README.rst new file mode 100644 index 000000000..432b4bb45 --- /dev/null +++ b/roles/intercept-job/README.rst @@ -0,0 +1,21 @@ +If a special SSH key is placed in the right place, stops and waits for user to +SSH in to the node. + +This role is intended to be used in pre/post playbooks to allow a smoother +self-service experience than autoholds can offer, at the expense that one can +only access the node for the length of the job timeout. + +**Role Variables** + +.. zuul:rolevar:: intercept_job_pub_key_path + :default: ``{{ zuul.project.src_dir }}/intercept_job.pub`` + + If a public key is found here, the intercept-job role will install it, print + details for SSH'ing into this machine, and wait until the + intercept_job_stop_path exists. + +.. zuul:rolevar:: intercept_job_stop_path + :default: ``{{ zuul.project.src_dir }}/intercept_job.stop`` + + If this file exists, the intercept-job role will stop waiting and allow the + playbook to continue. diff --git a/roles/intercept-job/defaults/main.yaml b/roles/intercept-job/defaults/main.yaml new file mode 100644 index 000000000..429312995 --- /dev/null +++ b/roles/intercept-job/defaults/main.yaml @@ -0,0 +1,2 @@ +intercept_job_stop_path: "{{ zuul.project.src_dir }}/intercept_job.stop" +intercept_job_pub_key_path: "{{ zuul.project.src_dir }}/intercept_job.pub" diff --git a/roles/intercept-job/files/waiter.sh b/roles/intercept-job/files/waiter.sh new file mode 100644 index 000000000..2212602e5 --- /dev/null +++ b/roles/intercept-job/files/waiter.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -eu +INTERCEPT_STOP_FILE=${1:-} +if [ -z "${INTERCEPT_STOP_FILE}" ] ; then + echo usage: $0 intercept_stop_file + exit 1 +fi +echo "to continue job:" +echo touch ${INTERCEPT_STOP_FILE} +echo +echo "to cancel job:" +echo kill $BASHPID + +while ! [ -e "${INTERCEPT_STOP_FILE}" ] ; do + sleep 5 +done diff --git a/roles/intercept-job/tasks/main.yaml b/roles/intercept-job/tasks/main.yaml new file mode 100644 index 000000000..571eb8efb --- /dev/null +++ b/roles/intercept-job/tasks/main.yaml @@ -0,0 +1,8 @@ +- name: Check for intercept SSH key + stat: + path: "{{ intercept_job_pub_key_path }}" + register: intercept_key_stat + +- name: Wait + when: intercept_key_stat.stat.exists + include_tasks: wait.yaml diff --git a/roles/intercept-job/tasks/wait.yaml b/roles/intercept-job/tasks/wait.yaml new file mode 100644 index 000000000..283f4dfef --- /dev/null +++ b/roles/intercept-job/tasks/wait.yaml @@ -0,0 +1,28 @@ +- name: Slurp it + slurp: + path: "{{ intercept_job_pub_key_path }}" + register: intercept_key + +- name: Add to current user + authorized_key: + key: "{{ intercept_key.content|b64decode }}" + user: "{{ ansible_user }}" + +- name: Inform user of connection details + debug: + msg: "ssh {{ ansible_ssh_user }}@{{ ansible_ssh_host }}" + +- name: Copy waiter script + copy: + src: waiter.sh + dest: ./waiter.sh + mode: "0755" + +- name: Start waiter task + command: ./waiter.sh {{ intercept_job_stop_path }} + +- name: Remove key from current user + authorized_key: + key: "{{ intercept_key.content|b64decode }}" + user: "{{ ansible_user }}" + state: absent diff --git a/test-playbooks/intercept-job.yaml b/test-playbooks/intercept-job.yaml new file mode 100644 index 000000000..5f544d78c --- /dev/null +++ b/test-playbooks/intercept-job.yaml @@ -0,0 +1,33 @@ +- name: Set up async task that will add intercept key + hosts: all + tasks: + - name: Start keygen + command: ssh-keygen -N '' -t ed25519 -f {{ zuul.project.src_dir }}/intercept_job + async: 300 + poll: 0 + register: keygen + - name: Start background script to stop the waiting + shell: | + sleep 60 + touch "{{ zuul.project.src_dir }}/intercept_job.stop" + async: 120 + poll: 0 + register: touched_file + - async_status: + jid: "{{ keygen.ansible_job_id }}" + register: keygen_check + until: keygen_check.finished == 1 + retries: 2 + - include_role: + name: intercept-job + - async_status: + jid: "{{ touched_file.ansible_job_id }}" + register: touched_file_check + until: touched_file_check.finished == 1 + retries: 3 + - stat: + path: "{{ zuul.project.src_dir }}/intercept_job.stop" + register: stop_waiting + - assert: + that: + - stop_waiting.stat.exists