├── doc ├── images │ ├── splunkins_metadata.png │ ├── pipeline_filter_option.png │ ├── pipeline_sendsplunkfile.png │ ├── app_splunk_app_jenkins_build.png │ ├── app_splunk_app_jenkins_node.png │ ├── json_jenkins_sourcetype_old.png │ ├── splunk_for_jenkins_post_job.png │ ├── splunk_for_jenkins_config_basic.png │ ├── app_splunk_app_jenkins_master_health.png │ ├── app_splunk_app_jenkins_node_overview.png │ └── app_splunk_app_jenkins_testanalysis.png ├── splunk-devops-extend-usage.md └── extension.md ├── Jenkinsfile ├── splunk-devops └── src │ ├── test │ ├── resources │ │ ├── com │ │ │ └── splunk │ │ │ │ └── splunkjenkins │ │ │ │ ├── CoverageMetricTest │ │ │ │ ├── config.xml │ │ │ │ └── jobs │ │ │ │ │ ├── JaCoCo │ │ │ │ │ ├── workspace │ │ │ │ │ │ ├── jacoco.exec │ │ │ │ │ │ ├── classes │ │ │ │ │ │ │ └── com │ │ │ │ │ │ │ │ └── mycompany │ │ │ │ │ │ │ │ ├── App.class │ │ │ │ │ │ │ │ └── App$Util.class │ │ │ │ │ │ └── java │ │ │ │ │ │ │ └── com │ │ │ │ │ │ │ └── mycompany │ │ │ │ │ │ │ └── App.java │ │ │ │ │ └── config.xml │ │ │ │ │ ├── Cobertura │ │ │ │ │ ├── workspace │ │ │ │ │ │ └── coverage.xml │ │ │ │ │ └── config.xml │ │ │ │ │ └── Clover │ │ │ │ │ ├── config.xml │ │ │ │ │ └── workspace │ │ │ │ │ └── clover.xml │ │ │ │ ├── PostBuildGroovyScriptTest.zip │ │ │ │ └── TestResultAdapterTest │ │ │ │ └── jobs │ │ │ │ ├── testng_job1 │ │ │ │ └── config.xml │ │ │ │ └── xunit_job1 │ │ │ │ └── config.xml │ │ ├── splunk_metadata.properties │ │ └── logging.properties │ └── java │ │ └── com │ │ └── splunk │ │ └── splunkjenkins │ │ ├── SplunkResponse.java │ │ ├── utils │ │ ├── SystemConfigTest.java │ │ ├── PlainTextConsoleUtilsTest.java │ │ ├── LogEventHelperTest.java │ │ └── TestCaseResultUtilsTest.java │ │ ├── BaseTest.java │ │ ├── JdkSplunkLogHandlerTest.java │ │ ├── TestResultAdapterTest.java │ │ ├── HealthMonitorTest.java │ │ ├── SplunkSendJsonFileTest.java │ │ ├── CoverageMetricTest.java │ │ ├── listeners │ │ └── LoggingQueueListenerTest.java │ │ ├── TeeConsoleLogFilterTest.java │ │ ├── SplunkArchiveFileTest.java │ │ ├── SplunkLogServiceTest.java │ │ └── SplunkJenkinsInstallationTest.java │ └── main │ ├── webapp │ ├── images │ │ ├── splunkIcon.png │ │ ├── hec_token_list.png │ │ ├── hec_global_config.png │ │ ├── splunk_logo_black.png │ │ └── splunk_logo_green.png │ └── help-splunkAppUrl.html │ ├── resources │ ├── com │ │ └── splunk │ │ │ └── splunkjenkins │ │ │ ├── SplunkJenkinsInstallation │ │ │ ├── help-retriesOnError.html │ │ │ ├── help-no-splunkAppUrl.html │ │ │ ├── help-maxEventsBatchSize.html │ │ │ ├── help-metadataHost.html │ │ │ ├── help-metadataSource.html │ │ │ ├── help-splunkAppUrl.html │ │ │ ├── config.properties │ │ │ ├── help-rawEventEnabled.html │ │ │ ├── help-delay.html │ │ │ ├── help-indexName.html │ │ │ ├── help-globalPipelineFilter.html │ │ │ ├── help-httpInputConfig.html │ │ │ ├── lib.js │ │ │ ├── help-host.html │ │ │ ├── help-metaDataConfig.html │ │ │ ├── help-token.html │ │ │ ├── help-ignoredJobs.html │ │ │ ├── help-groovyBinding.html │ │ │ └── config.jelly │ │ │ ├── SplunkArtifactNotifier │ │ │ ├── help-skipGlobalSplunkArchive.html │ │ │ ├── config.properties │ │ │ ├── help-sizeLimit.html │ │ │ ├── help-publishFromSlave.html │ │ │ └── config.jelly │ │ │ ├── listeners │ │ │ └── Messages.properties │ │ │ ├── model │ │ │ └── MetaDataConfigItem │ │ │ │ ├── help-value.html │ │ │ │ ├── help-keyName.html │ │ │ │ ├── help-dataSource.html │ │ │ │ └── config.jelly │ │ │ └── Messages.properties │ ├── index.jelly │ ├── sample.groovy │ └── metadata.properties │ ├── java │ └── com │ │ └── splunk │ │ └── splunkjenkins │ │ ├── model │ │ ├── TestStatus.java │ │ ├── EmptyTestCaseGroup.java │ │ ├── JunitResultAggregateAdapter.java │ │ ├── EventType.java │ │ ├── LoggingJobExtractor.java │ │ ├── JunitTestCaseGroup.java │ │ ├── JunitResultAdapter.java │ │ ├── AbstractTestResultAdapter.java │ │ ├── TestCaseResult.java │ │ └── CloverCoverageMetrics.java │ │ ├── utils │ │ ├── CoverageDetailJsonSerializer.java │ │ ├── SpecialDoubleAdapter.java │ │ ├── SpecialFloatAdapter.java │ │ ├── MultipleHostResolver.java │ │ ├── RemoteUtils.java │ │ ├── CustomSSLConnectionSocketFactory.java │ │ └── PlainTextConsoleUtils.java │ │ ├── links │ │ ├── HealthLinkAction.java │ │ ├── ComputerLogActionFactory.java │ │ ├── BuildableItemActionFactory.java │ │ ├── LinkSplunkAction.java │ │ ├── RunActionFactory.java │ │ └── ReportAction.java │ │ ├── listeners │ │ ├── UserSecurityListener.java │ │ ├── LoggingItemListener.java │ │ ├── LoggingComputerListener.java │ │ └── LoggingConfigListener.java │ │ ├── LoggingInitStep.java │ │ ├── WebPostAccessLogger.java │ │ └── SplunkArtifactNotifier.java │ └── groovy │ └── com │ └── splunk │ └── splunkjenkins │ └── UserActionDSL.groovy ├── splunk-devops-extend └── src │ ├── main │ ├── resources │ │ ├── com │ │ │ └── splunk │ │ │ │ └── splunkjenkins │ │ │ │ ├── SplunkMessageStep │ │ │ │ ├── help-globalScriptEnabled.html │ │ │ │ └── config.jelly │ │ │ │ ├── SplunkPipelineJobProperty │ │ │ │ ├── help.html │ │ │ │ ├── help-enableDiagram.html │ │ │ │ └── config-details.jelly │ │ │ │ ├── SplunkConsoleLogStep │ │ │ │ └── config.jelly │ │ │ │ └── SplunkLogFileStep │ │ │ │ ├── config.properties │ │ │ │ └── config.jelly │ │ ├── index.jelly │ │ └── logging.properties │ └── java │ │ └── com │ │ └── splunk │ │ └── splunkjenkins │ │ ├── console │ │ ├── DelayBufferedConsoleWork.java │ │ ├── ConsoleRecordCacheUtils.java │ │ ├── SplunkConsoleTaskListenerDecorator.java │ │ ├── ConsoleNoteHandler.java │ │ ├── LabelConsoleLineStream.java │ │ ├── PipelineConsoleDecoder.java │ │ └── SplunkTaskListenerFactory.java │ │ ├── SplunkPipelineJobProperty.java │ │ ├── SplunkinsDslVariable.java │ │ ├── PipelineGraphVizSupport.java │ │ ├── SplunkConsoleLogStep.java │ │ └── SplunkLogFileStep.java │ └── test │ └── java │ └── com │ └── splunk │ └── splunkjenkins │ ├── console │ ├── ConsoleNoteHandlerTest.java │ └── LabelMarkupTextTest.java │ ├── SplunkinsDslVariableTest.java │ ├── SplunkLogFileStepTest.java │ ├── PipelineExecuteDiagramTest.java │ ├── StageStepNodesTest.java │ └── SplunkConsoleLogStepTest.java ├── .gitignore ├── Jenkinsfile.mvn ├── readme.md └── splunk-devops-shaded └── pom.xml /doc/images/splunkins_metadata.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/splunk-devops-plugin/HEAD/doc/images/splunkins_metadata.png -------------------------------------------------------------------------------- /doc/images/pipeline_filter_option.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/splunk-devops-plugin/HEAD/doc/images/pipeline_filter_option.png -------------------------------------------------------------------------------- /doc/images/pipeline_sendsplunkfile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/splunk-devops-plugin/HEAD/doc/images/pipeline_sendsplunkfile.png -------------------------------------------------------------------------------- /doc/images/app_splunk_app_jenkins_build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/splunk-devops-plugin/HEAD/doc/images/app_splunk_app_jenkins_build.png -------------------------------------------------------------------------------- /doc/images/app_splunk_app_jenkins_node.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/splunk-devops-plugin/HEAD/doc/images/app_splunk_app_jenkins_node.png -------------------------------------------------------------------------------- /doc/images/json_jenkins_sourcetype_old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/splunk-devops-plugin/HEAD/doc/images/json_jenkins_sourcetype_old.png -------------------------------------------------------------------------------- /doc/images/splunk_for_jenkins_post_job.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/splunk-devops-plugin/HEAD/doc/images/splunk_for_jenkins_post_job.png -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | // https://github.com/jenkins-infra/pipeline-library 2 | buildPlugin(platforms: ['linux'], jdkVersions: [17], useArtifactCachingProxy:false) 3 | -------------------------------------------------------------------------------- /doc/images/splunk_for_jenkins_config_basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/splunk-devops-plugin/HEAD/doc/images/splunk_for_jenkins_config_basic.png -------------------------------------------------------------------------------- /splunk-devops/src/test/resources/com/splunk/splunkjenkins/CoverageMetricTest/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /doc/images/app_splunk_app_jenkins_master_health.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/splunk-devops-plugin/HEAD/doc/images/app_splunk_app_jenkins_master_health.png -------------------------------------------------------------------------------- /doc/images/app_splunk_app_jenkins_node_overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/splunk-devops-plugin/HEAD/doc/images/app_splunk_app_jenkins_node_overview.png -------------------------------------------------------------------------------- /doc/images/app_splunk_app_jenkins_testanalysis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/splunk-devops-plugin/HEAD/doc/images/app_splunk_app_jenkins_testanalysis.png -------------------------------------------------------------------------------- /splunk-devops/src/main/webapp/images/splunkIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/splunk-devops-plugin/HEAD/splunk-devops/src/main/webapp/images/splunkIcon.png -------------------------------------------------------------------------------- /splunk-devops/src/main/webapp/help-splunkAppUrl.html: -------------------------------------------------------------------------------- 1 |

Please configure the URL for splunk_app_jenkins in 2 | Splunk plugin configure advance section 3 |

-------------------------------------------------------------------------------- /splunk-devops/src/main/webapp/images/hec_token_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/splunk-devops-plugin/HEAD/splunk-devops/src/main/webapp/images/hec_token_list.png -------------------------------------------------------------------------------- /splunk-devops/src/main/webapp/images/hec_global_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/splunk-devops-plugin/HEAD/splunk-devops/src/main/webapp/images/hec_global_config.png -------------------------------------------------------------------------------- /splunk-devops/src/main/webapp/images/splunk_logo_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/splunk-devops-plugin/HEAD/splunk-devops/src/main/webapp/images/splunk_logo_black.png -------------------------------------------------------------------------------- /splunk-devops/src/main/webapp/images/splunk_logo_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/splunk-devops-plugin/HEAD/splunk-devops/src/main/webapp/images/splunk_logo_green.png -------------------------------------------------------------------------------- /splunk-devops-extend/src/main/resources/com/splunk/splunkjenkins/SplunkMessageStep/help-globalScriptEnabled.html: -------------------------------------------------------------------------------- 1 |
2 | Run the customized command in the plugin config page 3 |
-------------------------------------------------------------------------------- /splunk-devops-extend/src/main/resources/com/splunk/splunkjenkins/SplunkPipelineJobProperty/help.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | Enable sending pipeline execution diagram by default 4 |

5 |
-------------------------------------------------------------------------------- /splunk-devops/src/test/resources/splunk_metadata.properties: -------------------------------------------------------------------------------- 1 | source=unit_test 2 | host=dev 3 | sourcetype=json:jenkins 4 | sourcetype_text=text:jenkins 5 | file.sourcetype=text:jenkins 6 | json_file.sourcetype=json:jenkins -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/SplunkJenkinsInstallation/help-retriesOnError.html: -------------------------------------------------------------------------------- 1 |
2 |

The number of times the plugin will try to send data to Splunk before giving up.

3 |
4 | -------------------------------------------------------------------------------- /splunk-devops-extend/src/main/resources/com/splunk/splunkjenkins/SplunkConsoleLogStep/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | Forwarding pipeline console log to Splunk 4 | -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/SplunkJenkinsInstallation/help-no-splunkAppUrl.html: -------------------------------------------------------------------------------- 1 |

Please configure the URL for splunk_app_jenkins in 2 | Splunk plugin configure advance section 3 |

-------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/SplunkJenkinsInstallation/help-maxEventsBatchSize.html: -------------------------------------------------------------------------------- 1 |
2 |

The buffer size of the batch of events to send to Splunk. The default value is 262144(256KB).

3 |
4 | -------------------------------------------------------------------------------- /splunk-devops/src/test/resources/com/splunk/splunkjenkins/PostBuildGroovyScriptTest.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/splunk-devops-plugin/HEAD/splunk-devops/src/test/resources/com/splunk/splunkjenkins/PostBuildGroovyScriptTest.zip -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/SplunkJenkinsInstallation/help-metadataHost.html: -------------------------------------------------------------------------------- 1 |
2 | Jenkins master hostname, used in Splunk query 3 | 4 | search host="servername" 5 | 6 |
-------------------------------------------------------------------------------- /splunk-devops-extend/src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 2 | 5 |
6 | This plugin extracts pipeline job stages information 7 |
8 | -------------------------------------------------------------------------------- /splunk-devops-extend/src/main/resources/com/splunk/splunkjenkins/SplunkPipelineJobProperty/help-enableDiagram.html: -------------------------------------------------------------------------------- 1 |
2 | pipeline execution diagram is described in graphviz dot format, see also 3 | http://graphviz.org/documentation/ 4 |
-------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/SplunkJenkinsInstallation/help-metadataSource.html: -------------------------------------------------------------------------------- 1 |
2 | Event source name, where the event originated, used in Splunk query 3 | 4 | search source="sourcename" 5 | 6 |
-------------------------------------------------------------------------------- /splunk-devops-extend/src/main/resources/logging.properties: -------------------------------------------------------------------------------- 1 | handlers=java.util.logging.ConsoleHandler 2 | java.util.logging.ConsoleHandler.level=ALL 3 | com.splunk.splunkjenkins.console.level=FINEST 4 | java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter -------------------------------------------------------------------------------- /splunk-devops/src/test/resources/com/splunk/splunkjenkins/CoverageMetricTest/jobs/JaCoCo/workspace/jacoco.exec: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/splunk-devops-plugin/HEAD/splunk-devops/src/test/resources/com/splunk/splunkjenkins/CoverageMetricTest/jobs/JaCoCo/workspace/jacoco.exec -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/SplunkArtifactNotifier/help-skipGlobalSplunkArchive.html: -------------------------------------------------------------------------------- 1 |
2 | To skip the global post job archiving DSL e.g. 3 | 4 | archive("**/*.log") 5 | 6 | when the DSL does not fit for specific set of job. 7 |
-------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/SplunkJenkinsInstallation/help-splunkAppUrl.html: -------------------------------------------------------------------------------- 1 |

2 | The Splunk App for Jenkins host address, e.g. 3 | http://hostname:port/en-US/app/splunk_app_jenkins/
4 |

-------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/SplunkJenkinsInstallation/config.properties: -------------------------------------------------------------------------------- 1 | ConfigTitle=Splunk for Jenkins Configuration 2 | Hostname=Splunk Host Name 3 | default_metadata=\ 4 | host={0}\ 5 | #audit trail is not enabled by default\n\ 6 | jenkins_config.enabled=false 7 | #end of metadata -------------------------------------------------------------------------------- /splunk-devops/src/test/resources/com/splunk/splunkjenkins/CoverageMetricTest/jobs/JaCoCo/workspace/classes/com/mycompany/App.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/splunk-devops-plugin/HEAD/splunk-devops/src/test/resources/com/splunk/splunkjenkins/CoverageMetricTest/jobs/JaCoCo/workspace/classes/com/mycompany/App.class -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/SplunkJenkinsInstallation/help-rawEventEnabled.html: -------------------------------------------------------------------------------- 1 |
2 |

HTTP Event Collector supports arbitrary data formats similar to Splunk forwarders, 3 | relying on line-breaking rules etc. Supported in Splunk 6.3.1511 and later release 4 |

5 |
6 | -------------------------------------------------------------------------------- /splunk-devops/src/test/resources/com/splunk/splunkjenkins/CoverageMetricTest/jobs/JaCoCo/workspace/classes/com/mycompany/App$Util.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/splunk-devops-plugin/HEAD/splunk-devops/src/test/resources/com/splunk/splunkjenkins/CoverageMetricTest/jobs/JaCoCo/workspace/classes/com/mycompany/App$Util.class -------------------------------------------------------------------------------- /splunk-devops-extend/src/main/resources/com/splunk/splunkjenkins/SplunkPipelineJobProperty/config-details.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 2 |
3 | Splunk plugin for Jenkins provides deep insights into your Jenkins controller and agent infrastructure, job and 4 | build details such as console logs, status, artifacts, and an incredibly efficient way to analyze test results. 5 |
6 | -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/SplunkJenkinsInstallation/help-delay.html: -------------------------------------------------------------------------------- 1 |
2 |

Schedules the specified task for repeated fixed-rate execution beginning after the specified delay. Subsequent 3 | executions take place at approximately regular intervals, separated by the specified period.

4 |
5 | -------------------------------------------------------------------------------- /splunk-devops/src/test/resources/logging.properties: -------------------------------------------------------------------------------- 1 | handlers=java.util.logging.FileHandler 2 | java.util.logging.FileHandler.pattern=debug.log 3 | java.util.logging.FileHandler.level=ALL 4 | com.splunk.splunkjenkins.level=FINEST 5 | org.apache.http.level = ALL 6 | jenkins.model.Jenkins.level = ALL 7 | java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/model/TestStatus.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.model; 2 | 3 | /** 4 | * TestNG used PASS,FAIL,SKIP, Junit(CaseResult.Status) used passed,skipped,pass,failed,fixed,regression 5 | * collapse them into 3 (passed, failure, skipped) 6 | */ 7 | public enum TestStatus { 8 | PASSED, FAILURE, SKIPPED 9 | } 10 | -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/SplunkJenkinsInstallation/help-indexName.html: -------------------------------------------------------------------------------- 1 |
2 |

The index in Splunk to which the events will be indexed to. Defaults to "main" index in Splunk. Note: If using a 3 | custom index, that index must be already exist on your Splunk instance. Splunk-Jenkins will not create it for 4 | you.

5 |
6 | -------------------------------------------------------------------------------- /splunk-devops-extend/src/main/resources/com/splunk/splunkjenkins/SplunkLogFileStep/config.properties: -------------------------------------------------------------------------------- 1 | ant_pattern=\ 2 | You can use wildcards like "{1}" \ 3 | See \ 4 | the {0} attribute of Ant fileset for the exact format.\ 5 | The base directory is the workspace.\ 6 | You can only archive files that are located in your workspace. -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/SplunkArtifactNotifier/config.properties: -------------------------------------------------------------------------------- 1 | ant_pattern=\ 2 | You can use wildcards like "{1}" \ 3 | See \ 4 | the {0} attribute of Ant fileset for the exact format.\ 5 | The base directory is the workspace.\ 6 | You can only archive files that are located in your workspace. -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/sample.groovy: -------------------------------------------------------------------------------- 1 | //send job metadata and junit reports with page size set to 50 (each event contains max 50 test cases) 2 | splunkins.sendTestReport(50) 3 | //send coverage, each event contains max 50 class metrics 4 | splunkins.sendCoverageReport(50) 5 | //send all logs from workspace to splunk, with each file size limits to 10MB 6 | splunkins.archive("**/*.log", null, false, "10MB") 7 | 8 | //end -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/SplunkArtifactNotifier/help-sizeLimit.html: -------------------------------------------------------------------------------- 1 |
2 | Limit the single file size to prevent publishing the whole huge log file generated accidentally by some program and uses up 3 | your Splunk daily volume license. The plugin will log a "file truncated to size:xx" event in Splunk when size limit is reached 4 | and will stop publishing the remaining content. 5 |
-------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | work 3 | 4 | # Vim junk files 5 | .*.swp 6 | *~ 7 | 8 | # IntelliJ project files 9 | .idea/ 10 | *.iml 11 | 12 | # Eclipse IDE project files 13 | .classpath 14 | .project 15 | .settings/ 16 | /splunkjenkins/nbproject/ 17 | 18 | #debug log 19 | debug.log 20 | debug.log.lck 21 | 22 | # OS X 23 | .DS_Store 24 | 25 | # mvn versions:set 26 | pom.xml.versionsBackup 27 | pom.xml.releaseBackup 28 | dependency-reduced-pom.xml 29 | -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/SplunkJenkinsInstallation/help-globalPipelineFilter.html: -------------------------------------------------------------------------------- 1 |
2 | When enabled and splunk-devops-extend is installed, 3 | all pipeline jobs' console logs will be sent by default.
4 | There is no need to use the sendSplunkConsoleLog {…} step in Scripted Pipeline, 5 | or the sendSplunkConsoleLog() option in Declarative Pipeline 6 |
7 | -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/listeners/Messages.properties: -------------------------------------------------------------------------------- 1 | audit.abort_job=aborted job {0} 2 | audit.start_job=started job {0} 3 | audit.create_item=created {0} 4 | audit.rename_item=renamed {0} to {1} 5 | audit.update_item=updated {0} 6 | audit.delete_item=deleted {0} 7 | audit.cloned_item=cloned item {0} from {1} 8 | audit.user_fail_login=failed to login 9 | audit.user_fail_auth=failed to authenticate 10 | audit.user_logout=logged out 11 | audit.user_login=logged in -------------------------------------------------------------------------------- /splunk-devops-extend/src/main/resources/com/splunk/splunkjenkins/SplunkMessageStep/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/model/MetaDataConfigItem/help-value.html: -------------------------------------------------------------------------------- 1 |
2 | The event can be turned off by setting it to Disabled 3 | Index is a data repository in Splunk, you can set a different data retention policy and access privileges for each 4 | index in Splunk. 5 | Source Type is used by Splunk to determine how incoming data is formatted. 6 | It is recommended to use the plugin's default config if you are using Splunk App for Jenkins as the App expects a certain metadata format. 7 |
-------------------------------------------------------------------------------- /splunk-devops-extend/src/test/java/com/splunk/splunkjenkins/console/ConsoleNoteHandlerTest.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.console; 2 | 3 | import junit.framework.TestCase; 4 | 5 | public class ConsoleNoteHandlerTest extends TestCase { 6 | 7 | public void testRead() { 8 | String tag = ""; 9 | ConsoleNoteHandler handler = new ConsoleNoteHandler(); 10 | handler.read(tag); 11 | assertEquals("test", handler.getLabel()); 12 | assertEquals("testId", handler.getNodeId()); 13 | } 14 | } -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/metadata.properties: -------------------------------------------------------------------------------- 1 | source=jenkins 2 | #for new type not specified with x.index=y 3 | index=jenkins_statistics 4 | sourcetype=json:jenkins 5 | sourcetype_text=text:jenkins 6 | #default index and source type settings 7 | build_report.index=jenkins 8 | build_event.index=jenkins_statistics 9 | queue_info.index=jenkins_statistics 10 | jenkins_config.index=jenkins_statistics 11 | slave_info.index=jenkins_statistics 12 | console_log.index=jenkins_console 13 | file.index=jenkins_artifact 14 | file.sourcetype=text:jenkins 15 | json_file.index=jenkins_artifact 16 | json_file.sourcetype=json:jenkins -------------------------------------------------------------------------------- /splunk-devops/src/test/java/com/splunk/splunkjenkins/SplunkResponse.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | public class SplunkResponse { 7 | List entry; 8 | 9 | public String getFirst(String key) { 10 | return getItem(0, key); 11 | } 12 | 13 | public void setEntry(List entry) { 14 | this.entry = entry; 15 | } 16 | 17 | public String getItem(int idx, String key) { 18 | return entry.get(idx).content.get(key).toString(); 19 | } 20 | 21 | public static class EntryItem { 22 | Map content; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/model/MetaDataConfigItem/help-keyName.html: -------------------------------------------------------------------------------- 1 |
2 |

3 | The event can be turned off by setting it to Disabled.
4 | 5 | Index is a data repository in Splunk, you can set a different data retention policy and access privileges for 6 | each index in Splunk.
7 | 8 | Source Type is used by Splunk to determine how incoming data is formatted.
9 |

10 | 11 |

It is recommended to use the plugin's default config 12 | if you are using Splunk App for Jenkins as the App expects a certain metadata format. 13 |

14 | 15 |
-------------------------------------------------------------------------------- /splunk-devops/src/test/resources/com/splunk/splunkjenkins/CoverageMetricTest/jobs/JaCoCo/workspace/java/com/mycompany/App.java: -------------------------------------------------------------------------------- 1 | package com.mycompany; 2 | 3 | /** 4 | * Hello world! 5 | */ 6 | public class App { 7 | public String getText() { 8 | return "hello"; 9 | } 10 | 11 | public int getResult(boolean even) { 12 | if (even) { 13 | return 2; 14 | } else { 15 | return 1; 16 | } 17 | } 18 | 19 | public boolean isOk() { 20 | return false; 21 | } 22 | 23 | public static class Util { 24 | public static int calc(int a, int b) { 25 | return a + b; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/SplunkJenkinsInstallation/help-httpInputConfig.html: -------------------------------------------------------------------------------- 1 |
2 |
You need enable HTTP Event Collector in Splunk.
 3 | To enable it, go to Splunk Settings > Data inputs > HTTP Event Collector.
 4 | Then click the Global Settings button in the upper-right corner.
 5 | You can configure HTTP Port number and SSL on the Global Settings.
 6 | Port is 8088, and SSL is enabled by default.
 7 |     
8 | 9 |

10 | See more details at HTTP Event Collector 11 | walk through 12 |

13 |
14 | -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/SplunkJenkinsInstallation/lib.js: -------------------------------------------------------------------------------- 1 | function checkDatasourceKey(event) { 2 | item=event.target 3 | var configItem = item.closest("[name=metadataItemSet]") 4 | if (configItem==null){ 5 | return 6 | } 7 | var tableNode = configItem.querySelector(".splunk-meta-config-value") 8 | if (item.value == "disabled") { 9 | tableNode.style.display = "none" 10 | tableNode.nextSibling.style.display = "none" 11 | } else { 12 | tableNode.style.display = "" 13 | tableNode.nextSibling.style.display = ""; 14 | } 15 | } 16 | 17 | Behaviour.specify(".splunk-meta-config-item", 'splk-config-item-check', 0, function (ele) { 18 | ele.onchange = checkDatasourceKey 19 | }); -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/SplunkJenkinsInstallation/help-host.html: -------------------------------------------------------------------------------- 1 |
2 |

The Splunk Indexer hostname or IP address, multiple hosts separated by comma. Example: 3 | "192.168.1.4,192.168.1.5"

4 |
    5 |
  • For Splunk cloud user, the host name is something like http-inputs-xx.splunkcloud.com or 6 | http-inputs-xx.cloud.splunk.com, and HTTP Input Port is 443 7 |
  • 8 |
  • For Splunk enterprise user, the host name is the indexer host name, and HTTP Input Port is 8088 by default 9 |
  • 10 |
11 | You need enable "HTTP Event Collector" In Splunk to use the plugin, please checkout 12 | HTTP Event Collector 13 | 14 |
15 | -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/SplunkJenkinsInstallation/help-metaDataConfig.html: -------------------------------------------------------------------------------- 1 |
2 |

Specify index, host, source, soucetype for the events.

3 |

index is a data repository in Splunk, please create the 4 indexes in Splunk: 4 | 5 | jenkins, jenkins_statistics, jenkins_console and jenkins_artifact 6 | 7 |

8 |

9 | host is used to identify which Jenkins master is the source of the data. 10 | the metadata can be used in Splunk query language such as
11 | index="jenkins" host="servername" sourcetype="json:jenkins"
12 |

13 | 14 |

15 | See more details 16 |

17 |
18 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/utils/CoverageDetailJsonSerializer.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.utils; 2 | 3 | import com.splunk.splunkjenkins.model.CoverageMetricsAdapter; 4 | import shaded.splk.com.google.gson.JsonElement; 5 | import shaded.splk.com.google.gson.JsonSerializationContext; 6 | import shaded.splk.com.google.gson.JsonSerializer; 7 | 8 | import java.lang.reflect.Type; 9 | 10 | public class CoverageDetailJsonSerializer implements JsonSerializer { 11 | @Override 12 | public JsonElement serialize(CoverageMetricsAdapter.CoverageDetail coverageDetail, Type type, 13 | JsonSerializationContext jsonSerializationContext) { 14 | return jsonSerializationContext.serialize(coverageDetail.getReport()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /splunk-devops/src/test/java/com/splunk/splunkjenkins/utils/SystemConfigTest.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.utils; 2 | 3 | import com.splunk.splunkjenkins.listeners.LoggingConfigListener; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.assertFalse; 7 | import static org.junit.Assert.assertTrue; 8 | 9 | public class SystemConfigTest { 10 | @Test 11 | public void testConfigXmlIgnored() { 12 | assertTrue(LoggingConfigListener.IGNORED.matcher("/opt/jenkins/queue.xml").find()); 13 | assertTrue(LoggingConfigListener.IGNORED.matcher("/opt/jenkins/hudson.model.UpdateCenter.xml").find()); 14 | assertTrue(LoggingConfigListener.IGNORED.matcher("/opt/jenkins/job/foo/builds/1/config.xml").find()); 15 | assertFalse(LoggingConfigListener.IGNORED.matcher("/opt/jenkins/job/foo/config.xml").find()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/links/HealthLinkAction.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.links; 2 | 3 | import com.splunk.splunkjenkins.Messages; 4 | import com.splunk.splunkjenkins.SplunkJenkinsInstallation; 5 | import hudson.Extension; 6 | import hudson.model.ManagementLink; 7 | 8 | @SuppressWarnings("unused") 9 | @Extension 10 | public class HealthLinkAction extends ManagementLink { 11 | @Override 12 | public String getIconFileName() { 13 | return Messages.SplunkIconName(); 14 | } 15 | 16 | @Override 17 | public String getDisplayName() { 18 | return "Jenkins Health"; 19 | } 20 | 21 | @Override 22 | public String getUrlName() { 23 | SplunkJenkinsInstallation instance = SplunkJenkinsInstallation.get(); 24 | return instance.getAppUrlOrHelp() + "jenkins_health?form.hostname=" + instance.getMetadataHost(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/SplunkJenkinsInstallation/help-token.html: -------------------------------------------------------------------------------- 1 |
2 |

You can find the HTTP Event Collector Token in Splunk, go to Splunk Settings > Data inputs > HTTP Event 3 | Collector. 4 |

5 | 6 |

7 | See more details at HTTP Event Collector 8 | walk through 9 |

10 |

To verify the token via curl command

11 |
12 | hec_host=<splunk-host>
13 | hec_port=8088
14 | hec_token=<splunk-token>
15 | curl -k "https://${hec_host}:${hec_port}/services/collector/event" -H "Authorization: Splunk ${hec_token}" -d \
16 | '{"host":"test-host","index":"jenkins_console","sourcetype":"json:jenkins","source":"logger://dummy","event":{"level":"INFO","log_source":"cmdline","message":"Test HEC"}}'
17 | 
18 |     
19 |
20 | -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/SplunkArtifactNotifier/help-publishFromSlave.html: -------------------------------------------------------------------------------- 1 |
2 | Publish log files directly from the agent, instead of proxy for the process to the master. 3 | When publish from agent is selected, Jenkins master will transfer the plugin and its dependence to agent and 4 | initiate 5 | the publishing process from the agent. 6 | Take below into consideration: 7 |
    8 |
  • agent and master load 9 |
  • 10 |
  • 11 | log file size 12 |
  • 13 |
  • 14 | agent type, long lived agent or one time use agent 15 |
  • 16 |
17 | Rule of thumb for selecting publish type: 18 | 19 |
    20 |
  • a. if log files size is less than 5MB, publish from master is preferred.
  • 21 |
  • b. if Splunk instance is in an isolated network which is not reachable from agent, you need publish from 22 | master. 23 |
  • 24 |
25 | 26 |
27 | -------------------------------------------------------------------------------- /splunk-devops/src/test/java/com/splunk/splunkjenkins/BaseTest.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins; 2 | 3 | import com.splunk.splunkjenkins.utils.SplunkLogService; 4 | import org.junit.After; 5 | import org.junit.Before; 6 | import org.junit.Rule; 7 | import org.jvnet.hudson.test.JenkinsRule; 8 | 9 | import static com.splunk.splunkjenkins.SplunkConfigUtil.checkTokenAvailable; 10 | import static com.splunk.splunkjenkins.utils.LogEventHelper.getDefaultDslScript; 11 | 12 | public class BaseTest { 13 | @Rule 14 | public JenkinsRule j = new JenkinsRule(); 15 | 16 | @Before 17 | public void setUp() throws Exception { 18 | org.junit.Assume.assumeTrue(checkTokenAvailable()); 19 | SplunkJenkinsInstallation.get().setScriptContent(getDefaultDslScript()); 20 | SplunkJenkinsInstallation.get().updateCache(); 21 | } 22 | 23 | @After 24 | public void tearDown() { 25 | SplunkLogService.getInstance().stopWorker(); 26 | SplunkLogService.getInstance().releaseConnection(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/utils/SpecialDoubleAdapter.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.utils; 2 | 3 | 4 | import shaded.splk.com.google.gson.TypeAdapter; 5 | import shaded.splk.com.google.gson.stream.JsonReader; 6 | import shaded.splk.com.google.gson.stream.JsonToken; 7 | import shaded.splk.com.google.gson.stream.JsonWriter; 8 | 9 | import java.io.IOException; 10 | 11 | public class SpecialDoubleAdapter extends TypeAdapter { 12 | @Override 13 | public void write(JsonWriter jsonWriter, Double number) throws IOException { 14 | if (number == null || Double.isNaN(number) || Double.isInfinite(number)) { 15 | jsonWriter.nullValue(); 16 | } else { 17 | jsonWriter.value(number); 18 | } 19 | } 20 | 21 | @Override 22 | public Double read(JsonReader in) throws IOException { 23 | if (in.peek() == JsonToken.NULL) { 24 | in.nextNull(); 25 | return null; 26 | } 27 | return in.nextDouble(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/utils/SpecialFloatAdapter.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.utils; 2 | 3 | 4 | import shaded.splk.com.google.gson.TypeAdapter; 5 | import shaded.splk.com.google.gson.stream.JsonReader; 6 | import shaded.splk.com.google.gson.stream.JsonToken; 7 | import shaded.splk.com.google.gson.stream.JsonWriter; 8 | 9 | import java.io.IOException; 10 | 11 | public class SpecialFloatAdapter extends TypeAdapter { 12 | @Override 13 | public void write(JsonWriter jsonWriter, Float number) throws IOException { 14 | if (number == null || Float.isNaN(number) || Float.isInfinite(number)) { 15 | jsonWriter.nullValue(); 16 | } else { 17 | jsonWriter.value(number); 18 | } 19 | } 20 | 21 | @Override 22 | public Float read(JsonReader in) throws IOException { 23 | if (in.peek() == JsonToken.NULL) { 24 | in.nextNull(); 25 | return null; 26 | } 27 | return (float) in.nextDouble(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/links/ComputerLogActionFactory.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.links; 2 | 3 | import com.splunk.splunkjenkins.SplunkJenkinsInstallation; 4 | import com.splunk.splunkjenkins.utils.LogEventHelper; 5 | import hudson.Extension; 6 | import hudson.model.Action; 7 | import hudson.model.Computer; 8 | import hudson.model.TransientComputerActionFactory; 9 | 10 | import java.util.Collections; 11 | import java.util.Collection; 12 | 13 | //TODO add agent page and update link 14 | @Extension 15 | public class ComputerLogActionFactory extends TransientComputerActionFactory { 16 | @Override 17 | public Collection createFor(Computer target) { 18 | String query = new LogEventHelper.UrlQueryBuilder() 19 | .putIfAbsent("master", SplunkJenkinsInstallation.get().getMetadataHost()) 20 | .putIfAbsent("slave", target.getName()) 21 | .build(); 22 | return Collections.singleton(new LinkSplunkAction("jenkins_slave", query, "Splunk")); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/links/BuildableItemActionFactory.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.links; 2 | 3 | import com.splunk.splunkjenkins.SplunkJenkinsInstallation; 4 | import com.splunk.splunkjenkins.utils.LogEventHelper; 5 | import hudson.Extension; 6 | import hudson.Util; 7 | import hudson.model.AbstractProject; 8 | import hudson.model.Action; 9 | import hudson.model.BuildableItem; 10 | import jenkins.model.TransientActionFactory; 11 | 12 | import edu.umd.cs.findbugs.annotations.NonNull; 13 | import java.util.Collection; 14 | import java.util.Collections; 15 | 16 | @SuppressWarnings("unused") 17 | @Extension 18 | public class BuildableItemActionFactory extends TransientActionFactory { 19 | @Override 20 | public Class type() { 21 | return BuildableItem.class; 22 | } 23 | 24 | @NonNull 25 | @Override 26 | public Collection createFor(@NonNull BuildableItem target) { 27 | return Collections.singleton(new LinkSplunkAction("build", "", "Splunk")); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /splunk-devops-extend/src/main/resources/com/splunk/splunkjenkins/SplunkLogFileStep/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/utils/MultipleHostResolver.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.utils; 2 | 3 | import shaded.splk.org.apache.http.conn.DnsResolver; 4 | 5 | import java.net.InetAddress; 6 | import java.net.UnknownHostException; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public class MultipleHostResolver implements DnsResolver { 11 | public static final String NAME_DELIMITER = ","; 12 | 13 | @Override 14 | public InetAddress[] resolve(final String host) throws UnknownHostException { 15 | if (host == null) { 16 | return null; 17 | } 18 | String hostname = host; 19 | //split by comma 20 | String[] hosts = hostname.split(NAME_DELIMITER); 21 | List addressList = new ArrayList<>(); 22 | for (String endpointHost : hosts) { 23 | InetAddress[] addresses = InetAddress.getAllByName(endpointHost); 24 | for (InetAddress address : addresses) { 25 | addressList.add(address); 26 | } 27 | } 28 | return addressList.toArray(new InetAddress[0]); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /splunk-devops-extend/src/main/java/com/splunk/splunkjenkins/console/DelayBufferedConsoleWork.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.console; 2 | 3 | import hudson.Extension; 4 | import hudson.model.AsyncPeriodicWork; 5 | import hudson.model.TaskListener; 6 | 7 | import java.io.IOException; 8 | import java.util.concurrent.TimeUnit; 9 | import java.util.logging.Level; 10 | 11 | @Extension 12 | public class DelayBufferedConsoleWork extends AsyncPeriodicWork { 13 | // not shorter than 15s, default to 1 min 30s 14 | private long period = TimeUnit.SECONDS.toMillis(Math.max(15, Long.getLong(DelayBufferedConsoleWork.class.getName(), 90))); 15 | 16 | public DelayBufferedConsoleWork() { 17 | super("Flush cached splunk console log"); 18 | } 19 | 20 | @Override 21 | protected void execute(TaskListener taskListener) throws IOException, InterruptedException { 22 | ConsoleRecordCacheUtils.flushLog(); 23 | } 24 | 25 | @Override 26 | public long getRecurrencePeriod() { 27 | return period; 28 | } 29 | 30 | @Override 31 | protected Level getNormalLoggingLevel() { 32 | return Level.FINE; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/SplunkArtifactNotifier/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/SplunkJenkinsInstallation/help-ignoredJobs.html: -------------------------------------------------------------------------------- 1 |
2 | Check Regular 3 | Expression to select which build will be ignored for monitoring. Build url matches 4 | the pattern will be ignored, for example:
5 | (?:backgroundJobName|adhocJobName|tempJobName) 6 |

will match backgroundJobName, backgroundJobNameBlah, blahbackgroundJobName

7 | ^(?:backgroundJobName|adhocJobName|tempJobName)$ 8 |

will match backgroundJobName but neither backgroundJobNameBlah nor blahbackgroundJobName

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
(?:X) X, as a non-capturing group
^The beginning of a line
$The end of a line
X|YEither X or Y
\wA word character: [a-zA-Z_0-9]
31 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/model/EmptyTestCaseGroup.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.model; 2 | 3 | import hudson.tasks.test.TestResult; 4 | 5 | import java.util.Collections; 6 | import java.util.List; 7 | 8 | import static com.splunk.splunkjenkins.Constants.NO_TEST_REPORT_FOUND; 9 | 10 | public class EmptyTestCaseGroup extends JunitTestCaseGroup { 11 | private String message; 12 | 13 | public String getMessage() { 14 | return message; 15 | } 16 | 17 | @Override 18 | public int getFailures() { 19 | return 0; 20 | } 21 | 22 | @Override 23 | public int getPasses() { 24 | return 0; 25 | } 26 | 27 | @Override 28 | public int getSkips() { 29 | return 0; 30 | } 31 | 32 | @Override 33 | public int getTotal() { 34 | return 0; 35 | } 36 | 37 | @Override 38 | public float getDuration() { 39 | return 0; 40 | } 41 | 42 | @Override 43 | public List getTestcase() { 44 | return Collections.emptyList(); 45 | } 46 | 47 | public void setWarning(boolean flag) { 48 | this.message = flag?NO_TEST_REPORT_FOUND:null; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /doc/splunk-devops-extend-usage.md: -------------------------------------------------------------------------------- 1 | ## A component of Splunk Plugin for Jenkins to support pipeline jobs 2 | 3 | ### Provided steps 4 | - sendSplunkFile 5 | ```Groovy 6 | sendSplunkFile includes: "*.log", sizeLimit: "50MB" 7 | ``` 8 | 9 | ![sendSplunkFile](images/pipeline_sendsplunkfile.png) 10 | 11 | - sendSplunkConsoleLog 12 | 13 | since version 1.9.0, the plugin is able to capture all pipeline log via [TaskListenerDecorator](https://javadoc.jenkins.io/plugin/workflow-api/org/jenkinsci/plugins/workflow/log/TaskListenerDecorator.html) 14 | 15 | ![enable-pipeline-log](images/pipeline_filter_option.png) 16 | 17 | sendSplunkConsoleLog step is only required when the global option is not checked 18 | 19 | ```Groovy 20 | // scripted pipeline 21 | sendSplunkConsoleLog { 22 | node{ 23 | sh "echo testjob"; 24 | } 25 | } 26 | 27 | // declarative pipeline 28 | pipeline { 29 | agent any 30 | options { 31 | timeout(time: 1, unit: 'HOURS') 32 | sendSplunkConsoleLog() 33 | } 34 | stages { 35 | stage('Example') { 36 | steps { 37 | echo 'Hello World' 38 | } 39 | } 40 | } 41 | } 42 | ``` 43 | 44 | 45 | ## Change Log 46 | Located in the [CHANGELOG.md](CHANGELOG.md) -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/model/MetaDataConfigItem/help-dataSource.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | 31 | 32 | 33 | 34 | 36 | 37 |
Build Report Junit or other Test Reports sent by calling "send(message)" DSL script 5 |
Build Event Sent when job are started and completed 10 |
Queue Information Basic queue information and Jenkins health metrics 15 |
Console Log Build console log, agent log and Jenkins master log (jenkins.log) 20 |
Log File Artifact contents, send by calling "archive(includes, excludes)" DSL script 25 |
Agent Information The agent monitor data 30 |
Jenkins Config The contents of Jenkins configuration items, e.g. config.xml, sent when the config file get updated. 35 |
38 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/model/JunitResultAggregateAdapter.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.model; 2 | 3 | import hudson.Extension; 4 | import hudson.tasks.junit.CaseResult; 5 | import hudson.tasks.junit.SuiteResult; 6 | import hudson.tasks.test.AggregatedTestResultAction; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | @Extension(optional = true) 12 | public class JunitResultAggregateAdapter extends AbstractTestResultAdapter { 13 | @Override 14 | public List getTestResult(AggregatedTestResultAction resultAction) { 15 | List caseResults = new ArrayList<>(); 16 | for (AggregatedTestResultAction.ChildReport childReport : resultAction.getChildReports()) { 17 | if (childReport.result instanceof hudson.tasks.junit.TestResult) { 18 | hudson.tasks.junit.TestResult testResult = (hudson.tasks.junit.TestResult) childReport.result; 19 | for (SuiteResult suite : testResult.getSuites()) { 20 | for (CaseResult testCase : suite.getCases()) { 21 | caseResults.add(testCase); 22 | } 23 | } 24 | } 25 | } 26 | return caseResults; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/utils/RemoteUtils.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.utils; 2 | 3 | import com.splunk.splunkjenkins.SplunkJenkinsInstallation; 4 | import hudson.util.Secret; 5 | import org.apache.commons.beanutils.BeanUtils; 6 | 7 | import java.util.Map; 8 | 9 | public class RemoteUtils { 10 | 11 | public static void initSplunkConfigOnAgent(Map eventCollectorProperty) { 12 | // Init SplunkJenkins global config in slave, can not reference Jenkins.getInstance(), Xtream 13 | SplunkJenkinsInstallation config = new SplunkJenkinsInstallation(false); 14 | try { 15 | String tokenValue = (String) eventCollectorProperty.remove("token"); 16 | BeanUtils.populate(config, eventCollectorProperty); 17 | config.setToken(Secret.fromString(tokenValue)); 18 | config.setEnabled(true); 19 | initSplunkConfigOnAgent(config); 20 | } catch (Exception e) { 21 | e.printStackTrace(); 22 | } 23 | 24 | } 25 | 26 | public static void initSplunkConfigOnAgent(SplunkJenkinsInstallation instance) { 27 | SplunkJenkinsInstallation.initOnAgent(instance); 28 | // only use one thread on agent 29 | SplunkLogService.getInstance().MAX_WORKER_COUNT = 1; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/model/EventType.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.model; 2 | 3 | public enum EventType { 4 | BUILD_REPORT(false), 5 | BUILD_EVENT(false), 6 | QUEUE_INFO(false), 7 | JENKINS_CONFIG(false), 8 | CONSOLE_LOG(true), 9 | FILE(true), 10 | SLAVE_INFO(false), 11 | LOG(false), 12 | BATCH_JSON(false), 13 | JSON_FILE(true); 14 | 15 | /** 16 | * whether the data need to be split by line breaker before send 17 | */ 18 | private boolean needSplit; 19 | 20 | EventType(boolean needSplit) { 21 | this.needSplit = needSplit; 22 | } 23 | 24 | /** 25 | * Need spit the content line by line if raw event not supported 26 | * Only applied for non-structural data, such as file and console text. 27 | * It doesn't applied for json data or xml data 28 | * 29 | * @return true if need spit the contents line by line if raw event not supported; 30 | * false otherwise. 31 | */ 32 | public boolean needSplit() { 33 | return needSplit; 34 | } 35 | 36 | /** 37 | * @param suffix the config metadata, can be either index, source or sourcetype 38 | * @return return name.suffix 39 | */ 40 | public String getKey(String suffix) { 41 | return this.name().toLowerCase() + "." + suffix; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /splunk-devops/src/test/java/com/splunk/splunkjenkins/JdkSplunkLogHandlerTest.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import java.util.UUID; 7 | import java.util.logging.Handler; 8 | import java.util.logging.Logger; 9 | 10 | import static com.splunk.splunkjenkins.SplunkConfigUtil.verifySplunkSearchResult; 11 | import static org.junit.Assert.assertTrue; 12 | 13 | public class JdkSplunkLogHandlerTest extends BaseTest { 14 | 15 | @Before 16 | public void setUp() throws Exception { 17 | super.setUp(); 18 | LoggingInitStep.registerHandler(); 19 | } 20 | 21 | @Test 22 | public void publish() throws Exception { 23 | Handler[] handlers = Logger.getLogger("").getHandlers(); 24 | boolean found = false; 25 | for (Handler handler : handlers) { 26 | if (handler instanceof JdkSplunkLogHandler) { 27 | found = true; 28 | break; 29 | } 30 | } 31 | assertTrue("LoggingInitStep.setupSplunkJenkins() should be called", found); 32 | String message = "test_log_" + UUID.randomUUID(); 33 | Logger.getLogger("test_info_logger").info(message); 34 | Logger.getLogger("test_warning_logger").warning(message); 35 | verifySplunkSearchResult(message + " level=WARNING", 1); 36 | verifySplunkSearchResult(message + " level=INFO", 1); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /splunk-devops/src/test/java/com/splunk/splunkjenkins/TestResultAdapterTest.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins; 2 | 3 | import hudson.model.FreeStyleProject; 4 | import hudson.model.Run; 5 | import org.junit.Test; 6 | import org.jvnet.hudson.test.recipes.LocalData; 7 | 8 | import java.io.IOException; 9 | import java.util.UUID; 10 | import java.util.concurrent.ExecutionException; 11 | 12 | import static com.splunk.splunkjenkins.SplunkConfigUtil.verifySplunkSearchResult; 13 | 14 | public class TestResultAdapterTest extends BaseTest { 15 | @LocalData 16 | @Test 17 | public void verifyTestNG() throws ExecutionException, InterruptedException, IOException { 18 | String query = "ExampleIntegrationTest| spath | search \"testsuite.testcase{}.classname\"=ExampleIntegrationTest"; 19 | FreeStyleProject project = (FreeStyleProject) j.getInstance().getItem("testng_job1"); 20 | String jobName = UUID.randomUUID().toString(); 21 | project.renameTo(jobName); 22 | long startTime = System.currentTimeMillis(); 23 | Run run = project.scheduleBuild2(0).get(); 24 | verifySplunkSearchResult(query, startTime, 1); 25 | //verify test_summary.total number 26 | String buildUrl = run.getUrl(); 27 | query = "type=completed test_summary build_url=\"" + buildUrl + "\"| spath | search test_summary.total=2"; 28 | verifySplunkSearchResult(query, startTime, 1); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Jenkinsfile.mvn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | // Parameters to run test 3 | properties([ 4 | parameters([string(defaultValue: '', description: 'Splunk host', name: 'host') 5 | , string(defaultValue: '8089', description: 'Splunk management port', name: 'port') 6 | , string(defaultValue: '', description: 'Username', name: 'username') 7 | , string(defaultValue: '', description: 'Password', name: 'password') 8 | , string(defaultValue: '', description: 'Local Repo Url', name: 'repoUrl') 9 | ]), 10 | [$class: 'jenkins.model.BuildDiscarderProperty', strategy: [$class : 'LogRotator', 11 | numToKeepStr : '50', 12 | artifactNumToKeepStr: '20']] 13 | ]) 14 | 15 | node { 16 | checkout scm; 17 | def mvnHome = tool name: 'default', type: 'hudson.tasks.Maven$MavenInstallation' 18 | def mvnCmd = "${mvnHome}/bin/mvn"; 19 | if (params.password) { 20 | mvnCmd += " -Dhost=${params.host} -Dport=${params.port} -Dpassword=${params.password} -Dusername=${params.username}" 21 | } 22 | mvnCmd += " -Djava.net.preferIPv4Stack=true clean verify" 23 | 24 | if (params.repoUrl) { 25 | mvnCmd += " -Plocal -Drepos.url=${params.repoUrl} deploy cobertura:cobertura" 26 | } 27 | sh mvnCmd 28 | } 29 | -------------------------------------------------------------------------------- /splunk-devops-extend/src/test/java/com/splunk/splunkjenkins/console/LabelMarkupTextTest.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.console; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.IOException; 7 | import java.io.OutputStream; 8 | 9 | import static junit.framework.TestCase.assertEquals; 10 | 11 | public class LabelMarkupTextTest{ 12 | @Test 13 | public void testMarkup() throws IOException { 14 | LabelMarkupText labelMarkupText=new LabelMarkupText(); 15 | labelMarkupText.addMarkup(0,0, 16 | "","test   "); 17 | OutputStream outputStream=new ByteArrayOutputStream(); 18 | labelMarkupText.write(outputStream); 19 | String outputs=outputStream.toString(); 20 | assertEquals("href=url ", outputs); 21 | // test enclosing labels 22 | labelMarkupText.addMarkup(0,0,"",""); 23 | labelMarkupText.addMarkup(0,0,"","  "); 24 | outputStream=new ByteArrayOutputStream(); 25 | labelMarkupText.write(outputStream); 26 | labelMarkupText.writePreviousLabel(outputStream); 27 | outputs=outputStream.toString(); 28 | assertEquals("parallel_label=\"a\" ", outputs); 29 | } 30 | } -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/model/MetaDataConfigItem/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 18 | 21 | 22 | 23 | 24 | 28 | 29 | 30 |
31 | 32 |
33 |
34 |
13 | 14 | 16 | 17 | 19 | 20 |
25 | 26 | 27 |
35 |
-------------------------------------------------------------------------------- /splunk-devops/src/test/resources/com/splunk/splunkjenkins/TestResultAdapterTest/jobs/testng_job1/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | 7 | 8 | true 9 | false 10 | false 11 | false 12 | 13 | false 14 | 15 | 16 | touch testng-reporter-log-result.xml 17 | 18 | 19 | 20 | 21 | testng-reporter-log-result.xml 22 | true 23 | true 24 | false 25 | false 26 | 100 27 | 0 28 | 100 29 | 100 30 | 2 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /splunk-devops/src/test/java/com/splunk/splunkjenkins/HealthMonitorTest.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins; 2 | 3 | import hudson.model.FreeStyleProject; 4 | import hudson.model.Label; 5 | import hudson.model.PeriodicWork; 6 | import hudson.model.TaskListener; 7 | import org.junit.Test; 8 | import org.jvnet.hudson.test.TouchBuilder; 9 | 10 | import java.util.UUID; 11 | 12 | import static com.splunk.splunkjenkins.SplunkConfigUtil.verifySplunkSearchResult; 13 | import static org.junit.Assert.*; 14 | 15 | public class HealthMonitorTest extends BaseTest { 16 | 17 | 18 | @Test 19 | public void execute() throws Exception { 20 | HealthMonitor monitor = PeriodicWork.all().get(HealthMonitor.class); 21 | FreeStyleProject p = j.createFreeStyleProject("test-sleep" + UUID.randomUUID()); 22 | p.setAssignedLabel(Label.get("no-such-node")); 23 | p.getBuildersList().add(new TouchBuilder()); 24 | long startTime = System.currentTimeMillis(); 25 | p.scheduleBuild2(0); 26 | monitor.lastAccessTime=0; 27 | monitor.execute(TaskListener.NULL); 28 | verifySplunkSearchResult("event_tag=queue", startTime, 1); 29 | verifySplunkSearchResult("event_tag=slave", startTime, 1); 30 | } 31 | 32 | @Test 33 | public void getRecurrencePeriod() throws Exception { 34 | long userDefault = 45000; 35 | long period = PeriodicWork.all().get(HealthMonitor.class).getRecurrencePeriod(); 36 | assertEquals(userDefault, period); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/utils/CustomSSLConnectionSocketFactory.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.utils; 2 | 3 | import shaded.splk.org.apache.http.HttpHost; 4 | import shaded.splk.org.apache.http.conn.ssl.SSLConnectionSocketFactory; 5 | import shaded.splk.org.apache.http.protocol.HttpContext; 6 | 7 | import javax.net.ssl.HostnameVerifier; 8 | import javax.net.ssl.SSLContext; 9 | import java.io.IOException; 10 | import java.net.InetSocketAddress; 11 | import java.net.Socket; 12 | 13 | import static com.splunk.splunkjenkins.utils.MultipleHostResolver.NAME_DELIMITER; 14 | 15 | public class CustomSSLConnectionSocketFactory extends SSLConnectionSocketFactory { 16 | 17 | public CustomSSLConnectionSocketFactory(SSLContext sslContext, HostnameVerifier hostnameVerifier) { 18 | super(sslContext, hostnameVerifier); 19 | } 20 | 21 | @Override 22 | public Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress, InetSocketAddress localAddress, HttpContext context) throws IOException { 23 | if (host.getHostName().contains(NAME_DELIMITER)) { 24 | HttpHost resolvedHost = new HttpHost(remoteAddress.getHostName(), host.getPort(), host.getSchemeName()); 25 | return super.connectSocket(connectTimeout, socket, resolvedHost, remoteAddress, localAddress, context); 26 | } else{ 27 | return super.connectSocket(connectTimeout, socket, host, remoteAddress, localAddress, context); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /splunk-devops-extend/src/main/java/com/splunk/splunkjenkins/SplunkPipelineJobProperty.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins; 2 | 3 | import hudson.Extension; 4 | import jenkins.model.OptionalJobProperty; 5 | import org.jenkinsci.Symbol; 6 | import org.jenkinsci.plugins.workflow.job.WorkflowJob; 7 | import org.kohsuke.stapler.DataBoundConstructor; 8 | import org.kohsuke.stapler.DataBoundSetter; 9 | 10 | import edu.umd.cs.findbugs.annotations.CheckForNull; 11 | 12 | @SuppressWarnings("rawtypes") 13 | public class SplunkPipelineJobProperty extends OptionalJobProperty { 14 | Boolean enableDiagram; 15 | 16 | @DataBoundConstructor 17 | public SplunkPipelineJobProperty() { 18 | } 19 | 20 | @CheckForNull 21 | public Boolean getEnableDiagram() { 22 | return enableDiagram; 23 | } 24 | 25 | @DataBoundSetter 26 | public void setEnableDiagram(Boolean enableDiagram) { 27 | this.enableDiagram = enableDiagram; 28 | } 29 | 30 | public boolean isDiagramEnabled() { 31 | return Boolean.TRUE.equals(enableDiagram); 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return String.format("SplunkPipelineJobProperty{enableDiagram=%s}", enableDiagram); 37 | } 38 | 39 | @Extension 40 | @Symbol("splunkinsJobOption") 41 | public static class DescriptorImpl extends OptionalJobPropertyDescriptor { 42 | @Override 43 | public String getDisplayName() { 44 | return "Opt in data sent to Splunk"; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /splunk-devops/src/test/java/com/splunk/splunkjenkins/utils/PlainTextConsoleUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.utils; 2 | 3 | import hudson.console.ConsoleNote; 4 | import org.junit.Assert; 5 | import org.junit.Test; 6 | 7 | import java.io.ByteArrayOutputStream; 8 | 9 | public class PlainTextConsoleUtilsTest { 10 | private byte[] logText = ("\u001B[8mha:////4KIKPqK5tXCDtTm83KR8dOlkGTotzP4liGbxukwLqvjJAA\u001B[0m[Pipeline] }\n" + 11 | "\u001B[8mha:////4KY/nBiyccGoc9OKNQirqOjwEcX/CTScoTrGPCj/nnzYAAAApB+LCAAAAAAAA\u001B[0m[Pipeline] echo\n" + 12 | "hello branch-2").getBytes(); 13 | private String expected = "[Pipeline] }\n[Pipeline] echo\nhello branch-2"; 14 | 15 | @Test 16 | public void arrayIndexOf() { 17 | int MAX_FINDS = 2; 18 | int idx = 0; 19 | for (int i = 1; i <= MAX_FINDS + 1; i++) { 20 | idx = PlainTextConsoleUtils.arrayIndexOf(logText, idx + 1, logText.length, ConsoleNote.POSTAMBLE); 21 | if (i > MAX_FINDS) { 22 | Assert.assertTrue("not found in step" + i, idx == -1); 23 | } else { 24 | Assert.assertTrue("find the index in step" + i, idx > 0); 25 | } 26 | } 27 | } 28 | 29 | @Test 30 | public void decodeConsole() { 31 | ByteArrayOutputStream bout = new ByteArrayOutputStream(); 32 | PlainTextConsoleUtils.decodeConsole(logText, logText.length, bout); 33 | String console = bout.toString(); 34 | Assert.assertEquals(expected, console); 35 | } 36 | } -------------------------------------------------------------------------------- /splunk-devops/src/test/resources/com/splunk/splunkjenkins/CoverageMetricTest/jobs/Cobertura/workspace/coverage.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | --source 7 | src/main/java 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 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/links/LinkSplunkAction.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.links; 2 | 3 | 4 | import com.splunk.splunkjenkins.SplunkJenkinsInstallation; 5 | import hudson.model.Action; 6 | 7 | import com.splunk.splunkjenkins.Messages; 8 | import jenkins.model.Jenkins; 9 | import org.apache.commons.lang3.StringUtils; 10 | 11 | public class LinkSplunkAction implements Action { 12 | String query; 13 | String page; 14 | String displayName; 15 | 16 | public LinkSplunkAction(String tab, String query, String displayName) { 17 | this.query = query; 18 | this.page = tab; 19 | this.displayName = displayName; 20 | } 21 | 22 | @Override 23 | public String getIconFileName() { 24 | if (Jenkins.get().hasPermission(ReportAction.SPLUNK_LINK)) { 25 | return Messages.SplunkIconName(); 26 | } else { 27 | return null; 28 | } 29 | } 30 | 31 | @Override 32 | public String getDisplayName() { 33 | if (Jenkins.get().hasPermission(ReportAction.SPLUNK_LINK)) { 34 | return displayName; 35 | } else { 36 | return null; 37 | } 38 | } 39 | 40 | @Override 41 | public String getUrlName() { 42 | SplunkJenkinsInstallation instance = SplunkJenkinsInstallation.get(); 43 | if (StringUtils.isNotEmpty(query)) { 44 | return instance.getAppUrlOrHelp() + page + "?" + query; 45 | } else { 46 | return instance.getAppUrlOrHelp() + page; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/model/LoggingJobExtractor.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.model; 2 | 3 | import hudson.ExtensionList; 4 | import hudson.ExtensionPoint; 5 | import hudson.model.Run; 6 | import org.jvnet.tiger_types.Types; 7 | 8 | import java.lang.reflect.ParameterizedType; 9 | import java.lang.reflect.Type; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | public abstract class LoggingJobExtractor implements ExtensionPoint { 15 | public final Class targetType; 16 | 17 | public LoggingJobExtractor() { 18 | Type type = Types.getBaseClass(getClass(), LoggingJobExtractor.class); 19 | if (type instanceof ParameterizedType) 20 | targetType = Types.erasure(Types.getTypeArgument(type, 0)); 21 | else 22 | throw new IllegalStateException(getClass() + " uses the raw type for extending LoggingJobExtractor"); 23 | } 24 | 25 | public abstract Map extract(R r, boolean completed); 26 | 27 | /** 28 | * @return Returns all the registered {@link LoggingJobExtractor}s 29 | */ 30 | public static ExtensionList all() { 31 | return ExtensionList.lookup(LoggingJobExtractor.class); 32 | } 33 | 34 | public static List canApply(Run run) { 35 | List extensions = new ArrayList<>(); 36 | for (LoggingJobExtractor extendListener : LoggingJobExtractor.all()) { 37 | if (extendListener.targetType.isInstance(run)) { 38 | extensions.add(extendListener); 39 | } 40 | } 41 | return extensions; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/listeners/UserSecurityListener.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.listeners; 2 | 3 | import hudson.Extension; 4 | import hudson.model.User; 5 | import jenkins.security.SecurityListener; 6 | import org.acegisecurity.userdetails.UserDetails; 7 | 8 | import edu.umd.cs.findbugs.annotations.NonNull; 9 | 10 | import static com.splunk.splunkjenkins.utils.LogEventHelper.logUserAction; 11 | 12 | /** 13 | * Note: all the username are user id, not user full name 14 | */ 15 | @Extension 16 | public class UserSecurityListener extends SecurityListener { 17 | @Override 18 | protected void authenticated(@NonNull UserDetails details) { 19 | logUserAction(details.getUsername(), "authenticated"); 20 | } 21 | 22 | @Override 23 | protected void failedToAuthenticate(@NonNull String username) { 24 | logUserAction(getFullName(username), Messages.audit_user_fail_auth()); 25 | } 26 | 27 | @Override 28 | protected void loggedIn(@NonNull String username) { 29 | logUserAction(getFullName(username), Messages.audit_user_login()); 30 | } 31 | 32 | @Override 33 | protected void failedToLogIn(@NonNull String username) { 34 | //covered by failedToAuthenticate 35 | } 36 | 37 | @Override 38 | protected void loggedOut(@NonNull String username) { 39 | logUserAction(getFullName(username), Messages.audit_user_logout()); 40 | } 41 | 42 | /** 43 | * @param username 44 | * @return full name 45 | */ 46 | private String getFullName(String username) { 47 | //user may not exists when user failed to login 48 | User user = User.get(username); 49 | return user.getFullName(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /splunk-devops-extend/src/test/java/com/splunk/splunkjenkins/SplunkinsDslVariableTest.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins; 2 | 3 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; 4 | import org.jenkinsci.plugins.workflow.job.WorkflowJob; 5 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 6 | import org.junit.Before; 7 | import org.junit.ClassRule; 8 | import org.junit.Rule; 9 | import org.junit.Test; 10 | import org.jvnet.hudson.test.BuildWatcher; 11 | import org.jvnet.hudson.test.JenkinsRule; 12 | 13 | import java.util.UUID; 14 | 15 | import static com.splunk.splunkjenkins.SplunkConfigUtil.checkTokenAvailable; 16 | 17 | import static com.splunk.splunkjenkins.SplunkConfigUtil.verifySplunkSearchResult; 18 | import static org.junit.Assert.*; 19 | 20 | public class SplunkinsDslVariableTest { 21 | @ClassRule 22 | public static BuildWatcher buildWatcher = new BuildWatcher(); 23 | @Rule 24 | public JenkinsRule r = new JenkinsRule(); 25 | 26 | @Before 27 | public void setUp() throws Exception { 28 | org.junit.Assume.assumeTrue(checkTokenAvailable()); 29 | } 30 | 31 | @Test 32 | public void testDslScript() throws Exception { 33 | WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p"); 34 | String eventSource = "test_dsl_var_" + UUID.randomUUID().toString(); 35 | p.setDefinition(new CpsFlowDefinition("node { splunkins.send('hello', '" + eventSource + "') }")); 36 | WorkflowRun b1 = r.assertBuildStatusSuccess(p.scheduleBuild2(0)); 37 | assertFalse(b1.isBuilding()); 38 | assertTrue(b1.getDuration() > 0); 39 | String query = "index=" + SplunkConfigUtil.INDEX_NAME + " source=" + eventSource; 40 | int expected = 1; 41 | verifySplunkSearchResult(query, b1.getTimeInMillis(), expected); 42 | } 43 | } -------------------------------------------------------------------------------- /splunk-devops-extend/src/main/java/com/splunk/splunkjenkins/SplunkinsDslVariable.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins; 2 | 3 | 4 | import hudson.Extension; 5 | import hudson.model.Run; 6 | import hudson.model.TaskListener; 7 | import hudson.util.LogTaskListener; 8 | import org.jenkinsci.plugins.workflow.cps.CpsScript; 9 | import org.jenkinsci.plugins.workflow.cps.GlobalVariable; 10 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 11 | 12 | import edu.umd.cs.findbugs.annotations.NonNull; 13 | import java.lang.reflect.Field; 14 | import java.util.Map; 15 | import java.util.logging.Level; 16 | import java.util.logging.Logger; 17 | 18 | import static com.splunk.splunkjenkins.utils.LogEventHelper.getBuildVariables; 19 | 20 | @Extension(optional = true) 21 | public class SplunkinsDslVariable extends GlobalVariable { 22 | @NonNull 23 | @Override 24 | public String getName() { 25 | return "splunkins"; 26 | } 27 | 28 | @NonNull 29 | @Override 30 | public Object getValue(@NonNull CpsScript script) throws Exception { 31 | Run build = script.$build(); 32 | if (build == null) { 33 | throw new IllegalStateException("cannot find associated build"); 34 | } 35 | // try to access WorkflowRun.listener 36 | TaskListener listener = TaskListener.NULL; 37 | try { 38 | Field field = WorkflowRun.class.getDeclaredField("listener"); 39 | field.setAccessible(true); 40 | listener = (TaskListener) field.get(build); 41 | } catch (Exception e) { 42 | listener = new LogTaskListener(Logger.getLogger(SplunkinsDslVariable.class.getName()), Level.INFO); 43 | } 44 | Map buildParameters = getBuildVariables(build); 45 | RunDelegate delegate = new RunDelegate(build, buildParameters, listener); 46 | return delegate; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/links/RunActionFactory.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.links; 2 | 3 | import com.splunk.splunkjenkins.SplunkJenkinsInstallation; 4 | import com.splunk.splunkjenkins.utils.LogEventHelper; 5 | import hudson.Extension; 6 | import hudson.model.Action; 7 | import hudson.model.Job; 8 | import hudson.model.Run; 9 | import jenkins.model.TransientActionFactory; 10 | 11 | import edu.umd.cs.findbugs.annotations.NonNull; 12 | import java.io.File; 13 | import java.util.Collection; 14 | import java.util.Collections; 15 | 16 | @SuppressWarnings("unused") 17 | @Extension 18 | public class RunActionFactory extends TransientActionFactory { 19 | @Override 20 | public Class type() { 21 | return Run.class; 22 | } 23 | 24 | @NonNull 25 | @Override 26 | public Collection createFor(@NonNull Run target) { 27 | Job job = target.getParent(); 28 | LogEventHelper.UrlQueryBuilder builder = new LogEventHelper.UrlQueryBuilder() 29 | .putIfAbsent("job", job.getFullName()) 30 | .putIfAbsent("build", target.getNumber() + ""); 31 | File junitFile = new File(target.getRootDir(), "junitResult.xml"); 32 | if (junitFile.exists() || job.getClass().getName().startsWith("hudson.maven.")) { 33 | // test page is using master query param instead of host 34 | builder.putIfAbsent("master", SplunkJenkinsInstallation.get().getMetadataHost()); 35 | String query = builder.build(); 36 | return Collections.singleton(new LinkSplunkAction("testAnalysis", query, "Splunk")); 37 | } 38 | String query = builder.putIfAbsent("type", "build") 39 | .putIfAbsent("host", SplunkJenkinsInstallation.get().getMetadataHost()).build(); 40 | return Collections.singleton(new LinkSplunkAction("build", query, "Splunk")); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/SplunkJenkinsInstallation/help-groovyBinding.html: -------------------------------------------------------------------------------- 1 |
2 |

Provide a groovy script to customize event process, 3 | will be executed when job is completed

4 | 5 |
6 |
7 | You can customize the events sent to splunk by call: 8 |
  
 9 | //send job metadata and junit reports with page size set to 50 (each event contains max 50 test cases)
10 | splunkins.sendTestReport(50)
11 | //send coverage, each event contains max 50 class metrics
12 | splunkins.sendCoverageReport(50)
13 | //send all logs from workspace to splunk, with each file size limits to 10MB
14 | splunkins.archive("**/*.log", null, false, "10MB")
15 | 
16 | 
17 | The groovy script can use the variable splunkins, which provides access to the following objects and methods: 18 |
 
19 | 
20 | Action getAction(Class type);
21 | Action getActionByClassName(String className);
22 | //send message to splunk
23 | boolean send(Object message);
24 | //Archive all configured artifacts from slave, with each file size limit to 10MB, using ant patterns defined in http://ant.apache.org/manual/Types/fileset.html
25 | archive(String includes, String excludes = null, boolean uploadFromSlave = false, String fileSizeLimit = "")
26 | //will send build parameters as metadata and with the object returned from closure to splunk
27 | sendReport(Closure closure)
28 | //a junit report with summary of passes,failures,skips and details of testcase
29 | getJunitReport()
30 | //a a list of junit report each with summary of passes,failures,skips and details of testcase
31 | //each report contains max pageSize testcases
32 | getJunitReport(int pageSize)
33 | sendTestReport(pageSize)  //send Test report, with pagination support
34 | sendCoverageReport(pageSize)  //send coverage report, with pagination support
35 |   
36 |
37 |
38 | -------------------------------------------------------------------------------- /splunk-devops/src/test/java/com/splunk/splunkjenkins/SplunkSendJsonFileTest.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins; 2 | 3 | import hudson.model.FreeStyleProject; 4 | import hudson.model.Run; 5 | import hudson.tasks.Shell; 6 | import org.junit.Test; 7 | 8 | import java.util.UUID; 9 | 10 | import static com.splunk.splunkjenkins.SplunkConfigUtil.verifySplunkSearchResult; 11 | 12 | public class SplunkSendJsonFileTest extends BaseTest { 13 | 14 | private Run createBuild(String textContent, String fileName) throws Exception { 15 | FreeStyleProject project = j.createFreeStyleProject("publisher_test" + UUID.randomUUID()); 16 | project.getBuildersList().add(new Shell("cat > " + fileName + " <<'EOF'\n" + textContent + "\nEOF")); 17 | SplunkArtifactNotifier splunkArtifactNotifier = new SplunkArtifactNotifier("*", null, false, false, "10MB"); 18 | project.getPublishersList().add(splunkArtifactNotifier); 19 | Run build = project.scheduleBuild2(0).get(); 20 | j.assertBuildStatusSuccess(build); 21 | return build; 22 | } 23 | 24 | 25 | @Test 26 | public void testSendJsonFile() throws Exception { 27 | String name = "client_" + UUID.randomUUID(); 28 | String jsonText = "{\n\"name\":\n\"" + name + "\"\n}"; 29 | String query = "index=" + SplunkConfigUtil.INDEX_NAME + " name=" + name; 30 | Run build = createBuild(jsonText, "user.json"); 31 | verifySplunkSearchResult(query, build.getTimeInMillis(), 1); 32 | } 33 | 34 | @Test 35 | public void testSendTextFile() throws Exception { 36 | String name = "client_" + UUID.randomUUID(); 37 | String jsonText = "line1: " + name + "\nline2: " + name; 38 | String query = "index=" + SplunkConfigUtil.INDEX_NAME + " " + name; 39 | Run build = createBuild(jsonText, "user.txt"); 40 | verifySplunkSearchResult(query, build.getTimeInMillis(), 2); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /splunk-devops-extend/src/main/java/com/splunk/splunkjenkins/console/ConsoleRecordCacheUtils.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.console; 2 | 3 | import com.splunk.splunkjenkins.model.EventRecord; 4 | import com.splunk.splunkjenkins.model.EventType; 5 | import com.splunk.splunkjenkins.utils.SplunkLogService; 6 | import jenkins.util.JenkinsJVM; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.concurrent.ConcurrentLinkedQueue; 11 | import java.util.logging.Level; 12 | import java.util.logging.Logger; 13 | 14 | public class ConsoleRecordCacheUtils { 15 | private static final int CACHED_LINES_LIMIT; 16 | private transient static final Logger LOGGER = Logger.getLogger(SplunkConsoleTaskListenerDecorator.class.getName()); 17 | private transient static final ConcurrentLinkedQueue consoleQueue = new ConcurrentLinkedQueue<>(); 18 | 19 | static { 20 | if (JenkinsJVM.isJenkinsJVM()) { 21 | CACHED_LINES_LIMIT = 200; 22 | } else { 23 | CACHED_LINES_LIMIT = 10; 24 | } 25 | } 26 | 27 | public static void enqueue(EventRecord record) { 28 | boolean added = consoleQueue.add(record); 29 | if (!added) { 30 | LOGGER.warning("failed to add log " + record.getMessageString()); 31 | } else if (consoleQueue.size() > CACHED_LINES_LIMIT) { 32 | flushLog(); 33 | } 34 | } 35 | 36 | public static void flushLog() { 37 | EventRecord record; 38 | List pendingRecords = new ArrayList<>(); 39 | try { 40 | while ((record = consoleQueue.poll()) != null) { 41 | pendingRecords.add(record); 42 | } 43 | SplunkLogService.getInstance().sendBatch(pendingRecords, EventType.CONSOLE_LOG); 44 | } catch (Throwable ex) { 45 | LOGGER.log(Level.SEVERE, "flush log error", ex); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /splunk-devops/src/test/resources/com/splunk/splunkjenkins/CoverageMetricTest/jobs/JaCoCo/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | 7 | 8 | true 9 | false 10 | false 11 | false 12 | 13 | false 14 | 15 | 16 | **/**.exec 17 | **/classes 18 | **/java 19 | 20 | 21 | 0 22 | 0 23 | 0 24 | 0 25 | 0 26 | 0 27 | 0 28 | 0 29 | 0 30 | 0 31 | 0 32 | 0 33 | false 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/links/ReportAction.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.links; 2 | 3 | import com.splunk.splunkjenkins.Messages; 4 | import com.splunk.splunkjenkins.SplunkJenkinsInstallation; 5 | import hudson.Extension; 6 | import hudson.model.RootAction; 7 | import hudson.security.Permission; 8 | import hudson.security.PermissionGroup; 9 | import hudson.security.PermissionScope; 10 | import jenkins.model.Jenkins; 11 | 12 | @SuppressWarnings("unused") 13 | @Extension 14 | public class ReportAction implements RootAction { 15 | 16 | /** 17 | * Permission group for Splunk Link related permissions. 18 | */ 19 | public static final PermissionGroup PERMISSIONS = 20 | new PermissionGroup(ReportAction.class, Messages._PermissionGroup()); 21 | /** 22 | * Permission to get the Splunk link displayed. 23 | */ 24 | public static final Permission SPLUNK_LINK = new Permission(PERMISSIONS, 25 | "SplunkLink", Messages._PluginViewPermission_Description(), Jenkins.ADMINISTER, PermissionScope.JENKINS); 26 | 27 | 28 | @Override 29 | public String getIconFileName() { 30 | if (Jenkins.getInstance().hasPermission(ReportAction.SPLUNK_LINK)){ 31 | return Messages.SplunkIconName(); 32 | } 33 | return null; 34 | } 35 | 36 | @Override 37 | public String getDisplayName() { 38 | if (Jenkins.getInstance().hasPermission(ReportAction.SPLUNK_LINK)){ 39 | return "Splunk"; 40 | } 41 | return null; 42 | } 43 | 44 | @Override 45 | public String getUrlName() { 46 | if (Jenkins.getInstance().hasPermission(ReportAction.SPLUNK_LINK)){ 47 | SplunkJenkinsInstallation instance = SplunkJenkinsInstallation.get(); 48 | return instance.getAppUrlOrHelp() + "overview?overview_jenkinsmaster=" + instance.getMetadataHost(); 49 | } 50 | return null; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /splunk-devops-extend/src/test/java/com/splunk/splunkjenkins/SplunkLogFileStepTest.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins; 2 | 3 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; 4 | import org.jenkinsci.plugins.workflow.job.WorkflowJob; 5 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 6 | import org.junit.Before; 7 | import org.junit.ClassRule; 8 | import org.junit.Rule; 9 | import org.junit.Test; 10 | import org.jvnet.hudson.test.BuildWatcher; 11 | import org.jvnet.hudson.test.JenkinsRule; 12 | 13 | import java.util.UUID; 14 | 15 | import static com.splunk.splunkjenkins.SplunkConfigUtil.checkTokenAvailable; 16 | import static com.splunk.splunkjenkins.SplunkConfigUtil.verifySplunkSearchResult; 17 | import static org.junit.Assert.*; 18 | 19 | public class SplunkLogFileStepTest { 20 | @ClassRule 21 | public static BuildWatcher buildWatcher = new BuildWatcher(); 22 | @Rule 23 | public JenkinsRule r = new JenkinsRule(); 24 | String fileName = UUID.randomUUID().toString() + ".log"; 25 | 26 | private String jobScript = "node{\n" + 27 | "sh \"echo testjob\";\n" + 28 | "sh \"echo 'hello world' > " + fileName + "\";\n" + 29 | "sendSplunkFile includes: \"*.log\";\n" + 30 | "}"; 31 | 32 | @Before 33 | public void setUp() throws Exception { 34 | org.junit.Assume.assumeTrue(checkTokenAvailable()); 35 | } 36 | 37 | @Test 38 | public void testSendFile() throws Exception { 39 | long startTime = System.currentTimeMillis(); 40 | WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p"); 41 | p.setDefinition(new CpsFlowDefinition(jobScript)); 42 | WorkflowRun b1 = r.assertBuildStatusSuccess(p.scheduleBuild2(0)); 43 | assertFalse(b1.isBuilding()); 44 | r.assertLogContains("testjob", b1); 45 | assertTrue(b1.getDuration() > 0); 46 | //check log 47 | verifySplunkSearchResult("source=" + b1.getUrl() + fileName, startTime, 1); 48 | } 49 | } -------------------------------------------------------------------------------- /splunk-devops-extend/src/main/java/com/splunk/splunkjenkins/console/SplunkConsoleTaskListenerDecorator.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.console; 2 | 3 | import com.splunk.splunkjenkins.utils.RemoteUtils; 4 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 5 | import jenkins.util.JenkinsJVM; 6 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 7 | import org.jenkinsci.plugins.workflow.log.TaskListenerDecorator; 8 | 9 | import edu.umd.cs.findbugs.annotations.NonNull; 10 | import java.io.IOException; 11 | import java.io.OutputStream; 12 | import java.util.Map; 13 | 14 | public class SplunkConsoleTaskListenerDecorator extends TaskListenerDecorator { 15 | private static final long serialVersionUID = 1L; 16 | @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") 17 | transient PipelineConsoleDecoder decoder; 18 | // it is optional but not use Optional since Optional is not serializable 19 | Map remoteSplunkinsConfig = null; 20 | String source; 21 | 22 | public SplunkConsoleTaskListenerDecorator(WorkflowRun run) { 23 | this.decoder = new PipelineConsoleDecoder(run); 24 | this.source = run.getUrl() + "console"; 25 | } 26 | 27 | @NonNull 28 | @Override 29 | public OutputStream decorate(@NonNull OutputStream outputStream) throws IOException { 30 | if (!JenkinsJVM.isJenkinsJVM()) { 31 | if (remoteSplunkinsConfig != null) { 32 | RemoteUtils.initSplunkConfigOnAgent(remoteSplunkinsConfig); 33 | } else { 34 | // no-op 35 | return outputStream; 36 | } 37 | } 38 | if (decoder == null) { 39 | // resume from restart 40 | decoder = new PipelineConsoleDecoder(null); 41 | } 42 | //called for every step 43 | return new LabelConsoleLineStream(outputStream, source, decoder); 44 | } 45 | 46 | protected void setRemoteSplunkinsConfig(Map remoteSplunkinsConfig) { 47 | this.remoteSplunkinsConfig = remoteSplunkinsConfig; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /splunk-devops/src/test/resources/com/splunk/splunkjenkins/CoverageMetricTest/jobs/Clover/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | 7 | 8 | false 9 | 10 | 11 | false 12 | false 13 | 14 | 15 | 0 16 | 0 17 | 18 | false 19 | project 20 | 21 | 22 | 23 | true 24 | false 25 | false 26 | false 27 | 28 | false 29 | 30 | 31 | 32 | 33 | clover.xml 34 | 35 | 70 36 | 80 37 | 80 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /splunk-devops-extend/src/test/java/com/splunk/splunkjenkins/PipelineExecuteDiagramTest.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins; 2 | 3 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; 4 | import org.jenkinsci.plugins.workflow.job.WorkflowJob; 5 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 6 | import org.junit.Before; 7 | import org.junit.ClassRule; 8 | import org.junit.Rule; 9 | import org.junit.Test; 10 | import org.jvnet.hudson.test.BuildWatcher; 11 | import org.jvnet.hudson.test.JenkinsRule; 12 | 13 | import static com.splunk.splunkjenkins.SplunkConfigUtil.checkTokenAvailable; 14 | import static com.splunk.splunkjenkins.SplunkConfigUtil.verifySplunkSearchResult; 15 | import static org.junit.Assert.assertFalse; 16 | import static org.junit.Assert.assertTrue; 17 | 18 | public class PipelineExecuteDiagramTest { 19 | @ClassRule 20 | public static BuildWatcher buildWatcher = new BuildWatcher(); 21 | @Rule 22 | public JenkinsRule r = new JenkinsRule(); 23 | private String jobScript = "properties([splunkinsJobOption(enableDiagram: true)])\n" + 24 | "stage(\"unit-test\"){\n" + 25 | " node{\n" + 26 | " echo \"hello\"\n" + 27 | " echo \"hello world2\"\n" + 28 | " }\n" + 29 | "}"; 30 | 31 | @Before 32 | public void setUp() throws Exception { 33 | org.junit.Assume.assumeTrue(checkTokenAvailable()); 34 | } 35 | 36 | @Test 37 | public void testExecuteDiagram() throws Exception { 38 | long startTime = System.currentTimeMillis(); 39 | WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "testExecuteDiagram"); 40 | p.setDefinition(new CpsFlowDefinition(jobScript)); 41 | WorkflowRun b1 = r.assertBuildStatusSuccess(p.scheduleBuild2(0)); 42 | assertFalse(b1.isBuilding()); 43 | r.assertLogContains("hello", b1); 44 | assertTrue(b1.getDuration() > 0); 45 | //check exec_node 46 | verifySplunkSearchResult("source=" + b1.getUrl() + "graphviz", startTime, 1); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/model/JunitTestCaseGroup.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.model; 2 | 3 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 4 | import hudson.Util; 5 | import hudson.tasks.test.TestResult; 6 | 7 | import java.io.Serializable; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | @SuppressFBWarnings("URF_UNREAD_FIELD") 12 | public class JunitTestCaseGroup implements Serializable{ 13 | int failures; 14 | int passes; 15 | int skips; 16 | int total; 17 | float duration; 18 | //alias, fields for json serialization 19 | int tests; 20 | float time; 21 | //backward compatible with junit3 xml which has errors field 22 | int errors = 0; 23 | List testcase = new ArrayList<>(); 24 | 25 | public void add(TestResult result) { 26 | this.failures += result.getFailCount(); 27 | this.passes += result.getPassCount(); 28 | this.skips += result.getSkipCount(); 29 | this.total += result.getTotalCount(); 30 | this.duration += result.getDuration(); 31 | //update alias 32 | this.tests = this.total; 33 | this.time = this.duration; 34 | this.testcase.add(result); 35 | } 36 | 37 | public int getFailures() { 38 | return failures; 39 | } 40 | 41 | public int getPasses() { 42 | return passes; 43 | } 44 | 45 | public int getSkips() { 46 | return skips; 47 | } 48 | 49 | public int getTotal() { 50 | return total; 51 | } 52 | 53 | public float getDuration() { 54 | return duration; 55 | } 56 | 57 | public List getTestcase() { 58 | return testcase; 59 | } 60 | 61 | @Override 62 | public String toString() { 63 | return "failures: " + failures + 64 | ", passes: " + passes + 65 | ", skips: " + skips + 66 | ", errors: " + errors + 67 | ", total: " + total + 68 | ", duration: " + Util.getTimeSpanString(1000L * (long) duration); 69 | 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/listeners/LoggingItemListener.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.listeners; 2 | 3 | import com.splunk.splunkjenkins.utils.SplunkLogService; 4 | import hudson.Extension; 5 | import hudson.model.Item; 6 | import hudson.model.listeners.ItemListener; 7 | 8 | import java.io.File; 9 | 10 | import static com.splunk.splunkjenkins.utils.LogEventHelper.getRelativeJenkinsHomePath; 11 | import static com.splunk.splunkjenkins.utils.LogEventHelper.getUserName; 12 | import static com.splunk.splunkjenkins.utils.LogEventHelper.logUserAction; 13 | 14 | @Extension 15 | public class LoggingItemListener extends ItemListener { 16 | @Override 17 | public void onCreated(Item item) { 18 | logUserAction(getUserName(), Messages.audit_create_item(getConfigPath(item))); 19 | } 20 | 21 | @Override 22 | public void onCopied(Item src, Item item) { 23 | logUserAction(getUserName(), Messages.audit_cloned_item(getConfigPath(item), getConfigPath(src))); 24 | } 25 | 26 | @Override 27 | public void onDeleted(Item item) { 28 | logUserAction(getUserName(), Messages.audit_delete_item(getConfigPath(item))); 29 | } 30 | 31 | @Override 32 | public void onRenamed(Item item, String oldName, String newName) { 33 | //no-op, we use onLocationChanged 34 | } 35 | 36 | @Override 37 | public void onUpdated(Item item) { 38 | //prior to delete, makeDisabled was called and onUpdated is triggered 39 | logUserAction(getUserName(), Messages.audit_update_item(getConfigPath(item))); 40 | } 41 | 42 | @Override 43 | public void onLocationChanged(Item item, String oldFullName, String newFullName) { 44 | logUserAction(getUserName(), Messages.audit_rename_item(oldFullName, newFullName)); 45 | } 46 | 47 | private String getConfigPath(Item item) { 48 | if (item == null) { 49 | return "unknown"; 50 | } 51 | return getRelativeJenkinsHomePath(item.getRootDir() + File.separator + "config.xml"); 52 | } 53 | 54 | @Override 55 | public void onBeforeShutdown() { 56 | SplunkLogService.getInstance().stopWorker(); 57 | SplunkLogService.getInstance().releaseConnection(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/listeners/LoggingComputerListener.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.listeners; 2 | 3 | import com.splunk.splunkjenkins.SplunkJenkinsInstallation; 4 | import com.splunk.splunkjenkins.utils.SplunkLogService; 5 | import hudson.Extension; 6 | import hudson.model.Computer; 7 | import hudson.model.TaskListener; 8 | import hudson.slaves.ComputerListener; 9 | import hudson.slaves.OfflineCause; 10 | 11 | import edu.umd.cs.findbugs.annotations.CheckForNull; 12 | import edu.umd.cs.findbugs.annotations.NonNull; 13 | import java.io.IOException; 14 | import java.util.Map; 15 | 16 | import static com.splunk.splunkjenkins.Constants.EVENT_CAUSED_BY; 17 | import static com.splunk.splunkjenkins.model.EventType.SLAVE_INFO; 18 | import static com.splunk.splunkjenkins.utils.LogEventHelper.getComputerStatus; 19 | 20 | @SuppressWarnings("unused") 21 | @Extension 22 | public class LoggingComputerListener extends ComputerListener { 23 | @Override 24 | public void onOnline(Computer c, TaskListener listener) throws IOException, InterruptedException { 25 | updateStatus(c, "Online"); 26 | listener.getLogger().flush(); 27 | } 28 | 29 | @Override 30 | public void onOffline(@NonNull Computer c, @CheckForNull OfflineCause cause) { 31 | updateStatus(c, "Offline"); 32 | } 33 | 34 | @Override 35 | public void onTemporarilyOnline(Computer c) { 36 | updateStatus(c, "Temporarily Online"); 37 | } 38 | 39 | @Override 40 | public void onTemporarilyOffline(Computer c, OfflineCause cause) { 41 | updateStatus(c, "Temporarily Offline"); 42 | } 43 | 44 | @Override 45 | public void onLaunchFailure(Computer c, TaskListener taskListener) throws IOException, InterruptedException { 46 | updateStatus(c, "Launch Failure"); 47 | taskListener.getLogger().flush(); 48 | } 49 | 50 | private void updateStatus(Computer c, String eventSource) { 51 | if (SplunkJenkinsInstallation.get().isEventDisabled(SLAVE_INFO)) { 52 | return; 53 | } 54 | Map slaveInfo = getComputerStatus(c); 55 | slaveInfo.put(EVENT_CAUSED_BY, eventSource); 56 | SplunkLogService.getInstance().send(slaveInfo, SLAVE_INFO); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /splunk-devops/src/test/java/com/splunk/splunkjenkins/utils/LogEventHelperTest.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.utils; 2 | 3 | import com.splunk.splunkjenkins.BaseTest; 4 | import hudson.model.Label; 5 | import hudson.model.Slave; 6 | import org.junit.*; 7 | import org.jvnet.hudson.test.JenkinsRule; 8 | 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.concurrent.TimeUnit; 12 | import java.util.logging.Level; 13 | 14 | import static com.splunk.splunkjenkins.SplunkConfigUtil.checkTokenAvailable; 15 | import static org.junit.Assert.*; 16 | 17 | public class LogEventHelperTest extends BaseTest { 18 | private static final java.util.logging.Logger LOG = java.util.logging.Logger.getLogger(LogEventHelper.class.getName()); 19 | 20 | @Test 21 | public void parseFileSize() throws Exception { 22 | long oneMB = 1024 * 1024; 23 | long twoKB = 2 * 1024; 24 | assertEquals(oneMB, LogEventHelper.parseFileSize("1MB")); 25 | assertEquals(512 * 1024, LogEventHelper.parseFileSize("0.5MB")); 26 | assertEquals(twoKB, LogEventHelper.parseFileSize("2KB")); 27 | assertEquals(123535, LogEventHelper.parseFileSize("123535")); 28 | assertEquals(0, LogEventHelper.parseFileSize("12s")); 29 | } 30 | 31 | @Test 32 | public void testSlaveStats() throws Exception { 33 | Slave slave = j.createOnlineSlave(); 34 | Map> slaveStats = LogEventHelper.getSlaveStats(); 35 | assertTrue("should not be empty", !slaveStats.isEmpty()); 36 | String slaveName = slave.getNodeName(); 37 | String monitorName = "ClockMonitor"; 38 | long timeToWait = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(1); 39 | boolean hasMonitorData = false; 40 | while (System.currentTimeMillis() < timeToWait) { 41 | slaveStats = LogEventHelper.getSlaveStats(); 42 | LOG.log(Level.FINER, slaveStats.toString()); 43 | if (slaveStats.containsKey(slaveName)) { 44 | hasMonitorData = slaveStats.get(slaveName).containsKey(monitorName); 45 | 46 | } 47 | if (hasMonitorData) { 48 | break; 49 | } 50 | } 51 | assertTrue(hasMonitorData); 52 | } 53 | } -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/LoggingInitStep.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins; 2 | 3 | import com.splunk.splunkjenkins.utils.LogEventHelper; 4 | import hudson.init.Initializer; 5 | import hudson.util.PluginServletFilter; 6 | import jenkins.util.Timer; 7 | 8 | import javax.servlet.ServletException; 9 | import java.util.concurrent.TimeUnit; 10 | import java.util.logging.Handler; 11 | import java.util.logging.Level; 12 | import java.util.logging.Logger; 13 | 14 | import static hudson.init.InitMilestone.JOB_LOADED; 15 | 16 | @edu.umd.cs.findbugs.annotations.SuppressFBWarnings("LG_LOST_LOGGER_DUE_TO_WEAK_REFERENCE") 17 | public class LoggingInitStep { 18 | private final static String rootLoggerName = ""; 19 | 20 | @Initializer(after = JOB_LOADED) 21 | public static void setupSplunkJenkins() { 22 | Timer.get().schedule(new Runnable() { 23 | @Override 24 | public void run() { 25 | registerHandler(); 26 | } 27 | }, 30, TimeUnit.SECONDS); 28 | } 29 | 30 | protected static void registerHandler() { 31 | Handler[] handlers = Logger.getLogger(rootLoggerName).getHandlers(); 32 | for (Handler handler : handlers) { 33 | if (handler instanceof JdkSplunkLogHandler) { 34 | // already registered 35 | return; 36 | } 37 | } 38 | //only log warning message for HealthMonitor which runs every 20s 39 | Logger.getLogger(HealthMonitor.class.getName()).setLevel(Level.WARNING); 40 | Logger.getLogger(rootLoggerName).addHandler(JdkSplunkLogHandler.LogHolder.LOG_HANDLER); 41 | //init plugin 42 | SplunkJenkinsInstallation.get().updateCache(); 43 | SplunkJenkinsInstallation.markComplete(true); 44 | Logger.getLogger(LoggingInitStep.class.getName()).info("plugin splunk-devops version " + LogEventHelper.getBuildVersion() + " loaded"); 45 | // check filter 46 | if (Constants.ENABLE_POST_LOGGER) { 47 | try { 48 | PluginServletFilter.addFilter(new WebPostAccessLogger()); 49 | } catch (ServletException e) { 50 | Logger.getLogger(LoggingInitStep.class.getName()).warning("failed to register splunk web access logger"); 51 | } 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /splunk-devops/src/test/java/com/splunk/splunkjenkins/CoverageMetricTest.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins; 2 | 3 | import hudson.model.AbstractBuild; 4 | import hudson.model.FreeStyleProject; 5 | import org.junit.Test; 6 | import org.jvnet.hudson.test.recipes.LocalData; 7 | 8 | import java.util.UUID; 9 | 10 | import static com.splunk.splunkjenkins.SplunkConfigUtil.verifySplunkSearchResult; 11 | 12 | public class CoverageMetricTest extends BaseTest { 13 | @LocalData 14 | @Test 15 | public void getCoberturaReport() throws Exception { 16 | String jobName = "Cobertura"; 17 | getCoverageReport(jobName, 50); 18 | } 19 | 20 | @LocalData 21 | @Test 22 | public void getCloverReport() throws Exception { 23 | String jobName = "Clover"; 24 | getCoverageReport(jobName, 50); 25 | } 26 | 27 | @LocalData 28 | @Test 29 | public void getJaCoCoReport() throws Exception { 30 | String jobName = "JaCoCo"; 31 | getCoverageReport(jobName, 50); 32 | } 33 | 34 | public void getCoverageReport(String jobName, int methodPercentage) throws Exception { 35 | FreeStyleProject project = (FreeStyleProject) j.getInstance().getItem(jobName); 36 | String newName = UUID.randomUUID().toString(); 37 | project.renameTo(newName); 38 | long startTime = System.currentTimeMillis(); 39 | AbstractBuild build = project.scheduleBuild2(0).get(); 40 | //verify coverage summary 41 | String query = "event_tag=job_event build_url=" + build.getUrl() + " \"coverage.methods\" >= " + methodPercentage; 42 | verifySplunkSearchResult(query, startTime, 1); 43 | //verify details 44 | query = "source=\"unit_test/coverage\" build_url=" + build.getUrl() + "|" 45 | + "rename \"coverage{}.methods_percentage\" as methods |mvexpand methods " + 46 | "|search methods>=" + methodPercentage; 47 | verifySplunkSearchResult(query, startTime, 1); 48 | //check total and covered number 49 | query = "splunk_server=local index=plugin_sandbox build_url=" + build.getUrl() + 50 | " source=\"unit_test/coverage\" \"com.mycompany\"\n" + 51 | "|spath output=coverage_json path=coverage{}|mvexpand coverage_json\n" + 52 | "|spath input=coverage_json|where name=\"com.mycompany\" and methods_total>methods_covered|table methods*"; 53 | verifySplunkSearchResult(query, startTime, 1); 54 | } 55 | } -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/model/JunitResultAdapter.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.model; 2 | 3 | import hudson.Extension; 4 | import hudson.tasks.junit.CaseResult; 5 | import hudson.tasks.junit.SuiteResult; 6 | import hudson.tasks.junit.TestResultAction; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | @Extension(optional = true) 12 | public class JunitResultAdapter extends AbstractTestResultAdapter { 13 | @Override 14 | public List getTestResult(TestResultAction resultAction) { 15 | List caseResults = new ArrayList<>(); 16 | hudson.tasks.junit.TestResult result = resultAction.getResult(); 17 | for (SuiteResult suite : result.getSuites()) { 18 | for (CaseResult testCase : suite.getCases()) { 19 | caseResults.add(convert(testCase, suite.getName())); 20 | } 21 | } 22 | return caseResults; 23 | } 24 | 25 | /** 26 | * @param methodResult 27 | * @return unified test case result 28 | */ 29 | private TestCaseResult convert(CaseResult methodResult, String suiteName) { 30 | String buildUrl = ""; 31 | if (methodResult.getRun() != null) { 32 | buildUrl = methodResult.getRun().getUrl(); 33 | } 34 | TestCaseResult testCaseResult = new TestCaseResult(); 35 | testCaseResult.setTestName(methodResult.getName()); 36 | testCaseResult.setUniqueName(methodResult.getFullName()); 37 | testCaseResult.setDuration(methodResult.getDuration()); 38 | testCaseResult.setClassName(methodResult.getClassName()); 39 | testCaseResult.setErrorDetails(methodResult.getErrorDetails()); 40 | testCaseResult.setErrorStackTrace(methodResult.getErrorStackTrace()); 41 | testCaseResult.setSkippedMessage(methodResult.getSkippedMessage()); 42 | testCaseResult.setFailedSince(methodResult.getFailedSince()); 43 | testCaseResult.setStderr(trimToLimit(methodResult.getStderr(), methodResult.getFullName(), buildUrl)); 44 | testCaseResult.setStdout(trimToLimit(methodResult.getStdout(), methodResult.getFullName(), buildUrl)); 45 | testCaseResult.setGroupName(suiteName); 46 | TestStatus status = TestStatus.SKIPPED; 47 | if (!methodResult.isSkipped()) { 48 | status = methodResult.isPassed() ? TestStatus.PASSED : TestStatus.FAILURE; 49 | } 50 | testCaseResult.setStatus(status); 51 | return testCaseResult; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /splunk-devops-extend/src/main/java/com/splunk/splunkjenkins/console/ConsoleNoteHandler.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.console; 2 | 3 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 4 | import org.jsoup.Jsoup; 5 | import org.jsoup.nodes.Attributes; 6 | import org.jsoup.nodes.Document; 7 | import org.jsoup.nodes.Element; 8 | 9 | public class ConsoleNoteHandler { 10 | 11 | private String href; 12 | private String nodeId; 13 | private String startId; 14 | private String enclosingId; 15 | private String label; 16 | 17 | public String getHref() { 18 | return href; 19 | } 20 | 21 | public String getNodeId() { 22 | return nodeId; 23 | } 24 | 25 | public String getStartId() { 26 | return startId; 27 | } 28 | 29 | public String getEnclosingId() { 30 | return enclosingId; 31 | } 32 | 33 | public String getLabel() { 34 | return label; 35 | } 36 | 37 | /** 38 | * parse first html tag or with nodeId attribute 39 | * 40 | * @param tag 41 | * @see org.jenkinsci.plugins.workflow.job.console.NewNodeConsoleNote 42 | * @see hudson.console.HyperlinkNote 43 | * @see org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApprovalNote 44 | */ 45 | @SuppressFBWarnings("SF_SWITCH_NO_DEFAULT") 46 | public void read(String tag) { 47 | Document doc = Jsoup.parse(tag); 48 | Element nodeEle = doc.getElementsByTag("a").first(); 49 | if (nodeEle == null) { 50 | nodeEle = doc.getElementsByTag("span").first(); 51 | } 52 | if (nodeEle == null) { 53 | return; 54 | } 55 | if (nodeEle.attributesSize() == 0) { 56 | return; 57 | } 58 | Attributes attrs = nodeEle.attributes(); 59 | href = getAttribute(attrs, "href"); 60 | nodeId = getAttribute(attrs, "nodeid"); 61 | startId = getAttribute(attrs, "startId"); 62 | enclosingId = getAttribute(attrs, "enclosingId"); 63 | label = getAttribute(attrs, "label"); 64 | } 65 | 66 | 67 | private String getAttribute(Attributes attrs, String attrKey) { 68 | String key = attrKey.toLowerCase(); 69 | // In the HTML syntax, attribute names may be written with any mix of lower- and uppercase letters that, when converted to all-lowercase, matches the attribute's name; attribute names are case-insensitive. 70 | if (attrs.hasKey(key)) { 71 | return attrs.get(key); 72 | } else { 73 | return null; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/utils/PlainTextConsoleUtils.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.utils; 2 | 3 | import hudson.console.ConsoleNote; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | 7 | public class PlainTextConsoleUtils { 8 | 9 | public static int arrayIndexOf(byte[] buf, int start, int end, byte[] matches) { 10 | int e = end - matches.length + 1; 11 | 12 | OUTER: 13 | for (int i = start; i < e; i++) { 14 | if (buf[i] == matches[0]) { 15 | // check for the rest of the match 16 | for (int j = 1; j < matches.length; j++) { 17 | if (buf[i + j] != matches[j]) 18 | continue OUTER; 19 | } 20 | return i; // found it 21 | } 22 | } 23 | return -1; // not found 24 | } 25 | 26 | /** 27 | * the logical extracted from PlainTextConsoleOutputStream 28 | * console annotation will be removed, e.g. 29 | * Input:Started by user ESC[8mha:AAAAlh+LCAAAAAAAAP9b85aBtbiIQTGjNKU4P08vOT+vOD8nVc83PyU1x6OyILUoJzMv2y+/JJUBAhiZGBgqihhk0NSjKDWzXb3RdlLBUSYGJk8GtpzUvPSSDB8G5tKinBIGIZ+sxLJE/ZzEvHT94JKizLx0a6BxUmjGOUNodHsLgAzOEgYu/dLi1CL9vNKcHACFIKlWvwAAAA==ESC[0manonymous 30 | * Output:Started by user anonymous 31 | * 32 | * @param in the byte array 33 | * @param length how many bytes we want to read in 34 | * @param out write max(length) to out 35 | * @see hudson.console.PlainTextConsoleOutputStream 36 | */ 37 | public static void decodeConsole(byte[] in, int length, ByteArrayOutputStream out) { 38 | int next = arrayIndexOf(in, 0, length, ConsoleNote.PREAMBLE); 39 | // perform byte[]->char[] while figuring out the char positions of the BLOBs 40 | int written = 0; 41 | while (next >= 0) { 42 | //text prior to PREAMBLE 43 | if (next > written) { 44 | out.write(in, written, next - written); 45 | written = next; 46 | } 47 | int endPos = arrayIndexOf(in, next + ConsoleNote.PREAMBLE.length, length, ConsoleNote.POSTAMBLE); 48 | if (endPos < 0) { 49 | break; 50 | } else { 51 | //skips to POSTAMBLE 52 | written = endPos + ConsoleNote.POSTAMBLE.length; 53 | next = arrayIndexOf(in, written, length, ConsoleNote.PREAMBLE); 54 | } 55 | } 56 | // finish the remaining bytes->chars conversion 57 | out.write(in, written, length - written); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /splunk-devops/src/test/java/com/splunk/splunkjenkins/listeners/LoggingQueueListenerTest.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.listeners; 2 | 3 | import com.splunk.splunkjenkins.BaseTest; 4 | import com.splunk.splunkjenkins.Constants; 5 | import hudson.model.FreeStyleProject; 6 | import hudson.model.Label; 7 | import hudson.model.Queue; 8 | import hudson.model.queue.QueueTaskFuture; 9 | import org.junit.Test; 10 | 11 | import java.io.IOException; 12 | import java.util.concurrent.ExecutionException; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | import static com.splunk.splunkjenkins.SplunkConfigUtil.verifySplunkSearchResult; 16 | import static org.junit.Assert.assertEquals; 17 | 18 | public class LoggingQueueListenerTest extends BaseTest { 19 | private int waitInQueueTime = 5; 20 | private String jobName = "validate-queue-foo"; 21 | 22 | @Test 23 | public void testQueueMessage() throws ExecutionException, InterruptedException, IOException { 24 | FreeStyleProject project = j.createFreeStyleProject(jobName); 25 | long startTime = System.currentTimeMillis(); 26 | // set a quiet period 27 | QueueTaskFuture future = project.scheduleBuild2(waitInQueueTime); 28 | Queue.Item[] items = j.jenkins.getQueue().getItems(); 29 | assertEquals(1, items.length); 30 | Long queueId = items[0].getId(); 31 | future.waitForStart(); 32 | // check wait message 33 | String splQuery = "event_tag=queue queue_id=" + queueId + " type=dequeue_waiting " 34 | + "waiting_time>" + waitInQueueTime; 35 | verifySplunkSearchResult(splQuery, startTime, 1); 36 | splQuery = "event_tag=queue queue_id=" + queueId + " type=" + Constants.DEQUEUE_TAG_NAME; 37 | verifySplunkSearchResult(splQuery, startTime, 1); 38 | // set a label 39 | String nodeLabel = "remotes-no-such-node"; 40 | project.setAssignedLabel(Label.get(nodeLabel)); 41 | project.scheduleBuild2(0); 42 | items = j.jenkins.getQueue().getItems(); 43 | assertEquals(1, items.length); 44 | queueId = items[0].getId(); 45 | // job waits for start since there is no node have the label 46 | Thread.sleep(TimeUnit.SECONDS.toMillis(15)); 47 | j.jenkins.getQueue().clear(); 48 | splQuery = "event_tag=queue queue_id=" + queueId + " type=dequeue_buildable " 49 | + "buildable_time>3"; 50 | verifySplunkSearchResult(splQuery, startTime, 1); 51 | splQuery = "event_tag=queue queue_id=" + queueId + " type=" + Constants.DEQUEUE_TAG_NAME; 52 | verifySplunkSearchResult(splQuery, startTime, 1); 53 | } 54 | } -------------------------------------------------------------------------------- /splunk-devops-extend/src/main/java/com/splunk/splunkjenkins/console/LabelConsoleLineStream.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.console; 2 | 3 | import com.splunk.splunkjenkins.model.EventRecord; 4 | import hudson.util.ByteArrayOutputStream2; 5 | 6 | import java.io.FilterOutputStream; 7 | import java.io.IOException; 8 | import java.io.OutputStream; 9 | import java.util.logging.Level; 10 | import java.util.logging.Logger; 11 | import java.util.regex.Pattern; 12 | 13 | import org.apache.commons.lang3.StringUtils; 14 | 15 | import static com.splunk.splunkjenkins.Constants.CONSOLE_TEXT_SINGLE_LINE_MAX_LENGTH; 16 | import static com.splunk.splunkjenkins.model.EventType.CONSOLE_LOG; 17 | 18 | public class LabelConsoleLineStream extends FilterOutputStream { 19 | private static final int RECEIVE_BUFFER_SIZE = 512; 20 | private static final Logger LOGGER = Logger.getLogger(LabelConsoleLineStream.class.getName()); 21 | public static final Pattern ANSI_COLOR_ESCAPE = Pattern.compile("\u001B\\[[\\d;]+m"); 22 | private ByteArrayOutputStream2 branch = new ByteArrayOutputStream2(RECEIVE_BUFFER_SIZE); 23 | PipelineConsoleDecoder decoder; 24 | String source; 25 | 26 | public LabelConsoleLineStream(OutputStream out, String source, PipelineConsoleDecoder decoder) { 27 | super(out); 28 | this.decoder = decoder; 29 | this.source = source; 30 | } 31 | 32 | @Override 33 | public void write(int b) throws IOException { 34 | super.write(b); 35 | if (b == '\n') { 36 | eol(); 37 | } else { 38 | //do not need write \n 39 | branch.write(b); 40 | } 41 | if (branch.size() > CONSOLE_TEXT_SINGLE_LINE_MAX_LENGTH) { 42 | eol(); 43 | } 44 | } 45 | 46 | protected void eol() { 47 | String line = decoder.decodeLine(branch.getBuffer(), branch.size()); 48 | if (line == null) { 49 | // actually line can not be null, always ends with \n, add null check in case decode error 50 | return; 51 | } 52 | // reuse the buffer under normal circumstances 53 | branch.reset(); 54 | line = ANSI_COLOR_ESCAPE.matcher(line).replaceAll(""); 55 | if (StringUtils.isNotBlank(line)) { 56 | EventRecord record = new EventRecord(line, CONSOLE_LOG); 57 | record.setSource(source); 58 | ConsoleRecordCacheUtils.enqueue(record); 59 | } 60 | } 61 | 62 | @Override 63 | public void flush() throws IOException { 64 | super.flush(); 65 | ConsoleRecordCacheUtils.flushLog(); 66 | LOGGER.log(Level.FINE, "flush splunk log for " + source); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /splunk-devops/src/test/java/com/splunk/splunkjenkins/TeeConsoleLogFilterTest.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins; 2 | 3 | import com.splunk.splunkjenkins.utils.LogEventHelper; 4 | import com.splunk.splunkjenkins.utils.SplunkLogService; 5 | import hudson.console.ConsoleAnnotationOutputStream; 6 | import hudson.model.FreeStyleBuild; 7 | import hudson.model.FreeStyleProject; 8 | import hudson.tasks.Shell; 9 | 10 | import java.io.IOException; 11 | import java.io.StringReader; 12 | import java.io.StringWriter; 13 | import java.nio.charset.StandardCharsets; 14 | import java.util.UUID; 15 | import java.util.concurrent.TimeUnit; 16 | import java.util.logging.Logger; 17 | 18 | import hudson.util.ByteArrayOutputStream2; 19 | import org.apache.commons.io.IOUtils; 20 | import org.junit.After; 21 | import org.junit.Before; 22 | import org.junit.Rule; 23 | import org.junit.Test; 24 | import org.jvnet.hudson.test.CaptureEnvironmentBuilder; 25 | import org.jvnet.hudson.test.JenkinsRule; 26 | 27 | import static com.splunk.splunkjenkins.SplunkConfigUtil.checkTokenAvailable; 28 | import static com.splunk.splunkjenkins.SplunkConfigUtil.verifySplunkSearchResult; 29 | import static org.junit.Assert.assertEquals; 30 | import static org.junit.Assert.assertFalse; 31 | import static org.junit.Assert.fail; 32 | 33 | public class TeeConsoleLogFilterTest extends BaseTest { 34 | private static final Logger LOG = Logger.getLogger(TeeConsoleLogFilterTest.class.getName()); 35 | 36 | @Test 37 | public void decorateLogger() throws Exception { 38 | FreeStyleProject p = j.createFreeStyleProject("console_" + UUID.randomUUID()); 39 | CaptureEnvironmentBuilder captureEnvironment = new CaptureEnvironmentBuilder(); 40 | p.getBuildersList().add(captureEnvironment); 41 | p.getBuildersList().add(new Shell("echo $PATH;echo $$")); 42 | long eventCount = SplunkLogService.getInstance().getSentCount(); 43 | FreeStyleBuild b = j.buildAndAssertSuccess(p); 44 | long timeToWait = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(2); 45 | while (eventCount >= SplunkLogService.getInstance().getSentCount() && (p.getLastBuild() == null || p.getLastBuild().isBuilding())) { 46 | Thread.sleep(1000); 47 | if (System.currentTimeMillis() > timeToWait) { 48 | LOG.fine("queue size:" + SplunkLogService.getInstance().getQueueSize()); 49 | fail("can not send event in time"); 50 | } 51 | } 52 | String query = "index=" + SplunkConfigUtil.INDEX_NAME + " source=" + b.getUrl() + "console"; 53 | int expected = 5; 54 | verifySplunkSearchResult(query, b.getTimeInMillis(), expected); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/Messages.properties: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | #see also http://docs.oracle.com/javase/6/docs/api/java/util/Properties.html 21 | DisplayName=Splunk-Jenkins 22 | Description=A Splunk-Jenkins plugin for Splunking Jenkins. 23 | PleaseProvideHost=Please provide the hostname to a Splunk instance. 24 | CloudHostPrefix=You are using Splunk Cloud, please provide host name starts input- or http-inputs-, \ 25 | please try input-{0} or http-inputs-{0}. See also http://dev.splunk.com/view/event-collector/SP-CAAAE7G 26 | HostNameSchemaWarning=Invalid hostname, do you mean {0}? 27 | HostNameListWarning=Multiple hosts is not supported, please setup load balancer, and user the virtual name 28 | HostNameInvalid=Failed to validate hostname 29 | ValueIntErrorMsg=This value must be a valid int. 30 | ValueCannotBeBlank=This value cannot be blank. 31 | ConfigBuildStepTitle=Send data to Splunk 32 | ConfigBuildStepSendLog=Send build log 33 | ConfigBuildStepSendEnvVars=Send environment variables 34 | ConfigBuildStepSendFiles=Files to send as an event 35 | ConfigBuildFileToAppend=File to append to event 36 | InvalidToken=Token is invalid 37 | InvalidPattern=The pattern is invalid, please check Pattern 38 | InvalidHostOrToken=Invalid config, please check Hostname or Token 39 | SplunArtifactArchive=Send files to Splunk 40 | SplunkIconName=/plugin/splunk-devops/images/splunk_logo_green.png 41 | PermissionGroup=Splunk 42 | PluginViewPermission.Description=This permission grants access to the Splunk Instance Link. 43 | -------------------------------------------------------------------------------- /splunk-devops/src/test/resources/com/splunk/splunkjenkins/TestResultAdapterTest/jobs/xunit_job1/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | 7 | 8 | true 9 | false 10 | false 11 | false 12 | 13 | false 14 | 15 | 16 | cat >test-result.xml <<'EOF' 17 | <?xml version="1.0" encoding="UTF-8"?> 18 | <testsuite name="hudson.util.ProcessTreeTest" time="0.357" tests="3" errors="0" skipped="0" failures="0"> 19 | <testcase name="remoting" classname="hudson.util.ProcessTreeTest" time="0.157"/> 20 | <testcase name="remoting-a" classname="hudson.util.ProcessTreeTest" time="0.157"/> 21 | <testcase name="remoting-b" classname="hudson.util.ProcessTreeTest" time="0.157"/> 22 | 23 | </testsuite> 24 | EOF 25 | 26 | 27 | 28 | 29 | 30 | 31 | test-result.xml 32 | true 33 | true 34 | true 35 | true 36 | 37 | 38 | 39 | 40 | 41 | 1 42 | 43 | 2 44 | 45 | 46 | 47 | 1 48 | 49 | 2 50 | 51 | 52 | 1 53 | 54 | 3000 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/WebPostAccessLogger.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins; 2 | 3 | import com.splunk.splunkjenkins.utils.SplunkLogService; 4 | import jenkins.model.Jenkins; 5 | import org.acegisecurity.Authentication; 6 | 7 | import javax.servlet.Filter; 8 | import javax.servlet.FilterChain; 9 | import javax.servlet.FilterConfig; 10 | import javax.servlet.ServletException; 11 | import javax.servlet.ServletRequest; 12 | import javax.servlet.ServletResponse; 13 | import javax.servlet.http.HttpServletRequest; 14 | import java.io.IOException; 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | import java.util.logging.Logger; 18 | import java.util.regex.Pattern; 19 | 20 | import static com.splunk.splunkjenkins.Constants.TAG; 21 | import static com.splunk.splunkjenkins.model.EventType.JENKINS_CONFIG; 22 | 23 | public class WebPostAccessLogger implements Filter { 24 | private static final Logger LOG = Logger.getLogger(WebPostAccessLogger.class.getName()); 25 | private static final Pattern FILTER_PATTERN = Pattern.compile("/(?:configSubmit|updateSubmit|script|doDelete)"); 26 | 27 | @Override 28 | public void init(FilterConfig filterConfig) throws ServletException { 29 | LOG.info("splunk-filter loaded"); 30 | } 31 | 32 | @Override 33 | public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 34 | try { 35 | loggerUserAction(servletRequest); 36 | } catch (Exception e) { 37 | //ignore; 38 | } 39 | filterChain.doFilter(servletRequest, servletResponse); 40 | } 41 | 42 | private void loggerUserAction(ServletRequest servletRequest) { 43 | if (SplunkJenkinsInstallation.get().isEventDisabled(JENKINS_CONFIG)) { 44 | return; 45 | } 46 | if (!(servletRequest instanceof HttpServletRequest)) { 47 | return; 48 | } 49 | HttpServletRequest request = (HttpServletRequest) servletRequest; 50 | if (!"POST".equals(request.getMethod())) { 51 | return; 52 | } 53 | Authentication auth = Jenkins.getAuthentication(); 54 | String path = request.getRequestURI(); 55 | if (auth == null || path == null || !auth.isAuthenticated()) { 56 | return; 57 | } 58 | if (!FILTER_PATTERN.matcher(path).find()) { 59 | return; 60 | } 61 | Map auditInfo = new HashMap(); 62 | auditInfo.put("user", auth.getName()); 63 | auditInfo.put("message", "POST " + request.getRequestURI()); 64 | auditInfo.put(TAG, "audit_trail"); 65 | SplunkLogService.getInstance().send(auditInfo, JENKINS_CONFIG, "web_access"); 66 | } 67 | 68 | @Override 69 | public void destroy() { 70 | 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Splunk for Jenkins 2 | --------- 3 | 4 | To Install Develop Version 5 | ---- 6 | - clone the repo 7 | - `mvn package` 8 | - mvn will generate `splunk-devops/target/splunk-devops.hpi` which you can install into Jenkins by the web interface or just put it in the `JENKINS_HOME/plugins` folder 9 | 10 | 11 | To Setup 12 | ---- 13 | ### Configure plugin 14 | 15 | - Go to https://jenkins-url/configure 16 | - Enter Hostname, Port, and Token 17 | - Enable RawEvent support if you are using Splunk version 6.3.1511 or later 18 | - Click "Test Connection" to verify the config 19 | - Enable it and Save 20 | 21 | ![Screenshot](doc/images/splunk_for_jenkins_config_basic.png) 22 | 23 | ### Customize Job Data Sent to Splunk 24 | 25 | - In the advance configure section, you can customize the post data using groovy DSL 26 | - ``send(Object message)`` will send the information to splunk 27 | - ``AbstractBuild build``, ``Map env`` can be used directly. Variable env is a Map of Environment variables, build is hudson.model.AbstractBuild 28 | - `getBuildEvent()` will return metadata about the build, such as build result, build URL, user who triggered the build 29 | - `getJunitReport(int pageSize)` will return a list of test results, which contains total, passes, failures, skips, time and testcase of type List 30 | - `getJunitReport()` is an alias of `getJunitReport(Integer.MAX_VALUE)[0]` 31 | - sendCoverageReport(pageSize) send coverage report, with pagination support 32 | - sendTestReport(pageSize) send Test report, with pagination support 33 | - `archive(String includes, String excludes, boolean uploadFromSlave, String fileSizeLimit)` send log file to splunk 34 | - `archive(String includes)` is an alias of `archive(includes, null, false, "")` 35 | - `getAction(Class type)` is an alias of ` build.getAction(type)` 36 | - `getActionByClassName(String className)` same as `getAction(Class type)` but no need to import the class before use 37 | - `hasPublisherName(String className)` check whether the publisher is configured for the build (applied to AbstractBuild only) 38 | - Here is the default settings for post job data processing 39 | 40 | ```groovy 41 | //send job metadata and junit reports with page size set to 50 (each event contains max 50 test cases) 42 | splunkins.sendTestReport(50) 43 | //send coverage, each event contains max 50 class metrics 44 | splunkins.sendCoverageReport(50) 45 | //send all logs from workspace to splunk, with each file size limits to 10MB 46 | splunkins.archive("**/*.log", null, false, "10MB") 47 | 48 | ``` 49 | 50 | Contributing to the plugin 51 | ---- 52 | - clone the repo and update code 53 | - start splunk, you can get a free trail version from 54 | [Splunk](https://splunk.com/) 55 | - `mvn clean verify -Dsplunk-host=localhost -Dsplunk-username=admin -Dsplunk-passwd=changeme` 56 | to run tests using local splunk instance. 57 | - send pull requests 58 | 59 | Documentation 60 | ---- 61 | See the [documentation](doc/splunk-devops-usage.md) 62 | -------------------------------------------------------------------------------- /splunk-devops-extend/src/main/java/com/splunk/splunkjenkins/PipelineGraphVizSupport.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins; 2 | 3 | import com.splunk.splunkjenkins.model.EventType; 4 | import com.splunk.splunkjenkins.model.LoggingJobExtractor; 5 | import com.splunk.splunkjenkins.utils.SplunkLogService; 6 | import hudson.Extension; 7 | import org.jenkinsci.plugins.workflow.graph.BlockEndNode; 8 | import org.jenkinsci.plugins.workflow.graph.BlockStartNode; 9 | import org.jenkinsci.plugins.workflow.graph.FlowGraphWalker; 10 | import org.jenkinsci.plugins.workflow.graph.FlowNode; 11 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 12 | 13 | import java.util.Collections; 14 | import java.util.Map; 15 | import java.util.logging.Level; 16 | import java.util.logging.Logger; 17 | 18 | /** 19 | * The getDot code is borrowed from https://plugins.jenkins.io/workflow-job 20 | */ 21 | @Extension 22 | public class PipelineGraphVizSupport extends LoggingJobExtractor { 23 | private static final String SUFFIX = "graphviz"; 24 | private static final Logger LOGGER = Logger.getLogger(PipelineGraphVizSupport.class.getName()); 25 | 26 | @Override 27 | public Map extract(WorkflowRun workflowRun, boolean completed) { 28 | if (!completed) { 29 | return Collections.EMPTY_MAP; 30 | } 31 | if (!SplunkJenkinsInstallation.get().isJobIgnored(workflowRun.getUrl())) { 32 | SplunkPipelineJobProperty jobProperty = workflowRun.getParent().getProperty(SplunkPipelineJobProperty.class); 33 | LOGGER.log(Level.FINE, "job {0}, property {1}", new Object[]{workflowRun.getUrl(), jobProperty}); 34 | if (jobProperty != null && jobProperty.isDiagramEnabled()) { 35 | String dotStr = getDot(workflowRun); 36 | String source = workflowRun.getUrl() + SUFFIX; 37 | SplunkLogService.getInstance().send(dotStr, EventType.BUILD_EVENT, source); 38 | } 39 | } 40 | return Collections.EMPTY_MAP; 41 | } 42 | 43 | 44 | private String getDot(WorkflowRun run) { 45 | StringBuffer buffer = new StringBuffer(); 46 | buffer.append("digraph G {\n"); 47 | FlowGraphWalker walker = new FlowGraphWalker(run.getExecution()); 48 | for (FlowNode n : walker) { 49 | for (FlowNode p : n.getParents()) { 50 | buffer.append(String.format("%s -> %s%n", p.getId(), n.getId())); 51 | } 52 | 53 | if (n instanceof BlockStartNode) { 54 | buffer.append(String.format("%s [shape=trapezium]%n", n.getId())); 55 | } else if (n instanceof BlockEndNode) { 56 | BlockEndNode sn = (BlockEndNode) n; 57 | buffer.append(String.format("%s [shape=invtrapezium]%n", n.getId())); 58 | buffer.append(String.format("%s -> %s [style=dotted]%n", sn.getStartNode().getId(), n.getId())); 59 | } 60 | buffer.append(String.format("%s [label=\"%s: %s\"]%n", n.getId(), n.getId(), n.getDisplayName())); 61 | } 62 | 63 | buffer.append("}"); 64 | return buffer.toString(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /splunk-devops/src/main/groovy/com/splunk/splunkjenkins/UserActionDSL.groovy: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins 2 | 3 | import com.splunk.splunkjenkins.listeners.LoggingRunListener 4 | import com.splunk.splunkjenkins.utils.LogEventHelper 5 | import hudson.model.Run 6 | import hudson.model.TaskListener 7 | import jenkins.model.Jenkins 8 | import org.apache.commons.lang3.StringUtils 9 | import org.codehaus.groovy.control.CompilerConfiguration 10 | import org.codehaus.groovy.control.customizers.ImportCustomizer 11 | import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval 12 | import org.jenkinsci.plugins.scriptsecurity.scripts.languages.GroovyLanguage 13 | import org.kohsuke.stapler.jelly.groovy.GroovyClosureScript 14 | 15 | import java.util.logging.Level 16 | import java.util.logging.Logger 17 | 18 | import static com.splunk.splunkjenkins.utils.LogEventHelper.getBuildVariables 19 | 20 | public class UserActionDSL { 21 | static final LOG = Logger.getLogger(LoggingRunListener.class.name) 22 | 23 | public void perform(Run build, TaskListener listener, GroovyCodeSource codeSource) { 24 | try { 25 | Map buildParameters = getBuildVariables(build); 26 | String scriptText=codeSource.getScriptText() 27 | if (StringUtils.isNotEmpty(scriptText)) { 28 | def workSpace; 29 | if (build.metaClass.respondsTo(build, "getWorkspace")) { 30 | //getWorkspace defined in build 31 | workSpace = build.workspace; 32 | } 33 | RunDelegate delegate = new RunDelegate(build: build, workSpace: workSpace, 34 | env: buildParameters, listener: listener); 35 | Binding binding = new Binding(); 36 | binding.setVariable("splunkins", delegate); 37 | try { 38 | //check approval, will throw UnapprovedUsageException 39 | ScriptApproval.get().using(scriptText, GroovyLanguage.get()) 40 | //call setDelegate to RunDelegate instance and run 41 | CompilerConfiguration cc = new CompilerConfiguration(); 42 | cc.scriptBaseClass = GroovyClosureScript.class.name; 43 | ImportCustomizer ic = new ImportCustomizer() 44 | ic.addStaticStars(LogEventHelper.class.name) 45 | ic.addStarImport("jenkins.model") 46 | cc.addCompilationCustomizers(ic) 47 | GroovyClosureScript dslScript = (GroovyClosureScript) new GroovyShell(Jenkins.instance.pluginManager.uberClassLoader, binding, cc) 48 | .parse(codeSource) 49 | dslScript.setDelegate(delegate); 50 | dslScript.run() 51 | } catch (Exception e) { 52 | LOG.log(Level.SEVERE, "UserActionDSL script failed", e); 53 | e.printStackTrace(listener.getLogger()) 54 | } 55 | listener.getLogger().flush(); 56 | } 57 | } catch (Exception e) { 58 | e.printStackTrace(); 59 | } 60 | } 61 | 62 | } 63 | 64 | -------------------------------------------------------------------------------- /splunk-devops/src/test/java/com/splunk/splunkjenkins/SplunkArchiveFileTest.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins; 2 | 3 | import hudson.Functions; 4 | import hudson.model.FreeStyleBuild; 5 | import hudson.model.FreeStyleProject; 6 | import hudson.model.Label; 7 | import hudson.tasks.BatchFile; 8 | import hudson.tasks.Shell; 9 | import org.junit.Test; 10 | import org.jvnet.hudson.test.CaptureEnvironmentBuilder; 11 | 12 | import java.util.UUID; 13 | import java.util.logging.Logger; 14 | 15 | import static com.splunk.splunkjenkins.SplunkConfigUtil.INDEX_NAME; 16 | import static com.splunk.splunkjenkins.SplunkConfigUtil.verifySplunkSearchResult; 17 | import static org.junit.Assert.assertNotNull; 18 | import static org.junit.Assert.fail; 19 | 20 | public class SplunkArchiveFileTest extends BaseTest { 21 | public static Object result = null; 22 | private static final Logger logger = Logger.getLogger(SplunkArchiveFileTest.class.getName()); 23 | 24 | public String buildWithScript(String groovyScript) throws Exception { 25 | Label label = j.jenkins.getLabel("filetest"); 26 | j.createOnlineSlave(label); 27 | FreeStyleProject project = j.createFreeStyleProject("verify_archive" + UUID.randomUUID()); 28 | CaptureEnvironmentBuilder captureEnvironment = new CaptureEnvironmentBuilder(); 29 | project.getBuildersList().add(captureEnvironment); 30 | if(Functions.isWindows()) { 31 | project.getBuildersList().add(new BatchFile("echo file-test > process_list.txt")); 32 | } else { 33 | project.getBuildersList().add(new Shell("echo file-test > process_list.txt")); 34 | } 35 | project.setAssignedLabel(label); 36 | SplunkJenkinsInstallation.get().setScriptContent(groovyScript); 37 | SplunkJenkinsInstallation.get().updateCache(); 38 | long start_time = System.currentTimeMillis(); 39 | FreeStyleBuild build = j.buildAndAssertSuccess(project); 40 | String buildUrl = build.getUrl(); 41 | int expected = 1; 42 | String query = "search index=" + INDEX_NAME 43 | + " source=\"" + buildUrl + "process_list.txt\""; 44 | logger.info(query); 45 | verifySplunkSearchResult(query, start_time, expected); 46 | return build.getUrl(); 47 | } 48 | 49 | @Test 50 | public void testUploadFromSlave() { 51 | String script = "println \"uploading files\"\n" + 52 | "splunkins.archive(\"*.txt\",\"\",true,\"0\")"; 53 | try { 54 | buildWithScript(script); 55 | } catch (Exception e) { 56 | e.printStackTrace(); 57 | fail("upload failed"); 58 | } 59 | } 60 | 61 | @Test 62 | public void testUploadFromMaster() throws Exception { 63 | result = null; 64 | String script = "println \"uploading files\"\n" + 65 | "def sentCount=splunkins.archive(\"*.txt\",\"\",false,\"10MB\");" + 66 | "com.splunk.splunkjenkins.SplunkArchiveFileTest.result=sentCount;" + 67 | "println \"send \"+sentCount"; 68 | buildWithScript(script); 69 | assertNotNull("archive file completed", result); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /splunk-devops/src/test/java/com/splunk/splunkjenkins/utils/TestCaseResultUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.utils; 2 | 3 | import com.splunk.splunkjenkins.BaseTest; 4 | import com.splunk.splunkjenkins.Constants; 5 | import com.splunk.splunkjenkins.model.AbstractTestResultAdapter; 6 | import com.splunk.splunkjenkins.model.JunitResultAdapter; 7 | import com.splunk.splunkjenkins.model.JunitTestCaseGroup; 8 | import hudson.model.Run; 9 | import hudson.tasks.junit.TestResult; 10 | import hudson.tasks.junit.TestResultAction; 11 | import org.junit.Test; 12 | 13 | import java.io.File; 14 | import java.io.FileWriter; 15 | import java.util.List; 16 | 17 | import static org.junit.Assert.assertEquals; 18 | import static org.junit.Assert.assertTrue; 19 | 20 | public class TestCaseResultUtilsTest extends BaseTest { 21 | public void splitJunitTestCase(int total, int pageSize) throws Exception { 22 | TestResult result = new TestResult(); 23 | File junitFile = File.createTempFile("junit", ".xml"); 24 | FileWriter out = new FileWriter(junitFile); 25 | out.write("\n"); 26 | out.write("\n" + 27 | "\t\n" + 28 | "\t\n" + 29 | ""); 30 | for (int i = 1; i < total; i++) { 31 | out.write("") 33 | .concat("\n")); 34 | } 35 | out.write(""); 36 | out.close(); 37 | result.parse(junitFile); 38 | result.tally(); 39 | assertEquals(total, result.getTotalCount()); 40 | assertEquals(1, result.getFailCount()); 41 | 42 | TestResultAction action = new TestResultAction((Run) null, result, null); 43 | JunitResultAdapter adapter = new JunitResultAdapter(); 44 | List suites = TestCaseResultUtils.split(adapter.getTestResult(action) 45 | , pageSize); 46 | int remained = (total % pageSize == 0) ? 0 : 1; 47 | int pageCount = total / pageSize + remained; 48 | assertEquals(pageCount, suites.size()); 49 | } 50 | 51 | @Test 52 | public void testSplitReminder() throws Exception { 53 | splitJunitTestCase(512, 5); 54 | } 55 | 56 | @Test 57 | public void testSplitDivide() throws Exception { 58 | splitJunitTestCase(5, 5); 59 | } 60 | 61 | @Test 62 | public void testTrimStdout() { 63 | int messageSize = (1 << 21) + 1; 64 | String originalMessage = new String(new byte[messageSize]); 65 | String truncatedMessage = AbstractTestResultAdapter.trimToLimit(originalMessage, "case1", "job/1"); 66 | assertEquals(Constants.MAX_JUNIT_STDIO_SIZE, truncatedMessage.length()); 67 | //short one 68 | messageSize = (1 << 21) - 1; 69 | originalMessage = new String(new byte[messageSize]); 70 | truncatedMessage = AbstractTestResultAdapter.trimToLimit(originalMessage, "case1", "job/1"); 71 | assertTrue(truncatedMessage.length() == messageSize); 72 | } 73 | } -------------------------------------------------------------------------------- /splunk-devops-extend/src/main/java/com/splunk/splunkjenkins/console/PipelineConsoleDecoder.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.console; 2 | 3 | import com.splunk.splunkjenkins.Constants; 4 | import com.splunk.splunkjenkins.utils.PlainTextConsoleUtils; 5 | import hudson.console.ConsoleNote; 6 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 7 | 8 | import edu.umd.cs.findbugs.annotations.CheckForNull; 9 | import java.io.ByteArrayInputStream; 10 | import java.io.ByteArrayOutputStream; 11 | import java.io.DataInputStream; 12 | import java.io.IOException; 13 | import java.io.Serializable; 14 | import java.util.logging.Logger; 15 | 16 | import static com.splunk.splunkjenkins.utils.PlainTextConsoleUtils.arrayIndexOf; 17 | import static java.util.logging.Level.WARNING; 18 | 19 | public class PipelineConsoleDecoder implements Serializable { 20 | private static final long serialVersionUID = 1L; 21 | private static final Logger LOG = Logger.getLogger(PipelineConsoleDecoder.class.getName()); 22 | private transient WorkflowRun run; 23 | private transient LabelMarkupText markupText = new LabelMarkupText(); 24 | private boolean parseLabelFlag = Constants.DECODE_PIPELINE_CONSOLE; 25 | 26 | public PipelineConsoleDecoder(WorkflowRun run) { 27 | this.run = run; 28 | if (run == null) { 29 | parseLabelFlag = false; 30 | } 31 | } 32 | 33 | @CheckForNull 34 | public String decodeLine(byte[] in, int length) { 35 | try { 36 | ByteArrayOutputStream bout = new ByteArrayOutputStream(); 37 | if (parseLabelFlag) { 38 | decodeConsoleObjectStream(in, length, bout); 39 | } else { 40 | PlainTextConsoleUtils.decodeConsole(in, length, bout); 41 | } 42 | return bout.toString("UTF-8"); 43 | } catch (IOException ex) { 44 | LOG.log(WARNING, "failed to decode log" + ex); 45 | return null; 46 | } 47 | } 48 | 49 | private void decodeConsoleObjectStream(byte[] in, int length, ByteArrayOutputStream out) throws IOException { 50 | int next = arrayIndexOf(in, 0, length, ConsoleNote.PREAMBLE); 51 | // perform byte[]->char[] while figuring out the char positions of the BLOBs 52 | int written = 0; 53 | while (next >= 0) { 54 | if (next > written) { 55 | out.write(in, written, next - written); 56 | written = next; 57 | } 58 | int rest = length - next; 59 | ByteArrayInputStream b = new ByteArrayInputStream(in, next, rest); 60 | try { 61 | ConsoleNote consoleNote = ConsoleNote.readFrom(new DataInputStream(b)); 62 | consoleNote.annotate(run, markupText, 0); 63 | markupText.write(out); 64 | } catch (IOException | ClassNotFoundException ex) { 65 | LOG.log(WARNING, "failed to decode console note", ex); 66 | } 67 | int bytesUsed = rest - b.available(); // bytes consumed by annotations 68 | written += bytesUsed; 69 | next = arrayIndexOf(in, written, length, ConsoleNote.PREAMBLE); 70 | } 71 | if (length - written > 0) { 72 | markupText.writePreviousLabel(out); 73 | // finish the remaining bytes->chars conversion 74 | out.write(in, written, length - written); 75 | } 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /splunk-devops/src/test/java/com/splunk/splunkjenkins/SplunkLogServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins; 2 | 3 | import java.io.IOException; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import java.util.UUID; 7 | import java.util.concurrent.TimeUnit; 8 | import java.util.logging.Logger; 9 | 10 | import com.splunk.splunkjenkins.model.EventType; 11 | import com.splunk.splunkjenkins.utils.SplunkLogService; 12 | import org.junit.*; 13 | 14 | import static com.splunk.splunkjenkins.SplunkConfigUtil.verifySplunkSearchResult; 15 | import static org.junit.Assert.*; 16 | 17 | public class SplunkLogServiceTest extends BaseTest { 18 | private static final Logger LOG = Logger.getLogger(SplunkLogServiceTest.class.getName()); 19 | private static final int BATCH_COUNT = 1000; 20 | 21 | /** 22 | * Test of update method, of class SplunkLogService. 23 | */ 24 | @Test 25 | public void testLogServiceSendMethod() throws IOException, InterruptedException { 26 | LOG.info("running test SplunkLogServiceTest testLogServiceSendMethod"); 27 | assertTrue("config should be valid", SplunkJenkinsInstallation.get().isValid()); 28 | String line = "127.0.0.1 - admin \"GET /en-US/ HTTP/1.1\""; 29 | boolean queuedGenericMessage = SplunkLogService.getInstance().send(line); 30 | assertTrue("should put message in queue", queuedGenericMessage); 31 | long timestamp = System.currentTimeMillis(); 32 | String query = "index=" + SplunkConfigUtil.INDEX_NAME + " |spath batch|where batch=" + timestamp; 33 | LOG.info(query); 34 | long initNumber = SplunkLogService.getInstance().getSentCount(); 35 | for (int i = 0; i < BATCH_COUNT; i++) { 36 | Map data = new HashMap(); 37 | data.put("id", UUID.randomUUID().toString()); 38 | data.put("batch", timestamp); 39 | data.put("number", i); 40 | boolean queued = SplunkLogService.getInstance().send(data); 41 | assertTrue("should put the message to queue", queued); 42 | } 43 | //give some time to send,max wait time is 3 minute 44 | long timeToWait = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(3); 45 | while (SplunkLogService.getInstance().getSentCount() < (BATCH_COUNT + initNumber)) { 46 | Thread.sleep(1000); 47 | long queueSize = SplunkLogService.getInstance().getQueueSize(); 48 | long sentCount = SplunkLogService.getInstance().getSentCount(); 49 | long remaining = BATCH_COUNT + initNumber - sentCount; 50 | LOG.fine("queue size:" + queueSize + " sent:" + sentCount); 51 | if (System.currentTimeMillis() > timeToWait) { 52 | fail("can not send events in time, remaining " + remaining); 53 | } 54 | } 55 | int expected = BATCH_COUNT; 56 | verifySplunkSearchResult(query, timestamp, expected); 57 | } 58 | 59 | @Test 60 | public void sendFloatNaN() { 61 | Map result = new HashMap(); 62 | result.put("floatNaN", Float.NaN); 63 | result.put("doubleMaxVal", Double.MAX_VALUE); 64 | result.put("doubleMinVal", Double.MIN_VALUE); 65 | result.put("doubleNaN", Double.NaN); 66 | long timestamp = System.currentTimeMillis(); 67 | SplunkLogService.getInstance().send(result, EventType.LOG); 68 | String query = "doubleMaxVal>9999999999999999"; 69 | verifySplunkSearchResult(query, timestamp, 1); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /splunk-devops-extend/src/main/java/com/splunk/splunkjenkins/console/SplunkTaskListenerFactory.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.console; 2 | 3 | import com.google.common.cache.CacheBuilder; 4 | import com.google.common.cache.CacheLoader; 5 | import com.google.common.cache.LoadingCache; 6 | import com.splunk.splunkjenkins.SplunkJenkinsInstallation; 7 | import hudson.Extension; 8 | import hudson.model.Queue; 9 | import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner; 10 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 11 | import org.jenkinsci.plugins.workflow.log.TaskListenerDecorator; 12 | 13 | import edu.umd.cs.findbugs.annotations.CheckForNull; 14 | import edu.umd.cs.findbugs.annotations.NonNull; 15 | 16 | import java.io.IOException; 17 | import java.util.concurrent.ExecutionException; 18 | import java.util.logging.Level; 19 | import java.util.logging.Logger; 20 | 21 | import static com.splunk.splunkjenkins.model.EventType.CONSOLE_LOG; 22 | 23 | @Extension(optional = true) 24 | public class SplunkTaskListenerFactory implements TaskListenerDecorator.Factory { 25 | private static final Logger LOGGER = Logger.getLogger(SplunkConsoleTaskListenerDecorator.class.getName()); 26 | private static final boolean ENABLE_REMOTE_DECORATOR = Boolean.parseBoolean(System.getProperty("splunkins.enableRemoteTaskListenerDecorator", "true")); 27 | private static final transient LoadingCache cachedDecorator = CacheBuilder.newBuilder() 28 | .weakKeys() 29 | .maximumSize(1024) 30 | .build(new CacheLoader() { 31 | @Override 32 | public SplunkConsoleTaskListenerDecorator load(WorkflowRun key) { 33 | SplunkConsoleTaskListenerDecorator decorator = new SplunkConsoleTaskListenerDecorator(key); 34 | if (ENABLE_REMOTE_DECORATOR) { 35 | decorator.setRemoteSplunkinsConfig(SplunkJenkinsInstallation.get().toMap()); 36 | } 37 | return decorator; 38 | } 39 | }); 40 | 41 | @Override 42 | /* 43 | data stream is passed to splunk decorator first (it sees data in the last due to decorator behavior) 44 | */ 45 | public boolean isAppliedBeforeMainDecorator() { 46 | return true; 47 | } 48 | 49 | @CheckForNull 50 | @Override 51 | public TaskListenerDecorator of(@NonNull FlowExecutionOwner flowExecutionOwner) { 52 | if (!SplunkJenkinsInstallation.get().isPipelineFilterEnabled()) { 53 | return null; 54 | } 55 | if (SplunkJenkinsInstallation.get().isEventDisabled(CONSOLE_LOG)) { 56 | return null; 57 | } 58 | try { 59 | Queue.Executable executable = flowExecutionOwner.getExecutable(); 60 | if (executable instanceof WorkflowRun) { 61 | WorkflowRun run = (WorkflowRun) executable; 62 | if (SplunkJenkinsInstallation.get().isJobIgnored(run.getUrl())) { 63 | return null; 64 | } 65 | return cachedDecorator.get(run); 66 | } 67 | } catch (IOException x) { 68 | LOGGER.log(Level.WARNING, null, x); 69 | } catch (ExecutionException e) { 70 | LOGGER.finer("failed to load cached decorator"); 71 | } 72 | return null; 73 | } 74 | 75 | public static void removeCache(WorkflowRun run) { 76 | cachedDecorator.invalidate(run); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /doc/extension.md: -------------------------------------------------------------------------------- 1 | Plugin implements some interfaces and marks the implementation to use annotation @Extension so Jenkins can load it dynamically 2 | 3 | # Configure 4 | 5 | ### SplunkJenkinsInstallation 6 | package com.splunk.splunkjenkins 7 | Follow jenkins conventions, using java bean property host, port, token, useSSL, scriptPath etc and some helper method such as 8 | doCheckHost to validate host, doTestHttpInput to verify connection, see also https://wiki.jenkins-ci. org/display/JENKINS/Form+Validation 9 | 10 | # Function 11 | ### Listeners 12 | package com.splunk.splunkjenkins.listeners, receives notifications from jenkins, for example in Jenkins delete job action 13 | ``` 14 | /** 15 | * Called in response to {@link Job#doDoDelete(StaplerRequest, StaplerResponse)} 16 | */ 17 | public void onDeleted(TopLevelItem item) throws IOException { 18 | ItemListener.fireOnDeleted(item); 19 | 20 | items.remove(item.getName()); 21 | // For compatibility with old views: 22 | for (View v : views) 23 | v.onJobRenamed(item, item.getName(), null); 24 | } 25 | 26 | ``` 27 | it will call fireOnDeleted to notify all ItemListeners 28 | 29 | 30 | ## Listeners list 31 | #### [SecurityListener](http://javadoc.jenkins-ci.org/jenkins/security/SecurityListener.html) 32 | record user login/logout and failedToLogIn events 33 | #### [SaveableListener](http://javadoc.jenkins-ci.org/hudson/model/listeners/SaveableListener.html) 34 | when user made changes to jenkins config (either plugin config or job config), record the config xml. disabled by default until user add jenkins_config.monitoring=true into metadata config 35 | #### [ItemListener](http://javadoc.jenkins-ci.org/hudson/model/listeners/RunListener.html) 36 | similar to SaveableListener but for Job only, it has finer grained audit event. used to capture job created, renamed, copied and deleted 37 | #### [QueueListener](http://javadoc.jenkins-ci.org/hudson/model/queue/QueueListener.html) 38 | listen for onEnterWaiting, onLeaveWaiting, onEnterBlocked, onLeaveBlocked, onEnterBuildable, onLeavebBuildable, and onLeft. Record the job queueTime in each of the phase along with other jenkins metrics and reports to Splunk. 39 | #### [RunListener](http://javadoc.jenkins-ci.org/hudson/model/listeners/RunListener.html) 40 | listen for onStarted and onCompleted, and extract upstream job, build cause, scm, job result, and invoke DSL if defined 41 | #### [ComputerListener](http://javadoc.jenkins-ci.org/hudson/slaves/ComputerListener.html) 42 | record slave online, offline, temporarilyOffline, and launchFailure 43 | ## [Notifier](http://javadoc.jenkins-ci.org/hudson/tasks/Notifier.html) 44 | Contribute to post build step, user can custom the logs to send to splunk in addition to what we have in Groovy DSL. 45 | 46 | ## Links 47 | ### RootAction 48 | add link to home page 49 | ### ManagementLink 50 | add link to management page 51 | ### TransientComputerActionFactory 52 | and link to other types, such as Build, Computer 53 | 54 | ## Task 55 | we extends AsyncPeriodicWork to run tasks periodic to gather agent metrics 56 | 57 | # Service 58 | SplunkLogService launched two threads to drain the splunk event queue, used (Splunk HTTP Event Collector)[http://dev.splunk.com/view/event-collector/SP-CAAAE6M] to pump data into splunk, message format 59 | 60 | ``` 61 | 62 | { 63 | "time": 1426279439, 64 | "host": "localhost", 65 | "source": "datasource", 66 | "sourcetype": "txt", 67 | "index": "main", 68 | "event": { jenkins_event_json_here } 69 | } 70 | 71 | ``` 72 | 73 | -------------------------------------------------------------------------------- /splunk-devops-shaded/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | com.splunk.splunkins 7 | pom 8 | 1.11.2-SNAPSHOT 9 | 10 | 11 | splunk-devops-shaded 12 | jar 13 | 14 | Shaded http client and gson jar 15 | 16 | 17 | 18 | 19 | org.apache.maven.plugins 20 | maven-shade-plugin 21 | 3.3.0 22 | 23 | 24 | package 25 | 26 | shade 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | true 35 | 36 | 37 | org.apache.httpcomponents:* 38 | com.google.code.gson:gson 39 | 40 | 41 | 42 | 43 | org.apache.http 44 | shaded.splk.org.apache.http 45 | 46 | 47 | com.google.gson 48 | shaded.splk.com.google.gson 49 | 50 | 51 | true 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | org.apache.httpcomponents 63 | httpcore 64 | 4.4.13 65 | 66 | 67 | org.apache.httpcomponents 68 | httpclient 69 | 4.5.13 70 | 71 | 72 | com.google.code.gson 73 | gson 74 | 2.8.9 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /splunk-devops/src/test/resources/com/splunk/splunkjenkins/CoverageMetricTest/jobs/Clover/workspace/clover.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 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 | -------------------------------------------------------------------------------- /splunk-devops/src/test/java/com/splunk/splunkjenkins/SplunkJenkinsInstallationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright 2023 CloudBees, Inc. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package com.splunk.splunkjenkins; 26 | 27 | import com.splunk.splunkjenkins.model.EventType; 28 | import com.splunk.splunkjenkins.model.MetaDataConfigItem; 29 | 30 | import java.io.File; 31 | import java.nio.charset.StandardCharsets; 32 | import java.util.Collections; 33 | import java.util.Map; 34 | import java.util.Set; 35 | 36 | import com.splunk.splunkjenkins.utils.RemoteUtils; 37 | import hudson.util.Secret; 38 | import jenkins.model.Jenkins; 39 | import org.apache.commons.io.FileUtils; 40 | 41 | import static org.hamcrest.MatcherAssert.assertThat; 42 | import static org.hamcrest.Matchers.containsString; 43 | import static org.hamcrest.Matchers.hasSize; 44 | import static org.hamcrest.Matchers.is; 45 | 46 | import org.junit.Rule; 47 | import org.junit.Test; 48 | import org.jvnet.hudson.test.JenkinsRule; 49 | 50 | public final class SplunkJenkinsInstallationTest { 51 | 52 | @Rule 53 | public final JenkinsRule r = new JenkinsRule(); 54 | 55 | @Test 56 | public void reload() throws Exception { 57 | SplunkJenkinsInstallation cfg = SplunkJenkinsInstallation.get(); 58 | cfg.setMetadataItemSet(Collections.singleton(new MetaDataConfigItem(EventType.BUILD_EVENT.toString(), "index", "value000"))); 59 | cfg.save(); 60 | File xmlFile = new File(Jenkins.get().getRootDir(), cfg.getId() + ".xml"); 61 | String xml = FileUtils.readFileToString(xmlFile, StandardCharsets.UTF_8); 62 | assertThat(xml, containsString("value000")); 63 | xml = xml.replace("value000", "value999"); 64 | FileUtils.writeStringToFile(xmlFile, xml, StandardCharsets.UTF_8); 65 | cfg.load(); 66 | Set metadataItemSet = cfg.getMetadataItemSet(); 67 | assertThat(metadataItemSet, hasSize(1)); 68 | assertThat(metadataItemSet.iterator().next().getValue(), is("value999")); 69 | } 70 | 71 | 72 | @Test 73 | public void testTokenConvert() { 74 | String token = "hello-token"; 75 | String host = "foo-host"; 76 | SplunkJenkinsInstallation cfg = SplunkJenkinsInstallation.get(); 77 | cfg.setToken(Secret.fromString(token)); 78 | cfg.setHost(host); 79 | Map config = cfg.toMap(); 80 | // try to convert back, will set to cachedConfig 81 | RemoteUtils.initSplunkConfigOnAgent(config); 82 | cfg.setHost(null); 83 | cfg.setToken(null); 84 | // try to get the cached config which will be used for remoting 85 | cfg = SplunkJenkinsInstallation.get(); 86 | assertThat(cfg.getHost(), is(host)); 87 | assertThat(cfg.getTokenValue(), is(token)); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/model/AbstractTestResultAdapter.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.model; 2 | 3 | import hudson.ExtensionList; 4 | import hudson.ExtensionPoint; 5 | import hudson.model.Run; 6 | import hudson.tasks.test.AbstractTestResultAction; 7 | import hudson.tasks.test.TestResult; 8 | import org.jvnet.tiger_types.Types; 9 | 10 | import edu.umd.cs.findbugs.annotations.NonNull; 11 | import java.lang.reflect.ParameterizedType; 12 | import java.lang.reflect.Type; 13 | import java.util.ArrayList; 14 | import java.util.Collections; 15 | import java.util.List; 16 | import java.util.logging.Level; 17 | 18 | import static com.splunk.splunkjenkins.Constants.MAX_JUNIT_STDIO_SIZE; 19 | 20 | public abstract class AbstractTestResultAdapter implements ExtensionPoint { 21 | private static final java.util.logging.Logger LOG = java.util.logging.Logger.getLogger(AbstractTestResultAdapter.class.getName()); 22 | 23 | public final Class targetType; 24 | 25 | public AbstractTestResultAdapter() { 26 | Type type = Types.getBaseClass(getClass(), AbstractTestResultAdapter.class); 27 | if (type instanceof ParameterizedType) 28 | targetType = Types.erasure(Types.getTypeArgument(type, 0)); 29 | else 30 | throw new IllegalStateException(getClass() + " uses the raw type for extending AbstractTestResultAdapter"); 31 | 32 | } 33 | 34 | public A getAction(Run run) { 35 | return run.getAction(targetType); 36 | } 37 | 38 | public boolean isApplicable(Run build) { 39 | return getAction(build) != null; 40 | } 41 | 42 | /** 43 | * @param build jenkins build 44 | * @return all the test result added in the build 45 | */ 46 | @NonNull 47 | public static List getTestResult(Run build) { 48 | return getTestResult(build, Collections.emptyList()); 49 | } 50 | 51 | /** 52 | * @param build jenkins build 53 | * @param ignoredActions a list of test action class name 54 | * @return the test result filtered by the test action name 55 | */ 56 | @NonNull 57 | public static List getTestResult(Run build, @NonNull List ignoredActions) { 58 | List adapters = ExtensionList.lookup(AbstractTestResultAdapter.class); 59 | List testResults = new ArrayList<>(); 60 | for (AbstractTestResultAdapter adapter : adapters) { 61 | if (adapter.isApplicable(build)) { 62 | AbstractTestResultAction action = adapter.getAction(build); 63 | if (ignoredActions.contains(action.getClass().getName())) { 64 | // the test action is ignored 65 | continue; 66 | } 67 | testResults.addAll(adapter.getTestResult(action)); 68 | } 69 | } 70 | return testResults; 71 | } 72 | 73 | public abstract List getTestResult(A resultAction); 74 | 75 | public static String trimToLimit(String message, String caseName, String url) { 76 | String truncatedMessage = "...truncated"; 77 | if (MAX_JUNIT_STDIO_SIZE < truncatedMessage.length() || message == null || message.length() <= MAX_JUNIT_STDIO_SIZE) { 78 | return message; 79 | } 80 | // setUniqueName was called before setStdout/setStderr in JunitResultAdapter/TestNGResultAdapter 81 | LOG.log(Level.WARNING, "build_url={0} testcase={1} message=\"stdout or stderr too large\" length={2,number,#}" + 82 | " truncated_size={3,number,#}\n" + 83 | "please adjust jenkins startup option -Dsplunkins.junitStdioLimit=x if you want to avoid this", 84 | new Object[]{url, caseName, message.length(), MAX_JUNIT_STDIO_SIZE}); 85 | return message.substring(0, MAX_JUNIT_STDIO_SIZE - truncatedMessage.length()) + truncatedMessage; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /splunk-devops-extend/src/test/java/com/splunk/splunkjenkins/StageStepNodesTest.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins; 2 | 3 | import hudson.model.labels.LabelAtom; 4 | import hudson.slaves.DumbSlave; 5 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; 6 | import org.jenkinsci.plugins.workflow.job.WorkflowJob; 7 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 8 | import org.junit.Before; 9 | import org.junit.ClassRule; 10 | import org.junit.Rule; 11 | import org.junit.Test; 12 | import org.jvnet.hudson.test.BuildWatcher; 13 | import org.jvnet.hudson.test.JenkinsRule; 14 | 15 | import static com.splunk.splunkjenkins.SplunkConfigUtil.checkTokenAvailable; 16 | import static com.splunk.splunkjenkins.SplunkConfigUtil.verifySplunkSearchResult; 17 | import static org.junit.Assert.assertFalse; 18 | import static org.junit.Assert.assertTrue; 19 | 20 | public class StageStepNodesTest { 21 | @ClassRule 22 | public static BuildWatcher buildWatcher = new BuildWatcher(); 23 | @Rule 24 | public JenkinsRule r = new JenkinsRule(); 25 | private String jobScript = "stage(\"unit-test\"){\n" + 26 | " node(\"ci-1\"){\n" + 27 | " echo \"hello\"\n" + 28 | " echo \"hello world2\"\n" + 29 | " }\n" + 30 | " node(\"ci-2\"){\n" + 31 | " echo \"hello\"\n" + 32 | " }\n" + 33 | "}"; 34 | private String nestedStage = "node{\n" + 35 | " stage('build') {\n" + 36 | " stage('linux'){\n" + 37 | " echo \"hello\"\n" + 38 | " }\n" + 39 | " stage('windows'){\n" + 40 | " echo \"hello\"\n" + 41 | " stage('build c'){\n" + 42 | " echo \"hello\"\n" + 43 | " build job:'dummy-job', wait:false \n" + 44 | " }\n" + 45 | " }\n" + 46 | " }\n" + 47 | "}"; 48 | 49 | @Before 50 | public void setUp() throws Exception { 51 | org.junit.Assume.assumeTrue(checkTokenAvailable()); 52 | } 53 | 54 | @Test 55 | public void testStageNodes() throws Exception { 56 | long startTime = System.currentTimeMillis(); 57 | DumbSlave node = r.createOnlineSlave(new LabelAtom("ci-1")); 58 | DumbSlave node1 = r.createOnlineSlave(new LabelAtom("ci-2")); 59 | WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "stage-node-job"); 60 | p.setDefinition(new CpsFlowDefinition(jobScript, true)); 61 | WorkflowRun b1 = r.assertBuildStatusSuccess(p.scheduleBuild2(0)); 62 | assertFalse(b1.isBuilding()); 63 | r.assertLogContains("hello", b1); 64 | assertTrue(b1.getDuration() > 0); 65 | //check exec_node 66 | verifySplunkSearchResult("\"stages{}.children{}.exec_node\"=\"" + node.getNodeName() + "\"", startTime, 1); 67 | verifySplunkSearchResult("\"stages{}.children{}.exec_node\"=\"" + node1.getNodeName() + "\"", startTime, 1); 68 | } 69 | 70 | @Test 71 | public void testFlattenStageResult() throws Exception { 72 | long startTime = System.currentTimeMillis(); 73 | WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "nested-stage-job"); 74 | p.setDefinition(new CpsFlowDefinition(nestedStage, true)); 75 | WorkflowJob dummy = r.jenkins.createProject(WorkflowJob.class, "dummy-job"); 76 | dummy.setDefinition(new CpsFlowDefinition("node{}", true)); 77 | WorkflowRun b1 = r.assertBuildStatusSuccess(p.scheduleBuild2(0)); 78 | assertFalse(b1.isBuilding()); 79 | r.assertLogContains("hello", b1); 80 | assertTrue(b1.getDuration() > 0); 81 | //check stage count 82 | verifySplunkSearchResult("type=completed build_url=" + b1.getUrl() + "|spath output=stages path=\"stages{}\"" + 83 | "| mvexpand stages" + 84 | "| table stages", startTime, 4); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/model/TestCaseResult.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.model; 2 | 3 | import hudson.tasks.test.TestObject; 4 | import hudson.tasks.test.TestResult; 5 | 6 | public class TestCaseResult extends TestResult { 7 | private float duration; 8 | private String className; 9 | private String testName; 10 | private String groupName; 11 | private boolean skipped; 12 | private String skippedMessage; 13 | private String errorStackTrace; 14 | private String errorDetails; 15 | private String stdout, stderr; 16 | private int failedSince; 17 | private String uniqueName; 18 | private TestStatus status; 19 | 20 | @Override 21 | public TestObject getParent() { 22 | return null; 23 | } 24 | 25 | @Override 26 | public TestResult findCorrespondingResult(String id) { 27 | return null; 28 | } 29 | 30 | @Override 31 | public String getDisplayName() { 32 | return null; 33 | } 34 | 35 | @Override 36 | public float getDuration() { 37 | return duration; 38 | } 39 | 40 | public void setDuration(float duration) { 41 | this.duration = duration; 42 | } 43 | 44 | public String getClassName() { 45 | return className; 46 | } 47 | 48 | public void setClassName(String className) { 49 | this.className = className; 50 | } 51 | 52 | public String getTestName() { 53 | return testName; 54 | } 55 | 56 | public void setTestName(String testName) { 57 | this.testName = testName; 58 | } 59 | 60 | public boolean isSkipped() { 61 | return skipped; 62 | } 63 | 64 | public void setSkipped(boolean skipped) { 65 | this.skipped = skipped; 66 | } 67 | 68 | public String getSkippedMessage() { 69 | return skippedMessage; 70 | } 71 | 72 | public void setSkippedMessage(String skippedMessage) { 73 | this.skippedMessage = skippedMessage; 74 | } 75 | 76 | @Override 77 | public String getErrorStackTrace() { 78 | return errorStackTrace; 79 | } 80 | 81 | public void setErrorStackTrace(String errorStackTrace) { 82 | this.errorStackTrace = errorStackTrace; 83 | } 84 | 85 | @Override 86 | public String getErrorDetails() { 87 | return errorDetails; 88 | } 89 | 90 | public void setErrorDetails(String errorDetails) { 91 | this.errorDetails = errorDetails; 92 | } 93 | 94 | @Override 95 | public String getStdout() { 96 | return stdout; 97 | } 98 | 99 | public void setStdout(String stdout) { 100 | this.stdout = stdout; 101 | } 102 | 103 | @Override 104 | public String getStderr() { 105 | return stderr; 106 | } 107 | 108 | public void setStderr(String stderr) { 109 | this.stderr = stderr; 110 | } 111 | 112 | @Override 113 | public int getFailedSince() { 114 | return failedSince; 115 | } 116 | 117 | public void setFailedSince(int failedSince) { 118 | this.failedSince = failedSince; 119 | } 120 | 121 | public String getUniqueName() { 122 | return uniqueName; 123 | } 124 | 125 | public void setUniqueName(String uniqueName) { 126 | this.uniqueName = uniqueName; 127 | } 128 | 129 | public TestStatus getStatus() { 130 | return status; 131 | } 132 | 133 | public void setStatus(TestStatus status) { 134 | this.status = status; 135 | } 136 | 137 | @Override 138 | public int getPassCount() { 139 | return TestStatus.PASSED == status ? 1 : 0; 140 | } 141 | 142 | @Override 143 | public int getFailCount() { 144 | return TestStatus.FAILURE == status ? 1 : 0; 145 | } 146 | 147 | @Override 148 | public int getSkipCount() { 149 | return TestStatus.SKIPPED == status ? 1 : 0; 150 | } 151 | 152 | public String getGroupName() { 153 | return groupName; 154 | } 155 | 156 | public void setGroupName(String groupName) { 157 | this.groupName = groupName; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /splunk-devops-extend/src/main/java/com/splunk/splunkjenkins/SplunkConsoleLogStep.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins; 2 | 3 | import com.google.common.collect.ImmutableSet; 4 | import com.splunk.splunkjenkins.console.ConsoleRecordCacheUtils; 5 | import com.splunk.splunkjenkins.console.SplunkConsoleTaskListenerDecorator; 6 | import hudson.Extension; 7 | import hudson.model.Run; 8 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 9 | import org.jenkinsci.plugins.workflow.log.TaskListenerDecorator; 10 | import org.jenkinsci.plugins.workflow.steps.BodyExecutionCallback; 11 | import org.jenkinsci.plugins.workflow.steps.BodyInvoker; 12 | import org.jenkinsci.plugins.workflow.steps.Step; 13 | import org.jenkinsci.plugins.workflow.steps.StepContext; 14 | import org.jenkinsci.plugins.workflow.steps.StepDescriptor; 15 | import org.jenkinsci.plugins.workflow.steps.StepExecution; 16 | import org.kohsuke.stapler.DataBoundConstructor; 17 | 18 | import edu.umd.cs.findbugs.annotations.NonNull; 19 | import java.util.Set; 20 | import java.util.logging.Level; 21 | import java.util.logging.Logger; 22 | 23 | public class SplunkConsoleLogStep extends Step { 24 | private static final Logger LOG = Logger.getLogger(SplunkConsoleLogStep.class.getName()); 25 | 26 | @DataBoundConstructor 27 | public SplunkConsoleLogStep() { 28 | } 29 | 30 | @Override 31 | public StepExecution start(StepContext context) throws Exception { 32 | return new ConsoleLogExecutionImpl(context); 33 | } 34 | 35 | @Extension(optional = true) 36 | public static class DescriptorImpl extends StepDescriptor { 37 | @Override 38 | public Set> getRequiredContext() { 39 | return ImmutableSet.of(Run.class); 40 | } 41 | 42 | /** 43 | * {@inheritDoc} 44 | */ 45 | @Override 46 | public String getFunctionName() { 47 | return "sendSplunkConsoleLog"; 48 | } 49 | 50 | /** 51 | * {@inheritDoc} 52 | */ 53 | @NonNull 54 | @Override 55 | public String getDisplayName() { 56 | return "Send console log Splunk"; 57 | } 58 | 59 | /** 60 | * {@inheritDoc} 61 | */ 62 | @Override 63 | public boolean takesImplicitBlockArgument() { 64 | return true; 65 | } 66 | } 67 | 68 | 69 | public static class ConsoleLogExecutionImpl extends StepExecution { 70 | public ConsoleLogExecutionImpl(StepContext context) { 71 | super(context); 72 | } 73 | 74 | /** 75 | * {@inheritDoc} 76 | */ 77 | @Override 78 | public boolean start() throws Exception { 79 | //refer to WithContextStep implementation 80 | StepContext context = getContext(); 81 | Run run = context.get(Run.class); 82 | BodyInvoker invoker = context.newBodyInvoker().withCallback(new BodyExecutionCallbackConsole()); 83 | if (!SplunkJenkinsInstallation.get().isPipelineFilterEnabled()) { 84 | invoker.withContext(TaskListenerDecorator.merge(context.get(TaskListenerDecorator.class), new SplunkConsoleTaskListenerDecorator((WorkflowRun) run))); 85 | } else { 86 | String jobName = run.getParent().getFullName(); 87 | LOG.log(Level.INFO, "ignored sendSplunkConsoleLog since global filter is enabled, job-name=" + jobName); 88 | } 89 | invoker.start(); 90 | return false; 91 | } 92 | 93 | /** 94 | * {@inheritDoc} 95 | */ 96 | @Override 97 | public void stop(@NonNull Throwable cause) throws Exception { 98 | getContext().onFailure(cause); 99 | } 100 | } 101 | 102 | public static class BodyExecutionCallbackConsole extends BodyExecutionCallback.TailCall { 103 | private static final long serialVersionUID = 1L; 104 | 105 | @Override 106 | protected void finished(StepContext stepContext) throws Exception { 107 | ConsoleRecordCacheUtils.flushLog(); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /splunk-devops-extend/src/main/java/com/splunk/splunkjenkins/SplunkLogFileStep.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins; 2 | 3 | import com.google.common.collect.ImmutableSet; 4 | import hudson.EnvVars; 5 | import hudson.Extension; 6 | import hudson.FilePath; 7 | import hudson.model.Run; 8 | import hudson.model.TaskListener; 9 | import org.jenkinsci.plugins.workflow.steps.Step; 10 | import org.jenkinsci.plugins.workflow.steps.StepContext; 11 | import org.jenkinsci.plugins.workflow.steps.StepDescriptor; 12 | import org.jenkinsci.plugins.workflow.steps.StepExecution; 13 | import org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution; 14 | import org.kohsuke.stapler.DataBoundConstructor; 15 | import org.kohsuke.stapler.DataBoundSetter; 16 | 17 | import edu.umd.cs.findbugs.annotations.NonNull; 18 | import java.util.Set; 19 | 20 | import static com.splunk.splunkjenkins.utils.LogEventHelper.parseFileSize; 21 | import static com.splunk.splunkjenkins.utils.LogEventHelper.sendFiles; 22 | 23 | /** 24 | * Send logs to splunk 25 | */ 26 | public class SplunkLogFileStep extends Step { 27 | //required fields 28 | String includes; 29 | 30 | @DataBoundSetter 31 | String sizeLimit; 32 | @DataBoundSetter 33 | String excludes; 34 | @DataBoundSetter 35 | boolean publishFromSlave; 36 | 37 | @DataBoundConstructor 38 | public SplunkLogFileStep(@NonNull String includes) { 39 | this.includes = includes; 40 | } 41 | 42 | @Override 43 | public StepExecution start(StepContext context) throws Exception { 44 | return new SplunkLogFileStepExecution(context, this); 45 | } 46 | 47 | public String getIncludes() { 48 | return includes; 49 | } 50 | 51 | public void setIncludes(String includes) { 52 | this.includes = includes; 53 | } 54 | 55 | public String getExcludes() { 56 | return excludes; 57 | } 58 | 59 | public void setExcludes(String excludes) { 60 | this.excludes = excludes; 61 | } 62 | 63 | public boolean isPublishFromSlave() { 64 | return publishFromSlave; 65 | } 66 | 67 | public void setPublishFromSlave(boolean publishFromSlave) { 68 | this.publishFromSlave = publishFromSlave; 69 | } 70 | 71 | public String getSizeLimit() { 72 | return sizeLimit; 73 | } 74 | 75 | public void setSizeLimit(String sizeLimit) { 76 | this.sizeLimit = sizeLimit; 77 | } 78 | 79 | @Extension 80 | public static class DescriptorImpl extends StepDescriptor { 81 | 82 | @Override 83 | public Set> getRequiredContext() { 84 | return ImmutableSet.of(Run.class, TaskListener.class, FilePath.class, EnvVars.class); 85 | } 86 | 87 | @Override 88 | public String getFunctionName() { 89 | return "sendSplunkFile"; 90 | } 91 | 92 | @NonNull 93 | @Override 94 | public String getDisplayName() { 95 | return "Send files to Splunk"; 96 | } 97 | } 98 | 99 | public static class SplunkLogFileStepExecution extends SynchronousNonBlockingStepExecution { 100 | protected SplunkLogFileStepExecution(StepContext context, SplunkLogFileStep step) throws Exception { 101 | super(context); 102 | this.step = step; 103 | } 104 | 105 | private static final long serialVersionUID = 1152009261375345133L; 106 | private transient SplunkLogFileStep step; 107 | 108 | @Override 109 | protected Void run() throws Exception { 110 | if (!SplunkJenkinsInstallation.get().isEnabled()) { 111 | return null; 112 | } 113 | TaskListener listener = getContext().get(TaskListener.class); 114 | FilePath workspace = getContext().get(FilePath.class); 115 | Run build = getContext().get(Run.class); 116 | EnvVars envVars = getContext().get(EnvVars.class); 117 | sendFiles(build, workspace, envVars, listener, 118 | step.includes, step.excludes, step.publishFromSlave, parseFileSize(step.sizeLimit)); 119 | return null; 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/model/CloverCoverageMetrics.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.model; 2 | 3 | import hudson.Extension; 4 | import hudson.plugins.clover.CloverBuildAction; 5 | import hudson.plugins.clover.Ratio; 6 | import hudson.plugins.clover.results.*; 7 | 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | import static com.splunk.splunkjenkins.Constants.COVERAGE_OVERALL_NAME; 14 | 15 | /** 16 | * CoverageMetric for clover 17 | */ 18 | @Extension(optional = true) 19 | public class CloverCoverageMetrics extends CoverageMetricsAdapter { 20 | /** 21 | * @return coverage summary 22 | * {@inheritDoc} 23 | */ 24 | @Override 25 | public Map getMetrics(CloverBuildAction coverageAction) { 26 | ProjectCoverage projectCoverage = coverageAction.getResult(); 27 | Map result = extract(projectCoverage); 28 | return result; 29 | } 30 | 31 | @Override 32 | public List getReport(CloverBuildAction coverageAction) { 33 | ProjectCoverage projectCoverage = coverageAction.getResult(); 34 | List result = new ArrayList<>(); 35 | CoverageDetail summary = new CoverageDetail(COVERAGE_OVERALL_NAME, CoverageLevel.PROJECT); 36 | result.add(summary); 37 | appendDetail(summary, coverageAction); 38 | for (PackageCoverage pcover : projectCoverage.getChildren()) { 39 | CoverageDetail packageDetail = new CoverageDetail(pcover.getName(), CoverageLevel.PACKAGE); 40 | result.add(packageDetail); 41 | appendDetail(packageDetail, pcover); 42 | for (FileCoverage fcover : pcover.getChildren()) { 43 | CoverageDetail fileDetail = new CoverageDetail(pcover.getName(), CoverageLevel.FILE); 44 | result.add(fileDetail); 45 | appendDetail(fileDetail, fcover); 46 | for (ClassCoverage clazzCover : fcover.getChildren()) { 47 | CoverageDetail clazzDetail = new CoverageDetail(clazzCover.getName(), CoverageLevel.CLASS); 48 | result.add(clazzDetail); 49 | appendDetail(clazzDetail, clazzCover); 50 | } 51 | } 52 | } 53 | return result; 54 | } 55 | 56 | private Map extract(AbstractCloverMetrics coverageObject) { 57 | Map result = new HashMap<>(); 58 | putMetricIfExists(result, Metric.METHOD, coverageObject.getMethodCoverage()); 59 | putMetricIfExists(result, Metric.STATEMENT, coverageObject.getStatementCoverage()); 60 | putMetricIfExists(result, Metric.CONDITIONAL, coverageObject.getConditionalCoverage()); 61 | putMetricIfExists(result, Metric.ELEMENT, coverageObject.getElementCoverage()); 62 | return result; 63 | } 64 | 65 | private void putMetricIfExists(Map result, Metric metric, Ratio ratio) { 66 | if (ratio.denominator > 0) { 67 | result.put(metric, ratio.getPercentage()); 68 | } 69 | } 70 | 71 | /** 72 | * get detail report about percentage, covered, and total number 73 | * 74 | * @param detail 75 | * @param coverageObject 76 | */ 77 | private void appendDetail(CoverageDetail detail, AbstractCloverMetrics coverageObject) { 78 | appendDetail(detail, Metric.METHOD, coverageObject.getMethodCoverage()); 79 | appendDetail(detail, Metric.STATEMENT, coverageObject.getStatementCoverage()); 80 | appendDetail(detail, Metric.CONDITIONAL, coverageObject.getConditionalCoverage()); 81 | appendDetail(detail, Metric.ELEMENT, coverageObject.getElementCoverage()); 82 | } 83 | 84 | private void appendDetail(CoverageDetail detail, Metric metricName, Ratio ratio) { 85 | if (ratio.denominator == 0) { 86 | return; 87 | } 88 | detail.add(metricName + PERCENTAGE_SUFFIX, ratio.getPercentage()); 89 | detail.add(metricName + TOTAL_SUFFIX, (int) ratio.denominator); 90 | detail.add(metricName + COVERED_SUFFIX, (int) ratio.numerator); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/SplunkArtifactNotifier.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins; 2 | 3 | import hudson.Extension; 4 | import hudson.FilePath; 5 | import hudson.Launcher; 6 | import hudson.model.*; 7 | import hudson.tasks.BuildStepDescriptor; 8 | import hudson.tasks.BuildStepMonitor; 9 | import hudson.tasks.Notifier; 10 | import hudson.tasks.Publisher; 11 | import jenkins.tasks.SimpleBuildStep; 12 | import org.kohsuke.stapler.DataBoundConstructor; 13 | 14 | import edu.umd.cs.findbugs.annotations.NonNull; 15 | import java.io.IOException; 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | import java.util.logging.Level; 19 | import java.util.logging.Logger; 20 | 21 | import static com.splunk.splunkjenkins.utils.LogEventHelper.parseFileSize; 22 | import static com.splunk.splunkjenkins.utils.LogEventHelper.sendFiles; 23 | 24 | @SuppressWarnings("unused") 25 | public class SplunkArtifactNotifier extends Notifier implements SimpleBuildStep { 26 | /** 27 | * {@link org.apache.tools.ant.types.FileSet} "includes" string, like "foo/bar/*.xml" 28 | */ 29 | private final String includeFiles; 30 | private final String excludeFiles; 31 | private final boolean publishFromSlave; 32 | private final boolean skipGlobalSplunkArchive; 33 | private final String sizeLimit; 34 | 35 | @DataBoundConstructor 36 | public SplunkArtifactNotifier(String includeFiles, String excludeFiles, boolean publishFromSlave, 37 | boolean skipGlobalSplunkArchive, String sizeLimit) { 38 | this.includeFiles = includeFiles; 39 | this.excludeFiles = excludeFiles; 40 | this.publishFromSlave = publishFromSlave; 41 | this.skipGlobalSplunkArchive = skipGlobalSplunkArchive; 42 | this.sizeLimit=sizeLimit; 43 | } 44 | 45 | @Override 46 | public BuildStepMonitor getRequiredMonitorService() { 47 | return BuildStepMonitor.NONE; 48 | } 49 | 50 | @Override 51 | public void perform(@NonNull Run build, @NonNull FilePath workspace, 52 | @NonNull Launcher launcher, @NonNull TaskListener listener) throws InterruptedException, IOException { 53 | Map envVars = new HashMap<>(); 54 | try { 55 | envVars = build.getEnvironment(listener); 56 | } catch (Exception ex) { 57 | listener.getLogger().println("failed to get env"); 58 | } 59 | long maxFileSize=parseFileSize(sizeLimit); 60 | listener.getLogger().println("sending files at job level, includes:" + includeFiles + " excludes:" + excludeFiles); 61 | int eventCount = sendFiles(build, workspace, envVars, listener, includeFiles, excludeFiles, publishFromSlave, maxFileSize); 62 | Logger.getLogger(this.getClass().getName()).log(Level.FINE,"sent "+eventCount+" events with file size limit "+maxFileSize); 63 | } 64 | 65 | @Extension 66 | public static class DescriptorImpl extends BuildStepDescriptor { 67 | @Override 68 | public boolean isApplicable(@SuppressWarnings("rawtypes") Class jobType) { 69 | return true; 70 | } 71 | 72 | public String getDisplayName() { 73 | return Messages.SplunArtifactArchive(); 74 | } 75 | } 76 | 77 | @Override 78 | public String toString() { 79 | return "SplunkArtifactNotifier{" + 80 | "includeFiles='" + includeFiles + '\'' + 81 | ", excludeFiles='" + excludeFiles + '\'' + 82 | ", publishFromSlave=" + publishFromSlave + 83 | ", skipGlobalSplunkArchive=" + skipGlobalSplunkArchive + 84 | ", sizeLimit='" + sizeLimit + '\'' + 85 | '}'; 86 | } 87 | 88 | public String getIncludeFiles() { 89 | return includeFiles; 90 | } 91 | 92 | public String getExcludeFiles() { 93 | return excludeFiles; 94 | } 95 | 96 | public boolean isPublishFromSlave() { 97 | return publishFromSlave; 98 | } 99 | 100 | public boolean isSkipGlobalSplunkArchive() { 101 | return skipGlobalSplunkArchive; 102 | } 103 | 104 | public String getSizeLimit() { 105 | return sizeLimit; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /splunk-devops/src/main/resources/com/splunk/splunkjenkins/SplunkJenkinsInstallation/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 58 | 60 | 61 | 62 | 63 | 66 | 68 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /splunk-devops/src/test/resources/com/splunk/splunkjenkins/CoverageMetricTest/jobs/Cobertura/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | 7 | 8 | false 9 | 10 | 11 | false 12 | false 13 | 14 | 15 | 0 16 | 0 17 | 18 | false 19 | project 20 | 21 | 22 | 23 | true 24 | false 25 | false 26 | false 27 | 28 | false 29 | 30 | 31 | 32 | coverage.xml 33 | false 34 | false 35 | false 36 | false 37 | false 38 | false 39 | 0 40 | true 41 | 42 | 43 | 44 | METHOD 45 | 8000000 46 | 47 | 48 | LINE 49 | 8000000 50 | 51 | 52 | CONDITIONAL 53 | 7000000 54 | 55 | 56 | 57 | 58 | 59 | 60 | METHOD 61 | 0 62 | 63 | 64 | LINE 65 | 0 66 | 67 | 68 | CONDITIONAL 69 | 0 70 | 71 | 72 | 73 | 74 | 75 | 76 | METHOD 77 | 0 78 | 79 | 80 | LINE 81 | 0 82 | 83 | 84 | CONDITIONAL 85 | 0 86 | 87 | 88 | 89 | ASCII 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /splunk-devops-extend/src/test/java/com/splunk/splunkjenkins/SplunkConsoleLogStepTest.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins; 2 | 3 | import com.splunk.splunkjenkins.console.PipelineConsoleDecoder; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; 6 | import org.jenkinsci.plugins.workflow.flow.FlowDurabilityHint; 7 | import org.jenkinsci.plugins.workflow.job.WorkflowJob; 8 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 9 | import org.jenkinsci.plugins.workflow.job.properties.DurabilityHintJobProperty; 10 | import org.junit.Before; 11 | import org.junit.ClassRule; 12 | import org.junit.Rule; 13 | import org.junit.Test; 14 | import org.jvnet.hudson.test.BuildWatcher; 15 | import org.jvnet.hudson.test.JenkinsRule; 16 | 17 | import java.io.ByteArrayOutputStream; 18 | import java.util.UUID; 19 | 20 | import static com.splunk.splunkjenkins.SplunkConfigUtil.checkTokenAvailable; 21 | import static com.splunk.splunkjenkins.SplunkConfigUtil.verifySplunkSearchResult; 22 | import static com.splunk.splunkjenkins.console.LabelConsoleLineStream.ANSI_COLOR_ESCAPE; 23 | import static org.junit.Assert.assertEquals; 24 | import static org.junit.Assert.assertFalse; 25 | import static org.junit.Assert.assertTrue; 26 | 27 | public class SplunkConsoleLogStepTest { 28 | @ClassRule 29 | public static BuildWatcher buildWatcher = new BuildWatcher(); 30 | @Rule 31 | public JenkinsRule r = new JenkinsRule(); 32 | String id = UUID.randomUUID().toString(); 33 | 34 | private String jobScript = "sendSplunkConsoleLog {" + 35 | "node{\n" + 36 | " sh \"echo testjob\";\n" + 37 | " sh \"echo " + id + "\";\n" + 38 | " }" + 39 | "}"; 40 | 41 | @Before 42 | public void setUp() throws Exception { 43 | org.junit.Assume.assumeTrue(checkTokenAvailable()); 44 | } 45 | 46 | @Test 47 | public void testSendConsoleLog() throws Exception { 48 | long startTime = System.currentTimeMillis(); 49 | WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p"); 50 | p.setDefinition(new CpsFlowDefinition(jobScript, true)); 51 | WorkflowRun b1 = r.assertBuildStatusSuccess(p.scheduleBuild2(0)); 52 | assertFalse(b1.isBuilding()); 53 | r.assertLogContains("testjob", b1); 54 | assertTrue(b1.getDuration() > 0); 55 | //check log 56 | verifySplunkSearchResult("source=" + b1.getUrl() + "console " + id, startTime, 1); 57 | } 58 | 59 | @Test 60 | public void testDecodeLine() throws Exception { 61 | WorkflowJob p = r.createProject(WorkflowJob.class, "p"); 62 | p.addProperty(new DurabilityHintJobProperty(FlowDurabilityHint.SURVIVABLE_NONATOMIC)); 63 | p.setDefinition(new CpsFlowDefinition("parallel first: {echo 'hello'}, second: {echo 'in-second'};", true)); 64 | WorkflowRun b = r.buildAndAssertSuccess(p); 65 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 66 | b.getLogText().writeRawLogTo(0, out); 67 | PipelineConsoleDecoder decoder = new PipelineConsoleDecoder(b); 68 | byte[] logs = out.toByteArray(); 69 | assertTrue("end with \n", logs[logs.length - 1] == '\n'); 70 | StringBuffer lines = new StringBuffer(); 71 | // decode line by line 72 | ByteArrayOutputStream branch = new ByteArrayOutputStream(); 73 | int lineCount = 0; 74 | for (int i = 0; i < logs.length; i++) { 75 | branch.write(logs[i]); 76 | if (logs[i] == '\n') { 77 | lines.append(decoder.decodeLine(branch.toByteArray(), branch.size())); 78 | branch.reset(); 79 | lineCount++; 80 | } 81 | } 82 | String text = lines.toString(); 83 | assertTrue(text.contains("parallel_label=\"first\" [Pipeline] echo")); 84 | assertTrue(text.contains("parallel_label=\"first\" hello")); 85 | assertTrue(text.contains("parallel_label=\"second\" [Pipeline] echo")); 86 | assertTrue(text.contains("parallel_label=\"second\" in-second")); 87 | assertEquals(lineCount, StringUtils.countMatches(text, "\n")); 88 | assertEquals(2, StringUtils.countMatches(text, "label=\"second\" ")); 89 | } 90 | 91 | @Test 92 | public void testAnsilColor() { 93 | String lineWithAnsi = "\u001B[0;31mline in red\nend\u001B[0m"; 94 | String line = ANSI_COLOR_ESCAPE.matcher(lineWithAnsi).replaceAll(""); 95 | assertEquals("line in red\nend", line); 96 | } 97 | } -------------------------------------------------------------------------------- /splunk-devops/src/main/java/com/splunk/splunkjenkins/listeners/LoggingConfigListener.java: -------------------------------------------------------------------------------- 1 | package com.splunk.splunkjenkins.listeners; 2 | 3 | import com.splunk.splunkjenkins.SplunkJenkinsInstallation; 4 | import com.splunk.splunkjenkins.utils.SplunkLogService; 5 | import hudson.Extension; 6 | import hudson.XmlFile; 7 | import hudson.model.Item; 8 | import hudson.model.Saveable; 9 | import hudson.model.User; 10 | import hudson.model.listeners.SaveableListener; 11 | import jenkins.model.Jenkins; 12 | import org.apache.commons.codec.digest.DigestUtils; 13 | 14 | import java.io.IOException; 15 | import java.util.WeakHashMap; 16 | import java.util.logging.Level; 17 | import java.util.logging.Logger; 18 | import java.util.regex.Pattern; 19 | import java.util.regex.PatternSyntaxException; 20 | 21 | import static com.splunk.splunkjenkins.Constants.JENKINS_CONFIG_PREFIX; 22 | import static com.splunk.splunkjenkins.model.EventType.JENKINS_CONFIG; 23 | import static com.splunk.splunkjenkins.utils.LogEventHelper.getRelativeJenkinsHomePath; 24 | import static com.splunk.splunkjenkins.utils.LogEventHelper.getUserName; 25 | import static com.splunk.splunkjenkins.utils.LogEventHelper.logUserAction; 26 | 27 | /** 28 | * record jenkins config and job changes 29 | * send config content to splunk 30 | */ 31 | 32 | @edu.umd.cs.findbugs.annotations.SuppressFBWarnings("VA_FORMAT_STRING_USES_NEWLINE") 33 | @Extension 34 | public class LoggingConfigListener extends SaveableListener { 35 | private static final String XML_COMMENT = "\n"; 36 | private static final Logger LOGGER = Logger.getLogger(LoggingConfigListener.class.getName()); 37 | //queue.xml or build/*/config.xml 38 | private static final String IGNORE_CONFIG_CHANGE_PATTERN = "(?:queue|nodeMonitors|UpdateCenter|global-build-stats).xml|" + 39 | "/(?:fingerprint|builds|config-history)/.*?xml"; 40 | public static final Pattern IGNORED; 41 | 42 | static { 43 | String ignorePatternStr = System.getProperty("splunkins.ignoreConfigChangePattern", IGNORE_CONFIG_CHANGE_PATTERN); 44 | Pattern ignorePattern; 45 | try { 46 | ignorePattern = Pattern.compile(ignorePatternStr, Pattern.CASE_INSENSITIVE); 47 | } catch (PatternSyntaxException ex) { 48 | ignorePattern = Pattern.compile(IGNORE_CONFIG_CHANGE_PATTERN, Pattern.CASE_INSENSITIVE); 49 | } 50 | IGNORED = ignorePattern; 51 | } 52 | 53 | private WeakHashMap cached = new WeakHashMap(512); 54 | 55 | @Override 56 | public void onChange(Saveable saveable, XmlFile file) { 57 | if (!SplunkJenkinsInstallation.isLogHandlerRegistered()) { 58 | return; 59 | } 60 | String configPath = file.getFile().getAbsolutePath(); 61 | if (saveable == null || IGNORED.matcher(configPath).find()) { 62 | LOGGER.log(Level.FINE, "{} is ignored", configPath); 63 | return; 64 | } 65 | if (saveable instanceof User) { 66 | //we use SecurityListener to capture login/logout events 67 | return; 68 | } 69 | if (SplunkJenkinsInstallation.get().isEventDisabled(JENKINS_CONFIG)) { 70 | return; 71 | } 72 | //log audit trail, excludes Item instances which were already tracked by other listener 73 | String relativePath = getRelativeJenkinsHomePath(configPath); 74 | if (!(saveable instanceof Item)) { 75 | logUserAction(getUserName(), Messages.audit_update_item(relativePath)); 76 | } 77 | if ("SYSTEM".equals(Jenkins.getAuthentication().getName())) { 78 | LOGGER.log(Level.FINE, "{0} is changed by system", configPath); 79 | //ignore changes made by daemons or background jobs 80 | return; 81 | } 82 | try { 83 | String configContent = file.asString(); 84 | String checkSum = DigestUtils.md5Hex(configPath + configContent); 85 | if (cached.containsKey(checkSum)) { 86 | //Save a job can trigger multiple SaveableListener, depends on jenkins versions 87 | // e.g. AbstractProject.submit may call setters which can trigger save() 88 | return; 89 | } 90 | cached.put(checkSum, 0); 91 | String sourceName = JENKINS_CONFIG_PREFIX + relativePath; 92 | String userName = getUserName(); 93 | String comment = String.format(XML_COMMENT, userName); 94 | SplunkLogService.getInstance().send(comment + configContent, JENKINS_CONFIG, sourceName); 95 | } catch (IOException e) { 96 | //just ignore 97 | } 98 | } 99 | } 100 | --------------------------------------------------------------------------------