Update layer with systemd + nginx layer #2

Merged
merged 14 commits into from Oct 9, 2016
View
@@ -1,2 +1,3 @@
bdist
.*.swp
+.tox/
View
@@ -1,26 +1,3 @@
-
-ifndef JUJU_REPOSITORY
- $(error JUJU_REPOSITORY is undefined)
-endif
-
-CHARM=cs:~cmars/mattermost
-CHARMS=$(JUJU_REPOSITORY)/trusty/mattermost $(JUJU_REPOSITORY)/xenial/mattermost
-BDIST_VERSION=2.2.0
-
-all: $(CHARMS)
-
-$(JUJU_REPOSITORY)/%/mattermost: bdist/mattermost.tar.gz
- charm build -s $*
-
-bdist/mattermost.tar.gz:
- -mkdir -p $(shell dirname $@)
- wget -O $@ https://releases.mattermost.com/$(BDIST_VERSION)/mattermost-team-$(BDIST_VERSION)-linux-amd64.tar.gz
-
-push: $(JUJU_REPOSITORY)/trusty/mattermost bdist/mattermost.tar.gz
- charm push $(JUJU_REPOSITORY)/trusty/mattermost $(CHARM) --resource bdist=bdist/mattermost.tar.gz
-
-grant:
- charm grant $(CHARM) --acl read everyone
-
-clean:
- $(RM) -r $(CHARMS)
+.PHONY: test
+test:
+ @tox
View
@@ -3,7 +3,12 @@ options:
type: string
description: Name of service shown in login screens and UI.
default: "Mattermost"
+ fqdn:
+ type: string
+ description: Fqdn of mattermost server to use
+ default: "mattermost.example.com"
port:
type: int
- description: Listen port
- default: 8065
+ description: Nginx listen port
+ default: 80
+
View
@@ -1,9 +1,17 @@
includes:
- 'layer:basic'
+ - 'layer:nginx'
+ - 'layer:tls-client'
- 'interface:pgsql'
- 'interface:http'
repo: https://github.com/cmars/juju-charm-mattermost.git
options:
- basic:
- packages:
- - '--no-install-recommends'
+ tls-client:
+ ca_certificate_path: "/etc/ssl/certs/ca.crt"
+ server_certificate_path: "/etc/ssl/certs/server.crt"
+ server_key_path: "/etc/ssl/certs/server.key"
+ client_certificate_path: "/etc/ssl/certs/client.crt"
+ client_key_path: "/etc/ssl/certs/client.key"
+ basic:
+ packages:
+ - '--no-install-recommends'
View
@@ -29,5 +29,4 @@ resources:
filename: mattermost.tar.gz
description: Binary distribution of Mattermost, obtained from https://www.mattermost.org/download/
series:
- - trusty
- xenial
View
@@ -1,123 +1,210 @@
import json
import os
import shutil
-from subprocess import check_call
+import socket
+
+from charms.reactive import (
+ hook,
+ when,
+ when_not,
+ set_state,
+ remove_state
+)
+
+from charmhelpers.core.hookenv import (
+ status_set,
+ close_port,
+ open_port,
+ unit_public_ip,
+ unit_private_ip,
+ resource_get,
+ config,
+ local_unit
+)
+
+from charmhelpers.core.host import (
+ add_group,
+ adduser,
+ user_exists,
+ group_exists,
+ service_running,
+ service_start,
+ service_stop,
+ service_restart
+)
-from charms.reactive import hook, when, when_not, set_state, remove_state, is_state
-from charmhelpers.core import hookenv
-from charmhelpers.core.host import add_group, adduser, service_running, service_start, service_restart
from charmhelpers.core.templating import render
-from charmhelpers.fetch import archiveurl, apt_install, apt_update
from charmhelpers.payload.archive import extract_tarfile
from charmhelpers.core.unitdata import kv
+from charms.layer.nginx import configure_site
+from charms.layer import options
-@hook('install')
-def install():
- install_workload()
+
+opts = options('tls-client')
+SRV_KEY = opts.get('server_key_path')
+SRV_CRT = opts.get('server_certificate_path')
@hook('upgrade-charm')
def upgrade_charm():
- was_running = False
if service_running("mattermost"):
- was_running = True
service_stop("mattermost")
- install_workload()
- if was_running:
- service_start("mattermost")
+ remove_state('mattermost.installed')
-def install_workload():
- # TODO(cmars): contribute resource support to charms.* or charmhelpers.*
- # Until then, this proves out the feature.
-
- # `resource-get` provisions the resource from the charmstore, or the controller if
- # it was specified on deploy. Note that this means if you deploy this charm locally,
- # you'll _have_ to provide the resource.
- check_call(['resource-get', 'bdist'])
-
- # `resource-get` puts the resource in ../resources relative to the
- # charmstore in a subdirectory with the resource name. The file in there
- # will have the same name as pushed or specified on deploy.
- # TODO(cmars): What if the resource is pushed with a different filename but
- # same resource name? How would the charm know? Or is that something that
- # the charm author shouldn't normally do?
- resource_path = os.path.join(hookenv.charm_dir(), '..', 'resources', 'bdist', 'mattermost.tar.gz')
- if not os.path.exists(resource_path):
- hookenv.status_set('error', 'failed to download resource')
- return
+@when_not('mattermost.installed')
+def install_mattermost():
+ """Grab the mattermost binary, unpack, install
+ to /opt.
+ """
+
+ status_set('maintenance', "Installing Mattermost")
+
+ # Create mattermost user & group if not exists
+ if not group_exists('mattermost'):
+ add_group("mattermost")
+ if not user_exists('mattermost'):
+ adduser("mattermost", system_user=True)
- extract_tarfile(resource_path, destpath="/opt")
+ # Get and uppack resource
+ if os.path.exists('/srv/mattermost'):
+ shutil.rmtree('/srv/mattermost')
- # Create mattermost user & group
- add_group("mattermost")
- adduser("mattermost", system_user=True)
+ mattermost_bdist = resource_get('bdist')
+ extract_tarfile(mattermost_bdist, destpath="/srv")
+ # Create data + log + config dirs
for dir in ("data", "logs", "config"):
- os.makedirs(os.path.join("/opt/mattermost", dir), mode=0o700, exist_ok=True)
- shutil.chown(os.path.join("/opt/mattermost", dir), user="mattermost", group="mattermost")
+ os.makedirs(os.path.join("/srv/mattermost", dir), mode=0o700,
+ exist_ok=True)
+ shutil.chown(os.path.join("/srv/mattermost", dir), user="mattermost",
+ group="mattermost")
- render(source='upstart',
- target="/etc/init/mattermost.conf",
- perms=0o644,
- context={})
- hookenv.status_set('maintenance', 'installation complete')
+ # Render systemd template
+ render(source="mattermost.service.tmpl",
+ target="/etc/systemd/system/mattermost.service",
+ perms=0o644,
+ owner="root",
+ context={})
+ set_state('mattermost.installed')
+ status_set('active', 'Mattermost installation complete')
@hook('config-changed')
def config_changed():
- conf = hookenv.config()
+ conf = config()
if conf.changed('port') and conf.previous('port'):
- hookenv.close_port(conf.previous('port'))
+ close_port(conf.previous('port'))
if conf.get('port'):
- hookenv.open_port(conf['port'])
+ open_port(conf['port'])
setup()
-@when("db.database.available")
-def db_available(db):
+@when_not('mattermost.db.available')
+@when('db.master.available')
+def get_set_db_data(db):
unit_data = kv()
- unit_data.set('db', {
- 'host': db.host(),
- 'port': db.port(),
- 'user': db.user(),
- 'password': db.password(),
- 'database': db.database(),
- })
+ unit_data.set('db', db.master.uri)
+ set_state('mattermost.db.available')
+
+
+@when('mattermost.db.available', 'mattermost.installed')
+@when_not('mattermost.initialized')
+def configure_mattermost():
+ """Call setup
+ """
setup()
- remove_state("db.database.available")
+ set_state("mattermost.initialized")
def setup():
+ """Gather and write out mattermost configs
+ """
+
unit_data = kv()
db = unit_data.get('db')
if not db:
- hookenv.status_set('blocked', 'need relation to postgresql')
+ status_set('blocked', 'need relation to postgresql')
return
- conf = hookenv.config()
- with open("/opt/mattermost/config/config.json", "r") as f:
- config = json.load(f)
+ conf = config()
+ with open("/srv/mattermost/config/config.json", "r") as f:
+ config_file = json.load(f)
# Config options
- svcconf = config.setdefault("ServiceSettings", {})
- svcconf['ListenAddress'] = ':%d' % (conf['port'])
+ svcconf = config_file.setdefault("ServiceSettings", {})
+ svcconf['ListenAddress'] = ':8065'
- teamconf = config.setdefault("TeamSettings", {})
+ teamconf = config_file.setdefault("TeamSettings", {})
teamconf['SiteName'] = conf['site_name']
# Database
- sqlconf = config.setdefault("SqlSettings", {})
+ sqlconf = config_file.setdefault("SqlSettings", {})
sqlconf['DriverName'] = 'postgres'
- sqlconf['DataSource'] = 'postgres://%(user)s:%(password)s@%(host)s:%(port)s/%(database)s?sslmode=disable&connect_timeout=10' % db
+ sqlconf['DataSource'] = '%s?sslmode=disable&connect_timeout=10' % db
- with open("/opt/mattermost/config/config.json", "w") as f:
- json.dump(config, f)
- remove_state("db.database.available")
+ with open("/srv/mattermost/config/config.json", "w") as f:
+ json.dump(config_file, f)
restart_service()
- hookenv.status_set('active', 'ready')
+ status_set('active', 'Mattermost configured')
+
+
+@when('certificates.available')
+def send_data(tls):
+ # Use the public ip of this unit as the Common Name for the certificate.
+ common_name = unit_public_ip()
+ # Get a list of Subject Alt Names for the certificate.
+ sans = []
+ sans.append(unit_public_ip())
+ sans.append(unit_private_ip())
+ sans.append(socket.gethostname())
+ # Create a path safe name by removing path characters from the unit name.
+ certificate_name = local_unit().replace('/', '_')
+ # Send the information on the relation object.
+ tls.request_server_cert(common_name, sans, certificate_name)
+
+
+@when('certificates.server.cert.available')
+@when_not('mattermost.ssl.available')
+def save_crt_key(tls):
+ '''Read the server crt/key from the relation object and
+ write to /etc/ssl/certs'''
+
+ # Remove the crt/key if they pre-exist
+ if os.path.exists(SRV_CRT):
+ os.remove(SRV_CRT)
+ if os.path.exists(SRV_KEY):
+ os.remove(SRV_KEY)
+
+ # Get and write out crt/key
+ server_cert, server_key = tls.get_server_cert()
+
+ with open(SRV_CRT, 'w') as crt_file:
+ crt_file.write(server_cert)
+ with open(SRV_KEY, 'w') as key_file:
+ key_file.write(server_key)
+
+ status_set('active', 'TLS crt/key ready')
+ set_state('mattermost.ssl.available')
+
+
+@when('nginx.available', 'mattermost.ssl.available',
+ 'mattermost.initialized')
+@when_not('mattermost.web.configured')
+def configure_webserver():
+ """Configure nginx
+ """
+
+ status_set('maintenance', 'Configuring website')
+ configure_site('mattermost', 'mattermost.nginx.tmpl',
+ key_path=SRV_KEY,
+ crt_path=SRV_CRT, fqdn=config('fqdn'))
+ open_port(443)
+ restart_service()
+ status_set('active', 'Mattermost available: %s' % unit_public_ip())
+ set_state('mattermost.web.configured')
def restart_service():
@@ -129,5 +216,5 @@ def restart_service():
@when('website.available')
def setup_website(website):
- conf = hookenv.config()
+ conf = config()
website.configure(conf['port'])
View
@@ -0,0 +1,3 @@
+tox
+flake8
+pep8
Oops, something went wrong.