logstash-output-jsm 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6ada91aab7f1f4d99dadc5dfddb192898e6737ea26a52106a5b960c99d882c32
4
+ data.tar.gz: f3c344ac7a36c52cd6d72c64715dcad00ca9994c2908f4739c890ec3b8be789b
5
+ SHA512:
6
+ metadata.gz: beb0ef29d50f43c9578a878f68818ce9c9120ecebdb3bb83215810963e17b5c4dbe250e1c88622226815440ec6539f9c61864463f08da868761c4242f6c12da8
7
+ data.tar.gz: ec8c72410a84a2c04acfef641644824583c3d12888e13996b0c60fb669daa613ef37f089b4690a941e563a8318e7505000e1e043db826a93a0fa8cf8a66a0636
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/NOTICE.TXT ADDED
@@ -0,0 +1,5 @@
1
+ Elasticsearch
2
+ Copyright 2012-2015 Elasticsearch
3
+
4
+ This product includes software developed by The Apache Software
5
+ Foundation (http://www.apache.org/).
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # Jira Service Management Logstash Plugin
2
+
3
+ 1. Download the [Logstash plugin](https://github.com/elastic/logstash).<br/>
4
+ The plugin is entirely free and open-source. It's under the Apache 2.0 license, so you can use it in the way that
5
+ best suits your needs.
6
+
7
+ 2. Install and run the Jira Service Management output plugin in Logstash:
8
+ * For Logstash 5.4+ <br/>
9
+ `bin/logstash-plugin install logstash-output-jsm`
10
+ * For other versions <br/>
11
+ `bin/plugin install logstash-output-jsm`
12
+
13
+ 3. Add a Logstash integration in Jira Service Management and copy the API key. <br/>
14
+ :warning: If the feature isn’t available on your site, keep checking Jira Service Management for updates.
15
+
16
+ 4. Use plugins such as [Mutate](https://www.elastic.co/guide/en/logstash/current/plugins-filters-mutate.html) to populate the fields that [logstash-output-jsm](https://github.com/atlassian/jsm-integration-scripts/) will use.
17
+
18
+ ``` ruby
19
+ filter {
20
+ mutate {
21
+ add_field => {
22
+ "jsmAction" => "create"
23
+ "alias" => "neo123"
24
+ "description" => "Every alert needs a description"
25
+ "actions" => ["Restart", "AnExampleAction"]
26
+ "tags" => ["OverwriteQuietHours","Critical"]
27
+ "[details][prop1]" => "val1"
28
+ "[details][prop2]" => "val2"
29
+ "entity" => "An example entity"
30
+ "priority" => "P4"
31
+ "source" => "custom source"
32
+ "user" => "custom user"
33
+ "note" => "alert is created"
34
+ }
35
+ }
36
+ ruby {
37
+ code => "event.set('teams', [{'name' => 'Integration'}, {'name' => 'Platform'}])"
38
+ }
39
+ }
40
+ ```
41
+
42
+ 5. Add the following to your configuration file and enter the integration API key you copied earlier into **apiKey**.
43
+ ``` ruby
44
+ output {
45
+ jsm {
46
+ "apiKey" => "logstash_integration_api_key"
47
+ }
48
+ }
49
+ ```
50
+
51
+ 6. Run Logstash.
@@ -0,0 +1,241 @@
1
+ # encoding: utf-8
2
+ require "logstash/outputs/base"
3
+ require "logstash/namespace"
4
+ require 'json'
5
+ require "uri"
6
+ require "net/http"
7
+ require "net/https"
8
+
9
+ # The Jira Service Management output is used to Create, Close, Acknowledge Alerts and Add Note to alerts in Jira Service Management.
10
+ # For this output to work, your event must contain "jsmAction" field and you must configure apiKey field in configuration.
11
+ # If jsmAction is "create", event must contain "message" field.
12
+ # For other actions ("close", "acknowledge" or "note"), event must contain "alias" or "alertId" field.
13
+ #
14
+ # If your event have the following fields (If you use default field names).
15
+ #
16
+ # Example event:
17
+ #
18
+ # {
19
+ # "note" => "test note",
20
+ # "jsmAction" => "create",
21
+ # "teams" => ["teams"],
22
+ # "description" => "test description",
23
+ # "source" => "test source",
24
+ # "message" => "test message",
25
+ # "priority" => "P4",
26
+ # "tags" => ["tags"],
27
+ # "@timestamp" => 2017-09-15T13:32:00.747Z,
28
+ # "@version" => "1",
29
+ # "host" => "Neo's-MacBook-Pro.local",
30
+ # "alias" => "test-alias",
31
+ # "details" => {
32
+ # "prop2" => "val2",
33
+ # "prop1" => "val1"
34
+ # },
35
+ # "actions" => ["actions"],
36
+ # "user" => "test user",
37
+ # "entity" => "test entity"
38
+ # }
39
+ #
40
+ # An alert with following properties will be created.
41
+ #
42
+ # {
43
+ # "message": "test message",
44
+ # "alias": "test alias",
45
+ # "teams": ["teams"],
46
+ # "description": "test description",
47
+ # "source": "test source",
48
+ # "note": "test note",
49
+ # "user": "test user",
50
+ # "priority": "P4",
51
+ # "tags": [
52
+ # "tags"
53
+ # ],
54
+ # "details": {
55
+ # "prop2": "val2",
56
+ # "prop1": "val1"
57
+ # },
58
+ # "actions": [
59
+ # "actions"
60
+ # ],
61
+ # "entity": "test entity",
62
+ # }
63
+ #
64
+ # Fields with prefix "Attribute" are the keys of the fields will be extracted from Logstash event.
65
+
66
+ class LogStash::Outputs::Jsm < LogStash::Outputs::Base
67
+
68
+ config_name "jsm"
69
+
70
+ # Jira Service Management Logstash Integration API Key
71
+ config :apiKey, :validate => :string, :required => true
72
+
73
+ # Proxy settings
74
+ config :proxy_address, :validate => :string, :required => false
75
+ config :proxy_port, :validate => :number, :required => false
76
+
77
+
78
+ # Host of Jira Service Management api, normally you should not need to change this field.
79
+ config :jsmBaseUrl, :validate => :string, :required => false, :default => 'https://api.atlassian.com/jsm/ops/integration/v2/alerts/'
80
+
81
+ # Url will be used to close alerts in Jira Service Management
82
+ config :closeActionPath, :validate => :string, :required => false, :default =>'/close'
83
+
84
+ # Url will be used to acknowledge alerts in Jira Service Management
85
+ config :acknowledgeActionPath, :validate => :string, :required => false, :default =>'/acknowledge'
86
+
87
+ # Url will be used to add notes to alerts in Jira Service Management
88
+ config :noteActionPath, :validate => :string, :required => false, :default =>'/notes'
89
+
90
+ # The value of this field holds the name of the action will be executed in Jira Service Management.
91
+ # This field must be in Event object. Should be one of "create", "close", "acknowledge" or "note". Other values will be discarded.
92
+ config :actionAttribute, :validate => :string, :required => false, :default => 'jsmAction'
93
+
94
+ # This value specifies the query parameter identifierType
95
+ config :identifierType, :validate => :string, :required => false, :default =>'id'
96
+
97
+ # This value will be set to eventual identifier according to event(id/alias).
98
+ config :identifier, :validate => :string, :required => false, :default =>''
99
+
100
+ # The value of this field holds the Id of the alert that actions will be executed.
101
+ # One of "alertId" or "alias" field must be in Event object, except from "create" action
102
+ config :alertIdAttribute, :validate => :string, :required => false, :default => 'alertId'
103
+
104
+ # The value of this field holds the alias of the alert that actions will be executed.
105
+ # One of "alertId" or "alias" field must be in Event object, except from "create" action
106
+ config :aliasAttribute, :validate => :string, :required => false, :default => 'alias'
107
+
108
+ # The value of this field holds the alert text.
109
+ config :messageAttribute, :validate => :string, :required => false, :default => 'message'
110
+
111
+ # The value of this field holds the list of team names which will be responsible for the alert.
112
+ config :teamsAttribute, :validate => :string, :required => false, :default => 'teams'
113
+
114
+ # The value of this field holds the Teams and users that the alert will become
115
+ # visible to without sending any notification.
116
+ config :visibleToAttribute, :validate => :string, :required => false, :default => 'visibleTo'
117
+
118
+ # The value of this field holds the detailed description of the alert.
119
+ config :descriptionAttribute, :validate => :string, :required => false, :default => 'description'
120
+
121
+ # The value of this field holds the comma separated list of actions that can be executed on the alert.
122
+ config :actionsAttribute, :validate => :string, :required => false, :default => 'actions'
123
+
124
+ # The value of this field holds the source of alert. By default, it will be assigned to IP address of incoming request.
125
+ config :sourceAttribute, :validate => :string, :required => false, :default => 'source'
126
+
127
+ # The value of this field holds the priority level of the alert
128
+ config :priorityAttribute, :validate => :string, :required => false, :default => 'priority'
129
+
130
+ # The value of this field holds the comma separated list of labels attached to the alert.
131
+ config :tagsAttribute, :validate => :string, :required => false, :default => 'tags'
132
+
133
+ # The value of this field holds the set of user defined properties. This will be specified as a nested JSON map
134
+ config :detailsAttribute, :validate => :string, :required => false, :default => 'details'
135
+
136
+ # The value of this field holds the entity the alert is related to.
137
+ config :entityAttribute, :validate => :string, :required => false, :default => 'entity'
138
+
139
+ # The value of this field holds the default owner of the execution. If user is not specified, owner of account will be used.
140
+ config :userAttribute, :validate => :string, :required => false, :default => 'user'
141
+
142
+ # The value of this field holds the additional alert note.
143
+ config :noteAttribute, :validate => :string, :required => false, :default => 'note'
144
+
145
+
146
+ public
147
+ def register
148
+ end # def register
149
+
150
+ public
151
+ def populateAliasOrId(event, params)
152
+ alertAlias = event.get(@aliasAttribute) if event.get(@aliasAttribute)
153
+ if alertAlias == nil then
154
+ alertId = event.get(@alertIdAttribute) if event.get(@alertIdAttribute)
155
+ if !(alertId == nil) then
156
+ @identifierType = 'id'
157
+ @identifier = alertId
158
+ end
159
+ else
160
+ @identifierType = 'alias'
161
+ @identifier = alertAlias
162
+ end
163
+ end # def populateAliasOrId
164
+
165
+ public
166
+ def executePost(uri, params)
167
+ unless uri == nil then
168
+ @logger.info("Executing url #{uri}")
169
+ url = URI(uri)
170
+ http = Net::HTTP.new(url.host, url.port, @proxy_address, @proxy_port)
171
+ if url.scheme == 'https'
172
+ http.use_ssl = true
173
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
174
+ end
175
+ request = Net::HTTP::Post.new(url.request_uri, initheader = { "Content-Type" =>"application/json", "Authorization" => "GenieKey #{@apiKey}" })
176
+ request.body = params.to_json
177
+ response = http.request(request)
178
+ body = response.body
179
+ body = JSON.parse(body)
180
+ @logger.warn("Executed [#{uri}]. Response:[#{body}]")
181
+ end
182
+ end # def executePost
183
+
184
+ public
185
+ def receive(event)
186
+ return unless output?(event)
187
+
188
+ @logger.info("processing #{event}")
189
+ jsmAction = event.get(@actionAttribute) if event.get(@actionAttribute)
190
+ if jsmAction then
191
+ params = {}
192
+ populateCommonContent(params, event)
193
+
194
+ case jsmAction.downcase
195
+ when "create"
196
+ uri = "#{@jsmBaseUrl}"
197
+ params = populateCreateAlertContent(params, event)
198
+ when "close"
199
+ uri = "#{@jsmBaseUrl}#{@identifier}#{@closeActionPath}?identifierType=#{@identifierType}"
200
+ when "acknowledge"
201
+ uri = "#{@jsmBaseUrl}#{@identifier}#{@acknowledgeActionPath}?identifierType=#{@identifierType}"
202
+ when "note"
203
+ uri = "#{@jsmBaseUrl}#{@identifier}#{@noteActionPath}?identifierType=#{@identifierType}"
204
+ else
205
+ @logger.warn("Action #{jsmAction} does not match any available action, discarding..")
206
+ return
207
+ end
208
+
209
+ executePost(uri, params)
210
+ else
211
+ @logger.warn("No jsmAction defined")
212
+ return
213
+ end
214
+ end # def receive
215
+
216
+ private
217
+ def populateCreateAlertContent(params, event)
218
+ params['message'] = event.get(@messageAttribute) if event.get(@messageAttribute)
219
+ params['alias'] = event.get(@aliasAttribute) if event.get(@aliasAttribute)
220
+ params['teams'] = event.get(@teamsAttribute) if event.get(@teamsAttribute)
221
+ params['visibleTo'] = event.get(@visibleToAttribute) if event.get(@visibleToAttribute)
222
+ params['description'] = event.get(@descriptionAttribute) if event.get(@descriptionAttribute)
223
+ params['actions'] = event.get(@actionsAttribute) if event.get(@actionsAttribute)
224
+ params['tags'] = event.get(@tagsAttribute) if event.get(@tagsAttribute)
225
+ params['entity'] = event.get(@entityAttribute) if event.get(@entityAttribute)
226
+ params['priority'] = event.get(@priorityAttribute) if event.get(@priorityAttribute)
227
+ params['details'] = event.get(@detailsAttribute) if event.get(@detailsAttribute)
228
+
229
+
230
+ return params
231
+ end
232
+
233
+ private
234
+ def populateCommonContent(params, event)
235
+ populateAliasOrId(event, params)
236
+ params['source'] = event.get(@sourceAttribute) if event.get(@sourceAttribute)
237
+ params['user'] = event.get(@userAttribute) if event.get(@userAttribute)
238
+ params['note'] = event.get(@noteAttribute) if event.get(@noteAttribute)
239
+ end
240
+
241
+ end # class LogStash::Outputs::Jsm
@@ -0,0 +1,24 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'logstash-output-jsm'
3
+ s.version = '0.1.1'
4
+ s.licenses = ["Apache License (2.0)"]
5
+ s.summary = "This output Creates, Closes, Acknowledges alerts and Adds Note to alerts in Jira Service Management."
6
+ s.description = "This gem is a logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
7
+ s.authors = ["Elastic"]
8
+ s.email = "info@elastic.co"
9
+ s.homepage = "http://www.elastic.co/guide/en/logstash/current/index.html"
10
+ s.require_paths = ["lib"]
11
+
12
+ # Files
13
+ s.files = Dir['lib/**/*', 'spec/**/*', 'vendor/**/*', '*.gemspec', '*.md', 'CONTRIBUTORS', 'Gemfile', 'LICENSE', 'NOTICE.TXT']
14
+ # Tests
15
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
16
+
17
+ # Special flag to let us know this is actually a logstash plugin
18
+ s.metadata = { "logstash_plugin" => "true", "logstash_group" => "output", "github_repo" => "https://github.com/atlassian/jsm-integration-scripts/tree/master/logstash-plugin" }
19
+
20
+ # Gem dependencies
21
+ s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
22
+ s.add_runtime_dependency "logstash-codec-plain"
23
+ s.add_development_dependency "logstash-devutils"
24
+ end
@@ -0,0 +1,52 @@
1
+ require "logstash/devutils/rspec/spec_helper"
2
+ require "logstash/outputs/jsm"
3
+ require "logstash/codecs/plain"
4
+ require "logstash/event"
5
+
6
+ describe LogStash::Outputs::Jsm do
7
+
8
+ subject {LogStash::Outputs::Jsm.new("apiKey" => "my_api_key" )}
9
+ let(:logger) { subject.logger}
10
+
11
+ describe "receive message" do
12
+
13
+ it "when jsmAction is not specified" do
14
+ expect(logger).to receive(:warn).with("No JSM action defined").once
15
+ subject.receive({"message" => "test_alert","@version" => "1","@timestamp" => "2015-09-22T11:20:00.250Z"})
16
+ end
17
+
18
+ it "when jsmAction is not valid" do
19
+ action = "invalid"
20
+ expect(logger).to receive(:warn).with("Action #{action} does not match any available action, discarding..").once
21
+ subject.receive({"message" => "test_alert","@version" => "1","@timestamp" => "2015-09-22T11:20:00.250Z", "jsmAction" => action})
22
+ end
23
+
24
+ it "when jsmAction is 'create'" do
25
+ event = {"message" => "test_alert", "@version" => "1", "@timestamp" => "2015-09-22T11:20:00.250Z", "jsmAction" => "create"}
26
+ expect(logger).to receive(:info).with("processing #{event}").once
27
+ expect(logger).to receive(:info).with("Executing url #{subject.jsmBaseUrl}#{subject.createActionUrl}").once
28
+ subject.receive(event)
29
+ end
30
+
31
+ it "when jsmAction is 'close'" do
32
+ event = {"message" => "test_alert", "@version" => "1", "@timestamp" => "2015-09-22T11:20:00.250Z", "jsmAction" => "close"}
33
+ expect(logger).to receive(:info).with("processing #{event}").once
34
+ expect(logger).to receive(:info).with("Executing url #{subject.jsmBaseUrl}#{subject.closeActionUrl}").once
35
+ subject.receive(event)
36
+ end
37
+
38
+ it "when jsmAction is 'acknowledge'" do
39
+ event = {"message" => "test_alert", "@version" => "1", "@timestamp" => "2015-09-22T11:20:00.250Z", "jsmAction" => "acknowledge"}
40
+ expect(logger).to receive(:info).with("processing #{event}").once
41
+ expect(logger).to receive(:info).with("Executing url #{subject.jsmBaseUrl}#{subject.acknowledgeActionUrl}").once
42
+ subject.receive(event)
43
+ end
44
+
45
+ it "when jsmAction is 'note'" do
46
+ event = {"message" => "test_alert", "@version" => "1", "@timestamp" => "2015-09-22T11:20:00.250Z", "jsmAction" => "note"}
47
+ expect(logger).to receive(:info).with("processing #{event}").once
48
+ expect(logger).to receive(:info).with("Executing url #{subject.jsmBaseUrl}#{subject.noteActionUrl}").once
49
+ subject.receive(event)
50
+ end
51
+ end
52
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: logstash-output-jsm
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Elastic
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-12-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: logstash-core-plugin-api
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '1.60'
20
+ - - "<="
21
+ - !ruby/object:Gem::Version
22
+ version: '2.99'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '1.60'
30
+ - - "<="
31
+ - !ruby/object:Gem::Version
32
+ version: '2.99'
33
+ - !ruby/object:Gem::Dependency
34
+ name: logstash-codec-plain
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: logstash-devutils
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ description: This gem is a logstash plugin required to be installed on top of the
62
+ Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This
63
+ gem is not a stand-alone program
64
+ email: info@elastic.co
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - Gemfile
70
+ - NOTICE.TXT
71
+ - README.md
72
+ - lib/logstash/outputs/jsm.rb
73
+ - logstash-output-jsm.gemspec
74
+ - spec/outputs/jsm_spec.rb
75
+ homepage: http://www.elastic.co/guide/en/logstash/current/index.html
76
+ licenses:
77
+ - Apache License (2.0)
78
+ metadata:
79
+ logstash_plugin: 'true'
80
+ logstash_group: output
81
+ github_repo: https://github.com/atlassian/jsm-integration-scripts/tree/master/logstash-plugin
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubygems_version: 3.3.5
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: This output Creates, Closes, Acknowledges alerts and Adds Note to alerts
101
+ in Jira Service Management.
102
+ test_files:
103
+ - spec/outputs/jsm_spec.rb