From 55c3a0fa8fb981588c10cdf4ca1ea9fbe49d7698 Mon Sep 17 00:00:00 2001 From: gardlt Date: Sun, 18 Jun 2017 12:05:30 -0500 Subject: [PATCH] [feat] adding sequenced deployments * added new structure to the armada yaml --- README.rst | 8 +- armada/handlers/armada.py | 183 ++++++----- armada/tests/unit/handlers/test_armada.py | 60 ++-- armada/tests/unit/utils/test_lint.py | 40 ++- armada/utils/lint.py | 22 +- docs/source/development/getting-started.rst | 41 +-- .../operations/guide-build-armada-yaml.rst | 126 ++++--- examples/openstack-helm.yaml | 310 ++++++++---------- 8 files changed, 400 insertions(+), 390 deletions(-) diff --git a/README.rst b/README.rst index 1d1d5acc..71e000c0 100644 --- a/README.rst +++ b/README.rst @@ -62,8 +62,8 @@ The installation is fairly straight forward: Recomended Enviroment: Ubuntu 16.04 -Installing Dependecies: -~~~~~~~~~~~~~~~~~~~~~~~ +Installing Dependecies +~~~~~~~~~~~~~~~~~~~~~~ you can run: @@ -72,8 +72,8 @@ you can run: NOTE: If you want to use virtualenv please refer to `pygit2`_ -Installing armada: -~~~~~~~~~~~~~~~~~~ +Installing armada +~~~~~~~~~~~~~~~~~ ``sudo pip install -e .`` diff --git a/armada/handlers/armada.py b/armada/handlers/armada.py index 9725c14f..fca552ee 100644 --- a/armada/handlers/armada.py +++ b/armada/handlers/armada.py @@ -75,12 +75,13 @@ class Armada(object): return chart, values def pre_flight_checks(self): - for ch in self.config['armada']['charts']: - location = ch.get('chart').get('source').get('location') - ct_type = ch.get('chart').get('source').get('type') + for group in self.config.get('armada').get('charts'): + for ch in group.get('chart_group'): + location = ch.get('chart').get('source').get('location') + ct_type = ch.get('chart').get('source').get('type') - if ct_type == 'git' and not git.check_available_repo(location): - raise ValueError(str("Invalid Url Path: " + location)) + if ct_type == 'git' and not git.check_available_repo(location): + raise ValueError(str("Invalid Url Path: " + location)) if not self.tiller.tiller_status(): raise Exception("Tiller Services is not Available") @@ -93,6 +94,8 @@ class Armada(object): Syncronize Helm with the Armada Config(s) ''' + # TODO: (gardlt) we need to break up this func into + # a more cleaner format # extract known charts on tiller right now if not self.skip_pre_flight: LOG.info("Performing Pre-Flight Checks") @@ -109,96 +112,104 @@ class Armada(object): for entry in self.config['armada']['charts']: - chart = dotify(entry['chart']) - values = entry.get('chart').get('values', {}) - pre_actions = {} - post_actions = {} + desc = entry.get('description', 'A Chart Group') + chart_group = entry.get('chart_group', []) - if chart.release_name is None: - continue + if entry.get('sequenced', False): + self.wait = True - # retrieve appropriate timeout value if 'wait' is specified - chart_timeout = None - if self.wait: - if self.timeout: - chart_timeout = self.timeout - elif getattr(chart, 'timeout', None): - chart_timeout = chart.timeout + LOG.info('Deploying: %s', desc) - # initialize helm chart and request a - # protoc helm chart object which will - # pull the sources down and walk the - # dependencies - chartbuilder = ChartBuilder(chart) - protoc_chart = chartbuilder.get_helm_chart() + for gchart in chart_group: + chart = dotify(gchart['chart']) + values = gchart.get('chart').get('values', {}) + pre_actions = {} + post_actions = {} + LOG.info('%s', chart.release_name) - # determine install or upgrade by examining known releases - LOG.debug("RELEASE: %s", chart.release_name) - - if release_prefix(prefix, 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, release_prefix(prefix, chart.release_name)) - - LOG.info("Checking Pre/Post Actions") - upgrade = entry.get('chart', {}).get('upgrade', False) - if upgrade: - if not self.disable_update_pre and upgrade.get('pre', - False): - pre_actions = getattr(chart.upgrade, 'pre', {}) - - if not self.disable_update_post and upgrade.get('post', - False): - LOG.info("Checking Post Actions") - 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 - - upgrade_diff = self.show_diff(chart, installed_chart, - installed_values, - chartbuilder.dump(), values) - - if not upgrade_diff: - LOG.info("There are no updates found in this chart") + if chart.release_name is None: continue - # do actual update - self.tiller.update_release(protoc_chart, - self.dry_run, - chart.release_name, - chart.namespace, - prefix, pre_actions, - post_actions, - disable_hooks=chart. - upgrade.no_hooks, - values=yaml.safe_dump(values), - wait=self.wait, - timeout=chart_timeout) + # retrieve appropriate timeout value if 'wait' is specified + chart_timeout = None + if self.wait: + if getattr(chart, 'timeout', None): + chart_timeout = chart.timeout + else: + chart_timeout = self.timeout - # process install - else: - LOG.info("Installing release %s", chart.release_name) - self.tiller.install_release(protoc_chart, - self.dry_run, - chart.release_name, - chart.namespace, - prefix, - values=yaml.safe_dump(values), - wait=self.wait, - timeout=chart_timeout) + chartbuilder = ChartBuilder(chart) + protoc_chart = chartbuilder.get_helm_chart() - LOG.debug("Cleaning up chart source in %s", - chartbuilder.source_directory) + # determine install or upgrade by examining known releases + LOG.debug("RELEASE: %s", chart.release_name) + deployed_releases = [x[0] for x in known_releases] + prefix_chart = release_prefix(prefix, chart.release_name) - chartbuilder.source_cleanup() + if prefix_chart in deployed_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 + LOG.info("Checking Pre/Post Actions") + apply_chart, apply_values = self.find_release_chart( + known_releases, prefix_chart) + + LOG.info("Checking Pre/Post Actions") + upgrade = gchart.get('chart', {}).get('upgrade', False) + + if upgrade: + if not self.disable_update_pre and upgrade.get('pre', + False): + pre_actions = getattr(chart.upgrade, 'pre', {}) + + if not self.disable_update_post and upgrade.get('post', + False): + 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 + + upgrade_diff = self.show_diff(chart, apply_chart, + apply_values, + chartbuilder.dump(), values) + + if not upgrade_diff: + LOG.info("There are no updates found in this chart") + continue + + # do actual update + self.tiller.update_release(protoc_chart, + self.dry_run, + chart.release_name, + chart.namespace, + prefix, pre_actions, + post_actions, + disable_hooks=chart. + upgrade.no_hooks, + values=yaml.safe_dump(values), + wait=self.wait, + timeout=chart_timeout) + + # process install + else: + LOG.info("Installing release %s", chart.release_name) + self.tiller.install_release(protoc_chart, + self.dry_run, + chart.release_name, + chart.namespace, + prefix, + values=yaml.safe_dump(values), + wait=self.wait, + timeout=chart_timeout) + + LOG.debug("Cleaning up chart source in %s", + chartbuilder.source_directory) + + chartbuilder.source_cleanup() if self.enable_chart_cleanup: self.tiller.chart_cleanup(prefix, self.config['armada']['charts']) diff --git a/armada/tests/unit/handlers/test_armada.py b/armada/tests/unit/handlers/test_armada.py index e3593757..34b51524 100644 --- a/armada/tests/unit/handlers/test_armada.py +++ b/armada/tests/unit/handlers/test_armada.py @@ -10,38 +10,37 @@ from armada.handlers.armada import Armada class ArmadaTestCase(unittest.TestCase): test_yaml = """ - endpoints: &endpoints - hello-world: - this: is an example - armada: release_prefix: armada charts: - - chart: - name: test_chart_1 - release_name: test_chart_1 - namespace: test - values: {} - source: - type: null - location: null - subpath: null - reference: null - dependencies: [] - timeout: 50 + - description: this is a test + sequenced: False + chart_group: + - chart: + name: test_chart_1 + release_name: test_chart_1 + namespace: test + values: {} + source: + type: null + location: null + subpath: null + reference: null + dependencies: [] + timeout: 50 - - chart: - name: test_chart_2 - release_name: test_chart_2 - namespace: test - values: {} - source: - type: null - location: null - subpath: null - reference: null - dependencies: [] - timeout: 5 + - chart: + name: test_chart_2 + release_name: test_chart_2 + namespace: test + values: {} + source: + type: null + location: null + subpath: null + reference: null + dependencies: [] + timeout: 5 """ @mock.patch('armada.handlers.armada.ChartBuilder') @@ -57,8 +56,9 @@ class ArmadaTestCase(unittest.TestCase): armada.tiller = mock_tiller armada.config = yaml.load(self.test_yaml) - chart_1 = armada.config['armada']['charts'][0]['chart'] - chart_2 = armada.config['armada']['charts'][1]['chart'] + charts = armada.config['armada']['charts'][0]['chart_group'] + chart_1 = charts[0]['chart'] + chart_2 = charts[1]['chart'] # mock irrelevant methods called by armada.sync() mock_tiller.list_charts.return_value = [] diff --git a/armada/tests/unit/utils/test_lint.py b/armada/tests/unit/utils/test_lint.py index 66836c49..7927bbe2 100644 --- a/armada/tests/unit/utils/test_lint.py +++ b/armada/tests/unit/utils/test_lint.py @@ -24,10 +24,11 @@ class LintTestCase(unittest.TestCase): armada: release_prefix: armada-test charts: - - chart: - name: chart - release_name: chart - namespace: chart + - chart_group: + - chart: + name: chart + release_name: chart + namespace: chart """) resp = lint.valid_manifest(config) self.assertTrue(resp) @@ -37,10 +38,11 @@ class LintTestCase(unittest.TestCase): armasda: release_prefix: armada-test charts: - - chart: - name: chart - release_name: chart - namespace: chart + - chart_group: + - chart: + name: chart + release_name: chart + namespace: chart """) with self.assertRaises(Exception): @@ -51,10 +53,11 @@ class LintTestCase(unittest.TestCase): armada: release: armada-test charts: - - chart: - name: chart - release_name: chart - namespace: chart + - chart_group: + - chart: + name: chart + release_name: chart + namespace: chart """) with self.assertRaises(Exception): @@ -62,13 +65,14 @@ class LintTestCase(unittest.TestCase): def test_lint_armada_removed(self): config = yaml.load(""" - armasda: + sarmada: release_prefix: armada-test - chart: - - chart: - name: chart - release_name: chart - namespace: chart + charts: + - chart_group: + - chart: + name: chart + release_name: chart + namespace: chart """) with self.assertRaises(Exception): diff --git a/armada/utils/lint.py b/armada/utils/lint.py index 33d2de48..b99584e0 100644 --- a/armada/utils/lint.py +++ b/armada/utils/lint.py @@ -1,3 +1,16 @@ +# Copyright 2017 The Armada 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. ARMADA_DEFINITION = 'armada' RELEASE_PREFIX = 'release_prefix' @@ -5,7 +18,7 @@ CHARTS_DEFINITION = 'charts' def valid_manifest(config): - if not (isinstance(config.get(ARMADA_DEFINITION, None), dict)): + if not isinstance(config.get(ARMADA_DEFINITION, None), dict): raise Exception("Did not declare armada object") armada_config = config.get('armada') @@ -16,8 +29,9 @@ def valid_manifest(config): if not isinstance(armada_config.get(CHARTS_DEFINITION), list): raise Exception('Check yaml invalid chart definition must be array') - for chart in armada_config.get('charts', []): - if not isinstance(chart.get('chart').get('name'), basestring): - raise Exception('Chart name needs to be a string') + for group in armada_config.get('charts'): + for chart in group.get('chart_group'): + if not isinstance(chart.get('chart').get('name'), basestring): + raise Exception('Chart name needs to be a string') return True diff --git a/docs/source/development/getting-started.rst b/docs/source/development/getting-started.rst index 98ec1cac..23c5b6aa 100644 --- a/docs/source/development/getting-started.rst +++ b/docs/source/development/getting-started.rst @@ -7,7 +7,7 @@ Docker To use the docker containter to develop: -1. Fork the [repo](http://github.com/att-comdev/armada) +1. Fork the `Repository `_ 2. Clone the forked repo .. code-block:: bash @@ -15,8 +15,11 @@ To use the docker containter to develop: cd armada export repo="https://github.com//armada.git" export branch="" + docker build . -t quay.io/attcomdev/armada:latest --build-arg REPO=$repo --build-arg VERSION=$branch + docker run -d --name armada -v ~/.kube/config:/root/.kube/config -v $(pwd)/examples/:/examples quay.io/attcomdev/armada:latest + .. note:: The first build will take a little while. Afterwards the it will much faster @@ -30,31 +33,7 @@ To use VirtualEnv we will need to add some extra steps 1. virtualenv venv 2. source ./venv/bin/activate -3. export LIBGIT2=$VIRTUAL_ENV -4. run modified bash below - - -.. code-block:: bash - - #!/bin/sh - - # Ubuntu 16.04 Install only - - sudo apt install git cmake make -y - sudo apt-get install -y python-dev libffi-dev libssl-dev libxml2-dev libxslt1-dev libssh2-1 libgit2-dev python-pip libgit2-24 - sudo apt-get install -y pkg-config libssh2-1-dev libhttp-parser-dev libssl-dev libz-dev - - LIBGIT_VERSION='0.25.0' - - wget https://github.com/libgit2/libgit2/archive/v${LIBGIT_VERSION}.tar.gz - tar xzf v${LIBGIT_VERSION}.tar.gz - cd libgit2-${LIBGIT_VERSION}/ - cmake . -DCMAKE_INSTALL_PREFIX=$LIBGIT2 - make - sudo make install - export LDFLAGS="-Wl,-rpath='$LIBGIT2/lib',--enable-new-dtags $LDFLAGS" - sudo pip install pygit2==${LIBGIT_VERSION} - sudo ldconfig +3. sudo sh ./tools/libgit2.sh Test that it worked with: @@ -65,7 +44,7 @@ Test that it worked with: From the directory of the forked repository: .. code-block:: bash - + pip install -r requirements.txt pip install -r test-requirements.txt @@ -76,10 +55,12 @@ Your env is now ready to go! :) Kubernetes ########## -To test your armada fixes/features you will need to set-up a Kubernetes cluster. We recommend: +To test your armada fixes/features you will need to set-up a Kubernetes cluster. -`Minikube `_ +We recommend: -`Halcyon `_ +`Kubeadm `_ + +`Kubeadm-aio `_ .. note:: When using Halcyon it will not generate a config file. Run the following commands to create one: `get_k8s_creds.sh `_ diff --git a/docs/source/operations/guide-build-armada-yaml.rst b/docs/source/operations/guide-build-armada-yaml.rst index 98f94659..d588b887 100644 --- a/docs/source/operations/guide-build-armada-yaml.rst +++ b/docs/source/operations/guide-build-armada-yaml.rst @@ -56,6 +56,19 @@ Behavior Chart Keywords ^^^^^^^^^^^^^^ +Chart Group +^^^^^^^^^^^ + ++-----------------+----------+------------------------------------------------------------------------+ +| keyword | type | action | ++=================+==========+========================================================================+ +| description | string | description of chart set | ++-----------------+----------+------------------------------------------------------------------------+ +| charts_group | array | stores definiton of the charts in a group | ++-----------------+----------+------------------------------------------------------------------------+ +| sequenced | bool | enables sequeced chart deployment in a group | ++-----------------+----------+------------------------------------------------------------------------+ + Chart ^^^^^ @@ -108,21 +121,24 @@ Simple Example armada: release_prefix: "my_armada" charts: - - chart: &cockroach - name: cockroach - release_name: cockroach - namespace: db - timeout: 20 - install: - no_hooks: false - values: - Replicas: 1 - source: - type: git - location: git://github.com/kubernetes/charts/ - subpath: stable/cockroachdb - reference: master - dependencies: [] + - description: I am a chart group + sequenced: False + chart_group: + - chart: &cockroach + name: cockroach + release_name: cockroach + namespace: db + timeout: 20 + install: + no_hooks: false + values: + Replicas: 1 + source: + type: git + location: git://github.com/kubernetes/charts/ + subpath: stable/cockroachdb + reference: master + dependencies: [] Multichart Example ~~~~~~~~~~~~~~~~~~ @@ -132,35 +148,57 @@ Multichart Example armada: release_prefix: "my_armada" charts: - - chart: &common - name: common - release_name: null - namespace: null - timeout: None - values: {} - source: - type: git - location: git://github.com/kubernetes/charts/ - subpath: common - reference: master - dependencies: [] - - - chart: &cockroach - name: cockroach - release_name: cockroach - namespace: db - timeout: 100 - install: - no_hooks: false - values: - Replicas: 1 - source: - type: git - location: git://github.com/kubernetes/charts/ - subpath: stable/cockroachdb - reference: master - dependencies: - - *common + - description: I am group 1 + sequenced: True + chart_group: + - chart: &common + name: common + release_name: common + namespace: db + timeout: 20 + install: + no_hooks: false + values: + Replicas: 1 + source: + type: git + location: git://github.com/kubernetes/charts/ + subpath: stable/common + reference: master + dependencies: [] + - chart: &cockroach + name: cockroach + release_name: cockroach + namespace: db + timeout: 20 + install: + no_hooks: false + values: + Replicas: 1 + source: + type: git + location: git://github.com/kubernetes/charts/ + subpath: stable/cockroachdb + reference: master + dependencies: [] + - description: I am group 2 + sequenced: False + chart_group: + - chart: &mariadb + name: mariadb + release_name: mariadb + namespace: db + timeout: 20 + install: + no_hooks: false + values: + Replicas: 1 + source: + type: git + location: git://github.com/kubernetes/charts/ + subpath: stable/mariadb + reference: master + dependencies: [] References ~~~~~~~~~~ diff --git a/examples/openstack-helm.yaml b/examples/openstack-helm.yaml index 08b44f9c..9ca4318a 100644 --- a/examples/openstack-helm.yaml +++ b/examples/openstack-helm.yaml @@ -1,182 +1,144 @@ -endpoints: &endpoints - hello-world: - this: is an example - armada: - - # results in "armada-keystone" release name below - # to avoid manipulating releases managed directtly - # with helm or other armadas release_prefix: armada - charts: - - # silent dependency - - chart: &helm-toolkit - name: helm-toolkit - release_name: null - namespace: null - values: {} - source: - type: git - location: git://github.com/openstack/openstack-helm - subpath: helm-toolkit - reference: master - dependencies: [] - - - chart: &mariadb - name: mariadb - release_name: mariadb - namespace: openstack - timeout: 50 - install: - no_hooks: false - upgrade: - no_hooks: false - pre: - delete: [] - create: [] - post: - delete: [] - create: [] - values: - endpoints: *endpoints - replicas: 1 - volume: - size: 1Gi - source: - type: git - location: git://github.com/openstack/openstack-helm - subpath: mariadb - reference: master - dependencies: + - description: Generate Bootstrap keys + sequenced: True + chart_group: + - chart: &helm-toolkit + name: helm-toolkit + release_name: null + namespace: null + values: {} + source: + type: git + location: git://github.com/openstack/openstack-helm + subpath: helm-toolkit + reference: master + dependencies: [] + - chart: &bootstrap-openstack + name: bootstrap-openstack + release_name: openstack + namespace: openstack + values: {} + source: + type: local + location: /home/ubuntu/openstack-helm + subpath: bootstrap + reference: master + dependencies: [] + - chart: &mariadb + name: mariadb + release_name: mariadb + namespace: openstack + timeout: 50 + install: + no_hooks: false + upgrade: + no_hooks: false + values: {} + source: + type: git + location: git://github.com/openstack/openstack-helm + subpath: mariadb + reference: master + dependencies: + - *helm-toolkit + - description: Undercloud Services add-ons + sequenced: False + chart_group: + - chart: &memcached + name: memcached + release_name: memcached + namespace: openstack + timeout: 10 + install: + no_hooks: false + upgrade: + no_hooks: false + values: {} + source: + type: git + location: git://github.com/openstack/openstack-helm + subpath: memcached + reference: master + dependencies: + - *helm-toolkit + - chart: &etcd + name: etcd + release_name: etcd + namespace: openstack + timeout: 10 + install: + no_hooks: false + upgrade: + no_hooks: false + values: {} + source: + type: git + location: git://github.com/openstack/openstack-helm + subpath: etcd + reference: master + dependencies: + - *helm-toolkit + - chart: &rabbitmq + name: rabbitmq + release_name: rabbitmq + namespace: openstack + timeout: 10 + install: + no_hooks: false + upgrade: + no_hooks: false + values: + replicas: 1 + source: + type: git + location: git://github.com/openstack/openstack-helm + subpath: rabbitmq + reference: master + dependencies: - *helm-toolkit - - chart: &memcached - name: memcached - release_name: memcached - namespace: openstack - timeout: 10 - install: - no_hooks: false - upgrade: - no_hooks: false - pre: - delete: [] - create: [] - post: - delete: [] - create: [] - values: - endpoints: *endpoints - source: - type: git - location: git://github.com/openstack/openstack-helm - subpath: memcached - reference: master - dependencies: - - *helm-toolkit - - - chart: &etcd - name: etcd - release_name: etcd - namespace: openstack - timeout: 10 - install: - no_hooks: false - upgrade: - no_hooks: false - pre: - delete: [] - create: [] - post: - delete: [] - create: [] - values: - endpoints: *endpoints - source: - type: git - location: git://github.com/openstack/openstack-helm - subpath: etcd - reference: master - dependencies: - - *helm-toolkit - - - chart: &rabbitmq - name: rabbitmq - release_name: rabbitmq - namespace: openstack - timeout: 10 - install: - no_hooks: false - upgrade: - no_hooks: false - pre: - delete: [] - create: [] - post: - delete: [] - create: [] - values: - endpoints: *endpoints - replicas: 1 - source: - type: git - location: git://github.com/openstack/openstack-helm - subpath: rabbitmq - reference: master - dependencies: - - *helm-toolkit - - - chart: &keystone - name: keystone - release_name: keystone - namespace: openstack - timeout: 20 - install: - no_hooks: false - upgrade: - no_hooks: false - post: - delete: [] - create: [] - pre: + - description: Openstack Services + sequenced: false + chart_group: + - chart: &keystone + name: keystone + release_name: keystone + namespace: openstack + timeout: 20 + install: + no_hooks: false + upgrade: + no_hooks: false + pre: delete: - - name: keystone-db-sync - type: job - - name: keystone-db-init - type: job - values: - endpoints: *endpoints - source: - type: git - location: git://github.com/openstack/openstack-helm - subpath: keystone - reference: master - dependencies: + - name: keystone-db-sync + type: job + - name: keystone-db-init + type: job + values: {} + source: + type: git + location: git://github.com/openstack/openstack-helm + subpath: keystone + reference: master + dependencies: - *helm-toolkit - - - chart: &horizon - name: horizon - release_name: horizon - namespace: openstack - timeout: 10 - install: - no_hooks: false - upgrade: - no_hooks: false - pre: - delete: [] - create: [] - post: - delete: [] - create: [] - values: - endpoints: *endpoints - source: - type: git - location: git://github.com/openstack/openstack-helm - subpath: horizon - reference: master - dependencies: + - chart: &horizon + name: horizon + release_name: horizon + namespace: openstack + timeout: 10 + install: + no_hooks: false + upgrade: + no_hooks: false + values: {} + source: + type: git + location: git://github.com/openstack/openstack-helm + subpath: horizon + reference: master + dependencies: - *helm-toolkit