Airship 2 support features

Airship 2 is using Argo for workflow management, rather
than the builtin Armada workflow functionality. Hence, this
adds an apply_chart CLI command to apply a single chart at
a time, so that Argo can manage the higher level orchestration.

Airship 2 is also using kubernetes as opposed to Deckhand as the
document store. Hence this adds an ArmadaChart kubernetes CRD,
which can be consumed by the apply_chart CLI command. The chart
`dependencies` feature is intentionally not supported by the CRD,
as there are additional complexities to make that work, and ideally
this feature should be deprecated as charts should be building in
there dependencies before consumption by Armada.

Functional tests are included to excercise these features
against a minikube cluster.

Change-Id: I2bbed83d6d80091322a7e60b918a534188467239
This commit is contained in:
Sean Eagan 2019-12-06 16:02:03 -06:00
parent 268d7a3958
commit c75898cd6a
31 changed files with 1270 additions and 190 deletions

View File

@ -28,6 +28,7 @@
- armada-docker-build-gate-ubuntu_xenial
- armada-docker-build-gate-opensuse
- armada-airskiff-deploy
- armada-airship2-integration
gate:
jobs:
- openstack-tox-pep8
@ -126,6 +127,26 @@
- ^releasenotes/.*$
- ^swagger/.*$
- job:
name: armada-airship2-integration
nodeset: armada-single-node
description: |
Deploy basic airship2 integration example using submitted Armada changes.
timeout: 9600
voting: false
pre-run:
- tools/gate/playbooks/git-config.yaml
run: tools/gate/playbooks/airship2-integration.yaml
post-run: tools/gate/playbooks/debug-report.yaml
required-projects:
- airship/treasuremap
irrelevant-files:
- ^.*\.rst$
- ^doc/.*$
- ^examples/.*$
- ^releasenotes/.*$
- ^swagger/.*$
- job:
name: armada-docker-publish-ubuntu_bionic
timeout: 1800

207
armada/cli/apply_chart.py Normal file
View File

@ -0,0 +1,207 @@
# Copyright 2020 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.
import click
from oslo_config import cfg
import prometheus_client
import yaml
from armada.cli import CliAction
from armada.exceptions.source_exceptions import InvalidPathException
from armada.handlers import metrics
from armada.handlers.chart_deploy import ChartDeploy
from armada.handlers.chart_download import ChartDownload
from armada.handlers.document import ReferenceResolver
from armada.handlers.lock import lock_and_thread
from armada.handlers.manifest import Chart
from armada.handlers.tiller import Tiller
CONF = cfg.CONF
@click.group()
def apply_chart():
""" Apply chart to cluster
"""
DESC = """
This command installs and updates an Armada chart.
[LOCATION] must be a relative path to Armada Chart or a reference
to an Armada Chart kubernetes CR which has the same format, except as
noted in the v2 document authoring documentation.
To install or upgrade a chart, run:
\b
$ armada apply_chart --release-prefix=armada my-chart.yaml
$ armada apply_chart --release-prefix=armada \
kube:armadacharts/my-namespace/my-chart
"""
SHORT_DESC = "Command deploys a chart."
@apply_chart.command(name='apply_chart', help=DESC, short_help=SHORT_DESC)
@click.argument('location')
@click.option(
'--release-prefix',
help="Prefix to prepend to chart release name.",
required=True)
@click.option(
'--disable-update-post',
help="Disable post-update Tiller operations.",
is_flag=True)
@click.option(
'--disable-update-pre',
help="Disable pre-update Tiller operations.",
is_flag=True)
@click.option(
'--metrics-output',
help=(
"Output path for prometheus metric data, should end in .prom. By "
"default, no metric data is output."),
default=None)
@click.option('--tiller-host', help="Tiller host IP.", default=None)
@click.option(
'--tiller-port', help="Tiller host port.", type=int, default=None)
@click.option(
'--tiller-namespace',
'-tn',
help="Tiller namespace.",
type=str,
default=None)
@click.option(
'--timeout',
help="Specifies time to wait for each chart to fully "
"finish deploying.",
type=int)
@click.option(
'--wait',
help=(
"Force Tiller to wait until the chart is deployed, "
"rather than using the chart's specified wait policy. "
"This is equivalent to sequenced chartgroups."),
is_flag=True)
@click.option(
'--target-chart',
help=(
"The target chart to deploy. Required for specifying "
"which chart to deploy when multiple are available."),
default=None)
@click.option('--bearer-token', help="User Bearer token", default=None)
@click.option('--debug', help="Enable debug logging.", is_flag=True)
@click.pass_context
def apply_chart(
ctx, location, release_prefix, disable_update_post, disable_update_pre,
metrics_output, tiller_host, tiller_port, tiller_namespace, timeout,
wait, target_chart, bearer_token, debug):
CONF.debug = debug
ApplyChart(
ctx, location, release_prefix, disable_update_post, disable_update_pre,
metrics_output, tiller_host, tiller_port, tiller_namespace, timeout,
wait, target_chart, bearer_token).safe_invoke()
class ApplyChart(CliAction):
def __init__(
self, ctx, location, release_prefix, disable_update_post,
disable_update_pre, metrics_output, tiller_host, tiller_port,
tiller_namespace, timeout, wait, target_chart, bearer_token):
super(ApplyChart, self).__init__()
self.ctx = ctx
self.release_prefix = release_prefix
# Filename can also be a URL reference
self.location = location
self.disable_update_post = disable_update_post
self.disable_update_pre = disable_update_pre
self.metrics_output = metrics_output
self.tiller_host = tiller_host
self.tiller_port = tiller_port
self.tiller_namespace = tiller_namespace
self.timeout = timeout
self.target_chart = target_chart
self.bearer_token = bearer_token
def output(self, resp):
for result in resp:
if not resp[result] and not result == 'diff':
self.logger.info('Did not perform chart %s(s)', result)
elif result == 'diff' and not resp[result]:
self.logger.info('No release changes detected')
ch = resp[result]
if not result == 'diff':
msg = 'Chart {} took action: {}'.format(ch, result)
if result == 'protected':
msg += ' and requires operator attention.'
elif result == 'purge':
msg += ' before install/upgrade.'
self.logger.info(msg)
else:
self.logger.info('Chart/values diff: %s', ch)
def invoke(self):
with Tiller(tiller_host=self.tiller_host, tiller_port=self.tiller_port,
tiller_namespace=self.tiller_namespace,
bearer_token=self.bearer_token) as tiller:
try:
doc_data = ReferenceResolver.resolve_reference(
self.location, k8s=tiller.k8s)
documents = list()
for d in doc_data:
documents.extend(list(yaml.safe_load_all(d.decode())))
except InvalidPathException as ex:
self.logger.error(str(ex))
return
except yaml.YAMLError as yex:
self.logger.error("Invalid YAML found: %s" % str(yex))
return
try:
resp = self.handle(documents, tiller)
self.output(resp)
finally:
if self.metrics_output:
path = self.metrics_output
self.logger.info(
'Storing metrics output in path: {}'.format(path))
prometheus_client.write_to_textfile(path, metrics.REGISTRY)
def handle(self, documents, tiller):
chart = Chart(documents, target_chart=self.target_chart).get_chart()
lock_name = 'chart-{}'.format(chart['metadata']['name'])
@lock_and_thread(lock_name)
def _handle():
chart_download = ChartDownload()
try:
chart_download.get_chart(chart)
chart_deploy = ChartDeploy(
None, self.disable_update_pre, self.disable_update_post, 1,
1, self.timeout, tiller)
# TODO: Only get release with matching name.
known_releases = tiller.list_releases()
return chart_deploy.execute(
chart, None, self.release_prefix, known_releases, 1)
finally:
chart_download.cleanup()
return _handle()

View File

@ -21,15 +21,14 @@ from armada import const
from armada.conf import set_current_chart
from armada.exceptions import armada_exceptions
from armada.exceptions import override_exceptions
from armada.exceptions import source_exceptions
from armada.exceptions import tiller_exceptions
from armada.exceptions import validate_exceptions
from armada.handlers import metrics
from armada.handlers.chart_deploy import ChartDeploy
from armada.handlers.chart_download import ChartDownload
from armada.handlers.manifest import Manifest
from armada.handlers.override import Override
from armada.utils.release import release_prefixer
from armada.utils import source
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
@ -88,7 +87,7 @@ class Armada(object):
raise
self.manifest = Manifest(
self.documents, target_manifest=target_manifest).get_manifest()
self.chart_cache = {}
self.chart_download = ChartDownload()
self.chart_deploy = ChartDeploy(
self.manifest, disable_update_pre, disable_update_post,
k8s_wait_attempts, k8s_wait_attempt_sleep, timeout, self.tiller)
@ -108,71 +107,7 @@ class Armada(object):
for group in manifest_data.get(const.KEYWORD_GROUPS, []):
for ch in group.get(const.KEYWORD_DATA).get(const.KEYWORD_CHARTS,
[]):
self.get_chart(ch)
def get_chart(self, ch):
manifest_name = self.manifest['metadata']['name']
chart_name = ch['metadata']['name']
with metrics.CHART_DOWNLOAD.get_context(manifest_name, chart_name):
return self._get_chart(ch)
def _get_chart(self, ch):
chart = ch.get(const.KEYWORD_DATA)
chart_source = chart.get('source', {})
location = chart_source.get('location')
ct_type = chart_source.get('type')
subpath = chart_source.get('subpath', '.')
proxy_server = chart_source.get('proxy_server')
if ct_type == 'local':
chart['source_dir'] = (location, subpath)
elif ct_type == 'tar':
source_key = (ct_type, location)
if source_key not in self.chart_cache:
LOG.info(
"Downloading tarball from: %s / proxy %s", location,
proxy_server or "not set")
if not CONF.certs:
LOG.warn(
'Disabling server validation certs to extract charts')
tarball_dir = source.get_tarball(
location, verify=False, proxy_server=proxy_server)
else:
tarball_dir = source.get_tarball(
location, verify=CONF.certs, proxy_server=proxy_server)
self.chart_cache[source_key] = tarball_dir
chart['source_dir'] = (self.chart_cache.get(source_key), subpath)
elif ct_type == 'git':
reference = chart_source.get('reference', 'master')
source_key = (ct_type, location, reference)
if source_key not in self.chart_cache:
auth_method = chart_source.get('auth_method')
logstr = 'Cloning repo: {} from branch: {}'.format(
location, reference)
if proxy_server:
logstr += ' proxy: {}'.format(proxy_server)
if auth_method:
logstr += ' auth method: {}'.format(auth_method)
LOG.info(logstr)
repo_dir = source.git_clone(
location,
reference,
proxy_server=proxy_server,
auth_method=auth_method)
self.chart_cache[source_key] = repo_dir
chart['source_dir'] = (self.chart_cache.get(source_key), subpath)
else:
name = ch['metadata']['name']
raise source_exceptions.ChartSourceException(ct_type, name)
for dep in ch.get(const.KEYWORD_DATA, {}).get('dependencies', []):
self.get_chart(dep)
self.chart_download.get_chart(ch, manifest=self.manifest)
def sync(self):
'''
@ -285,10 +220,7 @@ class Armada(object):
'''
LOG.info("Performing post-flight operations.")
# Delete temp dirs used for deployment
for chart_dir in self.chart_cache.values():
LOG.debug('Removing temp chart directory: %s', chart_dir)
source.source_cleanup(chart_dir)
self.chart_download.cleanup()
def _chart_cleanup(self, prefix, chart_groups, msg):
LOG.info('Processing chart cleanup to remove unspecified releases.')

View File

@ -47,14 +47,16 @@ class ChartDeploy(object):
def execute(
self, ch, cg_test_all_charts, prefix, known_releases, concurrency):
chart_name = ch['metadata']['name']
manifest_name = self.manifest['metadata']['name']
manifest_name = self.manifest['metadata'][
'name'] if self.manifest else ''
with metrics.CHART_HANDLE.get_context(concurrency, manifest_name,
chart_name):
return self._execute(
ch, cg_test_all_charts, prefix, known_releases)
def _execute(self, ch, cg_test_all_charts, prefix, known_releases):
manifest_name = self.manifest['metadata']['name']
manifest_name = self.manifest['metadata'][
'name'] if self.manifest else ''
chart = ch[const.KEYWORD_DATA]
chart_name = ch['metadata']['name']
namespace = chart.get('namespace')

View File

@ -0,0 +1,104 @@
# Copyright 2020 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.
from oslo_config import cfg
from oslo_log import log as logging
from armada import const
from armada.exceptions import source_exceptions
from armada.handlers import metrics
from armada.utils import source
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class ChartDownload():
def __init__(self):
self.chart_cache = {}
def get_chart(self, ch, manifest=None):
manifest_name = manifest['metadata']['name'] if manifest else None
chart_name = ch['metadata']['name']
with metrics.CHART_DOWNLOAD.get_context(manifest_name, chart_name):
return self._get_chart(ch, manifest)
def _get_chart(self, ch, manifest):
chart = ch.get(const.KEYWORD_DATA)
chart_source = chart.get('source', {})
location = chart_source.get('location')
ct_type = chart_source.get('type')
subpath = chart_source.get('subpath', '.')
proxy_server = chart_source.get('proxy_server')
if ct_type == 'local':
chart['source_dir'] = (location, subpath)
elif ct_type == 'tar':
source_key = (ct_type, location)
if source_key not in self.chart_cache:
LOG.info(
"Downloading tarball from: %s / proxy %s", location,
proxy_server or "not set")
if not CONF.certs:
LOG.warn(
'Disabling server validation certs to extract charts')
tarball_dir = source.get_tarball(
location, verify=False, proxy_server=proxy_server)
else:
tarball_dir = source.get_tarball(
location, verify=CONF.certs, proxy_server=proxy_server)
self.chart_cache[source_key] = tarball_dir
chart['source_dir'] = (self.chart_cache.get(source_key), subpath)
elif ct_type == 'git':
reference = chart_source.get('reference', 'master')
source_key = (ct_type, location, reference)
if source_key not in self.chart_cache:
auth_method = chart_source.get('auth_method')
logstr = 'Cloning repo: {} from branch: {}'.format(
location, reference)
if proxy_server:
logstr += ' proxy: {}'.format(proxy_server)
if auth_method:
logstr += ' auth method: {}'.format(auth_method)
LOG.info(logstr)
repo_dir = source.git_clone(
location,
reference,
proxy_server=proxy_server,
auth_method=auth_method)
self.chart_cache[source_key] = repo_dir
chart['source_dir'] = (self.chart_cache.get(source_key), subpath)
else:
name = ch['metadata']['name']
raise source_exceptions.ChartSourceException(ct_type, name)
for dep in ch.get(const.KEYWORD_DATA, {}).get('dependencies', []):
self.get_chart(dep, manifest=manifest)
def cleanup(self):
'''
Operations to run after deployment process has terminated
'''
LOG.info("Performing post-flight operations.")
# Delete temp dirs used for deployment
for chart_dir in self.chart_cache.values():
LOG.debug('Removing temp chart directory: %s', chart_dir)
source.source_cleanup(chart_dir)

View File

@ -17,8 +17,10 @@
import urllib.parse
import re
import requests
from oslo_log import log as logging
import requests
import yaml
from kubernetes.client.rest import ApiException
from armada.exceptions.source_exceptions import InvalidPathException
from armada.utils import keystone as ks_utils
@ -30,7 +32,7 @@ class ReferenceResolver(object):
"""Class for handling different data references to resolve the data."""
@classmethod
def resolve_reference(cls, design_ref):
def resolve_reference(cls, design_ref, k8s=None):
"""Resolve a reference to a design document.
Locate a schema handler based on the URI scheme of the data reference
@ -51,10 +53,16 @@ class ReferenceResolver(object):
# when scheme is a empty string assume it is a local
# file path
if design_uri.scheme == '':
handler = cls.scheme_handlers.get('file')
else:
handler = cls.scheme_handlers.get(design_uri.scheme, None)
scheme = design_uri.scheme or 'file'
handler = cls.scheme_handlers.get(scheme, None)
handler = handler.__get__(None, cls)
if design_uri.scheme == 'kube':
handler_2 = handler
def handler_1(design_uri):
return handler_2(design_uri, k8s)
handler = handler_1
if handler is None:
raise InvalidPathException(
@ -71,6 +79,50 @@ class ReferenceResolver(object):
return data
@classmethod
def resolve_reference_kube(cls, design_uri, k8s):
"""Retrieve design documents from kubernetes crd.
Return the result of converting the CRD to the armada/Chart/v2 schema.
:param design_uri: Tuple as returned by urllib.parse
for the design reference
"""
if design_uri.path != '':
parts = design_uri.path.split('/')
if len(parts) != 3:
raise InvalidPathException(
"Invalid kubernetes custom resource path segment count {} "
"for '{}', expected <kind>/<namespace>/<name>".format(
len(parts), design_uri.path))
plural, namespace, name = parts
if plural != 'armadacharts':
raise InvalidPathException(
"Invalid kubernetes custom resource kind '{}' for '{}', "
"only 'armadacharts' are supported".format(
plural, design_uri.path))
try:
cr = k8s.read_custom_resource(
group='armada.airshipit.org',
version='v1alpha1',
namespace=namespace,
plural=plural,
name=name)
except ApiException as err:
if err.status == 404:
raise InvalidPathException(
"Kubernetes custom resource not found: plural='{}', "
"namespace='{}', name='{}', api exception=\n{}".format(
plural, namespace, name, err.message))
raise
cr['schema'] = 'armada/Chart/v2'
spec = cr.pop('spec')
cr['data'] = spec
return yaml.safe_dump(cr, encoding='utf-8')
@classmethod
def resolve_reference_http(cls, design_uri):
"""Retrieve design documents from http/https endpoints.
@ -134,6 +186,7 @@ class ReferenceResolver(object):
return resp.content
scheme_handlers = {
'kube': resolve_reference_kube,
'http': resolve_reference_http,
'file': resolve_reference_file,
'https': resolve_reference_http,

View File

@ -22,56 +22,12 @@ from armada.handlers import schema
LOG = logging.getLogger(__name__)
class Manifest(object):
def __init__(self, documents, target_manifest=None):
"""Instantiates a Manifest object.
An Armada Manifest expects that at least one of each of the following
be included in ``documents``:
* A document with schema "armada/Chart/v1"
* A document with schema "armada/ChartGroup/v1"
And only one document of the following is allowed:
* A document with schema "armada/Manifest/v1"
If multiple documents with schema "armada/Manifest/v1" are provided,
specify ``target_manifest`` to select the target one.
:param List[dict] documents: Documents out of which to build the
Armada Manifest.
:param str target_manifest: The target manifest to use when multiple
documents with "armada/Manifest/v1" are contained in
``documents``. Default is None.
:raises ManifestException: If the expected number of document types
are not found or if the document types are missing required
properties.
"""
class Doc(object):
def __init__(self, documents):
self.documents = deepcopy(documents)
self.charts, self.groups, manifests = self._find_documents(
target_manifest)
self.charts, self.groups, self.manifests = self._find_documents()
if len(manifests) > 1:
error = (
'Multiple manifests are not supported. Ensure that the '
'`target_manifest` option is set to specify the target '
'manifest')
LOG.error(error)
raise exceptions.ManifestException(details=error)
else:
self.manifest = manifests[0] if manifests else None
if not all([self.charts, self.groups, self.manifest]):
expected_schemas = [schema.TYPE_CHART, schema.TYPE_CHARTGROUP]
error = (
'Documents must include at least one of each of {} '
'and only one {}').format(
expected_schemas, schema.TYPE_MANIFEST)
LOG.error(error)
raise exceptions.ManifestException(details=error)
def _find_documents(self, target_manifest=None):
def _find_documents(self):
"""Returns the chart documents, chart group documents,
and Armada manifest
@ -97,14 +53,44 @@ class Manifest(object):
if schema_info.type == schema.TYPE_CHARTGROUP:
groups.append(document)
if schema_info.type == schema.TYPE_MANIFEST:
manifest_name = document.get('metadata', {}).get('name')
if target_manifest:
if manifest_name == target_manifest:
manifests.append(document)
else:
manifests.append(document)
manifests.append(document)
return charts, groups, manifests
def _get_target_doc(self, sch, documents, target, target_arg_name):
"""Validates there is exactly one document of a given schema and
optionally name and returns it.
:param sch: Schema which corresponds to `documents`.
:param documents: Documents which correspond to `sch`.
:param target: The target document name of schema `sch` to return.
Default is None.
:raises ManifestException: If `target` is None and multiple `documents`
are passed, or if no documents are found matching the parameters.
"""
candidates = []
for manifest in documents:
if target:
manifest_name = manifest.get('metadata', {}).get('name')
if manifest_name == target:
candidates.append(manifest)
else:
candidates.append(manifest)
if len(candidates) > 1:
error = (
'Multiple {} documents are not supported. Ensure that the '
'`{}` option is set to specify the target one').format(
sch, target_arg_name)
LOG.error(error)
raise exceptions.ManifestException(details=error)
if not candidates:
error = 'Documents must include at least one {}'.format(sch)
LOG.error(error)
raise exceptions.ManifestException(details=error)
return candidates[0]
def find_chart_document(self, name):
"""Returns a chart document with the specified name
@ -121,22 +107,6 @@ class Manifest(object):
details='Could not find {} named "{}"'.format(
schema.TYPE_CHART, name))
def find_chart_group_document(self, name):
"""Returns a chart group document with the specified name
:param str name: name of the desired chart group document
:returns: The requested chart group document
:rtype: dict
:raises ManifestException: If a chart
group document with the specified name is not found
"""
for group in self.groups:
if group.get('metadata', {}).get('name') == name:
return group
raise exceptions.BuildChartGroupException(
details='Could not find {} named "{}"'.format(
schema.TYPE_CHARTGROUP, name))
def build_chart_deps(self, chart):
"""Recursively build chart dependencies for ``chart``.
@ -164,6 +134,90 @@ class Manifest(object):
else:
return chart
class Chart(Doc):
def __init__(self, documents, target_chart=None):
"""A Chart expects the following be included in ``documents``:
* A document with schema "armada/Chart/v1"
If multiple Charts are provided, specify ``target_chart`` to select the
target one.
:param documents: Documents out of which to build the
Chart.
:param target_chart: The target Chart to use when multiple
Charts are contained in ``documents``. Default is None.
:raises ManifestException: If the expected number of document types
are not found or if the document types are missing required
properties.
"""
super(Chart, self).__init__(documents)
self.chart = self._get_target_doc(
schema.TYPE_CHART, self.documents, target_chart, 'target_chart')
def get_chart(self):
"""Builds the Chart
:returns: The Chart.
:rtype: dict
"""
self.build_chart_deps(self.chart)
return self.chart
class Manifest(Doc):
def __init__(self, documents, target_manifest=None):
"""An Armada Manifest expects that at least one of each of the following
be included in ``documents``:
* A document with schema "armada/Chart/v1"
* A document with schema "armada/ChartGroup/v1"
And only one document of the following is allowed:
* A document with schema "armada/Manifest/v1"
If multiple documents with schema "armada/Manifest/v1" are provided,
specify ``target_manifest`` to select the target one.
:param List[dict] documents: Documents out of which to build the
Armada Manifest.
:param str target_manifest: The target manifest to use when multiple
documents with "armada/Manifest/v1" are contained in
``documents``. Default is None.
:raises ManifestException: If the expected number of document types
are not found or if the document types are missing required
properties.
"""
super(Manifest, self).__init__(documents)
self.manifest = self._get_target_doc(
schema.TYPE_MANIFEST, self.manifests, target_manifest,
'target_manifest')
if not all([self.charts, self.groups]):
expected_schemas = [schema.TYPE_CHART, schema.TYPE_CHARTGROUP]
error = 'Documents must include at least one of each of {}'.format(
expected_schemas)
LOG.error(error)
raise exceptions.ManifestException(details=error)
def find_chart_group_document(self, name):
"""Returns a chart group document with the specified name
:param str name: name of the desired chart group document
:returns: The requested chart group document
:rtype: dict
:raises ManifestException: If a chart
group document with the specified name is not found
"""
for group in self.groups:
if group.get('metadata', {}).get('name') == name:
return group
raise exceptions.BuildChartGroupException(
details='Could not find {} named "{}"'.format(
schema.TYPE_CHARTGROUP, name))
def build_chart_group(self, chart_group):
"""Builds the chart dependencies for`charts`chart group``.

View File

@ -59,6 +59,9 @@ class ChartWait():
schema_info = get_schema_info(self.chart['schema'])
resources = self.wait_config.get('resources')
if not resources:
resources = self.wait_config.get('resources_list')
if isinstance(resources, list):
# Explicit resource config list provided.
resources_list = resources

View File

@ -19,6 +19,7 @@ from oslo_config import cfg
from oslo_log import log
from armada.cli.apply import apply_create
from armada.cli.apply_chart import apply_chart
from armada.cli.delete import delete_charts
from armada.cli.rollback import rollback_charts
from armada.cli.test import test_charts
@ -49,6 +50,7 @@ def main(ctx, debug, api, url, token):
\b
$ armada apply
$ armada apply_chart
$ armada delete
$ armada rollback
$ armada test
@ -88,6 +90,7 @@ def main(ctx, debug, api, url, token):
main.add_command(apply_create)
main.add_command(apply_chart)
main.add_command(delete_charts)
main.add_command(rollback_charts)
main.add_command(test_charts)

View File

@ -143,16 +143,21 @@ data:
enabled: true
"""
CHART_SOURCES = [
('git://opendev.org/dummy/armada.git', 'chart_1'),
('/tmp/dummy/armada', 'chart_2'), ('/tmp/dummy/armada', 'chart_3'),
('/tmp/dummy/armada', 'chart_4')
]
CHART_SOURCES = (
('git://opendev.org/dummy/armada.git',
'chart_1'), ('/tmp/dummy/armada', 'chart_2'),
('/tmp/dummy/armada', 'chart_3'), ('/tmp/dummy/armada', 'chart_4'))
# TODO(seaneagan): Add unit tests with dependencies, including transitive.
class ArmadaHandlerTestCase(base.ArmadaTestCase):
def _test_pre_flight_ops(self, armada_obj):
def _test_pre_flight_ops(self, armada_obj, MockChartDownload):
def set_source_dir(ch, manifest=None):
d = ch['data']
d['source_dir'] = (d['source']['location'], d['source']['subpath'])
MockChartDownload.return_value.get_chart.side_effect = set_source_dir
armada_obj.pre_flight_ops()
expected_config = {
@ -305,38 +310,30 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase):
self.assertIn('data', armada_obj.manifest)
self.assertEqual(expected_config, armada_obj.manifest)
@mock.patch.object(armada, 'source')
def test_pre_flight_ops(self, mock_source):
@mock.patch.object(armada, 'ChartDownload')
def test_pre_flight_ops(self, MockChartDownload):
"""Test pre-flight checks and operations."""
yaml_documents = list(yaml.safe_load_all(TEST_YAML))
m_tiller = mock.Mock()
m_tiller.tiller_status.return_value = True
armada_obj = armada.Armada(yaml_documents, m_tiller)
# Mock methods called by `pre_flight_ops()`.
mock_source.git_clone.return_value = CHART_SOURCES[0][0]
self._test_pre_flight_ops(armada_obj, MockChartDownload)
self._test_pre_flight_ops(armada_obj)
MockChartDownload.return_value.get_chart.assert_called()
mock_source.git_clone.assert_called_once_with(
'git://opendev.org/dummy/armada.git',
'master',
auth_method=None,
proxy_server=None)
@mock.patch.object(armada, 'source')
def test_post_flight_ops(self, mock_source):
@mock.patch.object(armada, 'ChartDownload')
def test_post_flight_ops(self, MockChartDownload):
"""Test post-flight operations."""
yaml_documents = list(yaml.safe_load_all(TEST_YAML))
# Mock methods called by `pre_flight_ops()`.
m_tiller = mock.Mock()
m_tiller.tiller_status.return_value = True
mock_source.git_clone.return_value = CHART_SOURCES[0][0]
armada_obj = armada.Armada(yaml_documents, m_tiller)
self._test_pre_flight_ops(armada_obj)
self._test_pre_flight_ops(armada_obj, MockChartDownload)
armada_obj.post_flight_ops()
@ -345,8 +342,7 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase):
const.KEYWORD_CHARTS)):
if chart.get(
const.KEYWORD_DATA).get('source').get('type') == 'git':
mock_source.source_cleanup.assert_called_with(
CHART_SOURCES[counter][0])
MockChartDownload.return_value.cleanup.assert_called_with()
# TODO(seaneagan): Separate ChartDeploy tests into separate module.
# TODO(seaneagan): Once able to make mock library sufficiently thread safe,
@ -671,19 +667,17 @@ class ArmadaHandlerTestCase(base.ArmadaTestCase):
class ArmadaNegativeHandlerTestCase(base.ArmadaTestCase):
@mock.patch.object(armada, 'source')
def test_armada_get_manifest_exception(self, mock_source):
@mock.patch.object(armada, 'ChartDownload')
def test_armada_get_manifest_exception(self, MockChartDownload):
"""Test armada handling with invalid manifest."""
yaml_documents = list(yaml.safe_load_all(TEST_YAML))
error_re = (
'.*Documents must include at least one of each of .* and '
'only one .*')
error_re = ('.*Documents must include at least one of each of .*')
self.assertRaisesRegexp(
ManifestException, error_re, armada.Armada, yaml_documents[:1],
mock.MagicMock())
@mock.patch.object(armada, 'source')
def test_armada_override_exception(self, mock_source):
@mock.patch.object(armada, 'ChartDownload')
def test_armada_override_exception(self, MockChartDownload):
"""Test Armada checks with invalid chart override."""
yaml_documents = list(yaml.safe_load_all(TEST_YAML))
override = ('chart:example-chart-2:name=' 'overridden', )
@ -692,8 +686,8 @@ class ArmadaNegativeHandlerTestCase(base.ArmadaTestCase):
with self.assertRaisesRegexp(InvalidOverrideValueException, error_re):
armada.Armada(yaml_documents, mock.MagicMock(), set_ovr=override)
@mock.patch.object(armada, 'source')
def test_armada_manifest_exception_override_none(self, mock_source):
@mock.patch.object(armada, 'ChartDownload')
def test_armada_manifest_exception_override_none(self, MockChartDownload):
"""Test Armada checks with invalid manifest."""
yaml_documents = list(yaml.safe_load_all(TEST_YAML))
example_document = [

View File

@ -15,7 +15,7 @@
import copy
import os
import testtools
import testtools
import yaml
from armada import exceptions
@ -117,7 +117,7 @@ class ManifestTestCase(testtools.TestCase):
def test_find_documents(self):
armada_manifest = manifest.Manifest(self.documents)
chart_documents, chart_groups, manifests = armada_manifest. \
_find_documents(target_manifest='armada-manifest')
_find_documents()
# checking if all the chart documents are present
self.assertIsInstance(chart_documents, list)
@ -344,7 +344,8 @@ class ManifestNegativeTestCase(testtools.TestCase):
documents = copy.deepcopy(self.documents)
documents.append(documents[-1]) # Copy the last manifest.
error_re = r'Multiple manifests are not supported.*'
error_re = r'Multiple {} documents are not supported.*'.format(
schema.TYPE_MANIFEST)
self.assertRaisesRegexp(
exceptions.ManifestException, error_re, manifest.Manifest,
documents)
@ -355,7 +356,8 @@ class ManifestNegativeTestCase(testtools.TestCase):
documents = copy.deepcopy(self.documents)
documents.append(documents[-1]) # Copy the last manifest.
error_re = r'Multiple manifests are not supported.*'
error_re = r'Multiple {} documents are not supported.*'.format(
schema.TYPE_MANIFEST)
self.assertRaisesRegexp(
exceptions.ManifestException,
error_re,
@ -364,9 +366,7 @@ class ManifestNegativeTestCase(testtools.TestCase):
target_manifest='armada-manifest')
def _assert_missing_documents_raises(self, documents):
error_re = (
'.*Documents must include at least one of each of .* and '
'only one .*')
error_re = ('.*Documents must include at least one of each of .*')
self.assertRaisesRegexp(
exceptions.ManifestException, error_re, manifest.Manifest,
documents)
@ -374,7 +374,11 @@ class ManifestNegativeTestCase(testtools.TestCase):
def test_get_documents_missing_manifest(self):
# Validates exceptions.ManifestException is thrown if no manifest is
# found. Manifest is last document in sample YAML.
self._assert_missing_documents_raises(self.documents[:-1])
error_re = 'Documents must include at least one {}'.format(
schema.TYPE_MANIFEST)
self.assertRaisesRegexp(
exceptions.ManifestException, error_re, manifest.Manifest,
self.documents[:-1])
def test_get_documents_missing_charts(self):
# Validates exceptions.ManifestException is thrown if no chart is

View File

@ -0,0 +1,53 @@
Armada - Apply Chart
====================
Commands
--------
.. code:: bash
Usage: armada apply_chart [OPTIONS] [LOCATION]
This command installs and updates an Armada chart.
[LOCATION] must be a relative path to Armada Chart or a reference
to an Armada Chart kubernetes CR which has the same format, except as
noted in the :ref:`v2 document authoring documentation <document_authoring_v2>`.
To install or upgrade a chart, run:
$ armada apply_chart --release-prefix=armada my-chart.yaml
$ armada apply_chart --release-prefix=armada kube:armadacharts/my-namespace/my-chart
Options:
--release-prefix TEXT Release prefix to use. [required]
--disable-update-post Disable post-update Tiller operations.
--disable-update-pre Disable pre-update Tiller operations.
--metrics-output TEXT Output path for prometheus metric data, should
end in .prom. By default, no metric data is
output.
--tiller-host TEXT Tiller host IP.
--tiller-port INTEGER Tiller host port.
-tn, --tiller-namespace TEXT Tiller namespace.
--timeout INTEGER Specifies time to wait for each chart to fully
finish deploying.
--wait Force Tiller to wait until the chart is
deployed, rather than using the charts
specified wait policy. This is equivalent to
sequenced chartgroups.
--target-chart TEXT The target chart to deploy. Required for
specifying which chart to deploy when multiple
are available.
--bearer-token TEXT User Bearer token
--debug Enable debug logging.
--help Show this message and exit.
Synopsis
--------
The apply_chart command will deploy an armada chart definition, installing or
updating as appropriate.
``armada apply_chart --release-prefix=armada my-chart.yaml [--debug]``
``armada apply_chart --release-prefix=armada kube:armadacharts/my-namespace/my-chart [--debug]``

View File

@ -11,6 +11,7 @@ Commands Guide
:caption: Contents:
apply.rst
apply_chart.rst
rollback.rst
test.rst
tiller.rst

View File

@ -124,6 +124,7 @@ Chart
| dependencies | object | (optional) Override the `builtin chart dependencies`_ with a list of Chart documents |
| | | to use as dependencies instead. |
| | | NOTE: Builtin ".tgz" dependencies are not yet supported. |
| | | NOTE: This field is not supported in the ArmadaChart CRD. |
+-----------------+----------+---------------------------------------------------------------------------------------+
.. _wait_v2:
@ -154,6 +155,8 @@ Wait
| | | **array** - Lists all `Wait Resource`_ s to use, completely |
| | | overriding the default. Can be set to ``[]`` to disable all |
| | | resource types. |
| | | NOTE: To use the array form with the ArmadaChart CRD, the keyword |
| | | must be ``resources_list`` instead of ``resources``. |
| | | |
| | | See also `Wait Resources Examples`_. |
+-------------+----------+--------------------------------------------------------------------+

165
manifests/chart-crd.yaml Normal file
View File

@ -0,0 +1,165 @@
apiVersion: "apiextensions.k8s.io/v1"
kind: "CustomResourceDefinition"
metadata:
name: "armadacharts.armada.airshipit.org"
spec:
group: "armada.airshipit.org"
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
release:
type: string
namespace:
type: string
values:
type: object
x-kubernetes-preserve-unknown-fields: true
protected:
type: object
properties:
continue_processing:
type: boolean
test:
type: object
properties:
enabled:
type: boolean
timeout:
type: integer
options:
type: object
properties:
cleanup:
type: boolean
wait:
type: object
properties:
timeout:
type: integer
resources:
type: object
additionalProperties:
type: array
items:
type: object
properties:
labels:
type: object
additionalProperties:
type: string
min_ready:
x-kubernetes-int-or-string: true
anyOf:
- type: integer
- type: string
required:
type: boolean
resources_list:
type: array
items:
type: object
properties:
labels:
type: object
additionalProperties:
type: string
min_ready:
x-kubernetes-int-or-string: true
anyOf:
- type: integer
- type: string
required:
type: boolean
type:
type: string
required:
- type
labels:
type: object
additionalProperties:
type: string
native:
type: object
properties:
enabled:
type: boolean
# Note: This is specific to the kubernetes schema.
# Dynamically typed fields are disallowed by kubernetes
# structural schemas, so object and list resource overrides
# need two separate fields. We specify here that, exactly one
# of these can be given.
not:
allOf:
- required:
- resources
- required:
- resources_list
source:
type: object
properties:
type:
type: string
location:
type: string
subpath:
type: string
reference:
type: string
proxy_server:
type: string
auth_method:
type: string
required:
- location
- type
delete:
type: object
properties:
timeout:
type: integer
upgrade:
type: object
properties:
pre:
type: object
properties:
delete:
type: array
items:
type: object
properties:
type:
type: string
labels:
type: object
additionalProperties:
type: string
required:
- type
options:
type: object
properties:
force:
type: boolean
recreate_pods:
type: boolean
no_hooks:
type: boolean
required:
- namespace
- release
- source
scope: "Namespaced"
names:
plural: "armadacharts"
singular: "armadachart"
kind: "ArmadaChart"

View File

@ -0,0 +1,3 @@
resources:
- chart-crd.yaml
- rbac.yaml

62
manifests/rbac.yaml Normal file
View File

@ -0,0 +1,62 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: armada-controller
labels:
rbac.armada.airshipit.org/aggregate-to-armada: "true"
rules:
- apiGroups:
- "apps"
resources:
- deployments
- statefulsets
- daemonsets
verbs:
- get
- list
- watch
- apiGroups:
- batch
- extensions
resources:
- jobs
verbs:
- get
- list
- watch
- delete
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch
- delete
- apiGroups:
- apiextensions.k8s.io
resources:
- customresourcedefinitions
verbs:
- get
- create
- apiGroups:
- armada.process
resources:
- locks
verbs:
- get
- list
- create
- delete
- patch
- update
- apiGroups:
- armada.airshipit.org
resources:
- armadacharts
verbs:
- get
- list
---

View File

@ -0,0 +1,28 @@
#!/bin/bash
# Copyright 2020 AT&T Intellectual Property. All other rights reserved.
#
# 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.
set -xe
CURRENT_DIR="$(pwd)"
: "${INSTALL_PATH:="../"}"
cd ${INSTALL_PATH}
: "${OSH_INFRA_COMMIT:="eacf93722136636dcfbd2b68c59b71f071ffc085"}"
# Clone openstack-helm-infra
git clone https://opendev.org/openstack/openstack-helm-infra.git
cd openstack-helm-infra
git checkout "${OSH_INFRA_COMMIT}"
cd "${CURRENT_DIR}"

View File

@ -0,0 +1,45 @@
#!/bin/bash
# Copyright 2019, AT&T Intellectual Property
#
# 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.
set -xe
CURRENT_DIR="$(pwd)"
: "${OSH_INFRA_PATH:="../openstack-helm-infra"}"
# Configure proxy settings if $PROXY is set
if [ -n "${PROXY}" ]; then
. tools/deployment/airskiff/common/setup-proxy.sh
fi
# Deploy K8s with Minikube
cd "${OSH_INFRA_PATH}"
bash -c "./tools/deployment/common/005-deploy-k8s.sh"
# Add user to Docker group
# NOTE: This requires re-authentication. Restart your shell.
sudo adduser "$(whoami)" docker
sudo su - "$USER" -c bash <<'END_SCRIPT'
if echo $(groups) | grep -qv 'docker'; then
echo "You need to logout to apply group permissions"
echo "Please logout and login"
fi
END_SCRIPT
# clean up /etc/resolv.conf, if it includes a localhost dns address
sudo sed -i.bkp '/^nameserver.*127.0.0.1/d
w /dev/stdout' /etc/resolv.conf
cd "${CURRENT_DIR}"

View File

@ -0,0 +1,20 @@
#!/bin/bash
# Copyright 2020 AT&T Intellectual Property. All other rights reserved.
#
# 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.
set -xe
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
./tools/airship2-integration/test/test.sh basic

View File

@ -0,0 +1,24 @@
apiVersion: "armada.airshipit.org/v1alpha1"
kind: "ArmadaChart"
metadata:
name: a1
namespace: test
spec:
release: a1
namespace: test
wait:
timeout: 100
labels:
release_group: armada-a1
source:
location: https://kubernetes-charts-incubator.storage.googleapis.com/raw-0.2.3.tgz
subpath: raw
type: tar
values:
resources:
- apiVersion: v1
kind: ConfigMap
metadata:
name: a1
data:
chart: a1

View File

@ -0,0 +1,24 @@
apiVersion: "armada.airshipit.org/v1alpha1"
kind: "ArmadaChart"
metadata:
name: a2
namespace: test
spec:
release: a2
namespace: test
wait:
timeout: 100
labels:
release_group: armada-a1
source:
location: https://kubernetes-charts-incubator.storage.googleapis.com/raw-0.2.3.tgz
subpath: raw
type: tar
values:
resources:
- apiVersion: v1
kind: ConfigMap
metadata:
name: a2
data:
chart: a2

View File

@ -0,0 +1,24 @@
apiVersion: "armada.airshipit.org/v1alpha1"
kind: "ArmadaChart"
metadata:
name: b1
namespace: test
spec:
release: b1
namespace: test
wait:
timeout: 100
labels:
release_group: armada-b1
source:
location: https://kubernetes-charts-incubator.storage.googleapis.com/raw-0.2.3.tgz
subpath: raw
type: tar
values:
resources:
- apiVersion: v1
kind: ConfigMap
metadata:
name: b1
data:
chart: b1

View File

@ -0,0 +1,24 @@
apiVersion: "armada.airshipit.org/v1alpha1"
kind: "ArmadaChart"
metadata:
name: b2
namespace: test
spec:
release: b2
namespace: test
wait:
timeout: 100
labels:
release_group: armada-b2
source:
location: https://kubernetes-charts-incubator.storage.googleapis.com/raw-0.2.3.tgz
subpath: raw
type: tar
values:
resources:
- apiVersion: v1
kind: ConfigMap
metadata:
name: b2
data:
chart: b2

View File

@ -0,0 +1,24 @@
apiVersion: "armada.airshipit.org/v1alpha1"
kind: "ArmadaChart"
metadata:
name: b3
namespace: test
spec:
release: b3
namespace: test
wait:
timeout: 100
labels:
release_group: armada-b3
source:
location: https://kubernetes-charts-incubator.storage.googleapis.com/raw-0.2.3.tgz
subpath: raw
type: tar
values:
resources:
- apiVersion: v1
kind: ConfigMap
metadata:
name: b3
data:
chart: b3

View File

@ -0,0 +1,24 @@
apiVersion: "armada.airshipit.org/v1alpha1"
kind: "ArmadaChart"
metadata:
name: c1
namespace: test
spec:
release: c1
namespace: test
wait:
timeout: 100
labels:
release_group: armada-c1
source:
location: https://kubernetes-charts-incubator.storage.googleapis.com/raw-0.2.3.tgz
subpath: raw
type: tar
values:
resources:
- apiVersion: v1
kind: ConfigMap
metadata:
name: c1
data:
chart: c1

View File

@ -0,0 +1,51 @@
# Copyright (c) 2020 AT&T Intellectual Property. All rights reserved.
#
# 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.
---
apiVersion: batch/v1
kind: Job
metadata:
namespace: "${NAMESPACE}"
name: "${JOB_NAME}"
spec:
backoffLimit: 0
template:
spec:
serviceAccountName: ${SERVICE_ACCOUNT}
restartPolicy: Never
containers:
- name: "test-airship2-integration"
image: "${IMAGE}"
imagePullPolicy: Never
volumeMounts:
- name: kube-config
mountPath: /armada/.kube/config
command:
- /bin/bash
- -c
- |-
set -xe
apply_chart() {
NAME=$1
armada apply_chart kube:armadacharts/$NAMESPACE/${DOLLAR}NAME --release-prefix ${RELEASE_PREFIX}
}
for CHART in ${CHARTS_SPACE_SEPARATED}; do
apply_chart ${DOLLAR}CHART
done
volumes:
- name: kube-config
hostPath:
path: "${KUBE_CONFIG}"
...

View File

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: ${NAMESPACE}

View File

@ -0,0 +1,18 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: ${SERVICE_ACCOUNT}
namespace: ${NAMESPACE}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: ${SERVICE_ACCOUNT}-armada-controller
subjects:
- kind: ServiceAccount
name: ${SERVICE_ACCOUNT}
namespace: ${NAMESPACE}
roleRef:
kind: ClusterRole
name: armada-controller
apiGroup: rbac.authorization.k8s.io

View File

@ -0,0 +1,61 @@
#!/bin/bash
# Copyright 2020 AT&T Intellectual Property. All other rights reserved.
#
# 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.
set -xe
EXAMPLE=$1
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
export NAMESPACE=test
export SERVICE_ACCOUNT=test-armada
export KUBE_CONFIG=~/.kube/config
export RELEASE_PREFIX=test
export JOB_NAME=apply-chart-test
export IMAGE=quay.io/airshipit/armada:latest-ubuntu_bionic
TIMEOUT=300
# See https://stackoverflow.com/a/24964089
export DOLLAR="\$"
# Cleanup any previous runs
cleanup() {
kubectl delete namespace $NAMESPACE --ignore-not-found=true
for i in $(helm ls --short | grep $RELEASE_PREFIX-); do helm del --purge $i; done
}
cleanup
# Install namespace
envsubst < $DIR/test-namespace.yaml | kubectl apply -f -
# Install CRD
kubectl apply -k ./manifests
# Install RBAC
envsubst < $DIR/test-rbac.yaml | kubectl apply -f -
# Install example CRs
kubectl apply -R -f $DIR/examples/$EXAMPLE
# Run test
export CHARTS=$(kubectl get armadacharts -n $NAMESPACE -o name | cut -d / -f2)
export CHARTS_SPACE_SEPARATED=$(echo "$CHARTS" | tr "\n" " ")
envsubst < $DIR/test-job.yaml | kubectl create -f -
# Wait for test job completion
kubectl wait --timeout ${TIMEOUT}s --for=condition=Complete -n $NAMESPACE job/$JOB_NAME
POD_NAME=$(kubectl get pods -n $NAMESPACE -l job-name=$JOB_NAME -o json | jq -r '.items[0].metadata.name')
kubectl logs -n $NAMESPACE $POD_NAME
ACTUAL=$(helm ls --short)
EXPECTED=$(echo "$CHARTS" | sed -e "s/^/$RELEASE_PREFIX-/")
diff <(echo "$ACTUAL") <(echo "$EXPECTED")

View File

@ -0,0 +1,45 @@
# Copyright 2018 AT&T Intellectual Property. All other rights reserved.
#
# 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.
- hosts: primary
tasks:
- name: Clone Required Repositories
shell: |
./tools/airship2-integration/000-clone-dependencies.sh
args:
chdir: "{{ zuul.project.src_dir }}"
- name: Deploy Kubernetes with Minikube
shell: |
./tools/airship2-integration/010-deploy-k8s.sh
args:
chdir: "{{ zuul.project.src_dir }}"
- name: Build Armada with submitted changes
shell: |
# Add image to minikube
eval $(minikube docker-env)
make images
args:
chdir: "{{ zuul.project.src_dir }}"
become: yes
- name: Apply charts
shell: |
mkdir ~/.kube
cp -rp /home/zuul/.kube/config ~/.kube/config
./tools/airship2-integration/020-apply-charts.sh
args:
chdir: "{{ zuul.project.src_dir }}"
become: yes