├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── examples ├── .gitignore ├── find_hostgroups.py └── manage_hostgroup_flags.py ├── icinga2_api ├── __init__.py ├── api.py ├── cmdline.py └── defaults.py ├── install.sh ├── sample_api.yml ├── setup.py ├── uninstall.sh └── upload.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.txt 3 | dist/ 4 | build/ 5 | icinga2_api.egg-info/ 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## CHANGELOG 2 | 3 | ### v0.0.6 4 | 5 | - Making config file optional. If the user specifies mandatory attrs i.e. 6 | 7 | ``` 8 | host 9 | port 10 | user 11 | password 12 | ``` 13 | 14 | there isn't a point in cribbing about the lack of a configfile. If both command line params and config file provided - command line params override config file. 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Saurabh Hirani 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 9 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 12 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 13 | PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### icinga2_api 2 | 3 | Python library and command line utility to support [icinga2 api](http://docs.icinga.org/icinga2/snapshot/doc/module/icinga2/chapter/icinga2-api) 4 | 5 | ### Advantages over direct curl/wget calls 6 | 7 | * Abstract out repetitive data like host, port, credentials, etc. in a configuration file 8 | * Instead of specifying different HTTP headers - GET, PUT, POST - work with actions - create, read, update, delete. 9 | * Better error handling. 10 | * Python library and command line support. 11 | * See **Examples** mapping from command line curl calls => the command line utlity => the python library. 12 | * To see more curl calls check out [icinga2 api examples](https://github.com/saurabh-hirani/icinga2-api-examples) 13 | 14 | ### Installation 15 | 16 | * stable release: ```pip install icinga2_api``` 17 | * ongoing development package: 18 | 19 | ```bash 20 | git clone https://github.com/saurabh-hirani/icinga2_api 21 | cd icinga2_api 22 | sudo ./install.sh 23 | ``` 24 | 25 | ### Configuration setup 26 | 27 | - Configure the API parameters in ```~/.icinga2/api.yml``` (Default profile: default) 28 | - Check ```sample_api.yml``` in this repo to view a sample configuration. 29 | - The command line utility ```icinga2_api``` uses the config file to load the host connection params. If you don't want to use it, use the command line params 30 | 31 | ``` 32 | icinga2_api -H $host -a create|read|update|delete --user $user --password $password --port 5665 -u '/v1/objects/hosts' -d '{ payload }' 33 | ``` 34 | 35 | More command line examples below. 36 | 37 | - Use ```icinga2_api --help``` to view all the options 38 | 39 | 40 | ### Pre-requisites 41 | 42 | * A working icinga2 API setup through: 43 | - [icinga2 docker image](https://github.com/icinga/docker-icinga2) OR 44 | - [icinga2 vagrant boxes](https://github.com/Icinga/icinga-vagrant) OR 45 | - your own setup 46 | 47 | ### Examples 48 | 49 | * Check API status 50 | 51 | - Without icinga2_api, through the command line 52 | 53 | ```bash 54 | curl -u $ICINGA2_API_USER:$ICINGA2_API_PASSWORD \ 55 | -k "https://$ICINGA2_HOST:$ICINGA2_API_PORT/v1/status" | python -m json.tool 56 | ``` 57 | 58 | - With the icinga2_api command line utility 59 | 60 | ```bash 61 | icinga2_api -p docker 62 | ``` 63 | 64 | gives the output 65 | 66 | ```bash 67 | OK: read action succeeded 68 | { 69 | "status": "success", 70 | "request": { 71 | "url": "https://192.168.1.103:4665/v1/status", 72 | "headers": { 73 | "X-HTTP-Method-Override": "GET", 74 | "Accept": "application/json" 75 | }, 76 | "data": null 77 | }, 78 | "response": { 79 | "status_code": 200, 80 | "data": { 81 | "results": [ 82 | { 83 | "status": { 84 | "api": { 85 | "zones": { 86 | "docker-icinga2": { 87 | . 88 | . 89 | . 90 | 91 | ``` 92 | 93 | - With the icinga2_api library 94 | 95 | ```python 96 | from icinga2_api import api 97 | obj = api.Api(profile='docker') 98 | output = obj.read() 99 | print json.dumps(output['response']['data'], indent=2) 100 | ``` 101 | 102 | * Create a host: 103 | 104 | - Without icinga2_api, through the command line 105 | 106 | ```bash 107 | curl -u $ICINGA2_API_USER:$ICINGA2_API_PASSWORD \ 108 | -H 'Accept: application/json' -X PUT \ 109 | -k "https://$ICINGA2_HOST:$ICINGA2_API_PORT/v1/objects/hosts/api_dummy_host_1" \ 110 | -d '{ "templates": [ "generic-host" ], "attrs": { "address": "8.8.8.8", "vars.os" : "Linux", "groups": ["api_dummy_hostgroup"] } }' | python -m json.tool 111 | ``` 112 | 113 | - With the icinga2_api command line utility 114 | 115 | ```bash 116 | icinga2_api -p docker \ 117 | -a create \ 118 | -u '/v1/objects/hosts/api_dummy_host_1' \ 119 | -d '{ "templates": [ "generic-host" ], "attrs": { "address": "8.8.8.8", "vars.os" : "Linux" } }' 120 | ``` 121 | 122 | gives the output 123 | 124 | ```bash 125 | OK: create action succeeded 126 | { 127 | "status": "success", 128 | "request": { 129 | "url": "https://192.168.1.103:4665/v1/objects/hosts/api_dummy_host_1", 130 | "headers": { 131 | "Accept": "application/json" 132 | }, 133 | "data": { 134 | "templates": [ 135 | "generic-host" 136 | ], 137 | "attrs": { 138 | "vars.os": "Linux", 139 | "address": "8.8.8.8" 140 | } 141 | } 142 | }, 143 | "response": { 144 | "status_code": 200, 145 | "data": { 146 | "results": [ 147 | { 148 | "status": "Object was created", 149 | "code": 200.0 150 | } 151 | ] 152 | } 153 | } 154 | } 155 | ``` 156 | 157 | - With the icinga2_api library 158 | 159 | ```python 160 | from icinga2_api import api 161 | obj = api.Api(profile='docker') 162 | uri = '/v1/objects/hosts/api_dummy_host_1' 163 | data = { "templates": [ "generic-host" ], "attrs": { "address": "8.8.8.8", "vars.os" : "Linux" } } 164 | output = obj.create(uri, data) 165 | print json.dumps(output['response']['data'], indent=2) 166 | ``` 167 | 168 | * Read host name, address attributes for this host 169 | 170 | - Without icinga2_api, through the command line 171 | 172 | ```bash 173 | curl -u $ICINGA2_API_USER:$ICINGA2_API_PASSWORD \ 174 | -H 'Accept: application/json' -H 'X-HTTP-Method-Override: GET' \ 175 | -X POST \ 176 | -k "https://$ICINGA2_HOST:$ICINGA2_API_PORT/v1/objects/hosts/api_dummy_host_1" \ 177 | -d '{ "attrs": ["name", "address"] }' | python -m json.tool 178 | ``` 179 | 180 | - With the icinga2_api command line utility 181 | 182 | ```bash 183 | icinga2_api -p docker \ 184 | -a read \ 185 | -u '/v1/objects/hosts/api_dummy_host_1' \ 186 | -d '{ "attrs": ["name", "address"] }' 187 | ``` 188 | 189 | gives the output 190 | 191 | ```bash 192 | OK: read action succeeded 193 | { 194 | "status": "success", 195 | "request": { 196 | "url": "https://192.168.1.103:4665/v1/objects/hosts/api_dummy_host_1", 197 | "headers": { 198 | "X-HTTP-Method-Override": "GET", 199 | "Accept": "application/json" 200 | }, 201 | "data": { 202 | "attrs": [ 203 | "name", 204 | "address" 205 | ] 206 | } 207 | }, 208 | "response": { 209 | "status_code": 200, 210 | "data": { 211 | "results": [ 212 | { 213 | "meta": {}, 214 | "type": "Host", 215 | "attrs": { 216 | "name": "api_dummy_host_1", 217 | "address": "8.8.8.8" 218 | }, 219 | "joins": {}, 220 | "name": "api_dummy_host_1" 221 | } 222 | ] 223 | } 224 | } 225 | } 226 | ``` 227 | 228 | - With the icinga2_api library 229 | 230 | ```python 231 | from icinga2_api import api 232 | obj = api.Api(profile='docker') 233 | uri = '/v1/objects/hosts/api_dummy_host_1' 234 | data = { "attrs": ["name", "address"] } 235 | output = obj.read(uri, data) 236 | print json.dumps(output['response']['data'], indent=2) 237 | ``` 238 | 239 | * Update attributes for this host - add a custom var 240 | 241 | - Without icinga2_api, through the command line 242 | 243 | ```bash 244 | curl -u $ICINGA2_API_USER:$ICINGA2_API_PASSWORD \ 245 | -H 'Accept: application/json' -H 'X-HTTP-Method-Override: GET' \ 246 | -X POST \ 247 | -k "https://$ICINGA2_HOST:$ICINGA2_API_PORT/v1/objects/hosts/api_dummy_host_1" \ 248 | -d '{ "attrs": { "address": "8.8.8.8", "vars.os": "Linux", "vars.environment" : "stage" } }' 249 | ``` 250 | 251 | - With the icinga2_api command line utility 252 | 253 | ```bash 254 | icinga2_api -p docker \ 255 | -a update \ 256 | -u '/v1/objects/hosts/api_dummy_host_1' \ 257 | -d '{ "attrs": { "address": "8.8.8.8", "vars.os": "Linux", "vars.environment" : "stage" } }' 258 | ``` 259 | 260 | gives the output 261 | 262 | ```bash 263 | OK: update action succeeded 264 | { 265 | "status": "success", 266 | "request": { 267 | "url": "https://192.168.1.103:4665/v1/objects/hosts/api_dummy_host_1", 268 | "headers": { 269 | "Accept": "application/json" 270 | }, 271 | "data": { 272 | "attrs": { 273 | "vars.os": "Linux", 274 | "vars.environment": "stage", 275 | "address": "8.8.8.8" 276 | } 277 | } 278 | }, 279 | "response": { 280 | "status_code": 200, 281 | "data": { 282 | "results": [ 283 | { 284 | "status": "Attributes updated.", 285 | "code": 200.0, 286 | "type": "Host", 287 | "name": "api_dummy_host_1" 288 | } 289 | ] 290 | } 291 | } 292 | } 293 | ``` 294 | 295 | - With the icinga2_api library 296 | 297 | ```python 298 | from icinga2_api import api 299 | obj = api.Api(profile='docker') 300 | uri = '/v1/objects/hosts/api_dummy_host_1' 301 | data = { "attrs": { "address": "8.8.8.8", "vars.os": "Linux", "vars.environment" : "stage" } } 302 | output = obj.update(uri, data) 303 | print json.dumps(output['response']['data'], indent=2) 304 | ``` 305 | 306 | * Delete this host 307 | 308 | - Without icinga2_api, through the command line 309 | 310 | ```bash 311 | curl -u $ICINGA2_API_USER:$ICINGA2_API_PASSWORD \ 312 | -H 'Accept: application/json' -H 'X-HTTP-Method-Override: DELETE' -X POST \ 313 | -k "https://$ICINGA2_HOST:$ICINGA2_API_PORT/v1/objects/hosts/api_dummy_host_1" \ 314 | -d '{ "cascade": 1 }' 315 | ``` 316 | 317 | - With the icinga2_api command line utility 318 | 319 | ```bash 320 | icinga2_api -p docker \ 321 | -a delete \ 322 | -u '/v1/objects/hosts/api_dummy_host_1' \ 323 | -d '{ "cascade": 1 }' 324 | ``` 325 | 326 | gives the output 327 | 328 | ```bash 329 | OK: delete action succeeded 330 | { 331 | "status": "success", 332 | "request": { 333 | "url": "https://192.168.1.103:4665/v1/objects/hosts/api_dummy_host_1", 334 | "headers": { 335 | "X-HTTP-Method-Override": "DELETE", 336 | "Accept": "application/json" 337 | }, 338 | "data": { 339 | "cascade": 1 340 | } 341 | }, 342 | "response": { 343 | "status_code": 200, 344 | "data": { 345 | "results": [ 346 | { 347 | "status": "Object was deleted.", 348 | "code": 200.0, 349 | "type": "Host", 350 | "name": "api_dummy_host_1" 351 | } 352 | ] 353 | } 354 | } 355 | } 356 | ``` 357 | 358 | - With the icinga2_api library 359 | 360 | ```python 361 | from icinga2_api import api 362 | obj = api.Api(profile='docker') 363 | uri = '/v1/objects/hosts/api_dummy_host_1' 364 | data = {'cascade': 1} 365 | obj.delete(uri, data) 366 | ``` 367 | 368 | * Disable notifications for all hosts in hostgroup: 369 | 370 | - Without icinga2_api, through the command line 371 | 372 | ```bash 373 | curl -u $ICINGA2_API_USER:$ICINGA2_API_PASSWORD \ 374 | -H 'Accept: application/json' \ 375 | -X POST \ 376 | -k "https://$ICINGA2_HOST:$ICINGA2_API_PORT/v1/objects/hosts/" \ 377 | -d '{ "filter": "match(\"*,api_dummy_hostgroup,*\",host.groups)", \ 378 | "attrs": { "enable_notifications": false } }' | python -m json.tool 379 | ``` 380 | 381 | - With the icinga2_api command line utility 382 | 383 | ```bash 384 | icinga2_api -p docker \ 385 | -a update \ 386 | -u '/v1/objects/hosts/' \ 387 | -d '{ "filter": "match(\"*,api_dummy_hostgroup,*\",host.groups)", \ 388 | "attrs": { "enable_notifications": false } }' | python -m json.tool 389 | ``` 390 | 391 | - With the icinga2_api library: 392 | 393 | ```python 394 | from icinga2_api import api 395 | obj = api.Api(profile='docker') 396 | uri = '/v1/objects/hosts/' 397 | data = { 'filter': 'match("*,api_dummy_hostgroup,*",host.groups)', 398 | 'attrs': {'enable_notifications': false } } 399 | obj.update(uri, data) 400 | ``` 401 | 402 | This functionality can be abstracted out in a ```manage_hostgroup_hosts_notifications``` function 403 | 404 | ### Error handling examples 405 | 406 | * Deleting a non-existent host 407 | 408 | ```bash 409 | icinga2_api -p docker \ 410 | -a delete \ 411 | -u '/v1/objects/hosts/api_dummy_host_non_existent' \ 412 | -d '{ "cascade": 1 }' 413 | ``` 414 | 415 | gives the output 416 | 417 | ```bash 418 | CRITICAL: delete action failed 419 | { 420 | "status": "failure", 421 | "request": { 422 | "url": "https://192.168.1.103:4665/v1/objects/hosts/api_dummy_host_non_existent", 423 | "headers": { 424 | "X-HTTP-Method-Override": "DELETE", 425 | "Accept": "application/json" 426 | }, 427 | "data": { 428 | "cascade": 1 429 | } 430 | }, 431 | "response": { 432 | "status_code": 503, 433 | "data": "Error: Object does not exist.\n\n\t(0) libremote.so: void boost::throw_exception >(boost::exception_detail::error_info_injector const&) (+0xf8) [0x7f645d126428]\n\t(1) libremote.so: void boost::exception_detail::throw_exception_(std::invalid_argument const&, char const*, char const*, int) (+0x69) [0x7f645d1264e9]\n\t(2) libremote.so: icinga::ConfigObjectTargetProvider::GetTargetByName(icinga::String const&, icinga::String const&) const (+0xd2) [0x7f645d0cd772]\n\t(3) libremote.so: icinga::FilterUtility::GetFilterTargets(icinga::QueryDescription const&, boost::intrusive_ptr const&, boost::intrusive_ptr const&) (+0x1d1) [0x7f645d0cf011]\n\t(4) libremote.so: icinga::DeleteObjectHandler::HandleRequest(boost::intrusive_ptr const&, icinga::HttpRequest&, icinga::HttpResponse&) (+0x341) [0x7f645d0d76c1]\n\t(5) libremote.so: icinga::HttpHandler::ProcessRequest(boost::intrusive_ptr const&, icinga::HttpRequest&, icinga::HttpResponse&) (+0x52b) [0x7f645d0d58eb]\n\t(6) libremote.so: icinga::HttpServerConnection::ProcessMessageAsync(icinga::HttpRequest&) (+0x47d) [0x7f645d10c34d]\n\t(7) libbase.so: icinga::WorkQueue::WorkerThreadProc() (+0x4a2) [0x7f645da4c5d2]\n\t(8) libboost_thread-mt.so.1.53.0: (+0xd24a) [0x7f645e4a624a]\n\t(9) libpthread.so.0: (+0x7df5) [0x7f645ab37df5]\n\t(10) libc.so.6: clone (+0x6d) [0x7f645b04a1ad]\n\n" 434 | } 435 | } 436 | ``` 437 | 438 | The status code is non-200 and the data non-json. 439 | 440 | * More examples in examples/ 441 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | *.txt 2 | -------------------------------------------------------------------------------- /examples/find_hostgroups.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Quick and dirty program to find objects matching a proper regular expression 5 | 6 | EXAMPLES: 7 | 8 | # Find all 'hosts' in 'prod' icinga2_api profile with host.name matching 'web-' 9 | $proganame prod hosts 'web-' 10 | 11 | # Find all 'hostgroups' in 'prod' icinga2_api profile matching 'web-' 12 | $proganame prod hostgroups 'web-' 13 | """ 14 | 15 | import os 16 | import re 17 | import sys 18 | import json 19 | from icinga2_api import api 20 | 21 | USAGE = 'USAGE: %s host_profile object_type regex' % os.path.basename(__file__) 22 | 23 | class FindHostgroupsException(Exception): 24 | """ Exception class for this program """ 25 | pass 26 | 27 | def main(args): 28 | """ find hostgroups matching regex """ 29 | 30 | if len(args) != 3: 31 | raise FindHostgroupsException({'message': 'Invalid number of args'}) 32 | 33 | icinga2_host_profile = args[0] 34 | 35 | icinga2_obj_type = args[1] 36 | obj = api.Api(profile=icinga2_host_profile) 37 | 38 | regex = args[2] 39 | regex = regex.strip() 40 | if regex == '': 41 | raise FindHostgroupsException({'message': 'Invalid regex - %s' % regex}) 42 | compiled_regex = re.compile(regex) 43 | 44 | uri = '/v1/objects/%s' % icinga2_obj_type 45 | data = {"attrs": ["name"]} 46 | output = obj.read(uri, data) 47 | 48 | if output['status'] != 'success': 49 | raise FindHostgroupsException({'message': 'API call failed', 50 | 'output_ds': output}) 51 | 52 | results = output['response']['data']['results'] 53 | if not results: 54 | return [] 55 | 56 | return [x for x in results if re.search(compiled_regex, x['name'])] 57 | 58 | if __name__ == '__main__': 59 | try: 60 | print json.dumps(main(sys.argv[1:]), indent=2) 61 | except FindHostgroupsException as hostgroup_exception: 62 | details = hostgroup_exception.args[0] 63 | print 'ERROR: ' + details['message'] 64 | if 'output_ds' in details: 65 | print "------------------" 66 | print json.dumps(details['output_ds'], indent=2) 67 | print "------------------" 68 | print USAGE 69 | sys.exit(1) 70 | -------------------------------------------------------------------------------- /examples/manage_hostgroup_flags.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ Quick and dirty example to set flags on host/service checks for icinga2 """ 4 | 5 | """ 6 | Quick and dirty program to enable/disable host/service checks for icinga2 for 7 | hostgroups matching a pattern 8 | 9 | EXAMPLES: 10 | 11 | # Set 'enable_active_checks' to 'true' for all hosts in the hostgroups matching 'webs' for 'prod' 12 | # icinga2 profile 13 | $proganame prod enable_active_checks:true hosts webs 14 | 15 | # Set 'enable_notifications' to 'false' for all services in the hostgroups matching 'webs' for 'prod' 16 | # icinga2 profile 17 | $proganame prod enable_notifications:false services webs 18 | """ 19 | 20 | import os 21 | import sys 22 | import json 23 | import find_hostgroups 24 | from icinga2_api import api 25 | 26 | USAGE = 'USAGE: %s host_profile enable|disable hosts|services hostgroup_pattern' %\ 27 | os.path.basename(__file__) 28 | 29 | class HostgroupChecksException(Exception): 30 | """ Exception class for this program """ 31 | pass 32 | 33 | def main(args): 34 | """ enable|disable checks for hosts|services for matching hostgroups """ 35 | 36 | if len(args) != 4: 37 | raise HostgroupChecksException({'message': 'Invalid number of args'}) 38 | 39 | icinga2_host_profile = args[0] 40 | obj = api.Api(profile=icinga2_host_profile) 41 | 42 | action_state = args[1] 43 | action, state = action_state.split(':') 44 | 45 | if state == 'true': 46 | state = True 47 | else: 48 | state = False 49 | # no validation for action as of now 50 | 51 | target = args[2] 52 | if target != 'hosts' and target != 'services': 53 | raise HostgroupChecksException({'message': 'Invalid action target - %s' % target}) 54 | 55 | # find the hostgroups 56 | hostgroup_pattern = args[3] 57 | 58 | uri = '/v1/objects/%s' % target 59 | status = {} 60 | if action not in status: 61 | status[action] = {'success': [], 'failure': []} 62 | 63 | if hostgroup_pattern == '.+': 64 | data = {"attrs": {action: state}} 65 | output = obj.update(uri, data) 66 | if output['status'] != 'success': 67 | status[action]['failure'].append(output) 68 | else: 69 | status[action]['success'].append(output) 70 | return status 71 | 72 | target_hostgroups_ds = find_hostgroups.main([icinga2_host_profile, 73 | 'hostgroups', hostgroup_pattern]) 74 | hostgroup_names = [x['name'] for x in target_hostgroups_ds] 75 | 76 | # do the deed 77 | 78 | for hostgroup_name in hostgroup_names: 79 | print "STATUS: %s: %s" % (hostgroup_name, action) 80 | data = {"attrs": {action: state}, 81 | "filter": "\"%s\" in host.groups" % hostgroup_name} 82 | output = obj.update(uri, data) 83 | 84 | print "STATUS: %s: %s: %s" % (hostgroup_name, action, output['status'].upper()) 85 | if output['status'] != 'success': 86 | status[action]['failure'].append(output) 87 | else: 88 | status[action]['success'].append(output) 89 | 90 | return status 91 | 92 | if __name__ == '__main__': 93 | try: 94 | print json.dumps(main(sys.argv[1:]), indent=2) 95 | except HostgroupChecksException as hostgroup_exception: 96 | details = hostgroup_exception.args[0] 97 | print 'ERROR: ' + details['message'] 98 | if 'output_ds' in details: 99 | print "------------------" 100 | print json.dumps(details['output_ds'], indent=2) 101 | print "------------------" 102 | print USAGE 103 | sys.exit(1) 104 | -------------------------------------------------------------------------------- /icinga2_api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saurabh-hirani/icinga2_api/092fc0d8add6ed1dd7378a2388e57ac1546ae4c3/icinga2_api/__init__.py -------------------------------------------------------------------------------- /icinga2_api/api.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import yaml 5 | import json 6 | 7 | import requests 8 | requests.packages.urllib3.disable_warnings() 9 | 10 | from icinga2_api import defaults 11 | 12 | class ApiException(Exception): pass 13 | 14 | class Api(object): 15 | def __init__(self, configfile=defaults.CONFIGFILE, 16 | profile=defaults.PROFILE, **kwargs): 17 | # set the attributes passed by the user 18 | self.configfile = configfile 19 | self.profile = profile 20 | 21 | mandatory_attrs = [ 22 | 'host', 23 | 'port', 24 | 'user', 25 | 'password' 26 | ] 27 | optional_attrs = { 28 | 'timeout': defaults.TIMEOUT, 29 | 'verify': defaults.VERIFY, 30 | 'cert_path': defaults.CERT_PATH, 31 | 'verbose': defaults.VERBOSE 32 | } 33 | 34 | # load the defaults from the configfile 35 | cfg_defaults = {} 36 | if os.path.exists(configfile): 37 | configfile_ds = yaml.load(open(configfile).read()) 38 | 39 | if profile not in configfile_ds: 40 | raise ApiException('ERROR: Invalid profile [%s] not present in %s' % 41 | (profile, configfile)) 42 | cfg_defaults = configfile_ds[profile] 43 | 44 | # update cfg_defaults with optional values 45 | for attr in optional_attrs.keys(): 46 | if attr not in cfg_defaults: 47 | cfg_defaults[attr] = optional_attrs[attr] 48 | 49 | # initialize attributes to default values 50 | self.__dict__.update(cfg_defaults) 51 | 52 | # remove the None values from kwargs before updating 53 | kwargs = {k: kwargs[k] for k in kwargs if kwargs[k] is not None} 54 | 55 | # overrides the environment defaults by user passed values 56 | self.__dict__.update(kwargs) 57 | 58 | # find out the mandatory attrs not specified 59 | empty_attrs = [m for m in mandatory_attrs if m not in self.__dict__ or self.__dict__[m] is None] 60 | if empty_attrs: 61 | raise ApiException('ERROR: No values provided for %s' % empty_attrs) 62 | 63 | def _make_request(self, uri, headers, data, method='post'): 64 | # validate input 65 | if not uri.startswith('/'): 66 | raise ApiException('ERROR: Invalid uri [%s] must begin with single /' % 67 | uri) 68 | url = 'https://%s:%s%s' % (self.host, self.port, uri) 69 | 70 | # build the request body 71 | kwargs = { 72 | 'headers': headers, 73 | 'auth': (self.user, self.password), 74 | 'verify': False, 75 | } 76 | if data is not None: 77 | kwargs['data'] = json.dumps(data) 78 | if self.verify is not False: 79 | kwargs['verify'] = self.verify 80 | 81 | if self.verbose: 82 | print 'url: %s' % url 83 | print 'attrs: %s' % kwargs 84 | 85 | # make the request 86 | method_ref = getattr(requests, method) 87 | r = method_ref(url, **kwargs) 88 | 89 | output = { 90 | 'status': 'success', 91 | 'request': { 92 | 'url': url, 93 | 'headers': headers, 94 | 'data': data 95 | }, 96 | 'response': { 97 | 'status_code': r.status_code, 98 | 'data': None, 99 | } 100 | } 101 | 102 | if r.status_code == 200: 103 | # convert unicode to str 104 | data = yaml.safe_load(json.dumps(r.json())) 105 | output['response']['data'] = data 106 | for result in data['results']: 107 | if 'code' in result and result['code'] != 200: 108 | output['status'] = 'failure' 109 | return output 110 | 111 | output['status'] = 'failure' 112 | output['response']['data'] = r.text 113 | return output 114 | 115 | def create(self, uri, data): 116 | headers = { 117 | 'Accept': 'application/json', 118 | } 119 | return self._make_request(uri, headers, data, 'put') 120 | 121 | def read(self, uri=defaults.READ_ACTION_URI, data=None): 122 | headers = { 123 | 'Accept': 'application/json', 124 | 'X-HTTP-Method-Override': 'GET' 125 | } 126 | return self._make_request(uri, headers, data) 127 | 128 | def update(self, uri, data): 129 | headers = { 130 | 'Accept': 'application/json', 131 | } 132 | return self._make_request(uri, headers, data) 133 | 134 | def delete(self, uri, data=None): 135 | headers = { 136 | 'Accept': 'application/json', 137 | 'X-HTTP-Method-Override': 'DELETE' 138 | } 139 | return self._make_request(uri, headers, data) 140 | -------------------------------------------------------------------------------- /icinga2_api/cmdline.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import click 4 | import json 5 | import re 6 | 7 | from icinga2_api.api import Api 8 | from icinga2_api import defaults 9 | 10 | VALID_ACTIONS = ['create', 'read', 'update', 'delete'] 11 | 12 | def validate_uri(ctx, param, value): 13 | if not value.startswith('/'): 14 | raise click.BadParameter('should begin with single /') 15 | return value 16 | 17 | def validate_action(ctx, param, value): 18 | if value not in VALID_ACTIONS: 19 | raise click.BadParameter('should be in %s' % VALID_ACTIONS) 20 | return value 21 | 22 | def validate_data(ctx, param, value): 23 | if value is None: 24 | return value 25 | try: 26 | return json.loads(value) 27 | except ValueError as e: 28 | raise click.BadParameter('should be valid json') 29 | 30 | @click.command() 31 | @click.option('-c', '--configfile', 32 | help='icinga2 API config file. Default: %s' % defaults.CONFIGFILE, 33 | default=defaults.CONFIGFILE) 34 | @click.option('-p', '--profile', 35 | help='icinga2 profile to load. Default: %s' % defaults.PROFILE, 36 | default=defaults.PROFILE) 37 | @click.option('-a', '--action', help='|'.join(VALID_ACTIONS) + ' Default: read', 38 | callback=validate_action, 39 | default='read') 40 | @click.option('-H', '--host', help='icinga2 api host - not required if profile specified', 41 | default=None) 42 | @click.option('--port', help='icinga2 api port - not required if profile specified', 43 | default=None) 44 | @click.option('-u', '--uri', help='icinga2 api uri. Default: ' + defaults.READ_ACTION_URI, 45 | callback=validate_uri, 46 | default=defaults.READ_ACTION_URI) 47 | @click.option('-U', '--user', help='icinga2 api user - not required if profile specified', 48 | default=None) 49 | @click.option('--password', help='icinga2 api password - not required if profile specified', 50 | default=None) 51 | @click.option('-t', '--timeout', help='icinga2 api timeout - not required if profile specified', 52 | default=None) 53 | @click.option('-V', '--verify', help='verify certificate. Default: false', 54 | default=False) 55 | @click.option('-C', '--cert-path', help='verify certificate path - not required if profile specified', 56 | default=None) 57 | @click.option('-v', '--verbose/--no-verbose', help='verbose. Default: false', 58 | default=False) 59 | @click.option('-d', '--data', help='json data to pass', 60 | callback=validate_data, 61 | default=None) 62 | @click.pass_context 63 | def icinga2_api(ctx, **kwargs): 64 | """ 65 | https://github.com/saurabh-hirani/icinga2_api/blob/master/README.md 66 | """ 67 | if kwargs['verbose']: 68 | print 'args: %s' % kwargs 69 | obj = Api(**kwargs) 70 | kwargs['uri'] = re.sub("/{2,}", "/", kwargs['uri']) 71 | method_ref = getattr(obj, kwargs['action']) 72 | output_ds = method_ref(kwargs['uri'], kwargs['data']) 73 | 74 | exit_code = 0 75 | if output_ds['status'] != 'success': 76 | click.echo(click.style('CRITICAL: %s action failed' % kwargs['action'], fg='red')) 77 | exit_code = 2 78 | else: 79 | click.echo(click.style('OK: %s action succeeded' % kwargs['action'], fg='green')) 80 | 81 | click.echo(json.dumps(output_ds, indent=2)) 82 | ctx.exit(exit_code) 83 | 84 | if __name__ == '__main__': 85 | icinga2_api() 86 | -------------------------------------------------------------------------------- /icinga2_api/defaults.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | CONFIGFILE = os.path.join(os.environ['HOME'], '.icinga2', 'api.yml') 4 | PROFILE = 'default' 5 | READ_ACTION_URI = '/v1/status' 6 | TIMEOUT = 30 7 | VERIFY = False 8 | CERT_PATH = None 9 | VERBOSE = False 10 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | python setup.py install --record installed_files.txt 3 | -------------------------------------------------------------------------------- /sample_api.yml: -------------------------------------------------------------------------------- 1 | stage: 2 | host: icinga2_host 3 | port: icinga2_api_port 4 | user: icinga2_api_user 5 | password: icinga2_api_password 6 | timeout: 5 7 | verbose: true 8 | verify: false 9 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | setup( 3 | name='icinga2_api', 4 | license='MIT', 5 | version='0.0.6', 6 | url='https://github.com/saurabh-hirani/icinga2_api', 7 | description=('Python library and command-line support for the icinga2 API'), 8 | author='Saurabh Hirani', 9 | author_email='saurabh.hirani@gmail.com', 10 | packages=find_packages(), 11 | install_requires=[ 12 | 'click', 13 | 'requests', 14 | 'pyyaml', 15 | 'simplejson' 16 | ], 17 | entry_points = { 18 | 'console_scripts': [ 19 | 'icinga2_api = icinga2_api.cmdline:icinga2_api', 20 | ] 21 | } 22 | ) 23 | -------------------------------------------------------------------------------- /uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cat installed_files.txt | xargs rm -rvf 3 | -------------------------------------------------------------------------------- /upload.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | python setup.py sdist register upload 3 | --------------------------------------------------------------------------------