diff --git a/.gitignore b/.gitignore index 72364f99..61ab8ba4 100644 --- a/.gitignore +++ b/.gitignore @@ -87,3 +87,4 @@ ENV/ # Rope project settings .ropeproject + diff --git a/armada/armada.py b/armada/armada.py index 51e990b5..72256a53 100644 --- a/armada/armada.py +++ b/armada/armada.py @@ -49,6 +49,8 @@ class Armada(object): chart = dotify(entry['chart']) values = entry['chart']['values'] + pre_actions = {} + post_actions = {} if chart.release_name is None: continue @@ -61,16 +63,23 @@ class Armada(object): protoc_chart = chartbuilder.get_helm_chart() # determine install or upgrade by examining known releases + LOG.debug("RELEASE: %s", chart.release_name) + if chart.release_name in [x[0] for x in known_releases]: # indicate to the end user what path we are taking LOG.info("Upgrading release %s", chart.release_name) - # extract the installed chart and installed values from the # latest release so we can compare to the intended state installed_chart, installed_values = self.find_release_chart( known_releases, chart.release_name) + if not self.args.disable_update_pre: + pre_actions = getattr(chart.upgrade, 'pre', {}) + + if not self.args.disable_update_post: + post_actions = getattr(chart.upgrade, 'post', {}) + # show delta for both the chart templates and the chart values # TODO(alanmeadows) account for .files differences # once we support those @@ -85,7 +94,8 @@ class Armada(object): # do actual update self.tiller.update_release(protoc_chart, self.args.dry_run, - chart.release_name, + chart.release_name, chart.namespace, + pre_actions, post_actions, disable_hooks=chart. upgrade.no_hooks, values=yaml.safe_dump(values)) @@ -99,6 +109,15 @@ class Armada(object): chart.namespace, prefix, values=yaml.safe_dump(values)) + try: + LOG.info("Installing release %s", chart.release_name) + self.tiller.install_release(protoc_chart, + self.args.dry_run, + chart.release_name, + chart.namespace, + values=yaml.safe_dump(values)) + except Exception: + LOG.error("Install failed, continuing.") LOG.debug("Cleaning up chart source in %s", chartbuilder.source_directory) diff --git a/armada/chartbuilder.py b/armada/chartbuilder.py index 76c671e1..ee18b21f 100644 --- a/armada/chartbuilder.py +++ b/armada/chartbuilder.py @@ -135,7 +135,11 @@ class ChartBuilder(object): for root, _, files in os.walk(os.path.join(self.source_directory, 'templates'), topdown=True): for tpl_file in files: - templates.append(Template(name=tpl_file, + tname = os.path.relpath(os.path.join(root, tpl_file), + os.path.join(self.source_directory, + 'templates')) + + templates.append(Template(name=tname, data=open(os.path.join(root, tpl_file), 'r').read())) diff --git a/armada/k8s.py b/armada/k8s.py index e8883e4b..7b947950 100644 --- a/armada/k8s.py +++ b/armada/k8s.py @@ -1,4 +1,6 @@ from kubernetes import client, config +from kubernetes.client.rest import ApiException +from logutil import LOG class K8s(object): ''' @@ -10,3 +12,33 @@ class K8s(object): ''' config.load_kube_config() self.client = client.CoreV1Api() + self.api_client = client.BatchV1Api() + + def delete_job_action(self, name, namespace="default"): + ''' + :params name - name of the job + :params namespace - name of pod that job + ''' + try: + body = client.V1DeleteOptions() + self.api_client.delete_namespaced_job(name=name, + namespace=namespace, + body=body) + except ApiException as e: + LOG.error("Exception when deleting a job: %s", e) + + def create_job_action(self, name, namespace="default"): + ''' + :params name - name of the job + :params namespace - name of pod that job + ''' + LOG.debug(" %s in namespace: %s", name, namespace) + + def get_namespace_pod(self, namespace="default"): + ''' + :params - namespace - pod namespace + + This will return a list of objects req namespace + ''' + res = self.client.list_namespaced_pod(namespace) + return res diff --git a/armada/tiller.py b/armada/tiller.py index fa53080d..d3e7bc28 100644 --- a/armada/tiller.py +++ b/armada/tiller.py @@ -3,12 +3,13 @@ from hapi.services.tiller_pb2 import ReleaseServiceStub, ListReleasesRequest, \ from hapi.chart.config_pb2 import Config import grpc -from logutil import LOG from k8s import K8s +from logutil import LOG TILLER_PORT = 44134 TILLER_VERSION = b'2.1.3' TILLER_TIMEOUT = 300 +RELEASE_LIMIT = 64 class Tiller(object): ''' @@ -48,8 +49,7 @@ class Tiller(object): ''' Search all namespaces for a pod beginning with tiller-deploy* ''' - ret = self.k8s.client.list_pod_for_all_namespaces() - for i in ret.items: + for i in self.k8s.get_namespace_pod('kube-system').items: # TODO(alanmeadows): this is a bit loose if i.metadata.name.startswith('tiller-deploy'): return i @@ -69,9 +69,14 @@ class Tiller(object): ''' List Helm Releases ''' + releases = [] stub = ReleaseServiceStub(self.channel) - req = ListReleasesRequest() - return stub.ListReleases(req, self.timeout, metadata=self.metadata) + req = ListReleasesRequest(limit=RELEASE_LIMIT) + release_list = stub.ListReleases(req, self.timeout, + metadata=self.metadata) + for y in release_list: + releases.extend(y.releases) + return releases def list_charts(self): ''' @@ -80,9 +85,8 @@ class Tiller(object): Returns list of (name, version, chart, values) ''' charts = [] - for x in self.list_releases(): + for latest_release in self.list_releases(): try: - latest_release = x.releases[-1] charts.append((latest_release.name, latest_release.version, latest_release.chart, latest_release.config.raw)) @@ -90,8 +94,49 @@ class Tiller(object): continue return charts - def update_release(self, chart, dry_run, name, disable_hooks=False, - values=None): + def _pre_update_actions(self, actions, namespace): + ''' + :params actions - array of items actions + :params namespace - name of pod for actions + ''' + try: + for action in actions.get('delete', []): + name = action.get("name") + action_type = action.get("type") + if "job" in action_type: + LOG.info("Deleting %s in namespace: %s", name, namespace) + self.k8s.delete_job_action(name, namespace) + continue + LOG.error("Unable to execute name: %s type: %s ", name, type) + except Exception: + LOG.debug("PRE: Could not delete anything, please check yaml") + + try: + for action in actions.get('create', []): + name = action.get("name") + action_type = action.get("type") + if "job" in action_type: + LOG.info("Creating %s in namespace: %s", name, namespace) + self.k8s.create_job_action(name, action_type) + continue + except Exception: + LOG.debug("PRE: Could not create anything, please check yaml") + + def _post_update_actions(self, actions, namespace): + try: + for action in actions.get('create', []): + name = action.get("name") + action_type = action.get("type") + if "job" in action_type: + LOG.info("Creating %s in namespace: %s", name, namespace) + self.k8s.create_job_action(name, action_type) + continue + except Exception: + LOG.debug("POST: Could not create anything, please check yaml") + + def update_release(self, chart, dry_run, name, namespace, + pre_actions=None, post_actions=None, + disable_hooks=False, values=None): ''' Update a Helm Release ''' @@ -101,6 +146,8 @@ class Tiller(object): else: values = Config(raw=values) + self._pre_update_actions(pre_actions, namespace) + # build release install request stub = ReleaseServiceStub(self.channel) release_request = UpdateReleaseRequest( @@ -109,8 +156,11 @@ class Tiller(object): disable_hooks=disable_hooks, values=values, name=name) - return stub.UpdateRelease(release_request, self.timeout, - metadata=self.metadata) + + stub.UpdateRelease(release_request, self.timeout, + metadata=self.metadata) + + self._post_update_actions(post_actions, namespace) def install_release(self, chart, dry_run, name, namespace, prefix, values=None): diff --git a/examples/armada.yaml b/examples/armada.yaml index 879f6b12..3b5f04f3 100644 --- a/examples/armada.yaml +++ b/examples/armada.yaml @@ -11,9 +11,9 @@ armada: charts: - # silent dependency - - chart: &common - name: common + # silent dependency + - chart: &helm-toolkit + name: helm-toolkit release_name: null namespace: null values: {} @@ -23,19 +23,53 @@ armada: subpath: helm-toolkit reference: master dependencies: [] - - - chart: &keystone - name: keystone - release_name: keystone + + - chart: &mariadb + name: mariadb + release_name: mariadb namespace: openstack - install: + install: no_hooks: false upgrade: no_hooks: false pre: - delete: - - Job/keystone-db-sync - - Job/keystone-db-init + delete: [] + create: [] + post: + delete: [] + create: [] + values: + endpoints: *endpoints + source: + type: git + location: git://github.com/att-comdev/openstack-helm + subpath: mariadb + reference: master + dependencies: + - *helm-toolkit + + - chart: &keystone + name: keystone + release_name: keystone + namespace: openstack + install: + no_hooks: false + upgrade: + no_hooks: false + pre: + delete: + - name: keystone-db-sync + type: job + - name: keystone-db-init + type: job + create: + - name: keystone-db-sync + type: job + - name: keystone-db-init + type: job + post: + delete: [] + create: [] values: endpoints: *endpoints source: @@ -44,4 +78,4 @@ armada: subpath: keystone reference: master dependencies: - - *common + - *helm-toolkit diff --git a/scripts/armada b/scripts/armada index e890071e..41ebdb42 100755 --- a/scripts/armada +++ b/scripts/armada @@ -19,6 +19,11 @@ def parse_args(): ap.add_argument('-d', '--dry-run', action='store_true', default=False, required=False, help='Enable dry-run flag on all Tiller' 'Calls') + ap.add_argument('--disable-update-pre', action='store_true', default=False, + required=False, help='Disable pre update actions') + ap.add_argument('--disable-update-post', action='store_true', + default=False, required=False, + help='Disable post update actions') return ap.parse_args()