From d8cd2c15e34f32f4a869672dd65b7eb2a2163e50 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Fri, 8 Jun 2018 19:40:11 +0300 Subject: Change: support settings that are per-context This allows me to use hetznertool for personal stuff as well as work stuff. --- hetznertool | 195 +++++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 122 insertions(+), 73 deletions(-) diff --git a/hetznertool b/hetznertool index 919536e..3608177 100755 --- a/hetznertool +++ b/hetznertool @@ -8,24 +8,23 @@ import subprocess import yaml -CONFIG_FILENAME = os.path.expanduser('~/.config/hetznertool/hetznertool.yaml') +CONFIG_DIR = os.path.expanduser('~/.config/hetznertool') +DEFAULT_PROFILE = 'hetznertool' default_config = { - 'dnszone-dir': os.path.expanduser('~/qvarnlabs/code/dnszone'), - 'dnszone-file': 'db.h', - 'ansible-inventory-dir': '.', 'ssh-key': None, 'ns1': 'root@ns1.qvarnlabs.net', } def main(): - config = read_config() + profile = os.environ.get('HETZNERTOOL_PROFILE', DEFAULT_PROFILE) + config = read_config(profile) parser = create_parser(config) args = vars(parser.parse_args()) func = args['func'] - func(args) + func(config, args) def hcloud(*args): @@ -61,8 +60,8 @@ def parse_server_line(line): } -def dns_name(context, name): - return '{}-{}.h.qvarnlabs.eu'.format(context, name) +def dns_name(context, name, domain): + return '{}-{}.{}'.format(context, name, domain) class ServerSpecification(dict): @@ -100,49 +99,101 @@ class ServerSpecification(dict): -def create_func(args): +def create_func(config, args): spec = ServerSpecification() spec.from_file(args['specfile']) - use_context(args['context']) + context = args['context'] + use_context(context) + for name in spec.get_servers(): server = spec.get_server(name) - print('creating {} in {}'.format(name, args['context'])) - hcloud( - 'server', 'create', - '--name', name, - '--image', server['image'], - '--type', server['type'], - '--ssh-key', args['ssh_key'], - ) - update_zone_file(args) - write_inventory_files(args) - - -def list_func(args): + print('creating {} in {}'.format(name, context)) + if args['act']: + hcloud( + 'server', 'create', + '--name', name, + '--image', server['image'], + '--type', server['type'], + '--ssh-key', args['ssh_key'], + ) + if args['act']: + zonedir = get_zonedir_for_context(config, context) + zonefile = get_zonefile_for_context(config, context) + kick = get_kick_for_context(config, context) + domain = get_domain_for_context(config, context) + style = get_style_for_context(config, context) + update_zone_file(style, domain, kick, args, zonedir, zonefile) + write_inventory_files(config, args) + + +def list_func(config, args): contexts = list_contexts() for context in contexts: use_context(context) + domain = get_domain_for_context(config, context) + assert domain is not None for info in list_servers(): - domain = dns_name(context, info['name']) - print(domain, info['ipv4']) + name = dns_name(context, info['name'], domain) + print(name, info['ipv4']) + + +def get_config_for_context(config, context): + return config.get('contexts', {}).get(context, {}) + + +def get_domain_for_context(config, context): + cc = get_config_for_context(config, context) + return cc.get('domain') + + +def get_zonedir_for_context(config, context): + cc = get_config_for_context(config, context) + return cc.get('dnszone-dir') + + +def get_zonefile_for_context(config, context): + cc = get_config_for_context(config, context) + return cc.get('dnszone-file') -def delete_func(args): - use_context(args['context']) +def get_inventorydir_for_context(config, context): + cc = get_config_for_context(config, context) + return cc.get('ansible-inventory-dir') + + +def get_kick_for_context(config, context): + cc = get_config_for_context(config, context) + return cc.get('kick_bind9') + + +def get_style_for_context(config, context): + cc = get_config_for_context(config, context) + return cc.get('style') + + +def delete_func(config, args): + context = args['context'] + use_context(context) + domain = get_domain_for_context(config, context) for info in list_servers(): - domain = dns_name(args['context'], info['name']) + name = dns_name(args['context'], info['name'], domain) print( 'deleting {} ({} in {})'.format( - domain, info['name'], args['context'])) - hcloud('server', 'delete', info['name']) - update_zone_file(args) - write_inventory_files(args) + name, info['name'], args['context'])) + if args['act']: + hcloud('server', 'delete', info['name']) + if args['act']: + zonedir = get_zonedir_for_context(config, context) + zonefile = get_zonefile_for_context(config, context) + kick = get_kick_for_context(config, context) + style = get_style_for_context(config, context) + update_zone_file(style, domain, kick, args, zonedir, zonefile) + write_inventory_files(config, args) -def update_zone_file(args): - dirname = args['dnszone_dir'] - basename = args['dnszone_file'] + +def update_zone_file(style, domain, kick, args, dirname, basename): filename = os.path.join(dirname, basename) serial_name = os.path.join(dirname, 'serial') @@ -150,24 +201,33 @@ def update_zone_file(args): subprocess.check_call(['git', 'pull'], cwd=dirname) - serial = int(open(serial_name).readline().strip()) - serial += 1 - - with open(filename, 'w') as f: - write_zone(f, serial) - - open(serial_name, 'w').write('{}\n'.format(serial)) + if style == 'qvarnlabs': + serial = int(open(serial_name).readline().strip()) + serial += 1 + open(serial_name, 'w').write('{}\n'.format(serial)) + with open(filename, 'w') as f: + write_qvarnlabs_zone(f, serial) + elif style == 'dns-api': + serial = None + with open(filename, 'w') as f: + write_dnsapi_zone(f, domain) + else: + assert 0 + filenames = [basename] + if serial is not None: + filename.append(serial) subprocess.check_call( - ['git', 'commit', '-m', 'automatic zone update', basename, 'serial'], + ['git', 'commit', '-m', 'automatic zone update'] + filenames, cwd=dirname) subprocess.check_call(['git', 'push'], cwd=dirname) - kick_bind9(args['ns1'], filename, basename) + if kick: + kick_bind9(args['ns1'], filename, basename) -def write_zone(stream, serial): +def write_qvarnlabs_zone(stream, serial): stream.write(''' $TTL 30 $ORIGIN h.qvarnlabs.eu. @@ -187,10 +247,19 @@ $ORIGIN h.qvarnlabs.eu. '{}-{} IN A {}\n'.format(context, info['name'], info['ipv4'])) -def write_inventory_files(args): - dirname = args['ansible_inventory_dir'] +def write_dnsapi_zone(stream, domain): + for context in list_contexts(): + use_context(context) + for info in list_servers(): + stream.write( + '+{}-{}.{}:{}:60\n'.format( + context, info['name'], domain, info['ipv4'])) + + +def write_inventory_files(config, args): for context in list_contexts(): use_context(context) + dirname = get_inventorydir_for_context(config, context) filename = os.path.join(dirname, 'hosts.{}'.format(context)) print('Writing Ansible inventory file {}'.format(filename)) with open(filename, 'w') as f: @@ -207,9 +276,9 @@ def kick_bind9(ssh_target, filename, basename): -def read_config(): +def read_config(profile): config = copy.deepcopy(default_config) - filename = CONFIG_FILENAME + filename = os.path.join(CONFIG_DIR, '{}.yaml'.format(profile)) if os.path.exists(filename): with open(filename) as f: config.update(yaml.safe_load(f)) @@ -223,6 +292,8 @@ def create_parser(config): factory = parser.add_subparsers() create = factory.add_parser('create') + create.add_argument( + '-n', '--no-act', dest='act', default=True, action='store_false') create.add_argument( 'context', help='hcloud context to create VMs in') create.add_argument( @@ -233,18 +304,6 @@ def create_parser(config): required='ssh-key' not in config, default=config['ssh-key'], help='create VM so it allow login via ssh key uploaded as KEYNAME') - create.add_argument( - '--ansible-inventory-dir', - metavar='DIR', - required='ansible-inventory-dir' not in config, - default=config['ansible-inventory-dir'], - help='create Ansible inventory files in DIR') - create.add_argument( - '--dnszone-dir', default=config['dnszone-dir'], - metavar='DIR', help='write DNS zone directory into DIR') - create.add_argument( - '--dnszone-file', default=config['dnszone-file'], - metavar='FILE', help='write DNS zone directory into FILE') create.add_argument( '--ns1', default=config['ns1'], required='ns1' not in config, @@ -255,19 +314,9 @@ def create_parser(config): delete = factory.add_parser('delete') delete.add_argument( - 'context', help='hcloud context to delete all VMs from') - delete.add_argument( - '--ansible-inventory-dir', - metavar='DIR', - required='ansible-inventory-dir' not in config, - default=config['ansible-inventory-dir'], - help='create Ansible inventory files in DIR') + '-n', '--no-act', dest='act', default=True, action='store_false') delete.add_argument( - '--dnszone-dir', default=config['dnszone-dir'], - metavar='DIR', help='write DNS zone directory into DIR') - delete.add_argument( - '--dnszone-file', default=config['dnszone-file'], - metavar='FILE', help='write DNS zone directory into FILE') + 'context', help='hcloud context to delete all VMs from') delete.add_argument( '--ns1', default=config['ns1'], required='ns1' not in config, -- cgit v1.2.1