Browse Source

init

pull/2/head
decentral1se 2 months ago
commit
e0e0977b1e
No known key found for this signature in database
GPG Key ID: 3789458B3D0C410
  1. 5
      .ansible-lint.yml
  2. 16
      .drone.yml
  3. 18
      .envrc.sample
  4. 16
      .yamllint.yml
  5. 15
      LICENSE
  6. 3
      README.md
  7. 4
      defaults/main.yml
  8. 20
      meta/main.yml
  9. 17
      molecule/default/Dockerfile.j2
  10. 25
      molecule/default/converge.yml
  11. 37
      molecule/default/molecule.yml
  12. 16
      molecule/default/prepare.yml
  13. 36
      molecule/default/requirements.yml
  14. 5
      requirements.txt
  15. 143
      tasks/main.yml

5
.ansible-lint.yml

@ -0,0 +1,5 @@
---
skip_list:
- fqcn-builtins
- no-jinja-nesting
- experimental

16
.drone.yml

@ -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

@ -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

@ -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

@ -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

@ -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

@ -0,0 +1,4 @@
---
new_hetzner_backups_enabled: true
new_hetzner_delete_protection: true
new_hetzner_rebuild_protection: true

20
meta/main.yml

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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
Loading…
Cancel
Save