Browse Source

Allow adding new definitions to PKICatalog

* Detect and re-use existing Certs/Keys
* Negative functional test for join with missing cert
* Positive functional test to generate cert after initial construction
* Extract some promenade test code into tools/g2/lib/promenade.sh
* Add timestamps to tar'd up files

Change-Id: Ib717785fc2c8f6cd1db1970ecdf1f5184ed40e92
Mark Burnett 1 year ago
parent
commit
26e6792690

+ 4
- 1
docs/source/configuration/pki-catalog.rst View File

@@ -1,7 +1,10 @@
1 1
 PKI Catalog
2 2
 ===========
3 3
 
4
-Configuration for certificate and keypair generation in the cluster.
4
+Configuration for certificate and keypair generation in the cluster.  The
5
+``promenade generate-certs`` command will read all ``PKICatalog`` documents and
6
+either find pre-existing certificates/keys, or generate new ones based on the
7
+given definition.
5 8
 
6 9
 
7 10
 Sample Document

+ 21
- 0
examples/basic/PKICatalog-addition.yaml View File

@@ -0,0 +1,21 @@
1
+---
2
+schema: promenade/PKICatalog/v1
3
+metadata:
4
+  schema: metadata/Document/v1
5
+  name: cluster-certificates-addition
6
+  layeringDefinition:
7
+    abstract: false
8
+    layer: site
9
+data:
10
+  certificate_authorities:
11
+    kubernetes:
12
+      description: CA for Kubernetes components
13
+      certificates:
14
+        - document_name: kubelet-n3
15
+          common_name: system:node:n3
16
+          hosts:
17
+            - n3
18
+            - 192.168.77.13
19
+          groups:
20
+            - system:nodes
21
+...

+ 0
- 7
examples/basic/PKICatalog.yaml View File

@@ -48,13 +48,6 @@ data:
48 48
             - 192.168.77.12
49 49
           groups:
50 50
             - system:nodes
51
-        - document_name: kubelet-n3
52
-          common_name: system:node:n3
53
-          hosts:
54
-            - n3
55
-            - 192.168.77.13
56
-          groups:
57
-            - system:nodes
58 51
         - document_name: scheduler
59 52
           description: Service certificate for Kubernetes scheduler
60 53
           common_name: system:kube-scheduler

+ 12
- 3
promenade/config.py View File

@@ -94,7 +94,7 @@ class Configuration:
94 94
                 'No document found matching kind=%s schema=%s name=%s' %
95 95
                 (kind, schema, name))
96 96
 
97
-    def iterate(self, *, kind=None, schema=None, labels=None):
97
+    def iterate(self, *, kind=None, schema=None, labels=None, name=None):
98 98
         if kind is not None:
99 99
             if schema is not None:
100 100
                 raise AssertionError(
@@ -102,9 +102,14 @@ class Configuration:
102 102
             schema = 'promenade/%s/v1' % kind
103 103
 
104 104
         for document in self.documents:
105
-            if _matches_filter(document, schema=schema, labels=labels):
105
+            if _matches_filter(
106
+                    document, schema=schema, labels=labels, name=name):
106 107
                 yield document
107 108
 
109
+    def find(self, *args, **kwargs):
110
+        for doc in self.iterate(*args, **kwargs):
111
+            return doc
112
+
108 113
     def extract_genesis_config(self):
109 114
         LOG.debug('Extracting genesis config.')
110 115
         documents = []
@@ -179,7 +184,7 @@ class Configuration:
179 184
                              ['/apiserver', '--apiserver-count=2', '--v=5'])
180 185
 
181 186
 
182
-def _matches_filter(document, *, schema, labels):
187
+def _matches_filter(document, *, schema, labels, name):
183 188
     matches = True
184 189
     if schema is not None and not document.get('schema',
185 190
                                                '').startswith(schema):
@@ -194,6 +199,10 @@ def _matches_filter(document, *, schema, labels):
194 199
                 if document_labels[key] != value:
195 200
                     matches = False
196 201
 
202
+    if name is not None:
203
+        if _mg(document, 'name') != name:
204
+            matches = False
205
+
197 206
     return matches
198 207
 
199 208
 

+ 3
- 2
promenade/control/join_scripts.py View File

@@ -50,8 +50,9 @@ class JoinScriptsResource(BaseResource):
50 50
                 design_ref,
51 51
                 allow_missing_substitutions=False,
52 52
                 leave_kubectl=leave_kubectl)
53
-        except exceptions.DeckhandException as e:
54
-            raise falcon.HTTPInternalServerError(description=str(e))
53
+        except exceptions.DeckhandException:
54
+            LOG.exception('Caught Deckhand render error for configuration')
55
+            raise
55 56
 
56 57
         if config.get_path('KubernetesNode:.', SENTINEL) != SENTINEL:
57 58
             raise exceptions.ExistingKubernetesNodeDocumentError(

+ 23
- 0
promenade/exceptions.py View File

@@ -222,6 +222,24 @@ class PromenadeException(Exception):
222 222
             LOG.error(self.title + (self.description or ''))
223 223
 
224 224
 
225
+class PKIError(PromenadeException):
226
+    """
227
+    A parent error for PKI-related issues.
228
+    """
229
+    title = 'PKI Error'
230
+    # NOTE(mark-burnett): The API should never see these errors.
231
+    status = falcon.HTTP_500
232
+
233
+
234
+class IncompletePKIPairError(PKIError):
235
+    """
236
+    An incomplete pair (Certificate + Key or Pub + Priv) was found in cache.
237
+    """
238
+    title = 'Incomplete Pair Error'
239
+    # NOTE(mark-burnett): The API should never see these errors.
240
+    status = falcon.HTTP_500
241
+
242
+
225 243
 class ApiError(PromenadeException):
226 244
     """
227 245
     An error to handle general api errors.
@@ -294,6 +312,11 @@ class ValidationException(PromenadeException):
294 312
 
295 313
 class DeckhandException(PromenadeException):
296 314
     title = 'Deckhand Engine Error'
315
+    status = falcon.HTTP_400
316
+
317
+
318
+class TemplateRenderException(PromenadeException):
319
+    title = 'Template Rendering Error Error'
297 320
     status = falcon.HTTP_500
298 321
 
299 322
 

+ 124
- 33
promenade/generator.py View File

@@ -1,4 +1,6 @@
1
-from . import logging, pki
1
+from . import exceptions, logging, pki
2
+import collections
3
+import itertools
2 4
 import os
3 5
 import yaml
4 6
 
@@ -12,35 +14,129 @@ class Generator:
12 14
         self.config = config
13 15
         self.keys = pki.PKI()
14 16
         self.documents = []
17
+        self.outputs = collections.defaultdict(dict)
15 18
 
16 19
     @property
17 20
     def cluster_domain(self):
18 21
         return self.config['KubernetesNetwork:dns.cluster_domain']
19 22
 
20 23
     def generate(self, output_dir):
21
-        for ca_name, ca_def in self.config[
22
-                'PKICatalog:certificate_authorities'].items():
23
-            self.gen('ca', ca_name)
24
-            for cert_def in ca_def.get('certificates', []):
25
-                hosts = cert_def.get('hosts', [])
26
-                hosts.extend(
27
-                    get_host_list(
28
-                        cert_def.get('kubernetes_service_names', [])))
29
-                self.gen(
30
-                    'certificate',
31
-                    cert_def['document_name'],
32
-                    ca=ca_name,
33
-                    cn=cert_def['common_name'],
34
-                    hosts=hosts,
35
-                    groups=cert_def.get('groups', []))
36
-        for keypair_def in self.config['PKICatalog:keypairs']:
37
-            self.gen('keypair', keypair_def['name'])
38
-        _write(output_dir, self.documents)
39
-
40
-    def gen(self, kind, *args, **kwargs):
41
-        method = getattr(self.keys, 'generate_' + kind)
42
-
43
-        self.documents.extend(method(*args, **kwargs))
24
+        for catalog in self.config.iterate(kind='PKICatalog'):
25
+            for ca_name, ca_def in catalog['data'].get(
26
+                    'certificate_authorities', {}).items():
27
+                ca_cert, ca_key = self.get_or_gen_ca(ca_name)
28
+
29
+                for cert_def in ca_def.get('certificates', []):
30
+                    document_name = cert_def['document_name']
31
+                    cert, key = self.get_or_gen_cert(
32
+                        document_name,
33
+                        ca_cert=ca_cert,
34
+                        ca_key=ca_key,
35
+                        cn=cert_def['common_name'],
36
+                        hosts=_extract_hosts(cert_def),
37
+                        groups=cert_def.get('groups', []))
38
+
39
+            for keypair_def in catalog['data'].get('keypairs', []):
40
+                document_name = keypair_def['name']
41
+                self.get_or_gen_keypair(document_name)
42
+
43
+        self._write(output_dir)
44
+
45
+    def get_or_gen_ca(self, document_name):
46
+        kinds = [
47
+            'CertificateAuthority',
48
+            'CertificateAuthorityKey',
49
+        ]
50
+        return self._get_or_gen(self.gen_ca, kinds, document_name)
51
+
52
+    def get_or_gen_cert(self, document_name, **kwargs):
53
+        kinds = [
54
+            'Certificate',
55
+            'CertificateKey',
56
+        ]
57
+        return self._get_or_gen(self.gen_cert, kinds, document_name, **kwargs)
58
+
59
+    def get_or_gen_keypair(self, document_name):
60
+        kinds = [
61
+            'PublicKey',
62
+            'PrivateKey',
63
+        ]
64
+        return self._get_or_gen(self.gen_keypair, kinds, document_name)
65
+
66
+    def gen_ca(self, document_name, **kwargs):
67
+        return self.keys.generate_ca(document_name, **kwargs)
68
+
69
+    def gen_cert(self, document_name, *, ca_cert, ca_key, **kwargs):
70
+        ca_cert_data = ca_cert['data']
71
+        ca_key_data = ca_key['data']
72
+        return self.keys.generate_certificate(
73
+            document_name, ca_cert=ca_cert_data, ca_key=ca_key_data, **kwargs)
74
+
75
+    def gen_keypair(self, document_name):
76
+        return self.keys.generate_keypair(document_name)
77
+
78
+    def _get_or_gen(self, generator, kinds, document_name, *args, **kwargs):
79
+        docs = self._find_docs(kinds, document_name)
80
+        if not docs:
81
+            docs = generator(document_name, *args, **kwargs)
82
+
83
+        # Adding these to output should be idempotent, so we use a dict.
84
+        for doc in docs:
85
+            self.outputs[doc['schema']][doc['metadata']['name']] = doc
86
+
87
+        return docs
88
+
89
+    def _find_docs(self, kinds, document_name):
90
+        schemas = ['deckhand/%s/v1' % k for k in kinds]
91
+        docs = self._find_in_config(schemas, document_name)
92
+        if docs:
93
+            if len(docs) == len(kinds):
94
+                LOG.debug('Found docs in input config named %s, kinds: %s',
95
+                          document_name, kinds)
96
+                return docs
97
+            else:
98
+                raise exceptions.IncompletePKIPairError(
99
+                    'Incomplete set %s '
100
+                    'for name: %s' % (kinds, document_name))
101
+
102
+        else:
103
+            docs = self._find_in_outputs(schemas, document_name)
104
+            if docs:
105
+                LOG.debug('Found docs in current outputs named %s, kinds: %s',
106
+                          document_name, kinds)
107
+                return docs
108
+            else:
109
+                LOG.debug('No docs existing docs named %s, kinds: %s',
110
+                          document_name, kinds)
111
+                return []
112
+
113
+    def _find_in_config(self, schemas, document_name):
114
+        result = []
115
+        for schema in schemas:
116
+            doc = self.config.find(schema=schema, name=document_name)
117
+            if doc:
118
+                result.append(doc)
119
+        return result
120
+
121
+    def _find_in_outputs(self, schemas, document_name):
122
+        result = []
123
+        for schema in schemas:
124
+            if document_name in self.outputs.get(schema, {}):
125
+                result.append(self.outputs[schema][document_name])
126
+        return result
127
+
128
+    def _write(self, output_dir):
129
+        docs = list(
130
+            itertools.chain.from_iterable(
131
+                v.values() for v in self.outputs.values()))
132
+        with open(os.path.join(output_dir, 'certificates.yaml'), 'w') as f:
133
+            # Don't use safe_dump_all so we can block format certificate data.
134
+            yaml.dump_all(
135
+                docs,
136
+                stream=f,
137
+                default_flow_style=False,
138
+                explicit_start=True,
139
+                indent=2)
44 140
 
45 141
 
46 142
 def get_host_list(service_names):
@@ -52,12 +148,7 @@ def get_host_list(service_names):
52 148
     return service_list
53 149
 
54 150
 
55
-def _write(output_dir, docs):
56
-    with open(os.path.join(output_dir, 'certificates.yaml'), 'w') as f:
57
-        # Don't use safe_dump_all so we can block format certificate data.
58
-        yaml.dump_all(
59
-            docs,
60
-            stream=f,
61
-            default_flow_style=False,
62
-            explicit_start=True,
63
-            indent=2)
151
+def _extract_hosts(cert_def):
152
+    hosts = cert_def.get('hosts', [])
153
+    hosts.extend(get_host_list(cert_def.get('kubernetes_service_names', [])))
154
+    return hosts

+ 22
- 7
promenade/pki.py View File

@@ -14,7 +14,6 @@ LOG = logging.getLogger(__name__)
14 14
 
15 15
 class PKI:
16 16
     def __init__(self):
17
-        self.certificate_authorities = {}
18 17
         self._ca_config_string = None
19 18
 
20 19
     @property
@@ -40,7 +39,6 @@ class PKI:
40 39
             files={
41 40
                 'csr.json': self.csr(name=ca_name, groups=['Kubernetes']),
42 41
             })
43
-        self.certificate_authorities[ca_name] = result
44 42
 
45 43
         return (self._wrap_ca(ca_name, result['cert']),
46 44
                 self._wrap_ca_key(ca_name, result['key']))
@@ -56,7 +54,19 @@ class PKI:
56 54
         return (self._wrap_pub_key(name, pub_result['pub.pem']),
57 55
                 self._wrap_priv_key(name, priv_result['priv.pem']))
58 56
 
59
-    def generate_certificate(self, name, *, ca, cn, groups=[], hosts=[]):
57
+    def generate_certificate(self,
58
+                             name,
59
+                             *,
60
+                             ca_cert,
61
+                             ca_key,
62
+                             cn,
63
+                             groups=None,
64
+                             hosts=None):
65
+        if groups is None:
66
+            groups = []
67
+        if hosts is None:
68
+            hosts = []
69
+
60 70
         result = self._cfssl(
61 71
             [
62 72
                 'gencert', '-ca', 'ca.pem', '-ca-key', 'ca-key.pem', '-config',
@@ -64,8 +74,8 @@ class PKI:
64 74
             ],
65 75
             files={
66 76
                 'ca-config.json': self.ca_config,
67
-                'ca.pem': self.certificate_authorities[ca]['cert'],
68
-                'ca-key.pem': self.certificate_authorities[ca]['key'],
77
+                'ca.pem': ca_cert,
78
+                'ca-key.pem': ca_key,
69 79
                 'csr.json': self.csr(name=cn, groups=groups, hosts=hosts),
70 80
             })
71 81
 
@@ -75,12 +85,17 @@ class PKI:
75 85
     def csr(self,
76 86
             *,
77 87
             name,
78
-            groups=[],
79
-            hosts=[],
88
+            groups=None,
89
+            hosts=None,
80 90
             key={
81 91
                 'algo': 'rsa',
82 92
                 'size': 2048
83 93
             }):
94
+        if groups is None:
95
+            groups = []
96
+        if hosts is None:
97
+            hosts = []
98
+
84 99
         return json.dumps({
85 100
             'CN': name,
86 101
             'key': key,

+ 14
- 3
promenade/renderer.py View File

@@ -1,4 +1,4 @@
1
-from . import logging, tar_bundler
1
+from . import exceptions, logging, tar_bundler
2 2
 import base64
3 3
 import datetime
4 4
 import io
@@ -75,7 +75,13 @@ def render_template_into_bundler(*, bundler, config, destination_path,
75 75
     with open(source_path) as f:
76 76
         template = env.from_string(f.read())
77 77
     now = int(datetime.datetime.utcnow().timestamp())
78
-    data = template.render(config=config, now=now)
78
+    try:
79
+        data = template.render(config=config, now=now)
80
+    except jinja2.exceptions.TemplateRuntimeError as e:
81
+        LOG.exception('Error rendering template (%s)' % source_path)
82
+        raise exceptions.TemplateRenderException(
83
+            'Error rendering template (%s): %s' % (source_path, e))
84
+
79 85
     bundler.add(path=destination_path, data=data, mode=mode)
80 86
 
81 87
 
@@ -91,7 +97,12 @@ def render_template(config, *, template, context=None):
91 97
     env = _build_env()
92 98
 
93 99
     template_obj = env.from_string(template_contents.decode('utf-8'))
94
-    return template_obj.render(config=config, **context)
100
+    try:
101
+        return template_obj.render(config=config, **context)
102
+    except jinja2.exceptions.TemplateRuntimeError as e:
103
+        LOG.exception('Error rendering template (%s)' % template)
104
+        raise exceptions.TemplateRenderException(
105
+            'Error rendering template (%s): %s' % (template, e))
95 106
 
96 107
 
97 108
 def _build_env():

+ 2
- 0
promenade/tar_bundler.py View File

@@ -1,6 +1,7 @@
1 1
 import hashlib
2 2
 import io
3 3
 import tarfile
4
+import time
4 5
 
5 6
 from promenade import logging
6 7
 
@@ -25,6 +26,7 @@ class TarBundler:
25 26
             data_bytes = data
26 27
         tar_info.size = len(data_bytes)
27 28
         tar_info.mode = mode
29
+        tar_info.mtime = int(time.time())
28 30
 
29 31
         if tar_info.size > 0:
30 32
             # Ignore bandit false positive: B303:blacklist

+ 1
- 0
tools/g2/lib/config.sh View File

@@ -4,6 +4,7 @@ export BASE_IMAGE_URL=${BASE_IMAGE_URL:-https://cloud-images.ubuntu.com/releases
4 4
 export IMAGE_PROMENADE=${IMAGE_PROMENADE:-quay.io/attcomdev/promenade:latest}
5 5
 export NGINX_DIR="${TEMP_DIR}/nginx"
6 6
 export NGINX_URL="http://192.168.77.1:7777"
7
+export PROMENADE_BASE_URL="http://promenade-api.ucp.svc.cluster.local"
7 8
 export PROMENADE_DEBUG=${PROMENADE_DEBUG:-0}
8 9
 export REGISTRY_DATA_DIR=${REGISTRY_DATA_DIR:-/mnt/registry}
9 10
 export VIRSH_POOL=${VIRSH_POOL:-promenade}

+ 56
- 0
tools/g2/lib/promenade.sh View File

@@ -5,3 +5,59 @@ promenade_teardown_node() {
5 5
     ssh_cmd "${TARGET}" /usr/local/bin/promenade-teardown
6 6
     kubectl_cmd "${VIA}" delete node "${TARGET}"
7 7
 }
8
+
9
+promenade_render_curl_url() {
10
+    NAME=${1}
11
+    USE_DECKHAND=${2}
12
+    DECKHAND_REVISION=${3}
13
+    shift 3
14
+    LABELS=(${@})
15
+
16
+    LABEL_PARAMS=
17
+    for label in "${LABELS[@]}"; do
18
+        LABEL_PARAMS+="&labels.dynamic=${label}"
19
+    done
20
+
21
+    BASE_URL="${PROMENADE_BASE_URL}/api/v1.0/join-scripts"
22
+    if [[ ${USE_DECKHAND} == 1 ]]; then
23
+        DESIGN_REF="design_ref=deckhand%2Bhttp://deckhand-int.ucp.svc.cluster.local:9000/api/v1.0/revisions/${DECKHAND_REVISION}/rendered-documents"
24
+    else
25
+        DESIGN_REF="design_ref=${NGINX_URL}/promenade.yaml"
26
+    fi
27
+    HOST_PARAMS="hostname=${NAME}&ip=$(config_vm_ip "${NAME}")"
28
+
29
+    echo "${BASE_URL}?${DESIGN_REF}&${HOST_PARAMS}&leave_kubectl=true${LABEL_PARAMS}"
30
+}
31
+
32
+promenade_render_validate_url() {
33
+    echo "${PROMENADE_BASE_URL}/api/v1.0/validatedesign"
34
+}
35
+
36
+promenade_render_validate_body() {
37
+    USE_DECKHAND=${1}
38
+    DECKHAND_REVISION=${2}
39
+
40
+    if [[ ${USE_DECKHAND} == 1 ]]; then
41
+        JSON="{\"rel\":\"design\",\"href\":\"deckhand+http://deckhand-int.ucp.svc.cluster.local:9000/api/v1.0/revisions/${DECKHAND_REVISION}/rendered-documents\",\"type\":\"application/x-yaml\"}"
42
+    else
43
+        JSON="{\"rel\":\"design\",\"href\":\"${NGINX_URL}/promenade.yaml\",\"type\":\"application/x-yaml\"}"
44
+    fi
45
+
46
+    echo ${JSON}
47
+}
48
+
49
+promenade_health_check() {
50
+    VIA=${1}
51
+    log "Checking Promenade API health"
52
+    MAX_HEALTH_ATTEMPTS=6
53
+    for attempt in $(seq ${MAX_HEALTH_ATTEMPTS}); do
54
+        if ssh_cmd "${VIA}" curl -v --fail "${PROMENADE_BASE_URL}/api/v1.0/health"; then
55
+            log "Promenade API healthy"
56
+            break
57
+        elif [[ $attempt == "${MAX_HEALTH_ATTEMPTS}" ]]; then
58
+            log "Promenade health check failed, max retries (${MAX_HEALTH_ATTEMPTS}) exceeded."
59
+            exit 1
60
+        fi
61
+        sleep 10
62
+    done
63
+}

+ 34
- 1
tools/g2/manifests/resiliency.json View File

@@ -18,7 +18,10 @@
18 18
     },
19 19
     {
20 20
       "name": "Generate Certificates",
21
-      "script": "generate-certificates.sh"
21
+      "script": "generate-certificates.sh",
22
+      "arguments": [
23
+        "-x", "PKICatalog-addition.yaml"
24
+      ]
22 25
     },
23 26
     {
24 27
       "name": "Build Scripts",
@@ -40,6 +43,36 @@
40 43
         "-v", "n0",
41 44
         "-n", "n1",
42 45
         "-n", "n2",
46
+        "-l", "calico-etcd=enabled",
47
+        "-l", "kubernetes-apiserver=enabled",
48
+        "-l", "kubernetes-controller-manager=enabled",
49
+        "-l", "kubernetes-etcd=enabled",
50
+        "-l", "kubernetes-scheduler=enabled",
51
+        "-l", "ucp-control-plane=enabled",
52
+        "-e", "kubernetes n0 n0 n1 n2",
53
+        "-e", "calico n0 n0 n1 n2"
54
+      ]
55
+    },
56
+    {
57
+      "name": "Verify Join Failure",
58
+      "script": "fail-join-node.sh",
59
+      "arguments": [
60
+        "-v", "n0",
61
+        "-n", "n3"
62
+      ]
63
+    },
64
+    {
65
+      "name": "Update Generated Certs",
66
+      "script": "generate-certificates.sh",
67
+      "arguments": [
68
+        "-u"
69
+      ]
70
+    },
71
+    {
72
+      "name": "Join Final Master",
73
+      "script": "join-nodes.sh",
74
+      "arguments": [
75
+        "-v", "n0",
43 76
         "-n", "n3",
44 77
         "-l", "calico-etcd=enabled",
45 78
         "-l", "coredns=enabled",

+ 0
- 3
tools/g2/stages/build-scripts.sh View File

@@ -19,6 +19,3 @@ docker run --rm -t \
19 19
                 --validators \
20 20
                 -o scripts \
21 21
                 config/*.yaml
22
-
23
-mkdir -p "${NGINX_DIR}"
24
-cat "${TEMP_DIR}"/config/*.yaml > "${TEMP_DIR}/nginx/promenade.yaml"

+ 49
- 0
tools/g2/stages/fail-join-node.sh View File

@@ -0,0 +1,49 @@
1
+#!/usr/bin/env bash
2
+
3
+set -e
4
+
5
+source "${GATE_UTILS}"
6
+
7
+while getopts "n:v:" opt; do
8
+    case "${opt}" in
9
+        n)
10
+            NODE="${OPTARG}"
11
+            ;;
12
+        v)
13
+            VIA=${OPTARG}
14
+            ;;
15
+        *)
16
+            echo "Unknown option"
17
+            exit 1
18
+            ;;
19
+    esac
20
+done
21
+shift $((OPTIND-1))
22
+
23
+if [ $# -gt 0 ]; then
24
+    echo "Unknown arguments specified: ${*}"
25
+    exit 1
26
+fi
27
+
28
+SCRIPT_DIR="${TEMP_DIR}/join-fail-curled-scripts"
29
+
30
+mkdir -p "${SCRIPT_DIR}"
31
+
32
+CURL_ARGS=("-v" "--fail" "--max-time" "300")
33
+
34
+promenade_health_check "${VIA}"
35
+
36
+LABELS=(
37
+    "foo=bar"
38
+)
39
+
40
+USE_DECKHAND=0
41
+JOIN_CURL_URL="$(promenade_render_curl_url "${NODE}" "${USE_DECKHAND}" "" "${LABELS[@]}")"
42
+log "Attempting to get join script (should fail) via: ${JOIN_CURL_URL}"
43
+if ! ssh_cmd "${VIA}" curl "${CURL_ARGS[@]}" \
44
+    "${JOIN_CURL_URL}" > "${SCRIPT_DIR}/join-${NODE}.sh"; then
45
+    log "Failed to get join script"
46
+else
47
+    log "No failure when fetching join script"
48
+    exit 1
49
+fi

+ 58
- 1
tools/g2/stages/generate-certificates.sh View File

@@ -9,11 +9,61 @@ mkdir -p "${OUTPUT_DIR}"
9 9
 chmod 777 "${OUTPUT_DIR}"
10 10
 OUTPUT_FILE="${OUTPUT_DIR}/combined.yaml"
11 11
 
12
+CERTIFICATES_FILE="${OUTPUT_DIR}/certificates.yaml"
13
+OLD_CERTIFICATES_FILE="${OUTPUT_DIR}/certificates-old.yaml"
14
+
15
+IS_UPDATE=0
16
+DO_EXCLUDE=0
17
+EXCLUDE_PATTERNS=()
18
+
19
+while getopts "ux:" opt; do
20
+    case "${opt}" in
21
+        u)
22
+            IS_UPDATE=1
23
+            ;;
24
+        x)
25
+            DO_EXCLUDE=1
26
+            EXCLUDE_PATTERNS+=("${OPTARG}")
27
+            ;;
28
+        *)
29
+            echo "Unknown option"
30
+            exit 1
31
+            ;;
32
+    esac
33
+done
34
+shift $((OPTIND-1))
35
+
36
+function should_include_filename() {
37
+    FILENAME="${1}"
38
+    if [[ ${DO_EXCLUDE} == 1 ]]; then
39
+        for pattern in "${EXCLUDE_PATTERNS[@]}"; do
40
+            if echo "${FILENAME}" | grep "${pattern}" > /dev/null; then
41
+                return 1
42
+            fi
43
+        done
44
+    fi
45
+    return 0
46
+}
47
+
48
+# Ensure we do not duplicate configuration on update.
49
+rm -f "${OUTPUT_FILE}"
50
+
12 51
 for source_dir in $(config_configuration); do
13 52
     log Copying configuration from "${source_dir}"
14
-    cat "${WORKSPACE}/${source_dir}"/*.yaml >> "${OUTPUT_FILE}"
53
+    for filename in ${WORKSPACE}/${source_dir}/*.yaml; do
54
+        if should_include_filename "${filename}"; then
55
+            log Including config from "$filename"
56
+            cat "${filename}" >> "${OUTPUT_FILE}"
57
+        else
58
+            log Excluding config from "$filename"
59
+        fi
60
+    done
15 61
 done
16 62
 
63
+if [[ ${IS_UPDATE} == "1" && -e ${CERTIFICATES_FILE} ]]; then
64
+    mv "${CERTIFICATES_FILE}" "${OLD_CERTIFICATES_FILE}"
65
+fi
66
+
17 67
 log "Setting up local caches.."
18 68
 nginx_cache_and_replace_tar_urls "${OUTPUT_DIR}"/*.yaml
19 69
 registry_replace_references "${OUTPUT_DIR}"/*.yaml
@@ -30,3 +80,10 @@ docker run --rm -t \
30 80
             generate-certs \
31 81
                 -o /target \
32 82
                 "${FILES[@]}"
83
+
84
+if [[ -e "${OLD_CERTIFICATES_FILE}" ]]; then
85
+    rm -f "${OLD_CERTIFICATES_FILE}"
86
+fi
87
+
88
+mkdir -p "${NGINX_DIR}"
89
+cat "${TEMP_DIR}"/config/*.yaml > "${TEMP_DIR}/nginx/promenade.yaml"

+ 4
- 46
tools/g2/stages/join-nodes.sh View File

@@ -10,6 +10,7 @@ declare -a NODES
10 10
 
11 11
 GET_KEYSTONE_TOKEN=0
12 12
 USE_DECKHAND=0
13
+DECKHAND_REVISION=''
13 14
 
14 15
 while getopts "d:e:l:n:tv:" opt; do
15 16
     case "${opt}" in
@@ -46,43 +47,11 @@ if [ $# -gt 0 ]; then
46 47
 fi
47 48
 
48 49
 SCRIPT_DIR="${TEMP_DIR}/curled-scripts"
49
-BASE_PROM_URL="http://promenade-api.ucp.svc.cluster.local"
50 50
 
51 51
 echo Etcd Clusters: "${ETCD_CLUSTERS[@]}"
52 52
 echo Labels: "${LABELS[@]}"
53 53
 echo Nodes: "${NODES[@]}"
54 54
 
55
-render_curl_url() {
56
-    NAME=${1}
57
-    shift
58
-    LABELS=(${@})
59
-
60
-    LABEL_PARAMS=
61
-    for label in "${LABELS[@]}"; do
62
-        LABEL_PARAMS+="&labels.dynamic=${label}"
63
-    done
64
-
65
-    BASE_URL="${BASE_PROM_URL}/api/v1.0/join-scripts"
66
-    if [[ ${USE_DECKHAND} == 1 ]]; then
67
-        DESIGN_REF="design_ref=deckhand%2Bhttp://deckhand-int.ucp.svc.cluster.local:9000/api/v1.0/revisions/${DECKHAND_REVISION}/rendered-documents"
68
-    else
69
-        DESIGN_REF="design_ref=${NGINX_URL}/promenade.yaml"
70
-    fi
71
-    HOST_PARAMS="hostname=${NAME}&ip=$(config_vm_ip "${NAME}")"
72
-
73
-    echo "${BASE_URL}?${DESIGN_REF}&${HOST_PARAMS}&leave_kubectl=true${LABEL_PARAMS}"
74
-}
75
-
76
-render_validate_body() {
77
-    if [[ ${USE_DECKHAND} == 1 ]]; then
78
-        JSON="{\"rel\":\"design\",\"href\":\"deckhand+http://deckhand-int.ucp.svc.cluster.local:9000/api/v1.0/revisions/${DECKHAND_REVISION}/rendered-documents\",\"type\":\"application/x-yaml\"}"
79
-    else
80
-        JSON="{\"rel\":\"design\",\"href\":\"${NGINX_URL}/promenade.yaml\",\"type\":\"application/x-yaml\"}"
81
-    fi
82
-
83
-    echo ${JSON}
84
-}
85
-
86 55
 mkdir -p "${SCRIPT_DIR}"
87 56
 
88 57
 for NAME in "${NODES[@]}"; do
@@ -100,23 +69,12 @@ for NAME in "${NODES[@]}"; do
100 69
         CURL_ARGS+=("-H" "X-Auth-Token: ${TOKEN}")
101 70
     fi
102 71
 
103
-    log "Checking Promenade API health"
104
-    MAX_HEALTH_ATTEMPTS=6
105
-    for attempt in $(seq ${MAX_HEALTH_ATTEMPTS}); do
106
-        if ssh_cmd "${VIA}" curl -v "${CURL_ARGS[@]}" "${BASE_PROM_URL}/api/v1.0/health"; then
107
-            log "Promenade API healthy"
108
-            break
109
-        elif [[ $attempt == "${MAX_HEALTH_ATTEMPTS}" ]]; then
110
-            log "Promenade health check failed, max retries (${MAX_HEALTH_ATTEMPTS}) exceeded."
111
-            exit 1
112
-        fi
113
-        sleep 10
114
-    done
72
+    promenade_health_check "${VIA}"
115 73
 
116 74
     log "Validating documents"
117
-    ssh_cmd "${VIA}" curl -v "${CURL_ARGS[@]}" -X POST -H "Content-Type: application/json" -d $(render_validate_body) "${BASE_PROM_URL}/api/v1.0/validatedesign"
75
+    ssh_cmd "${VIA}" curl -v "${CURL_ARGS[@]}" -X POST -H "Content-Type: application/json" -d "$(promenade_render_validate_body "${USE_DECKHAND}" "${DECKHAND_REVISION}")" "$(promenade_render_validate_url)"
118 76
 
119
-    JOIN_CURL_URL="$(render_curl_url "${NAME}" "${LABELS[@]}")"
77
+    JOIN_CURL_URL="$(promenade_render_curl_url "${NAME}" "${USE_DECKHAND}" "${DECKHAND_REVISION}" "${LABELS[@]}")"
120 78
     log "Fetching join script via: ${JOIN_CURL_URL}"
121 79
     ssh_cmd "${VIA}" curl "${CURL_ARGS[@]}" \
122 80
         "${JOIN_CURL_URL}" > "${SCRIPT_DIR}/join-${NAME}.sh"

Loading…
Cancel
Save