| #!/usr/bin/env python |
| |
| # Copyright 2015 The Kubernetes 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. |
| |
| import os |
| import socket |
| import subprocess |
| |
| from charms import layer |
| from charms.reactive import when, when_any, when_not |
| from charms.reactive import set_state, remove_state |
| from charms.reactive import hook |
| from charmhelpers.core import hookenv |
| from charmhelpers.core import host |
| from charmhelpers.contrib.charmsupport import nrpe |
| from charms.reactive.helpers import data_changed |
| |
| from charms.layer import nginx |
| from charms.layer import tls_client |
| |
| from subprocess import Popen |
| from subprocess import PIPE |
| from subprocess import STDOUT |
| from subprocess import CalledProcessError |
| |
| |
| apilb_nginx = """/var/log/nginx.*.log { |
| daily |
| missingok |
| rotate 14 |
| compress |
| delaycompress |
| notifempty |
| create 0640 www-data adm |
| sharedscripts |
| prerotate |
| if [ -d /etc/logrotate.d/httpd-prerotate ]; then \\ |
| run-parts /etc/logrotate.d/httpd-prerotate; \\ |
| fi \\ |
| endscript |
| postrotate |
| invoke-rc.d nginx rotate >/dev/null 2>&1 |
| endscript |
| }""" |
| |
| |
| def get_ingress_address(relation): |
| try: |
| network_info = hookenv.network_get(relation.relation_name) |
| except NotImplementedError: |
| network_info = [] |
| |
| if network_info and 'ingress-addresses' in network_info: |
| # just grab the first one for now, maybe be more robust here? |
| return network_info['ingress-addresses'][0] |
| else: |
| # if they don't have ingress-addresses they are running a juju that |
| # doesn't support spaces, so just return the private address |
| return hookenv.unit_get('private-address') |
| |
| |
| @when('certificates.available', 'website.available') |
| def request_server_certificates(tls, website): |
| '''Send the data that is required to create a server certificate for |
| this server.''' |
| # Use the public ip of this unit as the Common Name for the certificate. |
| common_name = hookenv.unit_public_ip() |
| # Create SANs that the tls layer will add to the server cert. |
| sans = [ |
| hookenv.unit_public_ip(), |
| get_ingress_address(website), |
| socket.gethostname(), |
| ] |
| # maybe they have extra names they want as SANs |
| extra_sans = hookenv.config('extra_sans') |
| if extra_sans and not extra_sans == "": |
| sans.extend(extra_sans.split()) |
| # Create a path safe name by removing path characters from the unit name. |
| certificate_name = hookenv.local_unit().replace('/', '_') |
| # Request a server cert with this information. |
| tls.request_server_cert(common_name, sans, certificate_name) |
| |
| |
| @when('config.changed.extra_sans', 'certificates.available', |
| 'website.available') |
| def update_certificate(tls, website): |
| # Using the config.changed.extra_sans flag to catch changes. |
| # IP changes will take ~5 minutes or so to propagate, but |
| # it will update. |
| request_server_certificates(tls, website) |
| |
| |
| @when('certificates.server.cert.available', |
| 'nginx.available', 'tls_client.server.certificate.written') |
| def kick_nginx(tls): |
| # we are just going to sighup it, but still want to avoid kicking it |
| # without need |
| if data_changed('cert', tls.get_server_cert()): |
| # certificate changed, so sighup nginx |
| hookenv.log("Certificate information changed, sending SIGHUP to nginx") |
| host.service_restart('nginx') |
| tls_client.reset_certificate_write_flag('server') |
| |
| |
| @when('config.changed.port') |
| def close_old_port(): |
| config = hookenv.config() |
| old_port = config.previous('port') |
| if not old_port: |
| return |
| try: |
| hookenv.close_port(old_port) |
| except CalledProcessError: |
| hookenv.log('Port %d already closed, skipping.' % old_port) |
| |
| |
| def maybe_write_apilb_logrotate_config(): |
| filename = '/etc/logrotate.d/apilb_nginx' |
| if not os.path.exists(filename): |
| # Set log rotation for apilb log file |
| with open(filename, 'w+') as fp: |
| fp.write(apilb_nginx) |
| |
| |
| @when('nginx.available', 'apiserver.available', |
| 'certificates.server.cert.available') |
| def install_load_balancer(apiserver, tls): |
| ''' Create the default vhost template for load balancing ''' |
| # Get the tls paths from the layer data. |
| layer_options = layer.options('tls-client') |
| server_cert_path = layer_options.get('server_certificate_path') |
| cert_exists = server_cert_path and os.path.isfile(server_cert_path) |
| server_key_path = layer_options.get('server_key_path') |
| key_exists = server_key_path and os.path.isfile(server_key_path) |
| # Do both the key and certificate exist? |
| if cert_exists and key_exists: |
| # At this point the cert and key exist, and they are owned by root. |
| chown = ['chown', 'www-data:www-data', server_cert_path] |
| |
| # Change the owner to www-data so the nginx process can read the cert. |
| subprocess.call(chown) |
| chown = ['chown', 'www-data:www-data', server_key_path] |
| |
| # Change the owner to www-data so the nginx process can read the key. |
| subprocess.call(chown) |
| |
| port = hookenv.config('port') |
| hookenv.open_port(port) |
| services = apiserver.services() |
| nginx.configure_site( |
| 'apilb', |
| 'apilb.conf', |
| server_name='_', |
| services=services, |
| port=port, |
| server_certificate=server_cert_path, |
| server_key=server_key_path, |
| proxy_read_timeout=hookenv.config('proxy_read_timeout') |
| ) |
| |
| maybe_write_apilb_logrotate_config() |
| hookenv.status_set('active', 'Loadbalancer ready.') |
| |
| |
| @hook('upgrade-charm') |
| def upgrade_charm(): |
| maybe_write_apilb_logrotate_config() |
| |
| |
| @when('nginx.available') |
| def set_nginx_version(): |
| ''' Surface the currently deployed version of nginx to Juju ''' |
| cmd = 'nginx -v' |
| p = Popen(cmd, shell=True, |
| stdin=PIPE, |
| stdout=PIPE, |
| stderr=STDOUT, |
| close_fds=True) |
| raw = p.stdout.read() |
| # The version comes back as: |
| # nginx version: nginx/1.10.0 (Ubuntu) |
| version = raw.split(b'/')[-1].split(b' ')[0] |
| hookenv.application_version_set(version.rstrip()) |
| |
| |
| @when('website.available') |
| def provide_application_details(website): |
| ''' re-use the nginx layer website relation to relay the hostname/port |
| to any consuming kubernetes-workers, or other units that require the |
| kubernetes API ''' |
| website.configure(port=hookenv.config('port')) |
| |
| |
| @when('loadbalancer.available') |
| def provide_loadbalancing(loadbalancer): |
| '''Send the public address and port to the public-address interface, so |
| the subordinates can get the public address of this loadbalancer.''' |
| loadbalancer.set_address_port(hookenv.unit_get('public-address'), |
| hookenv.config('port')) |
| |
| |
| @when('nrpe-external-master.available') |
| @when_not('nrpe-external-master.initial-config') |
| def initial_nrpe_config(nagios=None): |
| set_state('nrpe-external-master.initial-config') |
| update_nrpe_config(nagios) |
| |
| |
| @when('nginx.available') |
| @when('nrpe-external-master.available') |
| @when_any('config.changed.nagios_context', |
| 'config.changed.nagios_servicegroups') |
| def update_nrpe_config(unused=None): |
| services = ('nginx',) |
| |
| hostname = nrpe.get_nagios_hostname() |
| current_unit = nrpe.get_nagios_unit_name() |
| nrpe_setup = nrpe.NRPE(hostname=hostname) |
| nrpe.add_init_service_checks(nrpe_setup, services, current_unit) |
| nrpe_setup.write() |
| |
| |
| @when_not('nrpe-external-master.available') |
| @when('nrpe-external-master.initial-config') |
| def remove_nrpe_config(nagios=None): |
| remove_state('nrpe-external-master.initial-config') |
| |
| # List of systemd services for which the checks will be removed |
| services = ('nginx',) |
| |
| # The current nrpe-external-master interface doesn't handle a lot of logic, |
| # use the charm-helpers code for now. |
| hostname = nrpe.get_nagios_hostname() |
| nrpe_setup = nrpe.NRPE(hostname=hostname) |
| |
| for service in services: |
| nrpe_setup.remove_check(shortname=service) |