def configure_proxy(self, proxy): """Configure GitLab for operation behind a reverse proxy.""" url = urlparse(self.get_external_uri()) if url.scheme == "https": port = 443 else: port = 80 if self.charm_config["proxy_via_ip"]: networks = hookenv.network_get(proxy.relation_name) internal_host = networks["ingress-addresses"][0] else: internal_host = socket.getfqdn() proxy_config = [ { "mode": "http", "external_port": port, "internal_host": internal_host, "internal_port": self.charm_config["http_port"], "subdomain": url.hostname, }, { "mode": "tcp", "external_port": self.charm_config["proxy_ssh_port"], "internal_host": internal_host, "internal_port": self.charm_config["ssh_port"], }, ] proxy.configure(proxy_config)
def configure_systemd_resolved(hosts={}): ''' Render a drop-in config file for systemd-resolved at /etc/systemd/resolved.conf.d/ and restart the systemd-resolved service. This is done in order to use a local bind9 server as a resolver. ''' charm_config = config() realm = charm_config['realm'] ch_host.mkdir('/etc/systemd/resolved.conf.d/', perms=0o755, owner='root', group='root') ctxt = {} ctxt['domain'] = realm ctxt['dns_ip'] = network_get('dns-server')['ingress-addresses'][0] core.templating.render( source='local-dns-bind.conf', target='/etc/bind/zones/{}'.format(realm), context=ctxt, owner='root', group='root', perms=0o644, ) ch_host.service_restart('systemd-resolved')
def websso_trusted_dashboard_changed(): """ Provide L7 endpoint details for the dashboard and also handle any config changes that may affect those. """ relations = relation_ids('websso-trusted-dashboard') if not relations: return # TODO: check for vault relation in order to determine url scheme tls_configured = config('ssl-key') or config('enforce-ssl') scheme = 'https://' if tls_configured else 'http://' if config('dns-ha') or config('os-public-hostname'): hostname = config('os-public-hostname') elif config('vip'): hostname = config('vip') else: # use an ingress-address of a given unit as a fallback netinfo = network_get('websso-trusted-dashboard') hostname = netinfo['ingress-addresses'][0] # provide trusted dashboard URL details for rid in relations: relation_set(relation_id=rid, relation_settings={ "scheme": scheme, "hostname": hostname, "path": "/auth/websso/" })
def provide_database(mysql): log('db requested') info = network_get('server', relation_id()) log('network info {0}'.format(info)) host = info['ingress-addresses'][0] if host == "": log("no service address yet") return for request, application in mysql.database_requests().items(): log('request -> {0} for app -> {1}'.format(request, application)) database_name = get_state('database') user = get_state('user') password = get_state('password') log('db params: {0}:{1}@{2}'.format(user, password, database_name)) mysql.provide_database( request_id=request, host=host, port=3306, database_name=database_name, user=user, password=password, ) clear_flag('server.database.requested')
def provide_lb_consumers(): '''Respond to any LB requests via the lb-consumers relation. This is used in favor for the more complex two relation setup using the website and loadbalancer relations going forward. ''' lb_consumers = endpoint_from_name('lb-consumers') lb_address = _get_lb_address() for request in lb_consumers.all_requests: response = request.response if request.protocol not in (request.protocols.tcp, request.protocols.http, request.protocols.https): response.error_type = response.error_types.unsupported response.error_fields = { 'protocol': 'Protocol must be one of: tcp, http, https' } lb_consumers.send_response(request) continue if lb_address: private_address = lb_address public_address = lb_address else: network_info = hookenv.network_get('lb-consumers', str(request.relation.id)) private_address = network_info['ingress-addresses'][0] public_address = hookenv.unit_get('public-address') if request.public: response.address = public_address else: response.address = private_address lb_consumers.send_response(request)
def configure_bind_zone(hosts={}): ''' Render bind9 zone configuration. ''' charm_config = config() realm = charm_config['realm'] ch_host.mkdir('/etc/bind/zones', perms=0o755, owner='root', group='bind') ctxt = {} ctxt['domain'] = realm hosts.update({ socket.gethostname(): network_get('dns-server')['ingress-addresses'][0] }) ctxt['hosts'] = hosts core.templating.render( source='bind_zone', target='/etc/bind/zones/{}'.format(realm), context=ctxt, owner='root', group='bind', perms=0o644, ) ch_host.service_restart('bind9')
def provide_database(mysql): log('db requested') info = network_get('server', relation_id()) log('network info {0}'.format(info)) host = info.get('ingress-addresses', [""])[0] if not host: log("no service address yet") return cfg = config() user = cfg.get('user') password = cfg.get('password') database = cfg.get('database') for request, application in mysql.database_requests().items(): log('request -> {0} for app -> {1}'.format(request, application)) log('db params: {0}:...@{1}'.format(user, database)) mysql.provide_database( request_id=request, host=host, port=3306, database_name=database, user=user, password=password, ) clear_flag('server.database.requested')
def get_local_ingress_address(binding): """Get ingress IP address for a binding. binding - e.g. 'monitors' """ # using network-get to retrieve the address details if available. hookenv.log("Getting ingress IP address for binding %s" % binding) try: network_info = hookenv.network_get(binding) if network_info is not None and "ingress-addresses" in network_info: hookenv.log("Using ingress-addresses") ip_address = network_info["ingress-addresses"][0] hookenv.log(ip_address) return ip_address except (NotImplementedError, FileNotFoundError): # We'll fallthrough to the Pre 2.3 code below. pass # Pre 2.3 output try: ip_address = hookenv.network_get_primary_address(binding) hookenv.log("Using primary-addresses") except NotImplementedError: # pre Juju 2.0 ip_address = hookenv.unit_private_ip() hookenv.log("Using unit_private_ip") hookenv.log(ip_address) return ip_address
def get_ingress_address(endpoint_name, ignore_addresses=None): try: network_info = hookenv.network_get(endpoint_name) except NotImplementedError: network_info = {} if not network_info or "ingress-addresses" not in network_info: # 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") addresses = network_info["ingress-addresses"] if ignore_addresses: hookenv.log("ingress-addresses before filtering: {}".format(addresses)) iter_filter = filter(lambda item: item not in ignore_addresses, addresses) addresses = list(iter_filter) hookenv.log("ingress-addresses after filtering: {}".format(addresses)) # Need to prefer non-fan IP addresses due to various issues, e.g. # https://bugs.launchpad.net/charm-gcp-integrator/+bug/1822997 # Fan typically likes to use IPs in the 240.0.0.0/4 block, so we'll # prioritize those last. Not technically correct, but good enough. try: sort_key = lambda a: int(a.partition(".")[0]) >= 240 # noqa: E731 addresses = sorted(addresses, key=sort_key) except Exception: hookenv.log(traceback.format_exc()) return addresses[0]
def websso_trusted_dashboard_changed(): """ Provide L7 endpoint details for the dashboard and also handle any config changes that may affect those. """ relations = relation_ids('websso-trusted-dashboard') if not relations: return # TODO: check for vault relation in order to determine url scheme tls_configured = config('ssl-key') or config('enforce-ssl') scheme = 'https://' if tls_configured else 'http://' if config('dns-ha') or config('os-public-hostname'): hostname = config('os-public-hostname') elif config('vip'): hostname = config('vip') else: # use an ingress-address of a given unit as a fallback netinfo = network_get('websso-trusted-dashboard') hostname = netinfo['ingress-addresses'][0] # provide trusted dashboard URL details for rid in relations: relation_set(relation_id=rid, relation_settings={ "scheme": scheme, "hostname": hostname, "path": "/auth/websso/" })
def configure_staticip(): network_config = hookenv.network_get( hookenv.config('binding') ) nics = {} for nic in network_config.get('bind-addresses'): nics.update({ [nic.get('interfacename')]: { 'addresses': [ nic.get('addresses')[0] ], 'gatewayv4': None, # TODO: Find a reliable way of getting # the gateway IP. 'dhcp': 'no', } }) netplan_config = { 'network': { 'version': 2, 'renderer': "networkd", 'ethernets': { } } } with open('/etc/netplan/00-set-staticip.yaml', 'w') as npfile: npfile.wirte(netplan_config)
def network_details(): # even if there are no relations for that endpoint # we can still get an address from a space bound to it net_details = hookenv.network_get('slurm-dbd-ha') return { 'hostname': socket.gethostname(), 'ingress_address': net_details['ingress-addresses'][0] }
def request_certificates(): cert_provider = endpoint_from_flag('cert-provider.available') # get ingress info ingress_for_clients = hookenv.network_get( 'clients')['ingress-addresses'] ingress_for_db = hookenv.network_get('db')['ingress-addresses'] # use first ingress address as primary and any additional as SANs server_cn, server_sans = ingress_for_clients[ 0], ingress_for_clients[:1] client_cn, client_sans = ingress_for_db[0], ingress_for_db[:1] # request a single server and single client cert; note that multiple certs # of either type can be requested as long as they have unique common names cert_provider.request_server_cert(server_cn, server_sans) cert_provider.request_client_cert(client_cn, client_sans)
def is_pod_up(endpoint): info = network_get(endpoint, relation_id()) # Check to see if the pod has been assigned it's internal and external ips for ingress in info['ingress-addresses']: if len(ingress) == 0: return False return True
def configure_relation_data(): endpoint = endpoint_from_flag('endpoint.redis.joined') info = network_get('redis', relation_id()) log('network info {0}'.format(info)) host = info['ingress-addresses'][0] if host == "": log("no service address yet") return else: endpoint.configure(host=host, port="6379")
def get_ingress_address(endpoint_name): ''' Returns ingress-address belonging to the named endpoint, if available. Falls back to private-address if necessary. ''' try: data = network_get(endpoint_name) except NotImplementedError: return unit_private_ip() if 'ingress-addresses' in data: return data['ingress-addresses'][0] else: return unit_private_ip()
def get_ingress_addresses(endpoint_name): """Returns all ingress-addresses belonging to the named endpoint, if available. Falls back to private-address if necessary.""" try: data = network_get(endpoint_name) except NotImplementedError: return [unit_private_ip()] if "ingress-addresses" in data: return data["ingress-addresses"] else: return [unit_private_ip()]
def get_service_ip(endpoint): try: info = network_get(endpoint, relation_id()) if 'ingress-addresses' in info: addr = info['ingress-addresses'][0] if len(addr): return addr else: log("No ingress-addresses: {}".format(info)) except Exception as e: log("Caught exception checking for service IP: {}".format(e)) return None
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')
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')
def listen_ip_address(): c = config() # Pre-juju2, listen_interface was used to control the interface used for # communication & replication between Cassandra units. if c['listen_interface'] or not hookenv.has_juju_version('2.3'): return (interface_to_ip(c['listen_interface']) or hookenv.unit_private_ip()) # Post-juju2, addresses for relation endpoints can be specified at deploy time # in a standard way, so listen_interface in config should be unnecessary. ip = hookenv.network_get('cluster')['bind-addresses'][0]['addresses'][0]['address'] hookenv.log('listen_ip_address == {!r}'.format(ip), DEBUG) return ip
def configure_relation_data(): cfg = config() endpoint = endpoint_from_flag('endpoint.bitcoind.joined') info = network_get('bitcoind', relation_id()) log('network info {0}'.format(info)) host = info['ingress-addresses'][0] if host == "": log("no service address yet") return else: endpoint.configure(host=host, port=cfg.get('btc-rpcport'), user=cfg.get('btc-rpcuser'), password=cfg.get('btc-rpcpassword'))
def listen(self, cidr=None, port=None): port = port or self.IPERF_BASE_PORT if cidr: bind_addreess = ch_ip.get_address_in_network(cidr) else: bind_addreess = ( hookenv.network_get('magpie') ['bind-addresses'][0]['addresses'][0]['address'] ) cmd = ( "iperf -s -m -fm --port " + str(port) + " -B " + bind_addreess + " | tee " + self.iperf_out + " &" ) os.system(cmd)
def ingress_address(endpoint, relid): # Work around https://github.com/juju/charm-helpers/issues/112 if not hookenv.has_juju_version("2.3"): return hookenv.unit_private_ip() try: d = hookenv.network_get(endpoint, relid) return d["ingress-addresses"][0] except NotImplementedError: # Warn, although this is normal with older Juju. hookenv.log( "Unable to determine ingress address, " "falling back to private ip", hookenv.WARNING, ) return hookenv.unit_private_ip()
def rpc_broadcast_ip_address(): c = config() # Pre-juju2, rpc_interface was used to control the interface used for # client communication. if c['rpc_interface'] or not hookenv.has_juju_version('2.3'): return (interface_to_ip(c['rpc_interface']) or hookenv.unit_public_ip()) # Post-juju2, addresses for relation endpoints can be specified at deploy time # in a standard way, so listen_interface in config should be unnecessary. # Only the database endpoint is used, as Cassandra only supports a single # IP address to broadcast as the connection address. ip = hookenv.network_get('database')['ingress-addresses'][0] hookenv.log('rpc_broadcast_ip_address == {!r}'.format(ip), DEBUG) return ip
def get_ingress_address6(endpoint_name): try: network_info = hookenv.network_get(endpoint_name) except NotImplementedError: network_info = {} if not network_info or "ingress-addresses" not in network_info: return None addresses = network_info["ingress-addresses"] for addr in addresses: ip_addr = ipaddress.ip_interface(addr).ip if ip_addr.version == 6: return str(ip_addr) else: return None
def get_bind_address(endpoint_name): ''' Returns the first bind-address found in network info belonging to the named endpoint, if available. Falls back to private-address if necessary. @param endpoint_name the endpoint from where taking the bind address ''' try: data = network_get(endpoint_name) except NotImplementedError: return unit_private_ip() # Consider that network-get returns something like: # # bind-addresses: # - macaddress: 02:d0:9e:31:d9:e0 # interfacename: ens5 # addresses: # - hostname: "" # address: 172.31.5.4 # cidr: 172.31.0.0/20 # - hostname: "" # address: 172.31.5.4 # cidr: 172.31.0.0/20 # - macaddress: 8a:32:d7:8d:f6:9a # interfacename: fan-252 # addresses: # - hostname: "" # address: 252.5.4.1 # cidr: 252.0.0.0/12 # egress-subnets: # - 172.31.5.4/32 # ingress-addresses: # - 172.31.5.4 # - 172.31.5.4 # - 252.5.4.1 if 'bind-addresses' in data: bind_addresses = data['bind-addresses'] if len(bind_addresses) > 0: if 'addresses' in bind_addresses[0]: if len(bind_addresses[0]['addresses']) > 0: return bind_addresses[0]['addresses'][0]['address'] return unit_private_ip()
def get_bind_address(): ''' Returns a non-fan bind address for the cni endpoint ''' try: data = network_get('cni') except NotImplementedError: # Juju < 2.1 return unit_private_ip() if 'bind-addresses' not in data: # Juju < 2.3 return unit_private_ip() for bind_address in data['bind-addresses']: if bind_address['interfacename'].startswith('fan-'): continue return bind_address['addresses'][0]['address'] # If we made it here, we didn't find a non-fan CNI bind-address, which is # unexpected. Let's log a message and play it safe. log('Could not find a non-fan bind-address. Using private-address.') return unit_private_ip()
def is_pod_up(endpoint): """Check to see if the pod of a relation is up. application-vimdb: 19:29:10 INFO unit.vimdb/0.juju-log network info In the example below: - 10.1.1.105 is the address of the application pod. - 10.152.183.199 is the service cluster ip { 'bind-addresses': [{ 'macaddress': '', 'interfacename': '', 'addresses': [{ 'hostname': '', 'address': '10.1.1.105', 'cidr': '' }] }], 'egress-subnets': [ '10.152.183.199/32' ], 'ingress-addresses': [ '10.152.183.199', '10.1.1.105' ] } """ try: info = network_get(endpoint, relation_id()) # Check to see if the pod has been assigned it's internal and # external ips for ingress in info['ingress-addresses']: if len(ingress) == 0: return False except: return False return True
def get_bind_address_interface(): ''' Returns a non-fan bind-address interface for the cni endpoint. Falls back to default_route_interface() if bind-address is not available. ''' try: data = network_get('cni') except NotImplementedError: # Juju < 2.1 return default_route_interface() if 'bind-addresses' not in data: # Juju < 2.3 return default_route_interface() for bind_address in data['bind-addresses']: if bind_address['interfacename'].startswith('fan-'): continue return bind_address['interfacename'] # If we made it here, we didn't find a non-fan CNI bind-address, which is # unexpected. Let's log a message and play it safe. log('Could not find a non-fan bind-address. Using fallback interface.') return default_route_interface()
def get_ingress_address(endpoint_name): try: network_info = hookenv.network_get(endpoint_name) except NotImplementedError: network_info = {} if not network_info or 'ingress-addresses' not in network_info: # 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') addresses = network_info['ingress-addresses'] # Need to prefer non-fan IP addresses due to various issues, e.g. # https://bugs.launchpad.net/charm-gcp-integrator/+bug/1822997 # Fan typically likes to use IPs in the 240.0.0.0/4 block, so we'll # prioritize those last. Not technically correct, but good enough. try: sort_key = lambda a: int(a.partition('.')[0]) >= 240 # noqa: E731 addresses = sorted(addresses, key=sort_key) except Exception: hookenv.log(traceback.format_exc()) return addresses[0]
when_not, ) from charmhelpers.core.hookenv import (application_version_set, network_get, status_set, config, open_port) from charmhelpers.core.host import ( service_restart, service_running, service_start, ) from charms.layer.redis import (render_conf, get_redis_version, REDIS_CONF, REDIS_SERVICE) PRIVATE_IP = network_get('redis')['ingress-addresses'][0] @when_not('redis.system.configured') def configure_system_for_redis(): with open('/etc/sysctl.conf', 'a') as f: f.write("\nvm.overcommit_memory = 1") call('sysctl vm.overcommit_memory=1'.split()) with open('/sys/kernel/mm/transparent_hugepage/enabled', 'w') as f: f.write('never') with open('/proc/sys/net/core/somaxconn', 'w') as f: f.write('1024') set_flag('redis.system.configured')
endpoint_from_flag, set_flag, when, when_not, ) from charmhelpers.core.hookenv import (network_get, log, status_set, config, open_port) from charmhelpers.core import unitdata from charms.layer.sentry import (render_sentry_config, start_restart, SENTRY_BIN, SENTRY_WEB_SERVICE, SENTRY_WORKER_SERVICE, SENTRY_CRON_SERVICE) PRIVATE_IP = network_get('http')['ingress-addresses'][0] kv = unitdata.kv() @when_not('manual.database.check.available') def check_user_provided_database(): if not config('db-uri'): clear_flag('sentry.manual.database.available') log("Manual database not configured") else: kv.set('db_uri', config('db-uri')) set_flag('sentry.manual.database.available') clear_flag('sentry.config.available') clear_flag('sentry.juju.database.available') set_flag('manual.database.check.available')
ES_DEFAULT_FILE_PATH = Path('/etc/default/elasticsearch') ES_PATH_CONF = Path('/etc/elasticsearch') ES_YML_PATH = ES_PATH_CONF / 'elasticsearch.yml' ES_PLUGIN = ES_HOME_DIR / 'bin' / 'elasticsearch-plugin' ES_SETUP_PASSWORDS = ES_HOME_DIR / 'bin' / 'elasticsearch-setup-passwords' JVM_OPTIONS = ES_PATH_CONF / 'jvm.options' JAVA_HOME = Path('/usr/lib/jvm/java-8-openjdk-amd64/jre') ES_PUBLIC_INGRESS_ADDRESS = network_get('public')['ingress-addresses'][0] ES_CLUSTER_INGRESS_ADDRESS = network_get('cluster')['ingress-addresses'][0] ES_NODE_TYPE = config('node-type') ES_CLUSTER_NAME = config('cluster-name') ES_HTTP_PORT = 9200 ES_TRANSPORT_PORT = 9300 ES_CERTS_DIR = Path("/etc/elasticsearch/certs") ES_CA = ES_CERTS_DIR / "ca.p12"