From e7f26fbf6f93bdf2059c82fc0770e3fda6effd69 Mon Sep 17 00:00:00 2001 From: strNophix Date: Mon, 14 Mar 2022 21:58:25 +0100 Subject: [PATCH] Replaced self_service.sh with service.py --- rm_customer.sh | 4 - roles/nginx-webserver/templates/index.php.j2 | 4 +- roles/postgresql/tasks/main.yml | 2 +- self_service.sh | 56 ----- service.py | 205 +++++++++++++++++++ templates/Vagrantfile.template | 16 +- 6 files changed, 217 insertions(+), 70 deletions(-) delete mode 100755 rm_customer.sh delete mode 100755 self_service.sh create mode 100755 service.py diff --git a/rm_customer.sh b/rm_customer.sh deleted file mode 100755 index 397355f..0000000 --- a/rm_customer.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash -USER_DIR=./customers/$1 -(cd $USER_DIR && vagrant destroy -f) -rm -rf $USER_DIR diff --git a/roles/nginx-webserver/templates/index.php.j2 b/roles/nginx-webserver/templates/index.php.j2 index 17ddd3d..26ba16c 100644 --- a/roles/nginx-webserver/templates/index.php.j2 +++ b/roles/nginx-webserver/templates/index.php.j2 @@ -14,9 +14,10 @@

Kernel: {{ ansible_facts.kernel }}

Memory usage: {{ ansible_facts.memfree_mb }}/{{ ansible_facts.memtotal_mb }}MB

Python version: {{ ansible_facts.python_version }}

+ {% if groups['postgresql'] is defined and groups['postgresql']|length > 0 %}

+ {% endif %} diff --git a/roles/postgresql/tasks/main.yml b/roles/postgresql/tasks/main.yml index 469b665..da8462a 100644 --- a/roles/postgresql/tasks/main.yml +++ b/roles/postgresql/tasks/main.yml @@ -63,7 +63,7 @@ become: yes community.postgresql.postgresql_user: name: postgres - password: coolshit + password: "{{ psql_pass }}" - name: Restart postgresql service service: name: postgresql diff --git a/self_service.sh b/self_service.sh deleted file mode 100755 index 9111a54..0000000 --- a/self_service.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env bash -copy_template() { - cp -f ../../templates/$1 ./$2 -} - -write_inventory_group() { - local fmt="[$1]\n" - for ((i=$2; i<$2+$3; i++)) do - fmt+="192.168.56.$i\n" - done - echo -e $fmt >> ./inventory -} - -# Take customer inputs -read -p "Klantnaam: " customerName -# read -p "IpInt: " ipAddr -# read -p "Number of webservers: " numWebserver -# read -p "Number of loadbalancers: " numLoadbalancers -# read -p "Number of postgresql instances: " numPostgresql -ipAddr=15 -numWebserver=0 -numLoadbalancers=0 -numPostgresql=1 - -# Create customer directory and cd -mkdir -p ./customers/$customerName && cd $_ - -# Copy and fill-in necessary templates -copy_template ./Vagrantfile.template ./Vagrantfile -sed -i "s/#{customerName}/$customerName/" ./Vagrantfile -sed -i "s/#{ipAddr}/$ipAddr/" ./Vagrantfile -sed -i "s/#{numWebserver}/$numWebserver/" ./Vagrantfile -sed -i "s/#{numLoadbalancers}/$numLoadbalancers/" ./Vagrantfile -sed -i "s/#{numPostgresql}/$numPostgresql/" ./Vagrantfile - -copy_template ./ansible.cfg.template ./ansible.cfg - -# Generate ansible inventory file. -ipOffset=$ipAddr - -write_inventory_group "webserver" $ipOffset $numWebserver -((ipOffset+=numWebserver)) - -write_inventory_group "loadbalancer" $ipOffset $numLoadbalancers -((ipOffset+=numLoadbalancers)) - -write_inventory_group "postgresql" $ipOffset $numPostgresql -((ipOffset+=numPostgresql)) - -# Generate a new seperate ssh key for the customer -mkdir -p ./.ssh/ -ssh-keygen -t rsa -b 2048 -f ./.ssh/id_rsa - -# Provision and configure machines -vagrant up -ansible-playbook ../../site.yml diff --git a/service.py b/service.py new file mode 100755 index 0000000..890df9d --- /dev/null +++ b/service.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python3 +import argparse +import sys +from os import path +from pathlib import Path +import os +import time +from typing import Any, Callable, DefaultDict, Iterable, Mapping +from jinja2 import Template +import itertools +import subprocess as sub +import shutil +import string +import secrets + +TEMPLATE_DIR = "./templates" + + +def gen_pass() -> str: + alphabet = string.ascii_letters + string.digits + password = "".join(secrets.choice(alphabet) for _ in range(20)) + return password + + +def check_positive(value: str): + ivalue = int(value) + if ivalue <= 0: + raise Exception("Supplied number must be >= 0") + return ivalue + + +def encode_member(member: str, mapping: Mapping[str, Any]) -> str: + return member + " " + " ".join([f"{k}={v}" for k, v in mapping.items()]) + + +def iter_ips(ip_format: str, start_octet: int): + ip_int = start_octet + ip_fmt = ip_format + while ip_int < 255: + yield ip_fmt.format(ip_int) + ip_int += 1 + + +class InventoryWriter: + def __init__(self, location: str) -> None: + self._file_handle = Path(location) + self._groups: dict[str, set[str]] = DefaultDict(set) + + def add(self, name: str, members: Iterable[str]): + self._groups[name] |= set(members) + + def _build_group(self, name: str, members: set[str]): + fmt = f"[{name}]\n" + "\n".join(members) + return fmt + + def flush(self): + txt = "" + for name, members in self._groups.items(): + txt += self._build_group(name, members) + "\n\n" + self._file_handle.write_text(txt, encoding="utf8") + + +def copy_template(src: str, dest: str, mapping: Mapping[str, Any] = {}): + c = Path(src).read_text() + t: Template = Template(c) + r = t.render(mapping) + Path(dest).write_text(r) + + +def list_envs(args: argparse.Namespace): + try: + customer_path = path.join("customers", args.customer_name, "envs") + print(" ".join(os.listdir(customer_path))) + except FileNotFoundError: + raise Exception(f"Customer `{args.customer_name}` does not exist.") + + +def delete_env(args: argparse.Namespace): + for env in args.env_names: + env_path = path.join("customers", args.customer_name, "envs", env) + sub.call(["vagrant", "destroy", "-f"], cwd=env_path) + shutil.rmtree(env_path) + print(f"Deleted `{env}` from customer `{ args.customer_name}`") + + +def create_env(args: argparse.Namespace): + if (args.num_nginx_web + args.num_nginx_lb + args.num_postgres) == 0: + raise Exception("At least one item should be deployed") + + env_path = path.join("customers", args.customer_name, "envs", args.env_name) + Path(env_path).mkdir(exist_ok=True, parents=True) + + # Template `ansible.cfg` + src = path.join(TEMPLATE_DIR, "ansible.cfg.template") + dest = path.join(env_path, "ansible.cfg") + copy_template(src=src, dest=dest) + + # Create inventory file + inv_path = path.join(env_path, "inventory") + iw = InventoryWriter(inv_path) + ip_generator = iter_ips(args.ip_format, args.ip_int) + + web_ips = itertools.islice(ip_generator, args.num_nginx_web) + iw.add("webserver", web_ips) + + lb_ips = itertools.islice(ip_generator, args.num_nginx_lb) + iw.add("loadbalancer", lb_ips) + + psql_gen_pass: Callable[[str], str] = lambda x: encode_member( + x, {"psql_pass": gen_pass()} + ) + + psql_ips = list(itertools.islice(ip_generator, args.num_postgres)) + psql_ips = map(psql_gen_pass, psql_ips) + iw.add("postgresql", psql_ips) + + iw.flush() + + # Template `Vagrantfile` + src = path.join(TEMPLATE_DIR, "Vagrantfile.template") + dest = path.join(env_path, "Vagrantfile") + + mapping = { + "env": args.env_name, + "customer_name": args.customer_name, + "ip_int": args.ip_int, + "ip_format": args.ip_format.replace("{}", "%d"), + "num_webserver": args.num_nginx_web, + "num_loadbalancers": args.num_nginx_lb, + "num_postgres": args.num_postgres, + } + copy_template(src=src, dest=dest, mapping=mapping) + + # Generate .ssh + ssh_dir = path.join(env_path, ".ssh") + Path(ssh_dir).mkdir(exist_ok=True) + ssh_key_cmd = [ + "ssh-keygen", + "-t", + "rsa", + "-b", + "2048", + "-f", + path.join(ssh_dir, "id_rsa"), + ] + sub.call(ssh_key_cmd) + + # Provision and configure machines + sub.call(["vagrant", "up"], cwd=env_path) + time.sleep(1) + sub.call(["ansible-playbook", "../../../../site.yml"], cwd=env_path) + + +def main() -> int: + parser = argparse.ArgumentParser() + sub_parser = parser.add_subparsers() + + list_parser = sub_parser.add_parser("list", help="list customer-owned environments") + list_parser.add_argument("customer_name", type=str, help="name of the customer") + list_parser.set_defaults(func=list_envs) + + cenv_parser = sub_parser.add_parser("create", help="create a new environment") + cenv_parser.add_argument("customer_name", type=str, help="name of the customer") + cenv_parser.add_argument("env_name", type=str, help="name of the environment") + cenv_parser.add_argument( + "--num-postgres", + type=check_positive, + help="number of postgres databases", + default=0, + ) + cenv_parser.add_argument( + "--num-nginx-web", + type=check_positive, + help="number of nginx webservers", + default=0, + ) + cenv_parser.add_argument( + "--num-nginx-lb", + type=check_positive, + help="number of nginx loadbalancers", + default=0, + ) + cenv_parser.add_argument( + "--ip-format", type=str, help="format of ip", default="192.168.56.{}" + ) + cenv_parser.add_argument( + "--ip-int", type=check_positive, help="4th octet to start at", default=10 + ) + cenv_parser.set_defaults(func=create_env) + + denv_parser = sub_parser.add_parser("delete", help="delete an environment") + denv_parser.add_argument("customer_name", type=str, help="name of the customer") + denv_parser.add_argument( + "env_names", type=str, nargs="+", help="name of one or more environments" + ) + + denv_parser.set_defaults(func=delete_env) + + args = parser.parse_args(sys.argv[1:]) + args.func(args) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/templates/Vagrantfile.template b/templates/Vagrantfile.template index 5304945..2fef70e 100644 --- a/templates/Vagrantfile.template +++ b/templates/Vagrantfile.template @@ -1,7 +1,7 @@ -$ip_int = #{ipAddr} +$ip_int = {{ ip_int }} def increment_ip() - ip = "192.168.56.%d" % [$ip_int] + ip = "{{ ip_format }}" % [$ip_int] $ip_int += 1 return ip end @@ -10,12 +10,12 @@ Vagrant.configure("2") do |config| config.ssh.insert_key = false config.ssh.private_key_path = ["./.ssh/id_rsa","~/.vagrant.d/insecure_private_key"] - num_webserver = #{numWebserver} - num_loadbalancer = #{numLoadbalancers} - num_postgresql = #{numPostgresql} + num_webserver = {{ num_webserver }} + num_loadbalancer = {{ num_loadbalancers }} + num_postgresql = {{ num_postgres }} (1..num_webserver).each do |nth| - machine_id = "#{customerName}-bloated-debian-web%d" % [nth] + machine_id = "{{ customer_name }}-{{ env }}-web%d" % [nth] machine_ip = increment_ip() config.vm.define machine_id do |web| @@ -34,7 +34,7 @@ Vagrant.configure("2") do |config| end (1..num_loadbalancer).each do |nth| - machine_id = "#{customerName}-bloated-debian-lb%d" % [nth] + machine_id = "{{ customer_name }}-{{ env }}-lb%d" % [nth] machine_ip = increment_ip() config.vm.define machine_id do |web| @@ -53,7 +53,7 @@ Vagrant.configure("2") do |config| end (1..num_postgresql).each do |nth| - machine_id = "#{customerName}-bloated-debian-db%d" % [nth] + machine_id = "{{ customer_name }}-{{ env }}-db%d" % [nth] machine_ip = increment_ip() config.vm.define machine_id do |web|