I could not find anything in the documentation, but maybe I am just being blind…
Is there any possibility to provision gitea as an oauth2 provider directly? I am already using the command line interface (in ansible) for adding LDAP authentication, now I would like to add an oauth2 app (drone), too.
I can do this manually but it would be nice if it is possible to automate this last step, too. Drone itself could be provisioned easily when I already had the client ID and secret from gitea…
You can do this with the API! I’ve deployed gitea + drone with ansible. The code I came up with is kind of tangled because talking to APIs in ansible is tangled, and because it must create a dummy user, but it works.
Here it is:
- name: DroneCI server
# This play talks to both Gitea and Drone to connect them to each other
hosts:
- drone.data.neuropoly.org
- drone.data.dev.neuropoly.org
vars:
drone_gitea_server: '{{ ansible_nodename | replace("drone.","",1) }}'
ci_admin_user: ci-admin
tasks:
# Gitea insists on hanging all OAuth apps off specific user accounts
# so we need a dummy account to hold Drone's credentials, and we need
# to know its password. The easiest way to knowing is resetting.
# This means this play is not 100% idempotent, but this password is not meant to
# be known by anyone except ansible; the site admins can reset it for themselves
# from the web UI if they really need it.
- name: 'Generate Gitea {{ci_admin_user}}''s password'
set_fact:
# ref: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/password_lookup.html#examples
ci_admin_password: "{{ lookup('password', '/dev/null') }}"
no_log: true
- name: 'Create/reset Gitea {{ci_admin_user}}''s account/password'
delegate_to: '{{drone_gitea_server}}'
shell: |
# `admin user list` doesn't have a way to query specific users, so we've gotta filter it ourselves.
# grep by itself could get confused by a different user with "{{ci_admin_user}}" in their email address
# so we use awk to target accurately, then `| grep ''` (meaning`!= ""`) to convert it to a simple exit code
# this `if` is here to make this idempotent
if /srv/gitea/gitea admin user list | awk '$2=="{{ci_admin_user}}"' | grep '' >/dev/null; then
# dummy user exists, reset its password
/srv/gitea/gitea admin user change-password \
--username {{ci_admin_user}} \
--password {{ci_admin_password}}
else
# dummy user does not exist, create it
/srv/gitea/gitea admin user create \
--username {{ci_admin_user}} \
--email {{ci_admin_user}}@{{drone_gitea_server}} \
--password {{ci_admin_password}} \
--must-change-password=false
fi
become: yes
become_user: 'gitea'
register: ci_owner
changed_when: ci_owner.stdout | regex_search("New user") # only count creation as a change; resetting the password isn't a real change
- name: Upload Drone Logo
delegate_to: '{{drone_gitea_server}}'
file:
path: /srv/gitea/data/avatars/
state: directory
recurse: yes
owner: gitea
group: gitea
- name: Upload Drone Logo
delegate_to: '{{drone_gitea_server}}'
copy:
# TODO: make this whole script into a role and put this file in a proper subpath in it
src: drone-logo-vector-dark.png
dest: /srv/gitea/data/avatars/drone-logo-vector-dark.png
owner: gitea
group: gitea
- name: "Set Gitea {{ci_admin_user}} profile page"
# It's weird that we can't just, but since we have to have a user
# make sure users can read an explanation for it inline
uri:
method: PATCH
url: 'https://{{drone_gitea_server}}/api/v1/user/settings'
force_basic_auth: yes
user: '{{ci_admin_user}}'
password: '{{ci_admin_password}}'
body_format: json
body:
description: "Manages the connection to https://{{inventory_hostname}}"
full_name: "DroneCI Administrator"
no_log: true
- name: "Set Gitea {{ci_admin_user}} profile page (avatar)"
delegate_to: '{{drone_gitea_server}}'
# /user/settings straight up ignores avatar_url
# so circumvent it by going into the database
command: |
/usr/bin/psql -c "update public.user set avatar = 'drone-logo-vector-dark.png' where name = '{{ci_admin_user}}'"
become: yes
become_user: gitea
register: psql_output
changed_when: false
- name: 'Check for OAuth credentials'
uri:
method: GET
url: 'https://{{drone_gitea_server}}/api/v1/user/applications/oauth2'
force_basic_auth: yes
user: '{{ci_admin_user}}'
password: '{{ci_admin_password}}'
no_log: true
register: gitea_oauth
- set_fact:
oauth_app_id: "{{ (gitea_oauth.json | selectattr('name', 'equalto', 'DroneCI') | map(attribute='id') | first) if (gitea_oauth.json|length>0) else None }}"
- name: Create OAuth credentials for DroneCI via /user/applications/oauth2 API
uri:
# As with '{{ci_admin_user}}''s password,
# the best -- in this case only -- way to know the OAuth
# credentials are to reset/create them, so this also
# makes this script non-idempotent, but only a little.
# If the app already exists, use 'PATCH' to reset it,
# but if not use 'POST' to create it.
method: "{{ 'PATCH' if oauth_app_id else 'POST' }}"
url: "https://{{drone_gitea_server}}/api/v1/user/applications/oauth2{{ '/' if oauth_app_id else ''}}{{oauth_app_id}}"
#headers:
# Authorization: 'Bearer {{ci_admin_token}}'
force_basic_auth: yes
user: '{{ci_admin_user}}'
password: '{{ci_admin_password}}'
body_format: json
body:
name: "DroneCI"
redirect_uris:
# Drone's redirect_uri is *always* /login (without any trailing anything)
# NB: {{inventory_hostname}} means Drone; it's not affected by the delegate_to:
- "https://{{inventory_hostname}}/login"
status_code: [200, 201] # POST returns 201 Created on success
#delegate_to: localhost # conflicts with -e ansible_become=yes -e ansible_user=ubuntu
no_log: true
register: gitea_ci_oauth
I then have a separate self-contained role that I am writing to deploy Drone, which I feed the OAuth2 credentials into:
- include_role:
name: neuropoly.droneci
no_log: true
vars:
drone_admin_user: "{{ci_admin_user}}" # reuse the Gitea admin account name for the Drone admin account name to hopefully keep things simple
drone_oauth_id: "{{ gitea_ci_oauth.json.client_id }}"
drone_oauth_secret: "{{ gitea_ci_oauth.json.client_secret }}"
drone_runner_capacity: '{{ansible_processor_cores}}'
I plan to publish this all to Ansible Galaxy at some point, but it’s still a work in progress. There might already be something on Ansible Galaxy that I’ve missed, or maybe there will be by the time I feel ready to sshare.