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 ChangeLog
LICENSE LICENSE
OWNERS 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 .installed.cfg
*.egg *.egg
etc/*.sample etc/*.sample
etc/hostname
etc/hosts
etc/resolv.conf
# PyInstaller # PyInstaller
# Usually these files are written by a python script from a template # Usually these files are written by a python script from a template
@ -97,3 +100,9 @@ ENV/
**/*.tgz **/*.tgz
**/_partials.tpl **/_partials.tpl
**/_globals.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 MAINTAINER Armada Team
ENV DEBIAN_FRONTEND noninteractive ENV DEBIAN_FRONTEND noninteractive
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
COPY . /armada COPY . /armada

View File

@ -10,22 +10,32 @@
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License
import json import json
import uuid
import logging as log import logging as log
import os
import uuid
import yaml
import falcon import falcon
from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
LOG = logging.getLogger(__name__) from armada import const
CONF = cfg.CONF
class BaseResource(object): class BaseResource(object):
def __init__(self): 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): def on_options(self, req, resp):
self_attrs = dir(self) self_attrs = dir(self)
@ -39,29 +49,23 @@ class BaseResource(object):
resp.headers['Allow'] = ','.join(allowed_methods) resp.headers['Allow'] = ','.join(allowed_methods)
resp.status = falcon.HTTP_200 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: if req.content_length is None or req.content_length == 0:
return None return None
if req.content_type is not None and req.content_type.lower( raw_body = req.stream.read(req.content_length or 0)
) == 'application/json':
raw_body = req.stream.read(req.content_length or 0)
if raw_body is None: if raw_body is None:
return None return None
try: try:
# json_body = json.loads(raw_body.decode('utf-8')) return yaml.safe_load_all(raw_body.decode('utf-8'))
# return json_body except yaml.YAMLError as jex:
return raw_body self.error(
except json.JSONDecodeError as jex: req.context,
self.error( "Invalid YAML in request: \n%s" % raw_body.decode('utf-8'))
req.context, raise Exception(
"Invalid JSON in request: \n%s" % raw_body.decode('utf-8')) "%s: Invalid YAML in body: %s" % (req.path, jex))
raise json.JSONDecodeError("%s: Invalid JSON in body: %s" %
(req.path, jex))
else:
raise json.JSONDecodeError("Requires application/json payload")
def return_error(self, resp, status_code, message="", retry=False): def return_error(self, resp, status_code, message="", retry=False):
resp.body = json.dumps({ 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 json
import falcon import falcon
from oslo_config import cfg
from oslo_log import log as logging
from armada import api from armada import api
from armada.common import policy from armada.common import policy
from armada.handlers.tiller import Tiller from armada.handlers.tiller import Tiller
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
class Status(api.BaseResource): class Status(api.BaseResource):
@policy.enforce('tiller:get_status') @policy.enforce('tiller:get_status')
@ -33,21 +28,27 @@ class Status(api.BaseResource):
get tiller status get tiller status
''' '''
try: 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): message = {
resp.status = falcon.HTTP_200 'tiller': {
else: 'state': tiller.tiller_status(),
resp.status = falcon.HTTP_503 'version': tiller.tiller_version()
}
}
resp.data = json.dumps(message) resp.status = falcon.HTTP_200
resp.body = json.dumps(message)
resp.content_type = 'application/json' resp.content_type = 'application/json'
except Exception as e: 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( self.return_error(
resp, falcon.HTTP_500, resp, falcon.HTTP_500, message=err_message)
message="Unable to get status: {}".format(e))
class Release(api.BaseResource): class Release(api.BaseResource):
@ -58,21 +59,23 @@ class Release(api.BaseResource):
''' '''
try: try:
# Get tiller releases # 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 = {} releases = {}
for release in handler.list_releases(): for release in tiller.list_releases():
if not releases.get(release.namespace, None): if not releases.get(release.namespace, None):
releases[release.namespace] = [] releases[release.namespace] = []
releases[release.namespace].append(release.name) releases[release.namespace].append(release.name)
resp.data = json.dumps({'releases': releases}) resp.body = json.dumps({'releases': releases})
resp.content_type = 'application/json' resp.content_type = 'application/json'
resp.status = falcon.HTTP_200 resp.status = falcon.HTTP_200
except Exception as e: 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( self.return_error(
resp, falcon.HTTP_500, resp, falcon.HTTP_500, message=err_message)
message="Unable to find Releases: {}".format(e))

View File

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

View File

@ -17,18 +17,20 @@ from uuid import UUID
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
LOG = logging.getLogger(__name__)
CONF = cfg.CONF CONF = cfg.CONF
class AuthMiddleware(object): class AuthMiddleware(object):
def __init__(self):
self.logger = logging.getLogger(__name__)
# Authentication # Authentication
def process_request(self, req, resp): def process_request(self, req, resp):
ctx = req.context ctx = req.context
for k, v in req.headers.items(): 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') auth_status = req.get_header('X-SERVICE-IDENTITY-STATUS')
service = True service = True
@ -65,8 +67,9 @@ class AuthMiddleware(object):
else: else:
ctx.is_admin_project = False ctx.is_admin_project = False
LOG.debug('Request from authenticated user %s with roles %s' % self.logger.debug(
(ctx.user, ','.join(ctx.roles))) 'Request from authenticated user %s with roles %s' %
(ctx.user, ','.join(ctx.roles)))
else: else:
ctx.authenticated = False ctx.authenticated = False
@ -91,6 +94,9 @@ class ContextMiddleware(object):
class LoggingMiddleware(object): class LoggingMiddleware(object):
def __init__(self):
self.logger = logging.getLogger(__name__)
def process_response(self, req, resp, resource, req_succeeded): def process_response(self, req, resp, resource, req_succeeded):
ctx = req.context ctx = req.context
extra = { extra = {
@ -99,4 +105,4 @@ class LoggingMiddleware(object):
'external_ctx': ctx.external_marker, 'external_ctx': ctx.external_marker,
} }
resp.append_header('X-Armada-Req', ctx.request_id) 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 # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import os
import falcon import falcon
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging
from armada import conf from armada import conf
from armada.api import ArmadaRequest 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 AuthMiddleware
from armada.api.middleware import ContextMiddleware from armada.api.middleware import ContextMiddleware
from armada.api.middleware import LoggingMiddleware from armada.api.middleware import LoggingMiddleware
from armada.api.tiller_controller import Release from armada.api.controller.test import Test
from armada.api.tiller_controller import Status from armada.api.controller.test import Tests
from armada.api.validation_controller import Validate 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 from armada.common import policy
LOG = logging.getLogger(__name__)
conf.set_app_default_configs() conf.set_app_default_configs()
CONF = cfg.CONF CONF = cfg.CONF
# Build API # Build API
def create(middleware=CONF.middleware): 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() policy.setup_policy()
@ -55,13 +49,17 @@ def create(middleware=CONF.middleware):
api = falcon.API(request_type=ArmadaRequest) api = falcon.API(request_type=ArmadaRequest)
# Configure API routing # Configure API routing
url_routes_v1 = (('apply', Apply()), url_routes_v1 = (
('releases', Release()), ('apply', Apply()),
('status', Status()), ('releases', Release()),
('validate', Validate())) ('status', Status()),
('tests', Tests()),
('test/{release}', Test()),
('validate', Validate()),
)
for route, service in url_routes_v1: 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 return api

View File

@ -11,3 +11,22 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # 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 # See the License for the specific language governing permissions and
# limitations under the License. # 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 from armada.handlers.armada import Armada
CONF = cfg.CONF
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()
class ApplyChartsCommand(cmd.Command): @click.group()
def get_parser(self, prog_name): def apply():
parser = super(ApplyChartsCommand, self).get_parser(prog_name) """ Apply manifest to cluster
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')
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): The apply argument must be relative path to Armada Manifest. Executing apply
applyCharts(parsed_args) 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 import yaml
from cliff import command as cmd import click
from oslo_config import cfg
from oslo_log import log as logging
from armada.cli import CliAction
from armada import const from armada import const
from armada.handlers.manifest import Manifest from armada.handlers.manifest import Manifest
from armada.handlers.tiller import Tiller from armada.handlers.tiller import Tiller
from armada.utils.release import release_prefix 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) The tiller command uses flags to obtain information from tiller services.
known_release_names = [release[0] for release in tiller.list_charts()] The test command will run the release chart tests either via a the manifest or
by targetings a relase.
if args.release: To test armada deployed releases:
LOG.info("RUNNING: %s tests", args.release)
resp = tiller.testing_release(args.release)
if not resp: $ armada test --file examples/simple.yaml
LOG.info("FAILED: %s", args.release)
return
test_status = getattr(resp.info.status, 'last_test_suite_run', To test release:
'FAILED')
if test_status.results[0].status:
LOG.info("PASSED: %s", args.release)
else:
LOG.info("FAILED: %s", args.release)
if args.file: $ armada test --release blog-1
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)
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: SHORT_DESC = "command test releases"
LOG.info('RUNNING: %s tests', release_name)
resp = tiller.testing_release(release_name)
if not resp:
continue
test_status = getattr(resp.info.status, @test.command(name='test', help=DESC, short_help=SHORT_DESC)
'last_test_suite_run', 'FAILED') @click.option('--file', help='armada manifest', type=str)
if test_status.results[0].status: @click.option('--release', help='helm release', type=str)
LOG.info("PASSED: %s", release_name) @click.option('--tiller-host', help="Tiller Host IP")
else: @click.option(
LOG.info("FAILED: %s", release_name) '--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: 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): if self.file:
def get_parser(self, prog_name): if not self.ctx.obj.get('api', False):
parser = super(TestServerCommand, self).get_parser(prog_name) documents = yaml.safe_load_all(open(self.file).read())
parser.add_argument( armada_obj = Manifest(documents).get_manifest()
'--release', action='store', help='testing Helm in Release') prefix = armada_obj.get(const.KEYWORD_ARMADA).get(
parser.add_argument( const.KEYWORD_PREFIX)
'-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')
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): if release_name in known_release_names:
testService(parsed_args) 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 # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from cliff import command as cmd
import click
from armada.cli import CliAction
from armada.handlers.tiller import Tiller 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: To obtain armada deployed releases:
resp = tiller.tiller_version()
LOG.info('Tiller Service: %s', tiller.tiller_status())
LOG.info('Tiller Version: %s', getattr(resp.Version, 'sem_ver', False))
if args.releases: $ armada tiller --releases
for release in tiller.list_releases():
LOG.info("Release: %s ( namespace= %s )", release.name, To obtain tiller service status/information:
release.namespace)
$ armada tiller --status
"""
SHORT_DESC = "command gets tiller infromation"
class TillerServerCommand(cmd.Command): @tiller.command(name='tiller', help=DESC, short_help=SHORT_DESC)
def get_parser(self, prog_name): @click.option('--tiller-host', help="Tiller host ip", default=None)
parser = super(TillerServerCommand, self).get_parser(prog_name) @click.option(
parser.add_argument('--status', action='store_true', '--tiller-port', help="Tiller host port", type=int, default=44134)
default=False, help='Check Tiller service') @click.option('--releases', help="list of deployed releses", is_flag=True)
parser.add_argument('--releases', action='store_true', @click.option('--status', help="Status of Armada services", is_flag=True)
default=False, help='List Tiller Releases') @click.pass_context
return parser 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 # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from cliff import command as cmd
import click
import yaml 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 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): DESC = """
documents = yaml.safe_load_all(open(args.file).read()) This command validates Armada Manifest
manifest_obj = Manifest(documents).get_manifest()
obj_check = validate_armada_object(manifest_obj)
doc_check = validate_armada_documents(documents)
try: The validate argument must be a relative path to Armada manifest
if doc_check and obj_check:
LOG.info('Successfully validated: %s', args.file) $ armada validate examples/simple.yaml
except Exception:
raise Exception('Failed to validate: %s', args.file) """
SHORT_DESC = "command validates Armada Manifest"
class ValidateYamlCommand(cmd.Command): @validate.command(name='validate', help=DESC, short_help=SHORT_DESC)
def get_parser(self, prog_name): @click.argument('filename')
parser = super(ValidateYamlCommand, self).get_parser(prog_name) @click.pass_context
parser.add_argument('file', type=str, metavar='FILE', def validate_manifest(ctx, filename):
help='Armada yaml file to validate') ValidateManifest(ctx, filename).invoke()
return parser
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', name=base.ARMADA % 'create_endpoints',
check_str=base.RULE_ADMIN_REQUIRED, check_str=base.RULE_ADMIN_REQUIRED,
description='install manifest charts', description='install manifest charts',
operations=[{'path': '/v1.0/apply/', 'method': 'POST'}]), operations=[{'path': '/api/v1.0/apply/', 'method': 'POST'}]),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=base.ARMADA % 'validate_manifest', name=base.ARMADA % 'validate_manifest',
check_str=base.RULE_ADMIN_REQUIRED, 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', 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', name=base.TILLER % 'get_status',
check_str=base.RULE_ADMIN_REQUIRED, check_str=base.RULE_ADMIN_REQUIRED,
description='Get tiller status', description='Get tiller status',
operations=[{'path': '/v1.0/status/', operations=[{'path': '/api/v1.0/status/', 'method': 'GET'}]),
'method': 'GET'}]),
policy.DocumentedRuleDefault( policy.DocumentedRuleDefault(
name=base.TILLER % 'get_release', name=base.TILLER % 'get_release',
check_str=base.RULE_ADMIN_REQUIRED, check_str=base.RULE_ADMIN_REQUIRED,
description='Get tiller release', description='Get tiller release',
operations=[{'path': '/v1.0/releases/', operations=[{'path': '/api/v1.0/releases/', 'method': 'GET'}]),
'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 oslo_config import cfg
from armada.conf import default from armada.conf import default
from armada import const
CONF = cfg.CONF CONF = cfg.CONF
# Load config file if exists # Load config file if exists
if (os.path.exists('etc/armada/armada.conf')): if (os.path.exists(const.CONFIG_PATH)):
CONF(['--config-file', 'etc/armada/armada.conf']) CONF(['--config-file', const.CONFIG_PATH])
def set_app_default_configs(): def set_app_default_configs():

View File

@ -14,6 +14,8 @@
from oslo_config import cfg from oslo_config import cfg
from keystoneauth1 import loading
from armada.conf import utils from armada.conf import utils
default_options = [ default_options = [
@ -71,7 +73,12 @@ The Keystone project domain name used for authentication.
def register_opts(conf): def register_opts(conf):
conf.register_opts(default_options) conf.register_opts(default_options)
conf.register_opts(
loading.get_auth_plugin_conf_options('password'),
group='keystone_authtoken')
def list_opts(): 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 # Statuses
STATUS_DEPLOYED = 'DEPLOYED' STATUS_DEPLOYED = 'DEPLOYED'
STATUS_FAILED = 'FAILED' 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.''' '''Exception that occurs during chart cleanup.'''
message = 'There was an error listing the helm chart releases.' 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, timeout=DEFAULT_TIMEOUT,
tiller_host=None, tiller_host=None,
tiller_port=44134, tiller_port=44134,
values=None, values=None):
debug=False):
''' '''
Initialize the Armada Engine and establish Initialize the Armada Engine and establish
a connection to Tiller a connection to Tiller
@ -69,14 +68,8 @@ class Armada(object):
self.timeout = timeout self.timeout = timeout
self.tiller = Tiller(tiller_host=tiller_host, tiller_port=tiller_port) self.tiller = Tiller(tiller_host=tiller_host, tiller_port=tiller_port)
self.values = values self.values = values
self.documents = list(yaml.safe_load_all(file)) self.documents = file
self.config = None 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): def get_armada_manifest(self):
return Manifest(self.documents).get_manifest() return Manifest(self.documents).get_manifest()
@ -193,7 +186,7 @@ class Armada(object):
Syncronize Helm with the Armada Config(s) 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 # TODO: (gardlt) we need to break up this func into
# a more cleaner format # a more cleaner format
@ -314,7 +307,7 @@ class Armada(object):
timeout=wait_values.get('timeout', DEFAULT_TIMEOUT) timeout=wait_values.get('timeout', DEFAULT_TIMEOUT)
) )
msg['upgraded'].append(prefix_chart) msg['upgrade'].append(prefix_chart)
# process install # process install
else: else:
@ -338,7 +331,7 @@ class Armada(object):
namespace=chart.namespace, namespace=chart.namespace,
timeout=wait_values.get('timeout', 3600)) timeout=wait_values.get('timeout', 3600))
msg['installed'].append(prefix_chart) msg['install'].append(prefix_chart)
LOG.debug("Cleaning up chart source in %s", LOG.debug("Cleaning up chart source in %s",
chartbuilder.source_directory) chartbuilder.source_directory)

View File

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

View File

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

View File

@ -12,39 +12,81 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import sys from urllib.parse import urlparse
import click
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log 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 CONF = cfg.CONF
class ArmadaApp(app.App): @click.group()
def __init__(self, **kwargs): @click.option(
super(ArmadaApp, self).__init__( '--debug/--no-debug', help='Enable or disable debugging', default=False)
description='Armada - Upgrade and deploy your charts', @click.option(
version=armada.__version__, '--api/--no-api', help='Execute service endpoints. (requires url option)',
command_manager=cm.CommandManager('armada'), default=False)
**kwargs) @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): Common actions from this point include:
parser = super(ArmadaApp, self).build_option_parser(
description, version, argparse_kwargs)
return parser
def configure_logging(self): \b
super(ArmadaApp, self).configure_logging() $ armada apply
log.register_options(CONF) $ armada test
log.set_defaults(default_log_levels=CONF.default_log_levels) $ armada tiller
log.setup(CONF, 'armada') $ 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): main.add_command(apply_create)
if argv is None: main.add_command(test_charts)
argv = sys.argv[1:] main.add_command(tiller_service)
return ArmadaApp().run(argv) main.add_command(validate_manifest)

View File

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

View File

@ -7,15 +7,42 @@ Commands
.. code:: bash .. 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: Options:
--api Contacts service endpoint
[-h] [--dry-run] [--debug-logging] [--disable-update-pre] --disable-update-post run charts without install
[--disable-update-post] [--enable-chart-cleanup] [--wait] --disable-update-pre run charts without install
[--timeout TIMEOUT] --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 Synopsis
-------- --------

View File

@ -7,11 +7,28 @@ Commands
.. code:: bash .. 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: Options:
--file TEXT armada manifest
[-h] [--release RELEASE] [--file FILE] --release TEXT helm release
--tiller-host TEXT Tiller Host IP
--tiller-port INTEGER Tiller host Port
--help Show this message and exit.
Synopsis Synopsis

View File

@ -7,12 +7,26 @@ Commands
.. code:: bash .. 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: Options:
--tiller-host TEXT Tiller host ip
[-h] [--status] [--releases] --tiller-port INTEGER Tiller host port
--releases list of deployed releases
--status Status of Armada services
--help Show this message and exit.
Synopsis Synopsis
-------- --------

View File

@ -7,11 +7,16 @@ Commands
.. code:: bash .. 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: Options:
--help Show this message and exit.
[-h]
Synopsis Synopsis
-------- --------

View File

@ -13,8 +13,7 @@ To use the docker containter to develop:
.. code-block:: bash .. code-block:: bash
git clone http://github.com/att-comdev/armada.git git clone http://github.com/att-comdev/armada.git && cd armada
cd armada
pip install tox pip install tox
@ -23,7 +22,7 @@ To use the docker containter to develop:
docker build . -t armada/latest 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:: .. note::
@ -45,7 +44,8 @@ From the directory of the forked repository:
git clone http://github.com/att-comdev/armada.git && cd armada 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 pip install -r requirements.txt -r test-requirements.txt
@ -53,6 +53,7 @@ From the directory of the forked repository:
# Testing your armada code # Testing your armada code
# The tox command will execute lint, bandit, cover # The tox command will execute lint, bandit, cover
pip install tox
tox tox
# For targeted test # For targeted test
@ -60,7 +61,6 @@ From the directory of the forked repository:
tox -e bandit tox -e bandit
tox -e cover tox -e cover
# policy and config are used in order to use and configure Armada API # policy and config are used in order to use and configure Armada API
tox -e genconfig tox -e genconfig
tox -e genpolicy 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 Get tiller releases
:>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
::
Request: Request
+++++++
Responses
+++++++++
**200**
^^^^^^^
obtain all running releases
**Example:**
.. code-block:: javascript
{ {
"file": "examples/openstack-helm.yaml", "message": {
"options": { "namespace": [
"debug": true, "armada-release",
"disable_update_pre": false, "armada-release"
"disable_update_post": false, ],
"enable_chart_cleanup": false, "default": [
"skip_pre_flight": false, "armada-release",
"dry_run": false, "armada-release"
"wait": false, ]
"timeout": false
} }
} }
:: **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" "message": {
} "tiller": {
"state": True,
Tiller Endpoints "version": "v2.5.0"
----------------- }
::
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"
} }
} }
**403**
^^^^^^^
:: Unable to Authorize or Permission
Endpoint: GET /tiller/status
Retrieves the status of the Tiller server.
:: **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 .. code-block:: bash
$ tox -e genconfig $ tox -e genconfig
$ tox -e genpolicy
Customize your configuration based on the information below Customize your configuration based on the information below
@ -20,9 +21,9 @@ tokens
.. note:: .. 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 .. code-block:: bash
@ -40,13 +41,13 @@ The service account must then be included in the armada.conf
.. code-block:: ini .. code-block:: ini
[keystone_authtoken] [keystone_authtoken]
auth_type = password
auth_uri = https://<keystone-api>:5000/ auth_uri = https://<keystone-api>:5000/
auth_url = https://<keystone-api>:35357/
auth_version = 3 auth_version = 3
delay_auth_decision = true 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 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 set -e
if [ "$1" = 'server' ]; then 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" exec uwsgi --http :${PORT} --http-timeout 3600 --paste config:/etc/armada/api-paste.ini --enable-threads -L --pyargv "--config-file /etc/armada/armada.conf"
fi else
if [ "$1" = 'tiller' ] || [ "$1" = 'apply' ]; then
exec $CMD "$@" exec $CMD "$@"
fi 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: {} values: {}
source: source:
type: git type: git
location: git://github.com/openstack/openstack-helm location: https://git.openstack.org/openstack/openstack-helm
subpath: helm-toolkit subpath: helm-toolkit
reference: master reference: master
dependencies: [] dependencies: []
@ -34,7 +34,7 @@ data:
values: {} values: {}
source: source:
type: git type: git
location: git://github.com/openstack/openstack-helm location: https://git.openstack.org/openstack/openstack-helm
subpath: mariadb subpath: mariadb
reference: master reference: master
dependencies: dependencies:
@ -59,7 +59,7 @@ data:
values: {} values: {}
source: source:
type: git type: git
location: git://github.com/openstack/openstack-helm location: https://git.openstack.org/openstack/openstack-helm
subpath: memcached subpath: memcached
reference: master reference: master
dependencies: dependencies:
@ -82,11 +82,18 @@ data:
no_hooks: false no_hooks: false
upgrade: upgrade:
no_hooks: false no_hooks: false
pre:
delete:
- name: keystone-bootstrap
type: job
labels:
- application: keystone
- component: bootstrap
values: values:
replicas: 2 replicas: 3
source: source:
type: git type: git
location: git://github.com/openstack/openstack-helm location: https://git.openstack.org/openstack/openstack-helm
subpath: keystone subpath: keystone
reference: master reference: master
dependencies: dependencies:

View File

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

View File

@ -40,11 +40,6 @@ upload-dir = doc/build/html
[entry_points] [entry_points]
console_scripts = console_scripts =
armada = armada.shell:main 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 = oslo.config.opts =
armada.conf = armada.conf.opts:list_opts armada.conf = armada.conf.opts:list_opts
oslo.policy.policies = oslo.policy.policies =

View File

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