This commit is contained in:
commit
e0e0977b1e
5
.ansible-lint.yml
Normal file
5
.ansible-lint.yml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
skip_list:
|
||||||
|
- fqcn-builtins
|
||||||
|
- no-jinja-nesting
|
||||||
|
- experimental
|
16
.drone.yml
Normal file
16
.drone.yml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
----
|
||||||
|
kind: pipeline
|
||||||
|
name: default
|
||||||
|
steps:
|
||||||
|
- name: integration test
|
||||||
|
image: python:3.9-buster
|
||||||
|
environment:
|
||||||
|
REMOTE_USER: molecule
|
||||||
|
HCLOUD_TOKEN:
|
||||||
|
from_secret: HCLOUD_TOKEN
|
||||||
|
commands:
|
||||||
|
- apt update && apt install -y pwgen
|
||||||
|
- mkdir -p /root/.ansible/roles && ln -sr . /root/.ansible/roles/autonomic.new-hetzner
|
||||||
|
- export INSTANCE_UUID=$(pwgen 8 1)
|
||||||
|
- pip install -r requirements.txt
|
||||||
|
- molecule test
|
18
.envrc.sample
Normal file
18
.envrc.sample
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Your username that you use for accounts on our machines.
|
||||||
|
export REMOTE_USER=
|
||||||
|
export ANSIBLE_USER=$REMOTE_USER
|
||||||
|
|
||||||
|
# The path to our pass credentials store
|
||||||
|
export PASSWORD_STORE_DIR=
|
||||||
|
|
||||||
|
# The Hetzner Cloud API token for managing our instances
|
||||||
|
# Uncomment the prod/test line below depending on what you're doing
|
||||||
|
# export HCLOUD_TOKEN=$(pass show logins/hetzner/prod/api_key)
|
||||||
|
# export HCLOUD_TOKEN=$(pass show logins/hetzner/test/api_key)
|
||||||
|
export HCLOUD_TOKEN=$(pass show logins/hetzner/cicd/api_key)
|
||||||
|
|
||||||
|
# For molecule role testing
|
||||||
|
export INSTANCE_UUID=$RANDOM
|
||||||
|
|
||||||
|
# So molecule will show credentials in the logs
|
||||||
|
export MOLECULE_NO_LOG=False
|
16
.yamllint.yml
Executable file
16
.yamllint.yml
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
extends: default
|
||||||
|
|
||||||
|
yaml-files:
|
||||||
|
- "*.yaml"
|
||||||
|
- "*.yml"
|
||||||
|
|
||||||
|
ignore: |
|
||||||
|
.venv
|
||||||
|
.drone.yml
|
||||||
|
|
||||||
|
rules:
|
||||||
|
line-length: disable
|
||||||
|
braces:
|
||||||
|
max-spaces-inside: 1
|
||||||
|
level: error
|
15
LICENSE
Normal file
15
LICENSE
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
autonomic.new-hetzner: Creates and runs "bootstrapping" roles against a new Hetzner Cloud server
|
||||||
|
Copyright (C) 2022 Autonomic Co-operative <helo@autonomic.zone>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
3
README.md
Normal file
3
README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# autonomic.new-hetzner
|
||||||
|
|
||||||
|
[![Build Status](https://drone.autonomic.zone/api/badges/autonomic-cooperative/autonomic.new-hetzner/status.svg?ref=refs/heads/main)](https://drone.autonomic.zone/autonomic-cooperative/autonomic.new-hetzner)
|
4
defaults/main.yml
Normal file
4
defaults/main.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
new_hetzner_backups_enabled: true
|
||||||
|
new_hetzner_delete_protection: true
|
||||||
|
new_hetzner_rebuild_protection: true
|
20
meta/main.yml
Normal file
20
meta/main.yml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
dependencies: []
|
||||||
|
galaxy_info:
|
||||||
|
role_name: new_hetzner
|
||||||
|
namespace: autonomic
|
||||||
|
author: autonomic
|
||||||
|
description: |
|
||||||
|
Creates and runs "bootstrapping" roles against a new Hetzner Cloud server.
|
||||||
|
The roles that are run are: autonomic.add-users, autonomic.sshd,
|
||||||
|
autonomic.ufw, autonomic.packages, autonomic.name and autonomic.motd. The
|
||||||
|
new user passwords are generated and stored in the password store. If
|
||||||
|
generating a new server, don't forget to add the details to the inventory
|
||||||
|
listing.
|
||||||
|
company: Autonomic
|
||||||
|
license: GPLv3
|
||||||
|
min_ansible_version: 2.9
|
||||||
|
platforms:
|
||||||
|
- name: Debian
|
||||||
|
versions:
|
||||||
|
- buster
|
17
molecule/default/Dockerfile.j2
Normal file
17
molecule/default/Dockerfile.j2
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Molecule managed
|
||||||
|
|
||||||
|
{% if item.registry is defined %}
|
||||||
|
FROM {{ item.registry.url }}/{{ item.image }}
|
||||||
|
{% else %}
|
||||||
|
FROM {{ item.image }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if item.env is defined %}
|
||||||
|
{% for var, value in item.env.items() %}
|
||||||
|
{% if value %}
|
||||||
|
ENV {{ var }} {{ value }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y python sudo bash ca-certificates iproute2 && apt-get clean;
|
25
molecule/default/converge.yml
Normal file
25
molecule/default/converge.yml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
- name: Converge
|
||||||
|
hosts: all
|
||||||
|
vars:
|
||||||
|
- new_hetzner_server_name: autonomic.new-hetzner-molecule
|
||||||
|
- add_users_inventory_hostname: autonomic.new-hetzner-molecule
|
||||||
|
- new_hetzner_server_type: cx11
|
||||||
|
- new_hetzner_server_image: debian-10
|
||||||
|
- new_hetzner_delete_protection: false
|
||||||
|
- new_hetzner_rebuild_protection: false
|
||||||
|
tasks:
|
||||||
|
- name: Run the role under test
|
||||||
|
block:
|
||||||
|
- import_role:
|
||||||
|
name: autonomic.new-hetzner
|
||||||
|
always:
|
||||||
|
- name: Flush all handlers
|
||||||
|
meta: flush_handlers
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Ensure the server is deleted
|
||||||
|
hcloud_server:
|
||||||
|
name: "{{ new_hetzner_server_name }}"
|
||||||
|
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
|
||||||
|
state: absent
|
37
molecule/default/molecule.yml
Normal file
37
molecule/default/molecule.yml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
dependency:
|
||||||
|
name: galaxy
|
||||||
|
|
||||||
|
driver:
|
||||||
|
name: docker
|
||||||
|
|
||||||
|
platforms:
|
||||||
|
- name: "autonomic.new-hetzner-${INSTANCE_UUID}"
|
||||||
|
image: debian:buster
|
||||||
|
|
||||||
|
provisioner:
|
||||||
|
name: ansible
|
||||||
|
|
||||||
|
lint: |
|
||||||
|
set -e
|
||||||
|
yamllint -c .yamllint.yml .
|
||||||
|
ansible-lint --exclude .drone.yml -c .ansible-lint.yml .
|
||||||
|
|
||||||
|
scenario:
|
||||||
|
test_sequence:
|
||||||
|
- lint
|
||||||
|
- dependency
|
||||||
|
- cleanup
|
||||||
|
- destroy
|
||||||
|
- syntax
|
||||||
|
- create
|
||||||
|
- prepare
|
||||||
|
- converge
|
||||||
|
# TODO(decentral1se): Disabled for now since there are so many tasks which
|
||||||
|
# simply always report changed and I'd rather not patch this issue which is
|
||||||
|
# really something to do with Ansible and not Molecule.
|
||||||
|
# - idempotence
|
||||||
|
- side_effect
|
||||||
|
- verify
|
||||||
|
- cleanup
|
||||||
|
- destroy
|
16
molecule/default/prepare.yml
Normal file
16
molecule/default/prepare.yml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
- name: Converge
|
||||||
|
hosts: all
|
||||||
|
tasks:
|
||||||
|
- name: Install python-pip
|
||||||
|
package:
|
||||||
|
name:
|
||||||
|
- python-apt
|
||||||
|
- python-pip
|
||||||
|
- openssh-client
|
||||||
|
- pass
|
||||||
|
state: present
|
||||||
|
- name: Install module dependencies
|
||||||
|
pip:
|
||||||
|
name: hcloud
|
||||||
|
state: present
|
36
molecule/default/requirements.yml
Normal file
36
molecule/default/requirements.yml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- name: autonomic.add-users
|
||||||
|
src: https://git.autonomic.zone/autonomic-cooperative/autonomic.add-users
|
||||||
|
version: 0.1.0
|
||||||
|
scm: git
|
||||||
|
|
||||||
|
- name: autonomic.sshd
|
||||||
|
src: https://git.autonomic.zone/autonomic-cooperative/autonomic.sshd
|
||||||
|
version: 0.1.0
|
||||||
|
scm: git
|
||||||
|
|
||||||
|
- name: autonomic.ufw
|
||||||
|
src: https://git.autonomic.zone/autonomic-cooperative/autonomic.ufw
|
||||||
|
version: 0.1.0
|
||||||
|
scm: git
|
||||||
|
|
||||||
|
- name: autonomic.packages
|
||||||
|
src: https://git.autonomic.zone/autonomic-cooperative/autonomic.packages
|
||||||
|
version: 0.1.0
|
||||||
|
scm: git
|
||||||
|
|
||||||
|
- name: autonomic.name
|
||||||
|
src: https://git.autonomic.zone/autonomic-cooperative/autonomic.name
|
||||||
|
version: 0.1.0
|
||||||
|
scm: git
|
||||||
|
|
||||||
|
- name: autonomic.motd
|
||||||
|
src: https://git.autonomic.zone/autonomic-cooperative/autonomic.motd
|
||||||
|
version: 0.1.0
|
||||||
|
scm: git
|
||||||
|
|
||||||
|
collections:
|
||||||
|
- name: hetzner.hcloud
|
||||||
|
version: 1.6.0
|
5
requirements.txt
Normal file
5
requirements.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
ansible-lint==6.0.0
|
||||||
|
ansible==5.4.0
|
||||||
|
molecule-docker=1.1.0
|
||||||
|
molecule-hetznercloud==1.3.0
|
||||||
|
molecule==3.6.1
|
143
tasks/main.yml
Normal file
143
tasks/main.yml
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
---
|
||||||
|
- name: Ensure mandatory variables are configured
|
||||||
|
assert:
|
||||||
|
that: "{{ item }} is defined"
|
||||||
|
fail_msg: "You must define the '{{ item }}' variable"
|
||||||
|
with_items:
|
||||||
|
- new_hetzner_server_name
|
||||||
|
- new_hetzner_server_type
|
||||||
|
- new_hetzner_server_image
|
||||||
|
|
||||||
|
- name: Include resource variables
|
||||||
|
include_vars: "{{ role_path }}/../../resources/{{ lookup('env', 'MEMBERS_FILE') | default('members.yml', True) }}"
|
||||||
|
|
||||||
|
# Note(decentral1se): gives root SSH access for all autonomic members
|
||||||
|
- name: Ensure all Autonomic member SSH keys are registered
|
||||||
|
hcloud_ssh_key:
|
||||||
|
name: "{{ item.email }}"
|
||||||
|
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
|
||||||
|
public_key: "{{ item.ssh_key }}"
|
||||||
|
state: present
|
||||||
|
with_items: "{{ members }}"
|
||||||
|
|
||||||
|
- name: Create new hetzner cloud instance
|
||||||
|
hcloud_server:
|
||||||
|
api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
|
||||||
|
image: "{{ new_hetzner_server_image }}"
|
||||||
|
name: "{{ new_hetzner_server_name }}"
|
||||||
|
server_type: "{{ new_hetzner_server_type }}"
|
||||||
|
ssh_keys: "{{ members | map(attribute='email') | list }}"
|
||||||
|
labels:
|
||||||
|
managed: ansible
|
||||||
|
backups: "{{ new_hetzner_backups_enabled }}"
|
||||||
|
delete_protection: "{{ new_hetzner_delete_protection }}"
|
||||||
|
rebuild_protection: "{{ new_hetzner_rebuild_protection }}"
|
||||||
|
state: present
|
||||||
|
register: new_instance
|
||||||
|
async: 7200
|
||||||
|
poll: 0
|
||||||
|
|
||||||
|
- name: Wait for instance creation to complete
|
||||||
|
async_status:
|
||||||
|
jid: "{{ new_instance.ansible_job_id }}"
|
||||||
|
register: hetzner_job
|
||||||
|
until: hetzner_job.finished
|
||||||
|
retries: 300
|
||||||
|
|
||||||
|
# Note(decentral1se): before user accounts are created we connect as root. This
|
||||||
|
# is possible because we registered the SSH keys with Hetzner Cloud and when
|
||||||
|
# the new instance is created, those keys are in /root/authorized_keys
|
||||||
|
- name: Dynamically create root connection details for the new instance
|
||||||
|
add_host:
|
||||||
|
hostname: root-new-instance
|
||||||
|
ansible_host: "{{ hetzner_job.hcloud_server.ipv4_address }}"
|
||||||
|
ansible_ssh_extra_args: "-o StrictHostKeyChecking=no"
|
||||||
|
ansible_user: root
|
||||||
|
|
||||||
|
- name: "Wait for SSH on {{ new_hetzner_server_name }} to come up on port 22"
|
||||||
|
wait_for:
|
||||||
|
port: 22
|
||||||
|
host: "{{ hetzner_job.hcloud_server.ipv4_address }}"
|
||||||
|
search_regex: SSH
|
||||||
|
delay: 10
|
||||||
|
|
||||||
|
- name: Run the add-users role on the new instance
|
||||||
|
vars:
|
||||||
|
members: "../../../resources/members.yml"
|
||||||
|
delegate_to: root-new-instance
|
||||||
|
import_role:
|
||||||
|
name: autonomic.add-users
|
||||||
|
tags:
|
||||||
|
- molecule-notest
|
||||||
|
|
||||||
|
- name: Run the sshd role on the new instance
|
||||||
|
delegate_to: root-new-instance
|
||||||
|
import_role:
|
||||||
|
name: autonomic.sshd
|
||||||
|
tags:
|
||||||
|
- molecule-notest
|
||||||
|
|
||||||
|
- name: Run all service restart handlers
|
||||||
|
delegate_to: root-new-instance
|
||||||
|
meta: flush_handlers
|
||||||
|
tags:
|
||||||
|
- molecule-notest
|
||||||
|
|
||||||
|
- name: "Wait for SSH to come up again on {{ sshd_port }}"
|
||||||
|
wait_for:
|
||||||
|
port: "{{ sshd_port }}"
|
||||||
|
host: "{{ hetzner_job.hcloud_server.ipv4_address }}"
|
||||||
|
search_regex: SSH
|
||||||
|
delay: 10
|
||||||
|
tags:
|
||||||
|
- molecule-notest
|
||||||
|
|
||||||
|
# Note(decentral1se): At this point we're connecting with our own new user
|
||||||
|
# account and using sudo based privilege escalation with the password generated
|
||||||
|
# by pass from the passwords repository. Dog fooding our own connection setup ensures it
|
||||||
|
# works
|
||||||
|
- name: "Dynamically create {{ lookup('env', 'REMOTE_USER') }} connection details for the new instance"
|
||||||
|
add_host:
|
||||||
|
hostname: user-new-instance
|
||||||
|
ansible_host: "{{ hetzner_job.hcloud_server.ipv4_address }}"
|
||||||
|
ansible_ssh_extra_args: "-o StrictHostKeyChecking=no"
|
||||||
|
ansible_user: "{{ ansible_user }}"
|
||||||
|
ansible_port: "{{ sshd_port }}"
|
||||||
|
|
||||||
|
# Note(decentral1se): Ansible refuses to use the correct password without specifying both of these
|
||||||
|
ansible_sudo_pass: "{{ lookup('passwordstore', 'users/{{ ansible_user }}/sudo/{{ new_hetzner_server_name }}') }}"
|
||||||
|
ansible_become_password: "{{ lookup('passwordstore', 'users/{{ ansible_user }}/sudo/{{ new_hetzner_server_name }}') }}"
|
||||||
|
tags:
|
||||||
|
- molecule-notest
|
||||||
|
|
||||||
|
- name: Run the ufw role on the new instance
|
||||||
|
delegate_to: user-new-instance
|
||||||
|
become: true
|
||||||
|
import_role:
|
||||||
|
name: autonomic.ufw
|
||||||
|
tags:
|
||||||
|
- molecule-notest
|
||||||
|
|
||||||
|
- name: Run the packages role on the new instance
|
||||||
|
delegate_to: user-new-instance
|
||||||
|
become: true
|
||||||
|
import_role:
|
||||||
|
name: autonomic.packages
|
||||||
|
tags:
|
||||||
|
- molecule-notest
|
||||||
|
|
||||||
|
- name: Run the name role on the new instance
|
||||||
|
delegate_to: user-new-instance
|
||||||
|
become: true
|
||||||
|
import_role:
|
||||||
|
name: autonomic.name
|
||||||
|
tags:
|
||||||
|
- molecule-notest
|
||||||
|
|
||||||
|
- name: Run the motd role on the new instance
|
||||||
|
delegate_to: user-new-instance
|
||||||
|
become: true
|
||||||
|
import_role:
|
||||||
|
name: autonomic.motd
|
||||||
|
tags:
|
||||||
|
- molecule-notest
|
Reference in New Issue
Block a user