Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
enyachoke committed Feb 5, 2024
0 parents commit 79e0614
Show file tree
Hide file tree
Showing 6 changed files with 260 additions and 0 deletions.
26 changes: 26 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: CI

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
release:
types: [published]

jobs:
docker-pre-release:
uses: mekomsolutions/shared-github-workflow/.github/workflows/docker-build-publish.yml@main
with:
image-name: "superset-ozonepro"
secrets:
DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_REGISTRY_USERNAME }}
DOCKER_HUB_PASSWORD: ${{ secrets.DOCKER_HUB_REGISTRY_PASSWORD }}

docker-release:
uses: mekomsolutions/shared-github-workflow/.github/workflows/docker-build-publish.yml@main
with:
image-name: "superset-ozonepro"
secrets:
DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_REGISTRY_USERNAME }}
DOCKER_HUB_PASSWORD: ${{ secrets.DOCKER_HUB_REGISTRY_PASSWORD }}
12 changes: 12 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM amancevice/superset:3.1.0
# Switching to root to install the required packages
USER root

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

# Switching back to using the `superset` user
USER superset
COPY ./superset-init.sh /etc/superset
COPY ./superset_config.py /etc/superset
COPY ./security.py /etc/superset
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
flask-oidc==1.3.0
flask_openid
itsdangerous==2.0.1
58 changes: 58 additions & 0 deletions security.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from flask import redirect, request
from flask_appbuilder.security.manager import AUTH_OID
from superset.security import SupersetSecurityManager
from flask_oidc import OpenIDConnect
from flask_appbuilder.security.views import AuthOIDView
from flask_login import login_user
from urllib.parse import quote
from flask_appbuilder.views import ModelView, SimpleFormView, expose
import logging
logger = logging.getLogger(__name__)

class AuthOIDCView(AuthOIDView):
def add_role_if_missing(self, sm, user_id, role_name):
found_role = sm.find_role(role_name)
session = sm.get_session
user = session.query(sm.user_model).get(user_id)
if found_role and found_role not in user.roles:
user.roles += [found_role]
session.commit()

@expose('/login/', methods=['GET', 'POST'])
def login(self, flag=True):
sm = self.appbuilder.sm
oidc = sm.oid


@self.appbuilder.sm.oid.require_login
def handle_login():
user = sm.auth_user_oid(oidc.user_getfield('email'))
if user is None:
info = oidc.user_getinfo(['preferred_username', 'given_name', 'family_name', 'email','roles'])
user = sm.add_user(info.get('preferred_username'), info.get('given_name'), info.get('family_name'), info.get('email'), sm.find_role('Gamma'))
role_info = oidc.user_getinfo(['roles'])
if role_info is not None:
for role in role_info['roles']:
self.add_role_if_missing(sm, user.id, role)
login_user(user, remember=False)
return redirect(self.appbuilder.get_url_for_index)

return handle_login()

@expose('/logout/', methods=['GET', 'POST'])
def logout(self):

oidc = self.appbuilder.sm.oid

oidc.logout()
super(AuthOIDCView, self).logout()
redirect_url = request.url_root.strip('/') + self.appbuilder.get_url_for_login

return redirect(oidc.client_secrets.get('issuer') + '/protocol/openid-connect/logout?redirect_uri=' + quote(redirect_url))

class OIDCSecurityManager(SupersetSecurityManager):
authoidview = AuthOIDCView
def __init__(self,appbuilder):
super(OIDCSecurityManager, self).__init__(appbuilder)
if self.auth_type == AUTH_OID:
self.oid = OpenIDConnect(self.appbuilder.get_app)
49 changes: 49 additions & 0 deletions superset-init.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
STEP_CNT=6

echo_step() {
cat <<EOF
######################################################################
Init Step ${1}/${STEP_CNT} [${2}] -- ${3}
######################################################################
EOF
}
# Initialize the database
echo_step "1" "Starting" "Applying DB migrations"
superset db upgrade
echo_step "1" "Complete" "Applying DB migrations"

# Create an admin user
echo_step "2" "Starting" "Setting up admin user ( $ADMIN_USERNAME / $ADMIN_PASSWORD )"
superset fab create-admin \
--username $ADMIN_USERNAME \
--firstname Superset \
--lastname Admin \
--email [email protected] \
--password $ADMIN_PASSWORD
echo_step "2" "Complete" "Setting up admin user"
# Create default roles and permissions
echo_step "3" "Starting" "Setting up roles and perms"
superset init

echo_step "3" "Complete" "Setting up roles and perms"
if [ "$SUPERSET_LOAD_EXAMPLES" = "yes" ]; then
# Load some data to play with" row_number() over(partition by visit.patient_id order by visit.visit_id) as number_occurences," +
echo_step "4" "Starting" "Loading examples"
# If Cypress run which consumes superset_test_config – load required data for tests
if [ "$CYPRESS_CONFIG" == "true" ]; then
superset load_test_users
superset load_examples --load-test-data
else
superset load_examples
fi
echo_step "4" "Complete" "Loading examples"
fi
echo_step "5" "Complete" "Loading datasources"
# superset import-datasources -p /etc/superset/datasources/datasources.yaml
superset import-datasources --recursive --path /etc/superset/datasources
superset import-dashboards --recursive --path /etc/superset/dashboards
echo_step "5" "Complete" "Loading datasources"
echo_step "6" "Complete" "Updating datasources"
superset set_database_uri -d $ANALYTICS_DATASOURCE_NAME -u postgresql://$ANALYTICS_DB_USER:$ANALYTICS_DB_PASSWORD@$ANALYTICS_DB_HOST:5432/$ANALYTICS_DB_NAME
echo_step "6" "Complete" "Updating datasources"
112 changes: 112 additions & 0 deletions superset_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import logging
import os
from dotenv import load_dotenv
from cachelib import RedisCache

from cachelib.file import FileSystemCache
from flask_appbuilder.security.manager import AUTH_OID
from security import OIDCSecurityManager

logger = logging.getLogger()

def password_from_env(url):
return os.getenv("ANALYTICS_DB_PASSWORD")

SQLALCHEMY_CUSTOM_PASSWORD_STORE = password_from_env

def get_env_variable(var_name, default=None):
"""Get the environment variable or raise exception."""
try:
return os.environ[var_name]
except KeyError:
if default is not None:
return default
else:
error_msg = "The environment variable {} was missing, abort...".format(
var_name
)
raise EnvironmentError(error_msg)


MAPBOX_API_KEY = os.getenv('MAPBOX_API_KEY', '')

DATABASE_DIALECT = get_env_variable("DATABASE_DIALECT", "postgres")
DATABASE_USER = get_env_variable("DATABASE_USER", "superset")
DATABASE_PASSWORD = get_env_variable("DATABASE_PASSWORD", "superset")
DATABASE_HOST = get_env_variable("DATABASE_HOST", "postgres")
DATABASE_PORT = get_env_variable("DATABASE_PORT", 5432)
DATABASE_DB = get_env_variable("DATABASE_DB", "superset")

SQLALCHEMY_TRACK_MODIFICATIONS = get_env_variable("SQLALCHEMY_TRACK_MODIFICATIONS", True)
SECRET_KEY = get_env_variable("SECRET_KEY", 'thisISaSECRET_1234')

# The SQLAlchemy connection string.
SQLALCHEMY_DATABASE_URI = "%s://%s:%s@%s:%s/%s" % (
DATABASE_DIALECT,
DATABASE_USER,
DATABASE_PASSWORD,
DATABASE_HOST,
DATABASE_PORT,
DATABASE_DB,
)

REDIS_HOST = get_env_variable("REDIS_HOST", "redis")
REDIS_PORT = get_env_variable("REDIS_PORT", 6379)
REDIS_CELERY_DB = get_env_variable("REDIS_CELERY_DB", 0)
REDIS_RESULTS_DB = get_env_variable("REDIS_CELERY_DB", 1)

RESULTS_BACKEND = RedisCache(host=REDIS_HOST, port=REDIS_PORT, key_prefix='superset_results')
# RESULTS_BACKEND = FileSystemCache("/app/superset_home/sqllab")

class CeleryConfig(object):
BROKER_URL = f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_CELERY_DB}"
CELERY_IMPORTS = ("superset.sql_lab",)
CELERY_RESULT_BACKEND = f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_RESULTS_DB}"
CELERY_ANNOTATIONS = {"tasks.add": {"rate_limit": "10/s"}}
CELERY_TASK_PROTOCOL = 1

CACHE_CONFIG = {
'CACHE_TYPE': 'redis',
'CACHE_DEFAULT_TIMEOUT': 300,
'CACHE_KEY_PREFIX': 'superset_',
'CACHE_REDIS_HOST': 'redis',
'CACHE_REDIS_PORT': 6379,
'CACHE_REDIS_DB': 1,
'CACHE_REDIS_URL': 'redis://redis:6379/1'
}

CELERY_CONFIG = CeleryConfig
SQLLAB_CTAS_NO_LIMIT = True
PERMANENT_SESSION_LIFETIME = 86400

class ReverseProxied(object):

def __init__(self, app):
self.app = app

def __call__(self, environ, start_response):
script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
if script_name:
environ['SCRIPT_NAME'] = script_name
path_info = environ['PATH_INFO']
if path_info.startswith(script_name):
environ['PATH_INFO'] = path_info[len(script_name):]

scheme = environ.get('HTTP_X_SCHEME', '')
if scheme:
environ['wsgi.url_scheme'] = scheme
return self.app(environ, start_response)


ADDITIONAL_MIDDLEWARE = [ReverseProxied, ]
AUTH_TYPE = AUTH_OID
OIDC_CLIENT_SECRETS = '/etc/superset/client_secret.json'
OIDC_ID_TOKEN_COOKIE_SECURE = False
OIDC_REQUIRE_VERIFIED_EMAIL = False
AUTH_USER_REGISTRATION = True
AUTH_USER_REGISTRATION_ROLE = 'Gamma'
CUSTOM_SECURITY_MANAGER = OIDCSecurityManager
ENABLE_PROXY_FIX = True

# Enable the security manager API.
FAB_ADD_SECURITY_API = True

0 comments on commit 79e0614

Please sign in to comment.