diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a8c6a3e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +FROM debian:wheezy + +RUN apt-get update -q +RUN DEBIAN_FRONTEND=noninteractive apt-get install --quiet --yes git procps apt-utils +RUN DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends --quiet --yes python-django python-redis python-mysqldb python-django-south python-django-registration python-paramiko python-simplejson python-daemon python-setproctitle python-pycurl python-recaptcha python-ipaddr python-bs4 python-requests python-markdown +RUN DEBIAN_FRONTEND=noninteractive apt-get install --quiet --yes gunicorn python-gevent beanstalkd nginx redis-server + +ADD python-django-markdown_0.6.1-1_all.deb / +RUN dpkg -i /python-django-markdown_0.6.1-1_all.deb + +WORKDIR /srv +RUN git clone --quiet https://github.com/grnet/ganetimgr.git + +COPY settings.py ganetimgr/ganetimgr/settings.py +COPY dj_database_url.py ganetimgr/dj_database_url.py +COPY ganetimgr.nginx.conf /etc/nginx/nginx.conf +COPY ganetimgr.gunicorn.conf /etc/gunicorn/ganetimgr.conf +COPY beanstalkd.conf /etc/default/beanstalkd +#COPY run.sh / + +#RUN cp ganetimgr/ganetimgr/settings.py.dist ganetimgr/ganetimgr/settings.py +RUN cp ganetimgr/templates/includes/analytics.html.dist ganetimgr/templates/includes/analytics.html +RUN ./ganetimgr/manage.py syncdb --noinput +#RUN echo "from django.contrib.auth.models import User; User.objects.create_superuser('admin', 'admin@example.com', DJANGO_ADMIN_PASS)" | python manage.py shell +RUN ./ganetimgr/manage.py migrate +RUN ./ganetimgr/manage.py collectstatic --noinput + +EXPOSE 80 +EXPOSE 8000 +#ENTRYPOINT bash /run.sh +ENTRYPOINT nginx && ./ganetimgr/manage.py runserver 0.0.0.0:8080 diff --git a/README.md b/README.md new file mode 100644 index 0000000..43cfa5d --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +ganetimgr-docker +================ + +docker build -t grnet/ganetimgr . + +docker run -p 80:80 grnet/ganetimgr:latest + +docker exec -ti ganetimgr /bin/bash + + +from django.contrib.auth.models import User; User.objects.create_superuser('admin', 'admin@example.com','12345') + diff --git a/beanstalkd.conf b/beanstalkd.conf new file mode 100644 index 0000000..4e2bd84 --- /dev/null +++ b/beanstalkd.conf @@ -0,0 +1,9 @@ +## Defaults for the beanstalkd init script, /etc/init.d/beanstalkd on +## Debian systems. Append ``-b /var/lib/beanstalkd'' for persistent +## storage. +BEANSTALKD_LISTEN_ADDR=0.0.0.0 +BEANSTALKD_LISTEN_PORT=11300 +DAEMON_OPTS="-l $BEANSTALKD_LISTEN_ADDR -p $BEANSTALKD_LISTEN_PORT" + +## Uncomment to enable startup during boot. +START=yes diff --git a/dj_database_url.py b/dj_database_url.py new file mode 100644 index 0000000..e269e9e --- /dev/null +++ b/dj_database_url.py @@ -0,0 +1,129 @@ +# -*- coding: utf-8 -*- + +import os + +try: + import urlparse +except ImportError: + import urllib.parse as urlparse + + +# Register database schemes in URLs. +urlparse.uses_netloc.append('postgres') +urlparse.uses_netloc.append('postgresql') +urlparse.uses_netloc.append('pgsql') +urlparse.uses_netloc.append('postgis') +urlparse.uses_netloc.append('mysql') +urlparse.uses_netloc.append('mysql2') +urlparse.uses_netloc.append('mysqlgis') +urlparse.uses_netloc.append('mysql-connector') +urlparse.uses_netloc.append('spatialite') +urlparse.uses_netloc.append('sqlite') +urlparse.uses_netloc.append('oracle') +urlparse.uses_netloc.append('oraclegis') +urlparse.uses_netloc.append('redshift') + +DEFAULT_ENV = 'DATABASE_URL' + +SCHEMES = { + 'postgres': 'django.db.backends.postgresql_psycopg2', + 'postgresql': 'django.db.backends.postgresql_psycopg2', + 'pgsql': 'django.db.backends.postgresql_psycopg2', + 'postgis': 'django.contrib.gis.db.backends.postgis', + 'mysql': 'django.db.backends.mysql', + 'mysql2': 'django.db.backends.mysql', + 'mysqlgis': 'django.contrib.gis.db.backends.mysql', + 'mysql-connector': 'mysql.connector.django', + 'spatialite': 'django.contrib.gis.db.backends.spatialite', + 'sqlite': 'django.db.backends.sqlite3', + 'oracle': 'django.db.backends.oracle', + 'oraclegis': 'django.contrib.gis.db.backends.oracle', + 'redshift': 'django_redshift_backend', +} + + +def config(env=DEFAULT_ENV, default=None, engine=None, conn_max_age=0): + """Returns configured DATABASE dictionary from DATABASE_URL.""" + + config = {} + + s = os.environ.get(env, default) + + if s: + config = parse(s, engine, conn_max_age) + + return config + + +def parse(url, engine=None, conn_max_age=0): + """Parses a database URL.""" + + if url == 'sqlite://:memory:': + # this is a special case, because if we pass this URL into + # urlparse, urlparse will choke trying to interpret "memory" + # as a port number + return { + 'ENGINE': SCHEMES['sqlite'], + 'NAME': ':memory:' + } + # note: no other settings are required for sqlite + + # otherwise parse the url as normal + config = {} + + url = urlparse.urlparse(url) + + # Split query strings from path. + path = url.path[1:] + if '?' in path and not url.query: + path, query = path.split('?', 2) + else: + path, query = path, url.query + query = urlparse.parse_qs(query) + + # If we are using sqlite and we have no path, then assume we + # want an in-memory database (this is the behaviour of sqlalchemy) + if url.scheme == 'sqlite' and path == '': + path = ':memory:' + + # Handle postgres percent-encoded paths. + hostname = url.hostname or '' + if '%2f' in hostname.lower(): + hostname = hostname.replace('%2f', '/').replace('%2F', '/') + + # Update with environment configuration. + config.update({ + 'NAME': urlparse.unquote(path or ''), + 'USER': urlparse.unquote(url.username or ''), + 'PASSWORD': urlparse.unquote(url.password or ''), + 'HOST': hostname, + 'PORT': url.port or '', + 'CONN_MAX_AGE': conn_max_age, + }) + + # Lookup specified engine. + engine = SCHEMES[url.scheme] if engine is None else engine + + # Pass the query string into OPTIONS. + options = {} + for key, values in query.items(): + if url.scheme == 'mysql' and key == 'ssl-ca': + options['ssl'] = {'ca': values[-1]} + continue + + options[key] = values[-1] + + # Support for Postgres Schema URLs + if 'currentSchema' in options and engine in ( + 'django.db.backends.postgresql_psycopg2', + 'django_redshift_backend', + ): + options['options'] = '-c search_path={0}'.format(options.pop('currentSchema')) + + if options: + config['OPTIONS'] = options + + if engine: + config['ENGINE'] = engine + + return config diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2ebb006 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +version: "2" + +services: + + beanstalkd: + image: agaveapi/beanstalkd + + database: + image: mysql + environment: + MYSQL_ROOT_PASSWORD= + MYSQL_USER=ganetimgr + MYSQL_PASSWORD= + MYSQL_DATABASE=ganetimgr + + worker: + build: + context: . + dockerfile: Dockerfile + links: + - beanstalkd + depends_on: + - database + - beanstalkd diff --git a/ganetimgr.gunicorn.conf b/ganetimgr.gunicorn.conf new file mode 100644 index 0000000..7b938cc --- /dev/null +++ b/ganetimgr.gunicorn.conf @@ -0,0 +1,12 @@ +CONFIG = { + 'mode': 'django', + 'working_dir': '/srv/ganetimgr', + 'user': 'www-data', + 'group': 'www-data', + 'args': ( + '--bind=127.0.0.1:8088', + '--workers=1', + '--worker-class=egg:gunicorn#gevent', + '--timeout=30', + ), +} diff --git a/ganetimgr.nginx.conf b/ganetimgr.nginx.conf new file mode 100644 index 0000000..87b57e8 --- /dev/null +++ b/ganetimgr.nginx.conf @@ -0,0 +1,26 @@ +worker_processes 1; + +events { + worker_connections 1024; +} + +http { + include mime.types; + + server { + listen 80; + server_name _; + + access_log /dev/stdout; + error_log /dev/stdout info; + + location /static { + alias /srv/ganetimgr/static; + } + + location / { + proxy_pass http://localhost:8080; + include proxy_params; + } + } +} diff --git a/python-django-markdown_0.6.1-1_all.deb b/python-django-markdown_0.6.1-1_all.deb new file mode 100644 index 0000000..1ad0720 Binary files /dev/null and b/python-django-markdown_0.6.1-1_all.deb differ diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..6caae93 --- /dev/null +++ b/run.sh @@ -0,0 +1,4 @@ +#!/bin/bash +/etc/init.d/beanstalkd start +/etc/init.d/redis-server start +/etc/init.d/gunicorn start diff --git a/settings.py b/settings.py new file mode 100644 index 0000000..073af51 --- /dev/null +++ b/settings.py @@ -0,0 +1,336 @@ +# -*- coding: utf-8 -*- vim:fileencoding=utf-8: +import os +import dj_database_url +BASE_DIR = os.path.dirname(os.path.dirname(__file__)) +PROJECT_DIR = os.path.join(BASE_DIR, 'ganetimgr') + +DATABASES= {} + +DATABASES['default'] = dj_database_url.config(default='sqlite:////%s' % os.path.join(BASE_DIR, 'db.sqlite3')) + +DEBUG = True +ALLOWED_HOSTS=["*"] +TEMPLATE_DEBUG = DEBUG + +SITE_ID = 1 + +# Time zone & localization +TIME_ZONE = 'Europe/Athens' + +_ = lambda s: s + +LANGUAGES = ( +# ('el', u'Ελληνικά'), + ('en', _('English')), +) + +#LANGUAGE_CODE = 'en-us' + +LOCALE_PATHS = ( + os.path.join(BASE_DIR, 'locale'), +) + +DATE_FORMAT = "d/m/Y H:i" +DATETIME_FORMAT = "d/m/Y H:i" + + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True +USE_L10N = True + +MEDIA_ROOT = '' +MEDIA_URL = '' + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', +) + + +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'middleware.ForceLogout.ForceLogoutMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.middleware.locale.LocaleMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', + 'middleware.UserMessages.UserMessageMiddleware', +) + +STATIC_URL = '/static/' +STATIC_ROOT = os.path.join(BASE_DIR, 'static') + +STATICFILES_DIRS = ( +) + +STATICFILES_FINDERS = ( + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', +) + +ROOT_URLCONF = 'ganetimgr.urls' + +TEMPLATE_DIRS = ( + os.path.join(BASE_DIR, 'templates'), +) + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.flatpages', + 'django.contrib.messages', + 'django.contrib.admin', + 'django.contrib.staticfiles', + 'registration', + 'django_markdown', + 'accounts', + 'south', + 'ganeti', + 'apply', + 'notifications', + 'stats', + 'auditlog', +# 'oauth2_provider', +# 'corsheaders', +) + +# Caching is a vital part of ganetimgr. +# If you deploy more than one ganetimgr instances on the same server, +# and want to use Redis for both, make sure you select a different db for each instance +# Warning!!! Redis db should ALWAYS be an integer, denoting db index. +# If memcache is your preferred cache, then select: +#CACHE_BACKEND="redis_cache.cache://127.0.0.1:6379?timeout=1500" +#CACHES = { +# 'default': { +# 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', +# } + # 'default': { + # 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', + # 'LOCATION': '127.0.0.1:11211', + # 'TIMEOUT': 1, + # } +#} + +LOGIN_URL = '/user/login' +LOGIN_REDIRECT_URL = '/' + +TEMPLATE_CONTEXT_PROCESSORS = ( + "django.contrib.auth.context_processors.auth", + "django.core.context_processors.debug", + "django.core.context_processors.i18n", + "django.core.context_processors.media", + "context.pending_notifications.notify", + "context.session_remaining.seconds", + "context.global_vars.settings_vars", + "django.core.context_processors.request", + "django.contrib.messages.context_processors.messages" +) + +EMAIL_HOST = "127.0.0.1" +EMAIL_PORT = 25 +USE_X_FORWARDED_HOST = True + +# Auth stuff +# If you plan to deploy LDAP modify according to your needs +AUTHENTICATION_BACKENDS = ( + #'django_auth_ldap.backend.LDAPBackend', + 'django.contrib.auth.backends.ModelBackend', +) + +#import ldap +#from django_auth_ldap.config import LDAPSearch + +#AUTH_LDAP_BIND_DN = "" +#AUTH_LDAP_BIND_PASSWORD = "" +#AUTH_LDAP_SERVER_URI = "ldap://ldap.example.com" +#AUTH_LDAP_START_TLS = True +#AUTH_LDAP_USER_SEARCH = LDAPSearch("ou=People,dc=dept,dc=example,dc=com", +# ldap.SCOPE_SUBTREE, "(uid=%(user)s)") +#AUTH_LDAP_USER_ATTR_MAP = { +# "first_name": "givenName", +# "last_name": "sn", +# "email": "mail" +# } + +ACCOUNT_ACTIVATION_DAYS = 10 + +AUTH_PROFILE_MODULE = 'accounts.UserProfile' + +SESSION_EXPIRE_AT_BROWSER_CLOSE = True +#SESSION_ENGINE = "django.contrib.sessions.backends.cache" +SESSION_COOKIE_AGE = 10800 + +IDLE_ACCOUNT_NOTIFICATION_DAYS = '180' + +# Number of days that hash verification is active +INSTANCE_ACTION_ACTIVE_DAYS = 7 + +# This works for our GRNET NOC Jira installation. Default is False +HELPDESK_INTEGRATION_JAVASCRIPT_URL = "" +HELPDESK_INTEGRATION_JAVASCRIPT_PARAMS = { + 'customfield_11551': 'tier-1' +} + +COLLECTD_URL = "http://stats.example.com" +# Graphs nodata image +NODATA_IMAGE = 'static/nodata.gif' + +SERVER_MONITORING_URL = 'https://monitoring.example.com' + +import _version +SW_VERSION = _version.VERSION + +NODATA_IMAGE = 'static/nodata.gif' + + +WHITELIST_IP_MAX_SUBNET_V4 = 26 +WHITELIST_IP_MAX_SUBNET_V6 = 64 + +# RSS Feed for the login page +FEED_URL = "" + +# Choose whether to support websockets console or not. +WEBSOCK_VNC_ENABLED = True +# This is meant to be used with twistednovncauthproxy +# twistd --pidfile=/tmp/proxy.pid -n vncap -c tcp:8888:interface=0.0.0.0 +NOVNC_PROXY = "vnc.proxy.com:8888" +NOVNC_USE_TLS = True + +BRANDING = { + "SERVICE_PROVIDED_BY": { + "NAME": "EXAMPLE", + "URL": "//example.dot.com", + "SOCIAL_NETWORKS": [ + { + "URL": "https://facebook.com/", + "FONT_AWESOME_NAME": "fa-facebook", + "FONT_COLOR": "#3b5998" + }, + { + "URL": "https://twitter.com/", + "FONT_AWESOME_NAME": "fa-twitter", + "FONT_COLOR": "#00acee" + } + ] + }, + "VIDEO": "", # iframe url + "LOGO": "/static/ganetimgr/img/logo.png", + "FAVICON": "/static/ganetimgr/img/favicon.ico", + "MOTTO": "virtual private servers", + "FOOTER_ICONS_IFRAME": False, + # show the administrative contact + # option when creating a new vm + "SHOW_ADMINISTRATIVE_FORM": True, + "SHOW_ORGANIZATION_FORM": True, + "TITLE": "GanetiMGR", +} + +# Set the email subject prefix: +EMAIL_SUBJECT_PREFIX = "[GANETIMGR SERVICE] " +SERVER_EMAIL = "no-reply@example.com" +DEFAULT_FROM_EMAIL = "no-reply@example.com" + +# Flatpages manipulation. Show or hide flatpages links in page. +FLATPAGES = { + "INFO": True, + "TOS": True, + "FAQ": True, +} +# Get a recaptcha key +RECAPTCHA_PUBLIC_KEY = '' +RECAPTCHA_PRIVATE_KEY = '' +RECAPTCHA_USE_SSL = True + +MARKDOWN_EDITOR_SKIN = 'simple' + + +######################### +# # +# Ganeti # +# # +######################### +# Select your ganetimgr prefix. This is applied in the tags +# of the instances. You could leave it as it is or set your own, +# eg. GANETI_TAG_PREFIX = "vmservice" +GANETI_TAG_PREFIX = "ganetimgr" +RAPI_CONNECT_TIMEOUT = 8 +RAPI_RESPONSE_TIMEOUT = 15 +# List of operating system images you provide... +OPERATING_SYSTEMS = { + "none": { + "description": "No operating system", + "provider": "noop", + "osparams": {}, + "ssh_key_param": "", + }, +} + + +# the urls of the available os images +OPERATING_SYSTEMS_URLS = ['http://example.com/images'] +SNF_OPERATING_SYSTEMS_URLS = ['http://example.com/snf-images/'] + + +# the provider and ssh key param +# We assume that they have the same configuration +OPERATING_SYSTEMS_PROVIDER = 'image+default' +OPERATING_SYSTEMS_SSH_KEY_PARAM = 'img_ssh_key_url' + +SNF_IMG_PROPERTIES = { + "SWAP": "2:512" +} +SNF_IMG_PASSWD = "example-passphrase" + +######################### +# # +# Auditlog # +# # +######################### + +# this option sets the amount of days old an audit entry has to be +# in order to be shown in audit log page +# '0' means show all entries + +AUDIT_ENTRIES_LAST_X_DAYS = 10 + + +# Instance specific django config. +ADMINS = ( + ('John Doe', 'john@example.com'), +) +MANAGERS = ADMINS + +#DATABASES = { +# 'default': { +# 'ENGINE': 'django.db.backends.', # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. +# 'NAME': '', # Or path to database file if using sqlite3. +# 'USER': '', # Not used with sqlite3. +# 'PASSWORD': '', # Not used with sqlite3. +# 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. +# 'PORT': '', # Set to empty string for default. Not used with sqlite3. +# 'OPTIONS': {'init_command': 'SET storage_engine=MYISAM;'} +# } +#} + +# Make this unique, and don't share it with anybody. +SECRET_KEY = '' + + +# OAUTH2 +if 'oauth2_provider' in INSTALLED_APPS and 'corsheaders' in INSTALLED_APPS: + OAUTH2_PROVIDER = { + 'ACCESS_TOKEN_EXPIRE_SECONDS': 60 * 60 * 24 * 7 * 10, + 'SCOPES': { + 'read': 'Read scope', + }, + 'CLIENT_ID_GENERATOR_CLASS': 'oauth2_provider.generators.ClientIdGenerator', + } + CORS_ORIGIN_ALLOW_ALL = True + MIDDLEWARE_CLASSES += ('corsheaders.middleware.CorsMiddleware',) +