def _fetch_mapping_basics(self, name): if not self: return self.status try: self.cursor.execute( "SELECT prefix, service, rewrite FROM mappings WHERE name = :name", locals()) if self.cursor.rowcount == 0: return RichStatus.fromError("mapping %s not found" % name) if self.cursor.rowcount > 1: return RichStatus.fromError( "mapping %s matched more than one entry?" % name) # We know there's exactly one mapping match. Good. prefix, service, rewrite = self.cursor.fetchone() return RichStatus.OK(name=name, prefix=prefix, service=service, rewrite=rewrite) except pg8000.Error as e: return RichStatus.fromError( "fetch_mapping_basics %s: could not fetch info: %s" % (name, e))
def setup(): try: conn = get_db("postgres") conn.autocommit = True cursor = conn.cursor() cursor.execute( "SELECT 1 FROM pg_database WHERE datname = 'ambassador'") results = cursor.fetchall() if not results: cursor.execute("CREATE DATABASE ambassador") conn.close() except pg8000.Error as e: return RichStatus.fromError("no ambassador database in setup: %s" % e) try: conn = get_db("ambassador") cursor = conn.cursor() cursor.execute(AMBASSADOR_TABLE_SQL) cursor.execute(PRINCIPAL_TABLE_SQL) conn.commit() conn.close() except pg8000.Error as e: return RichStatus.fromError("no data tables in setup: %s" % e) return RichStatus.OK()
def _fetch_consumer_basics(self, where): if not self: return self.status try: sql = "SELECT consumer_id, username, fullname, shortname FROM consumers WHERE %s" % where.sql self.cursor.execute(sql, where.keys) if self.cursor.rowcount == 0: return RichStatus.fromError("consumer %s not found" % where.hr) if self.cursor.rowcount > 1: return RichStatus.fromError( "consumer %s matched more than one entry?" % where.hr) # We know there's exactly one consumer match. Good. consumer_id, username, fullname, shortname = self.cursor.fetchone() return RichStatus.OK(consumer_id=consumer_id, username=username, fullname=fullname, shortname=shortname) except pg8000.Error as e: return RichStatus.fromError( "fetch_consumer_basics %s: could not fetch info: %s" % (where.hr, e))
def getIncomingJSON(req, *needed): """ Pull incoming arguments from JSON into a RichStatus. 'needed' specifies keys that are mandatory, but _all keys are converted_, so for any optional things, just check if the key is present in the returned RichStatus. If any 'needed' keys are missing, returns a False RichStatus including error text. If all 'needed' keys are present, returns a True RichStatus with elements copied from the input. """ try: incoming = req.get_json() except Exception as e: return RichStatus.fromError("invalid JSON: %s" % e) logging.debug("getIncomingJSON: %s" % incoming) if not incoming: incoming = {} missing = [] for key in needed: if key not in incoming: missing.append(key) if missing: return RichStatus.fromError("Required fields missing: %s" % " ".join(missing)) else: return RichStatus.OK(**incoming)
def wrapper(*args, **kwds): rc = RichStatus.fromError("impossible error") logging.debug("%s: method %s" % (func_name, request.method)) try: rc = f(*args, **kwds) except Exception as e: logging.exception(e) rc = RichStatus.fromError("%s: %s failed: %s" % (func_name, request.method, e)) return jsonify(rc.toDict())
def delete_mapping(self, name): if not self: return self.status try: # Have to delete modules first since it holds a foreign key rc = self._delete_mapping_modules(name) if not rc: self.conn.rollback() return rc modules_deleted = rc.modules_deleted rc = self._delete_mapping_basics(name) if not rc: self.conn.rollback() return rc self.conn.commit() return RichStatus.OK(name=name, deleted=rc.deleted, modules_deleted=modules_deleted) except pg8000.Error as e: return RichStatus.fromError( "delete_mapping %s: could not delete mapping: %s" % (name, e))
def delete_consumer(self, consumer_id): if not self: return self.status try: # Have to delete modules first since it holds a foreign key rc = self._delete_consumer_modules(consumer_id) if not rc: self.conn.rollback() return rc modules_deleted = rc.modules_deleted rc = self._delete_consumer_basics(consumer_id) if not rc: self.conn.rollback() return rc self.conn.commit() return RichStatus.OK(consumer_id=consumer_id, deleted=rc.deleted, modules_deleted=modules_deleted) except pg8000.Error as e: return RichStatus.fromError( "delete_consumer %s: could not delete consumer: %s" % (consumer_id, e))
def store_consumer(self, consumer_id, username, fullname, shortname, modules): if not self: return self.status try: rc = self._store_consumer_basics(consumer_id, username, fullname, shortname) if not rc: self.conn.rollback() return rc for module_name in modules.keys(): rc = self._store_consumer_module(consumer_id, module_name, modules[module_name]) if not rc: self.conn.rollback() return rc self.conn.commit() return RichStatus.OK(consumer_id=consumer_id) except pg8000.Error as e: return RichStatus.fromError( "store_consumer %s: could not store consumer: %s" % (consumer_id, e))
def _get_connection(self, autocommit=False): # Figure out where the DB lives... self.db_name = "postgres" self.db_host = "ambassador-store" self.db_port = 5432 if "AMBASSADOR_DB_NAME" in os.environ: self.db_name = os.environ["AMBASSADOR_DB_NAME"] if "AMBASSADOR_DB_HOST" in os.environ: self.db_host = os.environ["AMBASSADOR_DB_HOST"] if "AMBASSADOR_DB_PORT" in os.environ: self.db_port = int(os.environ["AMBASSADOR_DB_PORT"]) conn = None try: conn = pg8000.connect(user="******", password="******", database=self.db_name, host=self.db_host, port=self.db_port) # Start with autocommit on. conn.autocommit = True except pg8000.Error as e: self.status = RichStatus.fromError( "could not connect to db %s:%d - %s" % (self.db_host, self.db_port, e)) return conn
def generate_envoy_config(self, template=None, template_dir=None): # Finally! Render the template to JSON... envoy_json = self.to_json(template=template, template_dir=template_dir) rc = RichStatus.fromError("impossible") # ...and use the JSON parser as a final sanity check. try: obj = json.loads(envoy_json) rc = RichStatus.OK(msg="Envoy configuration OK", envoy_config=obj) except json.decoder.JSONDecodeError as e: rc = RichStatus.fromError("Invalid Envoy configuration: %s" % str(e), raw=envoy_json, exception=e) return rc
def handle_principal_post(req, name): try: rc = getIncomingJSON(req, 'fingerprint') logging.debug("handle_principal_post %s: got args %s" % (name, rc.toDict())) if not rc: return rc fingerprint = rc.fingerprint logging.debug("handle_principal_post %s: fingerprint %s" % (name, fingerprint)) conn = get_db("ambassador") cursor = conn.cursor() cursor.execute('INSERT INTO principals VALUES(:name, :fingerprint)', locals()) conn.commit() return RichStatus.OK(name=name) except pg8000.Error as e: return RichStatus.fromError("%s: could not save info: %s" % (name, e))
def handle_mapping_post(req, name): try: rc = getIncomingJSON(req, 'prefix', 'service') logging.debug("handle_mapping_post %s: got args %s" % (name, rc.toDict())) if not rc: return rc prefix = rc.prefix service = rc.service rewrite = '/' if 'rewrite' in rc: rewrite = rc.rewrite logging.debug("handle_mapping_post %s: pfx %s => svc %s (rewrite %s)" % (name, prefix, service, rewrite)) conn = get_db("ambassador") cursor = conn.cursor() cursor.execute( 'INSERT INTO mappings VALUES(:name, :prefix, :service, :rewrite)', locals()) conn.commit() app.reconfigurator.trigger() return RichStatus.OK(name=name) except pg8000.Error as e: return RichStatus.fromError("%s: could not save info: %s" % (name, e))
def validate_object(self, obj): # Each object must be a dict, and must include "apiVersion" # and "type" at toplevel. if not isinstance(obj, collections.Mapping): return RichStatus.fromError("not a dictionary") if not (("apiVersion" in obj) and ("kind" in obj) and ("name" in obj)): return RichStatus.fromError("must have apiVersion, kind, and name") obj_version = obj['apiVersion'] obj_kind = obj['kind'] obj_name = obj['name'] if obj_version.startswith("ambassador/"): obj_version = obj_version.split('/')[1] else: return RichStatus.fromError("apiVersion %s unsupported" % obj_version) schema_key = "%s-%s" % (obj_version, obj_kind) schema = self.schemas.get(schema_key, None) if not schema: schema_path = os.path.join(self.schema_dir_path, obj_version, "%s.schema" % obj_kind) try: schema = json.load(open(schema_path, "r")) except OSError: self.logger.debug("no schema at %s, skipping" % schema_path) except json.decoder.JSONDecodeError as e: self.logger.warning("corrupt schema at %s, skipping (%s)" % (schema_path, e)) if schema: self.schemas[schema_key] = schema try: jsonschema.validate(obj, schema) except jsonschema.exceptions.ValidationError as e: return RichStatus.fromError("not a valid %s: %s" % (obj_kind, e)) return RichStatus.OK(msg="valid %s" % obj_kind, details=(obj_kind, obj_version, obj_name))
def _autocommit(self, setting): if not self: return if self.conn: self.conn.autocommit = setting else: self.status = RichStatus.fromError( "cannot set autocommit with no connection")
def root(): rc = RichStatus.fromError("impossible error") logging.debug("handle_services: method %s" % request.method) try: rc = setup() if rc: if request.method == 'PUT': app.reconfigurator.trigger() rc = RichStatus.OK(msg="reconfigure requested") else: rc = handle_service_list(request) except Exception as e: logging.exception(e) rc = RichStatus.fromError("handle_services: %s failed: %s" % (request.method, e)) return jsonify(rc.toDict())
def fetch_mapping_module(self, name, module_name=None): if not self: return self.status if not module_name: return RichStatus.fromError( "fetch_mapping_module: module_name is required") return self._fetch_mapping_modules(name, module_name=module_name)
def handle_service(name): rc = RichStatus.fromError("impossible error") logging.debug("handle_service %s: method %s" % (name, request.method)) try: rc = setup() if rc: if request.method == 'POST': rc = handle_service_post(request, name) elif request.method == 'DELETE': rc = handle_service_del(request, name) else: rc = handle_service_get(request, name) except Exception as e: logging.exception(e) rc = RichStatus.fromError("%s: %s failed: %s" % (name, request.method, e)) return jsonify(rc.toDict())
def fetch_consumer_module(self, consumer_id, module_name=None): if not self: return self.status if not module_name: return RichStatus.fromError( "fetch_consumer_module: module_name is required") return self._fetch_consumer_modules(consumer_id, module_name=module_name)
def handle_service_get(req, name): try: conn = get_db("ambassador") cursor = conn.cursor() cursor.execute("SELECT prefix FROM services WHERE name = :name", locals()) [ prefix ] = cursor.fetchone() return RichStatus.OK(name=name, prefix=prefix) except pg8000.Error as e: return RichStatus.fromError("%s: could not fetch info: %s" % (name, e))
def handle_principal_get(req, name): try: conn = get_db("ambassador") cursor = conn.cursor() cursor.execute("SELECT fingerprint FROM principals WHERE name = :name", locals()) [fingerprint] = cursor.fetchone() return RichStatus.OK(name=name, fingerprint=fingerprint) except pg8000.Error as e: return RichStatus.fromError("%s: could not fetch info: %s" % (name, e))
def getIncomingJSON(req, *needed): try: incoming = req.get_json() except Exception as e: return RichStatus.fromError("invalid JSON: %s" % e) logging.debug("getIncomingJSON: %s" % incoming) if not incoming: incoming = {} missing = [] for key in needed: if key not in incoming: missing.append(key) if missing: return RichStatus.fromError("Required fields missing: %s" % " ".join(missing)) else: return RichStatus.OK(**incoming)
def _get_cursor(self): if not self: return cursor = None try: cursor = self.conn.cursor() except pg8000.Error as e: self.status = RichStatus.fromError( "could not get database cursor: %s" % e) return cursor
def store_principal(self, name, fingerprint): if not self: return self.status try: self.cursor.execute( 'INSERT INTO principals VALUES(:name, :fingerprint)', locals()) self.conn.commit() return RichStatus.OK(name=name) except pg8000.Error as e: return RichStatus.fromError( "store_principal %s: could not save info: %s" % (name, e))
def handle_service_del(req, name): try: conn = get_db("ambassador") cursor = conn.cursor() cursor.execute("DELETE FROM services WHERE name = :name", locals()) conn.commit() app.reconfigurator.trigger() return RichStatus.OK(name=name) except pg8000.Error as e: return RichStatus.fromError("%s: could not delete service: %s" % (name, e))
def handle_approved(): rc = RichStatus.fromError("impossible error") logging.debug("handle_principals: method %s" % request.method) try: rc = setup() if rc: rc = handle_principal_list(request) if rc: principals = [{ "fingerprint_sha256": x['fingerprint'] } for x in rc.principals] rc = RichStatus.OK(certificates=principals) except Exception as e: logging.exception(e) rc = RichStatus.fromError("handle_principals: %s failed: %s" % (request.method, e)) return jsonify(rc.toDict())
def _verify_database(self): if not self: return try: self.cursor.execute( "SELECT 1 FROM pg_database WHERE datname = 'ambassador'") results = self.cursor.fetchall() if not results: self.cursor.execute("CREATE DATABASE ambassador") except pg8000.Error as e: self.status = RichStatus.fromError("no ambassador database: %s" % e)
def fetch_all_modules(self): if not self: return self.status try: self.cursor.execute( "SELECT name, config FROM modules ORDER BY name") modules = {name: config for name, config in self.cursor} return RichStatus.OK(modules=modules, count=len(modules.keys())) except pg8000.Error as e: return RichStatus.fromError( "fetch_all_modules: could not fetch info: %s" % e)
def fetch_module(self, name): if not self: return self.status try: self.cursor.execute( "SELECT config FROM modules WHERE name = :name", locals()) if self.cursor.rowcount == 0: return RichStatus.fromError("module %s not found" % name) if self.cursor.rowcount > 1: return RichStatus.fromError( "module %s matched more than one entry?" % name) # We know there's exactly one module match. Good. config = self.cursor.fetchone() return RichStatus.OK(name=name, config=config) except pg8000.Error as e: return RichStatus.fromError( "fetch_module %s: could not fetch info: %s" % (name, e))
def delete_module(self, name): if not self: return self.status try: self.cursor.execute("DELETE FROM modules WHERE name = :name", locals()) deleted = self.cursor.rowcount self.conn.commit() return RichStatus.OK(name=name, deleted=deleted) except pg8000.Error as e: return RichStatus.fromError( "delete_module %s: could not delete module: %s" % (name, e))
def _delete_mapping_basics(self, name): if not self: return self.status try: self.cursor.execute("DELETE FROM mappings WHERE name = :name", locals()) deleted = self.cursor.rowcount return RichStatus.OK(name=name, deleted=deleted) except pg8000.Error as e: return RichStatus.fromError( "delete_mapping %s: could not delete mapping info: %s" % (name, e))