def test_dump(self): # Validate base case. chart_dir = self.useFixture(fixtures.TempDir()) self.addCleanup(shutil.rmtree, chart_dir.path) self._write_temporary_file_contents(chart_dir.path, 'Chart.yaml', self.chart_yaml) ch = yaml.safe_load(self.chart_stream) ch['data']['source_dir'] = (chart_dir.path, '') test_chart = ch chartbuilder = ChartBuilder(test_chart) self.assertRegex( repr(chartbuilder.dump()), 'hello-world-chart.*A sample Helm chart for Kubernetes.*') # Validate recursive case (with dependencies). dep_chart_dir = self.useFixture(fixtures.TempDir()) self.addCleanup(shutil.rmtree, dep_chart_dir.path) self._write_temporary_file_contents(dep_chart_dir.path, 'Chart.yaml', self.dependency_chart_yaml) dep_ch = yaml.safe_load(self.dependency_chart_stream) dep_ch['data']['source_dir'] = (dep_chart_dir.path, '') dependency_chart = dep_ch test_chart['data']['dependencies'] = [dependency_chart] chartbuilder = ChartBuilder(test_chart) re = inspect.cleandoc(""" hello-world-chart.*A sample Helm chart for Kubernetes.* dependency-chart.*Another sample Helm chart for Kubernetes.* """).replace('\n', '').strip() self.assertRegex(repr(chartbuilder.dump()), re)
def sync(self): ''' Syncronize Helm with the Armada Config(s) ''' msg = {'install': [], 'upgrade': [], 'diff': []} # TODO: (gardlt) we need to break up this func into # a more cleaner format LOG.info("Performing Pre-Flight Operations") self.pre_flight_ops() # extract known charts on tiller right now known_releases = self.tiller.list_charts() prefix = self.config.get(const.KEYWORD_ARMADA).get( const.KEYWORD_PREFIX) if known_releases is None: raise armada_exceptions.KnownReleasesException() for release in known_releases: LOG.debug("Release %s, Version %s found on tiller", release[0], release[1]) for entry in self.config[const.KEYWORD_ARMADA][const.KEYWORD_GROUPS]: chart_wait = self.wait desc = entry.get('description', 'A Chart Group') chart_group = entry.get(const.KEYWORD_CHARTS, []) test_charts = entry.get('test_charts', False) if entry.get('sequenced', False) or test_charts: chart_wait = True LOG.info('Deploying: %s', desc) for gchart in chart_group: chart = dotify(gchart['chart']) values = gchart.get('chart').get('values', {}) wait_values = gchart.get('chart').get('wait', {}) test_chart = gchart.get('chart').get('test', False) pre_actions = {} post_actions = {} if chart.release is None: continue if test_chart: chart_wait = True # retrieve appropriate timeout value if 'wait' is specified chart_timeout = self.timeout if chart_wait: if chart_timeout == DEFAULT_TIMEOUT: chart_timeout = getattr(chart, 'timeout', chart_timeout) chartbuilder = ChartBuilder(chart) protoc_chart = chartbuilder.get_helm_chart() # determine install or upgrade by examining known releases LOG.debug("RELEASE: %s", chart.release) deployed_releases = [x[0] for x in known_releases] prefix_chart = release_prefix(prefix, chart.release) if prefix_chart in deployed_releases: # indicate to the end user what path we are taking LOG.info("Upgrading release %s", chart.release) # 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, msg) if not upgrade_diff: LOG.info("There are no updates found in this chart") continue # do actual update LOG.info('wait: %s', chart_wait) self.tiller.update_release( protoc_chart, prefix_chart, chart.namespace, pre_actions=pre_actions, post_actions=post_actions, dry_run=self.dry_run, disable_hooks=chart.upgrade.no_hooks, values=yaml.safe_dump(values), wait=chart_wait, timeout=chart_timeout) if chart_wait: # TODO(gardlt): after v0.7.1 depricate timeout values if not wait_values.get('timeout', None): wait_values['timeout'] = chart_timeout self.tiller.k8s.wait_until_ready( release=prefix_chart, labels=wait_values.get('labels', ''), namespace=chart.namespace, timeout=wait_values.get('timeout', DEFAULT_TIMEOUT)) msg['upgrade'].append(prefix_chart) # process install else: LOG.info("Installing release %s", chart.release) self.tiller.install_release(protoc_chart, prefix_chart, chart.namespace, dry_run=self.dry_run, values=yaml.safe_dump(values), wait=chart_wait, timeout=chart_timeout) if chart_wait: if not wait_values.get('timeout', None): wait_values['timeout'] = chart_timeout self.tiller.k8s.wait_until_ready( release=prefix_chart, labels=wait_values.get('labels', ''), namespace=chart.namespace, timeout=wait_values.get('timeout', 3600)) msg['install'].append(prefix_chart) LOG.debug("Cleaning up chart source in %s", chartbuilder.source_directory) if test_charts or test_chart: LOG.info('Testing: %s', prefix_chart) resp = self.tiller.testing_release(prefix_chart) test_status = getattr(resp.info.status, 'last_test_suite_run', 'FAILED') LOG.info("Test INFO: %s", test_status) if resp: LOG.info("PASSED: %s", prefix_chart) else: LOG.info("FAILED: %s", prefix_chart) self.tiller.k8s.wait_until_ready(timeout=chart_timeout) LOG.info("Performing Post-Flight Operations") self.post_flight_ops() if self.enable_chart_cleanup: self.tiller.chart_cleanup( prefix, self.config[const.KEYWORD_ARMADA][const.KEYWORD_GROUPS]) return msg
def sync(self): ''' Synchronize Helm with the Armada Config(s) ''' msg = {'install': [], 'upgrade': [], 'diff': []} # TODO: (gardlt) we need to break up this func into # a more cleaner format self.pre_flight_ops() # extract known charts on tiller right now known_releases = self.tiller.list_charts() manifest_data = self.manifest.get(KEYWORD_ARMADA, {}) prefix = manifest_data.get(KEYWORD_PREFIX, '') for chartgroup in manifest_data.get(KEYWORD_GROUPS, []): cg_name = chartgroup.get('name', '<missing name>') cg_desc = chartgroup.get('description', '<missing description>') LOG.info('Processing ChartGroup: %s (%s)', cg_name, cg_desc) cg_sequenced = chartgroup.get('sequenced', False) cg_test_all_charts = chartgroup.get('test_charts', False) namespaces_seen = set() tests_to_run = [] cg_charts = chartgroup.get(KEYWORD_CHARTS, []) # Track largest Chart timeout to stop the ChartGroup at the end cg_max_timeout = 0 for chart_entry in cg_charts: chart = chart_entry.get('chart', {}) namespace = chart.get('namespace') release = chart.get('release') values = chart.get('values', {}) pre_actions = {} post_actions = {} wait_timeout = self.timeout wait_labels = {} release_name = release_prefix(prefix, release) # Retrieve appropriate timeout value if wait_timeout <= 0: # TODO(MarshM): chart's `data.timeout` should be deprecated chart_timeout = chart.get('timeout', 0) # Favor data.wait.timeout over data.timeout, until removed wait_values = chart.get('wait', {}) wait_timeout = wait_values.get('timeout', chart_timeout) wait_labels = wait_values.get('labels', {}) this_chart_should_wait = (cg_sequenced or self.force_wait or wait_timeout > 0 or len(wait_labels) > 0) if this_chart_should_wait and wait_timeout <= 0: LOG.warn('No Chart timeout specified, using default: %ss', DEFAULT_CHART_TIMEOUT) wait_timeout = DEFAULT_CHART_TIMEOUT # Track namespaces + labels touched namespaces_seen.add((namespace, tuple(wait_labels.items()))) # Naively take largest timeout to apply at end # TODO(MarshM) better handling of timeout/timer cg_max_timeout = max(wait_timeout, cg_max_timeout) # Chart test policy can override ChartGroup, if specified test_this_chart = chart.get('test', cg_test_all_charts) chartbuilder = ChartBuilder(chart) protoc_chart = chartbuilder.get_helm_chart() deployed_releases = [x[0] for x in known_releases] # Begin Chart timeout deadline deadline = time.time() + wait_timeout # TODO(mark-burnett): It may be more robust to directly call # tiller status to decide whether to install/upgrade rather # than checking for list membership. if release_name in deployed_releases: # indicate to the end user what path we are taking LOG.info("Upgrading release %s in namespace %s", release_name, namespace) # extract the installed chart and installed values from the # latest release so we can compare to the intended state apply_chart, apply_values = self.find_release_chart( known_releases, release_name) upgrade = chart.get('upgrade', {}) disable_hooks = upgrade.get('no_hooks', False) LOG.info("Checking Pre/Post Actions") if upgrade: upgrade_pre = upgrade.get('pre', {}) upgrade_post = upgrade.get('post', {}) if not self.disable_update_pre and upgrade_pre: pre_actions = upgrade_pre if not self.disable_update_post and upgrade_post: post_actions = upgrade_post # Show delta for both the chart templates and the chart # values # TODO(alanmeadows) account for .files differences # once we support those LOG.info('Checking upgrade chart diffs.') upgrade_diff = self.show_diff(chart, apply_chart, apply_values, chartbuilder.dump(), values, msg) if not upgrade_diff: LOG.info("There are no updates found in this chart") continue # TODO(MarshM): Add tiller dry-run before upgrade and # consider deadline impacts # do actual update timer = int(round(deadline - time.time())) LOG.info('Beginning Upgrade, wait=%s, timeout=%ss', this_chart_should_wait, timer) tiller_result = self.tiller.update_release( protoc_chart, release_name, namespace, pre_actions=pre_actions, post_actions=post_actions, dry_run=self.dry_run, disable_hooks=disable_hooks, values=yaml.safe_dump(values), wait=this_chart_should_wait, timeout=timer) if this_chart_should_wait: self.tiller.k8s.wait_until_ready( release=release_name, labels=wait_labels, namespace=namespace, k8s_wait_attempts=self.k8s_wait_attempts, k8s_wait_attempt_sleep=self.k8s_wait_attempt_sleep, timeout=timer) LOG.info('Upgrade completed with results from Tiller: %s', tiller_result.__dict__) msg['upgrade'].append(release_name) # process install else: LOG.info("Installing release %s in namespace %s", release_name, namespace) timer = int(round(deadline - time.time())) LOG.info('Beginning Install, wait=%s, timeout=%ss', this_chart_should_wait, timer) tiller_result = self.tiller.install_release( protoc_chart, release_name, namespace, dry_run=self.dry_run, values=yaml.safe_dump(values), wait=this_chart_should_wait, timeout=timer) if this_chart_should_wait: self.tiller.k8s.wait_until_ready( release=release_name, labels=wait_labels, namespace=namespace, k8s_wait_attempts=self.k8s_wait_attempts, k8s_wait_attempt_sleep=self.k8s_wait_attempt_sleep, timeout=timer) LOG.info('Install completed with results from Tiller: %s', tiller_result.__dict__) msg['install'].append(release_name) # Sequenced ChartGroup should run tests after each Chart timer = int(round(deadline - time.time())) if test_this_chart and cg_sequenced: LOG.info('Running sequenced test, timeout remaining: %ss.', timer) if timer <= 0: reason = ('Timeout expired before testing sequenced ' 'release %s' % release_name) LOG.error(reason) raise ArmadaTimeoutException(reason) self._test_chart(release_name, timer) # Un-sequenced ChartGroup should run tests at the end elif test_this_chart: # Keeping track of time remaining tests_to_run.append((release_name, timer)) # End of Charts in ChartGroup LOG.info('All Charts applied.') # After all Charts are applied, we should wait for the entire # ChartGroup to become healthy by looking at the namespaces seen # TODO(MarshM): Need to restrict to only releases we processed # TODO(MarshM): Need to determine a better timeout # (not cg_max_timeout) if cg_max_timeout <= 0: cg_max_timeout = DEFAULT_CHART_TIMEOUT deadline = time.time() + cg_max_timeout for (ns, labels) in namespaces_seen: labels_dict = dict(labels) timer = int(round(deadline - time.time())) LOG.info( 'Final wait for healthy namespace (%s), label=(%s), ' 'timeout remaining: %ss.', ns, labels_dict, timer) if timer <= 0: reason = ('Timeout expired waiting on namespace: %s, ' 'label: %s' % (ns, labels_dict)) LOG.error(reason) raise ArmadaTimeoutException(reason) self.tiller.k8s.wait_until_ready( namespace=ns, labels=labels_dict, k8s_wait_attempts=self.k8s_wait_attempts, k8s_wait_attempt_sleep=self.k8s_wait_attempt_sleep, timeout=timer) # After entire ChartGroup is healthy, run any pending tests for (test, test_timer) in tests_to_run: self._test_chart(test, test_timer) LOG.info("Performing Post-Flight Operations") self.post_flight_ops() if self.enable_chart_cleanup: self.tiller.chart_cleanup( prefix, self.manifest[KEYWORD_ARMADA][KEYWORD_GROUPS]) return msg