Browse Source

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
changes/20/570020/1
gardlt 1 year ago
parent
commit
7b26e59422
46 changed files with 2195 additions and 488 deletions
  1. 3
    0
      .dockerignore
  2. 0
    18
      .editorconfig
  3. 9
    0
      .gitignore
  4. 2
    0
      Dockerfile
  5. 28
    24
      armada/api/__init__.py
  6. 0
    60
      armada/api/armada_controller.py
  7. 71
    0
      armada/api/controller/armada.py
  8. 134
    0
      armada/api/controller/test.py
  9. 23
    20
      armada/api/controller/tiller.py
  10. 8
    17
      armada/api/controller/validation.py
  11. 11
    5
      armada/api/middleware.py
  12. 15
    17
      armada/api/server.py
  13. 19
    0
      armada/cli/__init__.py
  14. 188
    54
      armada/cli/apply.py
  15. 118
    71
      armada/cli/test.py
  16. 80
    25
      armada/cli/tiller.py
  17. 53
    24
      armada/cli/validate.py
  18. 107
    0
      armada/common/client.py
  19. 12
    2
      armada/common/policies/service.py
  20. 2
    4
      armada/common/policies/tiller.py
  21. 96
    0
      armada/common/session.py
  22. 3
    2
      armada/conf/__init__.py
  23. 8
    1
      armada/conf/default.py
  24. 3
    0
      armada/const.py
  25. 18
    0
      armada/exceptions/api_exceptions.py
  26. 5
    12
      armada/handlers/armada.py
  27. 7
    2
      armada/handlers/k8s.py
  28. 6
    6
      armada/handlers/tiller.py
  29. 66
    24
      armada/shell.py
  30. 8
    5
      armada/tests/unit/api/test_api.py
  31. 0
    0
      armada/tests/unit/common/test_policy.py
  32. 32
    5
      docs/source/commands/apply.rst
  33. 20
    3
      docs/source/commands/test.rst
  34. 17
    3
      docs/source/commands/tiller.rst
  35. 8
    3
      docs/source/commands/validate.rst
  36. 5
    5
      docs/source/development/getting-started.rst
  37. 493
    48
      docs/source/operations/guide-api.rst
  38. 8
    7
      docs/source/operations/guide-configure.rst
  39. 2
    6
      entrypoint.sh
  40. 441
    0
      etc/armada/armada.conf.sample
  41. 33
    0
      etc/armada/policy.yaml
  42. 13
    0
      examples/armada-keystone-manifest.yaml
  43. 12
    5
      examples/keystone-manifest.yaml
  44. 4
    5
      requirements.txt
  45. 0
    5
      setup.cfg
  46. 4
    0
      tools/keystone-account.sh

+ 3
- 0
.dockerignore View File

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

+ 0
- 18
.editorconfig View File

@@ -1,18 +0,0 @@
1
-# EditorConfig http://editorconfig.org
2
-
3
-root = true
4
-
5
-[*]
6
-indent_style = space
7
-indent_size = 4
8
-tab_width = 4
9
-end_of_line = lf
10
-charset = utf-8
11
-trim_trailing_whitespace = false
12
-insert_final_newline = true
13
-max_line_length = 80
14
-curly_bracket_next_line = false
15
-spaces_around_operators = true
16
-spaces_around_brackets = true
17
-indent_brace_style = K&R
18
-

+ 9
- 0
.gitignore View File

@@ -24,6 +24,9 @@ var/
24 24
 .installed.cfg
25 25
 *.egg
26 26
 etc/*.sample
27
+etc/hostname
28
+etc/hosts
29
+etc/resolv.conf
27 30
 
28 31
 # PyInstaller
29 32
 #  Usually these files are written by a python script from a template
@@ -97,3 +100,9 @@ ENV/
97 100
 **/*.tgz
98 101
 **/_partials.tpl
99 102
 **/_globals.tpl
103
+
104
+AUTHORS
105
+ChangeLog
106
+etc/armada/armada.conf
107
+etc/armada/policy.yaml
108
+.editorconfig

+ 2
- 0
Dockerfile View File

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

+ 28
- 24
armada/api/__init__.py View File

@@ -10,22 +10,32 @@
10 10
 # distributed under the License is distributed on an "AS IS" BASIS,
11 11
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 12
 # See the License for the specific language governing permissions and
13
-# limitations under the License.
13
+# limitations under the License
14 14
 
15 15
 import json
16
-import uuid
17 16
 import logging as log
17
+import os
18
+import uuid
19
+import yaml
18 20
 
19 21
 import falcon
22
+from oslo_config import cfg
20 23
 from oslo_log import log as logging
21 24
 
22
-LOG = logging.getLogger(__name__)
25
+from armada import const
26
+
27
+CONF = cfg.CONF
23 28
 
24 29
 
25 30
 class BaseResource(object):
26 31
 
27 32
     def __init__(self):
28
-        self.logger = LOG
33
+        if not (os.path.exists(const.CONFIG_PATH)):
34
+            logging.register_options(CONF)
35
+            logging.set_defaults(default_log_levels=CONF.default_log_levels)
36
+            logging.setup(CONF, 'armada')
37
+
38
+        self.logger = logging.getLogger(__name__)
29 39
 
30 40
     def on_options(self, req, resp):
31 41
         self_attrs = dir(self)
@@ -39,29 +49,23 @@ class BaseResource(object):
39 49
         resp.headers['Allow'] = ','.join(allowed_methods)
40 50
         resp.status = falcon.HTTP_200
41 51
 
42
-    def req_json(self, req):
52
+    def req_yaml(self, req):
43 53
         if req.content_length is None or req.content_length == 0:
44 54
             return None
45 55
 
46
-        if req.content_type is not None and req.content_type.lower(
47
-        ) == 'application/json':
48
-            raw_body = req.stream.read(req.content_length or 0)
49
-
50
-            if raw_body is None:
51
-                return None
52
-
53
-            try:
54
-                # json_body = json.loads(raw_body.decode('utf-8'))
55
-                # return json_body
56
-                return raw_body
57
-            except json.JSONDecodeError as jex:
58
-                self.error(
59
-                    req.context,
60
-                    "Invalid JSON in request: \n%s" % raw_body.decode('utf-8'))
61
-                raise json.JSONDecodeError("%s: Invalid JSON in body: %s" %
62
-                                           (req.path, jex))
63
-        else:
64
-            raise json.JSONDecodeError("Requires application/json payload")
56
+        raw_body = req.stream.read(req.content_length or 0)
57
+
58
+        if raw_body is None:
59
+            return None
60
+
61
+        try:
62
+            return yaml.safe_load_all(raw_body.decode('utf-8'))
63
+        except yaml.YAMLError as jex:
64
+            self.error(
65
+                req.context,
66
+                "Invalid YAML in request: \n%s" % raw_body.decode('utf-8'))
67
+            raise Exception(
68
+                "%s: Invalid YAML in body: %s" % (req.path, jex))
65 69
 
66 70
     def return_error(self, resp, status_code, message="", retry=False):
67 71
         resp.body = json.dumps({

+ 0
- 60
armada/api/armada_controller.py View File

@@ -1,60 +0,0 @@
1
-# Copyright 2017 The Armada Authors.
2
-#
3
-# Licensed under the Apache License, Version 2.0 (the "License");
4
-# you may not use this file except in compliance with the License.
5
-# You may obtain a copy of the License at
6
-#
7
-#     http://www.apache.org/licenses/LICENSE-2.0
8
-#
9
-# Unless required by applicable law or agreed to in writing, software
10
-# distributed under the License is distributed on an "AS IS" BASIS,
11
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
-# See the License for the specific language governing permissions and
13
-# limitations under the License.
14
-
15
-import json
16
-
17
-import falcon
18
-from oslo_log import log as logging
19
-
20
-from armada import api
21
-from armada.handlers.armada import Armada
22
-
23
-LOG = logging.getLogger(__name__)
24
-
25
-
26
-class Apply(api.BaseResource):
27
-    '''
28
-    apply armada endpoint service
29
-    '''
30
-
31
-    def on_post(self, req, resp):
32
-        try:
33
-
34
-            # Load data from request and get options
35
-            data = self.req_json(req)
36
-            opts = {}
37
-            # opts = data['options']
38
-
39
-            # Encode filename
40
-            # data['file'] = data['file'].encode('utf-8')
41
-            armada = Armada(
42
-                data,
43
-                disable_update_pre=opts.get('disable_update_pre', False),
44
-                disable_update_post=opts.get('disable_update_post', False),
45
-                enable_chart_cleanup=opts.get('enable_chart_cleanup', False),
46
-                dry_run=opts.get('dry_run', False),
47
-                wait=opts.get('wait', False),
48
-                timeout=opts.get('timeout', False))
49
-
50
-            msg = armada.sync()
51
-
52
-            resp.data = json.dumps({'message': msg})
53
-
54
-            resp.content_type = 'application/json'
55
-            resp.status = falcon.HTTP_200
56
-        except Exception as e:
57
-            self.error(req.context, "Failed to apply manifest")
58
-            self.return_error(
59
-                resp, falcon.HTTP_500,
60
-                message="Failed to install manifest: {} {}".format(e, data))

+ 71
- 0
armada/api/controller/armada.py View File

@@ -0,0 +1,71 @@
1
+# Copyright 2017 The Armada Authors.
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License");
4
+# you may not use this file except in compliance with the License.
5
+# You may obtain a copy of the License at
6
+#
7
+#     http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS,
11
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+# See the License for the specific language governing permissions and
13
+# limitations under the License.
14
+
15
+import json
16
+
17
+import falcon
18
+
19
+from armada import api
20
+from armada.common import policy
21
+from armada.handlers.armada import Armada
22
+
23
+
24
+class Apply(api.BaseResource):
25
+    '''
26
+    apply armada endpoint service
27
+    '''
28
+    @policy.enforce('armada:create_endpoints')
29
+    def on_post(self, req, resp):
30
+        try:
31
+
32
+            # Load data from request and get options
33
+
34
+            data = list(self.req_yaml(req))
35
+
36
+            if type(data[0]) is list:
37
+                data = list(data[0])
38
+
39
+            opts = req.params
40
+
41
+            # Encode filename
42
+            armada = Armada(
43
+                data,
44
+                disable_update_pre=req.get_param_as_bool(
45
+                    'disable_update_pre'),
46
+                disable_update_post=req.get_param_as_bool(
47
+                    'disable_update_post'),
48
+                enable_chart_cleanup=req.get_param_as_bool(
49
+                    'enable_chart_cleanup'),
50
+                dry_run=req.get_param_as_bool('dry_run'),
51
+                wait=req.get_param_as_bool('wait'),
52
+                timeout=int(opts.get('timeout', 3600)),
53
+                tiller_host=opts.get('tiller_host', None),
54
+                tiller_port=int(opts.get('tiller_port', 44134)),
55
+            )
56
+
57
+            msg = armada.sync()
58
+
59
+            resp.body = json.dumps(
60
+                {
61
+                    'message': msg,
62
+                }
63
+            )
64
+
65
+            resp.content_type = 'application/json'
66
+            resp.status = falcon.HTTP_200
67
+        except Exception as e:
68
+            err_message = 'Failed to apply manifest: {}'.format(e)
69
+            self.error(req.context, err_message)
70
+            self.return_error(
71
+                resp, falcon.HTTP_500, message=err_message)

+ 134
- 0
armada/api/controller/test.py View File

@@ -0,0 +1,134 @@
1
+# Copyright 2017 The Armada Authors.
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License");
4
+# you may not use this file except in compliance with the License.
5
+# You may obtain a copy of the License at
6
+#
7
+#     http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS,
11
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+# See the License for the specific language governing permissions and
13
+# limitations under the License.
14
+
15
+import json
16
+
17
+import falcon
18
+
19
+from armada import api
20
+from armada.common import policy
21
+from armada import const
22
+from armada.handlers.tiller import Tiller
23
+from armada.handlers.manifest import Manifest
24
+from armada.utils.release import release_prefix
25
+
26
+
27
+class Test(api.BaseResource):
28
+    '''
29
+    Test helm releases via release name
30
+    '''
31
+
32
+    @policy.enforce('armada:test_release')
33
+    def on_get(self, req, resp, release):
34
+        try:
35
+            self.logger.info('RUNNING: %s', release)
36
+            opts = req.params
37
+            tiller = Tiller(tiller_host=opts.get('tiller_host', None),
38
+                            tiller_port=opts.get('tiller_port', None))
39
+            tiller_resp = tiller.testing_release(release)
40
+            msg = {
41
+                'result': '',
42
+                'message': ''
43
+            }
44
+
45
+            if tiller_resp:
46
+                test_status = getattr(
47
+                    tiller_resp.info.status, 'last_test_suite_run', 'FAILED')
48
+
49
+                if test_status.result[0].status:
50
+                    msg['result'] = 'PASSED: {}'.format(release)
51
+                    msg['message'] = 'MESSAGE: Test Pass'
52
+                    self.logger.info(msg)
53
+                else:
54
+                    msg['result'] = 'FAILED: {}'.format(release)
55
+                    msg['message'] = 'MESSAGE: Test Fail'
56
+                    self.logger.info(msg)
57
+            else:
58
+                msg['result'] = 'FAILED: {}'.format(release)
59
+                msg['message'] = 'MESSAGE: No test found'
60
+
61
+            resp.body = json.dumps(msg)
62
+            resp.status = falcon.HTTP_200
63
+            resp.content_type = 'application/json'
64
+
65
+        except Exception as e:
66
+            err_message = 'Failed to test {}: {}'.format(release, e)
67
+            self.error(req.context, err_message)
68
+            self.return_error(
69
+                resp, falcon.HTTP_500, message=err_message)
70
+
71
+
72
+class Tests(api.BaseResource):
73
+    '''
74
+    Test helm releases via a manifest
75
+    '''
76
+
77
+    @policy.enforce('armada:tests_manifest')
78
+    def on_post(self, req, resp):
79
+        try:
80
+            opts = req.params
81
+            tiller = Tiller(tiller_host=opts.get('tiller_host', None),
82
+                            tiller_port=opts.get('tiller_port', None))
83
+
84
+            documents = self.req_yaml(req)
85
+            armada_obj = Manifest(documents).get_manifest()
86
+            prefix = armada_obj.get(const.KEYWORD_ARMADA).get(
87
+                const.KEYWORD_PREFIX)
88
+            known_releases = [release[0] for release in tiller.list_charts()]
89
+
90
+            message = {
91
+                'tests': {
92
+                    'passed': [],
93
+                    'skipped': [],
94
+                    'failed': []
95
+                }
96
+            }
97
+
98
+            for group in armada_obj.get(const.KEYWORD_ARMADA).get(
99
+                    const.KEYWORD_GROUPS):
100
+                for ch in group.get(const.KEYWORD_CHARTS):
101
+                    release_name = release_prefix(
102
+                        prefix, ch.get('chart').get('chart_name'))
103
+
104
+                    if release_name in known_releases:
105
+                        self.logger.info('RUNNING: %s tests', release_name)
106
+                        resp = tiller.testing_release(release_name)
107
+
108
+                        if not resp:
109
+                            continue
110
+
111
+                        test_status = getattr(
112
+                            resp.info.status, 'last_test_suite_run',
113
+                            'FAILED')
114
+                        if test_status.results[0].status:
115
+                            self.logger.info("PASSED: %s", release_name)
116
+                            message['test']['passed'].append(release_name)
117
+                        else:
118
+                            self.logger.info("FAILED: %s", release_name)
119
+                            message['test']['failed'].append(release_name)
120
+                    else:
121
+                        self.logger.info(
122
+                            'Release %s not found - SKIPPING', release_name)
123
+                        message['test']['skipped'].append(release_name)
124
+
125
+            resp.status = falcon.HTTP_200
126
+
127
+            resp.body = json.dumps(message)
128
+            resp.content_type = 'application/json'
129
+
130
+        except Exception as e:
131
+            err_message = 'Failed to test manifest: {}'.format(e)
132
+            self.error(req.context, err_message)
133
+            self.return_error(
134
+                resp, falcon.HTTP_500, message=err_message)

armada/api/tiller_controller.py → armada/api/controller/tiller.py View File

@@ -15,16 +15,11 @@
15 15
 import json
16 16
 
17 17
 import falcon
18
-from oslo_config import cfg
19
-from oslo_log import log as logging
20 18
 
21 19
 from armada import api
22 20
 from armada.common import policy
23 21
 from armada.handlers.tiller import Tiller
24 22
 
25
-LOG = logging.getLogger(__name__)
26
-CONF = cfg.CONF
27
-
28 23
 
29 24
 class Status(api.BaseResource):
30 25
     @policy.enforce('tiller:get_status')
@@ -33,21 +28,27 @@ class Status(api.BaseResource):
33 28
         get tiller status
34 29
         '''
35 30
         try:
36
-            message = {'tiller': Tiller().tiller_status()}
31
+            opts = req.params
32
+            tiller = Tiller(
33
+                tiller_host=opts.get('tiller_host', None),
34
+                tiller_port=opts.get('tiller_port', None))
37 35
 
38
-            if message.get('tiller', False):
39
-                resp.status = falcon.HTTP_200
40
-            else:
41
-                resp.status = falcon.HTTP_503
36
+            message = {
37
+                'tiller': {
38
+                    'state': tiller.tiller_status(),
39
+                    'version': tiller.tiller_version()
40
+                }
41
+            }
42 42
 
43
-            resp.data = json.dumps(message)
43
+            resp.status = falcon.HTTP_200
44
+            resp.body = json.dumps(message)
44 45
             resp.content_type = 'application/json'
45 46
 
46 47
         except Exception as e:
47
-            self.error(req.context, "Unable to find resources")
48
+            err_message = 'Failed to get Tiller Status: {}'.format(e)
49
+            self.error(req.context, err_message)
48 50
             self.return_error(
49
-                resp, falcon.HTTP_500,
50
-                message="Unable to get status: {}".format(e))
51
+                resp, falcon.HTTP_500, message=err_message)
51 52
 
52 53
 
53 54
 class Release(api.BaseResource):
@@ -58,21 +59,23 @@ class Release(api.BaseResource):
58 59
         '''
59 60
         try:
60 61
             # Get tiller releases
61
-            handler = Tiller()
62
+            opts = req.params
63
+            tiller = Tiller(tiller_host=opts.get('tiller_host', None),
64
+                            tiller_port=opts.get('tiller_port', None))
62 65
 
63 66
             releases = {}
64
-            for release in handler.list_releases():
67
+            for release in tiller.list_releases():
65 68
                 if not releases.get(release.namespace, None):
66 69
                     releases[release.namespace] = []
67 70
 
68 71
                 releases[release.namespace].append(release.name)
69 72
 
70
-            resp.data = json.dumps({'releases': releases})
73
+            resp.body = json.dumps({'releases': releases})
71 74
             resp.content_type = 'application/json'
72 75
             resp.status = falcon.HTTP_200
73 76
 
74 77
         except Exception as e:
75
-            self.error(req.context, "Unable to find resources")
78
+            err_message = 'Unable to find Tiller Releases: {}'.format(e)
79
+            self.error(req.context, err_message)
76 80
             self.return_error(
77
-                resp, falcon.HTTP_500,
78
-                message="Unable to find Releases: {}".format(e))
81
+                resp, falcon.HTTP_500, message=err_message)

armada/api/validation_controller.py → armada/api/controller/validation.py View File

@@ -13,17 +13,13 @@
13 13
 # limitations under the License.
14 14
 
15 15
 import json
16
-import yaml
17 16
 
18 17
 import falcon
19
-from oslo_log import log as logging
20 18
 
21 19
 from armada import api
22 20
 from armada.common import policy
23 21
 from armada.utils.lint import validate_armada_documents
24 22
 
25
-LOG = logging.getLogger(__name__)
26
-
27 23
 
28 24
 class Validate(api.BaseResource):
29 25
     '''
@@ -33,24 +29,19 @@ class Validate(api.BaseResource):
33 29
     @policy.enforce('armada:validate_manifest')
34 30
     def on_post(self, req, resp):
35 31
         try:
32
+            manifest = self.req_yaml(req)
33
+            documents = list(manifest)
36 34
 
37 35
             message = {
38
-                'valid':
39
-                validate_armada_documents(
40
-                    list(yaml.safe_load_all(self.req_json(req))))
36
+                'valid': validate_armada_documents(documents)
41 37
             }
42 38
 
43
-            if message.get('valid', False):
44
-                resp.status = falcon.HTTP_200
45
-            else:
46
-                resp.status = falcon.HTTP_400
47
-
48
-            resp.data = json.dumps(message)
39
+            resp.status = falcon.HTTP_200
40
+            resp.body = json.dumps(message)
49 41
             resp.content_type = 'application/json'
50 42
 
51 43
         except Exception:
52
-            self.error(req.context, "Failed: Invalid Armada Manifest")
44
+            err_message = 'Failed to validate Armada Manifest'
45
+            self.error(req.context, err_message)
53 46
             self.return_error(
54
-                resp,
55
-                falcon.HTTP_400,
56
-                message="Failed: Invalid Armada Manifest")
47
+                resp, falcon.HTTP_400, message=err_message)

+ 11
- 5
armada/api/middleware.py View File

@@ -17,18 +17,20 @@ from uuid import UUID
17 17
 from oslo_config import cfg
18 18
 from oslo_log import log as logging
19 19
 
20
-LOG = logging.getLogger(__name__)
21 20
 CONF = cfg.CONF
22 21
 
23 22
 
24 23
 class AuthMiddleware(object):
25 24
 
25
+    def __init__(self):
26
+        self.logger = logging.getLogger(__name__)
27
+
26 28
     # Authentication
27 29
     def process_request(self, req, resp):
28 30
         ctx = req.context
29 31
 
30 32
         for k, v in req.headers.items():
31
-            LOG.debug("Request with header %s: %s" % (k, v))
33
+            self.logger.debug("Request with header %s: %s" % (k, v))
32 34
 
33 35
         auth_status = req.get_header('X-SERVICE-IDENTITY-STATUS')
34 36
         service = True
@@ -65,8 +67,9 @@ class AuthMiddleware(object):
65 67
             else:
66 68
                 ctx.is_admin_project = False
67 69
 
68
-            LOG.debug('Request from authenticated user %s with roles %s' %
69
-                      (ctx.user, ','.join(ctx.roles)))
70
+            self.logger.debug(
71
+                'Request from authenticated user %s with roles %s' %
72
+                (ctx.user, ','.join(ctx.roles)))
70 73
         else:
71 74
             ctx.authenticated = False
72 75
 
@@ -91,6 +94,9 @@ class ContextMiddleware(object):
91 94
 
92 95
 
93 96
 class LoggingMiddleware(object):
97
+    def __init__(self):
98
+        self.logger = logging.getLogger(__name__)
99
+
94 100
     def process_response(self, req, resp, resource, req_succeeded):
95 101
         ctx = req.context
96 102
         extra = {
@@ -99,4 +105,4 @@ class LoggingMiddleware(object):
99 105
             'external_ctx': ctx.external_marker,
100 106
         }
101 107
         resp.append_header('X-Armada-Req', ctx.request_id)
102
-        LOG.info("%s - %s" % (req.uri, resp.status), extra=extra)
108
+        self.logger.info("%s - %s" % (req.uri, resp.status), extra=extra)

+ 15
- 17
armada/api/server.py View File

@@ -12,34 +12,28 @@
12 12
 # See the License for the specific language governing permissions and
13 13
 # limitations under the License.
14 14
 
15
-import os
16
-
17 15
 import falcon
18 16
 from oslo_config import cfg
19
-from oslo_log import log as logging
20 17
 
21 18
 from armada import conf
22 19
 from armada.api import ArmadaRequest
23
-from armada.api.armada_controller import Apply
20
+from armada.api.controller.armada import Apply
24 21
 from armada.api.middleware import AuthMiddleware
25 22
 from armada.api.middleware import ContextMiddleware
26 23
 from armada.api.middleware import LoggingMiddleware
27
-from armada.api.tiller_controller import Release
28
-from armada.api.tiller_controller import Status
29
-from armada.api.validation_controller import Validate
24
+from armada.api.controller.test import Test
25
+from armada.api.controller.test import Tests
26
+from armada.api.controller.tiller import Release
27
+from armada.api.controller.tiller import Status
28
+from armada.api.controller.validation import Validate
30 29
 from armada.common import policy
31 30
 
32
-LOG = logging.getLogger(__name__)
33 31
 conf.set_app_default_configs()
34 32
 CONF = cfg.CONF
35 33
 
36 34
 
37 35
 # Build API
38 36
 def create(middleware=CONF.middleware):
39
-    if not (os.path.exists('etc/armada/armada.conf')):
40
-        logging.register_options(CONF)
41
-        logging.set_defaults(default_log_levels=CONF.default_log_levels)
42
-        logging.setup(CONF, 'armada')
43 37
 
44 38
     policy.setup_policy()
45 39
 
@@ -55,13 +49,17 @@ def create(middleware=CONF.middleware):
55 49
         api = falcon.API(request_type=ArmadaRequest)
56 50
 
57 51
     # Configure API routing
58
-    url_routes_v1 = (('apply', Apply()),
59
-                     ('releases', Release()),
60
-                     ('status', Status()),
61
-                     ('validate', Validate()))
52
+    url_routes_v1 = (
53
+        ('apply', Apply()),
54
+        ('releases', Release()),
55
+        ('status', Status()),
56
+        ('tests', Tests()),
57
+        ('test/{release}', Test()),
58
+        ('validate', Validate()),
59
+    )
62 60
 
63 61
     for route, service in url_routes_v1:
64
-        api.add_route("/v1.0/{}".format(route), service)
62
+        api.add_route("/api/v1.0/{}".format(route), service)
65 63
 
66 64
     return api
67 65
 

+ 19
- 0
armada/cli/__init__.py View File

@@ -11,3 +11,22 @@
11 11
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 12
 # See the License for the specific language governing permissions and
13 13
 # limitations under the License.
14
+
15
+from oslo_config import cfg
16
+from oslo_log import log as logging
17
+
18
+CONF = cfg.CONF
19
+
20
+LOG = logging.getLogger(__name__)
21
+
22
+
23
+class CliAction(object):
24
+
25
+    def __init__(self):
26
+        self.logger = LOG
27
+        logging.register_options(CONF)
28
+        logging.set_defaults(default_log_levels=CONF.default_log_levels)
29
+        logging.setup(CONF, 'armada')
30
+
31
+    def invoke(self):
32
+        raise Exception()

+ 188
- 54
armada/cli/apply.py View File

@@ -12,61 +12,195 @@
12 12
 # See the License for the specific language governing permissions and
13 13
 # limitations under the License.
14 14
 
15
-from cliff import command as cmd
15
+import yaml
16 16
 
17
+import click
18
+from oslo_config import cfg
19
+
20
+from armada.cli import CliAction
17 21
 from armada.handlers.armada import Armada
18 22
 
23
+CONF = cfg.CONF
24
+
25
+
26
+@click.group()
27
+def apply():
28
+    """ Apply manifest to cluster
29
+
30
+    """
31
+
32
+
33
+DESC = """
34
+This command install and updates charts defined in armada manifest
35
+
36
+The apply argument must be relative path to Armada Manifest. Executing apply
37
+commnad once will install all charts defined in manifest. Re-executing apply
38
+commnad will execute upgrade.
39
+
40
+To see how to create an Armada manifest:
41
+    http://armada-helm.readthedocs.io/en/latest/operations/
42
+
43
+To obtain install/upgrade charts:
44
+
45
+    \b
46
+    $ armada apply examples/simple.yaml
47
+
48
+To obtain override manifest:
49
+
50
+    \b
51
+    $ armada apply examples/simple.yaml \
52
+--set manifest:simple-armada:relase_name="wordpress"
53
+
54
+    \b
55
+    or
56
+
57
+    \b
58
+    $ armada apply examples/simple.yaml \
59
+--values examples/simple-ovr-values.yaml
60
+
61
+"""
62
+
63
+SHORT_DESC = "command install manifest charts"
64
+
65
+
66
+@apply.command(name='apply', help=DESC, short_help=SHORT_DESC)
67
+@click.argument('filename')
68
+@click.option('--api', help="Contacts service endpoint", is_flag=True)
69
+@click.option(
70
+    '--disable-update-post', help="run charts without install", is_flag=True)
71
+@click.option(
72
+    '--disable-update-pre', help="run charts without install", is_flag=True)
73
+@click.option('--dry-run', help="run charts without install", is_flag=True)
74
+@click.option(
75
+    '--enable-chart-cleanup', help="Clean up Unmanaged Charts", is_flag=True)
76
+@click.option('--set', multiple=True, type=str, default=[])
77
+@click.option('--tiller-host', help="Tiller host ip")
78
+@click.option(
79
+    '--tiller-port', help="Tiller host port", type=int, default=44134)
80
+@click.option(
81
+    '--timeout', help="specifies time to wait for charts", type=int,
82
+    default=3600)
83
+@click.option('--values', '-f', multiple=True, type=str, default=[])
84
+@click.option(
85
+    '--wait', help="wait until all charts deployed", is_flag=True)
86
+@click.option(
87
+    '--debug/--no-debug', help='Enable or disable debugging', default=False)
88
+@click.pass_context
89
+def apply_create(ctx,
90
+                 filename,
91
+                 api,
92
+                 disable_update_post,
93
+                 disable_update_pre,
94
+                 dry_run,
95
+                 enable_chart_cleanup,
96
+                 set,
97
+                 tiller_host,
98
+                 tiller_port,
99
+                 timeout,
100
+                 values,
101
+                 wait,
102
+                 debug):
103
+
104
+    if debug:
105
+        CONF.debug = debug
106
+
107
+    ApplyManifest(
108
+        ctx,
109
+        filename,
110
+        api,
111
+        disable_update_post,
112
+        disable_update_pre,
113
+        dry_run,
114
+        enable_chart_cleanup,
115
+        set,
116
+        tiller_host,
117
+        tiller_port,
118
+        timeout,
119
+        values,
120
+        wait).invoke()
121
+
122
+
123
+class ApplyManifest(CliAction):
124
+    def __init__(self,
125
+                 ctx,
126
+                 filename,
127
+                 api,
128
+                 disable_update_post,
129
+                 disable_update_pre,
130
+                 dry_run,
131
+                 enable_chart_cleanup,
132
+                 set,
133
+                 tiller_host,
134
+                 tiller_port,
135
+                 timeout,
136
+                 values,
137
+                 wait):
138
+        super(ApplyManifest, self).__init__()
139
+        self.ctx = ctx
140
+        self.filename = filename
141
+        self.api = api
142
+        self.disable_update_post = disable_update_post
143
+        self.disable_update_pre = disable_update_pre
144
+        self.dry_run = dry_run
145
+        self.enable_chart_cleanup = enable_chart_cleanup
146
+        self.set = set
147
+        self.tiller_host = tiller_host
148
+        self.tiller_port = tiller_port
149
+        self.timeout = timeout
150
+        self.values = values
151
+        self.wait = wait
152
+
153
+    def output(self, resp):
154
+        for result in resp:
155
+            if not resp[result] and not result == 'diff':
156
+                self.logger.info(
157
+                    'Did not performed chart %s(s)', result)
158
+            elif result == 'diff' and not resp[result]:
159
+                self.logger.info('No Relase changes detected')
160
+
161
+            for ch in resp[result]:
162
+                if not result == 'diff':
163
+                    msg = 'Chart {} was {}'.format(ch, result)
164
+                    self.logger.info(msg)
165
+                else:
166
+                    self.logger.info('Chart values diff')
167
+                    self.logger.info(ch)
168
+
169
+    def invoke(self):
170
+
171
+        if not self.ctx.obj.get('api', False):
172
+            with open(self.filename) as f:
173
+                armada = Armada(
174
+                    list(yaml.safe_load_all(f.read())),
175
+                    self.disable_update_pre,
176
+                    self.disable_update_post,
177
+                    self.enable_chart_cleanup,
178
+                    self.dry_run,
179
+                    self.set,
180
+                    self.wait,
181
+                    self.timeout,
182
+                    self.tiller_host,
183
+                    self.tiller_port,
184
+                    self.values)
185
+
186
+                resp = armada.sync()
187
+                self.output(resp)
188
+        else:
189
+            query = {
190
+                'disable_update_post': self.disable_update_post,
191
+                'disable_update_pre': self.disable_update_pre,
192
+                'dry_run': self.dry_run,
193
+                'enable_chart_cleanup': self.enable_chart_cleanup,
194
+                'tiller_host': self.tiller_host,
195
+                'tiller_port': self.tiller_port,
196
+                'timeout': self.timeout,
197
+                'wait': self.wait
198
+            }
199
+
200
+            client = self.ctx.obj.get('CLIENT')
19 201
 
20
-def applyCharts(args):
21
-
22
-    armada = Armada(open(args.file).read(),
23
-                    args.disable_update_pre,
24
-                    args.disable_update_post,
25
-                    args.enable_chart_cleanup,
26
-                    args.dry_run,
27
-                    args.set,
28
-                    args.wait,
29
-                    args.timeout,
30
-                    args.tiller_host,
31
-                    args.tiller_port,
32
-                    args.values,
33
-                    args.debug_logging)
34
-    armada.sync()
35
-
36
-
37
-class ApplyChartsCommand(cmd.Command):
38
-    def get_parser(self, prog_name):
39
-        parser = super(ApplyChartsCommand, self).get_parser(prog_name)
40
-        parser.add_argument('file', type=str, metavar='FILE',
41
-                            help='Armada yaml file')
42
-        parser.add_argument('--dry-run', action='store_true',
43
-                            default=False, help='Run charts with dry run')
44
-        parser.add_argument('--debug-logging', action='store_true',
45
-                            default=False, help='Show debug logs')
46
-        parser.add_argument('--disable-update-pre', action='store_true',
47
-                            default=False, help='Disable pre upgrade actions')
48
-        parser.add_argument('--disable-update-post', action='store_true',
49
-                            default=False, help='Disable post upgrade actions')
50
-        parser.add_argument('--enable-chart-cleanup', action='store_true',
51
-                            default=False, help='Enable Chart Clean Up')
52
-        parser.add_argument('--set', action='append', help='Override Armada'
53
-                                                           ' manifest values.')
54
-        parser.add_argument('--wait', action='store_true',
55
-                            default=False, help='Wait until all charts'
56
-                                                'have been deployed')
57
-        parser.add_argument('--timeout', action='store', type=int,
58
-                            default=3600, help='Specifies time to wait'
59
-                                                ' for charts to deploy')
60
-        parser.add_argument('--tiller-host', action='store', type=str,
61
-                            help='Specify the tiller host')
62
-
63
-        parser.add_argument('--tiller-port', action='store', type=int,
64
-                            default=44134, help='Specify the tiller port')
65
-
66
-        parser.add_argument('--values', action='append',
67
-                            help='Override manifest values with a yaml file')
68
-
69
-        return parser
70
-
71
-    def take_action(self, parsed_args):
72
-        applyCharts(parsed_args)
202
+            with open(self.filename, 'r') as f:
203
+                resp = client.post_apply(
204
+                    manifest=f.read(), values=self.values, set=self.set,
205
+                    query=query)
206
+                self.output(resp.get('message'))

+ 118
- 71
armada/cli/test.py View File

@@ -14,94 +14,141 @@
14 14
 
15 15
 import yaml
16 16
 
17
-from cliff import command as cmd
18
-from oslo_config import cfg
19
-from oslo_log import log as logging
17
+import click
20 18
 
19
+from armada.cli import CliAction
21 20
 from armada import const
22 21
 from armada.handlers.manifest import Manifest
23 22
 from armada.handlers.tiller import Tiller
24 23
 from armada.utils.release import release_prefix
25 24
 
26
-LOG = logging.getLogger(__name__)
27 25
 
28
-CONF = cfg.CONF
26
+@click.group()
27
+def test():
28
+    """ Test Manifest Charts
29 29
 
30
+    """
30 31
 
31
-def testService(args):
32 32
 
33
-    tiller = Tiller(tiller_host=args.tiller_host, tiller_port=args.tiller_port)
34
-    known_release_names = [release[0] for release in tiller.list_charts()]
33
+DESC = """
34
+This command test deployed charts
35 35
 
36
-    if args.release:
37
-        LOG.info("RUNNING: %s tests", args.release)
38
-        resp = tiller.testing_release(args.release)
36
+The tiller command uses flags to obtain information from tiller services.
37
+The test command will run the release chart tests either via a the manifest or
38
+by targetings a relase.
39 39
 
40
-        if not resp:
41
-            LOG.info("FAILED: %s", args.release)
42
-            return
40
+To test armada deployed releases:
43 41
 
44
-        test_status = getattr(resp.info.status, 'last_test_suite_run',
45
-                              'FAILED')
46
-        if test_status.results[0].status:
47
-            LOG.info("PASSED: %s", args.release)
48
-        else:
49
-            LOG.info("FAILED: %s", args.release)
42
+    $ armada test --file examples/simple.yaml
50 43
 
51
-    if args.file:
52
-        documents = yaml.safe_load_all(open(args.file).read())
53
-        armada_obj = Manifest(documents).get_manifest()
54
-        prefix = armada_obj.get(const.KEYWORD_ARMADA).get(const.KEYWORD_PREFIX)
44
+To test release:
55 45
 
56
-        for group in armada_obj.get(const.KEYWORD_ARMADA).get(
57
-                const.KEYWORD_GROUPS):
58
-            for ch in group.get(const.KEYWORD_CHARTS):
59
-                release_name = release_prefix(
60
-                    prefix, ch.get('chart').get('chart_name'))
46
+    $ armada test --release blog-1
61 47
 
62
-                if release_name in known_release_names:
63
-                    LOG.info('RUNNING: %s tests', release_name)
64
-                    resp = tiller.testing_release(release_name)
48
+"""
65 49
 
66
-                    if not resp:
67
-                        continue
50
+SHORT_DESC = "command test releases"
68 51
 
69
-                    test_status = getattr(resp.info.status,
70
-                                          'last_test_suite_run', 'FAILED')
71
-                    if test_status.results[0].status:
72
-                        LOG.info("PASSED: %s", release_name)
73
-                    else:
74
-                        LOG.info("FAILED: %s", release_name)
75 52
 
53
+@test.command(name='test', help=DESC, short_help=SHORT_DESC)
54
+@click.option('--file', help='armada manifest', type=str)
55
+@click.option('--release', help='helm release', type=str)
56
+@click.option('--tiller-host', help="Tiller Host IP")
57
+@click.option(
58
+    '--tiller-port', help="Tiller host Port", type=int, default=44134)
59
+@click.pass_context
60
+def test_charts(ctx, file, release, tiller_host, tiller_port):
61
+    TestChartManifest(
62
+        ctx, file, release, tiller_host, tiller_port).invoke()
63
+
64
+
65
+class TestChartManifest(CliAction):
66
+    def __init__(self, ctx, file, release, tiller_host, tiller_port):
67
+
68
+        super(TestChartManifest, self).__init__()
69
+        self.ctx = ctx
70
+        self.file = file
71
+        self.release = release
72
+        self.tiller_host = tiller_host
73
+        self.tiller_port = tiller_port
74
+
75
+    def invoke(self):
76
+        tiller = Tiller(
77
+            tiller_host=self.tiller_host, tiller_port=self.tiller_port)
78
+        known_release_names = [release[0] for release in tiller.list_charts()]
79
+
80
+        if self.release:
81
+            if not self.ctx.obj.get('api', False):
82
+                self.logger.info("RUNNING: %s tests", self.release)
83
+                resp = tiller.testing_release(self.release)
84
+
85
+                if not resp:
86
+                    self.logger.info("FAILED: %s", self.release)
87
+                    return
88
+
89
+                test_status = getattr(resp.info.status, 'last_test_suite_run',
90
+                                      'FAILED')
91
+                if test_status.results[0].status:
92
+                    self.logger.info("PASSED: %s", self.release)
76 93
                 else:
77
-                    LOG.info('Release %s not found - SKIPPING', release_name)
78
-
79
-
80
-class TestServerCommand(cmd.Command):
81
-    def get_parser(self, prog_name):
82
-        parser = super(TestServerCommand, self).get_parser(prog_name)
83
-        parser.add_argument(
84
-            '--release', action='store', help='testing Helm in Release')
85
-        parser.add_argument(
86
-            '-f',
87
-            '--file',
88
-            type=str,
89
-            metavar='FILE',
90
-            help='testing Helm releases in Manifest')
91
-        parser.add_argument(
92
-            '--tiller-host',
93
-            action='store',
94
-            type=str,
95
-            default=None,
96
-            help='Specify the tiller host')
97
-        parser.add_argument(
98
-            '--tiller-port',
99
-            action='store',
100
-            type=int,
101
-            default=44134,
102
-            help='Specify the tiller port')
103
-
104
-        return parser
105
-
106
-    def take_action(self, parsed_args):
107
-        testService(parsed_args)
94
+                    self.logger.info("FAILED: %s", self.release)
95
+            else:
96
+                client = self.ctx.obj.get('CLIENT')
97
+                query = {
98
+                    'tiller_host': self.tiller_host,
99
+                    'tiller_port': self.tiller_port
100
+                }
101
+                resp = client.get_test_release(release=self.release,
102
+                                               query=query)
103
+
104
+                self.logger.info(resp.get('result'))
105
+                self.logger.info(resp.get('message'))
106
+
107
+        if self.file:
108
+            if not self.ctx.obj.get('api', False):
109
+                documents = yaml.safe_load_all(open(self.file).read())
110
+                armada_obj = Manifest(documents).get_manifest()
111
+                prefix = armada_obj.get(const.KEYWORD_ARMADA).get(
112
+                    const.KEYWORD_PREFIX)
113
+
114
+                for group in armada_obj.get(const.KEYWORD_ARMADA).get(
115
+                        const.KEYWORD_GROUPS):
116
+                    for ch in group.get(const.KEYWORD_CHARTS):
117
+                        release_name = release_prefix(
118
+                            prefix, ch.get('chart').get('chart_name'))
119
+
120
+                        if release_name in known_release_names:
121
+                            self.logger.info('RUNNING: %s tests', release_name)
122
+                            resp = tiller.testing_release(release_name)
123
+
124
+                            if not resp:
125
+                                continue
126
+
127
+                            test_status = getattr(
128
+                                resp.info.status, 'last_test_suite_run',
129
+                                'FAILED')
130
+                            if test_status.results[0].status:
131
+                                self.logger.info("PASSED: %s", release_name)
132
+                            else:
133
+                                self.logger.info("FAILED: %s", release_name)
134
+
135
+                        else:
136
+                            self.logger.info(
137
+                                'Release %s not found - SKIPPING',
138
+                                release_name)
139
+            else:
140
+                client = self.ctx.obj.get('CLIENT')
141
+                query = {
142
+                    'tiller_host': self.tiller_host,
143
+                    'tiller_port': self.tiller_port
144
+                }
145
+
146
+                with open(self.filename, 'r') as f:
147
+                    resp = client.get_test_manifest(manifest=f.read(),
148
+                                                    query=query)
149
+                    for test in resp.get('tests'):
150
+                        self.logger.info('Test State: %s', test)
151
+                        for item in test.get('tests').get(test):
152
+                            self.logger.info(item)
153
+
154
+                    self.logger.info(resp)

+ 80
- 25
armada/cli/tiller.py View File

@@ -12,41 +12,96 @@
12 12
 # See the License for the specific language governing permissions and
13 13
 # limitations under the License.
14 14
 
15
-from cliff import command as cmd
16 15
 
16
+import click
17
+
18
+from armada.cli import CliAction
17 19
 from armada.handlers.tiller import Tiller
18 20
 
19
-from oslo_config import cfg
20
-from oslo_log import log as logging
21 21
 
22
-LOG = logging.getLogger(__name__)
22
+@click.group()
23
+def tiller():
24
+    """ Tiller Services actions
25
+
26
+    """
27
+
28
+
29
+DESC = """
30
+This command gets tiller information
31
+
32
+The tiller command uses flags to obtain information from tiller services
33
+
34
+To obtain armada deployed releases:
35
+
36
+    $ armada tiller --releases
37
+
38
+To obtain tiller service status/information:
39
+
40
+    $ armada tiller --status
41
+
42
+"""
43
+
44
+SHORT_DESC = "command gets tiller infromation"
45
+
23 46
 
24
-CONF = cfg.CONF
47
+@tiller.command(name='tiller', help=DESC, short_help=SHORT_DESC)
48
+@click.option('--tiller-host', help="Tiller host ip", default=None)
49
+@click.option(
50
+    '--tiller-port', help="Tiller host port", type=int, default=44134)
51
+@click.option('--releases', help="list of deployed releses", is_flag=True)
52
+@click.option('--status', help="Status of Armada services", is_flag=True)
53
+@click.pass_context
54
+def tiller_service(ctx, tiller_host, tiller_port, releases, status):
55
+    TillerServices(ctx, tiller_host, tiller_port, releases, status).invoke()
25 56
 
26 57
 
27
-def tillerServer(args):
58
+class TillerServices(CliAction):
28 59
 
29
-    tiller = Tiller()
60
+    def __init__(self, ctx, tiller_host, tiller_port, releases, status):
61
+        super(TillerServices, self).__init__()
62
+        self.ctx = ctx
63
+        self.tiller_host = tiller_host
64
+        self.tiller_port = tiller_port
65
+        self.releases = releases
66
+        self.status = status
30 67
 
31
-    if args.status:
32
-        resp = tiller.tiller_version()
33
-        LOG.info('Tiller Service: %s', tiller.tiller_status())
34
-        LOG.info('Tiller Version: %s', getattr(resp.Version, 'sem_ver', False))
68
+    def invoke(self):
35 69
 
36
-    if args.releases:
37
-        for release in tiller.list_releases():
38
-            LOG.info("Release: %s ( namespace= %s )", release.name,
39
-                     release.namespace)
70
+        tiller = Tiller(
71
+            tiller_host=self.tiller_host, tiller_port=self.tiller_port)
40 72
 
73
+        if self.status:
74
+            if not self.ctx.obj.get('api', False):
75
+                self.logger.info('Tiller Service: %s', tiller.tiller_status())
76
+                self.logger.info('Tiller Version: %s', tiller.tiller_version())
77
+            else:
78
+                client = self.ctx.obj.get('CLIENT')
79
+                query = {
80
+                    'tiller_host': self.tiller_host,
81
+                    'tiller_port': self.tiller_port
82
+                }
83
+                resp = client.get_status(query=query)
84
+                tiller_status = resp.get('tiller').get('state', False)
85
+                tiller_version = resp.get('tiller').get('version')
41 86
 
42
-class TillerServerCommand(cmd.Command):
43
-    def get_parser(self, prog_name):
44
-        parser = super(TillerServerCommand, self).get_parser(prog_name)
45
-        parser.add_argument('--status', action='store_true',
46
-                            default=False, help='Check Tiller service')
47
-        parser.add_argument('--releases', action='store_true',
48
-                            default=False, help='List Tiller Releases')
49
-        return parser
87
+                self.logger.info("Tiller Service: %s", tiller_status)
88
+                self.logger.info("Tiller Version: %s", tiller_version)
50 89
 
51
-    def take_action(self, parsed_args):
52
-        tillerServer(parsed_args)
90
+        if self.releases:
91
+            if not self.ctx.obj.get('api', False):
92
+                for release in tiller.list_releases():
93
+                    self.logger.info(
94
+                        "Release %s in namespace: %s",
95
+                        release.name, release.namespace)
96
+            else:
97
+                client = self.ctx.obj.get('CLIENT')
98
+                query = {
99
+                    'tiller_host': self.tiller_host,
100
+                    'tiller_port': self.tiller_port
101
+                }
102
+                resp = client.get_releases(query=query)
103
+                for namespace in resp.get('releases'):
104
+                    for release in resp.get('releases').get(namespace):
105
+                        self.logger.info(
106
+                            'Release %s in namespace: %s', release,
107
+                            namespace)

+ 53
- 24
armada/cli/validate.py View File

@@ -12,39 +12,68 @@
12 12
 # See the License for the specific language governing permissions and
13 13
 # limitations under the License.
14 14
 
15
-from cliff import command as cmd
15
+
16
+import click
16 17
 import yaml
17 18
 
18
-from armada.utils.lint import validate_armada_documents, validate_armada_object
19
+from armada.cli import CliAction
20
+from armada.utils.lint import validate_armada_documents
21
+from armada.utils.lint import validate_armada_object
19 22
 from armada.handlers.manifest import Manifest
20 23
 
21
-from oslo_config import cfg
22
-from oslo_log import log as logging
23 24
 
24
-LOG = logging.getLogger(__name__)
25
+@click.group()
26
+def validate():
27
+    """ Test Manifest Charts
28
+
29
+    """
30
+
31
+
32
+DESC = """
33
+This command validates Armada Manifest
34
+
35
+The validate argument must be a relative path to Armada manifest
36
+
37
+    $ armada validate examples/simple.yaml
38
+
39
+"""
40
+
41
+SHORT_DESC = "command validates Armada Manifest"
25 42
 
26
-CONF = cfg.CONF
27 43
 
44
+@validate.command(name='validate', help=DESC, short_help=SHORT_DESC)
45
+@click.argument('filename')
46
+@click.pass_context
47
+def validate_manifest(ctx, filename):
48
+    ValidateManifest(ctx, filename).invoke()
28 49
 
29
-def validateYaml(args):
30
-    documents = yaml.safe_load_all(open(args.file).read())
31
-    manifest_obj = Manifest(documents).get_manifest()
32
-    obj_check = validate_armada_object(manifest_obj)
33
-    doc_check = validate_armada_documents(documents)
34 50
 
35
-    try:
36
-        if doc_check and obj_check:
37
-            LOG.info('Successfully validated: %s', args.file)
38
-    except Exception:
39
-        raise Exception('Failed to validate: %s', args.file)
51
+class ValidateManifest(CliAction):
40 52
 
53
+    def __init__(self, ctx, filename):
54
+        super(ValidateManifest, self).__init__()
55
+        self.ctx = ctx
56
+        self.filename = filename
41 57
 
42
-class ValidateYamlCommand(cmd.Command):
43
-    def get_parser(self, prog_name):
44
-        parser = super(ValidateYamlCommand, self).get_parser(prog_name)
45
-        parser.add_argument('file', type=str, metavar='FILE',
46
-                            help='Armada yaml file to validate')
47
-        return parser
58
+    def invoke(self):
59
+        if not self.ctx.obj.get('api', False):
60
+            documents = yaml.safe_load_all(open(self.filename).read())
61
+            manifest_obj = Manifest(documents).get_manifest()
62
+            obj_check = validate_armada_object(manifest_obj)
63
+            doc_check = validate_armada_documents(documents)
48 64
 
49
-    def take_action(self, parsed_args):
50
-        validateYaml(parsed_args)
65
+            try:
66
+                if doc_check and obj_check:
67
+                    self.logger.info(
68
+                        'Successfully validated: %s', self.filename)
69
+            except Exception:
70
+                raise Exception('Failed to validate: %s', self.filename)
71
+        else:
72
+            client = self.ctx.obj.get('CLIENT')
73
+            with open(self.filename, 'r') as f:
74
+                resp = client.post_validate(f.read())
75
+                if resp.get('valid', False):
76
+                    self.logger.info(
77
+                        'Successfully validated: %s', self.filename)
78
+                else:
79
+                    self.logger.error("Failed to validate: %s", self.filename)

+ 107
- 0
armada/common/client.py View File

@@ -0,0 +1,107 @@
1
+# Copyright 2017 The Armada Authors.
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License");
4
+# you may not use this file except in compliance with the License.
5
+# You may obtain a copy of the License at
6
+#
7
+#     http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS,
11
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+# See the License for the specific language governing permissions and
13
+# limitations under the License.
14
+
15
+import yaml
16
+
17
+from oslo_config import cfg
18
+from oslo_log import log as logging
19
+
20
+from armada.exceptions import api_exceptions as err
21
+from armada.handlers.armada import Override
22
+
23
+LOG = logging.getLogger(__name__)
24
+CONF = cfg.CONF
25
+
26
+API_VERSION = 'v{}/{}'
27
+
28
+
29
+class ArmadaClient(object):
30
+
31
+    def __init__(self, session):
32
+        self.session = session
33
+
34
+    def _set_endpoint(self, version, action):
35
+        return API_VERSION.format(version, action)
36
+
37
+    def get_status(self, query):
38
+
39
+        endpoint = self._set_endpoint('1.0', 'status')
40
+        resp = self.session.get(endpoint, query=query)
41
+
42
+        self._check_response(resp)
43
+
44
+        return resp.json()
45
+
46
+    def get_releases(self, query):
47
+
48
+        endpoint = self._set_endpoint('1.0', 'releases')
49
+        resp = self.session.get(endpoint, query=query)
50
+
51
+        self._check_response(resp)
52
+
53
+        return resp.json()
54
+
55
+    def post_validate(self, manifest=None):
56
+
57
+        endpoint = self._set_endpoint('1.0', 'validate')
58
+        resp = self.session.post(endpoint, body=manifest)
59
+
60
+        self._check_response(resp)
61
+
62
+        return resp.json()
63
+
64
+    def post_apply(self, manifest=None, values=None, set=None, query=None):
65
+
66
+        if values or set:
67
+            document = list(yaml.safe_load_all(manifest))
68
+            override = Override(
69
+                document, overrides=set, values=values).update_manifests()
70
+            manifest = yaml.dump(override)
71
+
72
+        endpoint = self._set_endpoint('1.0', 'apply')
73
+        resp = self.session.post(endpoint, body=manifest, query=query)
74
+
75
+        self._check_response(resp)
76
+
77
+        return resp.json()
78
+
79
+    def get_test_release(self, release=None, query=None):
80
+
81
+        endpoint = self._set_endpoint('1.0', 'test/{}'.format(release))
82
+        resp = self.session.get(endpoint, query=query)
83
+
84
+        self._check_response(resp)
85
+
86
+        return resp.json()
87
+
88
+    def post_test_manifest(self, manifest=None, query=None):
89
+
90
+        endpoint = self._set_endpoint('1.0', 'tests')
91
+        resp = self.session.post(endpoint, body=manifest, query=query)
92
+
93
+        self._check_response(resp)
94
+
95
+        return resp.json()
96
+
97
+    def _check_response(self, resp):
98
+        if resp.status_code == 401:
99
+            raise err.ClientUnauthorizedError(
100
+                "Unauthorized access to %s, include valid token.".format(
101
+                    resp.url))
102
+        elif resp.status_code == 403:
103
+            raise err.ClientForbiddenError(
104
+                "Forbidden access to %s" % resp.url)
105
+        elif not resp.ok:
106
+            raise err.ClientError(
107
+                "Error - received %d: %s" % (resp.status_code, resp.text))

+ 12
- 2
armada/common/policies/service.py View File

@@ -20,12 +20,22 @@ armada_policies = [
20 20
         name=base.ARMADA % 'create_endpoints',
21 21
         check_str=base.RULE_ADMIN_REQUIRED,
22 22
         description='install manifest charts',
23
-        operations=[{'path': '/v1.0/apply/', 'method': 'POST'}]),
23
+        operations=[{'path': '/api/v1.0/apply/', 'method': 'POST'}]),
24 24
     policy.DocumentedRuleDefault(
25 25
         name=base.ARMADA % 'validate_manifest',
26 26
         check_str=base.RULE_ADMIN_REQUIRED,
27
+        description='validate installed manifest',
28
+        operations=[{'path': '/api/v1.0/validate/', 'method': 'POST'}]),
29
+    policy.DocumentedRuleDefault(
30
+        name=base.ARMADA % 'test_release',
31
+        check_str=base.RULE_ADMIN_REQUIRED,
32
+        description='validate install manifest',
33
+        operations=[{'path': '/api/v1.0/test/{release}', 'method': 'GET'}]),
34
+    policy.DocumentedRuleDefault(
35
+        name=base.ARMADA % 'test_manifest',
36
+        check_str=base.RULE_ADMIN_REQUIRED,
27 37
         description='validate install manifest',
28
-        operations=[{'path': '/v1.0/validate/', 'method': 'POST'}]),
38
+        operations=[{'path': '/api/v1.0/tests/', 'method': 'POST'}]),
29 39
 ]
30 40
 
31 41
 

+ 2
- 4
armada/common/policies/tiller.py View File

@@ -20,15 +20,13 @@ tiller_policies = [
20 20
         name=base.TILLER % 'get_status',
21 21
         check_str=base.RULE_ADMIN_REQUIRED,
22 22
         description='Get tiller status',
23
-        operations=[{'path': '/v1.0/status/',
24
-                     'method': 'GET'}]),
23
+        operations=[{'path': '/api/v1.0/status/', 'method': 'GET'}]),
25 24
 
26 25
     policy.DocumentedRuleDefault(
27 26
         name=base.TILLER % 'get_release',
28 27
         check_str=base.RULE_ADMIN_REQUIRED,
29 28
         description='Get tiller release',
30
-        operations=[{'path': '/v1.0/releases/',
31
-                     'method': 'GET'}]),
29
+        operations=[{'path': '/api/v1.0/releases/', 'method': 'GET'}]),
32 30
 ]
33 31
 
34 32
 

+ 96
- 0
armada/common/session.py View File

@@ -0,0 +1,96 @@
1
+# Copyright 2017 The Armada Authors.
2
+#
3
+# Licensed under the Apache License, Version 2.0 (the "License");
4
+# you may not use this file except in compliance with the License.
5
+# You may obtain a copy of the License at
6
+#
7
+#     http://www.apache.org/licenses/LICENSE-2.0
8
+#
9
+# Unless required by applicable law or agreed to in writing, software
10
+# distributed under the License is distributed on an "AS IS" BASIS,
11
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+# See the License for the specific language governing permissions and
13
+# limitations under the License.
14
+
15
+import requests
16
+
17
+from oslo_config import cfg
18
+from oslo_log import log as logging
19
+
20
+LOG = logging.getLogger(__name__)
21
+CONF = cfg.CONF
22
+
23
+
24
+class ArmadaSession(object):
25
+    """
26
+    A session to the Armada API maintaining credentials and API options
27
+
28
+    :param string host: The armada server hostname or IP
29
+    :param int port: (optional) The service port appended if specified
30
+    :param string token: Auth token
31
+    :param string marker: (optional) external context marker
32
+    """
33
+
34
+    def __init__(self, host, port=None, scheme='http', token=None,
35
+                 marker=None):
36
+
37
+        self._session = requests.Session()
38
+        self._session.headers.update({
39
+            'X-Auth-Token': token,
40
+            'X-Context-Marker': marker
41
+        })
42
+        self.host = host
43
+        self.scheme = scheme
44
+
45
+        if port:
46
+            self.port = port
47
+            self.base_url = "{}://{}:{}/api/".format(
48
+                self.scheme, self.host, self.port)
49
+        else:
50
+            self.base_url = "{}://{}/api/".format(
51
+                self.scheme, self.host)
52
+
53
+        self.token = token
54
+        self.marker = marker
55
+        self.logger = LOG
56
+
57
+    # TODO Add keystone authentication to produce a token for this session
58
+    def get(self, endpoint, query=None):
59
+        """
60
+        Send a GET request to armada.
61
+
62
+        :param string endpoint: URL string following hostname and API prefix
63
+        :param dict query: A dict of k, v pairs to add to the query string
64
+
65
+        :return: A requests.Response object
66
+        """
67
+        api_url = '{}{}'.format(self.base_url, endpoint)
68
+        resp = self._session.get(
69
+            api_url, params=query, timeout=3600)
70
+
71
+        return resp
72
+
73
+    def post(self, endpoint, query=None, body=None, data=None):
74
+        """
75
+        Send a POST request to armada. If both body and data are specified,
76
+        body will will be used.
77
+
78
+        :param string endpoint: URL string following hostname and API prefix
79
+        :param dict query: dict of k, v parameters to add to the query string
80
+        :param string body: string to use as the request body.
81
+        :param data: Something json.dumps(s) can serialize.
82
+        :return: A requests.Response object
83
+        """
84
+        api_url = '{}{}'.format(self.base_url, endpoint)
85
+
86
+        self.logger.debug("Sending POST with armada_client session")
87
+        if body is not None:
88
+            self.logger.debug("Sending POST with explicit body: \n%s" % body)
89
+            resp = self._session.post(
90
+                api_url, params=query, data=body, timeout=3600)
91
+        else:
92
+            self.logger.debug("Sending POST with JSON body: \n%s" % str(data))
93
+            resp = self._session.post(
94
+                api_url, params=query, json=data, timeout=3600)
95
+
96
+        return resp

+ 3
- 2
armada/conf/__init__.py View File

@@ -17,12 +17,13 @@ import os
17 17
 from oslo_config import cfg
18 18
 
19 19
 from armada.conf import default
20
+from armada import const
20 21
 
21 22
 CONF = cfg.CONF
22 23
 
23 24
 # Load config file if exists
24
-if (os.path.exists('etc/armada/armada.conf')):
25
-    CONF(['--config-file', 'etc/armada/armada.conf'])
25
+if (os.path.exists(const.CONFIG_PATH)):
26
+    CONF(['--config-file', const.CONFIG_PATH])
26 27
 
27 28
 
28 29
 def set_app_default_configs():

+ 8
- 1
armada/conf/default.py View File

@@ -14,6 +14,8 @@
14 14
 
15 15
 from oslo_config import cfg
16 16
 
17
+from keystoneauth1 import loading
18
+
17 19
 from armada.conf import utils
18 20
 
19 21
 default_options = [
@@ -71,7 +73,12 @@ The Keystone project domain name used for authentication.
71 73
 
72 74
 def register_opts(conf):
73 75
     conf.register_opts(default_options)
76
+    conf.register_opts(
77
+        loading.get_auth_plugin_conf_options('password'),
78
+        group='keystone_authtoken')
74 79
 
75 80
 
76 81
 def list_opts():
77
-    return {'DEFAULT': default_options}
82
+    return {
83
+        'DEFAULT': default_options,
84
+        'keystone_authtoken': loading.get_auth_plugin_conf_options('password')}

+ 3
- 0
armada/const.py View File

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

+ 18
- 0
armada/exceptions/api_exceptions.py View File

@@ -31,3 +31,21 @@ class ApiJsonException(ApiException):
31 31
     '''Exception that occurs during chart cleanup.'''
32 32
 
33 33
     message = 'There was an error listing the helm chart releases.'
34
+
35
+
36
+class ClientUnauthorizedError(ApiException):
37
+    '''Exception that occurs during chart cleanup.'''
38
+
39
+    message = 'There was an error listing the helm chart releases.'
40
+
41
+
42
+class ClientForbiddenError(ApiException):
43
+    '''Exception that occurs during chart cleanup.'''
44
+
45
+    message = 'There was an error listing the helm chart releases.'
46
+
47
+
48
+class ClientError(ApiException):
49
+    '''Exception that occurs during chart cleanup.'''
50
+
51
+    message = 'There was an error listing the helm chart releases.'

+ 5
- 12
armada/handlers/armada.py View File

@@ -53,8 +53,7 @@ class Armada(object):
53 53
                  timeout=DEFAULT_TIMEOUT,
54 54
                  tiller_host=None,
55 55
                  tiller_port=44134,
56
-                 values=None,
57
-                 debug=False):
56
+                 values=None):
58 57
         '''
59 58
         Initialize the Armada Engine and establish
60 59
         a connection to Tiller
@@ -69,14 +68,8 @@ class Armada(object):
69 68
         self.timeout = timeout
70 69
         self.tiller = Tiller(tiller_host=tiller_host, tiller_port=tiller_port)
71 70
         self.values = values
72
-        self.documents = list(yaml.safe_load_all(file))
71
+        self.documents = file
73 72
         self.config = None
74
-        self.debug = debug
75
-
76
-        # Set debug value
77
-        # Define a default handler at INFO logging level
78
-        if self.debug:
79
-            logging.basicConfig(level=logging.DEBUG)
80 73
 
81 74
     def get_armada_manifest(self):
82 75
         return Manifest(self.documents).get_manifest()
@@ -193,7 +186,7 @@ class Armada(object):
193 186
         Syncronize Helm with the Armada Config(s)
194 187
         '''
195 188
 
196
-        msg = {'installed': [], 'upgraded': [], 'diff': []}
189
+        msg = {'install': [], 'upgrade': [], 'diff': []}
197 190
 
198 191
         # TODO: (gardlt) we need to break up this func into
199 192
         # a more cleaner format
@@ -314,7 +307,7 @@ class Armada(object):
314 307
                             timeout=wait_values.get('timeout', DEFAULT_TIMEOUT)
315 308
                         )
316 309
 
317
-                    msg['upgraded'].append(prefix_chart)
310
+                    msg['upgrade'].append(prefix_chart)
318 311
 
319 312
                 # process install
320 313
                 else:
@@ -338,7 +331,7 @@ class Armada(object):
338 331
                             namespace=chart.namespace,
339 332
                             timeout=wait_values.get('timeout', 3600))
340 333
 
341
-                    msg['installed'].append(prefix_chart)
334
+                    msg['install'].append(prefix_chart)
342 335
 
343 336
                 LOG.debug("Cleaning up chart source in %s",
344 337
                           chartbuilder.source_directory)

+ 7
- 2
armada/handlers/k8s.py View File

@@ -15,7 +15,9 @@
15 15
 import re
16 16
 import time
17 17
 
18
-from kubernetes import client, config, watch
18
+from kubernetes import client
19
+from kubernetes import config
20
+from kubernetes import watch
19 21
 from kubernetes.client.rest import ApiException
20 22
 from oslo_config import cfg
21 23
 from oslo_log import log as logging
@@ -37,7 +39,10 @@ class K8s(object):
37 39
         '''
38 40
         Initialize connection to Kubernetes
39 41
         '''
40
-        config.load_kube_config()
42
+        try:
43
+            config.load_incluster_config()
44
+        except:
45
+            config.load_kube_config()
41 46
 
42 47
         self.client = client.CoreV1Api()
43 48
         self.batch_api = client.BatchV1Api()

+ 6
- 6
armada/handlers/tiller.py View File

@@ -309,9 +309,6 @@ class Tiller(object):
309 309
 
310 310
         LOG.info("Wait: %s, Timeout: %s", wait, timeout)
311 311
 
312
-        if timeout > self.timeout:
313
-            self.timeout = timeout
314
-
315 312
         if values is None:
316 313
             values = Config(raw='')
317 314
         else:
@@ -349,8 +346,9 @@ class Tiller(object):
349 346
         try:
350 347
 
351 348
             stub = ReleaseServiceStub(self.channel)
352
-            release_request = TestReleaseRequest(name=release, timeout=timeout,
353
-                                                 cleanup=cleanup)
349
+
350
+            release_request = TestReleaseRequest(
351
+                name=release, timeout=timeout, cleanup=cleanup)
354 352
 
355 353
             content = self.get_release_content(release)
356 354
 
@@ -417,9 +415,11 @@ class Tiller(object):
417 415
             stub = ReleaseServiceStub(self.channel)
418 416
             release_request = GetVersionRequest()
419 417
 
420
-            return stub.GetVersion(
418
+            tiller_version = stub.GetVersion(
421 419
                 release_request, self.timeout, metadata=self.metadata)
422 420
 
421
+            return getattr(tiller_version.Version, 'sem_ver', None)
422
+
423 423
         except Exception:
424 424
             raise ex.TillerVersionException()
425 425
 

+ 66
- 24
armada/shell.py View File

@@ -12,39 +12,81 @@
12 12
 # See the License for the specific language governing permissions and
13 13
 # limitations under the License.
14 14
 
15
-import sys
15
+from urllib.parse import urlparse
16 16
 
17
+import click
17 18
 from oslo_config import cfg
18 19
 from oslo_log import log
19
-from cliff import app
20
-from cliff import commandmanager as cm
21 20
 
22
-import armada
21
+from armada.cli.apply import apply_create
22
+from armada.cli.test import test_charts
23
+from armada.cli.tiller import tiller_service
24
+from armada.cli.validate import validate_manifest
25
+from armada.common.client import ArmadaClient
26
+from armada.common.session import ArmadaSession
23 27
 
24 28
 CONF = cfg.CONF
25 29
 
26 30
 
27
-class ArmadaApp(app.App):
28
-    def __init__(self, **kwargs):
29
-        super(ArmadaApp, self).__init__(
30
-            description='Armada - Upgrade and deploy your charts',
31
-            version=armada.__version__,
32
-            command_manager=cm.CommandManager('armada'),
33
-            **kwargs)
31
+@click.group()
32
+@click.option(
33
+    '--debug/--no-debug', help='Enable or disable debugging', default=False)
34
+@click.option(
35
+    '--api/--no-api', help='Execute service endpoints. (requires url option)',
36
+    default=False)
37
+@click.option(
38
+    '--url', help='Armada Service Endpoint', envvar='HOST', default=None)
39
+@click.option(
40
+    '--token', help='Keystone Service Token', envvar='TOKEN', default=None)
41
+@click.pass_context
42
+def main(ctx, debug, api, url, token):
43
+    """
44
+    Multi Helm Chart Deployment Manager
34 45
 
35
-    def build_option_parser(self, description, version, argparse_kwargs=None):
36
-        parser = super(ArmadaApp, self).build_option_parser(
37
-            description, version, argparse_kwargs)
38
-        return parser
46
+    Common actions from this point include:
39 47
 
40
-    def configure_logging(self):
41
-        super(ArmadaApp, self).configure_logging()
42
-        log.register_options(CONF)
43
-        log.set_defaults(default_log_levels=CONF.default_log_levels)
44
-        log.setup(CONF, 'armada')
48
+    \b
49
+    $ armada apply
50
+    $ armada test
51
+    $ armada tiller
52
+    $ armada validate
45 53
 
54
+    Environment:
46 55
 
47
-def main(argv=None):
48
-    if argv is None:
49
-        argv = sys.argv[1:]
50
-    return ArmadaApp().run(argv)
56
+        \b
57
+        $TOKEN set auth token
58
+        $HOST  set armada service host endpoint
59
+
60
+    This tool will communicate with deployed Tiller in your Kubernetes cluster.
61
+    """
62
+
63
+    if not ctx.obj:
64
+        ctx.obj = {}
65
+
66
+    if api:
67
+        if not url or not token:
68
+            raise click.ClickException(
69
+                'When api option is enable user needs to pass url')
70
+        else:
71
+            ctx.obj['api'] = api
72
+            parsed_url = urlparse(url)
73
+            ctx.obj['CLIENT'] = ArmadaClient(
74
+                ArmadaSession(
75
+                    host=parsed_url.netloc,
76
+                    scheme=parsed_url.scheme,
77
+                    token=token)
78
+            )
79
+
80
+    log.register_options(CONF)
81
+
82
+    if debug:
83
+        CONF.debug = debug
84
+
85
+    log.set_defaults(default_log_levels=CONF.default_log_levels)
86
+    log.setup(CONF, 'armada')
87
+
88
+
89
+main.add_command(apply_create)
90
+main.add_command(test_charts)
91
+main.add_command(tiller_service)
92
+main.add_command(validate_manifest)

+ 8
- 5
armada/tests/unit/api/test_api.py View File

@@ -38,7 +38,7 @@ class TestAPI(APITestCase):
38 38
     @mock.patch('armada.api.armada_controller.Handler')
39 39
     def test_armada_apply(self, mock_armada):
40 40
         '''
41
-        Test /armada/apply endpoint
41
+        Test /api/v1.0/apply endpoint
42 42
         '''
43 43
         mock_armada.sync.return_value = None
44 44
 
@@ -54,7 +54,7 @@ class TestAPI(APITestCase):
54 54
 
55 55
         doc = {u'message': u'Success'}
56 56
 
57
-        result = self.simulate_post(path='/armada/apply', body=body)
57
+        result = self.simulate_post(path='/api/v1.0/apply', body=body)
58 58
         self.assertEqual(result.json, doc)
59 59
 
60 60
     @unittest.skip('Test does not handle auth/policy correctly')
@@ -62,6 +62,7 @@ class TestAPI(APITestCase):
62 62
     def test_tiller_status(self, mock_tiller):
63 63
         '''
64 64
         Test /status endpoint
65
+        Test /api/v1.0/status endpoint
65 66
         '''
66 67
 
67 68
         # Mock tiller status value
@@ -70,11 +71,13 @@ class TestAPI(APITestCase):
70 71
         # FIXME(lamt) This variable is unused.  Uncomment when it is.
71 72
         # doc = {u'message': u'Tiller Server is Active'}
72 73
 
73
-        result = self.simulate_get('/v1.0/status')
74
+        result = self.simulate_get('/api/v1.0/status')
74 75
 
75 76
         # TODO(lamt) This should be HTTP_401 if no auth is happening, but auth
76 77
         # is not implemented currently, so it falls back to a policy check
77 78
         # failure, thus a 403.  Change this once it is completed
79
+
80
+        # Fails due to invalid access
78 81
         self.assertEqual(falcon.HTTP_403, result.status)
79 82
 
80 83
         # FIXME(lamt) Need authentication - mock, fixture
@@ -84,7 +87,7 @@ class TestAPI(APITestCase):
84 87
     @mock.patch('armada.api.tiller_controller.Tiller')
85 88
     def test_tiller_releases(self, mock_tiller):
86 89
         '''
87
-        Test /tiller/releases endpoint
90
+        Test /api/v1.0/releases endpoint
88 91
         '''
89 92
 
90 93
         # Mock tiller status value
@@ -93,7 +96,7 @@ class TestAPI(APITestCase):
93 96
         # FIXME(lamt) This variable is unused. Uncomment when it is.
94 97
         # doc = {u'releases': {}}
95 98
 
96
-        result = self.simulate_get('/v1.0/releases')
99
+        result = self.simulate_get('/api/v1.0/releases')
97 100
 
98 101
         # TODO(lamt) This should be HTTP_401 if no auth is happening, but auth
99 102
         # is not implemented currently, so it falls back to a policy check

armada/tests/unit/test_policy.py → armada/tests/unit/common/test_policy.py View File


+ 32
- 5
docs/source/commands/apply.rst View File

@@ -7,15 +7,42 @@ Commands
7 7
 
8 8
 .. code:: bash
9 9
 
10
-    Usage: armada apply FILE
10
+    Usage: armada apply [OPTIONS] FILENAME
11 11
 
12
+      This command install and updates charts defined in armada manifest
12 13
 
13
-    Options:
14
+      The apply argument must be relative path to Armada Manifest. Executing
15
+      apply commnad once will install all charts defined in manifest. Re-
16
+      executing apply commnad will execute upgrade.
17
+
18
+      To see how to create an Armada manifest:
19
+      http://armada-helm.readthedocs.io/en/latest/operations/
20
+
21
+      To obtain install/upgrade charts:
22
+
23
+          $ armada apply examples/simple.yaml
14 24
 
15
-    [-h] [--dry-run] [--debug-logging] [--disable-update-pre]
16
-    [--disable-update-post] [--enable-chart-cleanup] [--wait]
17
-    [--timeout TIMEOUT]
25
+      To obtain override manifest:
18 26
 
27
+          $ armada apply examples/simple.yaml --set manifest:simple-armada:relase_name="wordpress"
28
+
29
+          or
30
+
31
+          $ armada apply examples/simple.yaml --values examples/simple-ovr-values.yaml
32
+
33
+    Options:
34
+      --api                   Contacts service endpoint
35
+      --disable-update-post   run charts without install
36
+      --disable-update-pre    run charts without install
37
+      --dry-run               run charts without install
38
+      --enable-chart-cleanup  Clean up Unmanaged Charts
39
+      --set TEXT
40
+      --tiller-host TEXT      Tiller host ip
41
+      --tiller-port INTEGER   Tiller host port
42
+      --timeout INTEGER       specifies time to wait for charts
43
+      -f, --values TEXT
44
+      --wait                  wait until all charts deployed
45
+      --help                  Show this message and exit.
19 46
 
20 47
 Synopsis
21 48
 --------

+ 20
- 3
docs/source/commands/test.rst View File

@@ -7,11 +7,28 @@ Commands
7 7
 
8 8
 .. code:: bash
9 9
 
10
-    Usage: armada test
10
+    Usage: armada test [OPTIONS]
11 11
 
12
-    Options:
12
+      This command test deployed charts
13
+
14
+      The tiller command uses flags to obtain information from tiller services.
15
+      The test command will run the release chart tests either via a
16
+      manifest or by targeting a relase.
17
+
18
+      To obtain armada deployed releases:
19
+
20
+          $ armada test --file examples/simple.yaml
13 21
 
14
-    [-h] [--release RELEASE] [--file FILE]
22
+      To test release:
23
+
24
+          $ armada test --release blog-1
25
+
26
+    Options:
27
+      --file TEXT            armada manifest
28
+      --release TEXT         helm release
29
+      --tiller-host TEXT     Tiller Host IP
30
+      --tiller-port INTEGER  Tiller host Port
31
+      --help                 Show this message and exit.
15 32
 
16 33
 
17 34
 Synopsis

+ 17
- 3
docs/source/commands/tiller.rst View File

@@ -7,12 +7,26 @@ Commands
7 7
 
8 8
 .. code:: bash
9 9
 
10
-    Usage: armada tiller
10
+    Usage: armada tiller [OPTIONS]
11 11
 
12
-    Options:
12
+      This command gets tiller information
13
+
14
+      The tiller command uses flags to obtain information from tiller services
15
+
16
+      To obtain armada deployed releases:
13 17
 
14
-    [-h] [--status] [--releases]
18
+          $ armada tiller --releases
15 19
 
20
+      To obtain tiller service status/information:
21
+
22
+          $ armada tiller --status
23
+
24
+    Options:
25
+      --tiller-host TEXT     Tiller host ip
26
+      --tiller-port INTEGER  Tiller host port
27
+      --releases             list of deployed releases
28
+      --status               Status of Armada services
29
+      --help                 Show this message and exit.
16 30
 
17 31
 Synopsis
18 32
 --------

+ 8
- 3
docs/source/commands/validate.rst View File

@@ -7,11 +7,16 @@ Commands
7 7
 
8 8
 .. code:: bash
9 9
 
10
-    Usage: armada validate FILE
10
+    Usage: armada validate [OPTIONS] FILENAME
11 11
 
12
-    Options:
12
+      This command validates Armada Manifest
13
+
14
+      The validate argument must be a relative path to Armada manifest
13 15
 
14
-    [-h]
16
+          $ armada validate examples/simple.yaml
17
+
18
+    Options:
19
+      --help  Show this message and exit.
15 20
 
16 21
 Synopsis
17 22
 --------

+ 5
- 5
docs/source/development/getting-started.rst View File

@@ -13,8 +13,7 @@ To use the docker containter to develop:
13 13
 
14 14
 .. code-block:: bash
15 15
 
16
-    git clone http://github.com/att-comdev/armada.git
17
-    cd armada
16
+    git clone http://github.com/att-comdev/armada.git && cd armada
18 17
 
19 18
     pip install tox
20 19
 
@@ -23,7 +22,7 @@ To use the docker containter to develop:
23 22
 
24 23
     docker build . -t armada/latest
25 24
 
26
-    docker run -d --name armada -v ~/.kube/config:/armada/.kube/config -v $(pwd)/etc:/armada/etc armada:local
25
+    docker run -d --name armada -v ~/.kube/:/armada/.kube/ -v $(pwd)/etc:/etc armada:local
27 26
 
28 27
 .. note::
29 28
 
@@ -45,7 +44,8 @@ From the directory of the forked repository:
45 44
 
46 45
 
47 46
     git clone http://github.com/att-comdev/armada.git && cd armada
48
-    virtualenv venv
47
+
48
+    virtualenv -p python3 venv
49 49
 
50 50
     pip install -r requirements.txt -r test-requirements.txt
51 51
 
@@ -53,6 +53,7 @@ From the directory of the forked repository:
53 53
 
54 54
     # Testing your armada code
55 55
     # The tox command will execute lint, bandit, cover
56
+    pip install tox
56 57
     tox
57 58
 
58 59
     # For targeted test
@@ -60,7 +61,6 @@ From the directory of the forked repository:
60 61
     tox -e bandit
61 62
     tox -e cover
62 63
 
63
-
64 64
     # policy and config are used in order to use and configure Armada API
65 65
     tox -e genconfig
66 66
     tox -e genpolicy

+ 493
- 48
docs/source/operations/guide-api.rst View File

@@ -1,87 +1,532 @@
1
-Armada RESTful API
2
-===================
1
+Armada Restful API v1.0
2
+=======================
3 3
 
4
-Armada Endpoints
4
+Description
5
+~~~~~~~~~~~
6
+
7
+The Armada API provides the services similar to the cli via Restful endpoints
8
+
9
+
10
+Base URL
11
+~~~~~~~~
12
+
13
+https://armada.localhost/api/v1.0/
14
+
15
+DEFAULT
16
+~~~~~~~
17
+
18
+GET ``/releases``
5 19
 -----------------
6 20
 
7
-::
8 21
 
9
-    Endpoint: POST /armada/apply
22
+Summary
23
++++++++
24
+
25
+Get tiller releases
26
+
27
+
28
+
29
+Request
30
++++++++
31
+
32
+
33
+Responses
34
++++++++++
10 35
 
11
-    :string file The yaml file to apply
12
-    :>json boolean debug Enable debug logging
13
-    :>json boolean disable_update_pre
14
-    :>json boolean disable_update_post
15
-    :>json boolean enable_chart_cleanup
16
-    :>json boolean skip_pre_flight
17
-    :>json object values Override manifest values
18
-    :>json boolean dry_run
19
-    :>json boolean wait
20
-    :>json float timeout
36
+**200**
37
+^^^^^^^
21 38
 
39
+obtain all running releases
22 40
 
23
-::
41
+**Example:**
24 42
 
25
-    Request:
43
+.. code-block:: javascript
26 44
 
27 45
     {
28
-        "file": "examples/openstack-helm.yaml",
29
-        "options": {
30
-            "debug": true,
31
-            "disable_update_pre": false,
32
-            "disable_update_post": false,
33
-            "enable_chart_cleanup": false,
34
-            "skip_pre_flight": false,
35
-            "dry_run": false,
36
-            "wait": false,
37
-            "timeout": false
46
+        "message": {
47
+            "namespace": [
48
+                "armada-release",
49
+                "armada-release"
50
+            ],
51
+            "default": [
52
+                "armada-release",
53
+                "armada-release"
54
+            ]
38 55
         }
39 56
     }
40 57
 
41
-::
58
+**403**
59
+^^^^^^^
60
+
61
+Unable to Authorize or Permission
62
+
63
+
64
+**405**
65
+^^^^^^^
66
+
67
+Failed to perform action
68
+
69
+GET ``/status``
70
+---------------
71
+
72
+
73
+Summary
74
++++++++
75
+
76
+Get armada running state
77
+
78
+
79
+Request
80
++++++++
81
+
42 82
 
43
-    Results:
83
+Responses
84
++++++++++
85
+
86
+**200**
87
+^^^^^^^
88
+
89
+obtain armada status
90
+
91
+**Example:**
92
+
93
+.. code-block:: javascript
44 94
 
45 95
     {
46
-        "message": "success"
96
+        "message": {
97
+            "tiller": {
98
+                "state": True,
99
+                "version": "v2.5.0"
100
+            }
101
+        }
47 102
     }
48 103
 
49
-Tiller Endpoints
104
+**403**
105
+^^^^^^^
106
+
107
+Unable to Authorize or Permission
108
+
109
+
110
+**405**
111
+^^^^^^^
112
+
113
+Failed to perform action
114
+
115
+
116
+GET ``/validate``
50 117
 -----------------
51 118
 
52
-::
53 119
 
54
-    Endpoint: GET /tiller/releases
120
+Summary
121
++++++++
55 122
 
56
-    Description: Retrieves tiller releases.
123
+Get tiller releases
57 124
 
58 125
 
59
-::
126
+Request
127
++++++++
60 128
 
61
-    Results:
129
+
130
+Responses
131
++++++++++
132
+
133
+**200**
134
+^^^^^^^
135
+
136
+obtain all running releases
137
+
138
+
139
+**Example:**
140
+
141
+.. code-block:: javascript
62 142
 
63 143
     {
64
-        "releases": {
65
-            "armada-memcached": "openstack",
66
-            "armada-etcd": "openstack",
67
-            "armada-keystone": "openstack",
68
-            "armada-rabbitmq": "openstack",
69
-            "armada-horizon": "openstack"
144
+        "valid": true
145
+    }
146
+
147
+**403**
148
+^^^^^^^
149
+
150
+Unable to Authorize or Permission
151
+
152
+
153
+**405**
154
+^^^^^^^
155
+
156
+Failed to perform action
157
+
158
+
159
+POST ``/apply``
160
+---------------
161
+
162
+
163
+Summary
164
++++++++
165
+
166
+Install/Update Armada Manifest
167
+
168
+Request
169
++++++++
170
+
171
+Body
172
+^^^^
173
+
174
+.. csv-table::
175
+    :delim: |
176
+    :header: "Name", "Required", "Type", "Format", "Properties", "Description"
177
+    :widths: 20, 10, 15, 15, 30, 25
178
+
179
+        disable-update-post | boolean |  |  |  |
180
+        disable-update-pre | boolean |  |  |  |
181
+        dry-run | boolean |  |  |  |
182
+        enable-chart-cleanup | boolean |  |  |  |
183
+        tiller-host | string |  |  |  |
184
+        tiller-port | int |  |  |  |
185
+        timeout | int |  |  |  |
186
+        wait | boolean |  |  |  |
187
+
188
+
189
+**Armada schema:**
190
+
191
+.. code-block:: javascript
192
+
193
+    {
194
+        "api": true,
195
+        "armada": {}
196
+    }
197
+
198
+Responses
199
++++++++++
200
+
201
+**200**
202
+^^^^^^^
203
+
204
+Succesfull installation/update of manifest
205
+
206
+**Example:**
207
+
208
+.. code-block:: javascript
209
+
210
+    {
211
+        "message": {
212
+            "installed": [
213
+                "armada-release",
214
+                "armada-release"
215
+            ],
216
+            "updated": [
217
+                "armada-release",
218
+                "armada-release"
219
+            ],
220
+            "diff": [
221
+                "values": "value diff",
222
+                "values": "value diff 2"
223
+            ]
70 224
         }
71 225
     }
72 226
 
227
+**403**
228
+^^^^^^^
229
+
230
+Unable to Authorize or Permission
231
+
232
+
233
+**405**
234
+^^^^^^^
235
+
236
+Failed to perform action
237
+
238
+
239
+POST ``/test/{release}``
240
+------------------------
241
+
242
+
243
+Summary
244
++++++++
245
+
246
+Test release name
247
+
73 248
 
74
-::
249
+Parameters
250
+++++++++++
75 251
 
76
-    Endpoint: GET /tiller/status
252
+.. csv-table::
253
+    :delim: |
254
+    :header: "Name", "Located in", "Required", "Type", "Format", "Properties", "Description"
255
+    :widths: 20, 15, 10, 10, 10, 20, 30
77 256
 
78
-    Retrieves the status of the Tiller server.
257
+        release | path | Yes | string |  |  | name of the release to test
79 258
 
259
+Request
260
++++++++
80 261
 
81
-::
82 262
 
83
-    Results:
263
+Responses
264
++++++++++
265
+
266
+**200**
267
+^^^^^^^
268
+
269
+Succesfully Test release response
270
+
271
+**Example:**
272
+
273
+.. code-block:: javascript
84 274
 
85 275
     {
86
-        "message": Tiller Server is Active
276
+        "message": {
277
+            "message": "armada-release",
278
+            "result": "No test found."
279
+        }
87 280
     }
281
+
282
+**403**
283
+^^^^^^^
284
+
285
+Unable to Authorize or Permission
286
+
287
+
288
+**405**
289
+^^^^^^^
290
+
291
+Failed to perform action
292
+
293
+POST ``/tests``
294
+---------------
295
+
296
+
297
+Summary
298
++++++++
299
+
300
+Test manifest releases
301
+
302
+Request
303
++++++++
304
+
305
+Body
306
+^^^^
307
+
308
+.. csv-table::
309
+    :delim: |
310
+    :header: "Name", "Required", "Type", "Format", "Properties", "Description"
311
+    :widths: 20, 10, 15, 15, 30, 25
312
+
313
+        armada | Yes |  |  |  |
314
+