#!/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. from charms import layer from charms.layer import snap from charms.reactive import hook from charms.reactive import is_state from charms.reactive import set_state from charms.reactive import when from charms.reactive import when_not from charmhelpers.core import hookenv from shlex import split from subprocess import check_call from subprocess import check_output @hook('upgrade-charm') def reset_delivery_states(): ''' Remove the state set when resources are unpacked. ''' install_snaps() @when('kubernetes-e2e.installed') def messaging(): ''' Probe our relations to determine the propper messaging to the end user ''' missing_services = [] if not is_state('kubernetes-master.available'): missing_services.append('kubernetes-master') if not is_state('certificates.available'): missing_services.append('certificates') if missing_services: if len(missing_services) > 1: subject = 'relations' else: subject = 'relation' services = ','.join(missing_services) message = 'Missing {0}: {1}'.format(subject, services) hookenv.status_set('blocked', message) return hookenv.status_set('active', 'Ready to test.') @when('config.changed.channel') def channel_changed(): install_snaps() def install_snaps(): ''' Deliver the e2e and kubectl components from the binary resource stream packages declared in the charm ''' channel = hookenv.config('channel') hookenv.status_set('maintenance', 'Installing kubectl snap') snap.install('kubectl', channel=channel, classic=True) hookenv.status_set('maintenance', 'Installing kubernetes-test snap') snap.install('kubernetes-test', channel=channel, classic=True) set_state('kubernetes-e2e.installed') @when('tls_client.ca.saved', 'tls_client.client.certificate.saved', 'tls_client.client.key.saved', 'kubernetes-master.available', 'kubernetes-e2e.installed') @when_not('kubeconfig.ready') def prepare_kubeconfig_certificates(master): ''' Prepare the data to feed to create the kubeconfig file. ''' layer_options = layer.options('tls-client') # Get all the paths to the tls information required for kubeconfig. ca = layer_options.get('ca_certificate_path') key = layer_options.get('client_key_path') cert = layer_options.get('client_certificate_path') servers = get_kube_api_servers(master) # pedantry kubeconfig_path = '/home/ubuntu/.kube/config' # Create kubernetes configuration in the default location for ubuntu. create_kubeconfig('/root/.kube/config', servers[0], ca, key, cert, user='root') create_kubeconfig(kubeconfig_path, servers[0], ca, key, cert, user='ubuntu') # Set permissions on the ubuntu users kubeconfig to ensure a consistent UX cmd = ['chown', 'ubuntu:ubuntu', kubeconfig_path] check_call(cmd) set_state('kubeconfig.ready') @when('kubernetes-e2e.installed', 'kubeconfig.ready') def set_app_version(): ''' Declare the application version to juju ''' cmd = ['kubectl', 'version', '--client'] from subprocess import CalledProcessError try: version = check_output(cmd).decode('utf-8') except CalledProcessError: message = "Missing kubeconfig causes errors. Skipping version set." hookenv.log(message) return git_version = version.split('GitVersion:"v')[-1] version_from = git_version.split('",')[0] hookenv.application_version_set(version_from.rstrip()) def create_kubeconfig(kubeconfig, server, ca, key, certificate, user='ubuntu', context='juju-context', cluster='juju-cluster'): '''Create a configuration for Kubernetes based on path using the supplied arguments for values of the Kubernetes server, CA, key, certificate, user context and cluster.''' # Create the config file with the address of the master server. cmd = 'kubectl config --kubeconfig={0} set-cluster {1} ' \ '--server={2} --certificate-authority={3} --embed-certs=true' check_call(split(cmd.format(kubeconfig, cluster, server, ca))) # Create the credentials using the client flags. cmd = 'kubectl config --kubeconfig={0} set-credentials {1} ' \ '--client-key={2} --client-certificate={3} --embed-certs=true' check_call(split(cmd.format(kubeconfig, user, key, certificate))) # Create a default context with the cluster. cmd = 'kubectl config --kubeconfig={0} set-context {1} ' \ '--cluster={2} --user={3}' check_call(split(cmd.format(kubeconfig, context, cluster, user))) # Make the config use this new context. cmd = 'kubectl config --kubeconfig={0} use-context {1}' check_call(split(cmd.format(kubeconfig, context))) def get_kube_api_servers(master): '''Return the kubernetes api server address and port for this relationship.''' hosts = [] # Iterate over every service from the relation object. for service in master.services(): for unit in service['hosts']: hosts.append('https://{0}:{1}'.format(unit['hostname'], unit['port'])) return hosts def determine_arch(): ''' dpkg wrapper to surface the architecture we are tied to''' cmd = ['dpkg', '--print-architecture'] output = check_output(cmd).decode('utf-8') return output.rstrip()