├── docs ├── .static │ ├── README │ └── css │ │ └── custom.css ├── requirements.txt ├── img │ ├── logo.png │ ├── config2.png │ ├── config3.png │ ├── jira_PAT.png │ ├── attachment1.png │ ├── attachment2.png │ ├── components.png │ ├── config_PAT.png │ ├── jira_dedup1.png │ ├── jira_dedup2.png │ ├── jira_dedup3.png │ ├── screenshot.png │ ├── screenshot1.png │ ├── ssl_certs.png │ ├── userguide1.png │ ├── userguide10.png │ ├── userguide2.png │ ├── userguide3.png │ ├── userguide4.png │ ├── userguide5.png │ ├── userguide6.png │ ├── userguide7.png │ ├── userguide8.png │ ├── userguide9.png │ ├── cloud │ │ ├── config1.png │ │ ├── config2.png │ │ ├── config3.png │ │ └── config4.png │ ├── config_basic.png │ ├── dedup │ │ ├── dedup1.png │ │ ├── dedup10.png │ │ ├── dedup11.png │ │ ├── dedup12.png │ │ ├── dedup13.png │ │ ├── dedup14.png │ │ ├── dedup2.png │ │ ├── dedup3.png │ │ ├── dedup4.png │ │ ├── dedup5.png │ │ ├── dedup6.png │ │ ├── dedup7.png │ │ ├── dedup8.png │ │ └── dedup9.png │ ├── jira_reporter.png │ ├── jiraoverview.png │ ├── jirarest_001.png │ ├── jirarest_002.png │ ├── jirarest_003.png │ ├── jirarest_004.png │ ├── userguide1_ar.png │ ├── config-overview1.png │ ├── config-overview2.png │ ├── screenshot_api.png │ ├── simulate_alert.png │ ├── config_getprojects.png │ ├── passthrough_img001.png │ ├── passthrough_img002.png │ ├── passthrough_img003.png │ ├── passthrough_img006.png │ ├── passthrough_img007.png │ ├── passthrough_img008.png │ ├── passthrough_img009.png │ ├── passthrough_img010.png │ ├── passthrough_img011.png │ ├── passthrough_img012.png │ ├── assignee_and_reporter.png │ ├── customfields_parsing.png │ ├── distributed_diagram.png │ ├── distributed_diagram2.png │ ├── screenshot_projects.png │ ├── userguide_getfields1.png │ ├── userguide_getfields2.png │ ├── assignee_and_reporter2.png │ ├── getjirakv_test_success.png │ ├── passthrough_img_bearer.png │ ├── userguide │ │ ├── select_labels.png │ │ ├── jira_auto_close1.png │ │ ├── jira_auto_close2.png │ │ ├── jira_auto_close3.png │ │ ├── jira_auto_close4.png │ │ ├── jira_auto_close5.png │ │ ├── select_account.png │ │ ├── select_metadata.png │ │ ├── select_priority.png │ │ ├── select_attachment.png │ │ ├── select_components.png │ │ ├── select_issue_type.png │ │ ├── attachement_example.png │ │ ├── summary_and_description.png │ │ ├── selects_assignee_reporter.png │ │ ├── jira_auto_close_issue_example1.png │ │ ├── jira_auto_close_issue_example2.png │ │ └── jira_auto_close_issue_example3.png │ ├── config_check_connectivity.png │ ├── getjirakv_test_auth_failed.png │ ├── passthrough_img_bearer_hf.png │ ├── passthrough_img_multi_hfs.png │ ├── passthrough_img_get_projects.png │ ├── troubleshoot_custom_commands.png │ ├── getjirakv_test_connect_failed.png │ ├── passthrough_img_get_issue_types.png │ └── passthrough_img_get_issue_priorities.png ├── about.rst ├── download.rst ├── compatibility.rst ├── support.rst ├── index.rst ├── deployment.rst ├── troubleshoot.rst ├── Makefile └── conf.py ├── package ├── VERSION ├── lib │ ├── requirements.txt │ └── jira_rest_handler.py ├── default │ ├── tags.conf │ ├── eventtypes.conf │ ├── ta_service_desk_simple_addon_settings.conf │ ├── server.conf │ ├── authorize.conf │ ├── collections.conf │ ├── transforms.conf │ ├── app.conf │ ├── web.conf │ ├── macros.conf │ ├── commands.conf │ ├── restmap.conf │ ├── props.conf │ ├── alert_actions.conf │ ├── data │ │ └── ui │ │ │ ├── nav │ │ │ └── default.xml │ │ │ ├── alerts │ │ │ └── jira_service_desk_replay.html │ │ │ └── views │ │ │ ├── overview_jira_analytic.xml │ │ │ └── rest_api_explore.xml │ ├── searchbnf.conf │ └── savedsearches.conf ├── static │ ├── appIcon.png │ ├── appIconAlt.png │ ├── appIcon_2x.png │ └── appIconAlt_2x.png ├── appserver │ └── static │ │ ├── alert_jira_service_desk.png │ │ ├── table.js │ │ └── table.css ├── README.txt ├── README │ ├── ta_service_desk_simple_addon_account.conf.spec │ ├── ta_service_desk_simple_addon_settings.conf.spec │ ├── props.conf.spec │ └── alert_actions.conf.spec ├── metadata │ └── default.meta ├── app.manifest └── bin │ ├── ta_service_desk_simple_addon_rh_account.py │ ├── jirajsonexpand.py │ ├── ta_service_desk_simple_addon_rh_account_handler.py │ ├── getjirakv.py │ ├── jirarest.py │ └── jiraoverview.py ├── Docker-env-testing ├── TA-jira-unit-tests ├── squid │ ├── squidusers │ └── squid.conf ├── TA-config │ └── local │ │ ├── web.conf │ │ ├── server.conf │ │ ├── inputs.conf │ │ └── indexes.conf ├── TA-config-hf │ └── local │ │ ├── server.conf │ │ ├── web.conf │ │ ├── indexes.conf │ │ └── outputs.conf ├── splunk-share-folder │ └── README.md ├── nginx │ ├── default.conf │ ├── jira.cert │ └── jira.key ├── README.md ├── docker-compose.yml └── default.yml ├── requirements.txt ├── version.json ├── TA-jira-unit-tests ├── metadata │ └── default.meta └── default │ ├── app.conf │ └── macros.conf ├── .readthedocs.yaml ├── .gitignore ├── README.md └── .github └── workflows └── github-actions-build.yml /docs/.static/README: -------------------------------------------------------------------------------- 1 | README 2 | -------------------------------------------------------------------------------- /package/VERSION: -------------------------------------------------------------------------------- 1 | 0.0.0-post.4+c96d043 2 | 0.0.0Rc96d043 -------------------------------------------------------------------------------- /Docker-env-testing/TA-jira-unit-tests: -------------------------------------------------------------------------------- 1 | ../TA-jira-unit-tests -------------------------------------------------------------------------------- /package/lib/requirements.txt: -------------------------------------------------------------------------------- 1 | splunktaucclib>=6.2.0 2 | openpyxl>=3.1.2 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | splunk-add-on-ucc-framework>=5.44.0 2 | requests>=2.32.3 -------------------------------------------------------------------------------- /Docker-env-testing/squid/squidusers: -------------------------------------------------------------------------------- 1 | proxy_user:$apr1$IBLt0ubk$RJup84XqunLcfYN.dqIlr0 -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx>=7.2.6 2 | sphinx-rtd-theme>=2.0.0 3 | jinja2>=3.1.4 -------------------------------------------------------------------------------- /Docker-env-testing/TA-config/local/web.conf: -------------------------------------------------------------------------------- 1 | [settings] 2 | enableSplunkWebSSL = true 3 | -------------------------------------------------------------------------------- /Docker-env-testing/TA-config-hf/local/server.conf: -------------------------------------------------------------------------------- 1 | [general] 2 | python.version = python3 3 | -------------------------------------------------------------------------------- /Docker-env-testing/TA-config-hf/local/web.conf: -------------------------------------------------------------------------------- 1 | [settings] 2 | enableSplunkWebSSL = true 3 | -------------------------------------------------------------------------------- /Docker-env-testing/TA-config/local/server.conf: -------------------------------------------------------------------------------- 1 | [general] 2 | python.version = python3 3 | -------------------------------------------------------------------------------- /Docker-env-testing/TA-config/local/inputs.conf: -------------------------------------------------------------------------------- 1 | [splunktcp://9997] 2 | connection_host = ip 3 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.1.1", 3 | "appID": "TA-jira-service-desk-simple-addon" 4 | } 5 | -------------------------------------------------------------------------------- /docs/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/logo.png -------------------------------------------------------------------------------- /docs/img/config2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/config2.png -------------------------------------------------------------------------------- /docs/img/config3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/config3.png -------------------------------------------------------------------------------- /docs/img/jira_PAT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/jira_PAT.png -------------------------------------------------------------------------------- /package/default/tags.conf: -------------------------------------------------------------------------------- 1 | # tags.conf 2 | 3 | [eventtype=jira_service_desk_modaction_result] 4 | modaction_result = enabled 5 | -------------------------------------------------------------------------------- /docs/img/attachment1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/attachment1.png -------------------------------------------------------------------------------- /docs/img/attachment2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/attachment2.png -------------------------------------------------------------------------------- /docs/img/components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/components.png -------------------------------------------------------------------------------- /docs/img/config_PAT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/config_PAT.png -------------------------------------------------------------------------------- /docs/img/jira_dedup1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/jira_dedup1.png -------------------------------------------------------------------------------- /docs/img/jira_dedup2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/jira_dedup2.png -------------------------------------------------------------------------------- /docs/img/jira_dedup3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/jira_dedup3.png -------------------------------------------------------------------------------- /docs/img/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/screenshot.png -------------------------------------------------------------------------------- /docs/img/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/screenshot1.png -------------------------------------------------------------------------------- /docs/img/ssl_certs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/ssl_certs.png -------------------------------------------------------------------------------- /docs/img/userguide1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide1.png -------------------------------------------------------------------------------- /docs/img/userguide10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide10.png -------------------------------------------------------------------------------- /docs/img/userguide2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide2.png -------------------------------------------------------------------------------- /docs/img/userguide3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide3.png -------------------------------------------------------------------------------- /docs/img/userguide4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide4.png -------------------------------------------------------------------------------- /docs/img/userguide5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide5.png -------------------------------------------------------------------------------- /docs/img/userguide6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide6.png -------------------------------------------------------------------------------- /docs/img/userguide7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide7.png -------------------------------------------------------------------------------- /docs/img/userguide8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide8.png -------------------------------------------------------------------------------- /docs/img/userguide9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide9.png -------------------------------------------------------------------------------- /docs/img/cloud/config1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/cloud/config1.png -------------------------------------------------------------------------------- /docs/img/cloud/config2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/cloud/config2.png -------------------------------------------------------------------------------- /docs/img/cloud/config3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/cloud/config3.png -------------------------------------------------------------------------------- /docs/img/cloud/config4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/cloud/config4.png -------------------------------------------------------------------------------- /docs/img/config_basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/config_basic.png -------------------------------------------------------------------------------- /docs/img/dedup/dedup1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/dedup/dedup1.png -------------------------------------------------------------------------------- /docs/img/dedup/dedup10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/dedup/dedup10.png -------------------------------------------------------------------------------- /docs/img/dedup/dedup11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/dedup/dedup11.png -------------------------------------------------------------------------------- /docs/img/dedup/dedup12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/dedup/dedup12.png -------------------------------------------------------------------------------- /docs/img/dedup/dedup13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/dedup/dedup13.png -------------------------------------------------------------------------------- /docs/img/dedup/dedup14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/dedup/dedup14.png -------------------------------------------------------------------------------- /docs/img/dedup/dedup2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/dedup/dedup2.png -------------------------------------------------------------------------------- /docs/img/dedup/dedup3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/dedup/dedup3.png -------------------------------------------------------------------------------- /docs/img/dedup/dedup4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/dedup/dedup4.png -------------------------------------------------------------------------------- /docs/img/dedup/dedup5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/dedup/dedup5.png -------------------------------------------------------------------------------- /docs/img/dedup/dedup6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/dedup/dedup6.png -------------------------------------------------------------------------------- /docs/img/dedup/dedup7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/dedup/dedup7.png -------------------------------------------------------------------------------- /docs/img/dedup/dedup8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/dedup/dedup8.png -------------------------------------------------------------------------------- /docs/img/dedup/dedup9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/dedup/dedup9.png -------------------------------------------------------------------------------- /docs/img/jira_reporter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/jira_reporter.png -------------------------------------------------------------------------------- /docs/img/jiraoverview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/jiraoverview.png -------------------------------------------------------------------------------- /docs/img/jirarest_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/jirarest_001.png -------------------------------------------------------------------------------- /docs/img/jirarest_002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/jirarest_002.png -------------------------------------------------------------------------------- /docs/img/jirarest_003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/jirarest_003.png -------------------------------------------------------------------------------- /docs/img/jirarest_004.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/jirarest_004.png -------------------------------------------------------------------------------- /docs/img/userguide1_ar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide1_ar.png -------------------------------------------------------------------------------- /package/static/appIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/package/static/appIcon.png -------------------------------------------------------------------------------- /docs/img/config-overview1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/config-overview1.png -------------------------------------------------------------------------------- /docs/img/config-overview2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/config-overview2.png -------------------------------------------------------------------------------- /docs/img/screenshot_api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/screenshot_api.png -------------------------------------------------------------------------------- /docs/img/simulate_alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/simulate_alert.png -------------------------------------------------------------------------------- /package/static/appIconAlt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/package/static/appIconAlt.png -------------------------------------------------------------------------------- /package/static/appIcon_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/package/static/appIcon_2x.png -------------------------------------------------------------------------------- /docs/img/config_getprojects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/config_getprojects.png -------------------------------------------------------------------------------- /docs/img/passthrough_img001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/passthrough_img001.png -------------------------------------------------------------------------------- /docs/img/passthrough_img002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/passthrough_img002.png -------------------------------------------------------------------------------- /docs/img/passthrough_img003.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/passthrough_img003.png -------------------------------------------------------------------------------- /docs/img/passthrough_img006.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/passthrough_img006.png -------------------------------------------------------------------------------- /docs/img/passthrough_img007.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/passthrough_img007.png -------------------------------------------------------------------------------- /docs/img/passthrough_img008.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/passthrough_img008.png -------------------------------------------------------------------------------- /docs/img/passthrough_img009.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/passthrough_img009.png -------------------------------------------------------------------------------- /docs/img/passthrough_img010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/passthrough_img010.png -------------------------------------------------------------------------------- /docs/img/passthrough_img011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/passthrough_img011.png -------------------------------------------------------------------------------- /docs/img/passthrough_img012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/passthrough_img012.png -------------------------------------------------------------------------------- /docs/img/assignee_and_reporter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/assignee_and_reporter.png -------------------------------------------------------------------------------- /docs/img/customfields_parsing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/customfields_parsing.png -------------------------------------------------------------------------------- /docs/img/distributed_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/distributed_diagram.png -------------------------------------------------------------------------------- /docs/img/distributed_diagram2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/distributed_diagram2.png -------------------------------------------------------------------------------- /docs/img/screenshot_projects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/screenshot_projects.png -------------------------------------------------------------------------------- /docs/img/userguide_getfields1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide_getfields1.png -------------------------------------------------------------------------------- /docs/img/userguide_getfields2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide_getfields2.png -------------------------------------------------------------------------------- /package/static/appIconAlt_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/package/static/appIconAlt_2x.png -------------------------------------------------------------------------------- /docs/img/assignee_and_reporter2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/assignee_and_reporter2.png -------------------------------------------------------------------------------- /docs/img/getjirakv_test_success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/getjirakv_test_success.png -------------------------------------------------------------------------------- /docs/img/passthrough_img_bearer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/passthrough_img_bearer.png -------------------------------------------------------------------------------- /docs/img/userguide/select_labels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide/select_labels.png -------------------------------------------------------------------------------- /package/default/eventtypes.conf: -------------------------------------------------------------------------------- 1 | # eventtypes.conf 2 | 3 | [jira_service_desk_modaction_result] 4 | search = sourcetype="jira:service_desk_alert_action" 5 | -------------------------------------------------------------------------------- /docs/img/config_check_connectivity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/config_check_connectivity.png -------------------------------------------------------------------------------- /docs/img/getjirakv_test_auth_failed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/getjirakv_test_auth_failed.png -------------------------------------------------------------------------------- /docs/img/passthrough_img_bearer_hf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/passthrough_img_bearer_hf.png -------------------------------------------------------------------------------- /docs/img/passthrough_img_multi_hfs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/passthrough_img_multi_hfs.png -------------------------------------------------------------------------------- /docs/img/userguide/jira_auto_close1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide/jira_auto_close1.png -------------------------------------------------------------------------------- /docs/img/userguide/jira_auto_close2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide/jira_auto_close2.png -------------------------------------------------------------------------------- /docs/img/userguide/jira_auto_close3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide/jira_auto_close3.png -------------------------------------------------------------------------------- /docs/img/userguide/jira_auto_close4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide/jira_auto_close4.png -------------------------------------------------------------------------------- /docs/img/userguide/jira_auto_close5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide/jira_auto_close5.png -------------------------------------------------------------------------------- /docs/img/userguide/select_account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide/select_account.png -------------------------------------------------------------------------------- /docs/img/userguide/select_metadata.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide/select_metadata.png -------------------------------------------------------------------------------- /docs/img/userguide/select_priority.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide/select_priority.png -------------------------------------------------------------------------------- /docs/img/passthrough_img_get_projects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/passthrough_img_get_projects.png -------------------------------------------------------------------------------- /docs/img/troubleshoot_custom_commands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/troubleshoot_custom_commands.png -------------------------------------------------------------------------------- /docs/img/userguide/select_attachment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide/select_attachment.png -------------------------------------------------------------------------------- /docs/img/userguide/select_components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide/select_components.png -------------------------------------------------------------------------------- /docs/img/userguide/select_issue_type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide/select_issue_type.png -------------------------------------------------------------------------------- /docs/img/getjirakv_test_connect_failed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/getjirakv_test_connect_failed.png -------------------------------------------------------------------------------- /docs/img/passthrough_img_get_issue_types.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/passthrough_img_get_issue_types.png -------------------------------------------------------------------------------- /docs/img/userguide/attachement_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide/attachement_example.png -------------------------------------------------------------------------------- /docs/img/userguide/summary_and_description.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide/summary_and_description.png -------------------------------------------------------------------------------- /docs/img/passthrough_img_get_issue_priorities.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/passthrough_img_get_issue_priorities.png -------------------------------------------------------------------------------- /docs/img/userguide/selects_assignee_reporter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide/selects_assignee_reporter.png -------------------------------------------------------------------------------- /docs/img/userguide/jira_auto_close_issue_example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide/jira_auto_close_issue_example1.png -------------------------------------------------------------------------------- /docs/img/userguide/jira_auto_close_issue_example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide/jira_auto_close_issue_example2.png -------------------------------------------------------------------------------- /docs/img/userguide/jira_auto_close_issue_example3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/docs/img/userguide/jira_auto_close_issue_example3.png -------------------------------------------------------------------------------- /package/appserver/static/alert_jira_service_desk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guilhemmarchand/TA-jira-service-desk-simple-addon/HEAD/package/appserver/static/alert_jira_service_desk.png -------------------------------------------------------------------------------- /Docker-env-testing/splunk-share-folder/README.md: -------------------------------------------------------------------------------- 1 | # Share folder with Splunk container 2 | 3 | Use this folder to easily make files available to the Splunk docker such as large applications (ES) and so forth. -------------------------------------------------------------------------------- /docs/about.rst: -------------------------------------------------------------------------------- 1 | About 2 | ===== 3 | 4 | * Author: Guilhem Marchand, Splunk certified consultant and part of Splunk Professional Services 5 | 6 | * First public release published in April 2020 7 | 8 | * License: Apache License 2.0 9 | -------------------------------------------------------------------------------- /package/default/ta_service_desk_simple_addon_settings.conf: -------------------------------------------------------------------------------- 1 | # ta_service_desk_simple_addon_settings.conf 2 | 3 | [proxy] 4 | 5 | [logging] 6 | loglevel = INFO 7 | 8 | [advanced_configuration] 9 | timeout = 120 10 | jira_passthrough_mode = 0 -------------------------------------------------------------------------------- /package/README.txt: -------------------------------------------------------------------------------- 1 | # JIRA Service Desk simple addon 2 | 3 | See: https://ta-jira-service-desk-simple-addon.readthedocs.io 4 | 5 | # Binary File Declaration 6 | lib/charset_normalizer/md__mypyc.cpython-310-x86_64-linux-gnu.so 7 | lib/charset_normalizer/md.cpython-310-x86_64-linux-gnu.so 8 | -------------------------------------------------------------------------------- /package/default/server.conf: -------------------------------------------------------------------------------- 1 | # server.conf 2 | 3 | [shclustering] 4 | conf_replication_include.ta_jira_service_desk_simple_addon_settings = true 5 | conf_replication_include.ta_service_desk_simple_addon_account = true 6 | conf_replication_include.ta_service_desk_simple_addon_settings = true -------------------------------------------------------------------------------- /docs/download.rst: -------------------------------------------------------------------------------- 1 | Download 2 | ======== 3 | 4 | **The Splunk application can be downloaded from:** 5 | 6 | Splunk base 7 | ########### 8 | 9 | - https://splunkbase.splunk.com/app/4958 10 | 11 | GitHub 12 | ###### 13 | 14 | - https://github.com/guilhemmarchand/TA-jira-service-desk-simple-addon 15 | -------------------------------------------------------------------------------- /Docker-env-testing/TA-config/local/indexes.conf: -------------------------------------------------------------------------------- 1 | 2 | [default] 3 | lastChanceIndex = lastchanceindex 4 | 5 | [volume:primary] 6 | path = /opt/splunk/var/libs/splunk 7 | 8 | [lastchanceindex] 9 | coldPath = volume:primary/lastchanceindex/colddb 10 | homePath = volume:primary/lastchanceindex/db 11 | thawedPath = $SPLUNK_DB/lastchanceindex/thaweddb 12 | -------------------------------------------------------------------------------- /TA-jira-unit-tests/metadata/default.meta: -------------------------------------------------------------------------------- 1 | 2 | # Application-level permissions 3 | 4 | [] 5 | owner = admin 6 | access = read : [ * ], write : [ admin ] 7 | export = system 8 | 9 | ### VIEWSTATES: even normal users should be able to create shared viewstates 10 | 11 | [viewstates] 12 | access = read : [ * ], write : [ * ] 13 | export = system 14 | -------------------------------------------------------------------------------- /package/README/ta_service_desk_simple_addon_account.conf.spec: -------------------------------------------------------------------------------- 1 | [] 2 | configuration_help_link = 3 | jira_auth_mode = 4 | jira_ssl_certificate_path = 5 | jira_ssl_certificate_pem = 6 | jira_url = 7 | password = 8 | ssl_help_link = 9 | username = 10 | using_api_token_help_link = 11 | using_pat_help_link = 12 | jira_passthrough_account = -------------------------------------------------------------------------------- /package/README/ta_service_desk_simple_addon_settings.conf.spec: -------------------------------------------------------------------------------- 1 | [proxy] 2 | proxy_enabled = 3 | proxy_type = 4 | proxy_url = 5 | proxy_port = 6 | proxy_username = 7 | proxy_password = 8 | proxy_rdns = 9 | 10 | [logging] 11 | loglevel = 12 | 13 | [advanced_configuration] 14 | timeout = 15 | jira_passthrough_mode = 16 | passthrough_help_link = -------------------------------------------------------------------------------- /Docker-env-testing/TA-config-hf/local/indexes.conf: -------------------------------------------------------------------------------- 1 | 2 | [default] 3 | lastChanceIndex = lastchanceindex 4 | 5 | [volume:primary] 6 | path = /opt/splunk/var/libs/splunk 7 | 8 | [lastchanceindex] 9 | coldPath = volume:primary/lastchanceindex/colddb 10 | homePath = volume:primary/lastchanceindex/db 11 | thawedPath = $SPLUNK_DB/lastchanceindex/thaweddb 12 | -------------------------------------------------------------------------------- /docs/.static/css/custom.css: -------------------------------------------------------------------------------- 1 | /* increase width */ 2 | .wy-nav-content { 3 | max-width: 1500px; 4 | } 5 | 6 | /* fix non wrapping tables */ 7 | .wy-table-responsive table td, 8 | .wy-table-responsive table th { 9 | white-space: normal !important; 10 | } 11 | 12 | .with-border img { 13 | border: 1px solid #555; 14 | margin-bottom: 30px !important; 15 | } -------------------------------------------------------------------------------- /TA-jira-unit-tests/default/app.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Splunk app configuration file 3 | # 4 | 5 | [install] 6 | is_configured = 0 7 | 8 | [package] 9 | id = TA-jira-unit-tests 10 | check_for_updates = true 11 | 12 | [ui] 13 | is_visible = 0 14 | label = TA-jira-unit-tests 15 | 16 | [launcher] 17 | author = Guilhem Marchand 18 | description = TA-jira-unit-tests 19 | version = 1.0.0 20 | -------------------------------------------------------------------------------- /Docker-env-testing/nginx/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 8081; 3 | listen [::]:8081; 4 | 5 | access_log /var/log/nginx/reverse-access.log; 6 | error_log /var/log/nginx/reverse-error.log; 7 | 8 | 9 | ssl on; 10 | ssl_certificate /etc/nginx/cert/jira.cert; 11 | ssl_certificate_key /etc/nginx/cert/jira.key; 12 | 13 | location / { 14 | proxy_pass http://jiraback:8080; 15 | } 16 | } -------------------------------------------------------------------------------- /package/default/authorize.conf: -------------------------------------------------------------------------------- 1 | # authorize.conf 2 | 3 | # 4 | # capabilities 5 | # 6 | 7 | # only roles with this capability can retrieve JIRA access with a least privilege appaorach 8 | [capability::jira_service_desk] 9 | 10 | # Minimal import 11 | [role_jira_alert_action] 12 | 13 | # Minimal import 14 | importRoles = user 15 | 16 | # enabling this capability allows the role to use the JIRA service desk alert action with a least privilege approach 17 | jira_service_desk = enabled 18 | 19 | # This is required for Splunk Cloud 20 | [role_sc_admin] 21 | jira_service_desk = enabled 22 | 23 | # As a generic practice 24 | [role_admin] 25 | jira_service_desk = enabled 26 | -------------------------------------------------------------------------------- /package/default/collections.conf: -------------------------------------------------------------------------------- 1 | # collections.conf 2 | 3 | [kv_jira_failures_replay] 4 | replicate = false 5 | # fields_list = _key, account, data, status, ctime, mtime, no_attempts 6 | field.account = string 7 | field.data = string 8 | field.status = string 9 | field.ctime = number 10 | field.mtime = number 11 | field.no_attempts = number 12 | 13 | [kv_jira_issues_backlog] 14 | replicate = false 15 | # fields_list = _key, account, status, ctime, mtime, jira_sha256, jira_id, jira_key, jira_self 16 | field.account = string 17 | field.status = string 18 | field.ctime = number 19 | field.mtime = number 20 | field.jira_sha256 = string 21 | field.jira_id = string 22 | field.jira_key = string 23 | field.jira_self = string 24 | -------------------------------------------------------------------------------- /package/default/transforms.conf: -------------------------------------------------------------------------------- 1 | # transforms.conf 2 | 3 | # This KVstore is used for storing ticket creation failures which will automatically be attempted again 4 | # according to a resilient policy 5 | [jira_failures_replay] 6 | external_type = kvstore 7 | collection = kv_jira_failures_replay 8 | fields_list = _key, account, data, status, ctime, mtime, no_attempts 9 | 10 | [jira_issues_backlog] 11 | external_type = kvstore 12 | collection = kv_jira_issues_backlog 13 | fields_list = _key, account, status, ctime, mtime, jira_sha256, jira_id, jira_key, jira_self 14 | 15 | # jira_issue reference extraction 16 | [extract_jira_issue] 17 | REGEX = \"key\":\"([^\"]*)\"\,\"self\":\"([^\"]*)\" 18 | FORMAT = jira_issue::$1 jira_issue_rest_url::$2 19 | -------------------------------------------------------------------------------- /package/default/app.conf: -------------------------------------------------------------------------------- 1 | # app.conf 2 | 3 | [install] 4 | state_change_requires_restart = true 5 | is_configured = 0 6 | state = enabled 7 | 8 | [launcher] 9 | author = Guilhem Marchand 10 | description = The JIRA service desk simple addon allows the creation of tickets to Atlassian JIRA Service Desk via a workflow action, which can be actioned via an adaptive response for ES customers. 11 | 12 | [ui] 13 | is_visible = 1 14 | label = JIRA Service Desk simple addon 15 | docs_section_override = AddOns:released 16 | supported_themes=enterprise,dark 17 | 18 | [package] 19 | id = TA-jira-service-desk-simple-addon 20 | check_for_updates = true 21 | 22 | [triggers] 23 | reload.ta_service_desk_simple_addon_settings = simple 24 | 25 | [id] 26 | name = TA-jira-service-desk-simple-addon 27 | -------------------------------------------------------------------------------- /package/default/web.conf: -------------------------------------------------------------------------------- 1 | # web.conf 2 | 3 | # 4 | # Splunk UCC 5 | # 6 | 7 | [expose:ta_service_desk_simple_addon_account] 8 | pattern = ta_service_desk_simple_addon_account 9 | methods = POST, GET 10 | 11 | [expose:ta_service_desk_simple_addon_account_specified] 12 | pattern = ta_service_desk_simple_addon_account/* 13 | methods = POST, GET, DELETE 14 | 15 | [expose:ta_service_desk_simple_addon_settings] 16 | pattern = ta_service_desk_simple_addon_settings 17 | methods = POST, GET 18 | 19 | [expose:ta_service_desk_simple_addon_settings_specified] 20 | pattern = ta_service_desk_simple_addon_settings/* 21 | methods = POST, GET, DELETE 22 | 23 | # 24 | # jira_service_desk 25 | # 26 | 27 | [expose:jira_service_desk] 28 | pattern = jira_service_desk/manager/* 29 | methods = GET, POST, DELETE 30 | -------------------------------------------------------------------------------- /package/default/macros.conf: -------------------------------------------------------------------------------- 1 | # macros.conf 2 | 3 | # fill the list of projects in the alert action UI 4 | [get_jira_projects(1)] 5 | definition = jirafill account=$account$ opt=1 | stats count by key, key_projects | dedup key_projects | sort key_projects | fields key, key_projects 6 | iseval = false 7 | args = account 8 | 9 | # fill the list of issue types in the alert action UI 10 | [get_jira_issue_types(1)] 11 | definition = jirafill account=$account$ opt=2 | stats count by issues | dedup issues | sort issues | fields issues 12 | iseval = false 13 | args = account 14 | 15 | # fill the list of priorities in the alert action UI 16 | [get_jira_priorities(1)] 17 | definition = jirafill account=$account$ opt=3 | stats count by priorities | dedup priorities | sort priorities | fields priorities 18 | iseval = false 19 | args = account 20 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the version of Python and other tools you might need 9 | build: 10 | os: ubuntu-20.04 11 | tools: 12 | python: "3.9" 13 | # You can also specify other tool versions: 14 | # nodejs: "16" 15 | # rust: "1.55" 16 | # golang: "1.17" 17 | 18 | # Build documentation in the docs/ directory with Sphinx 19 | sphinx: 20 | configuration: docs/conf.py 21 | # If using Sphinx, optionally build your docs in additional formats such as PDF 22 | # formats: 23 | # - pdf 24 | 25 | # Optionally declare the Python requirements required to build your docs 26 | python: 27 | install: 28 | - requirements: docs/requirements.txt 29 | -------------------------------------------------------------------------------- /docs/compatibility.rst: -------------------------------------------------------------------------------- 1 | Compatibility 2 | ============= 3 | 4 | Splunk compatibility 5 | #################### 6 | 7 | This application is compatible with Splunk 9.1.x and later. 8 | 9 | Splunk Enterprise Security compatibility 10 | ######################################## 11 | 12 | This application has been verified with ES 7.x/8.x. 13 | 14 | Python support 15 | ############## 16 | 17 | Only Python3 is supported. 18 | 19 | Web Browser compatibility 20 | ######################### 21 | 22 | The application can be used with any of the supported Web Browser by Splunk: 23 | 24 | https://docs.splunk.com/Documentation/Splunk/latest/Installation/Systemrequirements 25 | 26 | JIRA compatibility 27 | ################## 28 | 29 | The Add-on is compatible with all JIRA products, notably: 30 | 31 | - JIRA Server 32 | - JIRA Cloud 33 | - JIRA Data center 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #.gitignore 2 | 3 | # local configuration files 4 | */local/* 5 | */local 6 | 7 | # local metadata 8 | */metadata/local.meta 9 | 10 | # Compiled Python files 11 | *.pyo 12 | *.pyc 13 | 14 | # Backup files 15 | *~ 16 | 17 | # pycharm 18 | .idea 19 | 20 | # Read the doc & Sphinx 21 | docs/_build 22 | docs/_static 23 | docs/_templates 24 | 25 | # backups local 26 | backups 27 | 28 | # spl 29 | *.spl 30 | 31 | # Mac OS X cached files 32 | .DS_Store 33 | 34 | # Visual Basic Code 35 | .vscode 36 | 37 | # appinspect reports 38 | appinspect_report*.html 39 | 40 | # build 41 | build/*.tgz 42 | build/release-sha256.txt 43 | 44 | # output 45 | output/* 46 | !output/README.txt 47 | 48 | # Python pickle 49 | *.pickle 50 | 51 | # Docker testing env 52 | !Docker-env-testing/jira-volume/README.md 53 | Docker-env-testing/jira-volume/* 54 | Docker-env-testing/splunk-share-folder/*.[.spl, .tgz, .zip, .tar.gz] 55 | -------------------------------------------------------------------------------- /docs/support.rst: -------------------------------------------------------------------------------- 1 | Support 2 | ####### 3 | 4 | This application is community supported, no warranty is provided. 5 | 6 | Splunk Community 7 | ================ 8 | 9 | Open a question in Splunk answers for the application: 10 | 11 | - https://community.splunk.com 12 | 13 | Splunk community slack 14 | ====================== 15 | 16 | Contact me on Splunk community slack, and even better, ask the community! 17 | 18 | - https://splunk-usergroups.slack.com 19 | 20 | Open a issue in Git 21 | =================== 22 | 23 | To report an issue, request a feature change or improvement, please open an issue in Github: 24 | 25 | - https://github.com/guilhemmarchand/TA-jira-service-desk-simple-addon/issues 26 | 27 | Email support 28 | ============= 29 | 30 | * guilhem.marchand@gmail.com 31 | 32 | However, previous options are far betters, and will give you all the chances to get a quick support from the community of fellow Splunkers. 33 | -------------------------------------------------------------------------------- /package/default/commands.conf: -------------------------------------------------------------------------------- 1 | # commands.conf 2 | 3 | # The jirafill custom command is being used to retrieve the different values available 4 | # from projects, issue types and priorities. 5 | 6 | [jirafill] 7 | chunked = true 8 | filename = jirafill.py 9 | python.version = python3 10 | 11 | # generic rest wrapper, get any info using a rest endpoint 12 | 13 | [jirarest] 14 | chunked = true 15 | filename = jirarest.py 16 | python.version = python3 17 | 18 | # load replay KVstore content, used to handle distributed setup and replay KVstore purposes 19 | 20 | [getjirakv] 21 | chunked = true 22 | filename = getjirakv.py 23 | python.version = python3 24 | 25 | # jira overview statistics 26 | 27 | [jiraoverview] 28 | chunked = true 29 | filename = jiraoverview.py 30 | python.version = python3 31 | 32 | # A simple streaming command to expand a JSON response 33 | 34 | [jirajsonexpand] 35 | chunked = true 36 | filename = jirajsonexpand.py 37 | python.version = python3 38 | -------------------------------------------------------------------------------- /Docker-env-testing/nginx/jira.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICwzCCAaugAwIBAgIJAKC9wCpWX4tzMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV 3 | BAMTCWxvY2FsaG9zdDAeFw0yMDA4MjYxNzI2NDJaFw0zMDA4MjQxNzI2NDJaMBQx 4 | EjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC 5 | ggEBAMhYeHAFI+nuzxapZulf0gL4lKbluvjur35Owfn7v8LZQkDPv/KOBlLEvsjX 6 | Ay4BYl/UrPLZGU0f1bfFY4HDmUbt8PpoK8vpvdP6sQxtSZA+pBudodCE1A/lK0sW 7 | iSnO1br2+RgiSPs5l/X382uXJ3pKFnqkT4eLypdzuP4wnCaSLuErUv3+Jo8ZCgyh 8 | hqbwvgGUq3SCI/0hZ+ojIjD4rNBA4YElwZfMu6oL8oiNy0leJA4FVxxj569cYL80 9 | 0xq3ONa/ZcxqyRGnRcBr/Tml1sB2sShyyMkIL5GvWJLq2WtX/mbur22EkuyncC2O 10 | hlbS9AwSYMQRrHaWlJK4RAiXSb0CAwEAAaMYMBYwFAYDVR0RBA0wC4IJbG9jYWxo 11 | b3N0MA0GCSqGSIb3DQEBBQUAA4IBAQCRXRx0EtcdPV+cZV2JtVnwoQTrifkxeMJR 12 | hOLumQAX58C5g5Du3mQi0ZUfwAsTTNsZ+46JOPpQobMVydjakZyKMrcoRtK6LDl6 13 | GxjpZGK4kL4mJvzSGg51Gu9kJceIz8Cv9vmX4ESycUEpO2JqOBBOz/fmgWYwzvKe 14 | fMMnIApwOi8ZEtDo+RCgKtMEedA0dE+gok+Kqf3AbazQ3KY5tbXcLKBZ+18MbA2Q 15 | NnUE7hv85DCKGY2gSf1X+2Ru5FnQeJmdfTWmm93cRF7SQjYVz1leE0/Y6eScvbnK 16 | MnyJfysa0Vwfla2Y/I676FkQzN+jhIrIiRuBRxDQD4AKsY7UVMTW 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /package/appserver/static/table.js: -------------------------------------------------------------------------------- 1 | require([ 2 | 'jquery', 3 | 'splunkjs/mvc', 4 | 'splunkjs/mvc/tableview', 5 | 'splunkjs/mvc/simplexml/ready!' 6 | ], function($, mvc, TableView) { 7 | var CustomIconRenderer = TableView.BaseCellRenderer.extend({ 8 | canRender: function(cell) { 9 | if (typeof cell.value === "string") { 10 | return cell.value && cell.value.substr(0,5) === "icon|"; 11 | } 12 | }, 13 | render: function($td, cell) { 14 | var parts = cell.value.split("|"); 15 | $td.css({'text-align':'center'}).html('
'); 16 | if (parts.length > 3) { 17 | $td.attr("title", parts[3]); 18 | } 19 | } 20 | }); 21 | // Hook into all tables on the page 22 | $("div.dashboard-element.table").each(function(){ 23 | mvc.Components.get($(this).attr("id")).getVisualization(function(tableView){ 24 | tableView.addCellRenderer(new CustomIconRenderer()); 25 | }); 26 | }); 27 | }); -------------------------------------------------------------------------------- /package/default/restmap.conf: -------------------------------------------------------------------------------- 1 | # restmap.conf 2 | 3 | # 4 | # Splunk UCC 5 | # 6 | 7 | [admin:ta_service_desk_simple_addon] 8 | match = / 9 | members = ta_service_desk_simple_addon_account, ta_service_desk_simple_addon_settings 10 | 11 | [admin_external:ta_service_desk_simple_addon_account] 12 | handlertype = python 13 | python.version = python3 14 | handlerfile = ta_service_desk_simple_addon_rh_account.py 15 | handleractions = edit, list, remove, create 16 | handlerpersistentmode = true 17 | 18 | [admin_external:ta_service_desk_simple_addon_settings] 19 | handlertype = python 20 | python.version = python3 21 | handlerfile = ta_service_desk_simple_addon_rh_settings.py 22 | handleractions = edit, list 23 | handlerpersistentmode = true 24 | 25 | # 26 | # custom API 27 | # 28 | 29 | [script:jira_service_rest_handler] 30 | match = /jira_service_desk/manager 31 | script = jira_service_rest_handler.py 32 | scripttype = persist 33 | handler = jira_service_rest_handler.Jira_v1 34 | output_modes = json 35 | passPayload = true 36 | passSystemAuth = true 37 | capability = jira_service_desk 38 | python.version = python3 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JIRA Service Desk simple addon 2 | 3 | | branch | build status | 4 | | --- | --- | 5 | | master | [![master status](https://circleci.com/gh/guilhemmarchand/TA-jira-service-desk-simple-addon/tree/master.svg?style=svg)](https://circleci.com/gh/guilhemmarchand/TA-jira-service-desk-simple-addon/tree/master) 6 | 7 | ## The Splunk Add-on for JIRA Atlassian Service Desk provides alerts action for JIRA issues creation: 8 | 9 | - Trigger JIRA issue creation from Splunk core alerts and Enterprise Security correlation searches 10 | - Dynamic retrieval per JIRA project for types of issues and priority 11 | - Dynamic assignment of priority (optional) 12 | - Dynamic and/or static assignment of summary, description, assignee and labels 13 | - Custom fields full capabilities via the embedded custom field structure in alerts (optional) 14 | - Resilient store JIRA issue creation, shall a JIRA issue fails to be created, the resilient workflow handles automatic retries with a resilient policy 15 | - Monitoring of JIRA issue workflow via the embedded Overview dashboard and out of the box alerts 16 | 17 | ![screenshot](./docs/img/screenshot.png) 18 | 19 | See the online documentation: https://ta-jira-service-desk-simple-addon.readthedocs.io/en/latest/ 20 | -------------------------------------------------------------------------------- /package/appserver/static/table.css: -------------------------------------------------------------------------------- 1 | /* Table icons */ 2 | 3 | .ico_good, 4 | .ico_warn, 5 | .ico_error, 6 | .ico_unknown { 7 | display:inline-block; 8 | text-align: center; 9 | width: 30px; 10 | height: 30px; 11 | line-height: 30px; 12 | position:relative; 13 | } 14 | 15 | .ico_good { 16 | color: white; 17 | background-color: #39ba23; 18 | border-radius: 50%; 19 | font-size: 24px; 20 | } 21 | .ico_good .icon-check { 22 | color: #ffffff !important; 23 | } 24 | .ico_warn { 25 | color: #ffa500; 26 | font-size: 39px; 27 | } 28 | .ico_error { 29 | color: white; 30 | background-color: #d54642; 31 | border-radius: 10%; 32 | font-size: 25px; 33 | } 34 | .ico_unknown { 35 | color: white; 36 | background-color: rgb(26, 150, 240); 37 | border-radius: 50%; 38 | font-size: 24px; 39 | } 40 | 41 | 42 | .ico_small.ico_good, 43 | .ico_small.ico_warn, 44 | .ico_small.ico_error, 45 | .ico_small.ico_unknown { 46 | width: 18px; 47 | height: 18px; 48 | line-height: 18px; 49 | } 50 | .ico_small.ico_good { 51 | font-size: 15px; 52 | } 53 | .ico_small.ico_warn { 54 | font-size: 26px; 55 | width: 20px; 56 | } 57 | .ico_small.ico_error { 58 | font-size: 15px; 59 | } 60 | .ico_small.ico_unknown { 61 | font-size: 15px; 62 | } 63 | -------------------------------------------------------------------------------- /Docker-env-testing/squid/squid.conf: -------------------------------------------------------------------------------- 1 | auth_param basic program /usr/lib/squid3/basic_ncsa_auth /etc/squid/squidusers 2 | acl lan proxy_auth REQUIRED # creation of access list of all authenticated users 3 | acl SSL_ports port 443 4 | acl Safe_ports port 80 # http 5 | acl Safe_ports port 21 # ftp 6 | acl Safe_ports port 443 # https 7 | acl Safe_ports port 70 # gopher 8 | acl Safe_ports port 210 # wais 9 | acl Safe_ports port 1025-65535 # unregistered ports 10 | acl Safe_ports port 280 # http-mgmt 11 | acl Safe_ports port 488 # gss-http 12 | acl Safe_ports port 591 # filemaker 13 | acl Safe_ports port 777 # multiling http 14 | acl CONNECT method CONNECT 15 | #http_access deny !Safe_ports 16 | #http_access deny CONNECT !SSL_ports 17 | http_access allow localhost manager 18 | http_access deny manager 19 | http_access allow localhost 20 | #http_access allow all 21 | http_access allow lan # users from lan list have internet access 22 | #http_access deny all 23 | http_port 3128 24 | coredump_dir /var/spool/squid 25 | refresh_pattern ^ftp: 1440 20% 10080 26 | refresh_pattern ^gopher: 1440 0% 1440 27 | refresh_pattern -i (/cgi-bin/|\?) 0 0% 0 28 | refresh_pattern (Release|Packages(.gz)*)$ 0 20% 2880 29 | refresh_pattern . 0 20% 4320 -------------------------------------------------------------------------------- /package/metadata/default.meta: -------------------------------------------------------------------------------- 1 | 2 | # Application-level permissions 3 | 4 | [] 5 | owner = admin 6 | access = read : [ * ], write : [ admin ] 7 | export = system 8 | 9 | ### EVENT TYPES 10 | 11 | [eventtypes] 12 | export = system 13 | 14 | 15 | ### PROPS 16 | 17 | [props] 18 | export = system 19 | 20 | 21 | ### MACROS 22 | 23 | [macros] 24 | export = system 25 | 26 | 27 | ### TRANSFORMS 28 | 29 | [transforms] 30 | export = system 31 | 32 | 33 | ### LOOKUPS 34 | 35 | [lookups] 36 | export = system 37 | 38 | 39 | ### TAGS 40 | 41 | [tags] 42 | export = system 43 | 44 | 45 | ### VIEWS 46 | [views] 47 | export = none 48 | 49 | 50 | ### SAVEDSEARCHES 51 | [savedsearches] 52 | export = none 53 | 54 | 55 | ### VIEWSTATES: even normal users should be able to create shared viewstates 56 | 57 | [viewstates] 58 | access = read : [ * ], write : [ * ] 59 | export = system 60 | 61 | ### Resilient store needs to have write permissions to member of the role 62 | 63 | [transforms/jira_failures_replay] 64 | access = read : [ * ], write : [ admin, jira_alert_action ] 65 | 66 | [collections/kv_jira_failures_replay] 67 | access = read : [ * ], write : [ admin, jira_alert_action ] 68 | 69 | ### Dedup behaviour relies on this collection 70 | 71 | [transforms/jira_issues_backlog] 72 | access = read : [ * ], write : [ admin, jira_alert_action ] 73 | 74 | [collections/kv_jira_issues_backlog] 75 | access = read : [ * ], write : [ admin, jira_alert_action ] 76 | -------------------------------------------------------------------------------- /package/README/props.conf.spec: -------------------------------------------------------------------------------- 1 | [source::...jira_service_desk_modalert.log] 2 | @placement search-head, indexer 3 | SHOULD_LINEMERGE=[true|false] 4 | LINE_BREAKER= 5 | CHARSET= 6 | TIME_PREFIX= 7 | TIME_FORMAT= 8 | TRUNCATE= 9 | 10 | [source::...jira_service_desk_replay_modalert.log] 11 | @placement search-head, indexer 12 | SHOULD_LINEMERGE=[true|false] 13 | LINE_BREAKER= 14 | CHARSET= 15 | TIME_PREFIX= 16 | TIME_FORMAT= 17 | TRUNCATE= 18 | 19 | [source::...ta_jira_jirafill.log] 20 | @placement search-head, indexer 21 | SHOULD_LINEMERGE=[true|false] 22 | LINE_BREAKER= 23 | CHARSET= 24 | TIME_PREFIX= 25 | TIME_FORMAT= 26 | TRUNCATE= 27 | 28 | [source::...ta_jira_jirarest.log] 29 | @placement search-head, indexer 30 | SHOULD_LINEMERGE=[true|false] 31 | LINE_BREAKER= 32 | CHARSET= 33 | TIME_PREFIX= 34 | TIME_FORMAT= 35 | TRUNCATE= 36 | 37 | [source::...ta_jira_getjirakv.log] 38 | @placement search-head, indexer 39 | SHOULD_LINEMERGE=[true|false] 40 | LINE_BREAKER= 41 | CHARSET= 42 | TIME_PREFIX= 43 | TIME_FORMAT= 44 | TRUNCATE= 45 | 46 | [source::...ta_jira_jsonexpand.log] 47 | @placement search-head, indexer 48 | SHOULD_LINEMERGE=[true|false] 49 | LINE_BREAKER= 50 | CHARSET= 51 | TIME_PREFIX= 52 | TIME_FORMAT= 53 | TRUNCATE= 54 | -------------------------------------------------------------------------------- /package/app.manifest: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "2.0.0", 3 | "info": { 4 | "title": "JIRA Service Desk simple addon", 5 | "id": { 6 | "group": null, 7 | "name": "TA-jira-service-desk-simple-addon", 8 | "version": "2.0.7" 9 | }, 10 | "author": [ 11 | { 12 | "name": "Guilhem Marchand", 13 | "email": "guilhem.marchand@gmail.com", 14 | "company": "Octamis" 15 | } 16 | ], 17 | "releaseDate": null, 18 | "description": "The JIRA service desk simple addon allows the creation of tickets to Atlassian JIRA Service Desk via a workflow action, which can be actioned via an adaptive response for ES customers.", 19 | "classification": { 20 | "intendedAudience": null, 21 | "categories": [], 22 | "developmentStatus": null 23 | }, 24 | "commonInformationModels": null, 25 | "license": { 26 | "name": null, 27 | "text": null, 28 | "uri": null 29 | }, 30 | "privacyPolicy": { 31 | "name": null, 32 | "text": null, 33 | "uri": null 34 | }, 35 | "releaseNotes": { 36 | "name": null, 37 | "text": "./README.txt", 38 | "uri": "https://ta-jira-service-desk-simple-addon.readthedocs.io/en/latest/releasenotes.html" 39 | } 40 | }, 41 | "dependencies": null, 42 | "tasks": null, 43 | "inputGroups": null, 44 | "incompatibleApps": null, 45 | "platformRequirements": null, 46 | "supportedDeployments": [ 47 | "_standalone", 48 | "_distributed", 49 | "_search_head_clustering" 50 | ], 51 | "targetWorkloads": [ 52 | "_search_heads" 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /Docker-env-testing/README.md: -------------------------------------------------------------------------------- 1 | # Docker testing environment for JIRA 2 | 3 | ## Purpose 4 | 5 | On a daily basis, this is what is being used to verify the development of the Addon against JIRA software, it is composed of: 6 | 7 | - A JIRA Software container (you can get easily a trial version when the container bootstraps) 8 | - An NGINX acting as the SSL proxy with an unsecure certificate (the Addon requires to talk to JIRA over SSL) 9 | - A simple proxy running on Squid in a container, to validate the Addon behaviours behind a proxy 10 | - A Splunk standalone container 11 | 12 | ## Start 13 | 14 | Use Visual Studio for the better experience, or use a docker-compose command to start the environment: 15 | 16 | ``` 17 | docker-compose up -d 18 | ``` 19 | 20 | ## Stop and destroy 21 | 22 | To stop and destroy: 23 | 24 | ``` 25 | docker-compose stop 26 | docker-compose rm -f 27 | ``` 28 | 29 | Optional, to purge the unused volumes and free storage: 30 | 31 | ``` 32 | docker volume prune -f 33 | ``` 34 | 35 | ## Accesses 36 | 37 | *To access JIRA:* 38 | 39 | - https://localhost:8081 40 | 41 | *To access Splunk:* 42 | 43 | - https://localhost:8000 44 | 45 | ## TA configuration in Splunk 46 | 47 | Once you have setup JIRA (get the trial, create an account), use in the TA config page: 48 | 49 | - JIRA url: nginx:8081 50 | - JIRA login: 51 | - JIRA password: 52 | 53 | *Note: you cannot verify the certificate as it is an insecure self signed* 54 | 55 | Proxy: 56 | 57 | - Proxy host: proxy 58 | - Proxy port: 3128 59 | 60 | And that's it! You have a fully functional environment to play with. 61 | 62 | ## Exec into the Splunk container 63 | 64 | ``` 65 | docker-compose exec --user splunk splunk /bin/bash 66 | ``` 67 | -------------------------------------------------------------------------------- /Docker-env-testing/nginx/jira.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpQIBAAKCAQEAyFh4cAUj6e7PFqlm6V/SAviUpuW6+O6vfk7B+fu/wtlCQM+/ 3 | 8o4GUsS+yNcDLgFiX9Ss8tkZTR/Vt8VjgcOZRu3w+mgry+m90/qxDG1JkD6kG52h 4 | 0ITUD+UrSxaJKc7Vuvb5GCJI+zmX9ffza5cnekoWeqRPh4vKl3O4/jCcJpIu4StS 5 | /f4mjxkKDKGGpvC+AZSrdIIj/SFn6iMiMPis0EDhgSXBl8y7qgvyiI3LSV4kDgVX 6 | HGPnr1xgvzTTGrc41r9lzGrJEadFwGv9OaXWwHaxKHLIyQgvka9YkurZa1f+Zu6v 7 | bYSS7KdwLY6GVtL0DBJgxBGsdpaUkrhECJdJvQIDAQABAoIBAQCTGursDcouBcyH 8 | LkHDUDlLNy0nUv2ztMalLr0die1m69HK9wBQ4FniQlZ37/CEwj1ych2/fwsl5x8s 9 | /cGv0WhlfWb45q5p3qok0SlZH5HpWSV0pYzgJ/fx5+qfCuASE645xwhl4R7HYKWk 10 | 1Lgmouk40Neqm6VNuKY8CJuoARdW9acxmog2hKOFg4OJHwiezBq19yAAV+81BF1e 11 | 4PTuBg/+NWdpY9Tn6jbZorwrO5YzxnnalZtlpgv8Yjwa1B4HoWEWsHSfTTQRkYI8 12 | NHYpkenN9cxSWygeNL9XCcyAFczlis2DdsgWXK7LohwwpfDqHVbfQWHjtAPQsOc9 13 | meM7LbBBAoGBAPqJ2hqsgzuTQhqd82e1rVLxIPS5eN8ntLKw9HOMAFJM+BV3tKv0 14 | ohoCT94K/GhU3aUKVA65dgIzh4vVlt8NeVVu84f0PbyE//RQc+MrwUfXRALCsuNT 15 | jvFfqlS6T7GIrULQ34gxCqNve4mrKBM7uZDtM2jk3pSq5VrpjWcyB6hNAoGBAMy2 16 | g2+LTEBXUh+GwU5OfbuFvGOx3BQafB4ezJK91IYXmgjFLNmAl3p0GK394WP62wsP 17 | oh7+/1u87L39GkqTh8c4Ww+wJllpstjxUcGohBWz1gBZnMAZt18VlBuBImINN562 18 | qdZ1MBrSx7orSoRdBxIdxLVVzBCHvLICv7nCGd8xAoGAM8u+IJebxfxKxU/0W3OO 19 | 8r/RLWcO2yvNSsgUsbK2+kzZWS7XVlmMDc5+jow85KDu9cjt2eFzyT63/ivZNiIF 20 | /Kikhm3zOX9Rn0PmwYdxPRX/BIcWDrNesP4zK7Mc+Lol/9NI81IYPfO1WZ/d+CP3 21 | 2EOBxmTSs2+lXSc9yaDpPl0CgYEAnJ/vxAIR3xBwNsrFT4JiVH0n/CbyuTbiS7FI 22 | 1+UBm26nXZpujCMEwp3c3BJoxjQCL15QreJvweK0R1BFM1XZBqWplkExtitWuQE5 23 | ii/Cj1A+pZnqgleqosdBwCF3CwoNyhL7sqeKnJs0UM4af1clt8iq3gq8zNDtoEzW 24 | H+vQG6ECgYEA9tsMad6gRCLknxx4N4HthciprmPF7Xen/QIC4lGm0QIWIdpMT7aL 25 | cPP831bmBmwJYURoENDNfXAz6/Gu9hgWxda3OgVo/FUHMJD6n5gz2kn8mNAwbYr8 26 | /qyj6WTCJZW58vAz5igykPdgPtBb0kucvmUNqb7nrlpvSvTnRIUMMmY= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /package/default/props.conf: -------------------------------------------------------------------------------- 1 | # props.conf 2 | 3 | # 4 | # modular alerts 5 | # 6 | 7 | [source::...jira_service_desk_modalert.log] 8 | sourcetype = ta:jira:service:desk:simple:addon:log 9 | SHOULD_LINEMERGE=false 10 | LINE_BREAKER=([\r\n]+)\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\,\d* 11 | CHARSET=UTF-8 12 | TIME_PREFIX=^ 13 | TIME_FORMAT=%Y-%m-%d %H:%M:%S,%3N 14 | TRUNCATE=0 15 | 16 | REPORT-jira_extractions = extract_jira_issue 17 | 18 | [source::...jira_service_desk_replay_modalert.log] 19 | sourcetype = ta:jira:service:desk:replay:simple:addon:log 20 | SHOULD_LINEMERGE=false 21 | LINE_BREAKER=([\r\n]+)\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\,\d* 22 | CHARSET=UTF-8 23 | TIME_PREFIX=^ 24 | TIME_FORMAT=%Y-%m-%d %H:%M:%S,%3N 25 | TRUNCATE=0 26 | 27 | REPORT-jira_extractions = extract_jira_issue 28 | 29 | # 30 | # custom commands 31 | # 32 | 33 | [source::...ta_jira_jirafill.log] 34 | sourcetype = jira:custom_commands:jirafill 35 | SHOULD_LINEMERGE=false 36 | LINE_BREAKER=([\r\n]+)\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2} 37 | CHARSET=UTF-8 38 | TIME_PREFIX=^ 39 | TIME_FORMAT=%Y-%m-%d %H:%M:%S,%3N 40 | TRUNCATE=0 41 | TZ = UTC 42 | 43 | [source::...ta_jira_jirarest.log] 44 | sourcetype = jira:custom_commands:jirarest 45 | SHOULD_LINEMERGE=false 46 | LINE_BREAKER=([\r\n]+)\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2} 47 | CHARSET=UTF-8 48 | TIME_PREFIX=^ 49 | TIME_FORMAT=%Y-%m-%d %H:%M:%S,%3N 50 | TRUNCATE=0 51 | TZ = UTC 52 | 53 | [source::...ta_jira_getjirakv.log] 54 | sourcetype = jira:custom_commands:getjirakv 55 | SHOULD_LINEMERGE=false 56 | LINE_BREAKER=([\r\n]+)\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2} 57 | CHARSET=UTF-8 58 | TIME_PREFIX=^ 59 | TIME_FORMAT=%Y-%m-%d %H:%M:%S,%3N 60 | TRUNCATE=0 61 | TZ = UTC 62 | 63 | [source::...ta_jira_jsonexpand.log] 64 | sourcetype = jira:custom_commands:jirajsonexpand 65 | SHOULD_LINEMERGE=false 66 | LINE_BREAKER=([\r\n]+)\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2} 67 | CHARSET=UTF-8 68 | TIME_PREFIX=^ 69 | TIME_FORMAT=%Y-%m-%d %H:%M:%S,%3N 70 | TRUNCATE=0 71 | TZ = UTC 72 | 73 | # 74 | # REST API 75 | # 76 | 77 | [source::...jira_service_desk_rest_api.log] 78 | sourcetype = jira_service_desk:rest 79 | SHOULD_LINEMERGE=false 80 | LINE_BREAKER=([\r\n]+)\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2} 81 | CHARSET=UTF-8 82 | TIME_PREFIX=^ 83 | TIME_FORMAT=%Y-%m-%d %H:%M:%S,%3N 84 | TRUNCATE=0 85 | EXTRACT-log_level = \d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\,\d*\s(?\w*)\s 86 | TZ = UTC 87 | -------------------------------------------------------------------------------- /package/default/alert_actions.conf: -------------------------------------------------------------------------------- 1 | # alert_actions.conf 2 | 3 | # main alert action for JIRA Service Desk 4 | [jira_service_desk] 5 | label = Open/Update/Close an issue in JIRA 6 | description = This modular alert action allows you to interact with JIRA, to open, update or close an issue. 7 | param._cam = {"task": ["Create", "Update", "Close"], "subject": ["incident"], "category": ["Ticketing system", "Incident management"], "technology": [{"version": ["1.0.0"], "product": "JIRA Service Desk", "vendor": "Atlasian"}], "supports_adhoc": true, "drilldown_uri": "search?q=search%20index%3D_internal%20OR%20index%3Dcim_modaction%20sourcetype%3Djira%3Aservice_desk_alert_action&earliest=0&latest="} 8 | python.version = python3 9 | is_custom = 1 10 | payload_format = json 11 | param.account = 12 | param.jira_project = 13 | param.jira_issue_type = 14 | param.jira_priority = 15 | param.jira_priority_dynamic = 16 | param.jira_summary = Splunk Alert: $name$ 17 | param.jira_description = The alert condition for '$name$' was triggered. 18 | param.jira_auto_close = enabled 19 | param.jira_auto_close_key_value_pair = 20 | param.jira_auto_close_status_transition_value = Done 21 | param.jira_auto_close_status_transition_comment = 22 | param.jira_auto_close_issue_number_field_name = 23 | param.jira_assignee = 24 | param.jira_reporter = 25 | param.jira_labels = 26 | param.jira_components = 27 | param.jira_dedup = enabled 28 | param.jira_dedup_comment = 29 | param.jira_dedup_exclude_statuses = Done 30 | param.jira_dedup_content = 31 | param.jira_attachment = disabled 32 | param.jira_results_description = disabled 33 | param.jira_attachment_token = $results_file$ 34 | param.jira_customfields = 35 | icon_path = alert_jira_service_desk.png 36 | 37 | # replay alert action for JIRA Service Desk 38 | [jira_service_desk_replay] 39 | label = Replay an issue in JIRA 40 | description = This modular alert action allows you to replay an issue in JIRA, which had previously failed to be created for any temporary reasons, such as networking issues or temporary unavailability of the JIRA server. 41 | python.version = python3 42 | is_custom = 0 43 | payload_format = json 44 | param.account = 45 | param.ticket_uuid = 46 | param.ticket_data = 47 | param.ticket_status = 48 | param.ticket_no_attempts = 49 | param.ticket_max_attempts = 50 | param.ticket_ctime = 51 | param.ticket_mtime = 52 | icon_path = alert_jira_service_desk.png 53 | -------------------------------------------------------------------------------- /package/README/alert_actions.conf.spec: -------------------------------------------------------------------------------- 1 | [jira_service_desk] 2 | param._cam = Adaptive Response parameters. 3 | param.account = Select JIRA Account. It's a required parameter. 4 | param.jira_project = Project. It's a required parameter. 5 | param.jira_issue_type = Issue Type. It's a required parameter. 6 | param.jira_priority = Priority. 7 | param.jira_priority_dynamic = Dynamic Priority. 8 | param.jira_summary = Summary. It's a required parameter. 9 | param.jira_description = Description. It's a required parameter. 10 | param.jira_auto_close = Auto close issue:. It's a required parameter. It's default value is enabled. 11 | param.jira_auto_close_key_value_pair = Auto close key value pair. 12 | param.jira_auto_close_status_transition_value = Auto close status transition value. 13 | param.jira_auto_close_status_transition_comment = Auto close status transition comment. 14 | param.jira_auto_close_issue_number_field_name = Auto close issue number field name. 15 | param.jira_assignee = Assignee. 16 | param.jira_reporter = Reporter. 17 | param.jira_labels = Labels. 18 | param.jira_components = Components names. 19 | param.jira_dedup = JIRA dedup behaviour:. It's a required parameter. It's default value is disabled. 20 | param.jira_dedup_comment = JIRA dedup comment. 21 | param.jira_dedup_exclude_statuses = JIRA dedup excluded statuses. 22 | param.jira_dedup_content = JIRA dedup content. 23 | param.jira_attachment = Results attachment:. It's a required parameter. It's default value is disabled. 24 | param.jira_results_description = Add results to description:. It's a required parameter. It's default value is disabled. 25 | param.jira_attachment_token = Attachment token. 26 | param.jira_customfields = custom fields structure. 27 | 28 | [jira_service_desk_replay] 29 | param.account = Select Account. It's a required parameter. 30 | param.ticket_uuid = Ticket uuid. 31 | param.ticket_data = Ticket data. 32 | param.ticket_status = Ticket status. 33 | param.ticket_no_attempts = Ticket number of attempts. 34 | param.ticket_max_attempts = Ticket max number of attempts. 35 | param.ticket_ctime = Ticket creation time. 36 | param.ticket_mtime = Ticket modification time. 37 | -------------------------------------------------------------------------------- /.github/workflows/github-actions-build.yml: -------------------------------------------------------------------------------- 1 | name: Python package 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build_release: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python-version: ["3.10"] 11 | 12 | steps: 13 | - run: echo "job automatically triggered by a ${{ github.event_name }} event." 14 | 15 | - uses: actions/checkout@v3 16 | 17 | - uses: actions/setup-python@v4 18 | with: 19 | python-version: ${{ matrix.python-version }} 20 | 21 | - name: Install Python requirements 22 | run: | 23 | pip install -r requirements.txt 24 | 25 | # 26 | # Build release and run Appinspect 27 | # 28 | 29 | - name: Build the release and submit to Splunk Appinspect vetting 30 | env: 31 | SPLUNK_BASE_LOGIN: ${{ vars.SPLUNK_BASE_LOGIN }} 32 | SPLUNK_BASE_PASSWD: ${{ secrets.SPLUNK_BASE_PASSWD }} 33 | run: | 34 | cd build; python3 build.py --keep --submitappinspect --userappinspect "$SPLUNK_BASE_LOGIN" --passappinspect "$SPLUNK_BASE_PASSWD" 35 | id: build_and_submit_appinspect 36 | 37 | # 38 | # Archive artifacts 39 | # 40 | 41 | - name: Archive Appinspect html report 42 | if: always() 43 | uses: actions/upload-artifact@v4 44 | with: 45 | name: appinspect-report-html 46 | path: output/report_appinspect.html 47 | 48 | - name: Archive Appinspect json report 49 | if: always() 50 | uses: actions/upload-artifact@v4 51 | with: 52 | name: appinspect-report-json 53 | path: output/report_appinspect.json 54 | 55 | - name: Archive tgz application 56 | if: always() 57 | uses: actions/upload-artifact@v4 58 | with: 59 | path: output/*.tgz 60 | 61 | - name: Archive sha256sum 62 | if: always() 63 | uses: actions/upload-artifact@v4 64 | with: 65 | name: release-sha256.txt 66 | path: output/release-sha256.txt 67 | 68 | - name: Show output directory content 69 | run: | 70 | ls -ltr output/ 71 | 72 | - name: Retrieve version number 73 | run: | 74 | echo "VERSION_NUMBER=$(cat output/version.txt)" >> $GITHUB_ENV 75 | 76 | - name: Show version number 77 | run: | 78 | echo "Version number is ${{ env.VERSION_NUMBER }}" 79 | 80 | - name: Retrieve build number 81 | run: | 82 | echo "BUILD_NUMBER=$(cat output/build.txt)" >> $GITHUB_ENV 83 | 84 | - name: Show build number 85 | run: | 86 | echo "Build number is ${{ env.BUILD_NUMBER }}" 87 | 88 | # 89 | # 90 | # 91 | 92 | - run: echo "End of process, job status ${{ job.status }}." 93 | -------------------------------------------------------------------------------- /Docker-env-testing/TA-config-hf/local/outputs.conf: -------------------------------------------------------------------------------- 1 | # BASE SETTINGS 2 | 3 | [tcpout] 4 | defaultGroup = primary_indexers 5 | 6 | # When indexing a large continuous file that grows very large, a universal 7 | # or light forwarder may become "stuck" on one indexer, trying to reach 8 | # EOF before being able to switch to another indexer. The symptoms of this 9 | # are congestion on *one* indexer in the pool while others seem idle, and 10 | # possibly uneven loading of the disk usage for the target index. 11 | # In this instance, forceTimebasedAutoLB can help! 12 | # ** Do not enable if you have events > 64kB ** 13 | forceTimebasedAutoLB = true 14 | 15 | # Correct an issue with the default outputs.conf for the Universal Forwarder 16 | # or the SplunkLightForwarder app; these don't forward _internal events. 17 | forwardedindex.2.whitelist = (_audit|_introspection|_internal|_metrics) 18 | 19 | [tcpout:primary_indexers] 20 | server = splunk:9997 21 | 22 | # If you do not have two (or more) indexers, you must use the single stanza 23 | # configuration, which looks like this: 24 | #[tcpout-server://:] 25 | # = 26 | 27 | # If setting compressed=true, this must also be set on the indexer. 28 | # compressed = true 29 | 30 | # INDEXER DISCOVERY (ASK THE CLUSTER MASTER WHERE THE INDEXERS ARE) 31 | 32 | # This particular setting identifies the tag to use for talking to the 33 | # specific cluster master, like the "primary_indexers" group tag here. 34 | # indexerDiscovery = clustered_indexers 35 | 36 | # It's OK to have a tcpout group like the one above *with* a server list; 37 | # these will act as a seed until communication with the master can be 38 | # established, so it's a good idea to have at least a couple of indexers 39 | # listed in the tcpout group above. 40 | 41 | # [indexer_discovery:clustered_indexers] 42 | # pass4SymmKey = 43 | # This must include protocol and port like the example below. 44 | # master_uri = https://master.example.com:8089 45 | 46 | # SSL SETTINGS 47 | 48 | # sslCertPath = $SPLUNK_HOME/etc/auth/server.pem 49 | # sslRootCAPath = $SPLUNK_HOME/etc/auth/ca.pem 50 | # sslPassword = password 51 | # sslVerifyServerCert = true 52 | 53 | # COMMON NAME CHECKING - NEED ONE STANZA PER INDEXER 54 | # The same certificate can be used across all of them, but the configuration 55 | # here requires these settings to be per-indexer, so the same block of 56 | # configuration would have to be repeated for each. 57 | # [tcpout-server://10.1.12.112:9997] 58 | # sslCertPath = $SPLUNK_HOME/etc/certs/myServerCertificate.pem 59 | # sslRootCAPath = $SPLUNK_HOME/etc/certs/myCAPublicCertificate.pem 60 | # sslPassword = server_privkey_password 61 | # sslVerifyServerCert = true 62 | # sslCommonNameToCheck = servername 63 | # sslAltNameToCheck = servername -------------------------------------------------------------------------------- /package/default/data/ui/nav/default.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /package/default/data/ui/alerts/jira_service_desk_replay.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 6 | 7 | Select the account from the dropdown 8 | 9 |
10 |
11 |
12 | 13 |
14 | 15 |
16 |
17 |
18 | 19 |
20 | 21 |
22 |
23 |
24 | 25 |
26 | 27 |
28 |
29 |
30 | 31 |
32 | 33 |
34 |
35 |
36 | 37 |
38 | 39 |
40 |
41 |
42 | 43 |
44 | 45 |
46 |
47 |
48 | 49 |
50 | 51 |
52 |
53 |
-------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. TA-jira-service-desk-simple-addon documentation master file, created by 2 | sphinx-quickstart on Tue Sep 18 23:25:46 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to the Splunk Add-on for JIRA Atlassian Service Desk application documentation 7 | ====================================================================================== 8 | 9 | .. admonition:: **The Splunk Add-on for JIRA Atlassian Service Desk provides alerts action for JIRA issues creation:** 10 | 11 | - Trigger JIRA issue creation from Splunk core alerts and Enterprise Security correlation searches 12 | - Dynamic retrieval per JIRA project for types of issues and priority 13 | - Dynamic assignment of priority (optional) 14 | - Dynamic and/or static assignment of summary, description, assignee and labels 15 | - Custom fields full capabilities via the embedded custom field structure in alerts (optional) 16 | - Deduplication feature workflow with bi-directional integration, allows detecting a duplication issue creation request, and adding new comments automatically instead of creating duplicated issues 17 | - Auto close JIRA issues based on pattern detection (state aware alerting) 18 | - Attaching Splunk alert results to the JIRA issue in CSV, JSON or XLSXformat 19 | - Resilient store JIRA issue creation, shall a JIRA issue fails to be created, the resilient workflow handles automatic retries with a resilient policy 20 | - Monitoring of JIRA issue workflow via the embedded Overview dashboard and out of the box alerts 21 | - Get any information from JIRA via the REST API custom command wrapper, generate and index to summary events or the metric store issues statistics per projects 22 | 23 | .. image:: img/screenshot.png 24 | :alt: screenshot.png 25 | :align: center 26 | :width: 1200px 27 | :class: with-border 28 | 29 | .. image:: img/screenshot_projects.png 30 | :alt: screenshot_projects.png 31 | :align: center 32 | :width: 1200px 33 | :class: with-border 34 | 35 | .. image:: img/screenshot_api.png 36 | :alt: screenshot_api.png 37 | :align: center 38 | :width: 1200px 39 | :class: with-border 40 | 41 | .. image:: img/screenshot1.png 42 | :alt: screenshot1.png 43 | :align: center 44 | :width: 700px 45 | :class: with-border 46 | 47 | Overview: 48 | ========= 49 | 50 | .. toctree:: 51 | :maxdepth: 2 52 | 53 | about 54 | compatibility 55 | support 56 | download 57 | 58 | Deployment and configuration: 59 | ============================= 60 | 61 | .. toctree:: 62 | :maxdepth: 2 63 | 64 | deployment 65 | configuration 66 | 67 | User guide: 68 | =========== 69 | 70 | .. toctree:: 71 | :maxdepth: 2 72 | 73 | userguide 74 | 75 | Troubleshoot: 76 | ============= 77 | 78 | .. toctree:: 79 | :maxdepth: 1 80 | 81 | troubleshoot 82 | 83 | Versions and build history: 84 | =========================== 85 | 86 | .. toctree:: 87 | :maxdepth: 1 88 | 89 | releasenotes.rst 90 | -------------------------------------------------------------------------------- /package/default/searchbnf.conf: -------------------------------------------------------------------------------- 1 | # searchbnf.conf 2 | 3 | [jirafill-command] 4 | syntax = | jirafill target= 5 | description = \ 6 | This command is a REST API wrapper for JIRA used to populate dropdowns for the alert action creation. 7 | Syntax: \ 8 | | jirafill account= opt=<1 to get projects, 2 to get issue types, 3 to get priorities> 9 | comment1 = \ 10 | This example retrieves the list of projects available in JIRA 11 | example1 = \ 12 | | jirafill account=LAB opt=1 13 | shortdesc = REST API wrapper for JIRA, used to \ 14 | populate dropdowns for the alert action creation. 15 | usage = public 16 | tags = jirafill 17 | 18 | [jirarest-command] 19 | syntax = | jirarest account= target= method= json_request= 20 | description = \ 21 | This command is a REST API wrapper for JIRA API endpoints, it allows performing \ 22 | get HTTP calls against an endpoint and returns a JSON format answer. \ 23 | method defaults to GET if not specified, json_request is required for POST and PUT.\ 24 | Syntax: \ 25 | | jirarest account= target= method= json_request= 26 | comment1 = \ 27 | This example retrieves information from JIRA using a REST endpoint 28 | example1 = \ 29 | | jirarest account=LAB target="rest/api/2/myself" method="GET" 30 | shortdesc = REST API wrapper for JIRA, allows performing \ 31 | get HTTP calls against an endpoint. 32 | usage = public 33 | tags = jirarest 34 | 35 | [getjirakv-command] 36 | syntax = | getjirakv verify= 37 | description = \ 38 | This command is used to load the JIRA replay KVstore content. \ 39 | Its purpose is to handle distributed setup of the JIRA Add-on and \ 40 | publication failures automatic re-attempts.\ 41 | Syntax: \ 42 | | getjirakv 43 | comment1 = \ 44 | This example retrieves the content of the replay KVstore from a local or remote storage 45 | example1 = \ 46 | | getjirakv verify=False 47 | shortdesc = Load the JIRA replay KVstore content 48 | usage = public 49 | tags = getjirakv 50 | 51 | [jiraoverview-command] 52 | description = This command retrieves main Jira issues KPIs for all projects configured for all accounts \ 53 | Syntax: | jiraoverview 54 | comment1 = \ 55 | This example retrieves KPIs for Jira issues for all projects and all configured accounts 56 | example1 = \ 57 | | jiraoverview 58 | shortdesc = Get Jira issues KPIs 59 | usage = public 60 | tags = jiraoverview 61 | 62 | [jirajsonexpand-command] 63 | description = This streaming command takes a JSON input and export its records \ 64 | Syntax: | jirajsonexpand input= subinput= 65 | comment1 = \ 66 | This streaming command takes a JSON input and export its records 67 | example1 = \ 68 | | jirarest account="myaccount" target="/rest/api/3/search" method="GET" | jirajsonexpand input="_raw" subinput="issues" 69 | shortdesc = A simple JSON expand command 70 | usage = public 71 | tags = jirajsonexpand 72 | -------------------------------------------------------------------------------- /docs/deployment.rst: -------------------------------------------------------------------------------- 1 | Deployment & Requirements 2 | ######################### 3 | 4 | Deployment matrix 5 | ================= 6 | 7 | +----------------------+---------------------+ 8 | | Splunk roles | required | 9 | +======================+=====================+ 10 | | Search head | yes | 11 | +----------------------+---------------------+ 12 | | Indexer tiers | no | 13 | +----------------------+---------------------+ 14 | 15 | If Splunk search heads are running in Search Head Cluster (SHC), the Splunk application must be deployed by the SHC deployer. 16 | 17 | Dependencies 18 | ============ 19 | 20 | There are currently no dependencies for the application, but as with any Splunk modular action, the Splunk CIM application should be installed on the search heads. (``Splunk_SA_CIM``) 21 | 22 | Make sure you have declared the ``cim_modactions`` index as the Add-on logs would automatically be directed to this index is the SA CIM application is installed on the search heads. 23 | 24 | If the Splunk_SA_CIM is not installed, the Add-on logs will be generated in the ``_internal`` index. (This is a normal behaviour for Add-on developped with the Splunk Add-on builder that provide adaptive response capabilities) 25 | 26 | Role Based Access Control (RBAC) 27 | ================================ 28 | 29 | Since the release 2.1.0, the JIRA application leverages a least privilege approach using its internal REST API, this allows you to allow users to access and use the alert actions with no other capabilities than the builtin capability ``jira_service_desk``. 30 | 31 | **How things work:** 32 | 33 | - The application defines a capability called ``jira_service_desk``. 34 | - This capability is enabled in the builtin role ``jira_alert_action``. 35 | - The builtin role ``jira_alert_action`` is automatically inherited for the ``admin`` and ``sc_admin`` roles. 36 | - When calling the action, the backend underneath automatically call the JIRA App REST endpoints which access is constrained by the ``jira_service_desk`` capability. 37 | - These endpoints provide the necessary information to the JIRA App to allow the alert actions to work. 38 | 39 | **How to allow normal users to use the alert actions:** 40 | 41 | - To allow normal users to use the alert actions, you can directly inherit the ``jira_alert_action`` role in their role definition. 42 | - Alertnatively, You can also natively add the ``jira_service_desk`` capability to the existing roles. 43 | - Both approaches are equivalent. 44 | 45 | **What does provide the builtin jira_service_desk capability and the jira_alert_action role:** 46 | 47 | - The capability ``jira_service_desk`` and the associated role provide **nothing** except the access to the JIRA App REST endpoints, allowing the alert actions to work. 48 | 49 | 50 | Initial deployment 51 | ================== 52 | 53 | **The deployment of the Splunk application is very straight forward:** 54 | 55 | - Using the application manager in Splunk Web (Settings / Manages apps) 56 | 57 | - Extracting the content of the tgz archive in the "apps" directory of Splunk 58 | 59 | - For SHC configurations (Search Head Cluster), extract the tgz content in the SHC deployer and publish the SHC bundle 60 | 61 | Upgrades 62 | ======== 63 | 64 | Upgrading the Splunk application is pretty much the same operation than the initial deployment. 65 | -------------------------------------------------------------------------------- /Docker-env-testing/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | # JIRA Software 4 | # When the container is up, access using the SSL proxy (see bellow), this will bring you to an Atlasian page where you can enable a trial 5 | # When the trial end of life is reached, make sure to stop the container, purge the content of the jira-volume directory, start the container again and re-process the configuration 6 | jira: 7 | image: atlassian/jira-software 8 | hostname: jiraback 9 | restart: unless-stopped 10 | user: "root" 11 | environment: 12 | - ATL_PROXY_NAME=localhost 13 | - ATL_TOMCAT_SECURE=true 14 | - ATL_PROXY_PORT=8081 15 | - ATL_TOMCAT_SCHEME=https 16 | volumes: 17 | - jiradata:/var/atlassian/application-data/jira 18 | ports: 19 | - "8080:8080" 20 | 21 | # Nginx acting as the SSL proxy to JIRA 22 | # To configure the jira instance in Splunk, you will use nginx:8081 with SSL verify false 23 | # Locally on the docker guest, you can access JIRA by: https://localhost:8081 24 | nginx: 25 | image: nginx:latest 26 | restart: unless-stopped 27 | hostname: jira 28 | volumes: 29 | - ./nginx/default.conf:/etc/nginx/conf.d/default.conf 30 | - ./nginx/jira.cert:/etc/nginx/cert/jira.cert 31 | - ./nginx/jira.key:/etc/nginx/cert/jira.key 32 | ports: 33 | - 8081:8081 34 | - 443:443 35 | 36 | # To test and verify the TA capabilities with a proxy, in the TA configuration, you can configure the proxy with: 37 | # Enable: Yes 38 | # Host: proxy 39 | # Port: 3128 40 | # User: proxy_user 41 | # Password: ch@ngeM3 42 | # Auth see: https://veesp.com/blog/squid-authentication/ 43 | proxy: 44 | image: sameersbn/squid:3.5.27-2 45 | restart: unless-stopped 46 | hostname: proxy 47 | volumes: 48 | - ./squid/squid.conf:/etc/squid/squid.conf 49 | - ./squid/squidusers:/etc/squid/squidusers 50 | ports: 51 | - 3128:3128 52 | 53 | # Same, with a socks5 proxy 54 | # Host: proxysocks 55 | # Port: 1080 56 | # User: proxy_user 57 | # Password: proxy_password 58 | 59 | proxysocks: 60 | image: serjs/go-socks5-proxy 61 | restart: unless-stopped 62 | hostname: proxysocks 63 | environment: 64 | - PROXY_USER=proxy_user 65 | - PROXY_PASSWORD=proxy_password 66 | ports: 67 | - 1080:1080 68 | 69 | # Splunk 70 | # Access Splunk on the Docker guest with: https://localhost:8000 71 | splunk: 72 | image: splunk/splunk:latest 73 | hostname: splunk 74 | ports: 75 | - "8000:8000" 76 | - "8089:8089" 77 | - "9997:9997" 78 | - "8088:8088" 79 | environment: 80 | SPLUNK_START_ARGS: "--accept-license" 81 | SPLUNK_PASSWORD: "ch@ngeM3" 82 | volumes: 83 | - ./TA-config:/opt/splunk/etc/apps/TA-config 84 | - ./TA-jira-unit-tests:/opt/splunk/etc/apps/TA-jira-unit-tests 85 | - ./splunk-share-folder:/tmp/splunk-share-folder 86 | 87 | # Splunk HF 88 | # Access Splunk on the Docker guest with: https://localhost:8001 89 | # Use to simulate a distributed setup where the JIRA actions are achieved via a different host 90 | splunk_hf: 91 | image: splunk/splunk:latest 92 | hostname: splunk_hf 93 | ports: 94 | - "8001:8000" 95 | - "8089" 96 | environment: 97 | SPLUNK_START_ARGS: "--accept-license" 98 | SPLUNK_PASSWORD: "ch@ngeM3" 99 | volumes: 100 | - ./TA-config-hf:/opt/splunk/etc/apps/TA-config-hf 101 | 102 | volumes: 103 | jiradata: {} 104 | -------------------------------------------------------------------------------- /package/bin/ta_service_desk_simple_addon_rh_account.py: -------------------------------------------------------------------------------- 1 | import import_declare_test 2 | 3 | from splunktaucclib.rest_handler.endpoint import ( 4 | field, 5 | validator, 6 | RestModel, 7 | SingleModel, 8 | ) 9 | from splunktaucclib.rest_handler import admin_external, util 10 | from ta_service_desk_simple_addon_rh_account_handler import ( 11 | CustomRestHandlerCreateRemoteAccount, 12 | ) 13 | import logging 14 | 15 | util.remove_http_proxy_env_vars() 16 | 17 | 18 | special_fields = [ 19 | field.RestField( 20 | "name", 21 | required=True, 22 | encrypted=False, 23 | default=None, 24 | validator=validator.AllOf( 25 | validator.String( 26 | max_len=50, 27 | min_len=1, 28 | ), 29 | validator.Pattern( 30 | regex=r"""^[a-zA-Z]\w*$""", 31 | ), 32 | ), 33 | ) 34 | ] 35 | 36 | fields = [ 37 | field.RestField( 38 | "jira_passthrough_account", 39 | required=False, 40 | encrypted=False, 41 | default="0", 42 | validator=None, 43 | ), 44 | field.RestField( 45 | "jira_url", 46 | required=False, 47 | encrypted=False, 48 | default=None, 49 | validator=validator.Pattern( 50 | regex=r"""^https?://.*""", 51 | ), 52 | ), 53 | field.RestField( 54 | "username", 55 | required=False, 56 | encrypted=False, 57 | default=None, 58 | validator=validator.Pattern( 59 | regex=r"""^.*$""", 60 | ), 61 | ), 62 | field.RestField( 63 | "password", 64 | required=False, 65 | encrypted=True, 66 | default=None, 67 | validator=validator.Pattern( 68 | regex=r"""^.*$""", 69 | ), 70 | ), 71 | field.RestField( 72 | "configuration_help_link", 73 | required=False, 74 | encrypted=False, 75 | default=None, 76 | validator=None, 77 | ), 78 | field.RestField( 79 | "jira_auth_mode", 80 | required=False, 81 | encrypted=False, 82 | default="basic", 83 | validator=None, 84 | ), 85 | field.RestField( 86 | "using_api_token_help_link", 87 | required=False, 88 | encrypted=False, 89 | default=None, 90 | validator=None, 91 | ), 92 | field.RestField( 93 | "using_pat_help_link", 94 | required=False, 95 | encrypted=False, 96 | default=None, 97 | validator=None, 98 | ), 99 | field.RestField( 100 | "jira_ssl_certificate_path", 101 | required=False, 102 | encrypted=False, 103 | default=None, 104 | validator=validator.Pattern( 105 | regex=r"""^.*$""", 106 | ), 107 | ), 108 | field.RestField( 109 | "jira_ssl_certificate_pem", 110 | required=False, 111 | encrypted=False, 112 | default=None, 113 | validator=validator.Pattern( 114 | regex=r"""^.*$""", 115 | ), 116 | ), 117 | field.RestField( 118 | "ssl_help_link", required=False, encrypted=False, default=None, validator=None 119 | ), 120 | ] 121 | model = RestModel(fields, name=None, special_fields=special_fields) 122 | 123 | 124 | endpoint = SingleModel( 125 | "ta_service_desk_simple_addon_account", model, config_name="account" 126 | ) 127 | 128 | 129 | if __name__ == "__main__": 130 | logging.getLogger().addHandler(logging.NullHandler()) 131 | admin_external.handle( 132 | endpoint, 133 | handler=CustomRestHandlerCreateRemoteAccount, 134 | ) 135 | -------------------------------------------------------------------------------- /Docker-env-testing/default.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ansible_connection: local 3 | ansible_environment: {} 4 | ansible_post_tasks: [] 5 | ansible_pre_tasks: [] 6 | cert_prefix: https 7 | config: 8 | baked: default.yml 9 | defaults_dir: /tmp/defaults 10 | env: 11 | headers: null 12 | var: SPLUNK_DEFAULTS_URL 13 | verify: true 14 | host: 15 | headers: null 16 | url: null 17 | verify: true 18 | max_delay: 60 19 | max_retries: 3 20 | max_timeout: 1200 21 | dmc_asset_interval: 3,18,33,48 * * * * 22 | dmc_forwarder_monitoring: false 23 | docker: true 24 | es_ssl_enablement: --ssl_enablement auto 25 | hide_password: false 26 | java_download_url: null 27 | java_update_version: null 28 | java_version: null 29 | retry_delay: 6 30 | retry_num: 60 31 | shc_sync_retry_num: 60 32 | splunk: 33 | admin_user: admin 34 | allow_upgrade: true 35 | app_paths: 36 | default: /opt/splunk/etc/apps 37 | deployment: /opt/splunk/etc/deployment-apps 38 | httpinput: /opt/splunk/etc/apps/splunk_httpinput 39 | idxc: /opt/splunk/etc/master-apps 40 | shc: /opt/splunk/etc/shcluster/apps 41 | apps_location_local: [] 42 | appserver: 43 | port: 8065 44 | asan: false 45 | auxiliary_cluster_masters: [] 46 | build_url_bearer_token: null 47 | cluster_master_url: null 48 | connection_timeout: 0 49 | declarative_admin_password: false 50 | deployer_url: null 51 | deployment_client: 52 | name: null 53 | dfs: 54 | dfc_num_slots: 4 55 | dfw_num_slots: 10 56 | dfw_num_slots_enabled: false 57 | enable: false 58 | port: 9000 59 | spark_master_host: 127.0.0.1 60 | spark_master_webui_port: 8080 61 | disable_popups: false 62 | dsp: 63 | cert: null 64 | enable: false 65 | pipeline_desc: null 66 | pipeline_name: null 67 | pipeline_spec: null 68 | server: forwarders.scp.splunk.com:9997 69 | verify: false 70 | enable_service: false 71 | es: 72 | ssl_enablement: auto 73 | exec: /opt/splunk/bin/splunk 74 | group: splunk 75 | hec: 76 | cert: null 77 | enable: true 78 | password: null 79 | port: 8088 80 | ssl: true 81 | token: bd5b7e7a-aa04-4946-9522-2a95db1f721d 82 | home: /opt/splunk 83 | http_enableSSL: true 84 | http_enableSSL_cert: null 85 | http_enableSSL_privKey: null 86 | http_enableSSL_privKey_password: null 87 | http_port: 8000 88 | idxc: 89 | discoveryPass4SymmKey: FMKDGMK2QhdgLiAZQ8OMwoLDhsOfwoBdLMKGwollw7JjJw== 90 | label: idxc_label 91 | pass4SymmKey: FMKDGMK2QhdgLiAZQ8OMwoLDhsOfwoBdLMKGwollw7JjJw== 92 | replication_factor: 3 93 | replication_port: 9887 94 | search_factor: 3 95 | secret: FMKDGMK2QhdgLiAZQ8OMwoLDhsOfwoBdLMKGwollw7JjJw== 96 | ignore_license: false 97 | kvstore: 98 | port: 8191 99 | launch: {} 100 | license_download_dest: /tmp/splunk.lic 101 | license_master_url: "" 102 | multisite_master_port: 8089 103 | multisite_replication_factor_origin: 2 104 | multisite_replication_factor_total: 3 105 | multisite_search_factor_origin: 1 106 | multisite_search_factor_total: 3 107 | opt: /opt 108 | pass4SymmKey: null 109 | password: ch@ngeM3 110 | pid: /opt/splunk/var/run/splunk/splunkd.pid 111 | root_endpoint: null 112 | s2s: 113 | ca: null 114 | cert: null 115 | enable: true 116 | password: null 117 | port: 9997 118 | ssl: false 119 | search_head_captain_url: null 120 | secret: null 121 | service_name: null 122 | set_search_peers: true 123 | shc: 124 | deployer_push_mode: null 125 | label: shc_label 126 | pass4SymmKey: w5/DpMOvw7oPwqzDrsKecsO5RDzDkz91Yi0VMmvDicKLwoTDjQ== 127 | replication_factor: 3 128 | replication_port: 9887 129 | secret: w5/DpMOvw7oPwqzDrsKecsO5RDzDkz91Yi0VMmvDicKLwoTDjQ== 130 | smartstore: null 131 | ssl: 132 | ca: null 133 | cert: null 134 | enable: true 135 | password: null 136 | svc_port: 8089 137 | tar_dir: splunk 138 | user: splunk 139 | wildcard_license: false 140 | splunk_home_ownership_enforcement: true 141 | splunkbase_password: null 142 | splunkbase_token: null 143 | splunkbase_username: null 144 | wait_for_splunk_retry_num: 60 145 | -------------------------------------------------------------------------------- /TA-jira-unit-tests/default/macros.conf: -------------------------------------------------------------------------------- 1 | [jira_unit_test_gen_content] 2 | definition = makeresults\ 3 | | eval desc="This search looks for command-line arguments that from a user. Investigate whether these commands are legitimate."\ 4 | | eval _time=_time-((random() % 20) + 1)\ 5 | | eval time=strftime(_time, "%c")\ 6 | | eval src="WORKSTATIONTEST001"\ 7 | | eval user="acmeuser1"\ 8 | | eval count=1\ 9 | | eval process="C:\\Windows\\SysWOW64\\cmd.exe"\ 10 | | eval command_line="C:\\Users\\s17ifn4\\AppData\\Local\\Temp\\{FACEB1C6-5D12-40B0-A655-A82D6ACAC7D8}.bat"\ 11 | | eval process_sha256="79BEABE45C7CD6AC5510FD90EE526FE0"\ 12 | | eval parent_process="C:\\Windows\\syswow64\\MsiExec.exe -Embedding 79BEABE45C7CD6AC5510FD90EE526FE0 E Global\\MSI0000"\ 13 | | eval whitelist_link="[Add Rule to Whitelist|https://acme.splunkcloud.com/en-US/app/DarkFalcon/one_whitelist_to_rule_them_all?form.Computer=CHANGEME&form.User=CHANGEME&form.ParentProcess=CHANGEME&form.process=CHANGEME&form.CommandLine=CHANGEME&form.object_path=N/A&form.File_Path=N/A&form.Service_Start_Type=N/A&form.Hash=N/A&form.ParentCommandLine=N/A&form.registry_entry=N/A&form.Clients=N/A&form.Service_File_Name=N/A&form.Service_Type=N/A&form.DestinationIp=N/A&form.Name=N/A&form.Startup_Process=N/A&form.Target_Filename=N/A&form.Registry_Path=N/A&form.info=N/A&form.ProcessGuid=N/A&form.Service_Name=N/A&form.Image_EXE=N/A&form.EventType=N/A&form.Short_Message=N/A&form.SourceIp=N/A&form.base64=N/A&form.Sid=N/A&form.identity=N/A&form.file_create=N/A&form.Description=N/A&form.CurrentDirectory=N/A&form.file_name=N/A&form.ParentProcessId=N/A&form.input_reason=CHANGEME&form.Image=N/A]"\ 14 | | eval NotableLink="[Results Link|https://acme.splunkcloud.com:443/app/DA-ESS-ContentUpdate/search?q=%7Cloadjob%20scheduler__admin_REEtRVNTLUNvbnRlbnRVcGRhdGU__RMD5574beacbd5c8a000_at_1571174400_16062%20%7C%20head%203%20%7C%20tail%201&earliest=0&latest=now]"\ 15 | | eval test_singleline_text="I am a very bad guy \"my name is Batman\""\ 16 | | eval test_multiline_text="This is the test of custom field 10049, which is multiline"\ 17 | | eval test_url="https://wwww.google.co.uk/mysearchid01"\ 18 | | eval test_single_choice="Yes"\ 19 | | eval test_multi_choice_grp1="Web"\ 20 | | eval test_multi_choice_grp2="Email"\ 21 | | eval test_date=strftime(_time,"%Y-%m-%d")\ 22 | | eval test_datetime=strftime(_time,"%Y-%m-%dT%H:%M:%S.%3N%z")\ 23 | | eval test_number="40.92"\ 24 | | eval priority=if(match(test_singleline_text, "Batman"), "High", "Medium")\ 25 | \ 26 | | append [ makeresults\ 27 | | eval desc="This search looks for command-line arguments that from a user. Investigate whether these commands are legitimate."\ 28 | | eval _time=_time-((random() % 20) + 1)\ 29 | | eval time=strftime(_time, "%c")\ 30 | | eval src="WORKSTATIONTEST002"\ 31 | | eval user="acmeuser2"\ 32 | | eval count=2\ 33 | | eval process="C:\\Windows\\SysWOW64\\cmd.exe"\ 34 | | eval command_line="C:\\Users\\s17ifn4\\AppData\\Local\\Temp\\{FACEB1C6-5D12-40B0-A655-A82D6ACAC7D8}.bat"\ 35 | | eval process_sha256="79BEABE45C7CD6AC5510FD90EE526FE0"\ 36 | | eval parent_process="C:\\Windows\\syswow64\\MsiExec.exe -Embedding 79BEABE45C7CD6AC5510FD90EE526FE0 E Global\\MSI0000"\ 37 | | eval whitelist_link="[Add Rule to Whitelist|https://acme.splunkcloud.com/en-US/app/DarkFalcon/one_whitelist_to_rule_them_all?form.Computer=CHANGEME&form.User=CHANGEME&form.ParentProcess=CHANGEME&form.process=CHANGEME&form.CommandLine=CHANGEME&form.object_path=N/A&form.File_Path=N/A&form.Service_Start_Type=N/A&form.Hash=N/A&form.ParentCommandLine=N/A&form.registry_entry=N/A&form.Clients=N/A&form.Service_File_Name=N/A&form.Service_Type=N/A&form.DestinationIp=N/A&form.Name=N/A&form.Startup_Process=N/A&form.Target_Filename=N/A&form.Registry_Path=N/A&form.info=N/A&form.ProcessGuid=N/A&form.Service_Name=N/A&form.Image_EXE=N/A&form.EventType=N/A&form.Short_Message=N/A&form.SourceIp=N/A&form.base64=N/A&form.Sid=N/A&form.identity=N/A&form.file_create=N/A&form.Description=N/A&form.CurrentDirectory=N/A&form.file_name=N/A&form.ParentProcessId=N/A&form.input_reason=CHANGEME&form.Image=N/A]"\ 38 | | eval NotableLink="[Results Link|https://acme.splunkcloud.com:443/app/DA-ESS-ContentUpdate/search?q=%7Cloadjob%20scheduler__admin_REEtRVNTLUNvbnRlbnRVcGRhdGU__RMD5574beacbd5c8a000_at_1571174400_16062%20%7C%20head%203%20%7C%20tail%201&earliest=0&latest=now]"\ 39 | | eval test_singleline_text="I am a very bad guy \"my name is Robin\""\ 40 | | eval test_multiline_text="This is the test of custom field 10049, which is multiline"\ 41 | | eval test_url="https://wwww.google.co.uk/mysearchid02"\ 42 | | eval test_single_choice="No"\ 43 | | eval test_multi_choice_grp1="Web"\ 44 | | eval test_multi_choice_grp2="Email"\ 45 | | eval test_date=strftime(_time,"%Y-%m-%d")\ 46 | | eval test_datetime=strftime(_time,"%Y-%m-%dT%H:%M:%S.%3N%z")\ 47 | | eval test_number="67.23"\ 48 | | eval priority=if(match(test_singleline_text, "Batman"), "High", "Medium") ] 49 | iseval = 0 50 | -------------------------------------------------------------------------------- /package/bin/jirajsonexpand.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | import os 5 | import sys 6 | import time 7 | import json 8 | import logging 9 | from logging.handlers import RotatingFileHandler 10 | import urllib3 11 | 12 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 13 | 14 | splunkhome = os.environ["SPLUNK_HOME"] 15 | 16 | # set logging 17 | filehandler = RotatingFileHandler( 18 | f"{splunkhome}/var/log/splunk/ta_jira_jsonexpand.log", 19 | mode="a", 20 | maxBytes=10000000, 21 | backupCount=1, 22 | ) 23 | formatter = logging.Formatter( 24 | "%(asctime)s %(levelname)s %(filename)s %(funcName)s %(lineno)d %(message)s" 25 | ) 26 | filehandler.setFormatter(formatter) 27 | log = logging.getLogger() # root logger - Good to get it only once. 28 | for hdlr in log.handlers[:]: # remove the existing file handlers 29 | if isinstance(hdlr, logging.FileHandler): 30 | log.removeHandler(hdlr) 31 | log.addHandler(filehandler) # set the new handler 32 | # set the log level to INFO, DEBUG as the default is ERROR 33 | log.setLevel(logging.INFO) 34 | 35 | sys.path.append( 36 | os.path.join(splunkhome, "etc", "apps", "TA-jira-service-desk-simple-addon", "lib") 37 | ) 38 | 39 | # import Splunk libs 40 | from splunklib.searchcommands import ( 41 | dispatch, 42 | StreamingCommand, 43 | Configuration, 44 | Option, 45 | validators, 46 | ) 47 | from splunklib import six 48 | import splunklib.client as client 49 | from ta_jira_libs import jira_get_conf 50 | 51 | 52 | @Configuration() 53 | class TrackMePrettyJson(StreamingCommand): 54 | """ 55 | A Splunk streaming command that expands and pretty-prints JSON data from JIRA responses. 56 | This command is particularly useful for processing JIRA API responses that contain nested JSON structures. 57 | 58 | The command: 59 | - Takes a JSON input field 60 | - Expands a specified subfield containing nested JSON 61 | - Pretty-prints the expanded JSON 62 | - Streams the results for further processing 63 | """ 64 | 65 | input = Option( 66 | doc=""" 67 | **Syntax:** **input=**** 68 | **Description:** The fields containing the input to be expanded.""", 69 | require=False, 70 | default="_raw", 71 | validate=validators.Match("input", r"^.*$"), 72 | ) 73 | 74 | subinput = Option( 75 | doc=""" 76 | **Syntax:** **subinput=**** 77 | **Description:** The fields containing the subinput to be expanded.""", 78 | require=False, 79 | default="issues", 80 | validate=validators.Match("subinput", r"^.*$"), 81 | ) 82 | 83 | # status will be statically defined as imported 84 | 85 | def stream(self, records): 86 | """ 87 | Processes and expands JSON records from the input stream. 88 | 89 | This method: 90 | 1. Retrieves the JIRA configuration 91 | 2. Sets up logging 92 | 3. For each record in the input: 93 | - Parses the JSON from the specified input field 94 | - Extracts the specified subfield 95 | - Pretty-prints each item in the subfield 96 | - Yields the expanded results 97 | 98 | The method handles: 99 | - JSON parsing and validation 100 | - Error handling and logging 101 | - Pretty-printing of JSON output 102 | 103 | Args: 104 | records: An iterable of input records 105 | 106 | Yields: 107 | dict: A dictionary containing: 108 | - _time: The timestamp of processing 109 | - _raw: The pretty-printed JSON string 110 | 111 | Raises: 112 | Exception: If JSON parsing or processing fails 113 | """ 114 | # get conf 115 | jira_conf = jira_get_conf( 116 | self._metadata.searchinfo.session_key, self._metadata.searchinfo.splunkd_uri 117 | ) 118 | 119 | # set loglevel 120 | log.setLevel(jira_conf["logging"]["loglevel"]) 121 | 122 | # Loop, expand and yield 123 | count = 0 124 | 125 | for record in records: 126 | submainrecord = json.loads(record.get(self.input)) 127 | logging.debug(f'subrecords="{submainrecord.get(self.subinput)}"') 128 | 129 | for subrecord in submainrecord.get(self.subinput): 130 | logging.debug(f'subrecord="{subrecord}"') 131 | count += 1 132 | 133 | try: 134 | 135 | # yield 136 | yield { 137 | "_time": time.time(), 138 | "_raw": json.dumps(subrecord, indent=2), 139 | } 140 | 141 | logging.info( 142 | f'jirajsonexpand terminated successfully, results_count="{count}"' 143 | ) 144 | 145 | except Exception as e: 146 | logging.error( 147 | f'jirajsonexpand command failed with exception="{str(e)}"' 148 | ) 149 | raise Exception( 150 | f'jirajsonexpand command failed with exception="{str(e)}"' 151 | ) 152 | 153 | 154 | dispatch(TrackMePrettyJson, sys.argv, sys.stdin, sys.stdout, __name__) 155 | -------------------------------------------------------------------------------- /docs/troubleshoot.rst: -------------------------------------------------------------------------------- 1 | Trouble shooting 2 | ################ 3 | 4 | Connectivity to JIRA issues 5 | =========================== 6 | 7 | **Run the following command to verify the connectivity for each configured Jira accounts, also available as a report in the menu Get JIRA Info:** 8 | 9 | :: 10 | 11 | | jirafill account=_all opt=0 12 | 13 | .. image:: img/config_check_connectivity.png 14 | :alt: config_check_connectivity.png 15 | :align: center 16 | :width: 1200px 17 | :class: with-border 18 | 19 | If the connectivity fails for an account, for instance due to an authentication failure or due to network connectivity issues, the command returns the reason and response. 20 | 21 | If the connectivity to JIRA is not valid for some reasons (bad credentials, network connecttivity, etc), this will result in different Python error messages when attempting to load any of the report, load the alert action page or execute an alert action. 22 | 23 | In such as case, the easiest way is to validate the connectivity by achieving a rest cal using the curl command in CLI, ideally in any of the search head supposed to be using the alert action. (note: this step is valid for Linux only) 24 | 25 | :: 26 | 27 | curl -k https:///rest/api/latest/project --user : 28 | 29 | For more information, follow these links: 30 | 31 | - https://developer.atlassian.com/server/jira/platform/basic-authentication 32 | - https://developer.atlassian.com/cloud/jira/service-desk/basic-auth-for-rest-apis 33 | 34 | Overview dashboard and Add-on logs 35 | ================================== 36 | 37 | **The Splunk Add-on for JIRA Service Desk provides a builtin Overview dashboard that gives deep insights on the Add-on activity:** 38 | 39 | .. image:: img/screenshot.png 40 | :alt: screenshot.png 41 | :align: center 42 | :width: 1200px 43 | :class: with-border 44 | 45 | The dashboard exposes the JIRA issue workflow and direct links to access the Add-on logs. 46 | 47 | Custom command logs 48 | ------------------- 49 | 50 | **All custom commands generate logs into their own dedicated log files, which are indexed automatically in Splunk:** 51 | 52 | :: 53 | 54 | index=_internal sourcetype=jira:custom_commands:* 55 | 56 | .. image:: img/troubleshoot_custom_commands.png 57 | :alt: troubleshoot_custom_commands.png 58 | :align: center 59 | :width: 1200px 60 | :class: with-border 61 | 62 | Add-on logs for first REST call attempts 63 | ---------------------------------------- 64 | 65 | **When the alert action is triggered, the Add-on records its activity in:** 66 | 67 | :: 68 | 69 | (index="_internal" OR index="cim_modactions") (source="*jira_service_desk_modalert.log") 70 | 71 | When the JIRA issue is successfully achieved, the key sentence ``JIRA Service Desk ticket successfully created`` is logged. 72 | 73 | If an error is encountered during the API call, the key sentence ``JIRA Service Desk ticket creation has failed`` is logged. 74 | 75 | When the failure step is reached, for example if there is an issue with the credentials or reaching the JIRA instance, the workflow records the failure in a resilient store based on a KVstore lookup: 76 | 77 | :: 78 | 79 | | inputlookup jira_failures_replay | eval uuid=_key 80 | 81 | At this point, any failed call recorded in the KVstore is automatically re-attempted by the scheduled alert named: ``JIRA Service Desk - Resilient store Tracker`` 82 | 83 | An out of box alert named ``JIRA Service Desk - detection of temporary issue creation failure`` is provided to monitor and track any JIRA failure, **the alert is by default enabled**. 84 | 85 | Add-on logs for the resilient store feature 86 | ------------------------------------------- 87 | 88 | **The resilient store feature tracks its activity in:** 89 | 90 | :: 91 | 92 | (index="_internal" OR index="cim_modactions") (source="*jira_service_desk_replay_modalert.log") 93 | 94 | In normal circumstances, which means there have not been recent failed attempts, there would be no activity in this logs, nor content in the KVstore. 95 | 96 | If a record exists in the KVstore, the Add-on will re-attempt the creation every 5 minutes during 3 days per record, if it continuously failed durant that period, a key sentence ``permanent failure!`` is logged. 97 | 98 | An out of box alert named ``JIRA Service Desk - detection of permanent issue creation failure`` is provided to monitor and track permanent JIRA failures, **the alert is by default enabled**. 99 | 100 | After 7 days in the KVstore, a record is automatically and definitively purged. 101 | 102 | Add-on logs for the internal REST API endpoints 103 | ----------------------------------------------- 104 | 105 | **The application implements a REST API for different purposes, notably allowing a least privilege approach.** 106 | 107 | Access the logs: 108 | 109 | :: 110 | 111 | index=_internal sourcetype=jira_service_desk:rest 112 | 113 | Root cause for failures 114 | ----------------------- 115 | 116 | **Root causes of failures will be clearly exposes in the Add-on logs, most common causes could be:** 117 | 118 | - JIRA credential issues (verify the connectivity, see the configuration page) 119 | - Networking issues or JIRA instance not reachable 120 | - Content issues such as JIRA fields not available on the JIRA project (make sure these fields are associated with the right JIRA screens) 121 | - Content issues such as JIRA field receiving an unexpected content or format (some JIRA fields such as date and date time inputs require a valid format, etc) 122 | 123 | **Shall a REST call for JIRA issue creation fail, the Add-on automatically logs full JSON data which you can use to easily review the data and trouble shoot the root causes.** 124 | -------------------------------------------------------------------------------- /package/default/data/ui/views/overview_jira_analytic.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | true 7 | account=" 8 | " 9 | 10 | stats count by account | sort 0 account 11 | 12 | account 13 | account 14 | 15 |
16 | 17 | 18 | | jiraoverview 19 | -5m 20 | now 21 | 22 | 23 | 24 | | search $tk_account$ 25 | 26 | | table account project type value 27 | | eval {type} = value 28 | | stats first(total_*) as "total_*" by account, project 29 | 30 | | appendpipe [ stats sum(total_issues) as total_issues, sum(total_to_do) as total_to_do, sum(total_done) as total_done, sum(pct_total_in_progress) as pct_total_in_progress | eval project="TOTAL" ] 31 | | fillnull value=0 32 | 33 | | eval pct_total_done="% " . round(total_done/total_issues*100, 2), pct_total_to_do="% " . round(total_to_do/total_issues*100, 2), pct_total_in_progress="% " . round(total_in_progress/total_issues*100, 2) 34 | | foreach pct_* [ eval <<FIELD>> = if(isnull('<<FIELD>>'), "% 0.00", '<<FIELD>>' ) ] 35 | 36 | | eval _time=now() | fields _time, project, pct_*, total_* 37 | 38 | 39 | 40 | 41 | 42 | 50 | 51 | 52 | 53 | 54 | 55 | stats count(eval(project!="TOTAL")) as count 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | where project="TOTAL" | fields total_issues 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | where project="TOTAL" | fields total_to_do 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | where project="TOTAL" | fields total_in_progress 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | where project="TOTAL" | fields total_done 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | | where project="TOTAL" | fields - _time | fields total_to_do total_in_progress total_done | rename total_to_do as "To Do", total_in_progress as "In Progress", total_done as "Done" | transpose | rename column as "status category", "row 1" as "number of issues" 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | fields - _time | rename project as Project, pct_total_done as "% Done", pct_total_in_progress as "% In Progress", pct_total_to_do as "% To Do", total_done as "# Done", total_in_progress as "# In Progress", total_issues as "# Total", total_to_do as "# To Do" 133 | 134 | 135 | 136 |
137 |
138 |
139 |
-------------------------------------------------------------------------------- /package/bin/ta_service_desk_simple_addon_rh_account_handler.py: -------------------------------------------------------------------------------- 1 | import import_declare_test 2 | from splunktaucclib.rest_handler.admin_external import AdminExternalHandler 3 | import json 4 | import os 5 | import sys 6 | import requests 7 | 8 | # get the SPLUNK_HOME environment variable 9 | splunkhome = os.environ["SPLUNK_HOME"] 10 | 11 | # add the lib directory to the sys.path 12 | sys.path.append( 13 | os.path.join(splunkhome, "etc", "apps", "TA-jira-service-desk-simple-addon", "lib") 14 | ) 15 | 16 | # import least privileges access libs 17 | from ta_jira_libs import ( 18 | jira_get_conf, 19 | ) 20 | 21 | 22 | class CustomRestHandlerCreateRemoteAccount(AdminExternalHandler): 23 | def __init__(self, *args, **kwargs): 24 | AdminExternalHandler.__init__(self, *args, **kwargs) 25 | 26 | def checkConnectivity(self): 27 | # get conf 28 | jira_conf = jira_get_conf(self.getSessionKey(), self.handler._splunkd_uri) 29 | 30 | # check if the account is a passthrough account, if so, skip the connectivity validation 31 | jira_passthrough_account = int(self.payload.get("jira_passthrough_account", 0)) 32 | if jira_passthrough_account == 1: 33 | return 34 | else: 35 | # check our mandatory fields, if none, raise an exception 36 | if not self.payload.get("jira_url"): 37 | raise Exception( 38 | "If not creating a passthrough account, JIRA URL is required" 39 | ) 40 | if not self.payload.get("jira_auth_mode"): 41 | raise Exception( 42 | "If not creating a passthrough account, JIRA Authentication mode is required" 43 | ) 44 | if not self.payload.get("username"): 45 | raise Exception( 46 | "If not creating a passthrough account, Username is required" 47 | ) 48 | if not self.payload.get("password"): 49 | raise Exception( 50 | "If not creating a passthrough account, Password or API token is required" 51 | ) 52 | 53 | # Call the validate_connection endpoint 54 | header = { 55 | "Authorization": f"Splunk {self.getSessionKey()}", 56 | "Content-Type": "application/json", 57 | } 58 | 59 | url = ( 60 | "%s/services/jira_service_desk/manager/validate_connection" 61 | % self.handler._splunkd_uri 62 | ) 63 | data = { 64 | "jira_url": self.payload.get("jira_url"), 65 | "auth_mode": self.payload.get("jira_auth_mode"), 66 | "username": self.payload.get("username"), 67 | "jira_password": self.payload.get("password"), 68 | "jira_ssl_certificate_path": self.payload.get("jira_ssl_certificate_path"), 69 | "jira_ssl_certificate_pem": self.payload.get("jira_ssl_certificate_pem"), 70 | } 71 | 72 | try: 73 | response = requests.post( 74 | url, 75 | headers=header, 76 | data=json.dumps(data, indent=1), 77 | verify=False, # local splunkd API 78 | timeout=300, 79 | proxies=None, # Never use proxy for local Splunk API calls 80 | ) 81 | 82 | # Parse the response 83 | try: 84 | response_data = response.json() 85 | except: 86 | response_data = None 87 | 88 | # If we have a response with error details, use those 89 | if response_data and isinstance(response_data, dict): 90 | if "payload" in response_data and isinstance( 91 | response_data["payload"], dict 92 | ): 93 | if "response" in response_data["payload"]: 94 | raise Exception(response_data["payload"]["response"]) 95 | elif "error" in response_data["payload"]: 96 | raise Exception(response_data["payload"]["error"]) 97 | elif "message" in response_data["payload"]: 98 | raise Exception(response_data["payload"]["message"]) 99 | 100 | # If we get a non-200 status code, raise an exception 101 | if response.status_code != 200: 102 | raise Exception(f"HTTP {response.status_code}: {response.text}") 103 | 104 | except requests.exceptions.RequestException as e: 105 | # If we have a response object, try to get the error details 106 | if hasattr(e, "response") and e.response is not None: 107 | try: 108 | error_data = e.response.json() 109 | if "payload" in error_data and isinstance( 110 | error_data["payload"], dict 111 | ): 112 | if "response" in error_data["payload"]: 113 | raise Exception(error_data["payload"]["response"]) 114 | elif "error" in error_data["payload"]: 115 | raise Exception(error_data["payload"]["error"]) 116 | elif "message" in error_data["payload"]: 117 | raise Exception(error_data["payload"]["message"]) 118 | except: 119 | if e.response.text: 120 | raise Exception(e.response.text) 121 | 122 | # If we couldn't extract a specific error message, use the original error 123 | raise Exception(f"Failed to connect to JIRA: {str(e)}") 124 | 125 | def handleList(self, confInfo): 126 | AdminExternalHandler.handleList(self, confInfo) 127 | 128 | def handleEdit(self, confInfo): 129 | self.checkConnectivity() 130 | AdminExternalHandler.handleEdit(self, confInfo) 131 | 132 | def handleCreate(self, confInfo): 133 | self.checkConnectivity() 134 | AdminExternalHandler.handleCreate(self, confInfo) 135 | 136 | def handleRemove(self, confInfo): 137 | AdminExternalHandler.handleRemove(self, confInfo) 138 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help 23 | help: 24 | @echo "Please use \`make ' where is one of" 25 | @echo " html to make standalone HTML files" 26 | @echo " dirhtml to make HTML files named index.html in directories" 27 | @echo " singlehtml to make a single large HTML file" 28 | @echo " pickle to make pickle files" 29 | @echo " json to make JSON files" 30 | @echo " htmlhelp to make HTML files and a HTML help project" 31 | @echo " qthelp to make HTML files and a qthelp project" 32 | @echo " applehelp to make an Apple Help Book" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | @echo " coverage to run coverage check of the documentation (if enabled)" 49 | 50 | .PHONY: clean 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | .PHONY: html 55 | html: 56 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 57 | @echo 58 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 59 | 60 | .PHONY: dirhtml 61 | dirhtml: 62 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 63 | @echo 64 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 65 | 66 | .PHONY: singlehtml 67 | singlehtml: 68 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 69 | @echo 70 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 71 | 72 | .PHONY: pickle 73 | pickle: 74 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 75 | @echo 76 | @echo "Build finished; now you can process the pickle files." 77 | 78 | .PHONY: json 79 | json: 80 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 81 | @echo 82 | @echo "Build finished; now you can process the JSON files." 83 | 84 | .PHONY: htmlhelp 85 | htmlhelp: 86 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 87 | @echo 88 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 89 | ".hhp project file in $(BUILDDIR)/htmlhelp." 90 | 91 | .PHONY: qthelp 92 | qthelp: 93 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 94 | @echo 95 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 96 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 97 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/DA-ITSI-TELEGRAF-OS.qhcp" 98 | @echo "To view the help file:" 99 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/DA-ITSI-TELEGRAF-OS.qhc" 100 | 101 | .PHONY: applehelp 102 | applehelp: 103 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 104 | @echo 105 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 106 | @echo "N.B. You won't be able to view it unless you put it in" \ 107 | "~/Library/Documentation/Help or install it in your application" \ 108 | "bundle." 109 | 110 | .PHONY: devhelp 111 | devhelp: 112 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 113 | @echo 114 | @echo "Build finished." 115 | @echo "To view the help file:" 116 | @echo "# mkdir -p $$HOME/.local/share/devhelp/DA-ITSI-TELEGRAF-OS" 117 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/DA-ITSI-TELEGRAF-OS" 118 | @echo "# devhelp" 119 | 120 | .PHONY: epub 121 | epub: 122 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 123 | @echo 124 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 125 | 126 | .PHONY: latex 127 | latex: 128 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 129 | @echo 130 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 131 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 132 | "(use \`make latexpdf' here to do that automatically)." 133 | 134 | .PHONY: latexpdf 135 | latexpdf: 136 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 137 | @echo "Running LaTeX files through pdflatex..." 138 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 139 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 140 | 141 | .PHONY: latexpdfja 142 | latexpdfja: 143 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 144 | @echo "Running LaTeX files through platex and dvipdfmx..." 145 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 146 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 147 | 148 | .PHONY: text 149 | text: 150 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 151 | @echo 152 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 153 | 154 | .PHONY: man 155 | man: 156 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 157 | @echo 158 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 159 | 160 | .PHONY: texinfo 161 | texinfo: 162 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 163 | @echo 164 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 165 | @echo "Run \`make' in that directory to run these through makeinfo" \ 166 | "(use \`make info' here to do that automatically)." 167 | 168 | .PHONY: info 169 | info: 170 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 171 | @echo "Running Texinfo files through makeinfo..." 172 | make -C $(BUILDDIR)/texinfo info 173 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 174 | 175 | .PHONY: gettext 176 | gettext: 177 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 178 | @echo 179 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 180 | 181 | .PHONY: changes 182 | changes: 183 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 184 | @echo 185 | @echo "The overview file is in $(BUILDDIR)/changes." 186 | 187 | .PHONY: linkcheck 188 | linkcheck: 189 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 190 | @echo 191 | @echo "Link check complete; look for any errors in the above output " \ 192 | "or in $(BUILDDIR)/linkcheck/output.txt." 193 | 194 | .PHONY: doctest 195 | doctest: 196 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 197 | @echo "Testing of doctests in the sources finished, look at the " \ 198 | "results in $(BUILDDIR)/doctest/output.txt." 199 | 200 | .PHONY: coverage 201 | coverage: 202 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 203 | @echo "Testing of coverage in the sources finished, look at the " \ 204 | "results in $(BUILDDIR)/coverage/python.txt." 205 | 206 | .PHONY: xml 207 | xml: 208 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 209 | @echo 210 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 211 | 212 | .PHONY: pseudoxml 213 | pseudoxml: 214 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 215 | @echo 216 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 217 | -------------------------------------------------------------------------------- /package/lib/jira_rest_handler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | import re 5 | import json 6 | from urllib.parse import urlparse 7 | import urllib3 8 | 9 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 10 | 11 | from splunk.persistconn.application import PersistentServerConnectionApplication 12 | 13 | 14 | class RequestInfo(object): 15 | """ 16 | This represents the request. 17 | """ 18 | 19 | def __init__( 20 | self, 21 | user, 22 | session_key, 23 | system_authtoken, 24 | server_rest_uri, 25 | server_rest_host, 26 | server_rest_port, 27 | server_hostname, 28 | server_servername, 29 | connection_src_ip, 30 | connection_listening_port, 31 | method, 32 | path, 33 | query, 34 | raw_args, 35 | ): 36 | self.user = user 37 | self.session_key = session_key 38 | self.system_authtoken = system_authtoken 39 | self.server_rest_uri = server_rest_uri 40 | self.server_rest_host = server_rest_host 41 | self.server_rest_port = server_rest_port 42 | self.server_hostname = server_hostname 43 | self.server_servername = server_servername 44 | self.connection_src_ip = connection_src_ip 45 | self.connection_listening_port = connection_listening_port 46 | self.method = method 47 | self.path = path 48 | self.query = query 49 | self.raw_args = raw_args 50 | 51 | 52 | class RESTHandler(PersistentServerConnectionApplication): 53 | """ 54 | This is a REST handler base-class that makes implementing a REST handler easier. 55 | 56 | This works by resolving a name based on the path in the HTTP request and calls it. This class 57 | will look for a function that includes the HTTP verb followed by the path.abs 58 | 59 | For example, if a GET request is made to the endpoint is executed with the path 60 | /lookup_edit/lookup_contents, then this class will attempt to run a function named 61 | get_lookup_contents(). Note that the root path of the REST handler is removed. 62 | 63 | If a POST request is made to the endpoint is executed with the path 64 | /lookup_edit/lookup_contents, the this class will attempt to execute post_lookup_contents(). 65 | 66 | The arguments to the function will be the following: 67 | 68 | * request_info (an instance of RequestInfo) 69 | * keyword arguments (**kwargs) 70 | """ 71 | 72 | def __init__(self, command_line, command_arg, logger=None): 73 | self.logger = logger 74 | PersistentServerConnectionApplication.__init__(self) 75 | 76 | @classmethod 77 | def get_function_signature(cls, method, path): 78 | """ 79 | Get the function that should be called based on path and request method. 80 | """ 81 | 82 | if len(path) > 0: 83 | return method + "_" + re.sub(r"[^a-zA-Z0-9_]", "_", path).lower() 84 | else: 85 | return method 86 | 87 | def render_json(self, data, response_code=200, headers=None): 88 | """ 89 | Render the data as JSON 90 | """ 91 | 92 | combined_headers = {"Content-Type": "application/json"} 93 | 94 | if headers is not None: 95 | combined_headers.update(headers) 96 | 97 | return { 98 | "payload": json.dumps(data), 99 | "status": response_code, 100 | "headers": combined_headers, 101 | } 102 | 103 | def render_error_json(self, message, response_code=500): 104 | """ 105 | Render an error to be returned to the client. 106 | """ 107 | 108 | data = {"success": False, "message": message} 109 | 110 | return { 111 | "payload": json.dumps(data), 112 | "status": response_code, 113 | "headers": {"Content-Type": "application/json"}, 114 | } 115 | 116 | def get_forms_args_as_dict(self, form_args): 117 | post_arg_dict = {} 118 | 119 | for arg in form_args: 120 | name = arg[0] 121 | value = arg[1] 122 | 123 | post_arg_dict[name] = value 124 | 125 | return post_arg_dict 126 | 127 | def handle(self, in_string): 128 | try: 129 | # log 130 | self.logger.debug("trackme_rest_handler, handling incoming request.") 131 | 132 | # Parse the arguments 133 | args = self.parse_in_string(in_string) 134 | 135 | # 136 | # user info - add to request_info 137 | # 138 | 139 | session_key = args["session"]["authtoken"] 140 | user = args["session"]["user"] 141 | 142 | # 143 | # system auth 144 | # 145 | 146 | # If passSystemAuth = True, add system_authtoken 147 | try: 148 | system_authtoken = args["system_authtoken"] 149 | except Exception as e: 150 | system_authtoken = None 151 | 152 | # 153 | # server info 154 | # 155 | 156 | server_rest_uri = args["server"]["rest_uri"] 157 | 158 | # extract rest host and port 159 | parsed_uri = urlparse(server_rest_uri) 160 | server_rest_host = parsed_uri.hostname 161 | server_rest_port = parsed_uri.port 162 | 163 | server_hostname = args["server"]["hostname"] 164 | server_servername = args["server"]["servername"] 165 | 166 | # 167 | # connection info 168 | # 169 | 170 | connection_src_ip = args["connection"]["src_ip"] 171 | connection_listening_port = args["connection"]["listening_port"] 172 | 173 | # 174 | # http method 175 | # 176 | 177 | # Get the method 178 | method = args["method"] 179 | 180 | # Get the path and the args 181 | if "path_info" in args: 182 | path = args["path_info"] 183 | else: 184 | return {"payload": "No path was provided", "status": 403} 185 | 186 | if method.lower() == "post": 187 | # Load the parameters from the query 188 | query = args["query_parameters"] 189 | 190 | if query is None: 191 | query = {} 192 | 193 | # Apply the ones (if any) we got from the form 194 | query_form = self.get_forms_args_as_dict(args["form"]) 195 | 196 | if query_form is not None: 197 | query.update(query_form) 198 | else: 199 | query = args["query_parameters"] 200 | 201 | # 202 | # finally add to the request_info 203 | # 204 | 205 | # Make the request info object 206 | request_info = RequestInfo( 207 | user, 208 | session_key, 209 | system_authtoken, 210 | server_rest_uri, 211 | server_rest_host, 212 | server_rest_port, 213 | server_hostname, 214 | server_servername, 215 | connection_src_ip, 216 | connection_listening_port, 217 | method, 218 | path, 219 | query, 220 | args, 221 | ) 222 | 223 | # Get the function signature 224 | function_name = self.get_function_signature(method, path) 225 | 226 | try: 227 | function_to_call = getattr(self, function_name) 228 | except AttributeError: 229 | function_to_call = None 230 | 231 | # Try to run the function 232 | if function_to_call is not None: 233 | if self.logger is not None: 234 | self.logger.debug("Executing function, name=%s", function_name) 235 | 236 | # Execute the function 237 | return function_to_call(request_info, **query) 238 | else: 239 | if self.logger is not None: 240 | self.logger.warn( 241 | "A request could not be executed since the associated function " 242 | + "is missing, name=%s", 243 | function_name, 244 | ) 245 | 246 | return {"payload": "Path was not found", "status": 404} 247 | except Exception as exception: 248 | if self.logger is not None: 249 | self.logger.exception( 250 | "Failed to handle request due to an unhandled exception" 251 | ) 252 | 253 | return {"payload": str(exception), "status": 500} 254 | 255 | def convert_to_dict(self, query): 256 | """ 257 | Create a dictionary containing the parameters. 258 | """ 259 | parameters = {} 260 | 261 | for key, val in query: 262 | # If the key is already in the list, but the existing entry isn't a list then make the 263 | # existing entry a list and add thi one 264 | if key in parameters and not isinstance(parameters[key], list): 265 | parameters[key] = [parameters[key], val] 266 | 267 | # If the entry is already included as a list, then just add the entry 268 | elif key in parameters: 269 | parameters[key].append(val) 270 | 271 | # Otherwise, just add the entry 272 | else: 273 | parameters[key] = val 274 | 275 | return parameters 276 | 277 | def parse_in_string(self, in_string): 278 | """ 279 | Parse the in_string 280 | """ 281 | 282 | params = json.loads(in_string) 283 | 284 | params["method"] = params["method"].lower() 285 | 286 | params["form_parameters"] = self.convert_to_dict(params.get("form", [])) 287 | params["query_parameters"] = self.convert_to_dict(params.get("query", [])) 288 | 289 | return params 290 | -------------------------------------------------------------------------------- /package/bin/getjirakv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | # REST API SPL handler for JIRA, allows interracting with a remote Splunk KVstore instance 5 | # See: https://ta-jira-service-desk-simple-addon.readthedocs.io/en/latest/ 6 | 7 | import os 8 | import sys 9 | import requests 10 | import urllib3 11 | 12 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 13 | import time 14 | import csv 15 | import logging 16 | from logging.handlers import RotatingFileHandler 17 | 18 | splunkhome = os.environ["SPLUNK_HOME"] 19 | 20 | # set logging 21 | filehandler = RotatingFileHandler( 22 | f"{splunkhome}/var/log/splunk/ta_jira_getjirakv.log", 23 | mode="a", 24 | maxBytes=10000000, 25 | backupCount=1, 26 | ) 27 | formatter = logging.Formatter( 28 | "%(asctime)s %(levelname)s %(filename)s %(funcName)s %(lineno)d %(message)s" 29 | ) 30 | filehandler.setFormatter(formatter) 31 | log = logging.getLogger() # root logger - Good to get it only once. 32 | for hdlr in log.handlers[:]: # remove the existing file handlers 33 | if isinstance(hdlr, logging.FileHandler): 34 | log.removeHandler(hdlr) 35 | log.addHandler(filehandler) # set the new handler 36 | # set the log level to INFO, DEBUG as the default is ERROR 37 | log.setLevel(logging.INFO) 38 | 39 | sys.path.append( 40 | os.path.join(splunkhome, "etc", "apps", "TA-jira-service-desk-simple-addon", "lib") 41 | ) 42 | 43 | from splunklib.searchcommands import ( 44 | dispatch, 45 | GeneratingCommand, 46 | Configuration, 47 | Option, 48 | validators, 49 | ) 50 | 51 | # import least privileges access libs 52 | from ta_jira_libs import jira_get_conf, jira_get_bearer_token 53 | 54 | 55 | @Configuration(distributed=False) 56 | class GetJiraKv(GeneratingCommand): 57 | """ 58 | A Splunk search command that retrieves data from a remote Splunk KVstore instance. 59 | This command is used to get JIRA failure replay data from a distributed setup. 60 | 61 | The command: 62 | - Supports both local and remote KVstore instances 63 | - Handles bearer token authentication 64 | - Supports passthrough mode for distributed setups 65 | - Provides verification capability 66 | - Returns CSV-formatted data from the KVstore 67 | """ 68 | 69 | verify = Option( 70 | doc=""" 71 | **Syntax:** **verify=**** 72 | **Description:** verify the connectivity to a remote instance. True / False are supported.""", 73 | require=False, 74 | default="False", 75 | validate=validators.Match("verify", r"^(True|False)$"), 76 | ) 77 | 78 | def generate(self, **kwargs): 79 | """ 80 | Generates the search results by retrieving data from the KVstore. 81 | 82 | This method: 83 | 1. Retrieves the JIRA configuration 84 | 2. Sets up logging 85 | 3. Gets the bearer token if needed 86 | 4. Determines the KVstore instance to use 87 | 5. Constructs and executes the search query 88 | 6. Processes and yields the results 89 | 90 | The method handles: 91 | - Local and remote KVstore instances 92 | - Passthrough mode for distributed setups 93 | - Bearer token authentication 94 | - Error handling and logging 95 | - CSV data processing 96 | 97 | Yields: 98 | dict: A dictionary containing: 99 | - _time: The timestamp of the request 100 | - uuid: The unique identifier of the record 101 | - account: The JIRA account name 102 | - data: The JIRA data 103 | - status: The status of the record 104 | - ctime: Creation timestamp 105 | - mtime: Modification timestamp 106 | - no_attempts: Number of replay attempts 107 | 108 | If verification is enabled, yields a success/failure message instead. 109 | """ 110 | 111 | if self: 112 | 113 | # Get the session key 114 | session_key = self._metadata.searchinfo.session_key 115 | server_uri = self._metadata.searchinfo.splunkd_uri 116 | # get conf 117 | jira_conf = jira_get_conf( 118 | self._metadata.searchinfo.session_key, 119 | self._metadata.searchinfo.splunkd_uri, 120 | ) 121 | 122 | # set loglevel 123 | log.setLevel(jira_conf["logging"]["loglevel"]) 124 | 125 | # init 126 | jira_passthrough_mode = int( 127 | jira_conf["advanced_configuration"].get("jira_passthrough_mode", 0) 128 | ) 129 | kvstore_instance = jira_conf["advanced_configuration"].get( 130 | "kvstore_instance", None 131 | ) 132 | kvstore_search_filters = jira_conf["advanced_configuration"].get( 133 | "kvstore_search_filters", None 134 | ) 135 | bearer_token = None 136 | 137 | if kvstore_instance: 138 | bearer_token = jira_get_bearer_token(session_key, server_uri) 139 | 140 | # the root search 141 | search = '| inputlookup jira_failures_replay | eval uuid=_key, mtime=if(isnull(mtime), ctime, mtime), status=case(isnull(status), "tempoary_failure", isnull(data), "tagged_for_removal", 1=1, status), data=if(isnull(data), "null", data), no_attempts=if(isnull(no_attempts), 0, no_attempts)' 142 | 143 | # If the passthrough mode is disabled, there is no distributed setup 144 | # and the instance is the localhost 145 | if (not kvstore_instance or not bearer_token) and str( 146 | jira_passthrough_mode 147 | ) == "0": 148 | kvstore_instance = self._metadata.searchinfo.splunkd_uri 149 | header = f"Splunk {session_key}" 150 | elif str(jira_passthrough_mode) == "1": 151 | # yield 152 | data = { 153 | "_time": time.time(), 154 | "_raw": f'{{"response": "INFO: Passthrough mode is currently enabled in this instance, you can safety disable the alert execution for this instance."}}', 155 | } 156 | yield data 157 | sys.exit(0) 158 | elif kvstore_instance and not bearer_token: 159 | # yield 160 | data = { 161 | "_time": time.time(), 162 | "_raw": f'{{"response": "ERROR: The KVstore instance is set but not the bearer token."}}', 163 | } 164 | yield data 165 | sys.exit(0) 166 | elif bearer_token and not kvstore_instance: 167 | # yield 168 | data = { 169 | "_time": time.time(), 170 | "_raw": f'{{"response": "ERROR: The bearer token is set but not the KVstore instance."}}', 171 | } 172 | yield data 173 | sys.exit(0) 174 | else: 175 | header = f"Bearer {bearer_token}" 176 | search = f"{search} | search {kvstore_search_filters}" 177 | 178 | # Define the url 179 | if not kvstore_instance.startswith("https://"): 180 | url = f"https://{kvstore_instance}/services/search/jobs/export" 181 | else: 182 | url = f"{kvstore_instance}/services/search/jobs/export" 183 | 184 | # Get data 185 | output_mode = "csv" 186 | exec_mode = "oneshot" 187 | 188 | # Call 189 | try: 190 | response = requests.post( 191 | url, 192 | headers={"Authorization": header}, 193 | verify=False, 194 | data={ 195 | "search": search, 196 | "output_mode": output_mode, 197 | "exec_mode": exec_mode, 198 | }, 199 | ) 200 | csv_data = response.text 201 | response.raise_for_status() 202 | 203 | except Exception as e: 204 | response_error = f"JIRA Get remove KVstore has failed!. url={url}, data={search}, HTTP Error={response.status_code}, content={response.text}" 205 | self.logger.fatal(str(response_error)) 206 | data = { 207 | "_time": time.time(), 208 | "_raw": f'{{"response": "{response_error}"}}', 209 | } 210 | yield data 211 | sys.exit(0) 212 | 213 | if self.verify == "True": 214 | 215 | response_error = f"JIRA Get remove KVstore was successfull. url={url}, data={search}, HTTP Error={response.status_code}" 216 | data = { 217 | "_time": time.time(), 218 | "_raw": f'{{"response": "{response_error}"}}', 219 | } 220 | yield data 221 | sys.exit(0) 222 | 223 | else: 224 | 225 | # Use the CSV dict reader 226 | readCSV = csv.DictReader( 227 | csv_data.splitlines(True), 228 | delimiter=str(","), 229 | quotechar=str('"'), 230 | ) 231 | 232 | # For row in CSV, generate the _raw 233 | for row in readCSV: 234 | yield { 235 | "_time": time.time(), 236 | "uuid": str(row["uuid"]), 237 | "account": str(row["account"]), 238 | "data": str(row["data"]), 239 | "status": str(row["status"]), 240 | "ctime": str(row["ctime"]), 241 | "mtime": str(row["mtime"]), 242 | "no_attempts": str(row["no_attempts"]), 243 | } 244 | 245 | else: 246 | 247 | # yield 248 | data = { 249 | "_time": time.time(), 250 | "_raw": f'{{"response": "Error: bad request"}}', 251 | } 252 | yield data 253 | 254 | 255 | dispatch(GetJiraKv, sys.argv, sys.stdin, sys.stdout, __name__) 256 | -------------------------------------------------------------------------------- /package/bin/jirarest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | import json 5 | import sys 6 | import os 7 | import time 8 | import requests 9 | import logging 10 | from logging.handlers import RotatingFileHandler 11 | import urllib3 12 | 13 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 14 | 15 | splunkhome = os.environ["SPLUNK_HOME"] 16 | 17 | # set logging 18 | filehandler = RotatingFileHandler( 19 | f"{splunkhome}/var/log/splunk/ta_jira_jirarest.log", 20 | mode="a", 21 | maxBytes=10000000, 22 | backupCount=1, 23 | ) 24 | formatter = logging.Formatter( 25 | "%(asctime)s %(levelname)s %(filename)s %(funcName)s %(lineno)d %(message)s" 26 | ) 27 | filehandler.setFormatter(formatter) 28 | log = logging.getLogger() # root logger - Good to get it only once. 29 | for hdlr in log.handlers[:]: # remove the existing file handlers 30 | if isinstance(hdlr, logging.FileHandler): 31 | log.removeHandler(hdlr) 32 | log.addHandler(filehandler) # set the new handler 33 | # set the log level to INFO, DEBUG as the default is ERROR 34 | log.setLevel(logging.INFO) 35 | 36 | sys.path.append( 37 | os.path.join(splunkhome, "etc", "apps", "TA-jira-service-desk-simple-addon", "lib") 38 | ) 39 | 40 | from splunklib.searchcommands import ( 41 | dispatch, 42 | GeneratingCommand, 43 | Configuration, 44 | Option, 45 | validators, 46 | ) 47 | 48 | # Import JIRA libs 49 | from ta_jira_libs import ( 50 | jira_get_conf, 51 | jira_get_accounts, 52 | jira_get_account, 53 | jira_build_headers, 54 | jira_handle_ssl_certificate, 55 | jira_test_connectivity, 56 | ) 57 | 58 | 59 | @Configuration(distributed=False) 60 | class GenerateTextCommand(GeneratingCommand): 61 | """ 62 | A Splunk search command that provides a REST interface to JIRA. 63 | This command allows making HTTP requests to JIRA's REST API with various methods (GET, POST, PUT, DELETE). 64 | 65 | The command supports: 66 | - Configurable JIRA account selection 67 | - Multiple HTTP methods 68 | - JSON request payloads 69 | - SSL certificate handling 70 | - Proxy configuration 71 | """ 72 | 73 | account = Option( 74 | doc=""" 75 | **Syntax:** **account=**** 76 | **Description:** JIRA account to be used, if unspecified the first account configured will be taken into account.""", 77 | require=False, 78 | default=None, 79 | ) 80 | method = Option( 81 | doc=""" 82 | **Syntax:** **method=**** 83 | **Description:** method to use for API target. DELETE GET POST PUT are supported.""", 84 | require=False, 85 | validate=validators.Match("method", r"^(DELETE|GET|POST|PUT)$"), 86 | ) 87 | json_request = Option( 88 | doc=""" 89 | **Syntax:** **json_request=***JSON request* 90 | **Description:** JSON-formatted json_request.""", 91 | require=False, 92 | validate=validators.Match("json_request", r"^{.+}$"), 93 | ) 94 | target = Option(require=True) 95 | 96 | def generate(self): 97 | """ 98 | Generates the search results by making a REST request to JIRA. 99 | 100 | This method: 101 | 1. Retrieves the JIRA configuration 102 | 2. Sets up logging and proxy settings 103 | 3. Gets the specified account configuration 104 | 4. Tests connectivity to JIRA 105 | 5. Makes the REST request with the specified method 106 | 6. Processes and yields the response 107 | 108 | The method handles: 109 | - Different HTTP methods (GET, POST, PUT, DELETE) 110 | - JSON request payloads 111 | - SSL certificate verification 112 | - Proxy configuration 113 | - Error handling and response formatting 114 | 115 | Yields: 116 | dict: A dictionary containing: 117 | - _time: The timestamp of the request 118 | - _raw: The JSON response from JIRA or an error message 119 | """ 120 | 121 | # get conf 122 | jira_conf = jira_get_conf( 123 | self._metadata.searchinfo.session_key, self._metadata.searchinfo.splunkd_uri 124 | ) 125 | 126 | # set loglevel 127 | log.setLevel(jira_conf["logging"]["loglevel"]) 128 | 129 | # global configuration 130 | proxy_conf = jira_conf["proxy"] 131 | proxy_dict = proxy_conf.get("proxy_dict", {}) 132 | 133 | # set timeout 134 | timeout = int(jira_conf["advanced_configuration"].get("timeout", 120)) 135 | 136 | # get all acounts 137 | accounts_dict = jira_get_accounts( 138 | self._metadata.searchinfo.session_key, self._metadata.searchinfo.splunkd_uri 139 | ) 140 | accounts = accounts_dict.get("accounts", []) 141 | 142 | # define the account target 143 | if not self.account or self.account == "_any": 144 | account = str(accounts[0]) 145 | else: 146 | account = str(self.account) 147 | 148 | # account configuration 149 | account_conf = jira_get_account( 150 | self._metadata.searchinfo.session_key, 151 | self._metadata.searchinfo.splunkd_uri, 152 | account, 153 | ) 154 | 155 | jira_auth_mode = account_conf.get("jira_auth_mode", "basic") 156 | jira_url = account_conf.get("jira_url", None) 157 | jira_ssl_certificate_path = account_conf.get("jira_ssl_certificate_path", None) 158 | jira_ssl_certificate_pem = account_conf.get("jira_ssl_certificate_pem", None) 159 | jira_username = account_conf.get("username", None) 160 | jira_password = account_conf.get("jira_password", None) 161 | 162 | # Build the authentication header for JIRA 163 | jira_headers = jira_build_headers(jira_auth_mode, jira_username, jira_password) 164 | 165 | # SSL verification is always true or the path to the CA bundle for the SSL certificate to be verified 166 | # Handle SSL certificate configuration 167 | ssl_config, temp_cert_file = jira_handle_ssl_certificate( 168 | jira_ssl_certificate_path, jira_ssl_certificate_pem 169 | ) 170 | 171 | # verify the method 172 | if self.method: 173 | jira_method = self.method 174 | else: 175 | jira_method = "GET" 176 | 177 | if self.json_request: 178 | body_dict = json.loads(self.json_request) 179 | else: 180 | if jira_method == "POST" or jira_method == "PUT": 181 | raise Exception( 182 | f"jirarest: method {jira_method} requires a valid json_request. It is empty" 183 | ) 184 | 185 | # test connectivity systematically 186 | connected = False 187 | try: 188 | healthcheck_response = jira_test_connectivity( 189 | self._metadata.searchinfo.session_key, 190 | self._metadata.searchinfo.splunkd_uri, 191 | account, 192 | ) 193 | connected = True 194 | logging.debug( 195 | f'JIRA connect verification successful for account="{account}", response="{json.dumps(healthcheck_response)}"' 196 | ) 197 | except Exception as e: 198 | raise Exception( 199 | f'JIRA connect verification failed for account="{account}" with exception="{str(e)}"' 200 | ) 201 | 202 | # 203 | # main 204 | # 205 | 206 | if connected: 207 | 208 | # set proper headers 209 | if jira_method == "GET": 210 | jira_fields_response = requests.get( 211 | url=f"{str(jira_url)}/{str(self.target)}", 212 | headers=jira_headers, 213 | verify=ssl_config, 214 | proxies=proxy_dict, 215 | timeout=timeout, 216 | ) 217 | elif jira_method == "DELETE": 218 | jira_fields_response = requests.delete( 219 | url=f"{str(jira_url)}/{str(self.target)}", 220 | headers=jira_headers, 221 | verify=ssl_config, 222 | proxies=proxy_dict, 223 | timeout=timeout, 224 | ) 225 | elif jira_method == "POST": 226 | jira_fields_response = requests.post( 227 | url=f"{str(jira_url)}/{str(self.target)}", 228 | data=json.dumps(body_dict).encode("utf-8"), 229 | headers=jira_headers, 230 | verify=ssl_config, 231 | proxies=proxy_dict, 232 | timeout=timeout, 233 | ) 234 | elif jira_method == "PUT": 235 | jira_fields_response = requests.put( 236 | url=f"{str(jira_url)}/{str(self.target)}", 237 | data=json.dumps(body_dict).encode("utf-8"), 238 | headers=jira_headers, 239 | verify=ssl_config, 240 | proxies=proxy_dict, 241 | timeout=timeout, 242 | ) 243 | 244 | # Attenpt to get a JSON response, and render in Splunk 245 | try: 246 | 247 | json_response = jira_fields_response.json() 248 | data = {"_time": time.time(), "_raw": json.dumps(json_response)} 249 | yield data 250 | 251 | except Exception as e: 252 | 253 | # Build a custom response for Splunk dynamically 254 | 255 | # Create an action field, convenient to quickly understanding when things go wrong 256 | if jira_fields_response.status_code in (200, 201, 204): 257 | response_action = "success" 258 | else: 259 | response_action = "failure" 260 | 261 | # render 262 | if jira_fields_response.text: 263 | json_response = f'{{"action": "{response_action}", "status_code": "{jira_fields_response.status_code}", "text": "{jira_fields_response.text}"}}' 264 | else: 265 | json_response = f'{{"action": "{response_action}", "status_code": "{jira_fields_response.status_code}"}}' 266 | data = { 267 | "_time": time.time(), 268 | "_raw": str( 269 | json.dumps(json.loads(json_response, strict=False), indent=4) 270 | ), 271 | } 272 | 273 | yield data 274 | 275 | 276 | dispatch(GenerateTextCommand, sys.argv, sys.stdin, sys.stdout, __name__) 277 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # trackme documentation build configuration file, created by 4 | # sphinx-quickstart on Tue Sep 18 23:25:46 2018. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | # sys.path.insert(0, os.path.abspath('.')) 22 | 23 | # -- General configuration ------------------------------------------------ 24 | 25 | # If your documentation needs a minimal Sphinx version, state it here. 26 | # needs_sphinx = '1.0' 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be 29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 30 | # ones. 31 | extensions = [] 32 | 33 | # Add any paths that contain templates here, relative to this directory. 34 | templates_path = ["_templates"] 35 | 36 | # The suffix(es) of source filenames. 37 | # You can specify multiple suffix as a list of string: 38 | # source_suffix = ['.rst', '.md'] 39 | source_suffix = ".rst" 40 | 41 | # The encoding of source files. 42 | # source_encoding = 'utf-8-sig' 43 | 44 | # The master toctree document. 45 | master_doc = "index" 46 | 47 | # General information about the project. 48 | project = "TA-jira-service-desk-simple-addon" 49 | copyright = "2018-2025, Guilhem Marchand" 50 | author = "Guilhem Marchand" 51 | 52 | # The version info for the project you're documenting, acts as replacement for 53 | # |version| and |release|, also used in various other places throughout the 54 | # built documents. 55 | # 56 | # The short X.Y version. 57 | version = "2.0" 58 | # The full version, including alpha/beta/rc tags. 59 | release = "2" 60 | 61 | # The language for content autogenerated by Sphinx. Refer to documentation 62 | # for a list of supported languages. 63 | # 64 | # This is also used if you do content translation via gettext catalogs. 65 | # Usually you set "language" from the command line for these cases. 66 | language = "en" 67 | 68 | # There are two options for replacing |today|: either, you set today to some 69 | # non-false value, then it is used: 70 | # today = '' 71 | # Else, today_fmt is used as the format for a strftime call. 72 | # today_fmt = '%B %d, %Y' 73 | 74 | # List of patterns, relative to source directory, that match files and 75 | # directories to ignore when looking for source files. 76 | exclude_patterns = [".build"] 77 | 78 | # The reST default role (used for this markup: `text`) to use for all 79 | # documents. 80 | # default_role = None 81 | 82 | # If true, '()' will be appended to :func: etc. cross-reference text. 83 | # add_function_parentheses = True 84 | 85 | # If true, the current module name will be prepended to all description 86 | # unit titles (such as .. function::). 87 | # add_module_names = True 88 | 89 | # If true, sectionauthor and moduleauthor directives will be shown in the 90 | # output. They are ignored by default. 91 | # show_authors = False 92 | 93 | # The name of the Pygments (syntax highlighting) style to use. 94 | pygments_style = "sphinx" 95 | 96 | # A list of ignored prefixes for module index sorting. 97 | # modindex_common_prefix = [] 98 | 99 | # If true, keep warnings as "system message" paragraphs in the built documents. 100 | # keep_warnings = False 101 | 102 | # If true, `todo` and `todoList` produce output, else they produce nothing. 103 | todo_include_todos = False 104 | 105 | 106 | # -- Options for HTML output ---------------------------------------------- 107 | 108 | # The theme to use for HTML and HTML Help pages. See the documentation for 109 | # a list of builtin themes. 110 | # html_theme = 'alabaster' 111 | 112 | import sphinx_rtd_theme 113 | 114 | html_theme = "sphinx_rtd_theme" 115 | # html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 116 | 117 | # Theme options are theme-specific and customize the look and feel of a theme 118 | # further. For a list of options available for each theme, see the 119 | # documentation. 120 | html_theme_options = { 121 | "style_external_links": True, 122 | } 123 | 124 | # Add any paths that contain custom themes here, relative to this directory. 125 | # html_theme_path = [] 126 | 127 | # The name for this set of Sphinx documents. If None, it defaults to 128 | # " v documentation". 129 | # html_title = None 130 | 131 | # A shorter title for the navigation bar. Default is the same as html_title. 132 | # html_short_title = None 133 | 134 | # The name of an image file (relative to this directory) to place at the top 135 | # of the sidebar. 136 | html_logo = "img/logo.png" 137 | 138 | # The name of an image file (relative to this directory) to use as a favicon of 139 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 140 | # pixels large. 141 | # html_favicon = None 142 | 143 | # Add any paths that contain custom static files (such as style sheets) here, 144 | # relative to this directory. They are copied after the builtin static files, 145 | # so a file named "default.css" will overwrite the builtin "default.css". 146 | html_static_path = [".static"] 147 | 148 | # Add any extra paths that contain custom files (such as robots.txt or 149 | # .htaccess) here, relative to this directory. These files are copied 150 | # directly to the root of the documentation. 151 | # html_extra_path = [] 152 | 153 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 154 | # using the given strftime format. 155 | # html_last_updated_fmt = '%b %d, %Y' 156 | 157 | # If true, SmartyPants will be used to convert quotes and dashes to 158 | # typographically correct entities. 159 | # html_use_smartypants = True 160 | 161 | # Custom sidebar templates, maps document names to template names. 162 | # html_sidebars = {} 163 | 164 | # Additional templates that should be rendered to pages, maps page names to 165 | # template names. 166 | # html_additional_pages = {} 167 | 168 | # If false, no module index is generated. 169 | # html_domain_indices = True 170 | 171 | # If false, no index is generated. 172 | # html_use_index = True 173 | 174 | # If true, the index is split into individual pages for each letter. 175 | # html_split_index = False 176 | 177 | # If true, links to the reST sources are added to the pages. 178 | # html_show_sourcelink = True 179 | 180 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 181 | # html_show_sphinx = True 182 | 183 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 184 | # html_show_copyright = True 185 | 186 | # If true, an OpenSearch description file will be output, and all pages will 187 | # contain a tag referring to it. The value of this option must be the 188 | # base URL from which the finished HTML is served. 189 | # html_use_opensearch = '' 190 | 191 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 192 | # html_file_suffix = None 193 | 194 | # Language to be used for generating the HTML full-text search index. 195 | # Sphinx supports the following languages: 196 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 197 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 198 | html_search_language = "en" 199 | 200 | # A dictionary with options for the search language support, empty by default. 201 | # Now only 'ja' uses this config value 202 | # html_search_options = {'type': 'default'} 203 | 204 | # The name of a javascript file (relative to the configuration directory) that 205 | # implements a search results scorer. If empty, the default will be used. 206 | # html_search_scorer = 'scorer.js' 207 | 208 | # Output file base name for HTML help builder. 209 | htmlhelp_basename = "dsmondoc" 210 | 211 | # -- Options for LaTeX output --------------------------------------------- 212 | 213 | latex_elements = { 214 | # The paper size ('letterpaper' or 'a4paper'). 215 | #'papersize': 'letterpaper', 216 | # The font size ('10pt', '11pt' or '12pt'). 217 | #'pointsize': '10pt', 218 | # Additional stuff for the LaTeX preamble. 219 | #'preamble': '', 220 | # Latex figure (float) alignment 221 | #'figure_align': 'htbp', 222 | } 223 | 224 | # Grouping the document tree into LaTeX files. List of tuples 225 | # (source start file, target name, title, 226 | # author, documentclass [howto, manual, or own class]). 227 | latex_documents = [ 228 | ( 229 | master_doc, 230 | "TA-jira-service-desk-simple-addon.tex", 231 | "TA-jira-service-desk-simple-addon Documentation", 232 | "Guilhem Marchand", 233 | "manual", 234 | ), 235 | ] 236 | 237 | # The name of an image file (relative to this directory) to place at the top of 238 | # the title page. 239 | # latex_logo = None 240 | 241 | # For "manual" documents, if this is true, then toplevel headings are parts, 242 | # not chapters. 243 | # latex_use_parts = False 244 | 245 | # If true, show page references after internal links. 246 | # latex_show_pagerefs = False 247 | 248 | # If true, show URL addresses after external links. 249 | # latex_show_urls = False 250 | 251 | # Documents to append as an appendix to all manuals. 252 | # latex_appendices = [] 253 | 254 | # If false, no module index is generated. 255 | # latex_domain_indices = True 256 | 257 | 258 | # -- Options for manual page output --------------------------------------- 259 | 260 | # One entry per manual page. List of tuples 261 | # (source start file, name, description, authors, manual section). 262 | man_pages = [ 263 | ( 264 | master_doc, 265 | "TA-jira-service-desk-simple-addon", 266 | "TA-jira-service-desk-simple-addon Documentation", 267 | [author], 268 | 1, 269 | ) 270 | ] 271 | 272 | # If true, show URL addresses after external links. 273 | # man_show_urls = False 274 | 275 | 276 | # -- Options for Texinfo output ------------------------------------------- 277 | 278 | # Grouping the document tree into Texinfo files. List of tuples 279 | # (source start file, target name, title, author, 280 | # dir menu entry, description, category) 281 | texinfo_documents = [ 282 | ( 283 | master_doc, 284 | "TA-jira-service-desk-simple-addon", 285 | "TA-jira-service-desk-simple-addon Documentation", 286 | author, 287 | "TA-jira-service-desk-simple-addon", 288 | "Splunk application data sources availability monitoring.", 289 | "Miscellaneous", 290 | ), 291 | ] 292 | 293 | # Documents to append as an appendix to all manuals. 294 | # texinfo_appendices = [] 295 | 296 | # If false, no module index is generated. 297 | # texinfo_domain_indices = True 298 | 299 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 300 | # texinfo_show_urls = 'footnote' 301 | 302 | # If true, do not generate a @detailmenu in the "Top" node's menu. 303 | # texinfo_no_detailmenu = False 304 | 305 | 306 | def setup(app): 307 | app.add_css_file("css/custom.css") 308 | -------------------------------------------------------------------------------- /package/default/data/ui/views/rest_api_explore.xml: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | 6 | 7 |

JIRA Rest API

8 | 9 |

Use the custom command | jirarest account="<account>" target="<endpoint>" to perform an HTTP rest call against any endpoint of your JIRA instance.
The default method is GET, but you can as well perform PUT, POST and DELETE calls using the method argument and the json_request if the API endpoint requires data to be provided

10 | 11 |

For API references:

12 | 13 | Jira Server platform REST API reference 14 | 15 |

Add-on documentation page:

16 | 17 | REST wrapper documentation on Read the docs 18 | 19 | 20 |
21 |
22 | 23 | 24 | Try your own: 25 | | jirarest $tk_account$ target="rest/api/2/serverInfo" method="GET" 26 | 27 | 28 | true 29 | account=" 30 | " 31 | 32 | | rest splunk_server=local /servicesNS/nobody/TA-jira-service-desk-simple-addon/ta_service_desk_simple_addon_account | dedup title 33 | 34 | title 35 | title 36 | 37 | 38 | 39 | rest/api/2/myself 40 | target=" 41 | " 42 | rest/api/2/myself 43 | 44 | 45 | 46 | | jirarest $tk_account$ $tk_endpoint$ method="GET" | spath 47 | -24h@h 48 | now 49 | 1 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 |

Use REST and JQL to get the total number of issues per project, per status category and calculate percentages in each status (dynamically list the projects and iterate using the map command):

69 | 70 |
71 |
72 | 73 | 74 | You can use the JQL language and perform any advanced query in JIRA, the following example returns the number of issues per project: api/2/search?jql=project=<my project>&maxResults=0 75 | 76 | 77 | | jirarest $tk_account$ target="rest/api/2/project" method="GET" | spath | rename "{}.key" as key | table key | mvexpand key | streamstats count as project_no 78 | 79 | | map [ | jirarest $tk_account$ target="rest/api/2/search?jql=project=$$key$$&maxResults=0" method="GET" ] | streamstats count as result_no | rex field=_raw "\"total\":\s(?<total_issues>\d*)" 80 | | append [ 81 | | jirarest $tk_account$ target="rest/api/2/project" method="GET" | spath | rename "{}.key" as key | table key | mvexpand key | streamstats count as project_no 82 | | map [ | jirarest $tk_account$ target="rest/api/2/search?jql=project=$$key$$%20AND%20statuscategory%20IN%20%28%22Done%22%29&maxResults=0" ] | streamstats count as result_no | rex field=_raw "\"total\":\s(?<total_done>\d*)" 83 | ] 84 | | append [ 85 | | jirarest $tk_account$ target="rest/api/2/project" method="GET" | spath | rename "{}.key" as key | table key | mvexpand key | streamstats count as project_no 86 | | map [ | jirarest $tk_account$ target="rest/api/2/search?jql=project=$$key$$%20AND%20statuscategory%20IN%20%28%22To%20Do%22%29&maxResults=0" ] | streamstats count as result_no | rex field=_raw "\"total\":\s(?<total_to_do>\d*)" 87 | ] 88 | | append [ 89 | | jirarest $tk_account$ target="rest/api/2/project" method="GET" | spath | rename "{}.key" as key | table key | mvexpand key | streamstats count as project_no 90 | | map [ | jirarest $tk_account$ target="rest/api/2/search?jql=project=$$key$$%20AND%20statuscategory%20IN%20%28%22In%20Progress%22%29&maxResults=0" ] | streamstats count as result_no | rex field=_raw "\"total\":\s(?<total_in_progress>\d*)" 91 | ] 92 | | append [ | jirarest $tk_account$ target="rest/api/2/project" method="GET" | spath | rename "{}.key" as key | table key | mvexpand key | streamstats count as project_no ] 93 | | eval line_merge=case(isnum(project_no), project_no, isnum(result_no), result_no) 94 | | stats first(key) as project, first(total_*) as "total_*" by line_merge | fields - line_merge 95 | 96 | | eval pct_total_done="% " . round(total_done/total_issues*100, 2), pct_total_to_do="% " . round(total_to_do/total_issues*100, 2), pct_total_in_progress="% " . round(total_in_progress/total_issues*100, 2) 97 | | foreach pct_* [ eval <<FIELD>> = if(isnull('<<FIELD>>'), "% 0.00", '<<FIELD>>' ) ] 98 | 99 | | eval _time=now() | fields _time, project, pct_*, total_* 100 | -24h@h 101 | now 102 | 1 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 |
125 |
126 |
127 | 128 | 129 | 130 |

Use REST and JQL to get the total number of issues per project (dynamically list the projects and iterate using the map command):

131 | 132 |
133 |
134 | 135 | 136 | You can use the JQL language and perform any advanced query in JIRA, the following example returns the number of issues per project: api/2/search?jql=project=<my project>&maxResults=0 137 | 138 | 139 | | jirarest $tk_account$ target="rest/api/2/project" method="GET" | spath | rename "{}.key" as key | table key | mvexpand key | streamstats count as project_no 140 | | map [ | jirarest $tk_account$ target="rest/api/2/search?jql=project=$$key$$&maxResults=0" method="GET" ] | streamstats count as result_no 141 | | append [ | jirarest $tk_account$ target="rest/api/2/project" method="GET" | spath | rename "{}.key" as key | table key | mvexpand key | streamstats count as project_no ] 142 | | eval line_merge=case(isnum(project_no), project_no, isnum(result_no), result_no) 143 | | stats first(_time) as _time, first(key) as project, first(_raw) as _raw by line_merge | fields - line_merge 144 | -24h@h 145 | now 146 | 1 147 | 148 | 149 | 150 |
151 |
152 |
153 | 154 | 155 | 156 |

Use REST and JQL to get the number of issues that are resolved (status category "Done") per project:

157 | 158 |
159 |
160 | 161 | 162 | Get the number of issues per project in the status category "Done" 163 | 164 | 165 | | jirarest $tk_account$ target="rest/api/2/project" method="GET" | spath | rename "{}.key" as key | table key | mvexpand key | streamstats count as project_no 166 | | map [ | jirarest $tk_account$ target="rest/api/2/search?jql=project=$$key$$%20AND%20statuscategory%20IN%20%28%22Done%22%29&maxResults=0" method="GET" ] | streamstats count as result_no 167 | | append [ | jirarest $tk_account$ target="rest/api/2/project" method="GET" | spath | rename "{}.key" as key | table key | mvexpand key | streamstats count as project_no ] 168 | | eval line_merge=case(isnum(project_no), project_no, isnum(result_no), result_no) 169 | | stats first(_time) as _time, first(key) as project, first(_raw) as _raw by line_merge | fields - line_merge 170 | -24h@h 171 | now 172 | 1 173 | 174 | 175 | 176 |
177 |
178 |
179 | 180 | 181 | 182 |

Use REST and JQL to get the number of issues that are not resolved (status category "To Do" or "In Progress") per project:

183 | 184 |
185 |
186 | 187 | 188 | Get the number of issues per project in the status category "Done" 189 | 190 | 191 | | jirarest $tk_account$ target="rest/api/2/project" method="GET" | spath | rename "{}.key" as key | table key | mvexpand key | streamstats count as project_no 192 | | map [ | jirarest $tk_account$ target="rest/api/2/search?jql=project=$$key$$%20AND%20statuscategory%20IN%20%28%22To%20Do%22%2C%20%22In%20Progress%22%29&maxResults=0" method="GET" ] | streamstats count as result_no 193 | | append [ | jirarest $tk_account$ target="rest/api/2/project" method="GET" | spath | rename "{}.key" as key | table key | mvexpand key | streamstats count as project_no ] 194 | | eval line_merge=case(isnum(project_no), project_no, isnum(result_no), result_no) 195 | | stats first(_time) as _time, first(key) as project, first(_raw) as _raw by line_merge | fields - line_merge 196 | -24h@h 197 | now 198 | 1 199 | 200 | 201 | 202 |
203 |
204 |
205 |
-------------------------------------------------------------------------------- /package/default/savedsearches.conf: -------------------------------------------------------------------------------- 1 | # savedsearches.conf 2 | 3 | [JIRA Service Desk - modular action logs] 4 | description = This report exposes all logs from the JIRA Service Desk modular action 5 | dispatch.earliest_time = -60m 6 | dispatch.latest_time = now 7 | display.visualizations.show = 0 8 | request.ui_dispatch_app = TA-jira-service-desk-simple-addon 9 | request.ui_dispatch_view = search 10 | search = (index="_internal" OR index="cim_modactions") (source="*jira_service_desk_modalert.log") 11 | 12 | [JIRA Service Desk - modular resilient store action logs] 13 | description = This report exposes all logs from the JIRA Service Desk modular action 14 | dispatch.earliest_time = -60m 15 | dispatch.latest_time = now 16 | display.visualizations.show = 0 17 | request.ui_dispatch_app = TA-jira-service-desk-simple-addon 18 | request.ui_dispatch_view = search 19 | search = (index="_internal" OR index="cim_modactions") (source="*jira_service_desk_replay_modalert.log") 20 | 21 | [JIRA Service Desk - Issue creation successes] 22 | description = This report exposes all logs from the JIRA Service Desk modular action 23 | dispatch.earliest_time = -60m 24 | dispatch.latest_time = now 25 | display.visualizations.show = 0 26 | request.ui_dispatch_app = TA-jira-service-desk-simple-addon 27 | request.ui_dispatch_view = search 28 | search = (index="_internal" OR index="cim_modactions") (source="*jira_service_desk_modalert.log") "JIRA Service Desk ticket successfully created" 29 | 30 | [JIRA Service Desk - temporary issue creation failures] 31 | description = This report exposes all logs from the JIRA Service Desk modular action 32 | dispatch.earliest_time = -60m 33 | dispatch.latest_time = now 34 | display.visualizations.show = 0 35 | request.ui_dispatch_app = TA-jira-service-desk-simple-addon 36 | request.ui_dispatch_view = search 37 | search = (index="_internal" OR index="cim_modactions") (source="*jira_service_desk_modalert.log") "JIRA Service Desk ticket creation has failed" 38 | 39 | [JIRA Service Desk - permanent issue creation failures] 40 | description = This report exposes all logs from the JIRA Service Desk modular action 41 | dispatch.earliest_time = -60m 42 | dispatch.latest_time = now 43 | display.visualizations.show = 0 44 | request.ui_dispatch_app = TA-jira-service-desk-simple-addon 45 | request.ui_dispatch_view = search 46 | search = (index="_internal" OR index="cim_modactions") (source="*jira_service_desk_replay_modalert.log") "permanent failure!" 47 | 48 | [JIRA Service Desk - Check connection] 49 | description = This report checks the JIRA connectivity (settings, network and authentication) for all configured accounts 50 | dispatch.earliest_time = -5m 51 | dispatch.latest_time = now 52 | display.visualizations.show = 0 53 | request.ui_dispatch_app = TA-jira-service-desk-simple-addon 54 | request.ui_dispatch_view = search 55 | search = | jirafill account=_all opt=0 56 | 57 | [JIRA Service Desk - Get projects] 58 | description = This report exposes JIRA projects available 59 | dispatch.earliest_time = -5m 60 | dispatch.latest_time = now 61 | display.visualizations.show = 0 62 | request.ui_dispatch_app = TA-jira-service-desk-simple-addon 63 | request.ui_dispatch_view = search 64 | search = | jirafill account=_all opt=1 | stats values(key) as key, values(key_projects) as key_projects by account 65 | 66 | [JIRA Service Desk - Get issue types] 67 | description = This report exposes JIRA issue types available 68 | dispatch.earliest_time = -5m 69 | dispatch.latest_time = now 70 | display.visualizations.show = 0 71 | request.ui_dispatch_app = TA-jira-service-desk-simple-addon 72 | request.ui_dispatch_view = search 73 | search = | jirafill account=_all opt=2 | stats values(issues) as issues by account 74 | 75 | [JIRA Service Desk - Get issue priorities] 76 | description = This report exposes JIRA priorities available 77 | dispatch.earliest_time = -5m 78 | dispatch.latest_time = now 79 | display.visualizations.show = 0 80 | request.ui_dispatch_app = TA-jira-service-desk-simple-addon 81 | request.ui_dispatch_view = search 82 | search = | jirafill account=_all opt=3 | stats values(priorities) as priorities by account 83 | 84 | [JIRA Service Desk - Get status categories] 85 | description = This report exposes JIRA statuses available 86 | dispatch.earliest_time = -5m 87 | dispatch.latest_time = now 88 | display.visualizations.show = 0 89 | request.ui_dispatch_app = TA-jira-service-desk-simple-addon 90 | request.ui_dispatch_view = search 91 | search = | jirafill account=_all opt=4 | stats values(statusCategory) as statusCategory by account 92 | 93 | [JIRA Service Desk - Get fields description per project] 94 | description = This report exposes JIRA fields per project 95 | dispatch.earliest_time = -5m 96 | dispatch.latest_time = now 97 | display.visualizations.show = 0 98 | request.ui_dispatch_app = TA-jira-service-desk-simple-addon 99 | request.ui_dispatch_view = search 100 | search = | jirarest account=_any target="rest/api/2/project" | spath | rename "{}.key" as key | table key | mvexpand key | append [ | makeresults | eval key="noop" | fields - _time ] | streamstats count as project_no\ 101 | | map [ | jirarest account=_any target="rest/api/2/issue/createmeta?projectKeys=$key$&expand=projects.issuetypes.fields" ] | streamstats count as result_no\ 102 | | append [ | jirarest account=_any target="rest/api/2/project" | spath | rename "{}.key" as key | table key | mvexpand key | append [ | makeresults | eval key="noop" | fields - _time ] | streamstats count as project_no ]\ 103 | | eval line_merge=case(isnum(project_no), project_no, isnum(result_no), result_no)\ 104 | | stats first(key) as project, first(_raw) as "_raw" by line_merge | fields - line_merge | eval _time=now()\ 105 | | spath | where project!="noop" 106 | 107 | [JIRA Service Desk - Get fields description for all projects] 108 | description = This report exposes JIRA fields for all projects 109 | dispatch.earliest_time = -5m 110 | dispatch.latest_time = now 111 | display.visualizations.show = 0 112 | request.ui_dispatch_app = TA-jira-service-desk-simple-addon 113 | request.ui_dispatch_view = search 114 | search = | jirarest account=_any target="rest/api/2/issue/createmeta?expand=projects.issuetypes.fields" | spath 115 | 116 | [JIRA Service Desk - Replay collection] 117 | description = This report exposes the JIRA Replay KVstore collection.\ 118 | Tickets present in this collection are previously failed issue creation attempts, which are automatically re-attempted based on the replay policy. 119 | dispatch.earliest_time = -5m 120 | dispatch.latest_time = now 121 | display.visualizations.show = 0 122 | request.ui_dispatch_app = TA-jira-service-desk-simple-addon 123 | request.ui_dispatch_view = search 124 | search = | inputlookup jira_failures_replay | eval uuid=_key 125 | 126 | [JIRA Service Desk - Issues backlog collection] 127 | description = This report exposes the JIRA issues backlog which contains records for every JIRA issue created by the add-on, this collection is\ 128 | as well used by the JIRA add-on backend for the deduplication behaviour. A status created means an issue that was created, while status status updated\ 129 | reveals an update was performed via the JIRA deduplication feature. 130 | dispatch.earliest_time = -5m 131 | dispatch.latest_time = now 132 | display.visualizations.show = 0 133 | request.ui_dispatch_app = TA-jira-service-desk-simple-addon 134 | request.ui_dispatch_view = search 135 | search = | inputlookup jira_issues_backlog | eval key=_key\ 136 | | eval ctime=strftime(round(ctime, 0), "%c"), mtime=strftime(round(mtime, 0), "%c")\ 137 | | fields key, ctime, mtime, *\ 138 | | sort 0 - mtime 139 | 140 | [JIRA Service Desk - detection of temporary issue creation failure] 141 | alert.severity = 4 142 | alert.suppress = 1 143 | alert.suppress.fields = time 144 | alert.suppress.period = 1h 145 | alert.track = 1 146 | alert.digest_mode = 0 147 | counttype = number of events 148 | cron_schedule = */5 * * * * 149 | description = This alert will detect the first failure of an issue creation, once a ticket creation has failed, it is stored in the replay KVstore. 150 | disabled = 0 151 | dispatch.earliest_time = -15m 152 | dispatch.latest_time = -30s 153 | display.general.type = statistics 154 | display.page.search.tab = statistics 155 | display.visualizations.show = 0 156 | enableSched = 1 157 | quantity = 0 158 | relation = greater than 159 | request.ui_dispatch_app = TA-jira-service-desk-simple-addon 160 | request.ui_dispatch_view = search 161 | search = (index="_internal" OR index="cim_modactions") (source="*jira_service_desk_modalert.log")\ 162 | | rex "\"key\":\"(?[^\"]*)\","\ 163 | | transaction pid maxpause=5m\ 164 | | eval jira_transaction_status=if(isnull(jira_issue), "failure", "success")\ 165 | | where jira_transaction_status="failure" AND match(_raw, "JIRA Service Desk ticket creation has failed")\ 166 | | stats first(app) as app, first(action_mode) as action_mode, values(sid) as sid, first(search_name) as search_name, first(user) as user by _time\ 167 | | eval time=strftime(_time, "%c") 168 | 169 | [JIRA Service Desk - detection of permanent issue creation failure] 170 | alert.severity = 4 171 | alert.suppress = 1 172 | alert.suppress.fields = time 173 | alert.suppress.period = 1h 174 | alert.track = 1 175 | alert.digest_mode = 0 176 | counttype = number of events 177 | cron_schedule = */5 * * * * 178 | description = This alert will detect a definitive and permanent failure of an issue creation.\ 179 | once a ticket has reached the final state of the resilient policy, it is temporary stored upon definitive deletion but creation will not be attempted anymore. 180 | disabled = 0 181 | dispatch.earliest_time = -15m 182 | dispatch.latest_time = -30s 183 | display.general.type = statistics 184 | display.page.search.tab = statistics 185 | display.visualizations.show = 0 186 | enableSched = 1 187 | quantity = 0 188 | relation = greater than 189 | request.ui_dispatch_app = TA-jira-service-desk-simple-addon 190 | request.ui_dispatch_view = search 191 | search = (index="_internal" OR index="cim_modactions") (source="*jira_service_desk_replay_modalert.log") "permanent failure!"\ 192 | | stats first(app) as app, first(action_mode) as action_mode, values(sid) as sid, first(search_name) as search_name, first(user) as user by _time\ 193 | | eval time=strftime(_time, "%c") 194 | 195 | [JIRA Service Desk - Resilient store Tracker] 196 | action.jira_service_desk_replay = 1 197 | action.jira_service_desk_replay.param.account = $result.account$ 198 | action.jira_service_desk_replay.param.ticket_ctime = $result.ctime$ 199 | action.jira_service_desk_replay.param.ticket_data = $result.data$ 200 | action.jira_service_desk_replay.param.ticket_max_attempts = $result.max_attempts$ 201 | action.jira_service_desk_replay.param.ticket_mtime = $result.mtime$ 202 | action.jira_service_desk_replay.param.ticket_no_attempts = $result.no_attempts$ 203 | action.jira_service_desk_replay.param.ticket_status = $result.status$ 204 | action.jira_service_desk_replay.param.ticket_uuid = $result.uuid$ 205 | alert.digest_mode = 0 206 | alert.suppress = 0 207 | alert.track = 0 208 | counttype = number of events 209 | description = This alert tracks failed tickets stored in the resilient KVstore and manage ticket creation re-attempts 210 | cron_schedule = */5 * * * * 211 | dispatch.earliest_time = -15m 212 | dispatch.latest_time = now 213 | display.general.type = statistics 214 | display.page.search.tab = statistics 215 | enableSched = 1 216 | quantity = 0 217 | relation = greater than 218 | request.ui_dispatch_app = TA-jira-service-desk-simple-addon 219 | request.ui_dispatch_view = search 220 | search = | getjirakv | where isnotnull(uuid)\ 221 | | table account, uuid, data, no_attempts, status, ctime, mtime\ 222 | ```KVstore containing jira tickets failure is loaded```\ 223 | \ 224 | ```mtime contains the last modification epoch time of the ticket, it will be null if this is the first time we handle this failure```\ 225 | | fillnull value="n/a" mtime\ 226 | \ 227 | ```defines the maximal numbers of attempts, the bellow expects the job to run every 5 minutes, and allows attempting the ticket creation during 72 hours```\ 228 | ```once the 72 hour period is reached, the ticket is finally purged from the KVstore```\ 229 | | eval max_attempts=(60/5)*24*3\ 230 | | eval duration=mtime-ctime\ 231 | | eval expiration=mtime+(86400*7)\ 232 | | eval status=if(mtime!="n/a" AND now()>expiration, "tagged_for_removal", status)\ 233 | \ 234 | ```convert duration to human readable```\ 235 | | eval duration=if(mtime!="n/a", tostring(mtime-ctime, "duration"), "n/a") 236 | 237 | [JIRA Service Desk - Issues statistics report per project] 238 | description = This report exposes JIRA issues statistics per project, you can use this report with the collect or mcollect command for indexing purposes 239 | dispatch.earliest_time = -5m 240 | dispatch.latest_time = now 241 | display.visualizations.show = 0 242 | request.ui_dispatch_app = TA-jira-service-desk-simple-addon 243 | request.ui_dispatch_view = search 244 | search = | jirarest account=_any target="rest/api/2/project" | spath | rename "{}.key" as key | table key | mvexpand key | append [ | makeresults | eval key="noop" | fields - _time ] | streamstats count as project_no\ 245 | \ 246 | | map [ | jirarest account=_any target="rest/api/2/search?jql=project=$key$&maxResults=0" ] | streamstats count as result_no | rex field=_raw "\"total\":\s(?\d*)"\ 247 | | append [\ 248 | | jirarest account=_any target="rest/api/2/project" | spath | rename "{}.key" as key | table key | mvexpand key | append [ | makeresults | eval key="noop" | fields - _time ] | streamstats count as project_no\ 249 | | map [ | jirarest account=_any target="rest/api/2/search?jql=project=$key$%20AND%20statuscategory%20IN%20%28%22Done%22%29&maxResults=0" ] | streamstats count as result_no | rex field=_raw "\"total\":\s(?\d*)"\ 250 | ]\ 251 | | append [\ 252 | | jirarest account=_any target="rest/api/2/project" | spath | rename "{}.key" as key | table key | mvexpand key | append [ | makeresults | eval key="noop" | fields - _time ] | streamstats count as project_no\ 253 | | map [ | jirarest account=_any target="rest/api/2/search?jql=project=$key$%20AND%20statuscategory%20IN%20%28%22To%20Do%22%29&maxResults=0" ] | streamstats count as result_no | rex field=_raw "\"total\":\s(?\d*)"\ 254 | ]\ 255 | | append [\ 256 | | jirarest account=_any target="rest/api/2/project" | spath | rename "{}.key" as key | table key | mvexpand key | append [ | makeresults | eval key="noop" | fields - _time ] | streamstats count as project_no\ 257 | | map [ | jirarest account=_any target="rest/api/2/search?jql=project=$key$%20AND%20statuscategory%20IN%20%28%22In%20Progress%22%29&maxResults=0" ] | streamstats count as result_no | rex field=_raw "\"total\":\s(?\d*)"\ 258 | ]\ 259 | | append [ | jirarest account=_any target="rest/api/2/project" | spath | rename "{}.key" as key | table key | mvexpand key | append [ | makeresults | eval key="noop" | fields - _time ] | streamstats count as project_no ]\ 260 | | eval line_merge=case(isnum(project_no), project_no, isnum(result_no), result_no)\ 261 | | stats first(key) as project, first(total_*) as "total_*" by line_merge | fields - line_merge\ 262 | \ 263 | | eval pct_total_done=round(total_done/total_issues*100, 2), pct_total_to_do=round(total_to_do/total_issues*100, 2), pct_total_in_progress=round(total_in_progress/total_issues*100, 2)\ 264 | | foreach pct_* [ eval <> = if(isnull('<>'), "0.00", '<>' ) ]\ 265 | \ 266 | | where project!="noop"\ 267 | \ 268 | | eval _time=now() | fields _time, project, pct_*, total_* 269 | -------------------------------------------------------------------------------- /package/bin/jiraoverview.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | import sys 5 | import os 6 | import time 7 | import requests 8 | import urllib3 9 | 10 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 11 | import json 12 | import logging 13 | from logging.handlers import RotatingFileHandler 14 | 15 | splunkhome = os.environ["SPLUNK_HOME"] 16 | 17 | # set logging 18 | filehandler = RotatingFileHandler( 19 | f"{splunkhome}/var/log/splunk/ta_jira_jiraoverview.log", 20 | mode="a", 21 | maxBytes=10000000, 22 | backupCount=1, 23 | ) 24 | formatter = logging.Formatter( 25 | "%(asctime)s %(levelname)s %(filename)s %(funcName)s %(lineno)d %(message)s" 26 | ) 27 | filehandler.setFormatter(formatter) 28 | log = logging.getLogger() # root logger - Good to get it only once. 29 | for hdlr in log.handlers[:]: # remove the existing file handlers 30 | if isinstance(hdlr, logging.FileHandler): 31 | log.removeHandler(hdlr) 32 | log.addHandler(filehandler) # set the new handler 33 | # set the log level to INFO, DEBUG as the default is ERROR 34 | log.setLevel(logging.INFO) 35 | 36 | sys.path.append( 37 | os.path.join(splunkhome, "etc", "apps", "TA-jira-service-desk-simple-addon", "lib") 38 | ) 39 | 40 | from splunklib.searchcommands import ( 41 | dispatch, 42 | GeneratingCommand, 43 | Configuration, 44 | Option, 45 | validators, 46 | ) 47 | 48 | # Import JIRA libs 49 | from ta_jira_libs import ( 50 | jira_get_conf, 51 | jira_get_accounts, 52 | jira_get_account, 53 | jira_build_headers, 54 | jira_handle_ssl_certificate, 55 | jira_test_connectivity, 56 | ) 57 | 58 | 59 | @Configuration(distributed=False) 60 | class GenerateTextCommand(GeneratingCommand): 61 | """ 62 | A Splunk search command that provides an overview of JIRA projects and their metrics. 63 | This command retrieves key performance indicators (KPIs) for all configured JIRA projects. 64 | 65 | The command: 66 | - Connects to all configured JIRA accounts 67 | - Retrieves the list of projects for each account 68 | - Collects metrics for each project including: 69 | - Total number of issues 70 | - Number of completed issues 71 | """ 72 | 73 | def generate(self): 74 | """ 75 | Generates the search results by collecting JIRA project metrics. 76 | 77 | This method: 78 | 1. Retrieves the JIRA configuration 79 | 2. Sets up logging and proxy settings 80 | 3. Gets the list of configured accounts 81 | 4. For each account: 82 | - Retrieves account configuration 83 | - Tests connectivity 84 | - Gets the list of projects 85 | - For each project: 86 | - Gets total issue count 87 | - Gets completed issue count 88 | - Yields the metrics 89 | 90 | The method handles: 91 | - SSL certificate verification 92 | - Proxy configuration 93 | - Error handling and logging 94 | - Multiple JIRA accounts 95 | 96 | Yields: 97 | dict: A dictionary containing: 98 | - _time: The timestamp of the request 99 | - _raw: The metrics data 100 | - account: The JIRA account name 101 | - project: The project key 102 | - type: The metric type ('total_issues' or 'total_done') 103 | - value: The metric value 104 | """ 105 | 106 | # get conf 107 | jira_conf = jira_get_conf( 108 | self._metadata.searchinfo.session_key, self._metadata.searchinfo.splunkd_uri 109 | ) 110 | 111 | # set loglevel 112 | log.setLevel(jira_conf["logging"]["loglevel"]) 113 | 114 | # global configuration 115 | proxy_conf = jira_conf["proxy"] 116 | proxy_dict = proxy_conf.get("proxy_dict", {}) 117 | 118 | # set timeout 119 | timeout = int(jira_conf["advanced_configuration"].get("timeout", 120)) 120 | 121 | # get all acounts 122 | accounts_dict = jira_get_accounts( 123 | self._metadata.searchinfo.session_key, self._metadata.searchinfo.splunkd_uri 124 | ) 125 | accounts = accounts_dict.get("accounts", []) 126 | 127 | # loop through the accounts 128 | for account in accounts: 129 | 130 | # account configuration 131 | account_conf = jira_get_account( 132 | self._metadata.searchinfo.session_key, 133 | self._metadata.searchinfo.splunkd_uri, 134 | account, 135 | ) 136 | 137 | jira_auth_mode = account_conf.get("jira_auth_mode", "basic") 138 | jira_url = account_conf.get("jira_url", None) 139 | jira_ssl_certificate_path = account_conf.get( 140 | "jira_ssl_certificate_path", None 141 | ) 142 | jira_ssl_certificate_pem = account_conf.get( 143 | "jira_ssl_certificate_pem", None 144 | ) 145 | jira_username = account_conf.get("username", None) 146 | jira_password = account_conf.get("jira_password", None) 147 | 148 | # verify the url 149 | if not jira_url.startswith("https://"): 150 | jira_url = f"https://{str(jira_url)}" 151 | 152 | # Build the authentication header for JIRA 153 | jira_headers = jira_build_headers( 154 | jira_auth_mode, jira_username, jira_password 155 | ) 156 | 157 | # SSL verification is always true, or the path to the SSL bundle, or the SSL bundle itself 158 | ssl_config, temp_cert_file = jira_handle_ssl_certificate( 159 | jira_ssl_certificate_path, jira_ssl_certificate_pem 160 | ) 161 | 162 | # ensures connectivity and proceed 163 | # test connectivity systematically 164 | connected = False 165 | try: 166 | healthcheck_response = jira_test_connectivity( 167 | self._metadata.searchinfo.session_key, 168 | self._metadata.searchinfo.splunkd_uri, 169 | account, 170 | ) 171 | connected = True 172 | except Exception as e: 173 | raise Exception( 174 | f'JIRA connect verification failed for account="{account}" with exception="{str(e)}"' 175 | ) 176 | 177 | if not connected: 178 | raise Exception( 179 | f'JIRA connect verification failed for account="{account}" with exception="{healthcheck_response.get("response")}"' 180 | ) 181 | 182 | # Get the list of projects 183 | projects_list = [] 184 | jira_check_url = f"{jira_url}/rest/api/latest/project" 185 | try: 186 | response = requests.get( 187 | url=jira_check_url, 188 | headers=jira_headers, 189 | verify=ssl_config, 190 | proxies=proxy_dict, 191 | timeout=timeout, 192 | ) 193 | if response.status_code not in (200, 201, 204): 194 | raise Exception( 195 | f'JIRA operation failed, account="{account}", url="{jira_check_url}", HTTP Error="{response.status_code}", HTTP Response="{response.text}"' 196 | ) 197 | else: 198 | logging.debug(response.text) 199 | 200 | for project_response in json.loads(response.text): 201 | projects_list.append(project_response.get("key")) 202 | 203 | logging.debug(f'list of projects="{projects_list}"') 204 | 205 | except Exception as e: 206 | logging.error( 207 | f'JIRA operation failed for account="{account}" with exception="{str(e)}"' 208 | ) 209 | raise Exception( 210 | f'JIRA operation failedfor account="{account}" with exception="{str(e)}"' 211 | ) 212 | 213 | # Loop through the projects, and return the KPIs 214 | for project in projects_list: 215 | 216 | # total count of issues 217 | jira_check_url = f"{jira_url}/rest/api/latest/search?jql=project={project}&maxResults=0" 218 | try: 219 | response = requests.get( 220 | url=jira_check_url, 221 | headers=jira_headers, 222 | verify=ssl_config, 223 | proxies=proxy_dict, 224 | timeout=timeout, 225 | ) 226 | if response.status_code not in (200, 201, 204): 227 | logging.error( 228 | f'JIRA operation failed for account="{account}" with exception="{str(e)}"' 229 | ) 230 | else: 231 | logging.debug(response.text) 232 | 233 | yield_dict = { 234 | "account": account, 235 | "project": project, 236 | "type": "total_issues", 237 | "value": json.loads(response.text).get("total"), 238 | } 239 | 240 | yield { 241 | "_time": time.time(), 242 | "_raw": yield_dict, 243 | "account": account, 244 | "project": project, 245 | "type": "total_issues", 246 | "value": json.loads(response.text).get("total"), 247 | } 248 | 249 | except Exception as e: 250 | logging.error( 251 | f'JIRA operation failed for account="{account}" with exception="{str(e)}"' 252 | ) 253 | 254 | # total done 255 | jira_check_url = f"{jira_url}/rest/api/latest/search?jql=project={project}%20AND%20statuscategory%20IN%20%28%22Done%22%29&maxResults=0" 256 | try: 257 | response = requests.get( 258 | url=jira_check_url, 259 | headers=jira_headers, 260 | verify=ssl_config, 261 | proxies=proxy_dict, 262 | timeout=timeout, 263 | ) 264 | if response.status_code not in (200, 201, 204): 265 | logging.error( 266 | f'JIRA operation failed for account="{account}" with exception="{str(e)}"' 267 | ) 268 | else: 269 | logging.debug(response.text) 270 | 271 | yield_dict = { 272 | "account": account, 273 | "project": project, 274 | "type": "total_done", 275 | "value": json.loads(response.text).get("total"), 276 | } 277 | 278 | yield { 279 | "_time": time.time(), 280 | "_raw": yield_dict, 281 | "account": account, 282 | "project": project, 283 | "type": "total_done", 284 | "value": json.loads(response.text).get("total"), 285 | } 286 | 287 | except Exception as e: 288 | logging.error( 289 | f'JIRA operation failed for account="{account}" with exception="{str(e)}"' 290 | ) 291 | 292 | # total todo 293 | jira_check_url = f"{jira_url}/rest/api/latest/search?jql=project={project}%20AND%20statuscategory%20IN%20%28%22To%20Do%22%29&maxResults=0" 294 | try: 295 | response = requests.get( 296 | url=jira_check_url, 297 | headers=jira_headers, 298 | verify=ssl_config, 299 | proxies=proxy_dict, 300 | timeout=timeout, 301 | ) 302 | if response.status_code not in (200, 201, 204): 303 | logging.error( 304 | f'JIRA operation failed for account="{account}" with exception="{str(e)}"' 305 | ) 306 | else: 307 | logging.debug(response.text) 308 | 309 | yield_dict = { 310 | "account": account, 311 | "project": project, 312 | "type": "total_to_do", 313 | "value": json.loads(response.text).get("total"), 314 | } 315 | 316 | yield { 317 | "_time": time.time(), 318 | "_raw": yield_dict, 319 | "account": account, 320 | "project": project, 321 | "type": "total_to_do", 322 | "value": json.loads(response.text).get("total"), 323 | } 324 | 325 | except Exception as e: 326 | logging.error( 327 | f'JIRA operation failed for account="{account}" with exception="{str(e)}"' 328 | ) 329 | 330 | # total todo 331 | jira_check_url = f"{jira_url}/rest/api/latest/search?jql=project={project}%20AND%20statuscategory%20IN%20%28%22In%20Progress%22%29&maxResults=0" 332 | try: 333 | response = requests.get( 334 | url=jira_check_url, 335 | headers=jira_headers, 336 | verify=ssl_config, 337 | proxies=proxy_dict, 338 | timeout=timeout, 339 | ) 340 | if response.status_code not in (200, 201, 204): 341 | logging.error( 342 | f'JIRA operation failed for account="{account}" with exception="{str(e)}"' 343 | ) 344 | else: 345 | logging.debug(response.text) 346 | 347 | yield_dict = { 348 | "account": account, 349 | "project": project, 350 | "type": "total_in_progress", 351 | "value": json.loads(response.text).get("total"), 352 | } 353 | 354 | yield { 355 | "_time": time.time(), 356 | "_raw": yield_dict, 357 | "account": account, 358 | "project": project, 359 | "type": "total_in_progress", 360 | "value": json.loads(response.text).get("total"), 361 | } 362 | 363 | except Exception as e: 364 | logging.error( 365 | f'JIRA operation failed for account="{account}" with exception="{str(e)}"' 366 | ) 367 | 368 | 369 | dispatch(GenerateTextCommand, sys.argv, sys.stdin, sys.stdout, __name__) 370 | --------------------------------------------------------------------------------