floe-servicenow 0.1.0

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.
@@ -0,0 +1,138 @@
1
+ {
2
+ "Comment": "ServiceNow Table API Examples - Demonstrates all CRUD operations",
3
+ "StartAt": "ListTables",
4
+ "States": {
5
+ "ListTables": {
6
+ "Type": "Task",
7
+ "Resource": "servicenow://table/list_tables",
8
+ "Credentials": {
9
+ "username.$": "$$.Credentials.username",
10
+ "password.$": "$$.Credentials.password"
11
+ },
12
+ "Parameters": {
13
+ "instance_id.$": "$.instance_id",
14
+ "query": "nameSTARTSWITHcmdb",
15
+ "limit": 5,
16
+ "fields": "name,label"
17
+ },
18
+ "ResultPath": "$.tables",
19
+ "Next": "QueryRecords"
20
+ },
21
+ "QueryRecords": {
22
+ "Type": "Task",
23
+ "Resource": "servicenow://table/query_records",
24
+ "Credentials": {
25
+ "username.$": "$$.Credentials.username",
26
+ "password.$": "$$.Credentials.password"
27
+ },
28
+ "Parameters": {
29
+ "instance_id.$": "$.instance_id",
30
+ "table_name": "incident",
31
+ "query": "active=true^priority=1",
32
+ "limit": 10,
33
+ "fields": "sys_id,number,short_description,state,priority",
34
+ "display_value": "true"
35
+ },
36
+ "ResultPath": "$.incidents",
37
+ "Next": "GetRecord"
38
+ },
39
+ "GetRecord": {
40
+ "Type": "Task",
41
+ "Resource": "servicenow://table/get_record",
42
+ "Credentials": {
43
+ "username.$": "$$.Credentials.username",
44
+ "password.$": "$$.Credentials.password"
45
+ },
46
+ "Parameters": {
47
+ "instance_id.$": "$.instance_id",
48
+ "table_name": "incident",
49
+ "sys_id.$": "$.incidents.result[0].sys_id",
50
+ "fields": "number,short_description,state,priority,assigned_to",
51
+ "display_value": "all"
52
+ },
53
+ "ResultPath": "$.incident_detail",
54
+ "Next": "CreateRecord"
55
+ },
56
+ "CreateRecord": {
57
+ "Type": "Task",
58
+ "Resource": "servicenow://table/create_record",
59
+ "Credentials": {
60
+ "username.$": "$$.Credentials.username",
61
+ "password.$": "$$.Credentials.password"
62
+ },
63
+ "Parameters": {
64
+ "instance_id.$": "$.instance_id",
65
+ "table_name": "incident",
66
+ "data": {
67
+ "short_description": "Test incident created by workflow",
68
+ "urgency": "3",
69
+ "impact": "3",
70
+ "category": "Software",
71
+ "subcategory": "Email"
72
+ },
73
+ "display_value": "true"
74
+ },
75
+ "ResultPath": "$.new_incident",
76
+ "Next": "PatchRecord"
77
+ },
78
+ "PatchRecord": {
79
+ "Type": "Task",
80
+ "Resource": "servicenow://table/patch_record",
81
+ "Credentials": {
82
+ "username.$": "$$.Credentials.username",
83
+ "password.$": "$$.Credentials.password"
84
+ },
85
+ "Parameters": {
86
+ "instance_id.$": "$.instance_id",
87
+ "table_name": "incident",
88
+ "sys_id.$": "$.new_incident.result.sys_id",
89
+ "data": {
90
+ "state": "2",
91
+ "work_notes": "Updated by workflow - investigating issue"
92
+ },
93
+ "display_value": "true"
94
+ },
95
+ "ResultPath": "$.patched_incident",
96
+ "Next": "UpdateRecord"
97
+ },
98
+ "UpdateRecord": {
99
+ "Type": "Task",
100
+ "Resource": "servicenow://table/update_record",
101
+ "Credentials": {
102
+ "username.$": "$$.Credentials.username",
103
+ "password.$": "$$.Credentials.password"
104
+ },
105
+ "Parameters": {
106
+ "instance_id.$": "$.instance_id",
107
+ "table_name": "incident",
108
+ "sys_id.$": "$.new_incident.result.sys_id",
109
+ "data": {
110
+ "short_description": "Test incident - UPDATED",
111
+ "urgency": "3",
112
+ "impact": "3"
113
+ },
114
+ "display_value": "true"
115
+ },
116
+ "ResultPath": "$.updated_incident",
117
+ "Next": "DeleteRecord"
118
+ },
119
+ "DeleteRecord": {
120
+ "Type": "Task",
121
+ "Resource": "servicenow://table/delete_record",
122
+ "Credentials": {
123
+ "username.$": "$$.Credentials.username",
124
+ "password.$": "$$.Credentials.password"
125
+ },
126
+ "Parameters": {
127
+ "instance_id.$": "$.instance_id",
128
+ "table_name": "incident",
129
+ "sys_id.$": "$.new_incident.result.sys_id"
130
+ },
131
+ "ResultPath": "$.delete_result",
132
+ "Next": "Success"
133
+ },
134
+ "Success": {
135
+ "Type": "Succeed"
136
+ }
137
+ }
138
+ }
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
5
+
6
+ require "floe/cli"
7
+ require "floe/servicenow"
8
+ success = Floe::CLI.new.run(ARGV)
9
+ exit(success ? 0 : 1)
@@ -0,0 +1 @@
1
+ require "floe/servicenow"
@@ -0,0 +1,282 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Floe
6
+ module ServiceNow
7
+ class Cmdb < Floe::ServiceNow::Methods
8
+ # Get a Configuration Item (CI) by sys_id
9
+ def self.get_ci(params, secrets, _context)
10
+ error = verify_credentials(secrets)
11
+ error ||= verify_get_ci_params(params)
12
+ return ServiceNow.error!({}, :cause => error) if error
13
+
14
+ table = params["table"] || "cmdb_ci"
15
+ sys_id = params["sys_id"]
16
+
17
+ http_params = build_http_params(
18
+ "GET",
19
+ "/api/now/table/#{table}/#{sys_id}",
20
+ params,
21
+ secrets
22
+ )
23
+
24
+ http_request(http_params, {}, {})
25
+ end
26
+
27
+ # Query Configuration Items with optional filters
28
+ def self.query_cis(params, secrets, _context)
29
+ error = verify_credentials(secrets)
30
+ error ||= verify_query_cis_params(params)
31
+ return ServiceNow.error!({}, :cause => error) if error
32
+
33
+ instance_id = params["instance_id"]
34
+ table = params["table"] || "cmdb_ci"
35
+ username = secrets["username"]
36
+ password = secrets["password"]
37
+
38
+ require "base64"
39
+ authorization = "Basic #{::Base64.urlsafe_encode64("#{username}:#{password}")}"
40
+
41
+ headers = {
42
+ "Authorization" => authorization,
43
+ "Content-Type" => "application/json",
44
+ "Accept" => "application/json"
45
+ }
46
+
47
+ query_params = {}
48
+ query_params["sysparm_query"] = params["query"] if params["query"]
49
+ query_params["sysparm_limit"] = params["limit"] if params["limit"]
50
+ query_params["sysparm_offset"] = params["offset"] if params["offset"]
51
+ query_params["sysparm_fields"] = params["fields"] if params["fields"]
52
+
53
+ http_params = {
54
+ "Method" => params["method"],
55
+ "Url" => "https://#{instance_id}.service-now.com/api/now/table/#{table}",
56
+ "Options" => {"Encoding" => "JSON"},
57
+ "Headers" => headers,
58
+ "QueryParameters" => query_params
59
+ }
60
+
61
+ begin
62
+ http_request(http_params, {}, {})
63
+ rescue => err
64
+ ServiceNow.error!({}, :cause => err.to_s)
65
+ end
66
+ end
67
+
68
+ # Create a new Configuration Item
69
+ def self.create_ci(params, secrets, _context)
70
+ error = verify_credentials(secrets)
71
+ error ||= verify_create_ci_params(params)
72
+ return ServiceNow.error!({}, :cause => error) if error
73
+
74
+ table = params["table"] || "cmdb_ci"
75
+
76
+ http_params = build_http_params(
77
+ "POST",
78
+ "/api/now/table/#{table}",
79
+ params,
80
+ secrets,
81
+ :body => params.except("instance_id", "table")
82
+ )
83
+
84
+ http_request(http_params, {}, {})
85
+ end
86
+
87
+ # Update an existing Configuration Item
88
+ def self.update_ci(params, secrets, _context)
89
+ error = verify_credentials(secrets)
90
+ return ServiceNow.error!({}, :cause => error) if error
91
+
92
+ error = verify_update_ci_params(params)
93
+ return ServiceNow.error!({}, :cause => error) if error
94
+
95
+ table = params["table"] || "cmdb_ci"
96
+ sys_id = params["sys_id"]
97
+
98
+ http_params = build_http_params(
99
+ "PATCH",
100
+ "/api/now/table/#{table}/#{sys_id}",
101
+ params,
102
+ secrets,
103
+ :body => params.except("sys_id", "instance_id", "table")
104
+ )
105
+
106
+ http_request(http_params, {}, {})
107
+ end
108
+
109
+ # Delete a Configuration Item
110
+ def self.delete_ci(params, secrets, _context)
111
+ error = verify_credentials(secrets)
112
+ error ||= verify_delete_ci_params(params)
113
+ return ServiceNow.error!({}, :cause => error) if error
114
+
115
+ table = params["table"] || "cmdb_ci"
116
+ sys_id = params["sys_id"]
117
+
118
+ http_params = build_http_params(
119
+ "DELETE",
120
+ "/api/now/table/#{table}/#{sys_id}",
121
+ params,
122
+ secrets
123
+ )
124
+
125
+ http_request(http_params, {}, {})
126
+ end
127
+
128
+ # Get CI relationships
129
+ def self.get_ci_relationships(params, secrets, _context)
130
+ error = verify_credentials(secrets)
131
+ error ||= verify_get_ci_relationships_params(params)
132
+ return ServiceNow.error!({}, :cause => error) if error
133
+
134
+ sys_id = params["sys_id"]
135
+
136
+ # Get CI instance
137
+ query_params = {
138
+ "sysparm_display_value" => "true",
139
+ "sysparm_exclude_reference_link" => "true"
140
+ }
141
+
142
+ http_params = build_http_params(
143
+ "GET",
144
+ "/api/now/cmdb/instance/cmdb_ci/#{sys_id}",
145
+ params,
146
+ secrets,
147
+ :query => query_params
148
+ )
149
+
150
+ ci_response = http_request(http_params, {}, {})
151
+ return ci_response unless ci_response["success"]
152
+
153
+ # Get relationships
154
+ rel_query_params = {
155
+ "sysparm_query" => "parent=#{sys_id}^ORchild=#{sys_id}",
156
+ "sysparm_display_value" => "true"
157
+ }
158
+
159
+ rel_http_params = build_http_params(
160
+ "GET",
161
+ "/api/now/table/cmdb_rel_ci",
162
+ params,
163
+ secrets,
164
+ :query => rel_query_params
165
+ )
166
+
167
+ rel_response = http_request(rel_http_params, {}, {})
168
+ return rel_response unless rel_response["success"]
169
+
170
+ # Merge results
171
+ ci_data = ci_response["output"]["result"]
172
+ rel_data = rel_response["output"]["result"]
173
+ output = ci_data.merge("relationships" => rel_data)
174
+
175
+ ServiceNow.success!({}, :output => output)
176
+ end
177
+
178
+ # Create a CI relationship
179
+ def self.create_ci_relationship(params, secrets, _context)
180
+ error = verify_credentials(secrets)
181
+ error ||= verify_create_ci_relationship_params(params)
182
+ return ServiceNow.error!({}, :cause => error) if error
183
+
184
+ body = {
185
+ "parent" => params["parent_sys_id"],
186
+ "child" => params["child_sys_id"],
187
+ "type" => params["relationship_type"],
188
+ "connection_strength" => params["connection_strength"] || "1"
189
+ }
190
+
191
+ http_params = build_http_params(
192
+ "POST",
193
+ "/api/now/table/cmdb_rel_ci",
194
+ params,
195
+ secrets,
196
+ :body => body
197
+ )
198
+
199
+ http_request(http_params, {}, {})
200
+ end
201
+
202
+ # Get CI classes (types)
203
+ def self.get_ci_classes(params, secrets, _context)
204
+ error = verify_credentials(secrets)
205
+ error ||= verify_instance_id(params)
206
+ return ServiceNow.error!({}, :cause => error) if error
207
+
208
+ query_params = {
209
+ "sysparm_query" => "nameSTARTSWITHcmdb_ci",
210
+ "sysparm_fields" => "name,label,super_class"
211
+ }
212
+ query_params["sysparm_limit"] = params["limit"] if params["limit"]
213
+
214
+ http_params = build_http_params(
215
+ "GET",
216
+ "/api/now/table/sys_db_object",
217
+ params,
218
+ secrets,
219
+ :query => query_params
220
+ )
221
+
222
+ http_request(http_params, {}, {})
223
+ end
224
+
225
+ # Verify parameters for get_ci
226
+ private_class_method def self.verify_get_ci_params(params)
227
+ return "Missing Parameter: instance_id" if params["instance_id"].nil?
228
+ return "Missing Parameter: sys_id" if params["sys_id"].nil?
229
+
230
+ nil
231
+ end
232
+
233
+ # Verify parameters for query_cis
234
+ private_class_method def self.verify_query_cis_params(params)
235
+ return "Missing Parameter: instance_id" if params["instance_id"].nil?
236
+
237
+ nil
238
+ end
239
+
240
+ # Verify parameters for create_ci
241
+ private_class_method def self.verify_create_ci_params(params)
242
+ return "Missing Parameter: instance_id" if params["instance_id"].nil?
243
+
244
+ nil
245
+ end
246
+
247
+ # Verify parameters for update_ci
248
+ private_class_method def self.verify_update_ci_params(params)
249
+ return "Missing Parameter: instance_id" if params["instance_id"].nil?
250
+ return "Missing Parameter: sys_id" if params["sys_id"].nil?
251
+
252
+ nil
253
+ end
254
+
255
+ # Verify parameters for delete_ci
256
+ private_class_method def self.verify_delete_ci_params(params)
257
+ return "Missing Parameter: instance_id" if params["instance_id"].nil?
258
+ return "Missing Parameter: sys_id" if params["sys_id"].nil?
259
+
260
+ nil
261
+ end
262
+
263
+ # Verify parameters for get_ci_relationships
264
+ private_class_method def self.verify_get_ci_relationships_params(params)
265
+ return "Missing Parameter: instance_id" if params["instance_id"].nil?
266
+ return "Missing Parameter: sys_id" if params["sys_id"].nil?
267
+
268
+ nil
269
+ end
270
+
271
+ # Verify parameters for create_ci_relationship
272
+ private_class_method def self.verify_create_ci_relationship_params(params)
273
+ return "Missing Parameter: instance_id" if params["instance_id"].nil?
274
+ return "Missing Parameter: parent_sys_id" if params["parent_sys_id"].nil?
275
+ return "Missing Parameter: child_sys_id" if params["child_sys_id"].nil?
276
+ return "Missing Parameter: relationship_type" if params["relationship_type"].nil?
277
+
278
+ nil
279
+ end
280
+ end
281
+ end
282
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Floe
6
+ module ServiceNow
7
+ class Incident < Floe::ServiceNow::Methods
8
+ # Create a new incident in ServiceNow
9
+ def self.create_incident(params, secrets, _context)
10
+ error = verify_credentials(secrets)
11
+ error ||= verify_create_params(params)
12
+ return ServiceNow.error!({}, :cause => error) if error
13
+
14
+ http_params = build_http_params(
15
+ "POST",
16
+ "/api/now/table/incident",
17
+ params,
18
+ secrets,
19
+ :body => params.except("instance_id")
20
+ )
21
+
22
+ http_request(http_params, {}, {})
23
+ end
24
+
25
+ # Get an incident by sys_id
26
+ def self.get_incident(params, secrets, _context)
27
+ error = verify_credentials(secrets)
28
+ error ||= verify_get_params(params)
29
+ return ServiceNow.error!({}, :cause => error) if error
30
+
31
+ sys_id = params["sys_id"]
32
+
33
+ http_params = build_http_params(
34
+ "GET",
35
+ "/api/now/table/incident/#{sys_id}",
36
+ params,
37
+ secrets
38
+ )
39
+
40
+ http_request(http_params, {}, {})
41
+ end
42
+
43
+ # Update an existing incident
44
+ def self.update_incident(params, secrets, _context)
45
+ error = verify_credentials(secrets)
46
+ error ||= verify_update_params(params)
47
+ return ServiceNow.error!({}, :cause => error) if error
48
+
49
+ sys_id = params["sys_id"]
50
+
51
+ http_params = build_http_params(
52
+ "PATCH",
53
+ "/api/now/table/incident/#{sys_id}",
54
+ params,
55
+ secrets,
56
+ :body => params.except("sys_id", "instance_id")
57
+ )
58
+
59
+ http_request(http_params, {}, {})
60
+ end
61
+
62
+ def self.resolve_incident(params, secrets, context)
63
+ params['attributes'] ||= {}
64
+ params['attributes']['state'] = '6' # state enum 6 is resolved
65
+ params['attributes']['resolution_code'] = params.delete('close_code') if params['close_code']
66
+ params['attributes']['resolution_notes'] = params.delete('close_notes') if params['close_notes']
67
+
68
+ update_incident(params, secrets, context)
69
+ end
70
+
71
+ def self.close_incident(params, secrets, context)
72
+ params['attributes'] ||= {}
73
+ params['attributes']['state'] = '7' # state enum 7 is closed
74
+ params['attributes']['resolution_code'] = params.delete('close_code') if params['close_code']
75
+ params['attributes']['resolution_notes'] = params.delete('close_notes') if params['close_notes']
76
+
77
+ update_incident(params, secrets, context)
78
+ end
79
+
80
+ # Query incidents with optional filters
81
+ def self.query_incidents(params, secrets, _context)
82
+ error = verify_credentials(secrets)
83
+ error ||= verify_instance_id(params)
84
+ return ServiceNow.error!({}, :cause => error) if error
85
+
86
+ query_params = {}
87
+ query_params["sysparm_query"] = params["query"] if params["query"]
88
+ query_params["sysparm_limit"] = params["limit"] if params["limit"]
89
+ query_params["sysparm_offset"] = params["offset"] if params["offset"]
90
+ query_params["sysparm_fields"] = params["fields"] if params["fields"]
91
+
92
+ http_params = build_http_params(
93
+ "GET",
94
+ "/api/now/table/incident",
95
+ params,
96
+ secrets,
97
+ :query => query_params
98
+ )
99
+
100
+ http_request(http_params, {}, {})
101
+ end
102
+
103
+ # Verify parameters for create_incident
104
+ private_class_method def self.verify_create_params(params)
105
+ return "Missing Parameter: instance_id" if params["instance_id"].nil?
106
+ return "Missing Parameter: short_description" if params["short_description"].nil?
107
+
108
+ nil
109
+ end
110
+
111
+ # Verify parameters for get_incident
112
+ private_class_method def self.verify_get_params(params)
113
+ return "Missing Parameter: instance_id" if params["instance_id"].nil?
114
+ return "Missing Parameter: sys_id" if params["sys_id"].nil?
115
+
116
+ nil
117
+ end
118
+
119
+ # Verify parameters for update_incident
120
+ private_class_method def self.verify_update_params(params)
121
+ return "Missing Parameter: instance_id" if params["instance_id"].nil?
122
+ return "Missing Parameter: sys_id" if params["sys_id"].nil?
123
+
124
+ nil
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Floe
4
+ module ServiceNow
5
+ class Methods < Floe::BuiltinRunner::Methods
6
+ private_class_method def self.verify_credentials(secrets)
7
+ return "Missing Credentials" if secrets.nil?
8
+
9
+ # Allow either username/password OR access_token
10
+ has_basic_auth = secrets["username"] && secrets["password"]
11
+ has_oauth = secrets["access_token"]
12
+
13
+ return "Missing Credentials: Provide either (username and password) or access_token" unless has_basic_auth || has_oauth
14
+
15
+ nil
16
+ end
17
+
18
+ private_class_method def self.verify_instance_id(params)
19
+ return "Missing Parameter: instance_id" if params["instance_id"].nil?
20
+
21
+ nil
22
+ end
23
+
24
+ private_class_method def self.verify_required_param(params, param_name)
25
+ return "Missing Parameter: #{param_name}" if params[param_name].nil?
26
+
27
+ nil
28
+ end
29
+
30
+ private_class_method def self.build_http_params(method, path, params, secrets, options = {})
31
+ instance_id = params["instance_id"]
32
+
33
+ # Build authorization header - support both Basic Auth and OAuth
34
+ if secrets["access_token"]
35
+ authorization = "Bearer #{secrets["access_token"]}"
36
+ elsif secrets["username"] && secrets["password"]
37
+ require "base64"
38
+ authorization = "Basic #{::Base64.urlsafe_encode64("#{secrets["username"]}:#{secrets["password"]}")}"
39
+ end
40
+
41
+ headers = {
42
+ "Content-Type" => "application/json",
43
+ "Accept" => "application/json"
44
+ }
45
+ headers["Authorization"] = authorization if authorization
46
+
47
+ http_params = {
48
+ "Method" => method,
49
+ "Url" => "https://#{instance_id}.service-now.com#{path}",
50
+ "Options" => {"Encoding" => "JSON"},
51
+ "Headers" => headers
52
+ }
53
+
54
+ http_params["QueryParameters"] = options[:query] if options[:query]
55
+ http_params["Body"] = options[:body] if options[:body]
56
+
57
+ http_params
58
+ end
59
+
60
+ # Wrapper around http() that returns only the Body wrapped in success response
61
+ private_class_method def self.http_request(http_params, secrets, context)
62
+ response = http(http_params, secrets, context)
63
+
64
+ # If there's an error at the top level, return it as-is
65
+ return response if response.key?("success") && !response["success"]
66
+
67
+ # Extract the Body and wrap in success response
68
+ body = response.dig("output", "Body") || response
69
+ ServiceNow.success!({}, :output => body)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Floe
6
+ module ServiceNow
7
+ class OAuth < Floe::ServiceNow::Methods
8
+ def self.token(params, secrets, context)
9
+ error = verify_instance_id(params)
10
+ return ServiceNow.error!({}, :cause => error) if error
11
+
12
+ instance_id = params["instance_id"]
13
+
14
+ headers = {
15
+ "ContentType" => "application/x-www-form-urlencoded",
16
+ "Accept" => "application/json"
17
+ }
18
+
19
+ body_params = {}
20
+ body_params["grant_type"] = params["grant_type"] if params["grant_type"]
21
+ body_params["client_id"] = params["client_id"] if params["client_id"]
22
+ body_params["scope"] = params["scope"] if params["scope"]
23
+ body_params["username"] = secrets["username"] if secrets["username"]
24
+ body_params["password"] = secrets["password"] if secrets["password"]
25
+ body_params["client_secret"] = secrets["client_secret"] if secrets["client_secret"]
26
+ body_params["refresh_token"] = secrets["refresh_token"] if secrets["refresh_token"]
27
+
28
+ http_params = {
29
+ "Method" => "POST",
30
+ "Headers" => headers,
31
+ "Url" => "https://#{instance_id}.service-now.com/oauth_token.do",
32
+ "Body" => body_params
33
+ }
34
+
35
+ http_request(http_params, secrets, context)
36
+ end
37
+ end
38
+ end
39
+ end