feat(cli): using-click-framework

- using click framework
- added api client
- allow interactions between code and service endpoints
- documention on the command line
- updated gitignore

Change-Id: Ibe359025f5b35606d876c29fa88e04048f276cc8
This commit is contained in:
gardlt 2017-09-19 05:42:45 +00:00
parent a99fc4ad6c
commit 7b26e59422
46 changed files with 2200 additions and 493 deletions

View File

@ -5,3 +5,6 @@ CODE_OF_CONDUCT.rst
ChangeLog
LICENSE
OWNERS
etc/armada/armada.conf
etc/armada/policy.yaml
charts/*

View File

@ -1,18 +0,0 @@
# EditorConfig http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 4
tab_width = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = true
max_line_length = 80
curly_bracket_next_line = false
spaces_around_operators = true
spaces_around_brackets = true
indent_brace_style = K&R

9
.gitignore vendored
View File

@ -24,6 +24,9 @@ var/
.installed.cfg
*.egg
etc/*.sample
etc/hostname
etc/hosts
etc/resolv.conf
# PyInstaller
# Usually these files are written by a python script from a template
@ -97,3 +100,9 @@ ENV/
**/*.tgz
**/_partials.tpl
**/_globals.tpl
AUTHORS
ChangeLog
etc/armada/armada.conf
etc/armada/policy.yaml
.editorconfig

View File

@ -3,6 +3,8 @@ FROM ubuntu:16.04
MAINTAINER Armada Team
ENV DEBIAN_FRONTEND noninteractive
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
COPY . /armada

View File

@ -10,22 +10,32 @@
# 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.
# limitations under the License
import json
import uuid
import logging as log
import os
import uuid
import yaml
import falcon
from oslo_config import cfg
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
from armada import const
CONF = cfg.CONF
class BaseResource(object):
def __init__(self):
self.logger = LOG
if not (os.path.exists(const.CONFIG_PATH)):
logging.register_options(CONF)
logging.set_defaults(default_log_levels=CONF.default_log_levels)
logging.setup(CONF, 'armada')
self.logger = logging.getLogger(__name__)
def on_options(self, req, resp):
self_attrs = dir(self)
@ -39,29 +49,23 @@ class BaseResource(object):
resp.headers['Allow'] = ','.join(allowed_methods)
resp.status = falcon.HTTP_200
def req_json(self, req):
def req_yaml(self, req):
if req.content_length is None or req.content_length == 0:
return None
if req.content_type is not None and req.content_type.lower(
) == 'application/json':
raw_body = req.stream.read(req.content_length or 0)
raw_body = req.stream.read(req.content_length or 0)
if raw_body is None:
return None
if raw_body is None:
return None
try:
# json_body = json.loads(raw_body.decode('utf-8'))
# return json_body
return raw_body
except json.JSONDecodeError as jex:
self.error(
req.context,
"Invalid JSON in request: \n%s" % raw_body.decode('utf-8'))
raise json.JSONDecodeError("%s: Invalid JSON in body: %s" %
(req.path, jex))
else:
raise json.JSONDecodeError("Requires application/json payload")
try:
return yaml.safe_load_all(raw_body.decode('utf-8'))
except yaml.YAMLError as jex:
self.error(
req.context,
"Invalid YAML in request: \n%s" % raw_body.decode('utf-8'))
raise Exception(
"%s: Invalid YAML in body: %s" % (req.path, jex))
def return_error(self, resp, status_code, message="", retry=False):
resp.body = json.dumps({

View File

@ -1,60 +0,0 @@
# Copyright 2017 The Armada Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import falcon
from oslo_log import log as logging
from armada import api
from armada.handlers.armada import Armada
LOG = logging.getLogger(__name__)
class Apply(api.BaseResource):
'''
apply armada endpoint service
'''
def on_post(self, req, resp):
try:
# Load data from request and get options
data = self.req_json(req)
opts = {}
# opts = data['options']
# Encode filename
# data['file'] = data['file'].encode('utf-8')
armada = Armada(
data,
disable_update_pre=opts.get('disable_update_pre', False),
disable_update_post=opts.get('disable_update_post', False),
enable_chart_cleanup=opts.get('enable_chart_cleanup', False),
dry_run=opts.get('dry_run', False),
wait=opts.get('wait', False),
timeout=opts.get('timeout', False))
msg = armada.sync()
resp.data = json.dumps({'message': msg})
resp.content_type = 'application/json'
resp.status = falcon.HTTP_200
except Exception as e:
self.error(req.context, "Failed to apply manifest")
self.return_error(
resp, falcon.HTTP_500,
message="Failed to install manifest: {} {}".format(e, data))

View File

@ -0,0 +1,71 @@
# Copyright 2017 The Armada Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import falcon
from armada import api
from armada.common import policy
from armada.handlers.armada import Armada
class Apply(api.BaseResource):
'''
apply armada endpoint service
'''
@policy.enforce('armada:create_endpoints')
def on_post(self, req, resp):
try:
# Load data from request and get options
data = list(self.req_yaml(req))
if type(data[0]) is list:
data = list(data[0])
opts = req.params
# Encode filename
armada = Armada(
data,
disable_update_pre=req.get_param_as_bool(
'disable_update_pre'),
disable_update_post=req.get_param_as_bool(
'disable_update_post'),
enable_chart_cleanup=req.get_param_as_bool(
'enable_chart_cleanup'),
dry_run=req.get_param_as_bool('dry_run'),
wait=req.get_param_as_bool('wait'),
timeout=int(opts.get('timeout', 3600)),
tiller_host=opts.get('tiller_host', None),
tiller_port=int(opts.get('tiller_port', 44134)),
)
msg = armada.sync()
resp.body = json.dumps(
{
'message': msg,
}
)
resp.content_type = 'application/json'
resp.status = falcon.HTTP_200
except Exception as e:
err_message = 'Failed to apply manifest: {}'.format(e)
self.error(req.context, err_message)
self.return_error(
resp, falcon.HTTP_500, message=err_message)

View File

@ -0,0 +1,134 @@
# Copyright 2017 The Armada Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import falcon
from armada import api
from armada.common import policy
from armada import const
from armada.handlers.tiller import Tiller
from armada.handlers.manifest import Manifest
from armada.utils.release import release_prefix
class Test(api.BaseResource):
'''
Test helm releases via release name
'''
@policy.enforce('armada:test_release')
def on_get(self, req, resp, release):
try:
self.logger.info('RUNNING: %s', release)
opts = req.params
tiller = Tiller(tiller_host=opts.get('tiller_host', None),
tiller_port=opts.get('tiller_port', None))
tiller_resp = tiller.testing_release(release)
msg = {
'result': '',
'message': ''
}
if tiller_resp:
test_status = getattr(
tiller_resp.info.status, 'last_test_suite_run', 'FAILED')
if test_status.result[0].status:
msg['result'] = 'PASSED: {}'.format(release)
msg['message'] = 'MESSAGE: Test Pass'
self.logger.info(msg)
else:
msg['result'] = 'FAILED: {}'.format(release)
msg['message'] = 'MESSAGE: Test Fail'
self.logger.info(msg)
else:
msg['result'] = 'FAILED: {}'.format(release)
msg['message'] = 'MESSAGE: No test found'
resp.body = json.dumps(msg)
resp.status = falcon.HTTP_200
resp.content_type = 'application/json'
except Exception as e:
err_message = 'Failed to test {}: {}'.format(release, e)
self.error(req.context, err_message)
self.return_error(
resp, falcon.HTTP_500, message=err_message)
class Tests(api.BaseResource):
'''
Test helm releases via a manifest
'''
@policy.enforce('armada:tests_manifest')
def on_post(self, req, resp):
try:
opts = req.params
tiller = Tiller(tiller_host=opts.get('tiller_host', None),
tiller_port=opts.get('tiller_port', None))
documents = self.req_yaml(req)
armada_obj = Manifest(documents).get_manifest()
prefix = armada_obj.get(const.KEYWORD_ARMADA).get(
const.KEYWORD_PREFIX)
known_releases = [release[0] for release in tiller.list_charts()]
message = {
'tests': {
'passed': [],
'skipped': [],
'failed': []
}
}
for group in armada_obj.get(const.KEYWORD_ARMADA).get(
const.KEYWORD_GROUPS):
for ch in group.get(const.KEYWORD_CHARTS):
release_name = release_prefix(
prefix, ch.get('chart').get('chart_name'))
if release_name in known_releases:
self.logger.info('RUNNING: %s tests', release_name)
resp = tiller.testing_release(release_name)
if not resp:
continue
test_status = getattr(
resp.info.status, 'last_test_suite_run',
'FAILED')
if test_status.results[0].status:
self.logger.info("PASSED: %s", release_name)
message['test']['passed'].append(release_name)
else:
self.logger.info("FAILED: %s", release_name)
message['test']['failed'].append(release_name)
else:
self.logger.info(
'Release %s not found - SKIPPING', release_name)
message['test']['skipped'].append(release_name)
resp.status = falcon.HTTP_200
resp.body = json.dumps(message)
resp.content_type = 'application/json'
except Exception as e:
err_message = 'Failed to test manifest: {}'.format(e)
self.error(req.context, err_message)
self.return_error(
resp, falcon.HTTP_500, message=err_message)

View File

@ -15,16 +15,11 @@
import json
import falcon
from oslo_config import cfg
from oslo_log import log as logging
from armada import api
from armada.common import policy
from armada.handlers.tiller import Tiller
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class Status(api.BaseResource):
@policy.enforce('tiller:get_status')
@ -33,21 +28,27 @@ class Status(api.BaseResource):
get tiller status
'''
try:
message = {'tiller': Tiller().tiller_status()}
opts = req.params
tiller = Tiller(
tiller_host=opts.get('tiller_host', None),
tiller_port=opts.get('tiller_port', None))
if message.get('tiller', False):
resp.status = falcon.HTTP_200
else:
resp.status = falcon.HTTP_503
message = {
'tiller': {
'state': tiller.tiller_status(),
'version': tiller.tiller_version()
}
}
resp.data = json.dumps(message)
resp.status = falcon.HTTP_200
resp.body = json.dumps(message)
resp.content_type = 'application/json'
except Exception as e:
self.error(req.context, "Unable to find resources")
err_message = 'Failed to get Tiller Status: {}'.format(e)
self.error(req.context, err_message)
self.return_error(
resp, falcon.HTTP_500,
message="Unable to get status: {}".format(e))
resp, falcon.HTTP_500, message=err_message)
class Release(api.BaseResource):
@ -58,21 +59,23 @@ class Release(api.BaseResource):
'''
try:
# Get tiller releases
handler = Tiller()
opts = req.params
tiller = Tiller(tiller_host=opts.get('tiller_host', None),
tiller_port=opts.get('tiller_port', None))
releases = {}
for release in handler.list_releases():
for release in tiller.list_releases():
if not releases.get(release.namespace, None):
releases[release.namespace] = []
releases[release.namespace].append(release.name)
resp.data = json.dumps({'releases': releases})
resp.body = json.dumps({'releases': releases})
resp.content_type = 'application/json'
resp.status = falcon.HTTP_200
except Exception as e:
self.error(req.context, "Unable to find resources")
err_message = 'Unable to find Tiller Releases: {}'.format(e)
self.error(req.context, err_message)
self.return_error(
resp, falcon.HTTP_500,
message="Unable to find Releases: {}".format(e))
resp, falcon.HTTP_500, message=err_message)

View File

@ -13,17 +13,13 @@
# limitations under the License.
import json
import yaml
import falcon
from oslo_log import log as logging
from armada import api
from armada.common import policy
from armada.utils.lint import validate_armada_documents
LOG = logging.getLogger(__name__)
class Validate(api.BaseResource):
'''
@ -33,24 +29,19 @@ class Validate(api.BaseResource):
@policy.enforce('armada:validate_manifest')
def on_post(self, req, resp):
try:
manifest = self.req_yaml(req)
documents = list(manifest)
message = {
'valid':
validate_armada_documents(
list(yaml.safe_load_all(self.req_json(req))))
'valid': validate_armada_documents(documents)
}
if message.get('valid', False):
resp.status = falcon.HTTP_200
else:
resp.status = falcon.HTTP_400
resp.data = json.dumps(message)
resp.status = falcon.HTTP_200
resp.body = json.dumps(message)
resp.content_type = 'application/json'
except Exception:
self.error(req.context, "Failed: Invalid Armada Manifest")
err_message = 'Failed to validate Armada Manifest'
self.error(req.context, err_message)
self.return_error(
resp,
falcon.HTTP_400,
message="Failed: Invalid Armada Manifest")
resp, falcon.HTTP_400, message=err_message)

View File

@ -17,18 +17,20 @@ from uuid import UUID
from oslo_config import cfg
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class AuthMiddleware(object):
def __init__(self):
self.logger = logging.getLogger(__name__)
# Authentication
def process_request(self, req, resp):
ctx = req.context
for k, v in req.headers.items():
LOG.debug("Request with header %s: %s" % (k, v))
self.logger.debug("Request with header %s: %s" % (k, v))
auth_status = req.get_header('X-SERVICE-IDENTITY-STATUS')
service = True
@ -65,8 +67,9 @@ class AuthMiddleware(object):
else:
ctx.is_admin_project = False
LOG.debug('Request from authenticated user %s with roles %s' %
(ctx.user, ','.join(ctx.roles)))
self.logger.debug(
'Request from authenticated user %s with roles %s' %
(ctx.user, ','.join(ctx.roles)))
else:
ctx.authenticated = False
@ -91,6 +94,9 @@ class ContextMiddleware(object):
class LoggingMiddleware(object):
def __init__(self):
self.logger = logging.getLogger(__name__)
def process_response(self, req, resp, resource, req_succeeded):
ctx = req.context
extra = {
@ -99,4 +105,4 @@ class LoggingMiddleware(object):
'external_ctx': ctx.external_marker,
}
resp.append_header('X-Armada-Req', ctx.request_id)
LOG.info("%s - %s" % (req.uri, resp.status), extra=extra)
self.logger.info("%s - %s" % (req.uri, resp.status), extra=extra)

View File

@ -12,34 +12,28 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import falcon
from oslo_config import cfg
from oslo_log import log as logging
from armada import conf
from armada.api import ArmadaRequest
from armada.api.armada_controller import Apply
from armada.api.controller.armada import Apply
from armada.api.middleware import AuthMiddleware
from armada.api.middleware import ContextMiddleware
from armada.api.middleware import LoggingMiddleware
from armada.api.tiller_controller import Release
from armada.api.tiller_controller import Status
from armada.api.validation_controller import Validate
from armada.api.controller.test import Test
from armada.api.controller.test import Tests
from armada.api.controller.tiller import Release
from armada.api.controller.tiller import Status
from armada.api.controller.validation import Validate
from armada.common import policy
LOG = logging.getLogger(__name__)
conf.set_app_default_configs()
CONF = cfg.CONF
# Build API
def create(middleware=CONF.middleware):
if not (os.path.exists('etc/armada/armada.conf')):
logging.register_options(CONF)
logging.set_defaults(default_log_levels=CONF.default_log_levels)
logging.setup(CONF, 'armada')
policy.setup_policy()
@ -55,13 +49,17 @@ def create(middleware=CONF.middleware):
api = falcon.API(request_type=ArmadaRequest)
# Configure API routing
url_routes_v1 = (('apply', Apply()),
('releases', Release()),
('status', Status()),
('validate', Validate()))
url_routes_v1 = (
('apply', Apply()),
('releases', Release()),
('status', Status()),
('tests', Tests()),
('test/{release}', Test()),
('validate', Validate()),
)
for route, service in url_routes_v1:
api.add_route("/v1.0/{}".format(route), service)
api.add_route("/api/v1.0/{}".format(route), service)
return api

View File

@ -11,3 +11,22 @@
# 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
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
class CliAction(object):
def __init__(self):
self.logger = LOG
logging.register_options(CONF)
logging.set_defaults(default_log_levels=CONF.default_log_levels)
logging.setup(CONF, 'armada')
def invoke(self):
raise Exception()

View File

@ -12,61 +12,195 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from cliff import command as cmd
import yaml
import click
from oslo_config import cfg
from armada.cli import CliAction
from armada.handlers.armada import Armada
def applyCharts(args):
armada = Armada(open(args.file).read(),
args.disable_update_pre,
args.disable_update_post,
args.enable_chart_cleanup,
args.dry_run,
args.set,
args.wait,
args.timeout,
args.tiller_host,
args.tiller_port,
args.values,
args.debug_logging)
armada.sync()
CONF = cfg.CONF
class ApplyChartsCommand(cmd.Command):
def get_parser(self, prog_name):
parser = super(ApplyChartsCommand, self).get_parser(prog_name)
parser.add_argument('file', type=str, metavar='FILE',
help='Armada yaml file')
parser.add_argument('--dry-run', action='store_true',
default=False, help='Run charts with dry run')
parser.add_argument('--debug-logging', action='store_true',
default=False, help='Show debug logs')
parser.add_argument('--disable-update-pre', action='store_true',
default=False, help='Disable pre upgrade actions')
parser.add_argument('--disable-update-post', action='store_true',
default=False, help='Disable post upgrade actions')
parser.add_argument('--enable-chart-cleanup', action='store_true',
default=False, help='Enable Chart Clean Up')
parser.add_argument('--set', action='append', help='Override Armada'
' manifest values.')
parser.add_argument('--wait', action='store_true',
default=False, help='Wait until all charts'
'have been deployed')
parser.add_argument('--timeout', action='store', type=int,
default=3600, help='Specifies time to wait'
' for charts to deploy')
parser.add_argument('--tiller-host', action='store', type=str,
help='Specify the tiller host')
@click.group()
def apply():
""" Apply manifest to cluster
parser.add_argument('--tiller-port', action='store', type=int,
default=44134, help='Specify the tiller port')
"""
parser.add_argument('--values', action='append',
help='Override manifest values with a yaml file')
return parser
DESC = """
This command install and updates charts defined in armada manifest
def take_action(self, parsed_args):
applyCharts(parsed_args)
The apply argument must be relative path to Armada Manifest. Executing apply
commnad once will install all charts defined in manifest. Re-executing apply
commnad will execute upgrade.
To see how to create an Armada manifest:
http://armada-helm.readthedocs.io/en/latest/operations/
To obtain install/upgrade charts:
\b
$ armada apply examples/simple.yaml
To obtain override manifest:
\b
$ armada apply examples/simple.yaml \
--set manifest:simple-armada:relase_name="wordpress"
\b
or
\b
$ armada apply examples/simple.yaml \
--values examples/simple-ovr-values.yaml
"""
SHORT_DESC = "command install manifest charts"
@apply.command(name='apply', help=DESC, short_help=SHORT_DESC)
@click.argument('filename')
@click.option('--api', help="Contacts service endpoint", is_flag=True)
@click.option(
'--disable-update-post', help="run charts without install", is_flag=True)
@click.option(
'--disable-update-pre', help="run charts without install", is_flag=True)
@click.option('--dry-run', help="run charts without install", is_flag=True)
@click.option(
'--enable-chart-cleanup', help="Clean up Unmanaged Charts", is_flag=True)
@click.option('--set', multiple=True, type=str, default=[])
@click.option('--tiller-host', help="Tiller host ip")
@click.option(
'--tiller-port', help="Tiller host port", type=int, default=44134)
@click.option(
'--timeout', help="specifies time to wait for charts", type=int,
default=3600)
@click.option('--values', '-f', multiple=True, type=str, default=[])
@click.option(
'--wait', help="wait until all charts deployed", is_flag=True)
@click.option(
'--debug/--no-debug', help='Enable or disable debugging', default=False)
@click.pass_context
def apply_create(ctx,
filename,
api,
disable_update_post,
disable_update_pre,
dry_run,
enable_chart_cleanup,
set,
tiller_host,
tiller_port,
timeout,
values,
wait,
debug):
if debug:
CONF.debug = debug
ApplyManifest(
ctx,
filename,
api,
disable_update_post,
disable_update_pre,
dry_run,
enable_chart_cleanup,
set,
tiller_host,
tiller_port,
timeout,
values,
wait).invoke()
class ApplyManifest(CliAction):
def __init__(self,
ctx,
filename,
api,
disable_update_post,
disable_update_pre,
dry_run,
enable_chart_cleanup,
set,
tiller_host,
tiller_port,
timeout,
values,
wait):
super(ApplyManifest, self).__init__()
self.ctx = ctx
self.filename = filename
self.api = api
self.disable_update_post = disable_update_post
self.disable_update_pre = disable_update_pre
self.dry_run = dry_run
self.enable_chart_cleanup = enable_chart_cleanup
self.set = set
self.tiller_host = tiller_host
self.tiller_port = tiller_port
self.timeout = timeout
self.values = values
self.wait = wait
def output(self, resp):
for result in resp:
if not resp[result] and not result == 'diff':
self.logger.info(
'Did not performed chart %s(s)', result)
elif result == 'diff' and not resp[result]:
self.logger.info('No Relase changes detected')
for ch in resp[result]:
if not result == 'diff':
msg = 'Chart {} was {}'.format(ch, result)
self.logger.info(msg)
else:
self.logger.info('Chart values diff')
self.logger.info(ch)
def invoke(self):
if not self.ctx.obj.get('api', False):
with open(self.filename) as f:
armada = Armada(
list(yaml.safe_load_all(f.read())),
self.disable_update_pre,
self.disable_update_post,
self.enable_chart_cleanup,
self.dry_run,
self.set,
self.wait,
self.timeout,
self.tiller_host,
self.tiller_port,
self.values)
resp = armada.sync()
self.output(resp)
else:
query = {
'disable_update_post': self.disable_update_post,
'disable_update_pre': self.disable_update_pre,
'dry_run': self.dry_run,
'enable_chart_cleanup': self.enable_chart_cleanup,
'tiller_host': self.tiller_host,
'tiller_port': self.tiller_port,
'timeout': self.timeout,
'wait': self.wait
}
client = self.ctx.obj.get('CLIENT')
with open(self.filename, 'r') as f:
resp = client.post_apply(
manifest=f.read(), values=self.values, set=self.set,
query=query)
self.output(resp.get('message'))

View File

@ -14,94 +14,141 @@
import yaml
from cliff import command as cmd
from oslo_config import cfg
from oslo_log import log as logging
import click
from armada.cli import CliAction
from armada import const
from armada.handlers.manifest import Manifest
from armada.handlers.tiller import Tiller
from armada.utils.release import release_prefix
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
@click.group()
def test():
""" Test Manifest Charts
"""
def testService(args):
DESC = """
This command test deployed charts
tiller = Tiller(tiller_host=args.tiller_host, tiller_port=args.tiller_port)
known_release_names = [release[0] for release in tiller.list_charts()]
The tiller command uses flags to obtain information from tiller services.
The test command will run the release chart tests either via a the manifest or
by targetings a relase.
if args.release:
LOG.info("RUNNING: %s tests", args.release)
resp = tiller.testing_release(args.release)
To test armada deployed releases:
if not resp:
LOG.info("FAILED: %s", args.release)
return
$ armada test --file examples/simple.yaml
test_status = getattr(resp.info.status, 'last_test_suite_run',
'FAILED')
if test_status.results[0].status:
LOG.info("PASSED: %s", args.release)
else:
LOG.info("FAILED: %s", args.release)
To test release:
if args.file:
documents = yaml.safe_load_all(open(args.file).read())
armada_obj = Manifest(documents).get_manifest()
prefix = armada_obj.get(const.KEYWORD_ARMADA).get(const.KEYWORD_PREFIX)
$ armada test --release blog-1
for group in armada_obj.get(const.KEYWORD_ARMADA).get(
const.KEYWORD_GROUPS):
for ch in group.get(const.KEYWORD_CHARTS):
release_name = release_prefix(
prefix, ch.get('chart').get('chart_name'))
"""
if release_name in known_release_names:
LOG.info('RUNNING: %s tests', release_name)
resp = tiller.testing_release(release_name)
SHORT_DESC = "command test releases"
if not resp:
continue
test_status = getattr(resp.info.status,
'last_test_suite_run', 'FAILED')
if test_status.results[0].status:
LOG.info("PASSED: %s", release_name)
else:
LOG.info("FAILED: %s", release_name)
@test.command(name='test', help=DESC, short_help=SHORT_DESC)
@click.option('--file', help='armada manifest', type=str)
@click.option('--release', help='helm release', type=str)
@click.option('--tiller-host', help="Tiller Host IP")
@click.option(
'--tiller-port', help="Tiller host Port", type=int, default=44134)
@click.pass_context
def test_charts(ctx, file, release, tiller_host, tiller_port):
TestChartManifest(
ctx, file, release, tiller_host, tiller_port).invoke()
class TestChartManifest(CliAction):
def __init__(self, ctx, file, release, tiller_host, tiller_port):
super(TestChartManifest, self).__init__()
self.ctx = ctx
self.file = file
self.release = release
self.tiller_host = tiller_host
self.tiller_port = tiller_port
def invoke(self):
tiller = Tiller(
tiller_host=self.tiller_host, tiller_port=self.tiller_port)
known_release_names = [release[0] for release in tiller.list_charts()]
if self.release:
if not self.ctx.obj.get('api', False):
self.logger.info("RUNNING: %s tests", self.release)
resp = tiller.testing_release(self.release)
if not resp:
self.logger.info("FAILED: %s", self.release)
return
test_status = getattr(resp.info.status, 'last_test_suite_run',
'FAILED')
if test_status.results[0].status:
self.logger.info("PASSED: %s", self.release)
else:
LOG.info('Release %s not found - SKIPPING', release_name)
self.logger.info("FAILED: %s", self.release)
else:
client = self.ctx.obj.get('CLIENT')
query = {
'tiller_host': self.tiller_host,
'tiller_port': self.tiller_port
}
resp = client.get_test_release(release=self.release,
query=query)
self.logger.info(resp.get('result'))
self.logger.info(resp.get('message'))
class TestServerCommand(cmd.Command):
def get_parser(self, prog_name):
parser = super(TestServerCommand, self).get_parser(prog_name)
parser.add_argument(
'--release', action='store', help='testing Helm in Release')
parser.add_argument(
'-f',
'--file',
type=str,
metavar='FILE',
help='testing Helm releases in Manifest')
parser.add_argument(
'--tiller-host',
action='store',
type=str,
default=None,
help='Specify the tiller host')
parser.add_argument(
'--tiller-port',
action='store',
type=int,
default=44134,
help='Specify the tiller port')
if self.file:
if not self.ctx.obj.get('api', False):
documents = yaml.safe_load_all(open(self.file).read())
armada_obj = Manifest(documents).get_manifest()
prefix = armada_obj.get(const.KEYWORD_ARMADA).get(
const.KEYWORD_PREFIX)
return parser
for group in armada_obj.get(const.KEYWORD_ARMADA).get(
const.KEYWORD_GROUPS):
for ch in group.get(const.KEYWORD_CHARTS):
release_name = release_prefix(
prefix, ch.get('chart').get('chart_name'))
def take_action(self, parsed_args):
testService(parsed_args)
if release_name in known_release_names:
self.logger.info('RUNNING: %s tests', release_name)
resp = tiller.testing_release(release_name)
if not resp:
continue
test_status = getattr(
resp.info.status, 'last_test_suite_run',
'FAILED')
if test_status.results[0].status:
self.logger.info("PASSED: %s", release_name)
else:
self.logger.info("FAILED: %s", release_name)
else:
self.logger.info(
'Release %s not found - SKIPPING',
release_name)
else:
client = self.ctx.obj.get('CLIENT')
query = {
'tiller_host': self.tiller_host,
'tiller_port': self.tiller_port
}
with open(self.filename, 'r') as f:
resp = client.get_test_manifest(manifest=f.read(),
query=query)
for test in resp.get('tests'):
self.logger.info('Test State: %s', test)
for item in test.get('tests').get(test):
self.logger.info(item)
self.logger.info(resp)

View File

@ -12,41 +12,96 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from cliff import command as cmd
import click
from armada.cli import CliAction
from armada.handlers.tiller import Tiller
from oslo_config import cfg
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
@click.group()
def tiller():
""" Tiller Services actions
CONF = cfg.CONF
"""
def tillerServer(args):
DESC = """
This command gets tiller information
tiller = Tiller()
The tiller command uses flags to obtain information from tiller services
if args.status:
resp = tiller.tiller_version()
LOG.info('Tiller Service: %s', tiller.tiller_status())
LOG.info('Tiller Version: %s', getattr(resp.Version, 'sem_ver', False))
To obtain armada deployed releases:
if args.releases:
for release in tiller.list_releases():
LOG.info("Release: %s ( namespace= %s )", release.name,
release.namespace)
$ armada tiller --releases
To obtain tiller service status/information:
$ armada tiller --status
"""
SHORT_DESC = "command gets tiller infromation"
class TillerServerCommand(cmd.Command):
def get_parser(self, prog_name):
parser = super(TillerServerCommand, self).get_parser(prog_name)
parser.add_argument('--status', action='store_true',
default=False, help='Check Tiller service')
parser.add_argument('--releases', action='store_true',
default=False, help='List Tiller Releases')
return parser
@tiller.command(name='tiller', help=DESC, short_help=SHORT_DESC)
@click.option('--tiller-host', help="Tiller host ip", default=None)
@click.option(
'--tiller-port', help="Tiller host port", type=int, default=44134)
@click.option('--releases', help="list of deployed releses", is_flag=True)
@click.option('--status', help="Status of Armada services", is_flag=True)
@click.pass_context
def tiller_service(ctx, tiller_host, tiller_port, releases, status):
TillerServices(ctx, tiller_host, tiller_port, releases, status).invoke()
def take_action(self, parsed_args):
tillerServer(parsed_args)
class TillerServices(CliAction):
def __init__(self, ctx, tiller_host, tiller_port, releases, status):
super(TillerServices, self).__init__()
self.ctx = ctx
self.tiller_host = tiller_host
self.tiller_port = tiller_port
self.releases = releases
self.status = status
def invoke(self):
tiller = Tiller(
tiller_host=self.tiller_host, tiller_port=self.tiller_port)
if self.status:
if not self.ctx.obj.get('api', False):
self.logger.info('Tiller Service: %s', tiller.tiller_status())
self.logger.info('Tiller Version: %s', tiller.tiller_version())
else:
client = self.ctx.obj.get('CLIENT')
query = {
'tiller_host': self.tiller_host,
'tiller_port': self.tiller_port
}
resp = client.get_status(query=query)
tiller_status = resp.get('tiller').get('state', False)
tiller_version = resp.get('tiller').get('version')
self.logger.info("Tiller Service: %s", tiller_status)
self.logger.info("Tiller Version: %s", tiller_version)
if self.releases:
if not self.ctx.obj.get('api', False):
for release in tiller.list_releases():
self.logger.info(
"Release %s in namespace: %s",
release.name, release.namespace)
else:
client = self.ctx.obj.get('CLIENT')
query = {
'tiller_host': self.tiller_host,
'tiller_port': self.tiller_port
}
resp = client.get_releases(query=query)
for namespace in resp.get('releases'):
for release in resp.get('releases').get(namespace):
self.logger.info(
'Release %s in namespace: %s', release,
namespace)

View File

@ -12,39 +12,68 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from cliff import command as cmd
import click
import yaml
from armada.utils.lint import validate_armada_documents, validate_armada_object
from armada.cli import CliAction
from armada.utils.lint import validate_armada_documents
from armada.utils.lint import validate_armada_object
from armada.handlers.manifest import Manifest
from oslo_config import cfg
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
@click.group()
def validate():
""" Test Manifest Charts
CONF = cfg.CONF
"""
def validateYaml(args):
documents = yaml.safe_load_all(open(args.file).read())
manifest_obj = Manifest(documents).get_manifest()
obj_check = validate_armada_object(manifest_obj)
doc_check = validate_armada_documents(documents)
DESC = """
This command validates Armada Manifest
try:
if doc_check and obj_check:
LOG.info('Successfully validated: %s', args.file)
except Exception:
raise Exception('Failed to validate: %s', args.file)
The validate argument must be a relative path to Armada manifest
$ armada validate examples/simple.yaml
"""
SHORT_DESC = "command validates Armada Manifest"
class ValidateYamlCommand(cmd.Command):
def get_parser(self, prog_name):
parser = super(ValidateYamlCommand, self).get_parser(prog_name)
parser.add_argument('file', type=str, metavar='FILE',
help='Armada yaml file to validate')
return parser
@validate.command(name='validate', help=DESC, short_help=SHORT_DESC)
@click.argument('filename')
@click.pass_context
def validate_manifest(ctx, filename):
ValidateManifest(ctx, filename).invoke()
def take_action(self, parsed_args):
validateYaml(parsed_args)
class ValidateManifest(CliAction):
def __init__(self, ctx, filename):
super(ValidateManifest, self).__init__()
self.ctx = ctx
self.filename = filename
def invoke(self):
if not self.ctx.obj.get('api', False):
documents = yaml.safe_load_all(open(self.filename).read())
manifest_obj = Manifest(documents).get_manifest()
obj_check = validate_armada_object(manifest_obj)
doc_check = validate_armada_documents(documents)
try:
if doc_check and obj_check:
self.logger.info(
'Successfully validated: %s', self.filename)
except Exception:
raise Exception('Failed to validate: %s', self.filename)
else:
client = self.ctx.obj.get('CLIENT')
with open(self.filename, 'r') as f:
resp = client.post_validate(f.read())
if resp.get('valid', False):
self.logger.info(
'Successfully validated: %s', self.filename)
else:
self.logger.error("Failed to validate: %s", self.filename)

107
armada/common/client.py Normal file
View File

@ -0,0 +1,107 @@
# Copyright 2017 The Armada Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import yaml
from oslo_config import cfg
from oslo_log import log as logging
from armada.exceptions import api_exceptions as err
from armada.handlers.armada import Override
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
API_VERSION = 'v{}/{}'
class ArmadaClient(object):
def __init__(self, session):
self.session = session
def _set_endpoint(self, version, action):
return API_VERSION.format(version, action)
def get_status(self, query):
endpoint = self._set_endpoint('1.0', 'status')
resp = self.session.get(endpoint, query=query)
self._check_response(resp)
return resp.json()
def get_releases(self, query):
endpoint = self._set_endpoint('1.0', 'releases')
resp = self.session.get(endpoint, query=query)
self._check_response(resp)
return resp.json()
def post_validate(self, manifest=None):
endpoint = self._set_endpoint('1.0', 'validate')
resp = self.session.post(endpoint, body=manifest)
self._check_response(resp)
return resp.json()
def post_apply(self, manifest=None, values=None, set=None, query=None):
if values or set:
document = list(yaml.safe_load_all(manifest))
override = Override(
document, overrides=set, values=values).update_manifests()
manifest = yaml.dump(override)
endpoint = self._set_endpoint('1.0', 'apply')
resp = self.session.post(endpoint, body=manifest, query=query)
self._check_response(resp)
return resp.json()
def get_test_release(self, release=None, query=None):
endpoint = self._set_endpoint('1.0', 'test/{}'.format(release))
resp = self.session.get(endpoint, query=query)
self._check_response(resp)
return resp.json()
def post_test_manifest(self, manifest=None, query=None):
endpoint = self._set_endpoint('1.0', 'tests')
resp = self.session.post(endpoint, body=manifest, query=query)
self._check_response(resp)
return resp.json()
def _check_response(self, resp):
if resp.status_code == 401:
raise err.ClientUnauthorizedError(
"Unauthorized access to %s, include valid token.".format(
resp.url))
elif resp.status_code == 403:
raise err.ClientForbiddenError(
"Forbidden access to %s" % resp.url)
elif not resp.ok:
raise err.ClientError(
"Error - received %d: %s" % (resp.status_code, resp.text))

View File

@ -20,12 +20,22 @@ armada_policies = [
name=base.ARMADA % 'create_endpoints',
check_str=base.RULE_ADMIN_REQUIRED,
description='install manifest charts',
operations=[{'path': '/v1.0/apply/', 'method': 'POST'}]),
operations=[{'path': '/api/v1.0/apply/', 'method': 'POST'}]),
policy.DocumentedRuleDefault(
name=base.ARMADA % 'validate_manifest',
check_str=base.RULE_ADMIN_REQUIRED,
description='validate installed manifest',
operations=[{'path': '/api/v1.0/validate/', 'method': 'POST'}]),
policy.DocumentedRuleDefault(
name=base.ARMADA % 'test_release',
check_str=base.RULE_ADMIN_REQUIRED,
description='validate install manifest',
operations=[{'path': '/v1.0/validate/', 'method': 'POST'}]),
operations=[{'path': '/api/v1.0/test/{release}', 'method': 'GET'}]),
policy.DocumentedRuleDefault(
name=base.ARMADA % 'test_manifest',
check_str=base.RULE_ADMIN_REQUIRED,
description='validate install manifest',
operations=[{'path': '/api/v1.0/tests/', 'method': 'POST'}]),
]

View File

@ -20,15 +20,13 @@ tiller_policies = [
name=base.TILLER % 'get_status',
check_str=base.RULE_ADMIN_REQUIRED,
description='Get tiller status',
operations=[{'path': '/v1.0/status/',
'method': 'GET'}]),
operations=[{'path': '/api/v1.0/status/', 'method': 'GET'}]),
policy.DocumentedRuleDefault(
name=base.TILLER % 'get_release',
check_str=base.RULE_ADMIN_REQUIRED,
description='Get tiller release',
operations=[{'path': '/v1.0/releases/',
'method': 'GET'}]),
operations=[{'path': '/api/v1.0/releases/', 'method': 'GET'}]),
]

96
armada/common/session.py Normal file
View File

@ -0,0 +1,96 @@
# Copyright 2017 The Armada Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import requests
from oslo_config import cfg
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class ArmadaSession(object):
"""
A session to the Armada API maintaining credentials and API options
:param string host: The armada server hostname or IP
:param int port: (optional) The service port appended if specified
:param string token: Auth token
:param string marker: (optional) external context marker
"""
def __init__(self, host, port=None, scheme='http', token=None,
marker=None):
self._session = requests.Session()
self._session.headers.update({
'X-Auth-Token': token,
'X-Context-Marker': marker
})
self.host = host
self.scheme = scheme
if port:
self.port = port
self.base_url = "{}://{}:{}/api/".format(
self.scheme, self.host, self.port)
else:
self.base_url = "{}://{}/api/".format(
self.scheme, self.host)
self.token = token
self.marker = marker
self.logger = LOG
# TODO Add keystone authentication to produce a token for this session
def get(self, endpoint, query=None):
"""
Send a GET request to armada.
:param string endpoint: URL string following hostname and API prefix
:param dict query: A dict of k, v pairs to add to the query string
:return: A requests.Response object
"""
api_url = '{}{}'.format(self.base_url, endpoint)
resp = self._session.get(
api_url, params=query, timeout=3600)
return resp
def post(self, endpoint, query=None, body=None, data=None):
"""
Send a POST request to armada. If both body and data are specified,
body will will be used.
:param string endpoint: URL string following hostname and API prefix
:param dict query: dict of k, v parameters to add to the query string
:param string body: string to use as the request body.
:param data: Something json.dumps(s) can serialize.
:return: A requests.Response object
"""
api_url = '{}{}'.format(self.base_url, endpoint)
self.logger.debug("Sending POST with armada_client session")
if body is not None:
self.logger.debug("Sending POST with explicit body: \n%s" % body)
resp = self._session.post(
api_url, params=query, data=body, timeout=3600)
else:
self.logger.debug("Sending POST with JSON body: \n%s" % str(data))
resp = self._session.post(
api_url, params=query, json=data, timeout=3600)
return resp

View File

@ -17,12 +17,13 @@ import os
from oslo_config import cfg
from armada.conf import default
from armada import const
CONF = cfg.CONF
# Load config file if exists
if (os.path.exists('etc/armada/armada.conf')):
CONF(['--config-file', 'etc/armada/armada.conf'])
if (os.path.exists(const.CONFIG_PATH)):
CONF(['--config-file', const.CONFIG_PATH])
def set_app_default_configs():

View File

@ -14,6 +14,8 @@
from oslo_config import cfg
from keystoneauth1 import loading
from armada.conf import utils
default_options = [
@ -71,7 +73,12 @@ The Keystone project domain name used for authentication.
def register_opts(conf):
conf.register_opts(default_options)
conf.register_opts(
loading.get_auth_plugin_conf_options('password'),
group='keystone_authtoken')
def list_opts():
return {'DEFAULT': default_options}
return {
'DEFAULT': default_options,
'keystone_authtoken': loading.get_auth_plugin_conf_options('password')}

View File

@ -28,3 +28,6 @@ KEYWORD_CHART = 'chart'
# Statuses
STATUS_DEPLOYED = 'DEPLOYED'
STATUS_FAILED = 'FAILED'
# Configuration File
CONFIG_PATH = '/etc/armada/armada.conf'

View File

@ -31,3 +31,21 @@ class ApiJsonException(ApiException):
'''Exception that occurs during chart cleanup.'''
message = 'There was an error listing the helm chart releases.'
class ClientUnauthorizedError(ApiException):
'''Exception that occurs during chart cleanup.'''
message = 'There was an error listing the helm chart releases.'
class ClientForbiddenError(ApiException):
'''Exception that occurs during chart cleanup.'''
message = 'There was an error listing the helm chart releases.'
class ClientError(ApiException):
'''Exception that occurs during chart cleanup.'''
message = 'There was an error listing the helm chart releases.'

View File

@ -53,8 +53,7 @@ class Armada(object):
timeout=DEFAULT_TIMEOUT,
tiller_host=None,
tiller_port=44134,
values=None,
debug=False):
values=None):
'''
Initialize the Armada Engine and establish
a connection to Tiller
@ -69,14 +68,8 @@ class Armada(object):
self.timeout = timeout
self.tiller = Tiller(tiller_host=tiller_host, tiller_port=tiller_port)
self.values = values
self.documents = list(yaml.safe_load_all(file))
self.documents = file
self.config = None
self.debug = debug
# Set debug value
# Define a default handler at INFO logging level
if self.debug:
logging.basicConfig(level=logging.DEBUG)
def get_armada_manifest(self):
return Manifest(self.documents).get_manifest()
@ -193,7 +186,7 @@ class Armada(object):
Syncronize Helm with the Armada Config(s)
'''
msg = {'installed': [], 'upgraded': [], 'diff': []}
msg = {'install': [], 'upgrade': [], 'diff': []}
# TODO: (gardlt) we need to break up this func into
# a more cleaner format
@ -314,7 +307,7 @@ class Armada(object):
timeout=wait_values.get('timeout', DEFAULT_TIMEOUT)
)
msg['upgraded'].append(prefix_chart)
msg['upgrade'].append(prefix_chart)
# process install
else:
@ -338,7 +331,7 @@ class Armada(object):
namespace=chart.namespace,
timeout=wait_values.get('timeout', 3600))
msg['installed'].append(prefix_chart)
msg['install'].append(prefix_chart)
LOG.debug("Cleaning up chart source in %s",
chartbuilder.source_directory)

View File

@ -15,7 +15,9 @@
import re
import time
from kubernetes import client, config, watch
from kubernetes import client
from kubernetes import config
from kubernetes import watch
from kubernetes.client.rest import ApiException
from oslo_config import cfg
from oslo_log import log as logging
@ -37,7 +39,10 @@ class K8s(object):
'''
Initialize connection to Kubernetes
'''
config.load_kube_config()
try:
config.load_incluster_config()
except:
config.load_kube_config()
self.client = client.CoreV1Api()
self.batch_api = client.BatchV1Api()

View File

@ -309,9 +309,6 @@ class Tiller(object):
LOG.info("Wait: %s, Timeout: %s", wait, timeout)
if timeout > self.timeout:
self.timeout = timeout
if values is None:
values = Config(raw='')
else:
@ -349,8 +346,9 @@ class Tiller(object):
try:
stub = ReleaseServiceStub(self.channel)
release_request = TestReleaseRequest(name=release, timeout=timeout,
cleanup=cleanup)
release_request = TestReleaseRequest(
name=release, timeout=timeout, cleanup=cleanup)
content = self.get_release_content(release)
@ -417,9 +415,11 @@ class Tiller(object):
stub = ReleaseServiceStub(self.channel)
release_request = GetVersionRequest()
return stub.GetVersion(
tiller_version = stub.GetVersion(
release_request, self.timeout, metadata=self.metadata)
return getattr(tiller_version.Version, 'sem_ver', None)
except Exception:
raise ex.TillerVersionException()

View File

@ -12,39 +12,81 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
from urllib.parse import urlparse
import click
from oslo_config import cfg
from oslo_log import log
from cliff import app
from cliff import commandmanager as cm
import armada
from armada.cli.apply import apply_create
from armada.cli.test import test_charts
from armada.cli.tiller import tiller_service
from armada.cli.validate import validate_manifest
from armada.common.client import ArmadaClient
from armada.common.session import ArmadaSession
CONF = cfg.CONF
class ArmadaApp(app.App):
def __init__(self, **kwargs):
super(ArmadaApp, self).__init__(
description='Armada - Upgrade and deploy your charts',
version=armada.__version__,
command_manager=cm.CommandManager('armada'),
**kwargs)
@click.group()
@click.option(
'--debug/--no-debug', help='Enable or disable debugging', default=False)
@click.option(
'--api/--no-api', help='Execute service endpoints. (requires url option)',
default=False)
@click.option(
'--url', help='Armada Service Endpoint', envvar='HOST', default=None)
@click.option(
'--token', help='Keystone Service Token', envvar='TOKEN', default=None)
@click.pass_context
def main(ctx, debug, api, url, token):
"""
Multi Helm Chart Deployment Manager
def build_option_parser(self, description, version, argparse_kwargs=None):
parser = super(ArmadaApp, self).build_option_parser(
description, version, argparse_kwargs)
return parser
Common actions from this point include:
def configure_logging(self):
super(ArmadaApp, self).configure_logging()
log.register_options(CONF)
log.set_defaults(default_log_levels=CONF.default_log_levels)
log.setup(CONF, 'armada')
\b
$ armada apply
$ armada test
$ armada tiller
$ armada validate
Environment:
\b
$TOKEN set auth token
$HOST set armada service host endpoint
This tool will communicate with deployed Tiller in your Kubernetes cluster.
"""
if not ctx.obj:
ctx.obj = {}
if api:
if not url or not token:
raise click.ClickException(
'When api option is enable user needs to pass url')
else:
ctx.obj['api'] = api
parsed_url = urlparse(url)
ctx.obj['CLIENT'] = ArmadaClient(
ArmadaSession(
host=parsed_url.netloc,
scheme=parsed_url.scheme,
token=token)
)
log.register_options(CONF)
if debug:
CONF.debug = debug
log.set_defaults(default_log_levels=CONF.default_log_levels)
log.setup(CONF, 'armada')
def main(argv=None):
if argv is None:
argv = sys.argv[1:]
return ArmadaApp().run(argv)
main.add_command(apply_create)
main.add_command(test_charts)
main.add_command(tiller_service)
main.add_command(validate_manifest)

View File

@ -38,7 +38,7 @@ class TestAPI(APITestCase):
@mock.patch('armada.api.armada_controller.Handler')
def test_armada_apply(self, mock_armada):
'''
Test /armada/apply endpoint
Test /api/v1.0/apply endpoint
'''
mock_armada.sync.return_value = None
@ -54,7 +54,7 @@ class TestAPI(APITestCase):
doc = {u'message': u'Success'}
result = self.simulate_post(path='/armada/apply', body=body)
result = self.simulate_post(path='/api/v1.0/apply', body=body)
self.assertEqual(result.json, doc)
@unittest.skip('Test does not handle auth/policy correctly')
@ -62,6 +62,7 @@ class TestAPI(APITestCase):
def test_tiller_status(self, mock_tiller):
'''
Test /status endpoint
Test /api/v1.0/status endpoint
'''
# Mock tiller status value
@ -70,11 +71,13 @@ class TestAPI(APITestCase):
# FIXME(lamt) This variable is unused. Uncomment when it is.
# doc = {u'message': u'Tiller Server is Active'}
result = self.simulate_get('/v1.0/status')
result = self.simulate_get('/api/v1.0/status')
# TODO(lamt) This should be HTTP_401 if no auth is happening, but auth
# is not implemented currently, so it falls back to a policy check
# failure, thus a 403. Change this once it is completed
# Fails due to invalid access
self.assertEqual(falcon.HTTP_403, result.status)
# FIXME(lamt) Need authentication - mock, fixture
@ -84,7 +87,7 @@ class TestAPI(APITestCase):
@mock.patch('armada.api.tiller_controller.Tiller')
def test_tiller_releases(self, mock_tiller):
'''
Test /tiller/releases endpoint
Test /api/v1.0/releases endpoint
'''
# Mock tiller status value
@ -93,7 +96,7 @@ class TestAPI(APITestCase):
# FIXME(lamt) This variable is unused. Uncomment when it is.
# doc = {u'releases': {}}
result = self.simulate_get('/v1.0/releases')
result = self.simulate_get('/api/v1.0/releases')
# TODO(lamt) This should be HTTP_401 if no auth is happening, but auth
# is not implemented currently, so it falls back to a policy check

View File

@ -7,15 +7,42 @@ Commands
.. code:: bash
Usage: armada apply FILE
Usage: armada apply [OPTIONS] FILENAME
This command install and updates charts defined in armada manifest
The apply argument must be relative path to Armada Manifest. Executing
apply commnad once will install all charts defined in manifest. Re-
executing apply commnad will execute upgrade.
To see how to create an Armada manifest:
http://armada-helm.readthedocs.io/en/latest/operations/
To obtain install/upgrade charts:
$ armada apply examples/simple.yaml
To obtain override manifest:
$ armada apply examples/simple.yaml --set manifest:simple-armada:relase_name="wordpress"
or
$ armada apply examples/simple.yaml --values examples/simple-ovr-values.yaml
Options:
[-h] [--dry-run] [--debug-logging] [--disable-update-pre]
[--disable-update-post] [--enable-chart-cleanup] [--wait]
[--timeout TIMEOUT]
--api Contacts service endpoint
--disable-update-post run charts without install
--disable-update-pre run charts without install
--dry-run run charts without install
--enable-chart-cleanup Clean up Unmanaged Charts
--set TEXT
--tiller-host TEXT Tiller host ip
--tiller-port INTEGER Tiller host port
--timeout INTEGER specifies time to wait for charts
-f, --values TEXT
--wait wait until all charts deployed
--help Show this message and exit.
Synopsis
--------

View File

@ -7,11 +7,28 @@ Commands
.. code:: bash
Usage: armada test
Usage: armada test [OPTIONS]
This command test deployed charts
The tiller command uses flags to obtain information from tiller services.
The test command will run the release chart tests either via a
manifest or by targeting a relase.
To obtain armada deployed releases:
$ armada test --file examples/simple.yaml
To test release:
$ armada test --release blog-1
Options:
[-h] [--release RELEASE] [--file FILE]
--file TEXT armada manifest
--release TEXT helm release
--tiller-host TEXT Tiller Host IP
--tiller-port INTEGER Tiller host Port
--help Show this message and exit.
Synopsis

View File

@ -7,12 +7,26 @@ Commands
.. code:: bash
Usage: armada tiller
Usage: armada tiller [OPTIONS]
This command gets tiller information
The tiller command uses flags to obtain information from tiller services
To obtain armada deployed releases:
$ armada tiller --releases
To obtain tiller service status/information:
$ armada tiller --status
Options:
[-h] [--status] [--releases]
--tiller-host TEXT Tiller host ip
--tiller-port INTEGER Tiller host port
--releases list of deployed releases
--status Status of Armada services
--help Show this message and exit.
Synopsis
--------

View File

@ -7,11 +7,16 @@ Commands
.. code:: bash
Usage: armada validate FILE
Usage: armada validate [OPTIONS] FILENAME
This command validates Armada Manifest
The validate argument must be a relative path to Armada manifest
$ armada validate examples/simple.yaml
Options:
[-h]
--help Show this message and exit.
Synopsis
--------

View File

@ -13,8 +13,7 @@ To use the docker containter to develop:
.. code-block:: bash
git clone http://github.com/att-comdev/armada.git
cd armada
git clone http://github.com/att-comdev/armada.git && cd armada
pip install tox
@ -23,7 +22,7 @@ To use the docker containter to develop:
docker build . -t armada/latest
docker run -d --name armada -v ~/.kube/config:/armada/.kube/config -v $(pwd)/etc:/armada/etc armada:local
docker run -d --name armada -v ~/.kube/:/armada/.kube/ -v $(pwd)/etc:/etc armada:local
.. note::
@ -45,7 +44,8 @@ From the directory of the forked repository:
git clone http://github.com/att-comdev/armada.git && cd armada
virtualenv venv
virtualenv -p python3 venv
pip install -r requirements.txt -r test-requirements.txt
@ -53,6 +53,7 @@ From the directory of the forked repository:
# Testing your armada code
# The tox command will execute lint, bandit, cover
pip install tox
tox
# For targeted test
@ -60,7 +61,6 @@ From the directory of the forked repository:
tox -e bandit
tox -e cover
# policy and config are used in order to use and configure Armada API
tox -e genconfig
tox -e genpolicy

View File

@ -1,87 +1,532 @@
Armada RESTful API
===================
Armada Restful API v1.0
=======================
Armada Endpoints
Description
~~~~~~~~~~~
The Armada API provides the services similar to the cli via Restful endpoints
Base URL
~~~~~~~~
https://armada.localhost/api/v1.0/
DEFAULT
~~~~~~~
GET ``/releases``
-----------------
::
Endpoint: POST /armada/apply
Summary
+++++++
:string file The yaml file to apply
:>json boolean debug Enable debug logging
:>json boolean disable_update_pre
:>json boolean disable_update_post
:>json boolean enable_chart_cleanup
:>json boolean skip_pre_flight
:>json object values Override manifest values
:>json boolean dry_run
:>json boolean wait
:>json float timeout
Get tiller releases
::
Request:
Request
+++++++
Responses
+++++++++
**200**
^^^^^^^
obtain all running releases
**Example:**
.. code-block:: javascript
{
"file": "examples/openstack-helm.yaml",
"options": {
"debug": true,
"disable_update_pre": false,
"disable_update_post": false,
"enable_chart_cleanup": false,
"skip_pre_flight": false,
"dry_run": false,
"wait": false,
"timeout": false
"message": {
"namespace": [
"armada-release",
"armada-release"
],
"default": [
"armada-release",
"armada-release"
]
}
}
::
**403**
^^^^^^^
Results:
Unable to Authorize or Permission
**405**
^^^^^^^
Failed to perform action
GET ``/status``
---------------
Summary
+++++++
Get armada running state
Request
+++++++
Responses
+++++++++
**200**
^^^^^^^
obtain armada status
**Example:**
.. code-block:: javascript
{
"message": "success"
}
Tiller Endpoints
-----------------
::
Endpoint: GET /tiller/releases
Description: Retrieves tiller releases.
::
Results:
{
"releases": {
"armada-memcached": "openstack",
"armada-etcd": "openstack",
"armada-keystone": "openstack",
"armada-rabbitmq": "openstack",
"armada-horizon": "openstack"
"message": {
"tiller": {
"state": True,
"version": "v2.5.0"
}
}
}
**403**
^^^^^^^
::
Endpoint: GET /tiller/status
Retrieves the status of the Tiller server.
Unable to Authorize or Permission
::
**405**
^^^^^^^
Results:
Failed to perform action
GET ``/validate``
-----------------
Summary
+++++++
Get tiller releases
Request
+++++++
Responses
+++++++++
**200**
^^^^^^^
obtain all running releases
**Example:**
.. code-block:: javascript
{
"message": Tiller Server is Active
"valid": true
}
**403**
^^^^^^^
Unable to Authorize or Permission
**405**
^^^^^^^
Failed to perform action
POST ``/apply``
---------------
Summary
+++++++
Install/Update Armada Manifest
Request
+++++++
Body
^^^^
.. csv-table::
:delim: |
:header: "Name", "Required", "Type", "Format", "Properties", "Description"
:widths: 20, 10, 15, 15, 30, 25
disable-update-post | boolean | | | |
disable-update-pre | boolean | | | |
dry-run | boolean | | | |
enable-chart-cleanup | boolean | | | |
tiller-host | string | | | |
tiller-port | int | | | |
timeout | int | | | |
wait | boolean | | | |
**Armada schema:**
.. code-block:: javascript
{
"api": true,
"armada": {}
}
Responses
+++++++++
**200**
^^^^^^^
Succesfull installation/update of manifest
**Example:**
.. code-block:: javascript
{
"message": {
"installed": [
"armada-release",
"armada-release"
],
"updated": [
"armada-release",
"armada-release"
],
"diff": [
"values": "value diff",
"values": "value diff 2"
]
}
}
**403**
^^^^^^^
Unable to Authorize or Permission
**405**
^^^^^^^
Failed to perform action
POST ``/test/{release}``
------------------------
Summary
+++++++
Test release name
Parameters
++++++++++
.. csv-table::
:delim: |
:header: "Name", "Located in", "Required", "Type", "Format", "Properties", "Description"
:widths: 20, 15, 10, 10, 10, 20, 30
release | path | Yes | string | | | name of the release to test
Request
+++++++
Responses
+++++++++
**200**
^^^^^^^
Succesfully Test release response
**Example:**
.. code-block:: javascript
{
"message": {
"message": "armada-release",
"result": "No test found."
}
}
**403**
^^^^^^^
Unable to Authorize or Permission
**405**
^^^^^^^
Failed to perform action
POST ``/tests``
---------------
Summary
+++++++
Test manifest releases
Request
+++++++
Body
^^^^
.. csv-table::
:delim: |
:header: "Name", "Required", "Type", "Format", "Properties", "Description"
:widths: 20, 10, 15, 15, 30, 25
armada | Yes | | | |
**Armada schema:**
.. code-block:: javascript
{
"armada": {}
}
Responses
+++++++++
**200**
^^^^^^^
Succesfully Test of manifest
**Example:**
.. code-block:: javascript
{
"message": {
"failed": [
"armada-release",
"armada-release"
],
"passed": [
"armada-release",
"armada-release"
],
"skipped": [
"armada-release",
"armada-release"
]
}
}
**403**
^^^^^^^
Unable to Authorize or Permission
**405**
^^^^^^^
Failed to perform action
Data Structures
~~~~~~~~~~~~~~~
Armada Request Model Structure
------------------------------
.. csv-table::
:delim: |
:header: "Name", "Required", "Type", "Format", "Properties", "Description"
:widths: 20, 10, 15, 15, 30, 25
disable-update-post | boolean | | | |
disable-update-pre | boolean | | | |
dry-run | boolean | | | |
enable-chart-cleanup | boolean | | | |
tiller-host | string | | | |
tiller-port | int | | | |
timeout | int | | | |
wait | boolean | | | |
**Armada schema:**
Armada Response Model Structure
-------------------------------
.. csv-table::
:delim: |
:header: "Name", "Required", "Type", "Format", "Properties", "Description"
:widths: 20, 10, 15, 15, 30, 25
message | No | | | |
**Message schema:**
.. csv-table::
:delim: |
:header: "Name", "Required", "Type", "Format", "Properties", "Description"
:widths: 20, 10, 15, 15, 30, 25
installed | No | array of string | | |
updated | No | array of string | | |
values | No | array of string | | |
Releases Response Model Structure
---------------------------------
.. csv-table::
:delim: |
:header: "Name", "Required", "Type", "Format", "Properties", "Description"
:widths: 20, 10, 15, 15, 30, 25
message | No | | | |
**Message schema:**
.. csv-table::
:delim: |
:header: "Name", "Required", "Type", "Format", "Properties", "Description"
:widths: 20, 10, 15, 15, 30, 25
namespace | No | array of string | | |
Status Response Model Structure
-------------------------------
.. csv-table::
:delim: |
:header: "Name", "Required", "Type", "Format", "Properties", "Description"
:widths: 20, 10, 15, 15, 30, 25
message | No | | | |
**Message schema:**
.. csv-table::
:delim: |
:header: "Name", "Required", "Type", "Format", "Properties", "Description"
:widths: 20, 10, 15, 15, 30, 25
tiller | No | | | |
**Tiller schema:**
.. csv-table::
:delim: |
:header: "Name", "Required", "Type", "Format", "Properties", "Description"
:widths: 20, 10, 15, 15, 30, 25
state | No | string | | |
version | No | string | | |
Test Response Model Structure
-----------------------------
.. csv-table::
:delim: |
:header: "Name", "Required", "Type", "Format", "Properties", "Description"
:widths: 20, 10, 15, 15, 30, 25
message | No | | | |
**Message schema:**
.. csv-table::
:delim: |
:header: "Name", "Required", "Type", "Format", "Properties", "Description"
:widths: 20, 10, 15, 15, 30, 25
message | No | string | | |
result | No | string | | |
Tests Request Model Structure
-----------------------------
.. csv-table::
:delim: |
:header: "Name", "Required", "Type", "Format", "Properties", "Description"
:widths: 20, 10, 15, 15, 30, 25
armada | Yes | | | |
**Armada schema:**
Tests Response Model Structure
------------------------------
.. csv-table::
:delim: |
:header: "Name", "Required", "Type", "Format", "Properties", "Description"
:widths: 20, 10, 15, 15, 30, 25
message | No | | | |
**Message schema:**
.. csv-table::
:delim: |
:header: "Name", "Required", "Type", "Format", "Properties", "Description"
:widths: 20, 10, 15, 15, 30, 25
failed | No | array of string | | |
passed | No | array of string | | |
skipped | No | array of string | | |
Validate Response Model Structure
---------------------------------
.. csv-table::
:delim: |
:header: "Name", "Required", "Type", "Format", "Properties", "Description"
:widths: 20, 10, 15, 15, 30, 25
valid | No | boolean | | |

View File

@ -9,6 +9,7 @@ file can be generated via tox
.. code-block:: bash
$ tox -e genconfig
$ tox -e genpolicy
Customize your configuration based on the information below
@ -20,9 +21,9 @@ tokens
.. note::
If you do not have a keystone already deploy, then armada can deploy a keystone service.
If you do not have a keystone already deploy, then armada can deploy a keystone services:
armada apply keystone-manifest.yaml
$ armada apply keystone-manifest.yaml
.. code-block:: bash
@ -40,13 +41,13 @@ The service account must then be included in the armada.conf
.. code-block:: ini
[keystone_authtoken]
auth_type = password
auth_uri = https://<keystone-api>:5000/
auth_url = https://<keystone-api>:35357/
auth_version = 3
delay_auth_decision = true
auth_type = password
auth_url = https://<keystone-api>:35357/
project_name = service
project_domain_name = ucp
user_name = armada
user_domain_name = ucp
password = armada
project_domain_name = ucp
project_name = service
user_domain_name = ucp
user_name = armada

View File

@ -6,11 +6,7 @@ PORT="8000"
set -e
if [ "$1" = 'server' ]; then
exec uwsgi --http 0.0.0.0:${PORT} --paste config:$(pwd)/etc/armada/api-paste.ini --enable-threads -L --pyargv " --config-file $(pwd)/etc/armada/armada.conf"
fi
if [ "$1" = 'tiller' ] || [ "$1" = 'apply' ]; then
exec uwsgi --http :${PORT} --http-timeout 3600 --paste config:/etc/armada/api-paste.ini --enable-threads -L --pyargv "--config-file /etc/armada/armada.conf"
else
exec $CMD "$@"
fi
exec "$@"

View File

@ -0,0 +1,441 @@
[DEFAULT]
#
# From armada.conf
#
# IDs of approved API access roles. (list value)
#armada_apply_roles = admin
# The default Keystone authentication url. (string value)
#auth_url = http://0.0.0.0/v3
# Path to Kubernetes configurations. (string value)
#kubernetes_config_path = /home/user/.kube/
# Enables or disables Keystone authentication middleware. (boolean value)
#middleware = true
# The Keystone project domain name used for authentication. (string value)
#project_domain_name = default
# The Keystone project name used for authentication. (string value)
#project_name = admin
# Path to SSH private key. (string value)
#ssh_key_path = /home/user/.ssh/
# IDs of approved API access roles. (list value)
#tiller_release_roles = admin
# IDs of approved API access roles. (list value)
#tiller_status_roles = admin
#
# From oslo.log
#
# If set to true, the logging level will be set to DEBUG instead of the default
# INFO level. (boolean value)
# Note: This option can be changed without restarting.
#debug = false
# The name of a logging configuration file. This file is appended to any
# existing logging configuration files. For details about logging configuration
# files, see the Python logging module documentation. Note that when logging
# configuration files are used then all logging configuration is set in the
# configuration file and other logging configuration options are ignored (for
# example, logging_context_format_string). (string value)
# Note: This option can be changed without restarting.
# Deprecated group/name - [DEFAULT]/log_config
#log_config_append = <None>
# Defines the format string for %%(asctime)s in log records. Default:
# %(default)s . This option is ignored if log_config_append is set. (string
# value)
#log_date_format = %Y-%m-%d %H:%M:%S
# (Optional) Name of log file to send logging output to. If no default is set,
# logging will go to stderr as defined by use_stderr. This option is ignored if
# log_config_append is set. (string value)
# Deprecated group/name - [DEFAULT]/logfile
#log_file = <None>
# (Optional) The base directory used for relative log_file paths. This option
# is ignored if log_config_append is set. (string value)
# Deprecated group/name - [DEFAULT]/logdir
#log_dir = <None>
# Uses logging handler designed to watch file system. When log file is moved or
# removed this handler will open a new log file with specified path
# instantaneously. It makes sense only if log_file option is specified and
# Linux platform is used. This option is ignored if log_config_append is set.
# (boolean value)
#watch_log_file = false
# Use syslog for logging. Existing syslog format is DEPRECATED and will be
# changed later to honor RFC5424. This option is ignored if log_config_append
# is set. (boolean value)
#use_syslog = false
# Enable journald for logging. If running in a systemd environment you may wish
# to enable journal support. Doing so will use the journal native protocol
# which includes structured metadata in addition to log messages.This option is
# ignored if log_config_append is set. (boolean value)
#use_journal = false
# Syslog facility to receive log lines. This option is ignored if
# log_config_append is set. (string value)
#syslog_log_facility = LOG_USER
# Log output to standard error. This option is ignored if log_config_append is
# set. (boolean value)
#use_stderr = false
# Format string to use for log messages with context. (string value)
#logging_context_format_string = %(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [%(request_id)s %(user_identity)s] %(instance)s%(message)s
# Format string to use for log messages when context is undefined. (string
# value)
#logging_default_format_string = %(asctime)s.%(msecs)03d %(process)d %(levelname)s %(name)s [-] %(instance)s%(message)s
# Additional data to append to log message when logging level for the message
# is DEBUG. (string value)
#logging_debug_format_suffix = %(funcName)s %(pathname)s:%(lineno)d
# Prefix each line of exception output with this format. (string value)
#logging_exception_prefix = %(asctime)s.%(msecs)03d %(process)d ERROR %(name)s %(instance)s
# Defines the format string for %(user_identity)s that is used in
# logging_context_format_string. (string value)
#logging_user_identity_format = %(user)s %(tenant)s %(domain)s %(user_domain)s %(project_domain)s
# List of package logging levels in logger=LEVEL pairs. This option is ignored
# if log_config_append is set. (list value)
#default_log_levels = amqp=WARN,amqplib=WARN,boto=WARN,qpid=WARN,sqlalchemy=WARN,suds=INFO,oslo.messaging=INFO,oslo_messaging=INFO,iso8601=WARN,requests.packages.urllib3.connectionpool=WARN,urllib3.connectionpool=WARN,websocket=WARN,requests.packages.urllib3.util.retry=WARN,urllib3.util.retry=WARN,keystonemiddleware=WARN,routes.middleware=WARN,stevedore=WARN,taskflow=WARN,keystoneauth=WARN,oslo.cache=INFO,dogpile.core.dogpile=INFO
# Enables or disables publication of error events. (boolean value)
#publish_errors = false
# The format for an instance that is passed with the log message. (string
# value)
#instance_format = "[instance: %(uuid)s] "
# The format for an instance UUID that is passed with the log message. (string
# value)
#instance_uuid_format = "[instance: %(uuid)s] "
# Interval, number of seconds, of log rate limiting. (integer value)
#rate_limit_interval = 0
# Maximum number of logged messages per rate_limit_interval. (integer value)
#rate_limit_burst = 0
# Log level name used by rate limiting: CRITICAL, ERROR, INFO, WARNING, DEBUG
# or empty string. Logs with level greater or equal to rate_limit_except_level
# are not filtered. An empty string means that all levels are filtered. (string
# value)
#rate_limit_except_level = CRITICAL
# Enables or disables fatal status of deprecations. (boolean value)
#fatal_deprecations = false
[cors]
#
# From oslo.middleware
#
# Indicate whether this resource may be shared with the domain received in the
# requests "origin" header. Format: "<protocol>://<host>[:<port>]", no trailing
# slash. Example: https://horizon.example.com (list value)
#allowed_origin = <None>
# Indicate that the actual request can include user credentials (boolean value)
#allow_credentials = true
# Indicate which headers are safe to expose to the API. Defaults to HTTP Simple
# Headers. (list value)
#expose_headers =
# Maximum cache age of CORS preflight requests. (integer value)
#max_age = 3600
# Indicate which methods can be used during the actual request. (list value)
#allow_methods = OPTIONS,GET,HEAD,POST,PUT,DELETE,TRACE,PATCH
# Indicate which header field names may be used during the actual request.
# (list value)
#allow_headers =
[healthcheck]
#
# From oslo.middleware
#
# DEPRECATED: The path to respond to healtcheck requests on. (string value)
# This option is deprecated for removal.
# Its value may be silently ignored in the future.
#path = /healthcheck
# Show more detailed information as part of the response (boolean value)
#detailed = false
# Additional backends that can perform health checks and report that
# information back as part of a request. (list value)
#backends =
# Check the presence of a file to determine if an application is running on a
# port. Used by DisableByFileHealthcheck plugin. (string value)
#disable_by_file_path = <None>
# Check the presence of a file based on a port to determine if an application
# is running on a port. Expects a "port:path" list of strings. Used by
# DisableByFilesPortsHealthcheck plugin. (list value)
#disable_by_file_paths =
[keystone_authtoken]
#
# From armada.conf
#
# Authentication URL (string value)
#auth_url = <None>
# Domain ID to scope to (string value)
#domain_id = <None>
# Domain name to scope to (string value)
#domain_name = <None>
# Project ID to scope to (string value)
# Deprecated group/name - [keystone_authtoken]/tenant_id
#project_id = <None>
# Project name to scope to (string value)
# Deprecated group/name - [keystone_authtoken]/tenant_name
#project_name = <None>
# Domain ID containing project (string value)
#project_domain_id = <None>
# Domain name containing project (string value)
#project_domain_name = <None>
# Trust ID (string value)
#trust_id = <None>
# Optional domain ID to use with v3 and v2 parameters. It will be used for both
# the user and project domain in v3 and ignored in v2 authentication. (string
# value)
#default_domain_id = <None>
# Optional domain name to use with v3 API and v2 parameters. It will be used
# for both the user and project domain in v3 and ignored in v2 authentication.
# (string value)
#default_domain_name = <None>
# User id (string value)
#user_id = <None>
# Username (string value)
# Deprecated group/name - [keystone_authtoken]/user_name
#username = <None>
# User's domain id (string value)
#user_domain_id = <None>
# User's domain name (string value)
#user_domain_name = <None>
# User's password (string value)
#password = <None>
#
# From keystonemiddleware.auth_token
#
# Complete "public" Identity API endpoint. This endpoint should not be an
# "admin" endpoint, as it should be accessible by all end users.
# Unauthenticated clients are redirected to this endpoint to authenticate.
# Although this endpoint should ideally be unversioned, client support in the
# wild varies. If you're using a versioned v2 endpoint here, then this should
# *not* be the same endpoint the service user utilizes for validating tokens,
# because normal end users may not be able to reach that endpoint. (string
# value)
#auth_uri = <None>
# API version of the admin Identity API endpoint. (string value)
#auth_version = <None>
# Do not handle authorization requests within the middleware, but delegate the
# authorization decision to downstream WSGI components. (boolean value)
#delay_auth_decision = false
# Request timeout value for communicating with Identity API server. (integer
# value)
#http_connect_timeout = <None>
# How many times are we trying to reconnect when communicating with Identity
# API Server. (integer value)
#http_request_max_retries = 3
# Request environment key where the Swift cache object is stored. When
# auth_token middleware is deployed with a Swift cache, use this option to have
# the middleware share a caching backend with swift. Otherwise, use the
# ``memcached_servers`` option instead. (string value)
#cache = <None>
# Required if identity server requires client certificate (string value)
#certfile = <None>
# Required if identity server requires client certificate (string value)
#keyfile = <None>
# A PEM encoded Certificate Authority to use when verifying HTTPs connections.
# Defaults to system CAs. (string value)
#cafile = <None>
# Verify HTTPS connections. (boolean value)
#insecure = false
# The region in which the identity server can be found. (string value)
#region_name = <None>
# Directory used to cache files related to PKI tokens. (string value)
#signing_dir = <None>
# Optionally specify a list of memcached server(s) to use for caching. If left
# undefined, tokens will instead be cached in-process. (list value)
# Deprecated group/name - [keystone_authtoken]/memcache_servers
#memcached_servers = <None>
# In order to prevent excessive effort spent validating tokens, the middleware
# caches previously-seen tokens for a configurable duration (in seconds). Set
# to -1 to disable caching completely. (integer value)
#token_cache_time = 300
# Determines the frequency at which the list of revoked tokens is retrieved
# from the Identity service (in seconds). A high number of revocation events
# combined with a low cache duration may significantly reduce performance. Only
# valid for PKI tokens. (integer value)
#revocation_cache_time = 10
# (Optional) If defined, indicate whether token data should be authenticated or
# authenticated and encrypted. If MAC, token data is authenticated (with HMAC)
# in the cache. If ENCRYPT, token data is encrypted and authenticated in the
# cache. If the value is not one of these options or empty, auth_token will
# raise an exception on initialization. (string value)
# Allowed values: None, MAC, ENCRYPT
#memcache_security_strategy = None
# (Optional, mandatory if memcache_security_strategy is defined) This string is
# used for key derivation. (string value)
#memcache_secret_key = <None>
# (Optional) Number of seconds memcached server is considered dead before it is
# tried again. (integer value)
#memcache_pool_dead_retry = 300
# (Optional) Maximum total number of open connections to every memcached
# server. (integer value)
#memcache_pool_maxsize = 10
# (Optional) Socket timeout in seconds for communicating with a memcached
# server. (integer value)
#memcache_pool_socket_timeout = 3
# (Optional) Number of seconds a connection to memcached is held unused in the
# pool before it is closed. (integer value)
#memcache_pool_unused_timeout = 60
# (Optional) Number of seconds that an operation will wait to get a memcached
# client connection from the pool. (integer value)
#memcache_pool_conn_get_timeout = 10
# (Optional) Use the advanced (eventlet safe) memcached client pool. The
# advanced pool will only work under python 2.x. (boolean value)
#memcache_use_advanced_pool = false
# (Optional) Indicate whether to set the X-Service-Catalog header. If False,
# middleware will not ask for service catalog on token validation and will not
# set the X-Service-Catalog header. (boolean value)
#include_service_catalog = true
# Used to control the use and type of token binding. Can be set to: "disabled"
# to not check token binding. "permissive" (default) to validate binding
# information if the bind type is of a form known to the server and ignore it
# if not. "strict" like "permissive" but if the bind type is unknown the token
# will be rejected. "required" any form of token binding is needed to be
# allowed. Finally the name of a binding method that must be present in tokens.
# (string value)
#enforce_token_bind = permissive
# If true, the revocation list will be checked for cached tokens. This requires
# that PKI tokens are configured on the identity server. (boolean value)
#check_revocations_for_cached = false
# Hash algorithms to use for hashing PKI tokens. This may be a single algorithm
# or multiple. The algorithms are those supported by Python standard
# hashlib.new(). The hashes will be tried in the order given, so put the
# preferred one first for performance. The result of the first hash will be
# stored in the cache. This will typically be set to multiple values only while
# migrating from a less secure algorithm to a more secure one. Once all the old
# tokens are expired this option should be set to a single value for better
# performance. (list value)
#hash_algorithms = md5
# Authentication type to load (string value)
# Deprecated group/name - [keystone_authtoken]/auth_plugin
#auth_type = <None>
# Config Section from which to load plugin specific options (string value)
#auth_section = <None>
[oslo_middleware]
#
# From oslo.middleware
#
# The maximum body size for each request, in bytes. (integer value)
# Deprecated group/name - [DEFAULT]/osapi_max_request_body_size
# Deprecated group/name - [DEFAULT]/max_request_body_size
#max_request_body_size = 114688
# DEPRECATED: The HTTP Header that will be used to determine what the original
# request protocol scheme was, even if it was hidden by a SSL termination
# proxy. (string value)
# This option is deprecated for removal.
# Its value may be silently ignored in the future.
#secure_proxy_ssl_header = X-Forwarded-Proto
# Whether the application is behind a proxy or not. This determines if the
# middleware should parse the headers or not. (boolean value)
#enable_proxy_headers_parsing = false
[oslo_policy]
#
# From oslo.policy
#
# The file that defines policies. (string value)
#policy_file = policy.json
# Default rule. Enforced when a requested rule is not found. (string value)
#policy_default_rule = default
# Directories where policy configuration files are stored. They can be relative
# to any directory in the search path defined by the config_dir option, or
# absolute paths. The file defined by policy_file must exist for these
# directories to be searched. Missing or empty directories are ignored. (multi
# valued)
#policy_dirs = policy.d

33
etc/armada/policy.yaml Normal file
View File

@ -0,0 +1,33 @@
#
#"admin_required": "role:admin"
#
#"service_or_admin": "rule:admin_required or rule:service_role"
#
#"service_role": "role:service"
# install manifest charts
# POST api/v1.0/apply/
#"armada:create_endpoints": "rule:admin_required"
# validate installed manifest
# POST /api/v1.0/validate/
#"armada:validate_manifest": "rule:admin_required"
# validate install manifest
# GET /api/v1.0/test/{release}
#"armada:test_release": "rule:admin_required"
# validate install manifest
# POST /api/v1.0/tests/
#"armada:test_manifest": "rule:admin_required"
# Get tiller status
# GET /api/v1.0/status/
#"tiller:get_status": "rule:admin_required"
# Get tiller release
# GET /api/v1.0/releases/
#"tiller:get_release": "rule:admin_required"

View File

@ -0,0 +1,13 @@
---
schema: armada/Chart/v1
metadata:
schema: metadata/Document/v1
name: keystone
data:
values:
bootstrap:
script: |
openstack domain create 'ucp'
openstack project create --domain 'ucp' 'service'
openstack user create --domain ucp --project service --project-domain 'ucp' --password armada armada
openstack role add --project-domain ucp --user-domain ucp --user armada --project service admin

View File

@ -10,7 +10,7 @@ data:
values: {}
source:
type: git
location: git://github.com/openstack/openstack-helm
location: https://git.openstack.org/openstack/openstack-helm
subpath: helm-toolkit
reference: master
dependencies: []
@ -34,7 +34,7 @@ data:
values: {}
source:
type: git
location: git://github.com/openstack/openstack-helm
location: https://git.openstack.org/openstack/openstack-helm
subpath: mariadb
reference: master
dependencies:
@ -59,7 +59,7 @@ data:
values: {}
source:
type: git
location: git://github.com/openstack/openstack-helm
location: https://git.openstack.org/openstack/openstack-helm
subpath: memcached
reference: master
dependencies:
@ -82,11 +82,18 @@ data:
no_hooks: false
upgrade:
no_hooks: false
pre:
delete:
- name: keystone-bootstrap
type: job
labels:
- application: keystone
- component: bootstrap
values:
replicas: 2
replicas: 3
source:
type: git
location: git://github.com/openstack/openstack-helm
location: https://git.openstack.org/openstack/openstack-helm
subpath: keystone
reference: master
dependencies:

View File

@ -1,4 +1,4 @@
gitpython==2.1.5
gitpython
grpcio==1.6.0rc1
grpcio-tools==1.6.0rc1
keystoneauth1==2.21.0
@ -6,18 +6,17 @@ keystonemiddleware==4.9.1
kubernetes>=1.0.0
protobuf>=3.4.0
PyYAML==3.12
requests==2.17.3
requests
supermutes==0.2.5
urllib3==1.21.1
Paste>=2.0.3
PasteDeploy>=1.5.2
# API
falcon==1.1.0
falcon
uwsgi>=2.0.15
# CLI
cliff==2.7.0
click>=6.7
# Oslo
oslo.cache>=1.5.0 # Apache-2.0

View File

@ -40,11 +40,6 @@ upload-dir = doc/build/html
[entry_points]
console_scripts =
armada = armada.shell:main
armada =
apply = armada.cli.apply:ApplyChartsCommand
tiller = armada.cli.tiller:TillerServerCommand
validate = armada.cli.validate:ValidateYamlCommand
test = armada.cli.test:TestServerCommand
oslo.config.opts =
armada.conf = armada.conf.opts:list_opts
oslo.policy.policies =

View File

@ -1,5 +1,9 @@
#!/usr/bin/env bash
if [ -x $(which openstack) ]; then
pip install python-openstackclient
fi
openstack domain create 'ucp'
openstack project create --domain 'ucp' 'service'
openstack user create --domain ucp --project service --project-domain 'ucp' --password armada armada