Пример #1
0
def get_cred_config() -> Dict[str, str]:
    if "CLOUD_SQL_CREDENTIALS_SECRET" in os.environ:
        name = os.environ["CLOUD_SQL_CREDENTIALS_SECRET"]
        client = secretmanager.SecretManagerServiceClient()
        response = client.access_secret_version(request={"name": name})
        logger.info("Credentials pulled from CLOUD_SQL_CREDENTIALS_SECRET")
        return json.loads(response.payload.data.decode("UTF-8"))
    # [END cloudrun_user_auth_secrets]
    else:
        logger.info(
            "CLOUD_SQL_CREDENTIALS_SECRET env var not set. Defaulting to environment variables."
        )
        if "DB_USER" not in os.environ:
            raise Exception("DB_USER needs to be set.")

        if "DB_PASSWORD" not in os.environ:
            raise Exception("DB_PASSWORD needs to be set.")

        if "DB_NAME" not in os.environ:
            raise Exception("DB_NAME needs to be set.")

        if "CLOUD_SQL_CONNECTION_NAME" not in os.environ:
            raise Exception("CLOUD_SQL_CONNECTION_NAME needs to be set.")

        return {
            "DB_USER": os.environ["DB_USER"],
            "DB_PASSWORD": os.environ["DB_PASSWORD"],
            "DB_NAME": os.environ["DB_NAME"],
            "DB_HOST": os.environ.get("DB_HOST", None),
            "CLOUD_SQL_CONNECTION_NAME": os.environ["CLOUD_SQL_CONNECTION_NAME"],
        }
Пример #2
0
def init_tcp_connection_engine(
        db_config: Dict[str, str]) -> sqlalchemy.engine.base.Engine:
    creds = credentials.get_cred_config()
    db_user = creds["DB_USER"]
    db_pass = creds["DB_PASSWORD"]
    db_name = creds["DB_NAME"]
    db_host = creds["DB_HOST"]

    # Extract host and port from db_host
    host_args = db_host.split(":")
    db_hostname, db_port = host_args[0], int(host_args[1])

    pool = sqlalchemy.create_engine(
        # Equivalent URL:
        # postgres+pg8000://<db_user>:<db_pass>@<db_host>:<db_port>/<db_name>
        sqlalchemy.engine.url.URL(
            drivername="postgresql+pg8000",
            username=db_user,  # e.g. "my-database-user"
            password=db_pass,  # e.g. "my-database-password"
            host=db_hostname,  # e.g. "127.0.0.1"
            port=db_port,  # e.g. 5432
            database=db_name,  # e.g. "my-database-name"
        ),
        **db_config,
    )
    pool.dialect.description_encoding = None
    logger.info("Database engine initialised from tcp connection")

    return pool
Пример #3
0
def init_unix_connection_engine(
        db_config: Dict[str, str]) -> sqlalchemy.engine.base.Engine:
    creds = credentials.get_cred_config()
    db_user = creds["DB_USER"]
    db_pass = creds["DB_PASSWORD"]
    db_name = creds["DB_NAME"]
    db_socket_dir = creds.get("DB_SOCKET_DIR", "/cloudsql")
    cloud_sql_connection_name = creds["CLOUD_SQL_CONNECTION_NAME"]

    pool = sqlalchemy.create_engine(
        # Equivalent URL:
        # postgres+pg8000://<db_user>:<db_pass>@/<db_name>
        #                         ?unix_sock=<socket_path>/<cloud_sql_instance_name>/.s.PGSQL.5432
        sqlalchemy.engine.url.URL(
            drivername="postgresql+pg8000",
            username=db_user,  # e.g. "my-database-user"
            password=db_pass,  # e.g. "my-database-password"
            database=db_name,  # e.g. "my-database-name"
            query={
                "unix_sock":
                "{}/{}/.s.PGSQL.5432".format(
                    db_socket_dir,
                    cloud_sql_connection_name  # e.g. "/cloudsql"
                )  # i.e "<PROJECT-NAME>:<INSTANCE-REGION>:<INSTANCE-NAME>"
            },
        ),
        **db_config,
    )
    pool.dialect.description_encoding = None
    logger.info("Database engine initialised from unix conection")

    return pool
Пример #4
0
def init_connection_engine() -> Dict[str, int]:
    if os.getenv("TRAMPOLINE_CI", None):
        logger.info("Using NullPool for testing")
        db_config = {"poolclass": NullPool}
    else:
        db_config = {
            # Pool size is the maximum number of permanent connections to keep.
            "pool_size": 5,
            # Temporarily exceeds the set pool_size if no connections are available.
            "max_overflow": 2,
            # The total number of concurrent connections for your application will be
            # a total of pool_size and max_overflow.
            # SQLAlchemy automatically uses delays between failed connection attempts,
            # but provides no arguments for configuration.
            # 'pool_timeout' is the maximum number of seconds to wait when retrieving a
            # new connection from the pool. After the specified amount of time, an
            # exception will be thrown.
            "pool_timeout": 30,  # 30 seconds
            # 'pool_recycle' is the maximum number of seconds a connection can persist.
            # Connections that live longer than the specified amount of time will be
            # reestablished
            "pool_recycle": 1800,  # 30 minutes
        }

    if os.environ.get("DB_HOST"):
        return init_tcp_connection_engine(db_config)
    else:
        return init_unix_connection_engine(db_config)
def get_cred_config() -> Dict[str, str]:
    secret = os.environ.get("CLOUD_SQL_CREDENTIALS_SECRET")
    if secret:
        return json.loads(secret)
    # [END cloudrun_user_auth_secrets]
    else:
        logger.info(
            "CLOUD_SQL_CREDENTIALS_SECRET env var not set. Defaulting to environment variables."
        )
        if "DB_USER" not in os.environ:
            raise Exception("DB_USER needs to be set.")

        if "DB_PASSWORD" not in os.environ:
            raise Exception("DB_PASSWORD needs to be set.")

        if "DB_NAME" not in os.environ:
            raise Exception("DB_NAME needs to be set.")

        if "CLOUD_SQL_CONNECTION_NAME" not in os.environ:
            raise Exception("CLOUD_SQL_CONNECTION_NAME needs to be set.")

        return {
            "DB_USER": os.environ["DB_USER"],
            "DB_PASSWORD": os.environ["DB_PASSWORD"],
            "DB_NAME": os.environ["DB_NAME"],
            "DB_HOST": os.environ.get("DB_HOST", None),
            "CLOUD_SQL_CONNECTION_NAME":
            os.environ["CLOUD_SQL_CONNECTION_NAME"],
        }
Пример #6
0
def shutdown() -> None:
    # Find all Sessions in memory and close them.
    close_all_sessions()
    logger.info("All sessions closed.")
    # Each connection was released on execution, so just formally
    # dispose of the db connection if it's been instantiated
    if db:
        db.dispose()
        logger.info("Database connection disposed.")
Пример #7
0
def save_vote(team: str, uid: str, time_cast: datetime.datetime) -> None:
    # Preparing a statement before hand can help protect against injections.
    stmt = sqlalchemy.text("INSERT INTO pet_votes (time_cast, candidate, uid)"
                           " VALUES (:time_cast, :candidate, :uid)")

    # Using a with statement ensures that the connection is always released
    # back into the pool at the end of statement (even if an error occurs)
    with db.connect() as conn:
        conn.execute(stmt, time_cast=time_cast, candidate=team, uid=uid)
    logger.info("Vote for %s saved.", team)
Пример #8
0
def create_tables() -> None:

    # This is called before any request on the main app, ensuring the database has been setup
    logger.info("Creating tables")
    global db
    db = init_connection_engine()
    # Create pet_votes table if it doesn't already exist
    with db.connect() as conn:
        conn.execute("CREATE TABLE IF NOT EXISTS pet_votes"
                     "( vote_id SERIAL NOT NULL, "
                     "time_cast timestamp NOT NULL, "
                     "candidate VARCHAR(6) NOT NULL, "
                     "uid VARCHAR(128) NOT NULL, "
                     "PRIMARY KEY (vote_id)"
                     ");")
Пример #9
0
def shutdown_handler(signal: int, frame: FrameType) -> None:
    logger.info("Signal received, safely shutting down.")
    database.shutdown()
    middleware.logging_flush()
    print("Exiting process.", flush=True)
    sys.exit(0)