├── LICENSE ├── README.md ├── _modules └── consul_mod.py ├── _states ├── consul_check.py ├── consul_key.py └── consul_service.py └── requirements.txt /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # salt-consul 2 | Consul modules for SaltStack 3 | 4 | ## Background 5 | I'd been meaning to write this for awhile, then one afternoon just decided I didn't want to lay down json for every service & check, not to mention reload the service. Still a work in progress, but most of the functions I use are represented here. 6 | 7 | ## Quickstart 8 | 9 | - drop the modules into `{_modules,_states}` into `file_roots` on your `salt-master` 10 | - ensure the pypi `python-consul` package is installed 11 | 12 | 13 | ### Execution module examples: 14 | 15 | #### Key/Value 16 | 17 | `salt-call consul.key_put foo bar` 18 | 19 | `salt-call consul.key_get foo` 20 | 21 | `salt-call consul.key_delete foo` 22 | 23 | #### Services 24 | 25 | `salt-call consul.service_list` 26 | 27 | `salt-call consul.service_get foo` 28 | 29 | `salt-call consul.service_register name=foo port=6969 script=/path/to/script interval=10s` 30 | 31 | `salt-call consul.service_deregister name=foo` 32 | 33 | `salt-call consul.get_service_status name=foo` 34 | 35 | #### Checks 36 | 37 | `salt-call consul.check_list` 38 | 39 | `salt-call consul.check_get foo` 40 | 41 | `salt-call consul.check_register name=foo script=/path/to/script interval=10s` 42 | 43 | `salt-call consul.check_deregister name=foo` 44 | 45 | #### Nodes 46 | 47 | `salt-call consul.node_list` 48 | 49 | `salt-call consul.node_get foo` 50 | 51 | #### ttls 52 | 53 | `salt-call consul.ttl_pass foo type=service notes=bar` 54 | 55 | `salt-call consul.ttl_fail foo type=check notes=bar` 56 | 57 | `salt-call consul.ttl_warn foo notes=bar` 58 | 59 | #### ACLs 60 | 61 | `salt-call consul.acl_create master_token=master_token rules='key "" { policy = "read" }'` 62 | 63 | `salt-call consul.acl_list master_token=master_token` 64 | 65 | `salt-call consul.acl_get acl_id=e02be371-60c3-ed74-56a3-c325a86f36e3 master_token=master_token` 66 | 67 | `salt-call consul.acl_update acl_id=e02be371-60c3-ed74-56a3-c325a86f36e3 name=foo type=management master_token=master_token` 68 | 69 | `salt-call consul.acl_destroy acl_id=e02be371-60c3-ed74-56a3-c325a86f36e3` 70 | 71 | 72 | ### State module examples: 73 | 74 | #### Key/Value 75 | 76 | ```yaml 77 | consul-key-present: 78 | consul_key.present: 79 | - name: foo 80 | - value: bar 81 | 82 | consul-key-absent: 83 | consul_key.absent: 84 | - name: foo 85 | ``` 86 | 87 | #### Services 88 | 89 | ```yaml 90 | consul-service-present: 91 | consul_service.present: 92 | - name: foo 93 | - port: 6969 94 | - script: nc -z localhost 6969 95 | - interval: 10s 96 | 97 | consul-service-absent: 98 | consul_service.absent: 99 | - name: foo 100 | ``` 101 | 102 | #### Checks 103 | 104 | ```yaml 105 | consul-check-present: 106 | consul_check.present: 107 | - name: foo 108 | - script: nz -z localhost 6969 109 | - interval: 10s 110 | 111 | consul-check-absent: 112 | consul_check.absent: 113 | - name: foo 114 | ``` 115 | 116 | #### ttls 117 | 118 | ```yaml 119 | consul-set-service-ttl: 120 | consul_service.ttl_set: 121 | - name: foo 122 | - status: failing 123 | - notes: bar 124 | 125 | consul-set-check-ttl: 126 | consul_check.ttl_set: 127 | - name: foo 128 | - status: failing 129 | - notes: bar 130 | ``` 131 | 132 | 133 | ## TODO 134 | 135 | - acls 136 | 137 | ## Contributing 138 | - fork 139 | - code 140 | - submit pr 141 | 142 | 143 | -------------------------------------------------------------------------------- /_modules/consul_mod.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | Execution module to provide consul functionality to Salt 4 | 5 | .. versionadded:: 2014.7.0 6 | 7 | :configuration: This module requires the python-consul python module and uses the 8 | following defaults which may be overridden in the minion configuration: 9 | 10 | .. code-block:: yaml 11 | 12 | consul.host: 'localhost' 13 | consul.port: 8500 14 | consul.consistency: 'default' 15 | consul.token: 'ySsVJuvjBOZzqnP5zVPs3A==' 16 | ''' 17 | 18 | import os 19 | import salt.utils 20 | import codecs 21 | 22 | # Import third party libs 23 | HAS_CONSUL = False 24 | try: 25 | import consul as consul 26 | HAS_CONSUL = True 27 | except ImportError: 28 | pass 29 | 30 | __virtualname__ = 'consul' 31 | 32 | 33 | def __virtual__(): 34 | ''' 35 | Only load this module if python-consul 36 | is installed on this minion. 37 | ''' 38 | if HAS_CONSUL: 39 | return __virtualname__ 40 | else: 41 | return False 42 | 43 | 44 | def _connect(host='localhost', port=8500, consistency='default', token=None, **kwargs): 45 | ''' 46 | Returns an instance of the consul client 47 | ''' 48 | if not host: 49 | host = __salt__['config.option']('consul.host') 50 | if not port: 51 | port = __salt__['config.option']('consul.port') 52 | if not consistency: 53 | consistency = __salt__['config.option']('consul.consistency') 54 | if not token: 55 | token = __salt__['config.option']('consul.token') 56 | return consul.Consul(host, port, consistency) 57 | 58 | 59 | def key_delete(key, recurse=None, **kwargs): 60 | ''' 61 | Deletes the keys from consul, returns number of keys deleted 62 | 63 | CLI Example: 64 | 65 | .. code-block:: bash 66 | 67 | salt '*' consul.key_delete foo 68 | ''' 69 | c = _connect(**kwargs) 70 | index, data = c.kv.get(key) 71 | if not data: 72 | return False 73 | else: 74 | return c.kv.delete(key, recurse) 75 | 76 | 77 | def key_exists(key, **kwargs): 78 | ''' 79 | Return true if the key exists in consul 80 | 81 | CLI Example: 82 | 83 | .. code-block:: bash 84 | 85 | salt '*' consul.key_exists foo 86 | ''' 87 | c = _connect(**kwargs) 88 | index, data = c.kv.get(key) 89 | if not data: 90 | return False 91 | else: 92 | return True 93 | 94 | 95 | def key_get(key, **kwargs): 96 | ''' 97 | Gets the value of the key in consul 98 | 99 | CLI Example: 100 | 101 | .. code-block:: bash 102 | 103 | salt '*' consul.key_get foo 104 | ''' 105 | c = _connect(**kwargs) 106 | index, data = c.kv.get(key) 107 | if not data: 108 | return False 109 | else: 110 | return data['Value'] 111 | 112 | 113 | def key_put(key, value, value_from_file=False, encoding='utf8', **kwargs): 114 | ''' 115 | Sets the value of a key in consul 116 | 117 | CLI Example: 118 | 119 | .. code-block:: bash 120 | 121 | salt '*' consul.key_put foo bar 122 | ''' 123 | c = _connect(**kwargs) 124 | 125 | if value_from_file: 126 | if not os.path.isfile(value): 127 | ret = {} 128 | ret['result'] = False 129 | ret['comment'] = value + " does not exist" 130 | 131 | if not salt.utils.istextfile(value): 132 | ret = {} 133 | ret['result'] = False 134 | ret['comment'] = value + " is not a text file" 135 | 136 | else: 137 | file_contents = codecs.open(value, 'rb', encoding=encoding).read() 138 | c.kv.put(key, file_contents) 139 | 140 | else: 141 | c.kv.put(key, value) 142 | 143 | index, data = c.kv.get(key) 144 | return data['Value'] 145 | 146 | 147 | def service_list(catalog=False, dc=None, index=None, **kwargs): 148 | ''' 149 | List services known to Consul 150 | CLI Example: 151 | .. code-block:: bash 152 | salt '*' consul.service_list 153 | ''' 154 | c = _connect(**kwargs) 155 | services = [] 156 | if catalog: 157 | index, services = c.catalog.services(dc, index) 158 | else: 159 | for service, data in c.agent.services().items(): 160 | services.append(service) 161 | return services 162 | 163 | 164 | def service_get(name=None, service_id=None, dc=None, tag=None, index=None, **kwargs): 165 | ''' 166 | Get a Consul service's details 167 | 168 | CLI Example: 169 | 170 | .. code-block:: bash 171 | 172 | salt '*' consul.service_get 173 | ''' 174 | c = _connect(**kwargs) 175 | for service, data in c.agent.services().items(): 176 | if ('ID' in data and data['ID'] == service_id) or ('Name' in data and data['Name'] == name): 177 | return data 178 | return False 179 | 180 | 181 | def service_register(name, service_id=None, port=None, tags=None, script=None, interval=None, ttl=None, **kwargs): 182 | ''' 183 | Register a service with Consul 184 | 185 | CLI Example: 186 | 187 | .. code-block:: bash 188 | 189 | salt '*' consul.service_register foo 190 | ''' 191 | c = _connect(**kwargs) 192 | return c.agent.service.register(name, service_id, port, tags, script, interval, ttl) 193 | 194 | 195 | def service_deregister(name, **kwargs): 196 | ''' 197 | Deregister a service from Consul 198 | 199 | CLI Example: 200 | 201 | .. code-block:: bash 202 | 203 | salt '*' consul.service_deregister foo 204 | ''' 205 | c = _connect(**kwargs) 206 | if name not in service_list(): 207 | return False 208 | return c.agent.service.deregister(name) 209 | 210 | 211 | def check_list(**kwargs): 212 | ''' 213 | List checks known to Consul 214 | 215 | CLI Example: 216 | 217 | .. code-block:: bash 218 | 219 | salt '*' consul.check_list 220 | ''' 221 | c = _connect(**kwargs) 222 | checks = [] 223 | for check, data in c.agent.checks().items(): 224 | checks.append(check) 225 | return checks 226 | 227 | 228 | def check_get(name, **kwargs): 229 | ''' 230 | Get the details of a check in Consul 231 | 232 | CLI Example: 233 | 234 | .. code-block:: bash 235 | 236 | salt '*' consul.check_get 237 | ''' 238 | c = _connect(**kwargs) 239 | for check, data in c.agent.checks().items(): 240 | if name == check: 241 | return data 242 | return False 243 | 244 | 245 | def check_register(name, check_id=None, script=None, interval=None, ttl=None, notes=None, **kwargs): 246 | ''' 247 | Register a check with Consul 248 | 249 | CLI Example: 250 | 251 | .. code-block:: bash 252 | 253 | salt '*' consul.check_register foo 254 | ''' 255 | c = _connect(**kwargs) 256 | return c.agent.check.register(name, check_id, script, interval, ttl, notes) 257 | 258 | 259 | def check_deregister(name, **kwargs): 260 | ''' 261 | Deregister a check from Consul 262 | 263 | CLI Example: 264 | 265 | .. code-block:: bash 266 | 267 | salt '*' consul.check_deregister foo 268 | ''' 269 | c = _connect(**kwargs) 270 | if name not in check_list(): 271 | return False 272 | return c.agent.check.deregister(name) 273 | 274 | 275 | def get_service_status(name,index=None, passing=None, **kwargs): 276 | ''' 277 | Get the health status of a service in Consul 278 | 279 | CLI Example: 280 | 281 | .. code-block:: bash 282 | 283 | salt '*' consul.get_service_status 284 | ''' 285 | c = _connect(**kwargs) 286 | index, nodes = c.health.service(name, index, passing) 287 | node_list = [] 288 | for node in nodes: 289 | for check in node['Checks']: 290 | if name == check['ServiceName']: 291 | node_list.append({check['Node']: check['Status']}) 292 | return node_list 293 | 294 | 295 | def node_list(**kwargs): 296 | ''' 297 | List nodes in Consul 298 | 299 | CLI Example: 300 | 301 | .. code-block:: bash 302 | 303 | salt '*' consul.node_list 304 | ''' 305 | c = _connect(**kwargs) 306 | node_list = [] 307 | index, nodes = c.catalog.nodes() 308 | for node in nodes: 309 | node_list.append({node['Node']: node['Address']}) 310 | return node_list 311 | 312 | 313 | def node_get(name, dc=None, tag=None, index=None, **kwargs): 314 | ''' 315 | Get a Consul node's details 316 | 317 | CLI Example: 318 | 319 | .. code-block:: bash 320 | 321 | salt '*' consul.node_get 322 | ''' 323 | c = _connect(**kwargs) 324 | index, node = c.catalog.node(name, dc, tag, index) 325 | if not node: 326 | return False 327 | return node 328 | 329 | 330 | def dc_list(**kwargs): 331 | ''' 332 | List datacenters in Consul 333 | 334 | CLI Example: 335 | 336 | .. code-block:: bash 337 | 338 | salt '*' consul.dc_list 339 | ''' 340 | c = _connect(**kwargs) 341 | return c.catalog.datacenters() 342 | 343 | 344 | def ttl_pass(name, notes=None, type='check', **kwargs): 345 | ''' 346 | Mark a ttl-based service or check as passing 347 | 348 | CLI Example: 349 | 350 | .. code-block:: bash 351 | 352 | salt '*' consul.ttl_pass foo type=check 353 | 354 | salt '*' consul.ttl_pass foo type=service 355 | ''' 356 | c = _connect(**kwargs) 357 | if type == 'service': 358 | name = 'service:' + name 359 | return c.agent.check.ttl_pass(name, notes) 360 | 361 | 362 | def ttl_warn(name, notes=None, type='check', **kwargs): 363 | ''' 364 | Mark a ttl-based service or check as warning 365 | 366 | CLI Example: 367 | 368 | .. code-block:: bash 369 | 370 | salt '*' consul.ttl_warn foo type=check 371 | 372 | salt '*' consul.ttl_warn foo type=service 373 | ''' 374 | c = _connect(**kwargs) 375 | if type == 'service': 376 | name = 'service:' + name 377 | return c.agent.check.ttl_warn(name, notes) 378 | 379 | 380 | def ttl_fail(name, notes=None, type='check', **kwargs): 381 | ''' 382 | Mark a ttl-based service or check as failing 383 | 384 | CLI Example: 385 | 386 | .. code-block:: bash 387 | 388 | salt '*' consul.ttl_fail foo type=check 389 | 390 | salt '*' consul.ttl_fail foo type=service 391 | ''' 392 | c = _connect(**kwargs) 393 | if type == 'service': 394 | name = 'service:' + name 395 | return c.agent.check.ttl_fail(name, notes) 396 | 397 | 398 | def acl_create(master_token, rules, name=None, type='client', **kwargs): 399 | ''' 400 | Create an ACL token with supplied rules 401 | 402 | CLI Example: 403 | 404 | .. code-block:: bash 405 | 406 | salt '*' consul.create master_token=master_token rules='key "" { policy = "read" }' 407 | ''' 408 | kwargs = salt.utils.clean_kwargs() 409 | c = consul.Consul(token=master_token, **kwargs) 410 | rules = ' '.join(rules.split()) 411 | token = c.acl.create(rules=rules) 412 | return token 413 | 414 | 415 | def acl_list(master_token, **kwargs): 416 | ''' 417 | List ACLs 418 | 419 | CLI Example: 420 | 421 | .. code-block:: bash 422 | 423 | salt '*' consul.acl_list master_token=master_token 424 | ''' 425 | kwargs = salt.utils.clean_kwargs() 426 | c = consul.Consul(token=master_token, **kwargs) 427 | acls = [] 428 | for acl in c.acl.list(): 429 | acls.append({acl['ID']: {"Name": acl['Name'], "Rules": acl['Rules'] } }) 430 | return acls 431 | 432 | 433 | def acl_get(acl_id, master_token, **kwargs): 434 | ''' 435 | Get details about an ACL 436 | 437 | CLI Example: 438 | 439 | .. code-block:: bash 440 | 441 | salt '*' consul.acl_get acl_id=d6d5653f-8062-4aa2-9caa-9c2b4c3b1102 master_token=master_token 442 | ''' 443 | kwargs = salt.utils.clean_kwargs() 444 | c = consul.Consul(token=master_token, **kwargs) 445 | return c.acl.info(acl_id) 446 | 447 | 448 | def acl_clone(acl_id, master_token, **kwargs): 449 | ''' 450 | Clone an ACL 451 | 452 | CLI Example: 453 | 454 | .. code-block:: bash 455 | 456 | salt '*' consul.acl_clone acl_id=d6d5653f-8062-4aa2-9caa-9c2b4c3b1102 master_token=master_token 457 | ''' 458 | kwargs = salt.utils.clean_kwargs() 459 | c = consul.Consul(token=master_token, **kwargs) 460 | if not acl_get(acl_id, master_token=master_token): 461 | return False 462 | else: 463 | return c.acl.clone(acl_id) 464 | 465 | 466 | def acl_destroy(acl_id, master_token, **kwargs): 467 | ''' 468 | Destroy an ACL 469 | 470 | CLI Example: 471 | 472 | .. code-block:: bash 473 | 474 | salt '*' consul.acl_destroy acl_id=d6d5653f-8062-4aa2-9caa-9c2b4c3b1102 master_token=master_token 475 | ''' 476 | kwargs = salt.utils.clean_kwargs() 477 | c = consul.Consul(token=master_token, **kwargs) 478 | if not acl_get(acl_id, master_token=master_token): 479 | return False 480 | else: 481 | return c.acl.destroy(acl_id) 482 | 483 | 484 | def acl_update(acl_id, master_token, rules=None, name=None, type='client', **kwargs): 485 | ''' 486 | Updates an ACL 487 | 488 | CLI Example: 489 | 490 | .. code-block:: bash 491 | 492 | salt '*' consul.acl_update acl_id=efdce837-e9c2-a197-4379-41c5ce1cfac2 master_token=master_token rules='key "" { policy = "read" }' 493 | ''' 494 | kwargs = salt.utils.clean_kwargs() 495 | c = consul.Consul(token=master_token, **kwargs) 496 | if rules: 497 | rules = ' '.join(rules.split()) 498 | 499 | if not acl_get(acl_id, master_token=master_token): 500 | return False 501 | else: 502 | c.acl.update(acl_id=acl_id, rules=rules, name=name, type=type) 503 | return True 504 | 505 | 506 | 507 | -------------------------------------------------------------------------------- /_states/consul_check.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | Management of consul checks 4 | ========================== 5 | 6 | :maintainer: Aaron Bell 7 | :maturity: new 8 | :depends: - python-consul (http://python-consul.readthedocs.org/en/latest/) 9 | :platform: Linux 10 | 11 | .. versionadded:: 2014.7.0 12 | 13 | :depends: - consul Python module 14 | :configuration: See :py:mod:`salt.modules.consul` for setup instructions. 15 | 16 | .. code-block:: yaml 17 | 18 | check_in_consul: 19 | consul_check.present: 20 | - name: foo 21 | - script: nc -z localhost 6969 22 | - interval: 10s 23 | 24 | check_not_in_consul: 25 | consul_check.absent: 26 | - name: foo 27 | 28 | ''' 29 | 30 | __virtualname__ = 'consul_check' 31 | 32 | 33 | def __virtual__(): 34 | ''' 35 | Only load if the consul module is in __salt__ 36 | ''' 37 | if 'consul.key_put' in __salt__: 38 | return __virtualname__ 39 | return False 40 | 41 | 42 | def present(name, check_id=None, script=None, interval=None, ttl=None, notes=None): 43 | ''' 44 | Ensure the named check is present in Consul 45 | 46 | name 47 | consul check to manage 48 | 49 | check_id 50 | alternative check id 51 | 52 | script + interval 53 | path to script for health checks, paired with invocation interval 54 | 55 | ttl 56 | ttl for status 57 | 58 | notes 59 | not used internally by consul, meant to be human-readable 60 | 61 | ''' 62 | ret = {'name': name, 63 | 'changes': {}, 64 | 'result': True, 65 | 'comment': 'Check "%s" updated' % (name)} 66 | 67 | if not __salt__['consul.check_get'](name): 68 | __salt__['consul.check_register'](name, check_id, script, interval, ttl, notes) 69 | ret['changes'][name] = 'Check created' 70 | ret['comment'] = 'Check "%s" created' % (name) 71 | 72 | else: 73 | __salt__['consul.check_register'](name, check_id, script, interval, ttl, notes) 74 | 75 | return ret 76 | 77 | 78 | def absent(name): 79 | ''' 80 | Ensure the named check is absent in Consul 81 | 82 | name 83 | consul check to manage 84 | 85 | ''' 86 | ret = {'name': name, 87 | 'changes': {}, 88 | 'result': True, 89 | 'comment': 'Check "%s" removed' % (name)} 90 | 91 | if not __salt__['consul.check_get'](name): 92 | ret['comment'] = 'Check "%s" already absent' % (name) 93 | 94 | else: 95 | __salt__['consul.check_deregister'](name) 96 | 97 | return ret 98 | 99 | 100 | def ttl_set(name, status, notes=None): 101 | ''' 102 | Update a ttl-based service check to either passing, warning, or failing 103 | 104 | name 105 | consul service to manage 106 | 107 | status 108 | passing, warning, or failing 109 | 110 | notes 111 | optional notes for operators 112 | 113 | ''' 114 | 115 | type = 'check' 116 | 117 | ret = {'name': name, 118 | 'changes': {}, 119 | 'result': True, 120 | 'comment': 'Check set to %s' % (status)} 121 | 122 | statuses = ['passing', 'warning', 'failing'] 123 | 124 | if not __salt__['consul.service_get'](name): 125 | ret['comment'] = 'Check does not exist' % (name) 126 | 127 | if status not in statuses: 128 | ret['result'] = False 129 | ret['comment'] = 'Check must be one of: %s' % (" ".join(s)) 130 | 131 | else: 132 | __salt__['consul.ttl_' + status[:-3] ](name, type, notes) 133 | 134 | return ret 135 | -------------------------------------------------------------------------------- /_states/consul_key.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | Management of consul key/value 4 | ========================== 5 | 6 | :maintainer: Aaron Bell 7 | :maturity: new 8 | :depends: - python-consul (http://python-consul.readthedocs.org/en/latest/) 9 | :platform: Linux 10 | 11 | .. versionadded:: 2014.7.0 12 | 13 | :depends: - consul Python module 14 | :configuration: See :py:mod:`salt.modules.consul` for setup instructions. 15 | 16 | .. code-block:: yaml 17 | 18 | key_in_consul: 19 | consul_key.present: 20 | - name: foo 21 | - value: data 22 | 23 | The consul server information in the minion config file can be 24 | overridden in states using the following arguments: ``host``, ``post``, ``consistency``, 25 | ``password``. 26 | .. code-block:: yaml 27 | key_in_consul: 28 | consul_key.present: 29 | - name: foo 30 | - value: bar 31 | - host: hostname.consul 32 | - port: 6969 33 | ''' 34 | 35 | __virtualname__ = 'consul_key' 36 | 37 | import os 38 | import salt.utils 39 | 40 | def __virtual__(): 41 | ''' 42 | Only load if the consul module is in __salt__ 43 | ''' 44 | if 'consul.key_put' in __salt__: 45 | return __virtualname__ 46 | return False 47 | 48 | 49 | def present(name, value, value_from_file=False, **kwargs): 50 | ''' 51 | Ensure that the named key exists in consul with the value specified 52 | 53 | name 54 | consul key to manage 55 | 56 | value 57 | Data to persist in key 58 | ''' 59 | ret = {'name': name, 60 | 'changes': {}, 61 | 'result': True, 62 | 'comment': 'Key already set to defined value'} 63 | 64 | if value_from_file: 65 | if not os.path.isfile(value): 66 | ret = {} 67 | ret['result'] = False 68 | ret['comment'] = value + " does not exist" 69 | return ret 70 | 71 | if not salt.utils.istextfile(value): 72 | ret = {} 73 | ret['result'] = False 74 | ret['comment'] = value + " is not a text file" 75 | return ret 76 | 77 | should = open(value,'r').read() 78 | else: 79 | should = value 80 | 81 | if not __salt__['consul.key_get'](name, **kwargs): 82 | __salt__['consul.key_put'](name, value, value_from_file, **kwargs) 83 | ret['changes'][name] = 'Key created' 84 | ret['comment'] = 'Key "%s" set with value "%s"' % (name, value) 85 | 86 | elif __salt__['consul.key_get'](name, **kwargs) != should: 87 | __salt__['consul.key_put'](name, value, value_from_file, **kwargs) 88 | ret['changes'][name] = 'Value updated' 89 | ret['comment'] = 'Key "%s" updated with value "%s"' % (name, value) 90 | 91 | return ret 92 | 93 | 94 | def absent(name, recurse=False, **kwargs): 95 | ''' 96 | Ensure that the named key does not exist in consul 97 | 98 | name 99 | consul key to manage 100 | 101 | value 102 | Data to persist in key 103 | ''' 104 | ret = {'name': name, 105 | 'changes': {}, 106 | 'result': True, 107 | 'comment': 'Key(s) specified already absent'} 108 | 109 | if not __salt__['consul.key_get'](name, **kwargs): 110 | ret['comment'] = 'Key "%s" does not exist' % (name) 111 | 112 | else: 113 | __salt__['consul.key_delete'](name, **kwargs) 114 | ret['changes'][name] = 'Value updated' 115 | ret['comment'] = 'Key "%s" deleted' % (name) 116 | 117 | return ret 118 | -------------------------------------------------------------------------------- /_states/consul_service.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | ''' 3 | Management of consul services 4 | ========================== 5 | 6 | :maintainer: Aaron Bell 7 | :maturity: new 8 | :depends: - python-consul (http://python-consul.readthedocs.org/en/latest/) 9 | :platform: Linux` 10 | 11 | .. versionadded:: 2014.7.0 12 | 13 | :depends: - consul Python module 14 | :configuration: See :py:mod:`salt.modules.consul` for setup instructions. 15 | 16 | .. code-block:: yaml 17 | 18 | service_in_consul: 19 | consul_service.present: 20 | - name: foo 21 | - port: 6969 22 | - script: nc -z localhost 6969 23 | - interval: 10s 24 | 25 | service_not_in_consul: 26 | consul_service.absent: 27 | - name: foo 28 | 29 | ttl_status_set: 30 | consul_service.ttl_set: 31 | - name: foo 32 | - status: passing 33 | - notes: bar 34 | 35 | ''' 36 | 37 | __virtualname__ = 'consul_service' 38 | 39 | 40 | def __virtual__(): 41 | ''' 42 | Only load if the consul module is in __salt__ 43 | ''' 44 | if 'consul.key_put' in __salt__: 45 | return __virtualname__ 46 | return False 47 | 48 | 49 | def present(name, service_id=None, port=None, tags=None, script=None, interval=None, ttl=None): 50 | ''' 51 | Ensure the named service is present in Consul 52 | 53 | name 54 | consul service to manage 55 | 56 | service_id 57 | alternative service id 58 | 59 | port 60 | service port 61 | 62 | tags 63 | list of tags to associate with this service 64 | 65 | script + interval 66 | path to script for health checks, paired with invocation interval 67 | 68 | ttl 69 | length of time a service should remain healthy before being updated 70 | note: if this is specified, script + interval must not be used 71 | ''' 72 | ret = {'name': name, 73 | 'changes': {}, 74 | 'result': True, 75 | 'comment': 'Service "%s" updated' % (name)} 76 | 77 | if not __salt__['consul.service_get'](name, service_id): 78 | __salt__['consul.service_register'](name, service_id, port, tags, script, interval, ttl) 79 | ret['changes'][name] = 'Service created' 80 | ret['comment'] = 'Service "%s" created' % (name) 81 | 82 | else: 83 | __salt__['consul.service_register'](name, service_id, port, tags, script, interval, ttl) 84 | 85 | return ret 86 | 87 | 88 | def absent(name): 89 | ''' 90 | Ensure the named service is absent in Consul 91 | 92 | name 93 | consul service to manage 94 | 95 | ''' 96 | ret = {'name': name, 97 | 'changes': {}, 98 | 'result': True, 99 | 'comment': 'Service "%s" removed' % (name)} 100 | 101 | if not __salt__['consul.service_get'](name): 102 | ret['comment'] = 'Service "%s" already absent' % (name) 103 | 104 | else: 105 | __salt__['consul.service_deregister'](name) 106 | 107 | return ret 108 | 109 | 110 | def ttl_set(name, status, notes=None): 111 | ''' 112 | Update a ttl-based service check to either passing, warning, or failing 113 | 114 | name 115 | consul service to manage 116 | 117 | status 118 | passing, warning, or failing 119 | 120 | notes 121 | optional notes for operators 122 | 123 | ''' 124 | 125 | type = 'service' 126 | 127 | ret = {'name': name, 128 | 'changes': {}, 129 | 'result': True, 130 | 'comment': 'Service set to %s' % (status)} 131 | 132 | statuses = ['passing', 'warning', 'failing'] 133 | 134 | if not __salt__['consul.service_get'](name): 135 | ret['comment'] = 'Service does not exist' % (name) 136 | 137 | if status not in statuses: 138 | ret['result'] = False 139 | ret['comment'] = 'Status must be one of: %s' % (" ".join(s)) 140 | 141 | else: 142 | __salt__['consul.ttl_' + status[:-3] ](name, type, notes) 143 | 144 | return ret 145 | 146 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | python-consul==0.3.5 --------------------------------------------------------------------------------