def revert( structures, state, conn_data, features=None, dry_run: bool = False, simulation: bool = False, version: int = None, force: bool = False, ): feature_version = None if version is not None: if len(features) != 1: raise ValueError("cannot specify version with multiple features") feature_version = features[0] raise NotImplementedError("Cannot revert to a version yet") if features is None: features = state.keys() orig_features = features features = calc_deps_list(structures, features, False) for feature in features: if feature not in state and not (force and feature in orig_features): continue struct = structures[feature] if feature == feature_version: pass # TODO: implement print(struct.revert) if not dry_run: if not simulation: psql_command(None, struct.revert, **conn_data) declare_revert(state, feature, **conn_data)
def deploy( features, state, structures, conn_data, dry_run=False, simulation=False, reapply=False, except_target=False, ): if not features: features = structures.keys() base_features = features features = calc_deps_list(structures, features) reversions = [ feature for feature in features if needs_revert(state, structures, feature) ] if reversions and (dry_run or not simulation): reversions = calc_deps_list(structures, reversions, False) for feature in reversions: struct = structures[feature] print(struct.revert) if not dry_run: psql_command(None, struct.revert, **conn_data) del state[feature] features.append(feature) features = calc_deps_list(structures, features) for feature in features: if except_target and feature in base_features: continue reapply_ = (reapply and feature in base_features and structures[feature].head.idempotent) for feature_, path, prev_version, sha1sum in calc_apply_target( state, structures, feature, reapply_): print(path) if not dry_run: if not simulation: psql_command(None, path, **conn_data) declare_deploy(state, feature, prev_version, sha1sum, **conn_data)
def create_database(data, conn_data, dropdb=False): database = data["database"] member = database + "__member" if not test_user_exists(member, **conn_data): psql_command(f"CREATE ROLE {member}", **conn_data) for usert in ("owner", "client"): user = data[usert] has_pass = data.get(usert + "_password", None) password = has_pass or token_urlsafe(16) user_conn = conn_data.copy() user_conn.update(dict(user=user, password=password)) if has_pass: # check connection try: test_user_exists(user, **user_conn) # assume permissions are ok continue except AssertionError: pass else: data[usert + "_password"] = password extra_perms = "CREATEROLE" if usert == "owner" else "NOINHERIT" if test_user_exists(user, **conn_data): psql_command( f"ALTER ROLE {user} WITH LOGIN {extra_perms} ENCRYPTED PASSWORD '{password}'", **conn_data, ) else: psql_command( f"CREATE USER {user} WITH LOGIN {extra_perms} ENCRYPTED PASSWORD '{password}'", **conn_data, ) auth_secret = data.get("auth_secret", None) or token_urlsafe(32) data["auth_secret"] = auth_secret owner = data["owner"] exists = test_db_exists(database, **conn_data) if exists and dropdb: extra_roles = psql_command( f"select string_agg(rolname, ', ') from pg_catalog.pg_roles where rolname like '{database}\_\__\_%'", **conn_data, ).strip() psql_command(f"DROP DATABASE {database}", **conn_data) if extra_roles: psql_command(f"DROP ROLE {extra_roles}", **conn_data) exists = False if not exists: psql_command( f"CREATE DATABASE {database} WITH OWNER {owner} ENCODING UTF8", **conn_data) else: if (psql_command( f"SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_catalog.pg_database WHERE datname='{database}'", **conn_data, ).strip() != owner): psql_command(f"ALTER {database} SET OWNER TO {owner}", **conn_data) # TODO: this may already be the case psql_command(f"ALTER GROUP {owner} ADD USER {data['client']}", **conn_data) psql_command(f"ALTER GROUP {member} ADD USER {data['client']}", **conn_data) psql_command( f"ALTER DATABASE {database} SET \"app.jwt_secret\" TO '{auth_secret}'", **conn_data, ) conn_data = conn_data.copy() conn_data["db"] = database psql_command(f"ALTER SCHEMA public OWNER TO {owner}", **conn_data) return data
def test_user_exists(role, **kwargs): return (psql_command( f"select rolname from pg_catalog.pg_roles where rolname='{role}'", **kwargs).strip() == role)
def test_db_exists(test, **kwargs): return (psql_command( f"select datname from pg_catalog.pg_database where datname='{test}'", **kwargs, ).strip() == test)
"--remove", default=[], action="append", help="the permissions to remove (can be repeated)", ) args = argp.parse_args() permissions = args.permissions or ["superadmin"] db = args.database user = args.user conn_data = dict( user=ini_file[db]["owner"], password=ini_file[db]["owner_password"], port=ini_file["postgres"].get("port", 5432), db=ini_file[db]["database"], ) selector = f" WHERE email='{user}'" if "@" in user else f" WHERE handle='{user}'" existing = psql_command("SELECT permissions FROM members" + selector, **conn_data) existing = set( existing.strip().strip('"').strip("{").strip("}").split(",")) existing.discard("") permissions = (existing | set(permissions)) - set(args.remove) print("existing permissions:", existing) if existing != permissions: permissionsS = ",".join([f"'{p}'" for p in permissions]) psql_command( f"UPDATE members SET permissions=ARRAY[{permissionsS}]::permission[] {selector}", **conn_data, ) print("new permissions: ", permissions)
def declare_revert(state, feature, **conn_data): psql_command(f"DELETE FROM deploy_state WHERE feature='{feature}'", **conn_data) state.pop(feature, None)
def declare_deploy(state, feature, prev_version, sha1sum, **conn_data): psql_command( f"""INSERT INTO deploy_state (feature, prev_version, sha1sum) VALUES ('{feature}', {prev_version}, '{sha1sum}') ON CONFLICT (feature) DO UPDATE SET prev_version=EXCLUDED.prev_version, sha1sum=EXCLUDED.sha1sum""", **conn_data, ) state[feature] = (prev_version, sha1sum)
def init_db(conn_data): psql_command( """CREATE TABLE IF NOT EXISTS deploy_state(feature varchar primary key, prev_version smallint, sha1sum varchar(40))""", **conn_data, )
def db_state(conn_data): results = psql_command("select * from deploy_state", **conn_data) results = [x.split(",") for x in results.split("\n") if x] return {row[0]: (int(row[1]), row[2]) for row in results}