Support builtin chart dependencies

This adds support for using the same builtin chart dependencies [0]
as the Helm CLI would use.

[0]: https://helm.sh/docs/developing_charts/#chart-dependencies

Change-Id: Ifc541dc273fa2a5c5b4e43125f468ea3fdb0f379
This commit is contained in:
Sean Eagan 2019-08-15 16:53:43 -05:00
parent 77deecc294
commit f502f33e9f
8 changed files with 82 additions and 63 deletions

View File

@ -76,7 +76,7 @@ class ChartDeploy(object):
# Begin Chart timeout deadline
deadline = time.time() + chart_wait.get_timeout()
chartbuilder = ChartBuilder(ch)
chartbuilder = ChartBuilder.from_chart(ch)
new_chart = chartbuilder.get_helm_chart()
# TODO(mark-burnett): It may be more robust to directly call

View File

@ -13,6 +13,7 @@
# limitations under the License.
import os
import re
from google.protobuf.any_pb2 import Any
from hapi.chart.chart_pb2 import Chart
@ -37,38 +38,51 @@ class ChartBuilder(object):
into proper ``protoc`` Helm charts that can be pushed to Tiller.
'''
def __init__(self, chart):
'''Initialize the :class:`ChartBuilder` class.
@classmethod
def from_chart(cls, chart):
name = chart['metadata']['name']
chart_data = chart[const.KEYWORD_DATA]
source_dir = chart_data.get('source_dir')
source_directory = os.path.join(*source_dir)
dependencies = chart_data.get('dependencies')
dependency_builders = None
if dependencies:
dependency_builders = []
for chart_dep in dependencies:
builder = ChartBuilder.from_chart(chart_dep)
dependency_builders.append(builder)
:param dict chart: The document containing all intentions to pass to
Tiller.
'''
return cls(name, source_directory, dependency_builders)
def __init__(self, name, source_directory, dependency_builders=None):
self.name = name
self.source_directory = source_directory
if dependency_builders is None:
dependency_builders = []
charts_dir = os.path.join(source_directory, 'charts')
if os.path.isdir(charts_dir):
for f in os.scandir(charts_dir):
if not f.is_dir():
# TODO: Support ".tgz" dependency charts.
# Ignore regular files.
continue
# Ignore directories that start with "." or "_".
if re.match(r'^[._]', f.name):
continue
source_directory = os.path.join(charts_dir, f.name)
builder = ChartBuilder(f.name, source_directory)
dependency_builders.append(builder)
self.dependency_builders = dependency_builders
# cache for generated protoc chart object
self._helm_chart = None
# store chart schema
self.chart = chart
self.chart_data = chart[const.KEYWORD_DATA]
# extract, pull, whatever the chart from its source
self.source_directory = self.get_source_path()
# load ignored files from .helmignore if present
self.ignored_files = self.get_ignored_files()
def get_source_path(self):
'''Return the joined path of the source directory and subpath.
Returns "<source directory>/<subpath>" taken from the "source_dir"
property from the chart, or else "" if the property isn't a 2-tuple.
'''
source_dir = self.chart_data.get('source_dir')
return (
os.path.join(*source_dir) if (
source_dir and isinstance(source_dir, (list, tuple))
and len(source_dir) == 2) else "")
def get_ignored_files(self):
'''Load files to ignore from .helmignore if present.'''
try:
@ -209,7 +223,7 @@ class ChartBuilder(object):
Process all files in templates/ as a template to attach to the chart,
building a :class:`hapi.chart.template_pb2.Template` object.
'''
chart_name = self.chart['metadata']['name']
chart_name = self.name
templates = []
if not os.path.exists(os.path.join(self.source_directory,
'templates')):
@ -238,22 +252,21 @@ class ChartBuilder(object):
Constructs a :class:`hapi.chart.chart_pb2.Chart` object from the
``chart`` intentions, including all dependencies.
'''
if self._helm_chart:
return self._helm_chart
if not self._helm_chart:
self._helm_chart = self._get_helm_chart()
return self._helm_chart
def _get_helm_chart(self):
dependencies = []
chart_dependencies = self.chart_data.get('dependencies', [])
chart_name = self.chart['metadata']['name']
chart_release = self.chart_data.get('release', None)
for dep_chart in chart_dependencies:
dep_chart_name = dep_chart['metadata']['name']
for dep_builder in self.dependency_builders:
LOG.info(
"Building dependency chart %s for release %s.", dep_chart_name,
chart_release)
"Building dependency chart %s for chart %s.", dep_builder.name,
self.name)
try:
dependencies.append(ChartBuilder(dep_chart).get_helm_chart())
dependencies.append(dep_builder.get_helm_chart())
except Exception:
raise chartbuilder_exceptions.DependencyException(chart_name)
raise chartbuilder_exceptions.DependencyException(self.name)
try:
helm_chart = Chart(
@ -264,9 +277,8 @@ class ChartBuilder(object):
files=self.get_files())
except Exception as e:
raise chartbuilder_exceptions.HelmChartBuildException(
chart_name, details=e)
self.name, details=e)
self._helm_chart = helm_chart
return helm_chart
def dump(self):

View File

@ -169,7 +169,6 @@ data:
- no_hooks
additionalProperties: false
required:
- dependencies
- namespace
- chart_name
- release

View File

@ -366,7 +366,7 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase):
@mock.patch.object(armada.Armada, 'post_flight_ops')
@mock.patch.object(armada.Armada, 'pre_flight_ops')
@mock.patch('armada.handlers.chart_deploy.ChartBuilder')
@mock.patch('armada.handlers.chart_deploy.ChartBuilder.from_chart')
@mock.patch('armada.handlers.chart_deploy.Test')
def _do_test(
mock_test, mock_chartbuilder, mock_pre_flight,
@ -399,7 +399,6 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase):
mock_test_release.return_value = test_success
# Stub out irrelevant methods called by `armada.sync()`.
mock_chartbuilder.get_source_path.return_value = None
mock_chartbuilder.get_helm_chart.return_value = None
# Simulate chart diff, upgrade should only happen if non-empty.

View File

@ -145,7 +145,7 @@ class ChartBuilderTestCase(BaseChartBuilderTestCase):
self._write_temporary_file_contents(
chart_dir.path, 'Chart.yaml', self.chart_yaml)
chartbuilder = ChartBuilder(self._get_test_chart(chart_dir))
chartbuilder = ChartBuilder.from_chart(self._get_test_chart(chart_dir))
# Validate response type is :class:`hapi.chart.metadata_pb2.Metadata`
resp = chartbuilder.get_metadata()
@ -155,7 +155,7 @@ class ChartBuilderTestCase(BaseChartBuilderTestCase):
chart_dir = self.useFixture(fixtures.TempDir())
self.addCleanup(shutil.rmtree, chart_dir.path)
chartbuilder = ChartBuilder(self._get_test_chart(chart_dir))
chartbuilder = ChartBuilder.from_chart(self._get_test_chart(chart_dir))
self.assertRaises(
chartbuilder_exceptions.MetadataLoadException,
@ -181,7 +181,7 @@ class ChartBuilderTestCase(BaseChartBuilderTestCase):
for filename in ['template%d' % x for x in range(3)]:
self._write_temporary_file_contents(templates_subdir, filename, "")
chartbuilder = ChartBuilder(self._get_test_chart(chart_dir))
chartbuilder = ChartBuilder.from_chart(self._get_test_chart(chart_dir))
expected_files = (
'[type_url: "%s"\n, type_url: "%s"\n]' % ('./bar', './foo'))
@ -197,7 +197,7 @@ class ChartBuilderTestCase(BaseChartBuilderTestCase):
self._write_temporary_file_contents(
chart_dir.path, filename, "DIRC^@^@^@^B^@^@^@×Z®<86>F.1")
chartbuilder = ChartBuilder(self._get_test_chart(chart_dir))
chartbuilder = ChartBuilder.from_chart(self._get_test_chart(chart_dir))
chartbuilder.get_files()
def test_get_basic_helm_chart(self):
@ -212,7 +212,7 @@ class ChartBuilderTestCase(BaseChartBuilderTestCase):
ch['data']['source_dir'] = (chart_dir.path, '')
test_chart = ch
chartbuilder = ChartBuilder(test_chart)
chartbuilder = ChartBuilder.from_chart(test_chart)
helm_chart = chartbuilder.get_helm_chart()
expected = inspect.cleandoc(
@ -244,7 +244,7 @@ class ChartBuilderTestCase(BaseChartBuilderTestCase):
ch['data']['source_dir'] = (chart_dir.path, '')
test_chart = ch
chartbuilder = ChartBuilder(test_chart)
chartbuilder = ChartBuilder.from_chart(test_chart)
helm_chart = chartbuilder.get_helm_chart()
self.assertIsInstance(helm_chart, Chart)
@ -273,7 +273,7 @@ class ChartBuilderTestCase(BaseChartBuilderTestCase):
ch['data']['source_dir'] = (chart_dir.path, '')
test_chart = ch
chartbuilder = ChartBuilder(test_chart)
chartbuilder = ChartBuilder.from_chart(test_chart)
helm_chart = chartbuilder.get_helm_chart()
expected_files = (
@ -315,14 +315,14 @@ class ChartBuilderTestCase(BaseChartBuilderTestCase):
# 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, "")
self._write_temporary_file_contents(
charts_nested_subdir, 'Chart.yaml', self.chart_yaml)
# Files to **include** within charts/ subdirectory.
self._write_temporary_file_contents(charts_subdir, '.prov', "xyzzy")
@ -330,7 +330,7 @@ class ChartBuilderTestCase(BaseChartBuilderTestCase):
ch['data']['source_dir'] = (chart_dir.path, '')
test_chart = ch
chartbuilder = ChartBuilder(test_chart)
chartbuilder = ChartBuilder.from_chart(test_chart)
helm_chart = chartbuilder.get_helm_chart()
expected_files = (
@ -369,7 +369,7 @@ class ChartBuilderTestCase(BaseChartBuilderTestCase):
dependency_chart = dep_ch
main_chart['data']['dependencies'] = [dependency_chart]
chartbuilder = ChartBuilder(main_chart)
chartbuilder = ChartBuilder.from_chart(main_chart)
helm_chart = chartbuilder.get_helm_chart()
expected_dependency = inspect.cleandoc(
@ -429,7 +429,7 @@ class ChartBuilderTestCase(BaseChartBuilderTestCase):
ch['data']['source_dir'] = (chart_dir.path, '')
test_chart = ch
chartbuilder = ChartBuilder(test_chart)
chartbuilder = ChartBuilder.from_chart(test_chart)
self.assertRegex(
repr(chartbuilder.dump()),
'hello-world-chart.*A sample Helm chart for Kubernetes.*')
@ -444,7 +444,7 @@ class ChartBuilderTestCase(BaseChartBuilderTestCase):
dependency_chart = dep_ch
test_chart['data']['dependencies'] = [dependency_chart]
chartbuilder = ChartBuilder(test_chart)
chartbuilder = ChartBuilder.from_chart(test_chart)
re = inspect.cleandoc(
"""
@ -473,7 +473,7 @@ class ChartBuilderNegativeTestCase(BaseChartBuilderTestCase):
self._write_temporary_file_contents(
chart_dir.path, filename, "DIRC^@^@^@^B^@^@^@×Z®<86>F.1")
chartbuilder = ChartBuilder(self._get_test_chart(chart_dir))
chartbuilder = ChartBuilder.from_chart(self._get_test_chart(chart_dir))
# Confirm it failed for both encodings.
error_re = (
@ -494,7 +494,7 @@ class ChartBuilderNegativeTestCase(BaseChartBuilderTestCase):
self._write_temporary_file_contents(
chart_dir.path, filename, "DIRC^@^@^@^B^@^@^@×Z®<86>F.1")
chartbuilder = ChartBuilder(self._get_test_chart(chart_dir))
chartbuilder = ChartBuilder.from_chart(self._get_test_chart(chart_dir))
side_effects = [self.exc_to_raise, "", ""]
with mock.patch("builtins.open", mock.mock_open(read_data="")) \

View File

@ -52,9 +52,9 @@ Chart
| ``upgrade.options.no_hooks``, | |
| and now optional | |
+--------------------------------+------------------------------------------------------------+
| ``dependencies``, | Remove as desired. |
| ``source.subpath`` | |
| now optional | |
| ``source.subpath`` | Remove as desired. |
| now optional, deafults to no | |
| subpath. | |
+--------------------------------+------------------------------------------------------------+
| ``wait`` improvements | See `Wait Improvements`_. |
+--------------------------------+------------------------------------------------------------+

View File

@ -134,7 +134,9 @@ Chart
+-----------------+----------+---------------------------------------------------------------------------------------+
| source | object | provide a path to a ``git repo``, ``local dir``, or ``tarball url`` chart |
+-----------------+----------+---------------------------------------------------------------------------------------+
| dependencies | object | reference any chart dependencies before install |
| dependencies | object | (optional) reference any chart dependencies before install. |
| | | Defaults to using the standard `Builtin Chart Dependencies`_ as the Helm CLI does. |
| | | NOTE: Dependencies stored as ".tgz" archives are not yet supported. |
+-----------------+----------+---------------------------------------------------------------------------------------+
| timeout | int | time (in seconds) allotted for chart to deploy when 'wait' flag is set (DEPRECATED) |
+-----------------+----------+---------------------------------------------------------------------------------------+
@ -589,3 +591,5 @@ References
For working examples please check the examples in our repo
`here <https://opendev.org/airship/armada/src/branch/master/examples>`__.
.. _Builtin Chart Dependencies: https://helm.sh/docs/developing_charts/#chart-dependencies

View File

@ -121,7 +121,9 @@ Chart
+-----------------+----------+---------------------------------------------------------------------------------------+
| source | object | provide a path to a ``git repo``, ``local dir``, or ``tarball url`` chart |
+-----------------+----------+---------------------------------------------------------------------------------------+
| dependencies | object | (optional) reference any chart dependencies before install |
| dependencies | object | (optional) reference any chart dependencies before install. |
| | | Defaults to using the standard `Builtin Chart Dependencies`_ as the Helm CLI does. |
| | | NOTE: Dependencies stored as ".tgz" archives are not yet supported. |
+-----------------+----------+---------------------------------------------------------------------------------------+
.. _wait_v2:
@ -574,4 +576,7 @@ References
~~~~~~~~~~
For working examples please check the examples in our repo
`here <https://opendev.org/airship/armada/src/branch/master/examples>`__.
`here <https://github.com/openstack/airship-armada/tree/master/examples>`__
.. _Builtin Chart Dependencies: https://helm.sh/docs/developing_charts/#chart-dependencies