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)['chart'] ch['source_dir'] = (chart_dir.path, '') test_chart = dotify(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['chart']['source_dir'] = (dep_chart_dir.path, '') dependency_chart = dotify(dep_ch) test_chart.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 test_get_basic_helm_chart(self): # Before ChartBuilder is executed the `source_dir` points to a # directory that was either clone or unpacked from a tarball... pretend # that that logic has already been performed. 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)['chart'] ch['source_dir'] = (chart_dir.path, '') test_chart = dotify(ch) chartbuilder = ChartBuilder(test_chart) helm_chart = chartbuilder.get_helm_chart() expected = inspect.cleandoc(""" metadata { name: "hello-world-chart" version: "0.1.0" description: "A sample Helm chart for Kubernetes" } values { } """).strip() self.assertIsInstance(helm_chart, Chart) self.assertTrue(hasattr(helm_chart, 'metadata')) self.assertTrue(hasattr(helm_chart, 'values')) self.assertEqual(expected, repr(helm_chart).strip())
def test_get_helm_chart_with_files(self): # Create a chart directory with some test files. chart_dir = self.useFixture(fixtures.TempDir()) self.addCleanup(shutil.rmtree, chart_dir.path) # Chart.yaml is mandatory for `ChartBuilder.get_metadata`. self._write_temporary_file_contents(chart_dir.path, 'Chart.yaml', self.chart_yaml) self._write_temporary_file_contents(chart_dir.path, 'foo', "foobar") self._write_temporary_file_contents(chart_dir.path, 'bar', "bazqux") # Also create a nested directory and verify that files from it are also # added. nested_dir = self._make_temporary_subdirectory(chart_dir.path, 'nested') self._write_temporary_file_contents(nested_dir, 'nested0', "random") ch = yaml.safe_load(self.chart_stream)['chart'] ch['source_dir'] = (chart_dir.path, '') test_chart = dotify(ch) chartbuilder = ChartBuilder(test_chart) helm_chart = chartbuilder.get_helm_chart() expected_files = ('[type_url: "%s"\nvalue: "bazqux"\n, ' 'type_url: "%s"\nvalue: "foobar"\n, ' 'type_url: "%s"\nvalue: "random"\n]' % ('./bar', './foo', 'nested/nested0')) self.assertIsInstance(helm_chart, Chart) self.assertTrue(hasattr(helm_chart, 'metadata')) self.assertTrue(hasattr(helm_chart, 'values')) self.assertTrue(hasattr(helm_chart, 'files')) actual_files = sorted(helm_chart.files, key=lambda x: x.value) self.assertEqual(expected_files, repr(actual_files).strip())
def test_update_release(self, _0, _1, mock_release_service_stub): mock_release_service_stub.UpdateRelease.return_value = True mock_release_service_stub.GetReleaseStatus.return_value = dotify( {'namespace': 'testing'}) t = tiller.Tiller('test').update_release('foo', '', install=True) tiller.Tiller._logger.warn.assert_called() self.assertTrue(t)
def _http(self, verb, data=None, **kwargs): url = self.build_url(verb, **kwargs) response = self.api._http(verb, url, data=data) if response.content: return dotify(response.json()) else: return None
def __init__(self, chart, values_files, parent=None): ''' Initialize the ChartBuilder class Note that tthis will trigger a source pull as part of initialization as its necessary in order to examine the source service many of the calls on ChartBuilder ''' # cache for generated protoc chart object self._helm_chart = None # record whether this is a dependency based chart self.parent = parent # store chart schema self.chart = dotify(chart) # extract, pull, whatever the chart from its source self.source_directory = self.source_clone() # n.b.: these are later referred to as `overrides` # but they are what replace values in the chart/values.yaml # when handled by `Tiller` self.values_files = values_files
def test_dot_list_access(): """Test that we can access items in a ``DotList`` using dot notation""" dl = dotify(['fred', 'alex', 'bill']) assert_equals('fred', dl[0]) assert_equals('fred', dl._0) assert_equals('bill', dl[2]) assert_equals('bill', dl._2)
def test_get_helm_chart_includes_only_relevant_files(self): chart_dir = self.useFixture(fixtures.TempDir()) self.addCleanup(shutil.rmtree, chart_dir.path) templates_subdir = self._make_temporary_subdirectory( chart_dir.path, 'templates') charts_subdir = self._make_temporary_subdirectory( chart_dir.path, 'charts') templates_nested_subdir = self._make_temporary_subdirectory( templates_subdir, 'bin') charts_nested_subdir = self._make_temporary_subdirectory( charts_subdir, 'extra') self._write_temporary_file_contents(chart_dir.path, 'Chart.yaml', self.chart_yaml) self._write_temporary_file_contents(chart_dir.path, 'foo', "foobar") self._write_temporary_file_contents(chart_dir.path, 'bar', "bazqux") # Files to ignore within top-level directory. files_to_ignore = ['Chart.yaml', 'values.yaml', 'values.toml'] for file in files_to_ignore: self._write_temporary_file_contents(chart_dir.path, file, "") file_to_ignore = 'file_to_ignore' # Files to ignore within templates/ subdirectory. self._write_temporary_file_contents( templates_subdir, file_to_ignore, "") # Files to ignore within charts/ subdirectory. self._write_temporary_file_contents( charts_subdir, file_to_ignore, "") # Files to ignore within templates/bin subdirectory. self._write_temporary_file_contents( templates_nested_subdir, file_to_ignore, "") # Files to ignore within charts/extra subdirectory. self._write_temporary_file_contents( charts_nested_subdir, file_to_ignore, "") # Files to **include** within charts/ subdirectory. self._write_temporary_file_contents( charts_subdir, '.prov', "xyzzy") ch = yaml.safe_load(self.chart_stream)['chart'] ch['source_dir'] = (chart_dir.path, '') test_chart = dotify(ch) chartbuilder = ChartBuilder(test_chart) helm_chart = chartbuilder.get_helm_chart() expected_files = ('[type_url: "%s"\nvalue: "bazqux"\n, ' 'type_url: "%s"\nvalue: "foobar"\n, ' 'type_url: "%s"\nvalue: "xyzzy"\n]' % ('./bar', './foo', 'charts/.prov')) # Validate that only relevant files are included, that the ignored # files are present. self.assertIsInstance(helm_chart, Chart) self.assertTrue(hasattr(helm_chart, 'metadata')) self.assertTrue(hasattr(helm_chart, 'values')) self.assertTrue(hasattr(helm_chart, 'files')) actual_files = sorted(helm_chart.files, key=lambda x: x.value) self.assertEqual(expected_files, repr(actual_files).strip())
def test_update_release(self, _0, _1, mock_release_service_stub): mock_release_service_stub.UpdateRelease.return_value = True mock_release_service_stub.GetReleaseStatus.return_value = dotify( {"namespace": "testing"} ) t = tiller.Tiller("test").update_release("foo", "", install=True) tiller.Tiller._logger.warn.assert_called() self.assertTrue(t)
def test_chart_cleanup_no_releases(self, _0, mock_list, mock_uninstall): mock_list.return_value = [dotify({"name": "test-baz"})] tiller.Tiller("test").chart_cleanup( "test", [{"chart": {"release_name": "foo"}}, {"chart": {"release_name": "bar"}},], ) tiller.Tiller._logger.debug.assert_called() mock_uninstall.assert_called_once_with("test-baz")
def test_chartbuilder_source_clone(self): chart = dotify(self.chart_stream) ChartBuilder.source_clone = mock.Mock(return_value='path') chartbuilder = ChartBuilder(chart) resp = getattr(chartbuilder, 'source_directory', None) self.assertIsNotNone(resp) self.assertIsInstance(resp, basestring)
def test_defining_inherited_classes_alters_mapping(): """ Test that we can safely define inherited classes and they will be used. """ class MySubClass(DotDict): pass d = dotify({'a': {'b': {'c': 3}}}) assert_true(isinstance(d, MySubClass)) assert_true(isinstance(d['a'], MySubClass)) assert_true(isinstance(d['a']['b'], MySubClass)) reset_mapping() # Confirm reset has worked d = dotify({'a': {'b': {'c': 3}}}) assert_false(isinstance(d, MySubClass)) assert_false(isinstance(d['a'], MySubClass)) assert_false(isinstance(d['a']['b'], MySubClass))
def test_can_still_assign_own_attributes(): """ Test that we can still assign our own attributes to a ``DotList``. """ dl = dotify([]) dl.mine = 12 dl._hidden = 13 assert_equals(dl, []) assert_equals(dl._hidden, 13) assert_equals(dl.mine, 12)
def test_list_releases(self, _0, mock_release_service_stub): mock_release_service_stub.return_value.ListReleases.return_value = [ dotify({ 'next': '', 'releases': ['foo'] }) ] r = tiller.Tiller('test').list_releases() mock_release_service_stub.return_value.ListReleases.assert_called() self.assertEquals(len(r), 1) self.assertEquals(r[0], 'foo')
def test_dot_dict_raises_key_error_on_missing_key(): """ Test the behaviour of accessing missing keys in ``DotDict`` objects. """ d = dotify({'key_one': '1'}) raised = False try: d.key_two except KeyError: raised = True assert_true(raised)
def test_chart_source_clone(self, mock_os, mock_dot): from supermutes.dot import dotify import yaml mock_dot.dotify.return_value = dotify(yaml.load(self.chart_stream)) mock_os.path.join.return_value = self.chart_stream ChartBuilder.source_clone = mock.Mock(return_value='path') chartbuilder = ChartBuilder(self.chart_stream) resp = chartbuilder.get_metadata() self.assertIsNotNone(resp) self.assertIsInstance(resp, basestring)
def setUp(self): self.all_manifests = [] self.deployment_manifests = [] self.ingress_manifests = [] with open(COMPILED_MANIFESTS_FILE, 'r') as stream: for i in list(yaml.load_all(stream, Loader=yaml.FullLoader)): self.all_manifests.append(dotify(i)) for manifest in self.all_manifests: if manifest.kind == "Deployment": self.deployment_manifests.append(manifest) elif manifest.kind == "Ingress": self.ingress_manifests.append(manifest)
def get_metadata(self): ''' Process metadata ''' # extract Chart.yaml to construct metadata with open(os.path.join(self.source_directory, 'Chart.yaml')) as fd: chart_yaml = dotify(yaml.load(fd.read())) # construct Metadata object return Metadata(description=chart_yaml.description, name=chart_yaml.name, version=chart_yaml.version)
def test_raises_if_assign_out_of_range(): """ Test that we get an exception when we insert out of range to a ``DotList``. """ dl = dotify(['fred', 'alex', 'bill']) raised = False try: dl._10 = 'fred' except IndexError: raised = True assert_true(raised)
def get_metadata(self): ''' Process metadata ''' # extract Chart.yaml to construct metadata chart_yaml = dotify( yaml.load( pathlib.Path(self.source_directory, 'Chart.yaml').read_text())) # construct Metadata object return Metadata(description=chart_yaml.description, name=chart_yaml.name, version=chart_yaml.version)
def get_metadata(self): ''' Process metadata ''' # extract Chart.yaml to construct metadata chart_yaml = dotify(yaml.load(open(os.path.join(self.source_directory, 'Chart.yaml')).read())) # construct Metadata object return Metadata( description=chart_yaml.description, name=chart_yaml.name, version=chart_yaml.version )
def test_list_charts(self, _0, mock_list_releases): mock_list_releases.return_value = [ dotify({ 'name': 'foo', 'version': '0.1.0', 'chart': 'bar', 'config': { 'raw': 'foo: bar' } }) ] charts = tiller.Tiller('test').list_charts() self.assertEquals(len(charts), 1) self.assertEquals(charts[0], ('foo', '0.1.0', 'bar', 'foo: bar'))
def test_list_charts(self, _0, mock_list_releases): mock_list_releases.return_value = [ dotify( { "name": "foo", "version": "0.1.0", "chart": "bar", "config": {"raw": "foo: bar"}, } ) ] charts = tiller.Tiller("test").list_charts() self.assertEqual(len(charts), 1) self.assertEqual(charts[0], ("foo", "0.1.0", "bar", "foo: bar"))
def test_chart_cleanup_no_releases(self, _0, mock_list, mock_uninstall): mock_list.return_value = [dotify({'name': 'test-baz'})] tiller.Tiller('test').chart_cleanup('test', [ { 'chart': { 'release_name': 'foo' } }, { 'chart': { 'release_name': 'bar' } }, ]) tiller.Tiller._logger.debug.assert_called() mock_uninstall.assert_called_once_with('test-baz')
def test_dot_dict_adding_dictionaries(): """ Test the behaviour of adding dictionaries to ``DotDict`` objects. """ d = dotify({'key_one': '1'}) # Test adding dictionaries d.key_two = {'sub_key_1': ['item1', 'item2']} assert_equals(d, { 'key_one': '1', 'key_two': { 'sub_key_1': [ 'item1', 'item2' ] } })
def get_metadata(self): ''' Process metadata ''' # extract Chart.yaml to construct metadata try: with open(os.path.join(self.source_directory, 'Chart.yaml')) as f: chart_yaml = dotify(yaml.safe_load(f.read().encode('utf-8'))) except Exception: raise chartbuilder_exceptions.MetadataLoadException() # construct Metadata object return Metadata(description=chart_yaml.description, name=chart_yaml.name, version=chart_yaml.version)
def test_list_releases_with_namespace(self, _0, mock_list_release_request, mock_release_service_stub): mock_release_service_stub.return_value.ListReleases.return_value = [ dotify({ 'next': '', 'releases': ['foo'] }) ] r = tiller.Tiller('test').list_releases(namespace="test") mock_list_release_request.assert_called_with( limit=tiller.RELEASE_LIMIT, offset=None, namespace="test", status_codes=[]) mock_release_service_stub.return_value.ListReleases.assert_called() self.assertEqual(len(r), 1) self.assertEqual(r[0], 'foo')
def test_list_releases_with_namespace( self, _0, mock_list_release_request, mock_release_service_stub ): mock_release_service_stub.return_value.ListReleases.return_value = [ dotify({"next": "", "releases": ["foo"]}) ] r = tiller.Tiller("test").list_releases(namespace="test") mock_list_release_request.assert_called_with( limit=tiller.RELEASE_LIMIT, offset=None, filter="", namespace="test", status_codes=[], ) mock_release_service_stub.return_value.ListReleases.assert_called() self.assertEqual(len(r), 1) self.assertEqual(r[0], "foo")
def get_metadata(self): '''Process metadata 获取chart的metadata数据 Args: Returns: 返回tiller所需的metadata对象 ''' # extract Chart.yaml to construct metadata chart_yaml = dotify( yaml.load( open(os.path.join(self.source_directory, 'Chart.yaml')).read())) # construct Metadata object return Metadata(description=chart_yaml.description, name=chart_yaml.name, version=chart_yaml.version)
def test_list_releases_with_status_codes( self, _0, mock_list_release_request, mock_release_service_stub ): mock_release_service_stub.return_value.ListReleases.return_value = [ dotify({"next": "", "releases": ["foo"]}) ] r = tiller.Tiller("test").list_releases(status_codes=["DEPLOYED", "FAILED"]) # See status code enum definition in hapi/status_pb2.py mock_list_release_request.assert_called_with( limit=tiller.RELEASE_LIMIT, offset=None, filter="", namespace="", status_codes=[1, 4], ) mock_release_service_stub.return_value.ListReleases.assert_called() self.assertEqual(len(r), 1) self.assertEqual(r[0], "foo")
def __init__(self, filename): """ Initialise an ABE mock from data. filename is expected to be the path to an ABE file. """ with open(filename, 'r') as f: data = json.load(f) # map JSON fields to attributes self.__dict__ = data # Make all example requests and reponses accessible via dot syntax # (e.g. mock["OK"].request.status) for key, value in self.examples.items(): # Add the request URL automatically if missing. self._feed_inherited_fields(value, 'request', ['url', 'method']) self.examples[key] = dotify(value)
def __init__(self, filename): """ Initialise an ABE mock from data. filename is expected to be the path to an ABE file. """ with open(filename, "r") as f: data = json.load(f) # map JSON fields to attributes self.__dict__ = data # Make all example requests and reponses accessible via dot syntax # (e.g. mock["OK"].request.status) for key, value in self.examples.items(): # Add the request URL automatically if missing. self._feed_inherited_fields(value, "request", ["url", "method"]) self.examples[key] = dotify(value)
def __init__(self, chart, parent=None): ''' Initialize the ChartBuilder class Note that tthis will trigger a source pull as part of initialization as its necessary in order to examine the source service many of the calls on ChartBuilder ''' # cache for generated protoc chart object self._helm_chart = None # record whether this is a dependency based chart self.parent = parent # store chart schema self.chart = dotify(chart) # extract, pull, whatever the chart from its source self.source_directory = self.source_clone()
def __init__(self, data): """ Initialise an ABE mock from data. """ if not isinstance(data, dict): msg = ('Instanciating an AbeMock by filename is deprecated and ' 'will be removed in an upcoming release. ' 'Use AbeMock.from_filename instead'.format(data)) warnings.warn(msg, DeprecationWarning) with open(data, 'r') as f: data = json.load(f) # map JSON fields to attributes self.__dict__ = data # Make all example requests and reponses accessible via dot syntax # (e.g. mock["OK"].request.status) for key, value in self.examples.items(): # Add the request URL automatically if missing. self._feed_inherited_fields(value, 'request', ['url', 'method']) self.examples[key] = dotify(value)
def test_combination_dot(): """ Test that we can always have dot behaviour down a stack of objects """ dd = dotify({ "fred": 1, "dict_of_lists": { 'g': ['2', '3', '4'] }, "list_of_dicts": [ {'id': 2}, ] }) assert_equals(1, dd.fred) assert_equals('4', dd.dict_of_lists.g._2) assert_equals(2, dd.list_of_dicts._0.id) dd.dict_of_lists.h = 'another' assert_equals('another', dd['dict_of_lists']['h']) dd.list_of_dicts._0.another_key = "fred" assert_equals('fred', dd['list_of_dicts'][0]['another_key']) assert_equals(2, dd['list_of_dicts'][0]['id'])
def test_dot_dict_equality(): """Test the equality behaviour of ``DotDict``.""" d = dotify({'key_one': '1'}) # Test equality with dict assert_equals(d, {'key_one': '1'})
def GET(self, url): response = self.api.GET(url) return dotify(response.json())
def sync(self): ''' 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") self.pre_flight_checks() else: LOG.info("Skipping Pre-Flight Checks") known_releases = self.tiller.list_charts() prefix = self.config.get('armada').get('release_prefix') for release in known_releases: LOG.debug("Release %s, Version %s found on tiller", release[0], release[1]) for entry in self.config['armada']['charts']: desc = entry.get('description', 'A Chart Group') chart_group = entry.get('chart_group', []) if entry.get('sequenced', False): self.wait = True LOG.info('Deploying: %s', desc) 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) if chart.release_name is None: continue # 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 chartbuilder = ChartBuilder(chart) protoc_chart = chartbuilder.get_helm_chart() # 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) 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'])
def test_dot_list_data_entry(): """Test that we can add items to a ``DotList`` using dot notation""" dl = dotify(['fred', 'alex', 'bill']) dl._2 = 'bob' assert_equals('bob', dl[2])