#!/usr/bin/python from pdb import * import subprocess import sys import json import os keystone_conf = "/etc/keystone/keystone.conf" stored_passwd = "/var/lib/keystone/keystone.passwd" def execute(cmd, die=False): p = subprocess.Popen(cmd.split(" "), stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) stdout="" stderr="" for l in iter(p.stdout.readline, ''): print l.strip('\n') sys.stdout.flush() stdout += l for l in iter(p.stderr.readline, ''): print l.strip('\n') sys.stdout.flush() stderr += l p.communicate() rc = p.returncode if die and rc != 0: error_out("ERROR: command %s return non-zero.\n" % cmd) return (stdout, stderr, rc) def juju_log(msg): execute("juju-log \"%s\"" % msg) def error_out(msg): juju_log("FATAL ERROR: %s" % msg) exit(1) def config_get(): output = execute("config-get --format json")[0] config = json.loads(output) # make sure no config element is blank after config-get for c in config.keys(): if not config[c]: error_out("ERROR: Config option has no paramter: %s" % c) # tack on our private address and ip hostname = execute("unit-get private-address")[0].strip() ip = execute("dig +short %s" % hostname, die=True)[0].strip() config["hostname"] = hostname config["ip"] = ip return config def relation_set(relation_data): for k in relation_data: execute("relation-set %s=%s" % (k, relation_data[k]), die=True) def relation_get(relation_data): """ takes a list of options to query from the relation returns a k,v dict of the results. leave empty responses out of the results as they haven't yet been set on the other end. caller expects len(results.keys()) == len(relation_data) """ results = {} for r in relation_data: result = execute("relation-get %s" % r, die=True)[0].strip('\n') if result != "": results[r] = result return results def keystone_conf_update(opt, val): f = open(keystone_conf, "r+") orig = f.readlines() new = "" found = False for l in orig: if l.split(' ')[0] == opt: juju_log("Updating %s, setting %s = %s" % (keystone_conf, opt, val)) new += "%s = %s\n" % (opt, val) found = True else: new += l # insert a new value at the top of the file, after the 'DEFAULT' header so # as not to muck up paste deploy configuration later in the file if not found: juju_log("Adding new config option %s = %s" % (opt, val)) header = new.index("[DEAFULT]\n") new.insert((header+1), "%s = %s\n" % (key, value)) f.seek(0) f.truncate() for l in new: f.write(l) f.close def create_service_entry(manager, service_name, service_type, service_desc, owner=None): """ Add a new service entry to keystone if one does not already exist """ for service in manager.api.list_services(): if service[1] == service_name: juju_log("Service entry for '%s' already exists." % service_name) return manager.api.add_service(name=service_name, type=service_type, desc=service_desc, owner_id=owner) juju_log("Created new service entry '%s'" % service_name) def create_endpoint_template(manager, region, service, public_url, admin_url, internal_url): """ Create a new endpoint template for service if one does not already exist matching name *and* region """ for endpoint in manager.api.list_endpoint_templates(): if endpoint[1] == service and endpoint[3] == region: juju_log("Endpoint template already exists for '%s' in '%s'" % (service, region)) return manager.api.add_endpoint_template(region=region, service=service, public_url=public_url, admin_url=admin_url, internal_url=internal_url, enabled=1, is_global=1, version_id=None, version_list=None, version_info=None) juju_log("Created new endpoint template for '%s' in '%s'" % (region, service)) def create_tenant(manager, name): """ creates a tenant if it does not already exist """ tenants = manager.api.list_tenants() if not tenants or name not in map(lambda t: t[1], tenants): manager.api.add_tenant(name=name) juju_log("Created new tenant: %s" % name) return juju_log("Tenant '%s' already exists." % name) def create_user(manager, name, password, tenant): """ creates a user if it doesn't already exist, as a member of tenant """ users = manager.api.list_users() if not users and name not in map(lambda u: u[1], users): manager.api.add_user(name=name, password=password, tenant=tenant) juju_log("Created new user '%s'" % name) return juju_log("A user named '%s' already exists" % name) def create_role(manager, name, user): """ creates a role if it doesn't already exist. grants role to user """ roles = manager.api.list_roles() if not roles and name not in map(lambda r: r[1], roles): manager.api.add_role(name=name) juju_log("Created new role '%s'" % name) juju_log("A role named '%s' already exists" % name) # TODO Doesn't seem to be anyway of querying current role assignments? manager.api.grant_role(name, user) juju_log("Granted role '%s' to '%s'" % (name, user)) def generate_admin_token(manager, config): """ generate and add an admin token """ import random token = random.randrange(1000000000000, 9999999999999) manager.api.add_token(token, config["admin-user"], "admin", config["token-expiry"]) juju_log("Generated and added new random admin token.") return token def ensure_initial_admin(config): """ Ensures the minimum admin stuff exists in whatever database we're using. This and the helper functions it calls are meant to be idempotent and run during install as well as during db-changed. This will maintain the admin tenant, user, role, service entry and endpoint across every datastore we might use. TODO: Maybe seperate endpoint + service entry create to its own function? """ import manager create_tenant(manager, "admin") passwd = "" if os.path.isfile(stored_passwd): juju_log("Loading stored passwd from %s" % stored_passwd) passwd = open(stored_passwd, 'r').readline().strip('\n') if passwd == "": juju_log("Generating new passwd for user: %s" % config["admin-user"]) passwd = execute("pwgen -c 16 1", die=True)[0] open(stored_passwd, 'w+').writelines("%s\n" % passwd) create_user(manager, config["admin-user"], passwd, tenant="admin") create_role(manager, "Admin", config["admin-user"]) create_service_entry(manager, "keystone", "identity", "Keystone Identity Service") # following documentation here, perhaps we should be using juju # public/private addresses for public/internal urls. public_url = "http://%s:%s/v2.0" % (config["ip"], config["service-port"]) admin_url = "http://%s:%s/v2.0" % (config["ip"], config["admin-port"]) internal_url = "http://%s:%s/v2.0" % (config["ip"], config["service-port"]) create_endpoint_template(manager, "RegionOne", "keystone", public_url, admin_url, internal_url)