From 93630ac6e3477bfb6d9c507a8f1287b74047d740 Mon Sep 17 00:00:00 2001
From: Steve Wilkerson <wilkers.steve@gmail.com>
Date: Tue, 4 Sep 2018 10:51:13 -0500
Subject: [PATCH] MariaDB: Move chart to openstack-helm-infra

This moves the mariadb chart to openstack-helm-infra as part of
the effort to move charts to the appropriate repositories

Change-Id: Ife56e28de46c536108cebb4f4cdf6bad2a415289
Story: 2002204
Task: 21725
---
 .../install/developer/deploy-with-ceph.rst    |  13 +
 .../install/developer/deploy-with-nfs.rst     |  13 +
 doc/source/install/multinode.rst              |  13 +
 mariadb/.helmignore                           |  21 +
 mariadb/Chart.yaml                            |  25 +
 mariadb/README.rst                            |  38 +
 mariadb/files/nginx.tmpl                      | 901 ++++++++++++++++++
 mariadb/requirements.yaml                     |  18 +
 .../bin/_mariadb-ingress-controller.sh.tpl    |  38 +
 .../bin/_mariadb-ingress-error-pages.sh.tpl   |  26 +
 mariadb/templates/bin/_readiness.sh.tpl       |  52 +
 mariadb/templates/bin/_start.sh.tpl           | 188 ++++
 mariadb/templates/bin/_stop.sh.tpl            |  24 +
 mariadb/templates/configmap-bin.yaml          |  39 +
 mariadb/templates/configmap-etc.yaml          |  39 +
 mariadb/templates/configmap-services-tcp.yaml |  26 +
 mariadb/templates/deployment-error.yaml       |  83 ++
 mariadb/templates/deployment-ingress.yaml     | 202 ++++
 mariadb/templates/etc/_00-base.cnf.tpl        | 107 +++
 mariadb/templates/etc/_20-override.cnf.tpl    |  17 +
 mariadb/templates/etc/_99-force.cnf.tpl       |  19 +
 mariadb/templates/etc/_my.cnf.tpl             |  22 +
 mariadb/templates/job-image-repo-sync.yaml    |  20 +
 .../prometheus/bin/_create-mysql-user.sh.tpl  |  24 +
 .../prometheus/bin/_mysqld-exporter.sh.tpl    |  30 +
 .../prometheus/exporter-configmap-bin.yaml    |  29 +
 .../prometheus/exporter-deployment.yaml       |  91 ++
 .../prometheus/exporter-job-create-user.yaml  |  83 ++
 .../prometheus/exporter-secrets-etc.yaml      |  35 +
 .../prometheus/exporter-service.yaml          |  37 +
 .../prometheus/secrets/_exporter_user.cnf.tpl |  21 +
 mariadb/templates/pdb-mariadb.yaml            |  29 +
 .../templates/secret-db-root-password.yaml    |  27 +
 mariadb/templates/secrets-etc.yaml            |  27 +
 mariadb/templates/secrets/_admin_user.cnf.tpl |  21 +
 mariadb/templates/service-discovery.yaml      |  35 +
 mariadb/templates/service-error.yaml          |  34 +
 mariadb/templates/service-ingress.yaml        |  33 +
 mariadb/templates/service.yaml                |  30 +
 mariadb/templates/statefulset.yaml            | 182 ++++
 mariadb/values.yaml                           | 289 ++++++
 playbooks/osh-infra-dev-deploy-ceph.yaml      |   6 +
 playbooks/osh-infra-dev-deploy-nfs.yaml       |   6 +
 playbooks/osh-infra-multinode-deploy.yaml     |   6 +
 playbooks/osh-infra-openstack-support.yaml    |   6 +
 .../deployment/developer/ceph/045-mariadb.sh  |   1 +
 .../developer/common/045-mariadb.sh           |  34 +
 .../developer/common/100-grafana.sh           |  23 +-
 tools/deployment/developer/nfs/045-mariadb.sh |   1 +
 tools/deployment/multinode/045-mariadb.sh     |  33 +
 tools/deployment/multinode/100-grafana.sh     |  25 +-
 .../openstack-support/035-mariadb.sh          |  34 +
 52 files changed, 3130 insertions(+), 46 deletions(-)
 create mode 100644 mariadb/.helmignore
 create mode 100644 mariadb/Chart.yaml
 create mode 100644 mariadb/README.rst
 create mode 100644 mariadb/files/nginx.tmpl
 create mode 100644 mariadb/requirements.yaml
 create mode 100644 mariadb/templates/bin/_mariadb-ingress-controller.sh.tpl
 create mode 100644 mariadb/templates/bin/_mariadb-ingress-error-pages.sh.tpl
 create mode 100644 mariadb/templates/bin/_readiness.sh.tpl
 create mode 100644 mariadb/templates/bin/_start.sh.tpl
 create mode 100644 mariadb/templates/bin/_stop.sh.tpl
 create mode 100644 mariadb/templates/configmap-bin.yaml
 create mode 100644 mariadb/templates/configmap-etc.yaml
 create mode 100644 mariadb/templates/configmap-services-tcp.yaml
 create mode 100644 mariadb/templates/deployment-error.yaml
 create mode 100644 mariadb/templates/deployment-ingress.yaml
 create mode 100644 mariadb/templates/etc/_00-base.cnf.tpl
 create mode 100644 mariadb/templates/etc/_20-override.cnf.tpl
 create mode 100644 mariadb/templates/etc/_99-force.cnf.tpl
 create mode 100644 mariadb/templates/etc/_my.cnf.tpl
 create mode 100644 mariadb/templates/job-image-repo-sync.yaml
 create mode 100644 mariadb/templates/monitoring/prometheus/bin/_create-mysql-user.sh.tpl
 create mode 100644 mariadb/templates/monitoring/prometheus/bin/_mysqld-exporter.sh.tpl
 create mode 100644 mariadb/templates/monitoring/prometheus/exporter-configmap-bin.yaml
 create mode 100644 mariadb/templates/monitoring/prometheus/exporter-deployment.yaml
 create mode 100644 mariadb/templates/monitoring/prometheus/exporter-job-create-user.yaml
 create mode 100644 mariadb/templates/monitoring/prometheus/exporter-secrets-etc.yaml
 create mode 100644 mariadb/templates/monitoring/prometheus/exporter-service.yaml
 create mode 100644 mariadb/templates/monitoring/prometheus/secrets/_exporter_user.cnf.tpl
 create mode 100644 mariadb/templates/pdb-mariadb.yaml
 create mode 100644 mariadb/templates/secret-db-root-password.yaml
 create mode 100644 mariadb/templates/secrets-etc.yaml
 create mode 100644 mariadb/templates/secrets/_admin_user.cnf.tpl
 create mode 100644 mariadb/templates/service-discovery.yaml
 create mode 100644 mariadb/templates/service-error.yaml
 create mode 100644 mariadb/templates/service-ingress.yaml
 create mode 100644 mariadb/templates/service.yaml
 create mode 100644 mariadb/templates/statefulset.yaml
 create mode 100644 mariadb/values.yaml
 create mode 120000 tools/deployment/developer/ceph/045-mariadb.sh
 create mode 100755 tools/deployment/developer/common/045-mariadb.sh
 create mode 120000 tools/deployment/developer/nfs/045-mariadb.sh
 create mode 100755 tools/deployment/multinode/045-mariadb.sh
 create mode 100755 tools/deployment/openstack-support/035-mariadb.sh

diff --git a/doc/source/install/developer/deploy-with-ceph.rst b/doc/source/install/developer/deploy-with-ceph.rst
index 1658ea16f..915a8f549 100644
--- a/doc/source/install/developer/deploy-with-ceph.rst
+++ b/doc/source/install/developer/deploy-with-ceph.rst
@@ -67,6 +67,19 @@ Alternatively, this step can be performed by running the script directly:
 
   ./tools/deployment/developer/ceph/040-ldap.sh
 
+Deploy MariaDB
+^^^^^^^^^^^^^^
+
+.. literalinclude:: ../../../../tools/deployment/developer/ceph/045-mariadb.sh
+    :language: shell
+    :lines: 1,17-
+
+Alternatively, this step can be performed by running the script directly:
+
+.. code-block:: shell
+
+  ./tools/deployment/developer/ceph/045-mariadb.sh
+
 Deploy Prometheus
 ^^^^^^^^^^^^^^^^^
 
diff --git a/doc/source/install/developer/deploy-with-nfs.rst b/doc/source/install/developer/deploy-with-nfs.rst
index c6b9bc023..90ba42223 100644
--- a/doc/source/install/developer/deploy-with-nfs.rst
+++ b/doc/source/install/developer/deploy-with-nfs.rst
@@ -54,6 +54,19 @@ Alternatively, this step can be performed by running the script directly:
 
   ./tools/deployment/developer/nfs/040-ldap.sh
 
+Deploy MariaDB
+^^^^^^^^^^^^^^
+
+.. literalinclude:: ../../../../tools/deployment/developer/nfs/045-mariadb.sh
+    :language: shell
+    :lines: 1,17-
+
+Alternatively, this step can be performed by running the script directly:
+
+.. code-block:: shell
+
+  ./tools/deployment/developer/nfs/045-mariadb.sh
+
 Deploy Prometheus
 ^^^^^^^^^^^^^^^^^
 
diff --git a/doc/source/install/multinode.rst b/doc/source/install/multinode.rst
index 80930f7b3..257db44a9 100644
--- a/doc/source/install/multinode.rst
+++ b/doc/source/install/multinode.rst
@@ -67,6 +67,19 @@ Alternatively, this step can be performed by running the script directly:
 
   ./tools/deployment/multinode/040-ldap.sh
 
+Deploy MariaDB
+^^^^^^^^^^^^^^
+
+.. literalinclude:: ../../../tools/deployment/multinode/045-mariadb.sh
+    :language: shell
+    :lines: 1,17-
+
+Alternatively, this step can be performed by running the script directly:
+
+.. code-block:: shell
+
+  ./tools/deployment/multinode/045-mariadb.sh
+
 Deploy Prometheus
 ^^^^^^^^^^^^^^^^^
 
diff --git a/mariadb/.helmignore b/mariadb/.helmignore
new file mode 100644
index 000000000..f0c131944
--- /dev/null
+++ b/mariadb/.helmignore
@@ -0,0 +1,21 @@
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
diff --git a/mariadb/Chart.yaml b/mariadb/Chart.yaml
new file mode 100644
index 000000000..8f6d1a2a4
--- /dev/null
+++ b/mariadb/Chart.yaml
@@ -0,0 +1,25 @@
+# Copyright 2017 The Openstack-Helm Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+apiVersion: v1
+description: OpenStack-Helm MariaDB
+name: mariadb
+version: 0.1.0
+home: https://mariadb.com/kb/en/
+icon: http://badges.mariadb.org/mariadb-badge-180x60.png
+sources:
+  - https://github.com/MariaDB/server
+  - https://git.openstack.org/cgit/openstack/openstack-helm
+maintainers:
+  - name: OpenStack-Helm Authors
diff --git a/mariadb/README.rst b/mariadb/README.rst
new file mode 100644
index 000000000..93af0868a
--- /dev/null
+++ b/mariadb/README.rst
@@ -0,0 +1,38 @@
+openstack-helm/mariadb
+======================
+
+By default, this chart creates a 3-member mariadb galera cluster.
+
+This chart leverages StatefulSets, with persistent storage.
+
+It creates a job that acts as a temporary standalone galera cluster.
+This host is bootstrapped with authentication and then the WSREP
+bindings are exposed publicly. The cluster members being StatefulSets
+are provisioned one at a time. The first host must be marked as
+``Ready`` before the next host will be provisioned. This is determined
+by the readinessProbes which actually validate that MySQL is up and
+responsive.
+
+The configuration leverages xtrabackup-v2 for synchronization. This may
+later be augmented to leverage rsync which has some benefits.
+
+Once the seed job completes, which completes only when galera reports
+that it is Synced and all cluster members are reporting in thus matching
+the cluster count according to the job to the replica count in the helm
+values configuration, the job is terminated. When the job is no longer
+active, future StatefulSets provisioned will leverage the existing
+cluster members as gcomm endpoints. It is only when the job is running
+that the cluster members leverage the seed job as their gcomm endpoint.
+This ensures you can restart members and scale the cluster.
+
+The StatefulSets all leverage PVCs to provide stateful storage to
+``/var/lib/mysql``.
+
+You must ensure that your control nodes that should receive mariadb
+instances are labeled with ``openstack-control-plane=enabled``, or
+whatever you have configured in values.yaml for the label
+configuration:
+
+::
+
+    kubectl label nodes openstack-control-plane=enabled --all
diff --git a/mariadb/files/nginx.tmpl b/mariadb/files/nginx.tmpl
new file mode 100644
index 000000000..b74b2b633
--- /dev/null
+++ b/mariadb/files/nginx.tmpl
@@ -0,0 +1,901 @@
+{{ $all := . }}
+{{ $servers := .Servers }}
+{{ $cfg := .Cfg }}
+{{ $IsIPV6Enabled := .IsIPV6Enabled }}
+{{ $healthzURI := .HealthzURI }}
+{{ $backends := .Backends }}
+{{ $proxyHeaders := .ProxySetHeaders }}
+{{ $addHeaders := .AddHeaders }}
+
+{{ if $cfg.EnableModsecurity }}
+load_module /etc/nginx/modules/ngx_http_modsecurity_module.so;
+{{ end }}
+
+{{ if $cfg.EnableOpentracing }}
+load_module /etc/nginx/modules/ngx_http_opentracing_module.so;
+{{ end }}
+
+{{ if (and $cfg.EnableOpentracing (ne $cfg.ZipkinCollectorHost "")) }}
+load_module /etc/nginx/modules/ngx_http_zipkin_module.so;
+{{ end }}
+
+daemon off;
+
+worker_processes {{ $cfg.WorkerProcesses }};
+pid /run/nginx.pid;
+{{ if ne .MaxOpenFiles 0 }}
+worker_rlimit_nofile {{ .MaxOpenFiles }};
+{{ end}}
+
+{{/* http://nginx.org/en/docs/ngx_core_module.html#worker_shutdown_timeout */}}
+{{/* avoid waiting too long during a reload */}}
+worker_shutdown_timeout {{ $cfg.WorkerShutdownTimeout }} ;
+
+events {
+    multi_accept        on;
+    worker_connections  {{ $cfg.MaxWorkerConnections }};
+    use                 epoll;
+}
+
+http {
+    {{/* we use the value of the header X-Forwarded-For to be able to use the geo_ip module */}}
+    {{ if $cfg.UseProxyProtocol }}
+    real_ip_header      proxy_protocol;
+    {{ else }}
+    real_ip_header      {{ $cfg.ForwardedForHeader }};
+    {{ end }}
+
+    real_ip_recursive   on;
+    {{ range $trusted_ip := $cfg.ProxyRealIPCIDR }}
+    set_real_ip_from    {{ $trusted_ip }};
+    {{ end }}
+
+    {{/* databases used to determine the country depending on the client IP address */}}
+    {{/* http://nginx.org/en/docs/http/ngx_http_geoip_module.html */}}
+    {{/* this is require to calculate traffic for individual country using GeoIP in the status page */}}
+    geoip_country       /etc/nginx/GeoIP.dat;
+    geoip_city          /etc/nginx/GeoLiteCity.dat;
+    geoip_proxy_recursive on;
+
+    {{ if $cfg.EnableVtsStatus }}
+    vhost_traffic_status_zone shared:vhost_traffic_status:{{ $cfg.VtsStatusZoneSize }};
+    vhost_traffic_status_filter_by_set_key {{ $cfg.VtsDefaultFilterKey }};
+    {{ end }}
+
+    sendfile            on;
+
+    aio                 threads;
+    aio_write           on;
+
+    tcp_nopush          on;
+    tcp_nodelay         on;
+
+    log_subrequest      on;
+
+    reset_timedout_connection on;
+
+    keepalive_timeout  {{ $cfg.KeepAlive }}s;
+    keepalive_requests {{ $cfg.KeepAliveRequests }};
+
+    client_header_buffer_size       {{ $cfg.ClientHeaderBufferSize }};
+    client_header_timeout           {{ $cfg.ClientHeaderTimeout }}s;
+    large_client_header_buffers     {{ $cfg.LargeClientHeaderBuffers }};
+    client_body_buffer_size         {{ $cfg.ClientBodyBufferSize }};
+    client_body_timeout             {{ $cfg.ClientBodyTimeout }}s;
+
+    http2_max_field_size            {{ $cfg.HTTP2MaxFieldSize }};
+    http2_max_header_size           {{ $cfg.HTTP2MaxHeaderSize }};
+
+    types_hash_max_size             2048;
+    server_names_hash_max_size      {{ $cfg.ServerNameHashMaxSize }};
+    server_names_hash_bucket_size   {{ $cfg.ServerNameHashBucketSize }};
+    map_hash_bucket_size            {{ $cfg.MapHashBucketSize }};
+
+    proxy_headers_hash_max_size     {{ $cfg.ProxyHeadersHashMaxSize }};
+    proxy_headers_hash_bucket_size  {{ $cfg.ProxyHeadersHashBucketSize }};
+
+    variables_hash_bucket_size      {{ $cfg.VariablesHashBucketSize }};
+    variables_hash_max_size         {{ $cfg.VariablesHashMaxSize }};
+
+    underscores_in_headers          {{ if $cfg.EnableUnderscoresInHeaders }}on{{ else }}off{{ end }};
+    ignore_invalid_headers          {{ if $cfg.IgnoreInvalidHeaders }}on{{ else }}off{{ end }};
+
+    {{ if $cfg.EnableOpentracing }}
+    opentracing on;
+    {{ end }}
+
+    {{ if (and $cfg.EnableOpentracing (ne $cfg.ZipkinCollectorHost "")) }}
+    zipkin_collector_host           {{ $cfg.ZipkinCollectorHost }};
+    zipkin_collector_port           {{ $cfg.ZipkinCollectorPort }};
+    zipkin_service_name             {{ $cfg.ZipkinServiceName }};
+    {{ end }}
+
+    include /etc/nginx/mime.types;
+    default_type text/html;
+
+    {{ if $cfg.EnableBrotli }}
+    brotli on;
+    brotli_comp_level {{ $cfg.BrotliLevel }};
+    brotli_types {{ $cfg.BrotliTypes }};
+    {{ end }}
+
+    {{ if $cfg.UseGzip }}
+    gzip on;
+    gzip_comp_level 5;
+    gzip_http_version 1.1;
+    gzip_min_length 256;
+    gzip_types {{ $cfg.GzipTypes }};
+    gzip_proxied any;
+    gzip_vary on;
+    {{ end }}
+
+    # Custom headers for response
+    {{ range $k, $v := $addHeaders }}
+    add_header {{ $k }}            "{{ $v }}";
+    {{ end }}
+
+    server_tokens {{ if $cfg.ShowServerTokens }}on{{ else }}off{{ end }};
+
+    # disable warnings
+    uninitialized_variable_warn off;
+
+    # Additional available variables:
+    # $namespace
+    # $ingress_name
+    # $service_name
+    log_format upstreaminfo {{ if $cfg.LogFormatEscapeJSON }}escape=json {{ end }}'{{ buildLogFormatUpstream $cfg }}';
+
+    {{/* map urls that should not appear in access.log */}}
+    {{/* http://nginx.org/en/docs/http/ngx_http_log_module.html#access_log */}}
+    map $request_uri $loggable {
+        {{ range $reqUri := $cfg.SkipAccessLogURLs }}
+        {{ $reqUri }} 0;{{ end }}
+        default 1;
+    }
+
+    {{ if $cfg.DisableAccessLog }}
+    access_log off;
+    {{ else }}
+    access_log {{ $cfg.AccessLogPath }} upstreaminfo if=$loggable;
+    {{ end }}
+    error_log  {{ $cfg.ErrorLogPath }} {{ $cfg.ErrorLogLevel }};
+
+    {{ buildResolvers $cfg.Resolver }}
+
+    {{/* Whenever nginx proxies a request without a "Connection" header, the "Connection" header is set to "close" */}}
+    {{/* when making the target request.  This means that you cannot simply use */}}
+    {{/* "proxy_set_header Connection $http_connection" for WebSocket support because in this case, the */}}
+    {{/* "Connection" header would be set to "" whenever the original request did not have a "Connection" header, */}}
+    {{/* which would mean no "Connection" header would be in the target request.  Since this would deviate from */}}
+    {{/* normal nginx behavior we have to use this approach. */}}
+    # Retain the default nginx handling of requests without a "Connection" header
+    map $http_upgrade $connection_upgrade {
+        default          upgrade;
+        ''               close;
+    }
+
+    map {{ buildForwardedFor $cfg.ForwardedForHeader }} $the_real_ip {
+    {{ if $cfg.UseProxyProtocol }}
+        # Get IP address from Proxy Protocol
+        default          $proxy_protocol_addr;
+    {{ else }}
+        default          $remote_addr;
+    {{ end }}
+    }
+
+    # trust http_x_forwarded_proto headers correctly indicate ssl offloading
+    map $http_x_forwarded_proto $pass_access_scheme {
+        default          $http_x_forwarded_proto;
+        ''               $scheme;
+    }
+
+    map $http_x_forwarded_port $pass_server_port {
+        default           $http_x_forwarded_port;
+        ''                $server_port;
+    }
+
+    map $http_x_forwarded_host $best_http_host {
+        default          $http_x_forwarded_host;
+        ''               $this_host;
+    }
+
+    {{ if $all.IsSSLPassthroughEnabled }}
+    # map port {{ $all.ListenPorts.SSLProxy }} to 443 for header X-Forwarded-Port
+    map $pass_server_port $pass_port {
+        {{ $all.ListenPorts.SSLProxy }}              443;
+        default          $pass_server_port;
+    }
+    {{ else }}
+    map $pass_server_port $pass_port {
+        443              443;
+        default          $pass_server_port;
+    }
+    {{ end }}
+
+    # Obtain best http host
+    map $http_host $this_host {
+        default          $http_host;
+        ''               $host;
+    }
+
+    {{ if $cfg.ComputeFullForwardedFor }}
+    # We can't use $proxy_add_x_forwarded_for because the realip module
+    # replaces the remote_addr too soon
+    map $http_x_forwarded_for $full_x_forwarded_for {
+        {{ if $all.Cfg.UseProxyProtocol }}
+        default          "$http_x_forwarded_for, $proxy_protocol_addr";
+        ''               "$proxy_protocol_addr";
+        {{ else }}
+        default          "$http_x_forwarded_for, $realip_remote_addr";
+        ''               "$realip_remote_addr";
+        {{ end}}
+    }
+    {{ end }}
+
+    server_name_in_redirect off;
+    port_in_redirect        off;
+
+    ssl_protocols {{ $cfg.SSLProtocols }};
+
+    # turn on session caching to drastically improve performance
+    {{ if $cfg.SSLSessionCache }}
+    ssl_session_cache builtin:1000 shared:SSL:{{ $cfg.SSLSessionCacheSize }};
+    ssl_session_timeout {{ $cfg.SSLSessionTimeout }};
+    {{ end }}
+
+    # allow configuring ssl session tickets
+    ssl_session_tickets {{ if $cfg.SSLSessionTickets }}on{{ else }}off{{ end }};
+
+    {{ if not (empty $cfg.SSLSessionTicketKey ) }}
+    ssl_session_ticket_key /etc/nginx/tickets.key;
+    {{ end }}
+
+    # slightly reduce the time-to-first-byte
+    ssl_buffer_size {{ $cfg.SSLBufferSize }};
+
+    {{ if not (empty $cfg.SSLCiphers) }}
+    # allow configuring custom ssl ciphers
+    ssl_ciphers '{{ $cfg.SSLCiphers }}';
+    ssl_prefer_server_ciphers on;
+    {{ end }}
+
+    {{ if not (empty $cfg.SSLDHParam) }}
+    # allow custom DH file http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_dhparam
+    ssl_dhparam {{ $cfg.SSLDHParam }};
+    {{ end }}
+
+    {{ if not $cfg.EnableDynamicTLSRecords }}
+    ssl_dyn_rec_size_lo 0;
+    {{ end }}
+
+    ssl_ecdh_curve {{ $cfg.SSLECDHCurve }};
+
+    {{ if .CustomErrors }}
+    # Custom error pages
+    proxy_intercept_errors on;
+    {{ end }}
+
+    {{ range $errCode := $cfg.CustomHTTPErrors }}
+    error_page {{ $errCode }} = @custom_{{ $errCode }};{{ end }}
+
+    proxy_ssl_session_reuse on;
+
+    {{ if $cfg.AllowBackendServerHeader }}
+    proxy_pass_header Server;
+    {{ end }}
+
+    {{ if not (empty $cfg.HTTPSnippet) }}
+    # Custom code snippet configured in the configuration configmap
+    {{ $cfg.HTTPSnippet }}
+    {{ end }}
+
+    {{ range $name, $upstream := $backends }}
+    {{ if eq $upstream.SessionAffinity.AffinityType "cookie" }}
+    upstream sticky-{{ $upstream.Name }} {
+        sticky hash={{ $upstream.SessionAffinity.CookieSessionAffinity.Hash }} name={{ $upstream.SessionAffinity.CookieSessionAffinity.Name }}  httponly;
+
+        {{ if (gt $cfg.UpstreamKeepaliveConnections 0) }}
+        keepalive {{ $cfg.UpstreamKeepaliveConnections }};
+        {{ end }}
+
+        {{ range $server := $upstream.Endpoints }}server {{ $server.Address | formatIP }}:{{ $server.Port }} max_fails={{ $server.MaxFails }} fail_timeout={{ $server.FailTimeout }};
+        {{ end }}
+
+    }
+
+    {{ end }}
+
+
+    upstream {{ $upstream.Name }} {
+        # Load balance algorithm; empty for round robin, which is the default
+        {{ if ne $cfg.LoadBalanceAlgorithm "round_robin" }}
+        {{ $cfg.LoadBalanceAlgorithm }};
+        {{ end }}
+
+        {{ if $upstream.UpstreamHashBy }}
+        hash {{ $upstream.UpstreamHashBy }} consistent;
+        {{ end }}
+
+        {{ if (gt $cfg.UpstreamKeepaliveConnections 0) }}
+        keepalive {{ $cfg.UpstreamKeepaliveConnections }};
+        {{ end }}
+
+        {{ range $server := $upstream.Endpoints }}server {{ $server.Address | formatIP }}:{{ $server.Port }} max_fails={{ $server.MaxFails }} fail_timeout={{ $server.FailTimeout }};
+        {{ end }}
+    }
+
+    {{ end }}
+
+    {{/* build the maps that will be use to validate the Whitelist */}}
+    {{ range $index, $server := $servers }}
+    {{ range $location := $server.Locations }}
+    {{ $path := buildLocation $location }}
+
+    {{ if isLocationAllowed $location }}
+    {{ if gt (len $location.Whitelist.CIDR) 0 }}
+
+    # Deny for {{ print $server.Hostname  $path }}
+    geo $the_real_ip {{ buildDenyVariable (print $server.Hostname "_"  $path) }} {
+        default 1;
+
+        {{ range $ip := $location.Whitelist.CIDR }}
+        {{ $ip }} 0;{{ end }}
+    }
+    {{ end }}
+    {{ end }}
+    {{ end }}
+    {{ end }}
+
+    {{ range $rl := (filterRateLimits $servers ) }}
+    # Ratelimit {{ $rl.Name }}
+    geo $the_real_ip $whitelist_{{ $rl.ID }} {
+        default 0;
+        {{ range $ip := $rl.Whitelist }}
+        {{ $ip }} 1;{{ end }}
+    }
+
+    # Ratelimit {{ $rl.Name }}
+    map $whitelist_{{ $rl.ID }} $limit_{{ $rl.ID }} {
+        0 {{ $cfg.LimitConnZoneVariable }};
+        1 "";
+    }
+    {{ end }}
+
+    {{/* build all the required rate limit zones. Each annotation requires a dedicated zone */}}
+    {{/* 1MB -> 16 thousand 64-byte states or about 8 thousand 128-byte states */}}
+    {{ range $zone := (buildRateLimitZones $servers) }}
+    {{ $zone }}
+    {{ end }}
+
+    {{/* Build server redirects (from/to www) */}}
+    {{ range $hostname, $to := .RedirectServers }}
+    server {
+        {{ range $address := $all.Cfg.BindAddressIpv4 }}
+        listen {{ $address }}:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }};
+        listen {{ $address }}:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }} ssl;
+        {{ else }}
+        listen {{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }};
+        listen {{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }} ssl;
+        {{ end }}
+        {{ if $IsIPV6Enabled }}
+        {{ range $address := $all.Cfg.BindAddressIpv6 }}
+        listen {{ $address }}:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }};
+        listen {{ $address }}:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }};
+        {{ else }}
+        listen [::]:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }};
+        listen [::]:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }};
+        {{ end }}
+        {{ end }}
+        server_name {{ $hostname }};
+
+        {{ if ne $all.ListenPorts.HTTPS 443 }}
+        {{ $redirect_port := (printf ":%v" $all.ListenPorts.HTTPS) }}
+        return {{ $all.Cfg.HTTPRedirectCode }} $scheme://{{ $to }}{{ $redirect_port }}$request_uri;
+        {{ else }}
+        return {{ $all.Cfg.HTTPRedirectCode }} $scheme://{{ $to }}$request_uri;
+        {{ end }}
+    }
+    {{ end }}
+
+    {{ range $index, $server := $servers }}
+
+    ## start server {{ $server.Hostname }}
+    server {
+        server_name {{ $server.Hostname }} {{ $server.Alias }};
+        {{ template "SERVER" serverConfig $all $server }}
+
+        {{ if not (empty $cfg.ServerSnippet) }}
+        # Custom code snippet configured in the configuration configmap
+        {{ $cfg.ServerSnippet }}
+        {{ end }}
+
+        {{ template "CUSTOM_ERRORS" $all }}
+    }
+    ## end server {{ $server.Hostname }}
+
+    {{ end }}
+
+    # default server, used for NGINX healthcheck and access to nginx stats
+    server {
+        # Use the port {{ $all.ListenPorts.Status }} (random value just to avoid known ports) as default port for nginx.
+        # Changing this value requires a change in:
+        # https://github.com/kubernetes/ingress-nginx/blob/master/controllers/nginx/pkg/cmd/controller/nginx.go
+        listen {{ $all.ListenPorts.Status }} default_server reuseport backlog={{ $all.BacklogSize }};
+        {{ if $IsIPV6Enabled }}listen [::]:{{ $all.ListenPorts.Status }} default_server reuseport backlog={{ $all.BacklogSize }};{{ end }}
+        set $proxy_upstream_name "-";
+
+        location {{ $healthzURI }} {
+            access_log off;
+            return 200;
+        }
+
+        location /nginx_status {
+            set $proxy_upstream_name "internal";
+
+            {{ if $cfg.EnableVtsStatus }}
+            vhost_traffic_status_display;
+            vhost_traffic_status_display_format html;
+            {{ else }}
+            access_log off;
+            stub_status on;
+            {{ end }}
+        }
+
+        location / {
+            {{ if .CustomErrors }}
+            proxy_set_header    X-Code 404;
+            {{ end }}
+            set $proxy_upstream_name "upstream-default-backend";
+            proxy_pass          http://upstream-default-backend;
+        }
+
+        {{ template "CUSTOM_ERRORS" $all }}
+    }
+}
+
+stream {
+    log_format log_stream {{ $cfg.LogFormatStream }};
+
+    {{ if $cfg.DisableAccessLog }}
+    access_log off;
+    {{ else }}
+    access_log {{ $cfg.AccessLogPath }} log_stream;
+    {{ end }}
+
+    error_log  {{ $cfg.ErrorLogPath }};
+
+    # TCP services
+    {{ range $i, $tcpServer := .TCPBackends }}
+    upstream tcp-{{ $tcpServer.Port }}-{{ $tcpServer.Backend.Namespace }}-{{ $tcpServer.Backend.Name }}-{{ $tcpServer.Backend.Port }} {
+        # NOTE(portdirect): mark the 1st server as up, the 2nd as backup, and all others as down.
+        # The ingress controller will manage this list, based on the health checks in the backend pods,
+        # which approximates the pattern commonly used by Haproxy's httpchk.
+    {{ range $j, $endpoint := $tcpServer.Endpoints }}
+    {{ if eq $j 0 }}
+        # NOTE(portdirect): see https://docs.nginx.com/nginx/admin-guide/load-balancer/tcp-health-check/#passive-tcp-health-checks to tune passive healthchecks
+        server                  {{ $endpoint.Address }}:{{ $endpoint.Port }};
+    {{ else if eq $j 1 }}
+        server                  {{ $endpoint.Address }}:{{ $endpoint.Port }} backup;
+    {{ else }}
+        server                  {{ $endpoint.Address }}:{{ $endpoint.Port }} down;
+    {{ end }}
+    {{ end }}
+    }
+    server {
+        {{ range $address := $all.Cfg.BindAddressIpv4 }}
+        listen                  {{ $address }}:{{ $tcpServer.Port }}{{ if $tcpServer.Backend.ProxyProtocol.Decode }} proxy_protocol{{ end }};
+        {{ else }}
+        listen                  {{ $tcpServer.Port }}{{ if $tcpServer.Backend.ProxyProtocol.Decode }} proxy_protocol{{ end }};
+        {{ end }}
+        {{ if $IsIPV6Enabled }}
+        {{ range $address := $all.Cfg.BindAddressIpv6 }}
+        listen                  {{ $address }}:{{ $tcpServer.Port }}{{ if $tcpServer.Backend.ProxyProtocol.Decode }} proxy_protocol{{ end }};
+        {{ else }}
+        listen                  [::]:{{ $tcpServer.Port }}{{ if $tcpServer.Backend.ProxyProtocol.Decode }} proxy_protocol{{ end }};
+        {{ end }}
+        {{ end }}
+        proxy_timeout           {{ $cfg.ProxyStreamTimeout }};
+        proxy_pass              tcp-{{ $tcpServer.Port }}-{{ $tcpServer.Backend.Namespace }}-{{ $tcpServer.Backend.Name }}-{{ $tcpServer.Backend.Port }};
+        {{ if $tcpServer.Backend.ProxyProtocol.Encode }}
+        proxy_protocol          on;
+        {{ end }}
+    }
+
+    {{ end }}
+
+    # UDP services
+    {{ range $i, $udpServer := .UDPBackends }}
+    upstream udp-{{ $udpServer.Port }}-{{ $udpServer.Backend.Namespace }}-{{ $udpServer.Backend.Name }}-{{ $udpServer.Backend.Port }} {
+    {{ range $j, $endpoint := $udpServer.Endpoints }}
+        server                  {{ $endpoint.Address }}:{{ $endpoint.Port }};
+    {{ end }}
+    }
+
+    server {
+        {{ range $address := $all.Cfg.BindAddressIpv4 }}
+        listen                  {{ $address }}:{{ $udpServer.Port }} udp;
+        {{ else }}
+        listen                  {{ $udpServer.Port }} udp;
+        {{ end }}
+        {{ if $IsIPV6Enabled }}
+        {{ range $address := $all.Cfg.BindAddressIpv6 }}
+        listen                  {{ $address }}:{{ $udpServer.Port }} udp;
+        {{ else }}
+        listen                  [::]:{{ $udpServer.Port }} udp;
+        {{ end }}
+        {{ end }}
+        proxy_responses         {{ $cfg.ProxyStreamResponses }};
+        proxy_timeout           {{ $cfg.ProxyStreamTimeout }};
+        proxy_pass              udp-{{ $udpServer.Port }}-{{ $udpServer.Backend.Namespace }}-{{ $udpServer.Backend.Name }}-{{ $udpServer.Backend.Port }};
+    }
+
+    {{ end }}
+}
+
+{{/* definition of templates to avoid repetitions */}}
+{{ define "CUSTOM_ERRORS" }}
+        {{ $proxySetHeaders := .ProxySetHeaders }}
+        {{ range $errCode := .Cfg.CustomHTTPErrors }}
+        location @custom_{{ $errCode }} {
+            internal;
+
+            proxy_intercept_errors off;
+
+            proxy_set_header       X-Code             {{ $errCode }};
+            proxy_set_header       X-Format           $http_accept;
+            proxy_set_header       X-Original-URI     $request_uri;
+            proxy_set_header       X-Namespace        $namespace;
+            proxy_set_header       X-Ingress-Name     $ingress_name;
+            proxy_set_header       X-Service-Name     $service_name;
+
+            rewrite                (.*) / break;
+            proxy_pass             http://upstream-default-backend;
+        }
+        {{ end }}
+{{ end }}
+
+{{/* CORS support from https://michielkalkman.com/snippets/nginx-cors-open-configuration.html */}}
+{{ define "CORS" }}
+     {{ $cors := .CorsConfig }}
+     # Cors Preflight methods needs additional options and different Return Code
+     if ($request_method = 'OPTIONS') {
+        add_header 'Access-Control-Allow-Origin' '{{ $cors.CorsAllowOrigin }}' always;
+        {{ if $cors.CorsAllowCredentials }} add_header 'Access-Control-Allow-Credentials' '{{ $cors.CorsAllowCredentials }}' always; {{ end }}
+        add_header 'Access-Control-Allow-Methods' '{{ $cors.CorsAllowMethods }}' always;
+        add_header 'Access-Control-Allow-Headers' '{{ $cors.CorsAllowHeaders }}' always;
+        add_header 'Access-Control-Max-Age' 1728000;
+        add_header 'Content-Type' 'text/plain charset=UTF-8';
+        add_header 'Content-Length' 0;
+        return 204;
+     }
+
+        add_header 'Access-Control-Allow-Origin' '{{ $cors.CorsAllowOrigin }}' always;
+        {{ if $cors.CorsAllowCredentials }} add_header 'Access-Control-Allow-Credentials' '{{ $cors.CorsAllowCredentials }}' always; {{ end }}
+        add_header 'Access-Control-Allow-Methods' '{{ $cors.CorsAllowMethods }}' always;
+        add_header 'Access-Control-Allow-Headers' '{{ $cors.CorsAllowHeaders }}' always;
+
+{{ end }}
+
+{{/* definition of server-template to avoid repetitions with server-alias */}}
+{{ define "SERVER" }}
+        {{ $all := .First }}
+        {{ $server := .Second }}
+        {{ range $address := $all.Cfg.BindAddressIpv4 }}
+        listen {{ $address }}:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}};
+        {{ else }}
+        listen {{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}};
+        {{ end }}
+        {{ if $all.IsIPV6Enabled }}
+        {{ range $address := $all.Cfg.BindAddressIpv6 }}
+        listen {{ $address }}:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{ end }};
+        {{ else }}
+        listen [::]:{{ $all.ListenPorts.HTTP }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{ end }};
+        {{ end }}
+        {{ end }}
+        set $proxy_upstream_name "-";
+
+        {{/* Listen on {{ $all.ListenPorts.SSLProxy }} because port {{ $all.ListenPorts.HTTPS }} is used in the TLS sni server */}}
+        {{/* This listener must always have proxy_protocol enabled, because the SNI listener forwards on source IP info in it. */}}
+        {{ if not (empty $server.SSLCertificate) }}
+        {{ range $address := $all.Cfg.BindAddressIpv4 }}
+        listen {{ $address }}:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol {{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }} {{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}} ssl {{ if $all.Cfg.UseHTTP2 }}http2{{ end }};
+        {{ else }}
+        listen {{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol {{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }} {{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}} ssl {{ if $all.Cfg.UseHTTP2 }}http2{{ end }};
+        {{ end }}
+        {{ if $all.IsIPV6Enabled }}
+        {{ range $address := $all.Cfg.BindAddressIpv6 }}
+        {{ if not (empty $server.SSLCertificate) }}listen {{ $address }}:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }}{{ end }} {{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}} ssl {{ if $all.Cfg.UseHTTP2 }}http2{{ end }};
+        {{ else }}
+        {{ if not (empty $server.SSLCertificate) }}listen [::]:{{ if $all.IsSSLPassthroughEnabled }}{{ $all.ListenPorts.SSLProxy }} proxy_protocol{{ else }}{{ $all.ListenPorts.HTTPS }}{{ if $all.Cfg.UseProxyProtocol }} proxy_protocol{{ end }}{{ end }}{{ end }} {{ if eq $server.Hostname "_"}} default_server reuseport backlog={{ $all.BacklogSize }}{{end}} ssl {{ if $all.Cfg.UseHTTP2 }}http2{{ end }};
+        {{ end }}
+        {{ end }}
+        {{/* comment PEM sha is required to detect changes in the generated configuration and force a reload */}}
+        # PEM sha: {{ $server.SSLPemChecksum }}
+        ssl_certificate                         {{ $server.SSLCertificate }};
+        ssl_certificate_key                     {{ $server.SSLCertificate }};
+        {{ if not (empty $server.SSLFullChainCertificate)}}
+        ssl_trusted_certificate                 {{ $server.SSLFullChainCertificate }};
+        ssl_stapling                            on;
+        ssl_stapling_verify                     on;
+        {{ end }}
+        {{ end }}
+
+        {{ if (and (not (empty $server.SSLCertificate)) $all.Cfg.HSTS) }}
+        more_set_headers                        "Strict-Transport-Security: max-age={{ $all.Cfg.HSTSMaxAge }}{{ if $all.Cfg.HSTSIncludeSubdomains }}; includeSubDomains{{ end }};{{ if $all.Cfg.HSTSPreload }} preload{{ end }}";
+        {{ end }}
+
+
+        {{ if not (empty $server.CertificateAuth.CAFileName) }}
+        # PEM sha: {{ $server.CertificateAuth.PemSHA }}
+        ssl_client_certificate                  {{ $server.CertificateAuth.CAFileName }};
+        ssl_verify_client                       {{ $server.CertificateAuth.VerifyClient }};
+        ssl_verify_depth                        {{ $server.CertificateAuth.ValidationDepth }};
+        {{ if not (empty $server.CertificateAuth.ErrorPage)}}
+        error_page 495 496 = {{ $server.CertificateAuth.ErrorPage }};
+        {{ end }}
+        {{ end }}
+
+        {{ if not (empty $server.ServerSnippet) }}
+        {{ $server.ServerSnippet }}
+        {{ end }}
+
+        {{ range $location := $server.Locations }}
+        {{ $path := buildLocation $location }}
+        {{ $authPath := buildAuthLocation $location }}
+
+        {{ if not (empty $location.Rewrite.AppRoot)}}
+        if ($uri = /) {
+            return 302 {{ $location.Rewrite.AppRoot }};
+        }
+        {{ end }}
+
+        {{ if not (empty $authPath) }}
+        location = {{ $authPath }} {
+            internal;
+            set $proxy_upstream_name "external-authentication";
+
+            proxy_pass_request_body     off;
+            proxy_set_header            Content-Length "";
+
+            {{ if not (empty $location.ExternalAuth.Method) }}
+            proxy_method                {{ $location.ExternalAuth.Method }};
+            proxy_set_header            X-Original-URI          $request_uri;
+            proxy_set_header            X-Scheme                $pass_access_scheme;
+            {{ end }}
+
+            proxy_set_header            Host                    {{ $location.ExternalAuth.Host }};
+            proxy_set_header            X-Original-URL          $scheme://$http_host$request_uri;
+            proxy_set_header            X-Original-Method       $request_method;
+            proxy_set_header            X-Auth-Request-Redirect $request_uri;
+            proxy_set_header            X-Sent-From             "nginx-ingress-controller";
+
+            proxy_http_version          1.1;
+            proxy_ssl_server_name       on;
+            proxy_pass_request_headers  on;
+            client_max_body_size        "{{ $location.Proxy.BodySize }}";
+            {{ if isValidClientBodyBufferSize $location.ClientBodyBufferSize }}
+            client_body_buffer_size     {{ $location.ClientBodyBufferSize }};
+            {{ end }}
+
+            set $target {{ $location.ExternalAuth.URL }};
+            proxy_pass $target;
+        }
+        {{ end }}
+
+        location {{ $path }} {
+            {{ if $all.Cfg.EnableVtsStatus }}{{ if $location.VtsFilterKey }} vhost_traffic_status_filter_by_set_key {{ $location.VtsFilterKey }};{{ end }}{{ end }}
+
+            set $proxy_upstream_name "{{ buildUpstreamName $server.Hostname $all.Backends $location }}";
+
+            {{ $ing := (getIngressInformation $location.Ingress $path) }}
+            {{/* $ing.Metadata contains the Ingress metadata */}}
+            set $namespace      "{{ $ing.Namespace }}";
+            set $ingress_name   "{{ $ing.Rule }}";
+            set $service_name   "{{ $ing.Service }}";
+
+            {{ if (or $location.Rewrite.ForceSSLRedirect (and (not (empty $server.SSLCertificate)) $location.Rewrite.SSLRedirect)) }}
+            # enforce ssl on server side
+            if ($pass_access_scheme = http) {
+                {{ if ne $all.ListenPorts.HTTPS 443 }}
+                {{ $redirect_port := (printf ":%v" $all.ListenPorts.HTTPS) }}
+                return {{ $all.Cfg.HTTPRedirectCode }} https://$best_http_host{{ $redirect_port }}$request_uri;
+                {{ else }}
+                return {{ $all.Cfg.HTTPRedirectCode }} https://$best_http_host$request_uri;
+                {{ end }}
+            }
+            {{ end }}
+
+            {{ if $all.Cfg.EnableModsecurity }}
+            modsecurity on;
+
+            modsecurity_rules_file /etc/nginx/modsecurity/modsecurity.conf;
+            {{ if $all.Cfg.EnableOWASPCoreRules }}
+            modsecurity_rules_file /etc/nginx/owasp-modsecurity-crs/nginx-modsecurity.conf;
+            {{ end }}
+            {{ end }}
+
+            {{ if isLocationAllowed $location }}
+            {{ if gt (len $location.Whitelist.CIDR) 0 }}
+            if ({{ buildDenyVariable (print $server.Hostname "_"  $path) }}) {
+                return 403;
+            }
+            {{ end }}
+
+            port_in_redirect {{ if $location.UsePortInRedirects }}on{{ else }}off{{ end }};
+
+            {{ if not (empty $authPath) }}
+            # this location requires authentication
+            auth_request        {{ $authPath }};
+            auth_request_set    $auth_cookie $upstream_http_set_cookie;
+            add_header          Set-Cookie $auth_cookie;
+            {{- range $idx, $line := buildAuthResponseHeaders $location }}
+            {{ $line }}
+            {{- end }}
+            {{ end }}
+
+            {{ if not (empty $location.ExternalAuth.SigninURL) }}
+            error_page 401 = {{ buildAuthSignURL $location.ExternalAuth.SigninURL }};
+            {{ end }}
+
+            {{/* if the location contains a rate limit annotation, create one */}}
+            {{ $limits := buildRateLimit $location }}
+            {{ range $limit := $limits }}
+            {{ $limit }}{{ end }}
+
+            {{ if $location.BasicDigestAuth.Secured }}
+            {{ if eq $location.BasicDigestAuth.Type "basic" }}
+            auth_basic "{{ $location.BasicDigestAuth.Realm }}";
+            auth_basic_user_file {{ $location.BasicDigestAuth.File }};
+            {{ else }}
+            auth_digest "{{ $location.BasicDigestAuth.Realm }}";
+            auth_digest_user_file {{ $location.BasicDigestAuth.File }};
+            {{ end }}
+            proxy_set_header Authorization "";
+            {{ end }}
+
+            {{ if $location.CorsConfig.CorsEnabled }}
+            {{ template "CORS" $location }}
+            {{ end }}
+
+            {{ if not (empty $location.Redirect.URL) }}
+            if ($uri ~* {{ $path }}) {
+                return {{ $location.Redirect.Code }} {{ $location.Redirect.URL }};
+            }
+            {{ end }}
+
+            client_max_body_size                    "{{ $location.Proxy.BodySize }}";
+            {{ if isValidClientBodyBufferSize $location.ClientBodyBufferSize }}
+            client_body_buffer_size                 {{ $location.ClientBodyBufferSize }};
+            {{ end }}
+
+            {{/* By default use vhost as Host to upstream, but allow overrides */}}
+            {{ if not (empty $location.UpstreamVhost) }}
+            proxy_set_header Host                   "{{ $location.UpstreamVhost }}";
+            {{ else }}
+            proxy_set_header Host                   $best_http_host;
+            {{ end }}
+
+
+            # Pass the extracted client certificate to the backend
+            {{ if not (empty $server.CertificateAuth.CAFileName) }}
+            {{ if $server.CertificateAuth.PassCertToUpstream }}
+            proxy_set_header ssl-client-cert        $ssl_client_escaped_cert;
+            {{ else }}
+            proxy_set_header ssl-client-cert        "";
+            {{ end }}
+            proxy_set_header ssl-client-verify      $ssl_client_verify;
+            proxy_set_header ssl-client-dn          $ssl_client_s_dn;
+            {{ else }}
+            proxy_set_header ssl-client-cert        "";
+            proxy_set_header ssl-client-verify      "";
+            proxy_set_header ssl-client-dn          "";
+            {{ end }}
+
+            # Allow websocket connections
+            proxy_set_header                        Upgrade           $http_upgrade;
+            proxy_set_header                        Connection        $connection_upgrade;
+
+            proxy_set_header X-Real-IP              $the_real_ip;
+            {{ if $all.Cfg.ComputeFullForwardedFor }}
+            proxy_set_header X-Forwarded-For        $full_x_forwarded_for;
+            {{ else }}
+            proxy_set_header X-Forwarded-For        $the_real_ip;
+            {{ end }}
+            proxy_set_header X-Forwarded-Host       $best_http_host;
+            proxy_set_header X-Forwarded-Port       $pass_port;
+            proxy_set_header X-Forwarded-Proto      $pass_access_scheme;
+            proxy_set_header X-Original-URI         $request_uri;
+            proxy_set_header X-Scheme               $pass_access_scheme;
+
+            # Pass the original X-Forwarded-For
+            proxy_set_header X-Original-Forwarded-For {{ buildForwardedFor $all.Cfg.ForwardedForHeader }};
+
+            # mitigate HTTPoxy Vulnerability
+            # https://www.nginx.com/blog/mitigating-the-httpoxy-vulnerability-with-nginx/
+            proxy_set_header Proxy                  "";
+
+            # Custom headers to proxied server
+            {{ range $k, $v := $all.ProxySetHeaders }}
+            proxy_set_header {{ $k }}                    "{{ $v }}";
+            {{ end }}
+
+            proxy_connect_timeout                   {{ $location.Proxy.ConnectTimeout }}s;
+            proxy_send_timeout                      {{ $location.Proxy.SendTimeout }}s;
+            proxy_read_timeout                      {{ $location.Proxy.ReadTimeout }}s;
+
+            {{ if (or (eq $location.Proxy.ProxyRedirectFrom "default") (eq $location.Proxy.ProxyRedirectFrom "off")) }}
+            proxy_redirect                          {{ $location.Proxy.ProxyRedirectFrom }};
+            {{ else }}
+            proxy_redirect                          {{ $location.Proxy.ProxyRedirectFrom }} {{ $location.Proxy.ProxyRedirectTo }};
+            {{ end }}
+            proxy_buffering                         off;
+            proxy_buffer_size                       "{{ $location.Proxy.BufferSize }}";
+            proxy_buffers                           4 "{{ $location.Proxy.BufferSize }}";
+            proxy_request_buffering                 "{{ $location.Proxy.RequestBuffering }}";
+
+            proxy_http_version                      1.1;
+
+            proxy_cookie_domain                     {{ $location.Proxy.CookieDomain }};
+            proxy_cookie_path                       {{ $location.Proxy.CookiePath }};
+
+            # In case of errors try the next upstream server before returning an error
+            proxy_next_upstream                     {{ buildNextUpstream $location.Proxy.NextUpstream $all.Cfg.RetryNonIdempotent }};
+
+            {{/* rewrite only works if the content is not compressed */}}
+            {{ if $location.Rewrite.AddBaseURL }}
+            proxy_set_header                        Accept-Encoding     "";
+            {{ end }}
+
+            {{/* Add any additional configuration defined */}}
+            {{ $location.ConfigurationSnippet }}
+
+            {{ if not (empty $all.Cfg.LocationSnippet) }}
+            # Custom code snippet configured in the configuration configmap
+            {{ $all.Cfg.LocationSnippet }}
+            {{ end }}
+
+            {{/* if we are sending the request to a custom default backend, we add the required headers */}}
+            {{ if (hasPrefix $location.Backend "custom-default-backend-") }}
+            proxy_set_header       X-Code             503;
+            proxy_set_header       X-Format           $http_accept;
+            proxy_set_header       X-Namespace        $namespace;
+            proxy_set_header       X-Ingress-Name     $ingress_name;
+            proxy_set_header       X-Service-Name     $service_name;
+            {{ end }}
+
+
+            {{ if not (empty $location.Backend) }}
+            {{ buildProxyPass $server.Hostname $all.Backends $location }}
+            {{ else }}
+            # No endpoints available for the request
+            return 503;
+            {{ end }}
+            {{ else }}
+            # Location denied. Reason: {{ $location.Denied }}
+            return 503;
+            {{ end }}
+        }
+
+        {{ end }}
+
+        {{ if eq $server.Hostname "_" }}
+        # health checks in cloud providers require the use of port {{ $all.ListenPorts.HTTP }}
+        location {{ $all.HealthzURI }} {
+            access_log off;
+            return 200;
+        }
+
+        # this is required to avoid error if nginx is being monitored
+        # with an external software (like sysdig)
+        location /nginx_status {
+            allow 127.0.0.1;
+            {{ if $all.IsIPV6Enabled }}allow ::1;{{ end }}
+            deny all;
+
+            access_log off;
+            stub_status on;
+        }
+
+        {{ end }}
+
+{{ end }}
diff --git a/mariadb/requirements.yaml b/mariadb/requirements.yaml
new file mode 100644
index 000000000..53782e69b
--- /dev/null
+++ b/mariadb/requirements.yaml
@@ -0,0 +1,18 @@
+# Copyright 2017 The Openstack-Helm Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+dependencies:
+  - name: helm-toolkit
+    repository: http://localhost:8879/charts
+    version: 0.1.0
diff --git a/mariadb/templates/bin/_mariadb-ingress-controller.sh.tpl b/mariadb/templates/bin/_mariadb-ingress-controller.sh.tpl
new file mode 100644
index 000000000..af6e0c0c7
--- /dev/null
+++ b/mariadb/templates/bin/_mariadb-ingress-controller.sh.tpl
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+set -ex
+COMMAND="${@:-start}"
+
+function start () {
+  exec /usr/bin/dumb-init \
+      /nginx-ingress-controller \
+      --force-namespace-isolation \
+      --watch-namespace ${POD_NAMESPACE} \
+      --election-id=${RELEASE_NAME} \
+      --ingress-class=${INGRESS_CLASS} \
+      --default-backend-service=${POD_NAMESPACE}/${ERROR_PAGE_SERVICE} \
+      --tcp-services-configmap=${POD_NAMESPACE}/mariadb-services-tcp
+}
+
+
+function stop () {
+  kill -TERM 1
+}
+
+$COMMAND
diff --git a/mariadb/templates/bin/_mariadb-ingress-error-pages.sh.tpl b/mariadb/templates/bin/_mariadb-ingress-error-pages.sh.tpl
new file mode 100644
index 000000000..cf62c33f4
--- /dev/null
+++ b/mariadb/templates/bin/_mariadb-ingress-error-pages.sh.tpl
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+set -ex
+COMMAND="${@:-start}"
+
+if [ "x${COMMAND}" == "xstart" ]; then
+  exec /server
+elif [ "x${COMMAND}" == "xstop" ]; then
+  kill -TERM 1
+fi
diff --git a/mariadb/templates/bin/_readiness.sh.tpl b/mariadb/templates/bin/_readiness.sh.tpl
new file mode 100644
index 000000000..86f513e21
--- /dev/null
+++ b/mariadb/templates/bin/_readiness.sh.tpl
@@ -0,0 +1,52 @@
+#!/usr/bin/env bash
+
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+set -e
+
+MYSQL="mysql \
+  --defaults-file=/etc/mysql/admin_user.cnf \
+  --host=localhost \
+  --connect-timeout 2"
+
+mysql_status_query () {
+  STATUS=$1
+  $MYSQL -e "show status like \"${STATUS}\"" | \
+    awk "/${STATUS}/ { print \$NF; exit }"
+}
+
+if ! $MYSQL -e 'select 1' > /dev/null 2>&1 ; then
+    exit 1
+fi
+
+if [ "x$(mysql_status_query wsrep_cluster_status)" != "xPrimary" ]; then
+    # Not in primary cluster
+    exit 1
+fi
+if [ "x$(mysql_status_query wsrep_ready)" != "xON" ]; then
+    # WSREP not ready
+    exit 1
+fi
+if [ "x$(mysql_status_query wsrep_local_state_comment)" != "xSynced" ]; then
+    # WSREP not synced
+    exit 1
+fi
+
+# If we made it this far, its safe to remove the bootstrap file if present
+if [ -e ${BOOTSTRAP_FILE} ]; then
+  rm -f ${BOOTSTRAP_FILE}
+fi
diff --git a/mariadb/templates/bin/_start.sh.tpl b/mariadb/templates/bin/_start.sh.tpl
new file mode 100644
index 000000000..6920a9af2
--- /dev/null
+++ b/mariadb/templates/bin/_start.sh.tpl
@@ -0,0 +1,188 @@
+#!/bin/bash
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+set -xe
+
+# MariaDB 10.2.13 has a regression which breaks clustering, patch
+# around this for now
+if /usr/sbin/mysqld --version | grep --silent 10.2.13 ; then
+    sed -i 's^LSOF_OUT=.*^LSOF_OUT=$(lsof -sTCP:LISTEN -i TCP:${PORT} -a -c nc -c socat -F c 2> /dev/null || :)^' /usr/bin/wsrep_sst_xtrabackup-v2
+fi
+
+# Bootstrap database
+CLUSTER_INIT_ARGS=""
+CLUSTER_CONFIG_PATH=/etc/mysql/conf.d/10-cluster-config.cnf
+
+function exitWithManualRecovery() {
+
+    UUID=$(sed -e 's/^.*uuid:[\ ,\t]*//' -e 'tx' -e 'd' -e ':x' /var/lib/mysql/grastate.dat)
+    SEQNO=$(sed -e 's/^.*seqno:[\ ,\t]*//' -e 'tx' -e 'd' -e ':x' /var/lib/mysql/grastate.dat)
+
+    cat >/dev/stderr <<EOF
+   **********************************************************
+   *            MANUAL RECOVERY ACTION REQUIRED             *
+   **********************************************************
+
+All cluster members are down and grastate.dat indicates that it's not
+safe to start the cluster from this node. If you see this message on
+all nodes, you have to do a manual recovery by following these steps:
+
+    a) Find the node with the highest WSREP seq#:
+
+	POD ${PODNAME} uuid: ${UUID} seq: ${SEQNO}
+
+   	If you see uuid 00000000-0000-0000-0000-000000000000 with
+   	seq -1, the node crashed during DDL.
+
+   	If seq is -1 you will find a DETECTED CRASH message
+   	on your log. Check the output from InnoDB for the last
+   	transaction id available.
+
+    b) Set environment variable FORCE_RECOVERY=<NAME OF POD>
+       to force bootstrapping from the specified node.
+
+        Remember to remove FORCE_RECOVERY after your nodes
+        are fully recovered! You may lose data otherwise.
+
+You can ignore this message and wait for the next restart if at
+least one node started without errors.
+EOF
+
+    exit 1
+}
+
+# Construct cluster config
+MEMBERS=""
+for i in $(seq 1 ${MARIADB_REPLICAS}); do
+    if [ "$i" -eq "1" ]; then
+      NUM="0"
+    else
+      NUM="$(expr $i - 1)"
+    fi
+    CANDIDATE_POD="${POD_NAME_PREFIX}-$NUM.$(hostname -d)"
+    if [ "x${CANDIDATE_POD}" != "x${POD_NAME}.$(hostname -d)" ]; then
+        if [ -n "${MEMBERS}" ]; then
+            MEMBERS+=,
+        fi
+        MEMBERS+="${CANDIDATE_POD}:${WSREP_PORT}"
+    fi
+done
+
+echo "Writing cluster config for ${POD_NAME} to ${CLUSTER_CONFIG_PATH}"
+cat > ${CLUSTER_CONFIG_PATH} <<EOF
+[mysqld]
+wsrep_cluster_address="gcomm://${MEMBERS}"
+wsrep_node_address=${POD_IP}
+wsrep_node_name=${POD_NAME}.$(hostname -d)
+EOF
+
+if [ ! -z "${FORCE_RECOVERY// }" ]; then
+    	cat >/dev/stderr <<EOF
+   **********************************************************
+   *    !!!        FORCE_RECOVERY WARNING       !!!         *
+   **********************************************************
+
+POD is starting with FORCE_RECOVERY defined. Remember to unset this
+variable after recovery! You may end up in recovering from a node
+with old data on a crash!
+
+You have been warned ;-)
+
+   **********************************************************
+   *               FORCE_RECOVERY WARNING                   *
+   **********************************************************
+EOF
+
+fi
+
+if [ -d /var/lib/mysql/mysql -a -f /var/lib/mysql/grastate.dat ]; then
+
+    # Node already initialized
+
+    if [ "$(sed -e 's/^.*seqno:[\ ,\t]*//' -e 'tx' -e 'd' -e ':x' /var/lib/mysql/grastate.dat)" = "-1" ]; then
+    	cat >/dev/stderr <<EOF
+   **********************************************************
+   *                   DETECTED CRASH                       *
+   **********************************************************
+
+Trying to recover from a previous crash by running with wsrep-recover...
+EOF
+	mysqld --wsrep_cluster_address=gcomm:// --wsrep-recover
+    fi
+
+    echo "Check if we can find a cluster memeber."
+    if ! mysql --defaults-file=/etc/mysql/admin_user.cnf \
+        --connect-timeout 2 \
+         -e 'select 1'; then
+	# No other nodes are running
+    	if [ -z "${FORCE_RECOVERY// }" -a "$(sed -e 's/^.*safe_to_bootstrap:[\ ,\t]*//' -e 'tx' -e 'd' -e ':x' /var/lib/mysql/grastate.dat)" = "1" ]; then
+            echo 'Bootstrapping from this node.'
+            CLUSTER_INIT_ARGS=--wsrep-new-cluster
+        elif [ "x${FORCE_RECOVERY}x" = "x${POD_NAME}x" ]; then
+            echo 'Forced recovery bootstrap from this node.'
+            CLUSTER_INIT_ARGS=--wsrep-new-cluster
+            cp -f /var/lib/mysql/grastate.dat /var/lib/mysql/grastate.bak
+    	    cat >/var/lib/mysql/grastate.dat <<EOF
+`grep -v 'safe_to_bootstrap:' /var/lib/mysql/grastate.bak`
+safe_to_bootstrap: 1
+EOF
+	    chown -R mysql:mysql /var/lib/mysql/grastate.dat
+        else
+    	    exitWithManualRecovery
+    	fi
+    fi
+
+elif [ ! -d /var/lib/mysql/mysql -o "x${FORCE_BOOTSTRAP}" = "xtrue" ]; then
+    if [ "x${POD_NAME}" = "x${POD_NAME_PREFIX}-0" ]; then
+        echo No data found for pod 0
+        if [ "x${FORCE_BOOTSTRAP}" = "xtrue" ]; then
+            echo 'force_bootstrap set, so will force-initialize node 0.'
+            CLUSTER_INIT_ARGS=--wsrep-new-cluster
+            CLUSTER_BOOTSTRAP=true
+        elif ! mysql --defaults-file=/etc/mysql/admin_user.cnf \
+                     --connect-timeout 2 \
+                     -e 'select 1'; then
+            echo 'No other nodes found, so will initialize cluster.'
+            CLUSTER_INIT_ARGS=--wsrep-new-cluster
+            CLUSTER_BOOTSTRAP=true
+        else
+            echo 'Found other live nodes, will attempt to join them.'
+            mkdir /var/lib/mysql/mysql
+        fi
+    else
+        echo 'Not pod 0, so will avoid upstream database initialization.'
+        mkdir /var/lib/mysql/mysql
+    fi
+    chown -R mysql:mysql /var/lib/mysql
+fi
+
+
+if [ "x${CLUSTER_BOOTSTRAP}" = "xtrue" ]; then
+  mysql_install_db --user=mysql --datadir=/var/lib/mysql
+
+  cat > "${BOOTSTRAP_FILE}" << EOF
+DELETE FROM mysql.user ;
+CREATE OR REPLACE USER 'root'@'%' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ;
+GRANT ALL ON *.* TO 'root'@'%' WITH GRANT OPTION ;
+DROP DATABASE IF EXISTS test ;
+FLUSH PRIVILEGES ;
+EOF
+
+  CLUSTER_INIT_ARGS="${CLUSTER_INIT_ARGS} --init-file=${BOOTSTRAP_FILE}"
+fi
+
+exec mysqld ${CLUSTER_INIT_ARGS}
diff --git a/mariadb/templates/bin/_stop.sh.tpl b/mariadb/templates/bin/_stop.sh.tpl
new file mode 100644
index 000000000..c197065a0
--- /dev/null
+++ b/mariadb/templates/bin/_stop.sh.tpl
@@ -0,0 +1,24 @@
+#!/bin/bash
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+set -xe
+
+exec mysqladmin \
+    --defaults-file=/etc/mysql/admin_user.cnf \
+    --host=localhost \
+    --connect-timeout 2 \
+    shutdown
diff --git a/mariadb/templates/configmap-bin.yaml b/mariadb/templates/configmap-bin.yaml
new file mode 100644
index 000000000..5e0b62cfc
--- /dev/null
+++ b/mariadb/templates/configmap-bin.yaml
@@ -0,0 +1,39 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+{{- if .Values.manifests.configmap_bin }}
+{{- $envAll := . }}
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: mariadb-bin
+data:
+{{- if .Values.images.local_registry.active }}
+  image-repo-sync.sh: |
+{{- include "helm-toolkit.scripts.image_repo_sync" . | indent 4 }}
+{{- end }}
+  mariadb-ingress-controller.sh: |
+{{ tuple "bin/_mariadb-ingress-controller.sh.tpl" . | include "helm-toolkit.utils.template" | indent 4 }}
+  mariadb-ingress-error-pages.sh: |
+{{ tuple "bin/_mariadb-ingress-error-pages.sh.tpl" . | include "helm-toolkit.utils.template" | indent 4 }}
+  readiness.sh: |
+{{ tuple "bin/_readiness.sh.tpl" . | include "helm-toolkit.utils.template" | indent 4 }}
+  start.sh: |
+{{ tuple "bin/_start.sh.tpl" . | include "helm-toolkit.utils.template" | indent 4 }}
+  stop.sh: |
+{{ tuple "bin/_stop.sh.tpl" . | include "helm-toolkit.utils.template" | indent 4 }}
+{{- end }}
diff --git a/mariadb/templates/configmap-etc.yaml b/mariadb/templates/configmap-etc.yaml
new file mode 100644
index 000000000..aa11d5db9
--- /dev/null
+++ b/mariadb/templates/configmap-etc.yaml
@@ -0,0 +1,39 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+{{- if .Values.manifests.configmap_etc }}
+{{- $envAll := . }}
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: mariadb-etc
+data:
+  my.cnf: |
+{{ tuple "etc/_my.cnf.tpl" $envAll | include "helm-toolkit.utils.template" | indent 4 }}
+  00-base.cnf: |
+{{ tuple "etc/_00-base.cnf.tpl" $envAll | include "helm-toolkit.utils.template" | indent 4 }}
+  20-override.cnf: |
+{{ tuple "etc/_20-override.cnf.tpl" $envAll | include "helm-toolkit.utils.template" | indent 4 }}
+  99-force.cnf: |
+{{ tuple "etc/_99-force.cnf.tpl" $envAll | include "helm-toolkit.utils.template" | indent 4 }}
+{{- if $envAll.Values.conf.ingress }}
+  nginx.tmpl: |
+{{ $envAll.Values.conf.ingress | indent 4 }}
+{{- else }}
+{{ ( $envAll.Files.Glob "files/nginx.tmpl" ).AsConfig | indent 2 }}
+{{- end }}
+{{- end }}
diff --git a/mariadb/templates/configmap-services-tcp.yaml b/mariadb/templates/configmap-services-tcp.yaml
new file mode 100644
index 000000000..605a18609
--- /dev/null
+++ b/mariadb/templates/configmap-services-tcp.yaml
@@ -0,0 +1,26 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+{{- if .Values.manifests.configmap_services_tcp }}
+{{- $envAll := . }}
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: mariadb-services-tcp
+data:
+  {{ tuple "oslo_db" "internal" "mysql" . | include "helm-toolkit.endpoints.endpoint_port_lookup" }}: "{{ .Release.Namespace }}/{{ tuple "oslo_db" "direct" . | include "helm-toolkit.endpoints.hostname_short_endpoint_lookup" }}:{{ tuple "oslo_db" "direct" "mysql" . | include "helm-toolkit.endpoints.endpoint_port_lookup" }}"
+{{- end }}
diff --git a/mariadb/templates/deployment-error.yaml b/mariadb/templates/deployment-error.yaml
new file mode 100644
index 000000000..87d4c1616
--- /dev/null
+++ b/mariadb/templates/deployment-error.yaml
@@ -0,0 +1,83 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+{{- if .Values.manifests.deployment_error }}
+{{- $envAll := . }}
+
+{{- $serviceAccountName := "mariadb-ingress-error-pages"}}
+{{ tuple $envAll "error_pages" $serviceAccountName | include "helm-toolkit.snippets.kubernetes_pod_rbac_serviceaccount" }}
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: mariadb-ingress-error-pages
+  labels:
+{{ tuple $envAll "mariadb" "ingress-error-pages" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 4 }}
+spec:
+  replicas: {{ .Values.pod.replicas.error_page }}
+  selector:
+    matchLabels:
+{{ tuple $envAll "mariadb" "ingress-error-pages" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 6 }}
+{{ tuple $envAll | include "helm-toolkit.snippets.kubernetes_upgrades_deployment" | indent 2 }}
+  template:
+    metadata:
+      labels:
+{{ tuple $envAll "mariadb" "ingress-error-pages" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 8 }}
+      annotations:
+        configmap-bin-hash: {{ tuple "configmap-bin.yaml" . | include "helm-toolkit.utils.hash" }}
+        configmap-etc-hash: {{ tuple "configmap-etc.yaml" . | include "helm-toolkit.utils.hash" }}
+    spec:
+      serviceAccountName: {{ $serviceAccountName }}
+      affinity:
+{{ tuple $envAll "mariadb" "ingress-error-pages" | include "helm-toolkit.snippets.kubernetes_pod_anti_affinity" | indent 8 }}
+      nodeSelector:
+        {{ .Values.labels.error_server.node_selector_key }}: {{ .Values.labels.error_server.node_selector_value }}
+      terminationGracePeriodSeconds: {{ .Values.pod.lifecycle.termination_grace_period.error_pages.timeout | default "60" }}
+      initContainers:
+{{ tuple $envAll "error_pages" list | include "helm-toolkit.snippets.kubernetes_entrypoint_init_container" | indent 8 }}
+      containers:
+        - name: ingress-error-pages
+{{ tuple $envAll "error_pages" | include "helm-toolkit.snippets.image" | indent 10 }}
+{{ tuple $envAll $envAll.Values.pod.resources.error_pages | include "helm-toolkit.snippets.kubernetes_resources" | indent 10 }}
+          livenessProbe:
+            httpGet:
+              path: /healthz
+              port: 8080
+              scheme: HTTP
+            initialDelaySeconds: 30
+            timeoutSeconds: 5
+          ports:
+            - containerPort: 8080
+          command:
+            - /tmp/mariadb-ingress-error-pages.sh
+            - start
+          lifecycle:
+            preStop:
+              exec:
+                command:
+                  - /tmp/mariadb-ingress-error-pages.sh
+                  - stop
+          volumeMounts:
+            - name: ingress-bin
+              mountPath: /tmp/mariadb-ingress-error-pages.sh
+              subPath: mariadb-ingress-error-pages.sh
+              readOnly: true
+      volumes:
+      - name: ingress-bin
+        configMap:
+          name: mariadb-bin
+          defaultMode: 0555
+{{- end }}
diff --git a/mariadb/templates/deployment-ingress.yaml b/mariadb/templates/deployment-ingress.yaml
new file mode 100644
index 000000000..4bfc147fe
--- /dev/null
+++ b/mariadb/templates/deployment-ingress.yaml
@@ -0,0 +1,202 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+{{- if .Values.manifests.deployment_ingress }}
+{{- $envAll := . }}
+
+{{- $ingressClass := printf "%s-%s" .Release.Name "mariadb-ingress" }}
+
+{{- $serviceAccountName := printf "%s-%s" .Release.Name "ingress" }}
+{{ tuple $envAll "ingress" $serviceAccountName | include "helm-toolkit.snippets.kubernetes_pod_rbac_serviceaccount" }}
+---
+apiVersion: rbac.authorization.k8s.io/v1beta1
+kind: Role
+metadata:
+  name: {{ $serviceAccountName }}
+  namespace: {{ $envAll.Release.Namespace }}
+rules:
+  - apiGroups:
+      - ""
+    resources:
+      - services
+    verbs:
+      - get
+      - list
+      - watch
+  - apiGroups:
+      - extensions
+    resources:
+      - ingresses
+    verbs:
+      - get
+      - list
+      - watch
+  - apiGroups:
+      - ""
+    resources:
+      - events
+    verbs:
+      - create
+      - patch
+  - apiGroups:
+      - extensions
+    resources:
+      - ingresses/status
+    verbs:
+      - update
+  - apiGroups:
+      - ""
+    resources:
+      - configmaps
+      - endpoints
+      - nodes
+      - pods
+      - secrets
+    verbs:
+      - list
+      - watch
+  - apiGroups:
+      - ""
+    resources:
+      - configmaps
+      - pods
+      - secrets
+      - namespaces
+    verbs:
+      - get
+  - apiGroups:
+      - ""
+    resourceNames:
+      - {{ printf "%s-%s" .Release.Name $ingressClass | quote }}
+    resources:
+      - configmaps
+    verbs:
+      - get
+      - update
+  - apiGroups:
+      - ""
+    resources:
+      - configmaps
+    verbs:
+      - create
+  - apiGroups:
+      - ""
+    resources:
+      - endpoints
+    verbs:
+      - get
+      - create
+      - update
+---
+apiVersion: rbac.authorization.k8s.io/v1beta1
+kind: RoleBinding
+metadata:
+  name: {{ $serviceAccountName }}
+  namespace: {{ $envAll.Release.Namespace }}
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: {{ $serviceAccountName }}
+subjects:
+  - kind: ServiceAccount
+    name: {{ $serviceAccountName }}
+    namespace: {{ $envAll.Release.Namespace }}
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: mariadb-ingress
+  labels:
+{{ tuple $envAll "mariadb" "ingress" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 4 }}
+spec:
+  replicas: {{ .Values.pod.replicas.ingress }}
+  selector:
+    matchLabels:
+{{ tuple $envAll "mariadb" "ingress" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 6 }}
+{{ tuple $envAll | include "helm-toolkit.snippets.kubernetes_upgrades_deployment" | indent 2 }}
+  template:
+    metadata:
+      labels:
+{{ tuple $envAll "mariadb" "ingress" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 8 }}
+      annotations:
+        configmap-bin-hash: {{ tuple "configmap-bin.yaml" . | include "helm-toolkit.utils.hash" }}
+        configmap-etc-hash: {{ tuple "configmap-etc.yaml" . | include "helm-toolkit.utils.hash" }}
+    spec:
+      serviceAccountName: {{ $serviceAccountName }}
+      affinity:
+{{ tuple $envAll "mariadb" "ingress" | include "helm-toolkit.snippets.kubernetes_pod_anti_affinity" | indent 8 }}
+      nodeSelector:
+        {{ .Values.labels.ingress.node_selector_key }}: {{ .Values.labels.ingress.node_selector_value }}
+      terminationGracePeriodSeconds: 60
+      initContainers:
+{{ tuple $envAll "ingress" list | include "helm-toolkit.snippets.kubernetes_entrypoint_init_container" | indent 8 }}
+      containers:
+        - name: ingress
+{{ tuple $envAll "ingress" | include "helm-toolkit.snippets.image" | indent 10 }}
+{{ tuple $envAll $envAll.Values.pod.resources.ingress | include "helm-toolkit.snippets.kubernetes_resources" | indent 10 }}
+          readinessProbe:
+            tcpSocket:
+              port: {{ tuple "oslo_db" "internal" "mysql" . | include "helm-toolkit.endpoints.endpoint_port_lookup" }}
+          livenessProbe:
+            httpGet:
+              path: /healthz
+              port: 10254
+              scheme: HTTP
+            initialDelaySeconds: 10
+            timeoutSeconds: 1
+          env:
+            - name: POD_NAME
+              valueFrom:
+                fieldRef:
+                  fieldPath: metadata.name
+            - name: POD_NAMESPACE
+              valueFrom:
+                fieldRef:
+                  fieldPath: metadata.namespace
+            - name: RELEASE_NAME
+              value: {{ .Release.Name | quote }}
+            - name: INGRESS_CLASS
+              value: {{ $ingressClass | quote }}
+            - name: ERROR_PAGE_SERVICE
+              value: {{ tuple "oslo_db" "error_pages" . | include "helm-toolkit.endpoints.hostname_short_endpoint_lookup" | quote }}
+          command:
+            - /tmp/mariadb-ingress-controller.sh
+            - start
+          lifecycle:
+            preStop:
+              exec:
+                command:
+                  - /tmp/mariadb-ingress-controller.sh
+                  - stop
+          volumeMounts:
+            - name: mariadb-bin
+              mountPath: /tmp/mariadb-ingress-controller.sh
+              subPath: mariadb-ingress-controller.sh
+              readOnly: true
+            - name: mariadb-etc
+              mountPath: /etc/nginx/template/nginx.tmpl
+              subPath: nginx.tmpl
+              readOnly: true
+      volumes:
+        - name: mariadb-bin
+          configMap:
+            name: mariadb-bin
+            defaultMode: 0555
+        - name: mariadb-etc
+          configMap:
+            name: mariadb-etc
+            defaultMode: 0444
+{{- end }}
diff --git a/mariadb/templates/etc/_00-base.cnf.tpl b/mariadb/templates/etc/_00-base.cnf.tpl
new file mode 100644
index 000000000..5e2597f99
--- /dev/null
+++ b/mariadb/templates/etc/_00-base.cnf.tpl
@@ -0,0 +1,107 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+[mysqld]
+# Charset
+character_set_server=utf8
+collation_server=utf8_unicode_ci
+skip-character-set-client-handshake
+
+# Logging
+slow_query_log=on
+slow_query_log_file=/var/log/mysql/mariadb-slow.log
+log_warnings=2
+
+# General logging has huge performance penalty therefore is disabled by default
+general_log=off
+general_log_file=/var/log/mysql/mariadb-error.log
+
+long_query_time=3
+log_queries_not_using_indexes=on
+
+# Networking
+bind_address=0.0.0.0
+port={{ tuple "oslo_db" "direct" "mysql" . | include "helm-toolkit.endpoints.endpoint_port_lookup" }}
+
+# When a client connects, the server will perform hostname resolution,
+# and when DNS is slow, establishing the connection will become slow as well.
+# It is therefore recommended to start the server with skip-name-resolve to
+# disable all DNS lookups. The only limitation is that the GRANT statements
+# must then use IP addresses only.
+skip_name_resolve
+
+# Tuning
+user=mysql
+max_allowed_packet=256M
+open_files_limit=10240
+max_connections=8192
+max-connect-errors=1000000
+
+## Generally, it is unwise to set the query cache to be larger than 64-128M
+## as the costs associated with maintaining the cache outweigh the performance
+## gains.
+## The query cache is a well known bottleneck that can be seen even when
+## concurrency is moderate. The best option is to disable it from day 1
+## by setting query_cache_size=0 (now the default on MySQL 5.6)
+## and to use other ways to speed up read queries: good indexing, adding
+## replicas to spread the read load or using an external cache.
+query_cache_size=0
+query_cache_type=0
+
+sync_binlog=0
+thread_cache_size=16
+table_open_cache=2048
+table_definition_cache=1024
+
+#
+# InnoDB
+#
+# The buffer pool is where data and indexes are cached: having it as large as possible
+# will ensure you use memory and not disks for most read operations.
+# Typical values are 50..75% of available RAM.
+# TODO(tomasz.paszkowski): This needs to by dynamic based on available RAM.
+innodb_buffer_pool_size=1024M
+innodb_doublewrite=0
+innodb_file_format=Barracuda
+innodb_file_per_table=1
+innodb_flush_method=O_DIRECT
+innodb_io_capacity=500
+innodb_locks_unsafe_for_binlog=1
+innodb_log_file_size=128M
+innodb_old_blocks_time=1000
+innodb_read_io_threads=8
+innodb_write_io_threads=8
+
+# Clustering
+binlog_format=ROW
+default-storage-engine=InnoDB
+innodb_autoinc_lock_mode=2
+innodb_flush_log_at_trx_commit=2
+wsrep_cluster_name={{ tuple "oslo_db" "direct" . | include "helm-toolkit.endpoints.hostname_namespaced_endpoint_lookup" | replace "." "_" }}
+wsrep_on=1
+wsrep_provider=/usr/lib/galera/libgalera_smm.so
+wsrep_provider_options="gmcast.listen_addr=tcp://0.0.0.0:{{ tuple "oslo_db" "direct" "wsrep" . | include "helm-toolkit.endpoints.endpoint_port_lookup" }}"
+wsrep_slave_threads=12
+wsrep_sst_auth=root:{{ .Values.endpoints.oslo_db.auth.admin.password }}
+wsrep_sst_method=xtrabackup-v2
+
+[mysqldump]
+max-allowed-packet=16M
+
+[client]
+default_character_set=utf8
+protocol=tcp
+port={{ tuple "oslo_db" "direct" "mysql" . | include "helm-toolkit.endpoints.endpoint_port_lookup" }}
diff --git a/mariadb/templates/etc/_20-override.cnf.tpl b/mariadb/templates/etc/_20-override.cnf.tpl
new file mode 100644
index 000000000..7c445fd65
--- /dev/null
+++ b/mariadb/templates/etc/_20-override.cnf.tpl
@@ -0,0 +1,17 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+{{ .Values.database.config_override }}
diff --git a/mariadb/templates/etc/_99-force.cnf.tpl b/mariadb/templates/etc/_99-force.cnf.tpl
new file mode 100644
index 000000000..3d92e99ff
--- /dev/null
+++ b/mariadb/templates/etc/_99-force.cnf.tpl
@@ -0,0 +1,19 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+[mysqld]
+datadir=/var/lib/mysql
+tmpdir=/tmp
diff --git a/mariadb/templates/etc/_my.cnf.tpl b/mariadb/templates/etc/_my.cnf.tpl
new file mode 100644
index 000000000..33184d529
--- /dev/null
+++ b/mariadb/templates/etc/_my.cnf.tpl
@@ -0,0 +1,22 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+[mysqld]
+datadir=/var/lib/mysql
+basedir=/usr
+
+[client-server]
+!includedir /etc/mysql/conf.d/
diff --git a/mariadb/templates/job-image-repo-sync.yaml b/mariadb/templates/job-image-repo-sync.yaml
new file mode 100644
index 000000000..e099429a1
--- /dev/null
+++ b/mariadb/templates/job-image-repo-sync.yaml
@@ -0,0 +1,20 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+{{- if and .Values.manifests.job_image_repo_sync .Values.images.local_registry.active }}
+{{- $imageRepoSyncJob := dict "envAll" . "serviceName" "mariadb" -}}
+{{ $imageRepoSyncJob | include "helm-toolkit.manifests.job_image_repo_sync" }}
+{{- end }}
diff --git a/mariadb/templates/monitoring/prometheus/bin/_create-mysql-user.sh.tpl b/mariadb/templates/monitoring/prometheus/bin/_create-mysql-user.sh.tpl
new file mode 100644
index 000000000..49773d0a1
--- /dev/null
+++ b/mariadb/templates/monitoring/prometheus/bin/_create-mysql-user.sh.tpl
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+set -ex
+
+mysql --defaults-file=/etc/mysql/admin_user.cnf -e \
+  "CREATE OR REPLACE USER '${EXPORTER_USER}'@'%' IDENTIFIED BY '${EXPORTER_PASSWORD}'; \
+   GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO '${EXPORTER_USER}'@'%'; \
+   FLUSH PRIVILEGES;"
diff --git a/mariadb/templates/monitoring/prometheus/bin/_mysqld-exporter.sh.tpl b/mariadb/templates/monitoring/prometheus/bin/_mysqld-exporter.sh.tpl
new file mode 100644
index 000000000..6a7395fcc
--- /dev/null
+++ b/mariadb/templates/monitoring/prometheus/bin/_mysqld-exporter.sh.tpl
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+set -ex
+COMMAND="${@:-start}"
+
+function start () {
+  exec /bin/mysqld_exporter -config.my-cnf=/etc/mysql/mysql_user.cnf
+}
+
+function stop () {
+  kill -TERM 1
+}
+
+$COMMAND
diff --git a/mariadb/templates/monitoring/prometheus/exporter-configmap-bin.yaml b/mariadb/templates/monitoring/prometheus/exporter-configmap-bin.yaml
new file mode 100644
index 000000000..169f8e56a
--- /dev/null
+++ b/mariadb/templates/monitoring/prometheus/exporter-configmap-bin.yaml
@@ -0,0 +1,29 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+{{- if and .Values.manifests.monitoring.prometheus.configmap_bin .Values.monitoring.prometheus.enabled }}
+{{- $envAll := . }}
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: mysql-exporter-bin
+data:
+  create-mysql-user.sh: |
+{{ tuple "bin/_create-mysql-user.sh.tpl" . | include "helm-toolkit.utils.template" | indent 4 }}
+  mysqld-exporter.sh: |
+{{ tuple "bin/_mysqld-exporter.sh.tpl" . | include "helm-toolkit.utils.template" | indent 4 }}
+{{- end }}
diff --git a/mariadb/templates/monitoring/prometheus/exporter-deployment.yaml b/mariadb/templates/monitoring/prometheus/exporter-deployment.yaml
new file mode 100644
index 000000000..274a06c0e
--- /dev/null
+++ b/mariadb/templates/monitoring/prometheus/exporter-deployment.yaml
@@ -0,0 +1,91 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+{{- if and .Values.manifests.monitoring.prometheus.deployment_exporter .Values.monitoring.prometheus.enabled }}
+{{- $envAll := . }}
+
+{{- $serviceAccountName := "prometheus-mysql-exporter"}}
+{{ tuple $envAll "prometheus_mysql_exporter" $serviceAccountName | include "helm-toolkit.snippets.kubernetes_pod_rbac_serviceaccount" }}
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: prometheus-mysql-exporter
+  labels:
+{{ tuple $envAll "prometheus_mysql_exporter" "exporter" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 4 }}
+spec:
+  replicas: {{ .Values.pod.replicas.prometheus_mysql_exporter }}
+  selector:
+    matchLabels:
+{{ tuple $envAll "prometheus_mysql_exporter" "exporter" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 6 }}
+{{ tuple $envAll | include "helm-toolkit.snippets.kubernetes_upgrades_deployment" | indent 2 }}
+  template:
+    metadata:
+      labels:
+{{ tuple $envAll "prometheus_mysql_exporter" "exporter" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 8 }}
+      namespace: {{ .Values.endpoints.prometheus_mysql_exporter.namespace }}
+    spec:
+      serviceAccountName: {{ $serviceAccountName }}
+      nodeSelector:
+        {{ .Values.labels.prometheus_mysql_exporter.node_selector_key }}: {{ .Values.labels.prometheus_mysql_exporter.node_selector_value }}
+      terminationGracePeriodSeconds: {{ .Values.pod.lifecycle.termination_grace_period.prometheus_mysql_exporter.timeout | default "30" }}
+      initContainers:
+{{ tuple $envAll "prometheus_mysql_exporter" list | include "helm-toolkit.snippets.kubernetes_entrypoint_init_container" | indent 8 }}
+      containers:
+        - name: mysql-exporter
+{{ tuple $envAll "prometheus_mysql_exporter" | include "helm-toolkit.snippets.image" | indent 10 }}
+{{ tuple $envAll $envAll.Values.pod.resources.prometheus_mysql_exporter | include "helm-toolkit.snippets.kubernetes_resources" | indent 10 }}
+          command:
+            - /tmp/mysqld-exporter.sh
+            - start
+          ports:
+            - name: metrics
+              containerPort: {{ .Values.network.prometheus_mysql_exporter.port }}
+          env:
+            - name: EXPORTER_USER
+              valueFrom:
+                secretKeyRef:
+                  name: mysql-exporter-secrets
+                  key: EXPORTER_USER
+            - name: EXPORTER_PASSWORD
+              valueFrom:
+                secretKeyRef:
+                  name: mysql-exporter-secrets
+                  key: EXPORTER_PASSWORD
+            - name: DATA_SOURCE_NAME
+              valueFrom:
+                secretKeyRef:
+                  name: mysql-exporter-secrets
+                  key: DATA_SOURCE_NAME
+          volumeMounts:
+            - name: mysql-exporter-secrets
+              mountPath: /etc/mysql/mysql_user.cnf
+              subPath: mysql_user.cnf
+              readOnly: true
+            - name: mysql-exporter-bin
+              mountPath: /tmp/mysqld-exporter.sh
+              subPath: mysqld-exporter.sh
+              readOnly: true
+      volumes:
+        - name: mysql-exporter-secrets
+          secret:
+            secretName: mysql-exporter-secrets
+            defaultMode: 0444
+        - name: mysql-exporter-bin
+          configMap:
+            name: mysql-exporter-bin
+            defaultMode: 0555
+{{- end }}
diff --git a/mariadb/templates/monitoring/prometheus/exporter-job-create-user.yaml b/mariadb/templates/monitoring/prometheus/exporter-job-create-user.yaml
new file mode 100644
index 000000000..df7a14701
--- /dev/null
+++ b/mariadb/templates/monitoring/prometheus/exporter-job-create-user.yaml
@@ -0,0 +1,83 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+{{- if and .Values.manifests.monitoring.prometheus.job_user_create .Values.monitoring.prometheus.enabled }}
+{{- $envAll := . }}
+
+{{- $serviceAccountName := "exporter-create-sql-user" }}
+{{ tuple $envAll "prometheus_create_mysql_user" $serviceAccountName | include "helm-toolkit.snippets.kubernetes_pod_rbac_serviceaccount" }}
+---
+apiVersion: batch/v1
+kind: Job
+metadata:
+  name: exporter-create-sql-user
+spec:
+  template:
+    metadata:
+      labels:
+{{ tuple $envAll "prometheus_mysql_exporter" "create-sql-user" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 8 }}
+    spec:
+      serviceAccountName: {{ $serviceAccountName }}
+      restartPolicy: OnFailure
+      nodeSelector:
+        {{ .Values.labels.prometheus_mysql_exporter.node_selector_key }}: {{ .Values.labels.prometheus_mysql_exporter.node_selector_value }}
+      initContainers:
+{{ tuple $envAll "prometheus_create_mysql_user" list | include "helm-toolkit.snippets.kubernetes_entrypoint_init_container" | indent 8 }}
+      containers:
+        - name: exporter-create-sql-user
+{{ tuple $envAll "prometheus_create_mysql_user" | include "helm-toolkit.snippets.image" | indent 10 }}
+{{ tuple $envAll $envAll.Values.pod.resources.jobs.prometheus_create_mysql_user | include "helm-toolkit.snippets.kubernetes_resources" | indent 10 }}
+          command:
+            - /tmp/create-mysql-user.sh
+          env:
+            - name: EXPORTER_USER
+              valueFrom:
+                secretKeyRef:
+                  name: mysql-exporter-secrets
+                  key: EXPORTER_USER
+            - name: EXPORTER_PASSWORD
+              valueFrom:
+                secretKeyRef:
+                  name: mysql-exporter-secrets
+                  key: EXPORTER_PASSWORD
+            - name: MYSQL_SERVICE
+              value: {{ tuple "oslo_db" "direct" . | include "helm-toolkit.endpoints.hostname_short_endpoint_lookup" }}
+            - name: MYSQL_ROOT_USER
+              value: {{ .Values.endpoints.oslo_db.auth.admin.username }}
+            - name: MYSQL_ROOT_PASSWORD
+              valueFrom:
+                secretKeyRef:
+                  name: mariadb-db-root-password
+                  key: MYSQL_ROOT_PASSWORD
+          volumeMounts:
+            - name: mysql-exporter-bin
+              mountPath: /tmp/create-mysql-user.sh
+              subPath: create-mysql-user.sh
+              readOnly: true
+            - name: mariadb-secrets
+              mountPath: /etc/mysql/admin_user.cnf
+              subPath: admin_user.cnf
+              readOnly: true
+      volumes:
+        - name: mysql-exporter-bin
+          configMap:
+            name: mysql-exporter-bin
+            defaultMode: 0555
+        - name: mariadb-secrets
+          secret:
+            secretName: mariadb-secrets
+            defaultMode: 0444
+{{- end }}
diff --git a/mariadb/templates/monitoring/prometheus/exporter-secrets-etc.yaml b/mariadb/templates/monitoring/prometheus/exporter-secrets-etc.yaml
new file mode 100644
index 000000000..2d19c2756
--- /dev/null
+++ b/mariadb/templates/monitoring/prometheus/exporter-secrets-etc.yaml
@@ -0,0 +1,35 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+{{- if and .Values.manifests.monitoring.prometheus.secret_etc .Values.monitoring.prometheus.enabled }}
+{{- $envAll := . }}
+
+{{- $exporter_user := .Values.endpoints.oslo_db.auth.exporter.username }}
+{{- $exporter_password := .Values.endpoints.oslo_db.auth.exporter.password }}
+{{- $db_host := tuple "oslo_db" "direct" "mysql" $envAll | include "helm-toolkit.endpoints.host_and_port_endpoint_uri_lookup" }}
+{{- $data_source_name := printf "%s:%s@(%s)/" $exporter_user $exporter_password $db_host }}
+---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: mysql-exporter-secrets
+type: Opaque
+data:
+  DATA_SOURCE_NAME: {{ $data_source_name | b64enc }}
+  EXPORTER_USER: {{ .Values.endpoints.oslo_db.auth.exporter.username | b64enc }}
+  EXPORTER_PASSWORD: {{ .Values.endpoints.oslo_db.auth.exporter.password | b64enc }}
+  mysql_user.cnf: {{ tuple "secrets/_exporter_user.cnf.tpl" . | include "helm-toolkit.utils.template" | b64enc }}
+{{- end }}
diff --git a/mariadb/templates/monitoring/prometheus/exporter-service.yaml b/mariadb/templates/monitoring/prometheus/exporter-service.yaml
new file mode 100644
index 000000000..c040f2642
--- /dev/null
+++ b/mariadb/templates/monitoring/prometheus/exporter-service.yaml
@@ -0,0 +1,37 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+{{- if and .Values.manifests.monitoring.prometheus.service_exporter .Values.monitoring.prometheus.enabled }}
+{{- $envAll := . }}
+{{- $prometheus_annotations := $envAll.Values.monitoring.prometheus.mysqld_exporter }}
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ tuple "prometheus_mysql_exporter" "internal" . | include "helm-toolkit.endpoints.hostname_short_endpoint_lookup" }}
+  labels:
+{{ tuple $envAll "prometheus_mysql_exporter" "metrics" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 4 }}
+  annotations:
+{{- if .Values.monitoring.prometheus.enabled }}
+{{ tuple $prometheus_annotations | include "helm-toolkit.snippets.prometheus_service_annotations" | indent 4 }}
+{{- end }}
+spec:
+  ports:
+  - name: metrics
+    port: {{ .Values.network.prometheus_mysql_exporter.port }}
+  selector:
+{{ tuple $envAll "prometheus_mysql_exporter" "exporter" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 4 }}
+{{- end }}
diff --git a/mariadb/templates/monitoring/prometheus/secrets/_exporter_user.cnf.tpl b/mariadb/templates/monitoring/prometheus/secrets/_exporter_user.cnf.tpl
new file mode 100644
index 000000000..f3d03afa9
--- /dev/null
+++ b/mariadb/templates/monitoring/prometheus/secrets/_exporter_user.cnf.tpl
@@ -0,0 +1,21 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+[client]
+user = {{ .Values.endpoints.prometheus_mysql_exporter.auth.user.username }}
+password = {{ .Values.endpoints.prometheus_mysql_exporter.auth.user.password }}
+host = {{ tuple "oslo_db" "direct" . | include "helm-toolkit.endpoints.hostname_short_endpoint_lookup" }}
+port = {{ tuple "oslo_db" "direct" "mysql" . | include "helm-toolkit.endpoints.endpoint_port_lookup" }}
diff --git a/mariadb/templates/pdb-mariadb.yaml b/mariadb/templates/pdb-mariadb.yaml
new file mode 100644
index 000000000..19f85dc12
--- /dev/null
+++ b/mariadb/templates/pdb-mariadb.yaml
@@ -0,0 +1,29 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+{{- if .Values.manifests.pdb_server }}
+{{- $envAll := . }}
+---
+apiVersion: policy/v1beta1
+kind: PodDisruptionBudget
+metadata:
+  name: mariadb-server
+spec:
+  minAvailable: {{ .Values.pod.lifecycle.disruption_budget.mariadb.min_available }}
+  selector:
+    matchLabels:
+{{ tuple $envAll "mariadb" "server" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 6 }}
+{{- end }}
diff --git a/mariadb/templates/secret-db-root-password.yaml b/mariadb/templates/secret-db-root-password.yaml
new file mode 100644
index 000000000..e99f30b4e
--- /dev/null
+++ b/mariadb/templates/secret-db-root-password.yaml
@@ -0,0 +1,27 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+{{- if .Values.manifests.secret_db }}
+{{- $envAll := . }}
+---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: mariadb-db-root-password
+type: Opaque
+data:
+  MYSQL_ROOT_PASSWORD: {{ .Values.endpoints.oslo_db.auth.admin.password | b64enc }}
+{{- end }}
diff --git a/mariadb/templates/secrets-etc.yaml b/mariadb/templates/secrets-etc.yaml
new file mode 100644
index 000000000..1e6865986
--- /dev/null
+++ b/mariadb/templates/secrets-etc.yaml
@@ -0,0 +1,27 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+{{- if .Values.manifests.secret_etc }}
+{{- $envAll := . }}
+---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: mariadb-secrets
+type: Opaque
+data:
+  admin_user.cnf: {{ tuple "secrets/_admin_user.cnf.tpl" . | include "helm-toolkit.utils.template"  | b64enc }}
+{{- end }}
diff --git a/mariadb/templates/secrets/_admin_user.cnf.tpl b/mariadb/templates/secrets/_admin_user.cnf.tpl
new file mode 100644
index 000000000..c30120286
--- /dev/null
+++ b/mariadb/templates/secrets/_admin_user.cnf.tpl
@@ -0,0 +1,21 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+[client]
+user = {{ .Values.endpoints.oslo_db.auth.admin.username }}
+password = {{ .Values.endpoints.oslo_db.auth.admin.password }}
+host = {{ tuple "oslo_db" "direct" . | include "helm-toolkit.endpoints.hostname_short_endpoint_lookup" }}
+port = {{ tuple "oslo_db" "direct" "mysql" . | include "helm-toolkit.endpoints.endpoint_port_lookup" }}
diff --git a/mariadb/templates/service-discovery.yaml b/mariadb/templates/service-discovery.yaml
new file mode 100644
index 000000000..a705b9066
--- /dev/null
+++ b/mariadb/templates/service-discovery.yaml
@@ -0,0 +1,35 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+{{- if .Values.manifests.service_discovery }}
+{{- $envAll := . }}
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ tuple "oslo_db" "discovery" . | include "helm-toolkit.endpoints.hostname_short_endpoint_lookup" }}
+  annotations:
+    service.alpha.kubernetes.io/tolerate-unready-endpoints: "true"
+spec:
+  ports:
+    - name: mysql
+      port: {{ tuple "oslo_db" "direct" "mysql" . | include "helm-toolkit.endpoints.endpoint_port_lookup" }}
+    - name: wsrep
+      port: {{ tuple "oslo_db" "direct" "wsrep" . | include "helm-toolkit.endpoints.endpoint_port_lookup" }}
+  clusterIP: None
+  selector:
+{{ tuple $envAll "mariadb" "server" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 4 }}
+{{- end }}
diff --git a/mariadb/templates/service-error.yaml b/mariadb/templates/service-error.yaml
new file mode 100644
index 000000000..f8891448a
--- /dev/null
+++ b/mariadb/templates/service-error.yaml
@@ -0,0 +1,34 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+{{- if .Values.manifests.service_error }}
+{{- $envAll := . }}
+---
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+{{ tuple $envAll "mariadb" "ingress-error-pages" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 4 }}
+  name: {{ tuple "oslo_db" "error_pages" . | include "helm-toolkit.endpoints.hostname_short_endpoint_lookup" }}
+spec:
+  clusterIP: None
+  ports:
+    - port: 80
+      protocol: TCP
+      targetPort: 8080
+  selector:
+{{ tuple $envAll "mariadb" "ingress-error-pages" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 4 }}
+{{- end }}
diff --git a/mariadb/templates/service-ingress.yaml b/mariadb/templates/service-ingress.yaml
new file mode 100644
index 000000000..08d003e41
--- /dev/null
+++ b/mariadb/templates/service-ingress.yaml
@@ -0,0 +1,33 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+{{- if .Values.manifests.service_ingress }}
+{{- $envAll := . }}
+---
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+{{ tuple $envAll "mariadb" "ingress" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 4 }}
+  name: {{ tuple "oslo_db" "internal" . | include "helm-toolkit.endpoints.hostname_short_endpoint_lookup" }}
+spec:
+  ports:
+    - name: mysql
+      port: {{ tuple "oslo_db" "internal" "mysql" . | include "helm-toolkit.endpoints.endpoint_port_lookup" }}
+      protocol: TCP
+  selector:
+{{ tuple $envAll "mariadb" "ingress" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 4 }}
+{{- end }}
diff --git a/mariadb/templates/service.yaml b/mariadb/templates/service.yaml
new file mode 100644
index 000000000..2600fe4c4
--- /dev/null
+++ b/mariadb/templates/service.yaml
@@ -0,0 +1,30 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+{{- if .Values.manifests.service }}
+{{- $envAll := . }}
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ tuple "oslo_db" "direct" . | include "helm-toolkit.endpoints.hostname_short_endpoint_lookup" }}
+spec:
+  ports:
+    - name: mysql
+      port: {{ tuple "oslo_db" "direct" "mysql" . | include "helm-toolkit.endpoints.endpoint_port_lookup" }}
+  selector:
+{{ tuple $envAll "mariadb" "server" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 4 }}
+{{- end }}
diff --git a/mariadb/templates/statefulset.yaml b/mariadb/templates/statefulset.yaml
new file mode 100644
index 000000000..7165493eb
--- /dev/null
+++ b/mariadb/templates/statefulset.yaml
@@ -0,0 +1,182 @@
+{{/*
+Copyright 2017 The Openstack-Helm Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/}}
+
+{{- if .Values.manifests.statefulset }}
+{{- $envAll := . }}
+
+{{- $serviceAccountName := "mariadb" }}
+{{ tuple $envAll "mariadb" $serviceAccountName | include "helm-toolkit.snippets.kubernetes_pod_rbac_serviceaccount" }}
+---
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+  # NOTE(portdirect): the statefulset name must match the POD_NAME_PREFIX env var for discovery to work
+  name: {{ tuple "oslo_db" "direct" . | include "helm-toolkit.endpoints.hostname_short_endpoint_lookup" }}
+  labels:
+{{ tuple $envAll "mariadb" "server" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 4 }}
+spec:
+  serviceName: "{{ tuple "oslo_db" "discovery" . | include "helm-toolkit.endpoints.hostname_short_endpoint_lookup" }}"
+  podManagementPolicy: "Parallel"
+  replicas: {{ .Values.pod.replicas.server }}
+  selector:
+    matchLabels:
+{{ tuple $envAll "mariadb" "server" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 6 }}
+  template:
+    metadata:
+      labels:
+{{ tuple $envAll "mariadb" "server" | include "helm-toolkit.snippets.kubernetes_metadata_labels" | indent 8 }}
+    spec:
+      serviceAccountName: {{ $serviceAccountName }}
+      affinity:
+{{ tuple $envAll "mariadb" "server" | include "helm-toolkit.snippets.kubernetes_pod_anti_affinity" | indent 8 }}
+      nodeSelector:
+        {{ .Values.labels.server.node_selector_key }}: {{ .Values.labels.server.node_selector_value }}
+      initContainers:
+{{ tuple $envAll "mariadb" list | include "helm-toolkit.snippets.kubernetes_entrypoint_init_container" | indent 8 }}
+{{- if .Values.volume.chown_on_start }}
+        - name: mariadb-perms
+{{ tuple $envAll "mariadb" | include "helm-toolkit.snippets.image" | indent 10 }}
+          securityContext:
+            runAsUser: 0
+{{ tuple $envAll $envAll.Values.pod.resources.server | include "helm-toolkit.snippets.kubernetes_resources" | indent 10 }}
+          command:
+            - chown
+            - -R
+            - "mysql:mysql"
+            - /var/lib/mysql
+          volumeMounts:
+            - name: mysql-data
+              mountPath: /var/lib/mysql
+{{- end }}
+      containers:
+        - name: mariadb
+{{ tuple $envAll "mariadb" | include "helm-toolkit.snippets.image" | indent 10 }}
+{{ tuple $envAll $envAll.Values.pod.resources.server | include "helm-toolkit.snippets.kubernetes_resources" | indent 10 }}
+          env:
+            - name: POD_IP
+              valueFrom:
+                fieldRef:
+                  fieldPath: status.podIP
+            - name: POD_NAME
+              valueFrom:
+                fieldRef:
+                  fieldPath: metadata.name
+            - name: FORCE_BOOTSTRAP
+              value: {{ .Values.force_bootstrap | quote }}
+            - name: FORCE_RECOVERY
+              value: {{ .Values.force_recovey | quote }}
+            - name: BOOTSTRAP_FILE
+              value: {{ printf "/tmp/%s.sql" (randAlphaNum 8) }}
+            - name: MARIADB_REPLICAS
+              value: {{ .Values.pod.replicas.server | quote }}
+            - name: WSREP_PORT
+              value: {{ tuple "oslo_db" "direct" "wsrep" . | include "helm-toolkit.endpoints.endpoint_port_lookup" | quote }}
+            - name: POD_NAME_PREFIX
+              value: {{ tuple "oslo_db" "direct" . | include "helm-toolkit.endpoints.hostname_short_endpoint_lookup" }}
+            - name: MYSQL_ROOT_PASSWORD
+              valueFrom:
+                secretKeyRef:
+                  name: mariadb-db-root-password
+                  key: MYSQL_ROOT_PASSWORD
+          ports:
+            - name: mysql
+              protocol: TCP
+              containerPort: {{ tuple "oslo_db" "direct" "mysql" . | include "helm-toolkit.endpoints.endpoint_port_lookup" }}
+            - name: wsrep
+              protocol: TCP
+              containerPort: {{ tuple "oslo_db" "direct" "wsrep" . | include "helm-toolkit.endpoints.endpoint_port_lookup" }}
+          command:
+            - /tmp/start.sh
+          lifecycle:
+            preStop:
+              exec:
+                command:
+                  - /tmp/stop.sh
+          readinessProbe:
+            initialDelaySeconds: 30
+            periodSeconds: 30
+            timeoutSeconds: 3
+            exec:
+              command:
+                - /tmp/readiness.sh
+          volumeMounts:
+            - name: mycnfd
+              mountPath: /etc/mysql/conf.d
+            - name: mariadb-bin
+              mountPath: /tmp/start.sh
+              subPath: start.sh
+              readOnly: true
+            - name: mariadb-bin
+              mountPath: /tmp/stop.sh
+              subPath: stop.sh
+              readOnly: true
+            - name: mariadb-bin
+              mountPath: /tmp/readiness.sh
+              subPath: readiness.sh
+              readOnly: true
+            - name: mariadb-etc
+              mountPath: /etc/mysql/my.cnf
+              subPath: my.cnf
+              readOnly: true
+            - name: mariadb-etc
+              mountPath: /etc/mysql/conf.d/00-base.cnf
+              subPath: 00-base.cnf
+              readOnly: true
+            - name: mariadb-etc
+              mountPath: /etc/mysql/conf.d/20-override.cnf
+              subPath: 20-override.cnf
+              readOnly: true
+            - name: mariadb-etc
+              mountPath: /etc/mysql/conf.d/99-force.cnf
+              subPath: 99-force.cnf
+              readOnly: true
+            - name: mariadb-secrets
+              mountPath: /etc/mysql/admin_user.cnf
+              subPath: admin_user.cnf
+              readOnly: true
+            - name: mysql-data
+              mountPath: /var/lib/mysql
+      volumes:
+        - name: mycnfd
+          emptyDir: {}
+        - name: mariadb-bin
+          configMap:
+            name: mariadb-bin
+            defaultMode: 0555
+        - name: mariadb-etc
+          configMap:
+            name: mariadb-etc
+            defaultMode: 0444
+        - name: mariadb-secrets
+          secret:
+            secretName: mariadb-secrets
+            defaultMode: 0444
+        {{- if not .Values.volume.enabled }}
+        - name: mysql-data
+          emptyDir: {}
+        {{- end }}
+{{- if .Values.volume.enabled }}
+  volumeClaimTemplates:
+  - metadata:
+      name: mysql-data
+    spec:
+      accessModes: [ "ReadWriteOnce" ]
+      resources:
+        requests:
+          storage: {{ .Values.volume.size }}
+      storageClassName: {{ .Values.volume.class_name }}
+{{- end }}
+{{- end }}
diff --git a/mariadb/values.yaml b/mariadb/values.yaml
new file mode 100644
index 000000000..dffca8abf
--- /dev/null
+++ b/mariadb/values.yaml
@@ -0,0 +1,289 @@
+# Copyright 2017 The Openstack-Helm Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Default values for mariadb.
+# This is a YAML-formatted file.
+# Declare name/value pairs to be passed into your templates.
+# name: value
+
+release_group: null
+
+images:
+  tags:
+    # NOTE: if you update from 10.2.13 please look at
+    # https://review.openstack.org/#/q/Ifd09d7effe7d382074ca9e6678df36bdd4bce0af
+    # and check whether it's still needed
+    mariadb: docker.io/mariadb:10.2.13
+    ingress: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.9.0
+    error_pages: gcr.io/google_containers/defaultbackend:1.0
+    prometheus_create_mysql_user: docker.io/mariadb:10.2.13
+    prometheus_mysql_exporter: docker.io/prom/mysqld-exporter:v0.10.0
+    prometheus_mysql_exporter_helm_tests: docker.io/openstackhelm/heat:newton
+    dep_check: quay.io/stackanetes/kubernetes-entrypoint:v0.3.1
+    image_repo_sync: docker.io/docker:17.07.0
+  pull_policy: "IfNotPresent"
+  local_registry:
+    active: false
+    exclude:
+      - dep_check
+      - image_repo_sync
+
+labels:
+  server:
+    node_selector_key: openstack-control-plane
+    node_selector_value: enabled
+  ingress:
+    node_selector_key: openstack-control-plane
+    node_selector_value: enabled
+  prometheus_mysql_exporter:
+    node_selector_key: openstack-control-plane
+    node_selector_value: enabled
+  error_server:
+    node_selector_key: openstack-control-plane
+    node_selector_value: enabled
+
+pod:
+  affinity:
+    anti:
+      type:
+        default: preferredDuringSchedulingIgnoredDuringExecution
+      topologyKey:
+        default: kubernetes.io/hostname
+  replicas:
+    server: 1
+    ingress: 1
+    error_page: 1
+    prometheus_mysql_exporter: 1
+  lifecycle:
+    upgrades:
+      deployments:
+        revision_history: 3
+        pod_replacement_strategy: RollingUpdate
+        rolling_update:
+          max_unavailable: 1
+          max_surge: 3
+    termination_grace_period:
+      prometheus_mysql_exporter:
+        timeout: 30
+      error_pages:
+        timeout: 10
+    disruption_budget:
+      mariadb:
+        min_available: 0
+  resources:
+    enabled: false
+    prometheus_mysql_exporter:
+      limits:
+        memory: "1024Mi"
+        cpu: "2000m"
+      requests:
+        memory: "128Mi"
+        cpu: "500m"
+    server:
+      requests:
+        memory: "128Mi"
+        cpu: "100m"
+      limits:
+        memory: "1024Mi"
+        cpu: "2000m"
+    jobs:
+      tests:
+        limits:
+          memory: "1024Mi"
+          cpu: "2000m"
+        requests:
+          memory: "128Mi"
+          cpu: "100m"
+      prometheus_create_mysql_user:
+        limits:
+          memory: "1024Mi"
+          cpu: "2000m"
+        requests:
+          memory: "128Mi"
+          cpu: "100m"
+      image_repo_sync:
+        requests:
+          memory: "128Mi"
+          cpu: "100m"
+        limits:
+          memory: "1024Mi"
+          cpu: "2000m"
+
+dependencies:
+  dynamic:
+    common:
+      local_image_registry:
+        jobs:
+          - mariadb-image-repo-sync
+        services:
+          - endpoint: node
+            service: local_image_registry
+  static:
+    error_pages:
+      jobs: null
+    ingress:
+      jobs: null
+      services:
+        - endpoint: error_pages
+          service: oslo_db
+    mariadb:
+      jobs: null
+      services: null
+    prometheus_create_mysql_user:
+      services:
+        - endpoint: internal
+          service: oslo_db
+    prometheus_mysql_exporter:
+      jobs:
+        - exporter-create-sql-user
+      services:
+        - endpoint: internal
+          service: oslo_db
+    prometheus_mysql_exporter_tests:
+      services:
+        - endpoint: internal
+          service: prometheus_mysql_exporter
+        - endpoint: internal
+          service: monitoring
+    image_repo_sync:
+      services:
+        - endpoint: internal
+          service: local_image_registry
+
+force_bootstrap: false
+
+volume:
+  chown_on_start: true
+  enabled: true
+  class_name: general
+  size: 5Gi
+
+
+
+conf:
+  ingress: null
+
+database:
+  config_override: null
+  # Any configuration here will override the base config.
+  # config_override: |-
+  #   [mysqld]
+  #   wsrep_slave_threads=1
+
+monitoring:
+  prometheus:
+    enabled: false
+    mysqld_exporter:
+      scrape: true
+
+network:
+  prometheus_mysql_exporter:
+    port: 9104
+
+# typically overridden by environmental
+# values, but should include all endpoints
+# required by this chart
+endpoints:
+  cluster_domain_suffix: cluster.local
+  local_image_registry:
+    name: docker-registry
+    namespace: docker-registry
+    hosts:
+      default: localhost
+      internal: docker-registry
+      node: localhost
+    host_fqdn_override:
+      default: null
+    port:
+      registry:
+        node: 5000
+  monitoring:
+    name: prometheus
+    namespace: null
+    hosts:
+      default: prom-metrics
+      public: prometheus
+    host_fqdn_override:
+      default: null
+    path:
+      default: null
+    scheme:
+      default: 'http'
+    port:
+      api:
+        default: 9090
+        public: 80
+  prometheus_mysql_exporter:
+    namespace: null
+    auth:
+      user:
+        username: exporter
+        password: password
+    hosts:
+      default: mysql-exporter
+    host_fqdn_override:
+      default: null
+    path:
+      default: /metrics
+    scheme:
+      default: 'http'
+    port:
+      metrics:
+        default: 9104
+  oslo_db:
+    namespace: null
+    auth:
+      admin:
+        username: root
+        password: password
+      exporter:
+        username: exporter
+        password: password
+    hosts:
+      default: mariadb
+      direct: mariadb-server
+      discovery: mariadb-discovery
+      error_pages: mariadb-ingress-error-pages
+    host_fqdn_override:
+      default: null
+    path: null
+    scheme: mysql+pymysql
+    port:
+      mysql:
+        default: 3306
+      wsrep:
+        default: 4567
+
+manifests:
+  configmap_bin: true
+  configmap_etc: true
+  configmap_services_tcp: true
+  deployment_error: true
+  deployment_ingress: true
+  job_image_repo_sync: true
+  monitoring:
+    prometheus:
+      configmap_bin: true
+      deployment_exporter: true
+      job_user_create: true
+      secret_etc: true
+      service_exporter: true
+  pdb_server: true
+  secret_db: true
+  secret_etc: true
+  service_discovery: true
+  service_ingress: true
+  service_error: true
+  service: true
+  statefulset: true
diff --git a/playbooks/osh-infra-dev-deploy-ceph.yaml b/playbooks/osh-infra-dev-deploy-ceph.yaml
index 5f74dc3a5..b5a8ade5c 100644
--- a/playbooks/osh-infra-dev-deploy-ceph.yaml
+++ b/playbooks/osh-infra-dev-deploy-ceph.yaml
@@ -60,6 +60,12 @@
         ./tools/deployment/developer/ceph/040-ldap.sh
       args:
         chdir: "{{ zuul.project.src_dir }}"
+    - name: Deploy MariaDB
+      shell: |
+        set -xe;
+        ./tools/deployment/developer/ceph/045-mariadb.sh
+      args:
+        chdir: "{{ zuul.project.src_dir }}"
     - name: Deploy Prometheus
       shell: |
         set -xe;
diff --git a/playbooks/osh-infra-dev-deploy-nfs.yaml b/playbooks/osh-infra-dev-deploy-nfs.yaml
index 38542a1a0..019f45940 100644
--- a/playbooks/osh-infra-dev-deploy-nfs.yaml
+++ b/playbooks/osh-infra-dev-deploy-nfs.yaml
@@ -54,6 +54,12 @@
         ./tools/deployment/developer/nfs/040-ldap.sh
       args:
         chdir: "{{ zuul.project.src_dir }}"
+    - name: Deploy MariaDB
+      shell: |
+        set -xe;
+        ./tools/deployment/developer/nfs/045-mariadb.sh
+      args:
+        chdir: "{{ zuul.project.src_dir }}"
     - name: Deploy Prometheus
       shell: |
         set -xe;
diff --git a/playbooks/osh-infra-multinode-deploy.yaml b/playbooks/osh-infra-multinode-deploy.yaml
index ad2c820ac..d897667cc 100644
--- a/playbooks/osh-infra-multinode-deploy.yaml
+++ b/playbooks/osh-infra-multinode-deploy.yaml
@@ -44,6 +44,12 @@
         ./tools/deployment/multinode/040-ldap.sh
       args:
         chdir: "{{ zuul.project.src_dir }}"
+    - name: Deploy MariaDB
+      shell: |
+        set -xe;
+        ./tools/deployment/multinode/045-mariadb.sh
+      args:
+        chdir: "{{ zuul.project.src_dir }}"
     - name: Deploy Prometheus
       shell: |
         set -xe;
diff --git a/playbooks/osh-infra-openstack-support.yaml b/playbooks/osh-infra-openstack-support.yaml
index 2b77f4c00..400c4117e 100644
--- a/playbooks/osh-infra-openstack-support.yaml
+++ b/playbooks/osh-infra-openstack-support.yaml
@@ -60,3 +60,9 @@
         ./tools/deployment/openstack-support/030-memcached.sh
       args:
         chdir: "{{ zuul.project.src_dir }}"
+    - name: Deploy Mariadb
+      shell: |
+        set -xe;
+        ./tools/deployment/openstack-support/035-mariadb.sh
+      args:
+        chdir: "{{ zuul.project.src_dir }}"
diff --git a/tools/deployment/developer/ceph/045-mariadb.sh b/tools/deployment/developer/ceph/045-mariadb.sh
new file mode 120000
index 000000000..80f213b41
--- /dev/null
+++ b/tools/deployment/developer/ceph/045-mariadb.sh
@@ -0,0 +1 @@
+../common/045-mariadb.sh
\ No newline at end of file
diff --git a/tools/deployment/developer/common/045-mariadb.sh b/tools/deployment/developer/common/045-mariadb.sh
new file mode 100755
index 000000000..be0fad2b4
--- /dev/null
+++ b/tools/deployment/developer/common/045-mariadb.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+# Copyright 2017 The Openstack-Helm Authors.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+set -xe
+
+#NOTE: Lint and package chart
+make mariadb
+
+#NOTE: Deploy command
+: ${OSH_INFRA_EXTRA_HELM_ARGS:=""}
+helm upgrade --install mariadb ./mariadb \
+    --namespace=osh-infra \
+    --set pod.replicas.server=1 \
+    ${OSH_INFRA_EXTRA_HELM_ARGS} \
+    ${OSH_INFRA_EXTRA_HELM_ARGS_MARIADB}
+
+#NOTE: Wait for deploy
+./tools/deployment/common/wait-for-pods.sh osh-infra
+
+#NOTE: Validate Deployment info
+helm status mariadb
diff --git a/tools/deployment/developer/common/100-grafana.sh b/tools/deployment/developer/common/100-grafana.sh
index d63bf375f..b925a56d7 100755
--- a/tools/deployment/developer/common/100-grafana.sh
+++ b/tools/deployment/developer/common/100-grafana.sh
@@ -20,29 +20,8 @@ set -xe
 make grafana
 
 #NOTE: Deploy command
-tee /tmp/grafana.yaml << EOF
-dependencies:
-  static:
-    grafana:
-      jobs: null
-      services: null
-manifests:
-  job_db_init: false
-  job_db_init_session: false
-  job_db_session_sync: false
-  secret_db: false
-  secret_db_session: false
-conf:
-  grafana:
-    database:
-      type: sqlite3
-    session:
-      provider: file
-      provider_config: sessions
-EOF
 helm upgrade --install grafana ./grafana \
-    --namespace=osh-infra \
-    --values=/tmp/grafana.yaml
+    --namespace=osh-infra
 
 #NOTE: Wait for deploy
 ./tools/deployment/common/wait-for-pods.sh osh-infra
diff --git a/tools/deployment/developer/nfs/045-mariadb.sh b/tools/deployment/developer/nfs/045-mariadb.sh
new file mode 120000
index 000000000..80f213b41
--- /dev/null
+++ b/tools/deployment/developer/nfs/045-mariadb.sh
@@ -0,0 +1 @@
+../common/045-mariadb.sh
\ No newline at end of file
diff --git a/tools/deployment/multinode/045-mariadb.sh b/tools/deployment/multinode/045-mariadb.sh
new file mode 100755
index 000000000..4464122f9
--- /dev/null
+++ b/tools/deployment/multinode/045-mariadb.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+# Copyright 2017 The Openstack-Helm Authors.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+set -xe
+
+#NOTE: Lint and package chart
+make mariadb
+
+#NOTE: Deploy command
+: ${OSH_INFRA_EXTRA_HELM_ARGS:=""}
+helm upgrade --install mariadb ./mariadb \
+    --namespace=osh-infra \
+    ${OSH_INFRA_EXTRA_HELM_ARGS} \
+    ${OSH_INFRA_EXTRA_HELM_ARGS_MARIADB}
+
+#NOTE: Wait for deploy
+./tools/deployment/common/wait-for-pods.sh osh-infra
+
+#NOTE: Validate Deployment info
+helm status mariadb
diff --git a/tools/deployment/multinode/100-grafana.sh b/tools/deployment/multinode/100-grafana.sh
index 4fdf4ee26..1aff7ab1a 100755
--- a/tools/deployment/multinode/100-grafana.sh
+++ b/tools/deployment/multinode/100-grafana.sh
@@ -20,32 +20,9 @@ set -xe
 make grafana
 
 #NOTE: Deploy command
-tee /tmp/grafana.yaml << EOF
-dependencies:
-  static:
-    grafana:
-      jobs: null
-      services: null
-manifests:
-  job_db_init: false
-  job_db_init_session: false
-  job_db_session_sync: false
-  secret_db: false
-  secret_db_session: false
-conf:
-  grafana:
-    database:
-      type: sqlite3
-    session:
-      provider: file
-      provider_config: sessions
-pod:
-  replicas:
-    grafana: 2
-EOF
 helm upgrade --install grafana ./grafana \
     --namespace=osh-infra \
-    --values=/tmp/grafana.yaml
+    --set pod.replicas.grafana=2
 
 #NOTE: Wait for deploy
 ./tools/deployment/common/wait-for-pods.sh osh-infra
diff --git a/tools/deployment/openstack-support/035-mariadb.sh b/tools/deployment/openstack-support/035-mariadb.sh
new file mode 100755
index 000000000..6213fe72c
--- /dev/null
+++ b/tools/deployment/openstack-support/035-mariadb.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+
+# Copyright 2017 The Openstack-Helm Authors.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+set -xe
+
+#NOTE: Lint and package chart
+make mariadb
+
+#NOTE: Deploy command
+: ${OSH_INFRA_EXTRA_HELM_ARGS:=""}
+helm upgrade --install mariadb ./mariadb \
+    --namespace=openstack \
+    --set pod.replicas.server=1 \
+    ${OSH_INFRA_EXTRA_HELM_ARGS} \
+    ${OSH_INFRA_EXTRA_HELM_ARGS_MARIADB}
+
+#NOTE: Wait for deploy
+./tools/deployment/common/wait-for-pods.sh openstack
+
+#NOTE: Validate Deployment info
+helm status mariadb