ruby-jss 0.6.5 → 0.6.6

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.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +57 -5
  3. data/lib/jss.rb +78 -94
  4. data/lib/jss/api_connection.rb +8 -0
  5. data/lib/jss/api_object.rb +126 -102
  6. data/lib/jss/api_object/creatable.rb +33 -15
  7. data/lib/jss/api_object/distribution_point.rb +4 -1
  8. data/lib/jss/api_object/extension_attribute.rb +1 -1
  9. data/lib/jss/api_object/package.rb +121 -187
  10. data/lib/jss/api_object/policy.rb +590 -251
  11. data/lib/jss/api_object/script.rb +92 -128
  12. data/lib/jss/api_object/self_servable.rb +93 -117
  13. data/lib/jss/api_object/updatable.rb +12 -27
  14. data/lib/jss/api_object/uploadable.rb +12 -15
  15. data/lib/jss/client.rb +178 -156
  16. data/lib/jss/db_connection.rb +34 -49
  17. data/lib/jss/ruby_extensions/string.rb +25 -21
  18. data/lib/jss/version.rb +1 -1
  19. data/lib/jss/webhooks.rb +52 -0
  20. data/lib/jss/webhooks/README.md +269 -0
  21. data/lib/jss/webhooks/configuration.rb +212 -0
  22. data/lib/jss/webhooks/data/sample_handlers/RestAPIOperation-executable +90 -0
  23. data/lib/jss/webhooks/data/sample_handlers/RestAPIOperation.rb +44 -0
  24. data/lib/jss/webhooks/data/sample_jsons/ComputerAdded.json +27 -0
  25. data/lib/jss/webhooks/data/sample_jsons/ComputerCheckIn.json +27 -0
  26. data/lib/jss/webhooks/data/sample_jsons/ComputerInventoryCompleted.json +27 -0
  27. data/lib/jss/webhooks/data/sample_jsons/ComputerPolicyFinished.json +27 -0
  28. data/lib/jss/webhooks/data/sample_jsons/ComputerPushCapabilityChanged.json +27 -0
  29. data/lib/jss/webhooks/data/sample_jsons/JSSShutdown.json +14 -0
  30. data/lib/jss/webhooks/data/sample_jsons/JSSStartup.json +14 -0
  31. data/lib/jss/webhooks/data/sample_jsons/MobileDeviceCheckIn.json +26 -0
  32. data/lib/jss/webhooks/data/sample_jsons/MobileDeviceCommandCompleted.json +26 -0
  33. data/lib/jss/webhooks/data/sample_jsons/MobileDeviceEnrolled.json +26 -0
  34. data/lib/jss/webhooks/data/sample_jsons/MobileDevicePushSent.json +26 -0
  35. data/lib/jss/webhooks/data/sample_jsons/MobileDeviceUnEnrolled.json +26 -0
  36. data/lib/jss/webhooks/data/sample_jsons/PatchSoftwareTitleUpdated.json +14 -0
  37. data/lib/jss/webhooks/data/sample_jsons/PushSent.json +11 -0
  38. data/lib/jss/webhooks/data/sample_jsons/RestAPIOperation.json +15 -0
  39. data/lib/jss/webhooks/data/sample_jsons/SCEPChallenge.json +10 -0
  40. data/lib/jss/webhooks/data/sample_jsons/SmartGroupComputerMembershipChange.json +13 -0
  41. data/lib/jss/webhooks/data/sample_jsons/SmartGroupMobileDeviceMembershipChange.json +13 -0
  42. data/lib/jss/webhooks/event.rb +138 -0
  43. data/lib/jss/webhooks/event/computer_added.rb +37 -0
  44. data/lib/jss/webhooks/event/computer_check_in.rb +37 -0
  45. data/lib/jss/webhooks/event/computer_inventory_completed.rb +37 -0
  46. data/lib/jss/webhooks/event/computer_policy_finished.rb +37 -0
  47. data/lib/jss/webhooks/event/computer_push_capability_changed.rb +37 -0
  48. data/lib/jss/webhooks/event/handlers.rb +191 -0
  49. data/lib/jss/webhooks/event/jss_shutdown.rb +37 -0
  50. data/lib/jss/webhooks/event/jss_startup.rb +37 -0
  51. data/lib/jss/webhooks/event/mobile_device_check_in.rb +37 -0
  52. data/lib/jss/webhooks/event/mobile_device_command_completed.rb +37 -0
  53. data/lib/jss/webhooks/event/mobile_device_enrolled.rb +37 -0
  54. data/lib/jss/webhooks/event/mobile_device_push_sent.rb +37 -0
  55. data/lib/jss/webhooks/event/mobile_device_unenrolled.rb +37 -0
  56. data/lib/jss/webhooks/event/patch_software_title_updated.rb +37 -0
  57. data/lib/jss/webhooks/event/push_sent.rb +37 -0
  58. data/lib/jss/webhooks/event/rest_api_operation.rb +37 -0
  59. data/lib/jss/webhooks/event/scep_challenge.rb +37 -0
  60. data/lib/jss/webhooks/event/smart_group_computer_membership_change.rb +37 -0
  61. data/lib/jss/webhooks/event/smart_group_mobile_device_membership_change.rb +37 -0
  62. data/lib/jss/webhooks/event/webhook.rb +39 -0
  63. data/lib/jss/webhooks/event_objects.rb +111 -0
  64. data/lib/jss/webhooks/event_objects/computer.rb +48 -0
  65. data/lib/jss/webhooks/event_objects/jss.rb +35 -0
  66. data/lib/jss/webhooks/event_objects/mobile_device.rb +47 -0
  67. data/lib/jss/webhooks/event_objects/patch_software_title_update.rb +37 -0
  68. data/lib/jss/webhooks/event_objects/push.rb +32 -0
  69. data/lib/jss/webhooks/event_objects/rest_api_operation.rb +36 -0
  70. data/lib/jss/webhooks/event_objects/scep_challenge.rb +31 -0
  71. data/lib/jss/webhooks/event_objects/smart_group.rb +34 -0
  72. data/lib/jss/webhooks/server_app.rb +36 -0
  73. data/lib/jss/webhooks/server_app/routes.rb +26 -0
  74. data/lib/jss/webhooks/server_app/routes/handle_webhook_event.rb +38 -0
  75. data/lib/jss/webhooks/server_app/routes/home.rb +36 -0
  76. data/lib/jss/webhooks/server_app/self_signed_cert.rb +64 -0
  77. data/lib/jss/webhooks/server_app/server.rb +59 -0
  78. data/lib/jss/webhooks/version.rb +31 -0
  79. metadata +63 -4
@@ -25,8 +25,6 @@
25
25
  ###
26
26
  module JSS
27
27
 
28
-
29
-
30
28
  #####################################
31
29
  ### Module Variables
32
30
  #####################################
@@ -76,25 +74,23 @@ module JSS
76
74
  #####################################
77
75
 
78
76
  ### The name of the JSS database on the mysql server
79
- DEFAULT_DB_NAME = "jamfsoftware"
77
+ DEFAULT_DB_NAME = 'jamfsoftware'.freeze
80
78
 
81
79
  ### give the connection a 60 second timeout, for really slow
82
80
  ### net connections (like... from airplanes)
83
81
  DFT_TIMEOUT = 60
84
82
 
85
83
  ###
86
- DFT_SOCKET = '/var/mysql/mysql.sock'
84
+ DFT_SOCKET = '/var/mysql/mysql.sock'.freeze
87
85
 
88
86
  ### the default MySQL port
89
87
  DFT_PORT = 3306
90
88
 
91
89
  ### The default encoding in the tables - JAMF wisely uses UTF-8
92
- DFT_CHARSET = "utf8"
90
+ DFT_CHARSET = 'utf8'.freeze
93
91
 
94
92
  ### the strftime format for reading/writing dates in the db
95
- SQL_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
96
-
97
-
93
+ SQL_DATE_FORMAT = '%Y-%m-%d %H:%M:%S'.freeze
98
94
 
99
95
  attr_reader :server
100
96
  attr_reader :port
@@ -106,12 +102,11 @@ module JSS
106
102
  attr_reader :write_timeout
107
103
  attr_reader :connected
108
104
 
109
-
110
- def initialize ()
105
+ def initialize
111
106
  require 'mysql'
112
107
  @mysql = Mysql.init
113
108
  @connected = false
114
- end #init
109
+ end # init
115
110
 
116
111
  ###
117
112
  ### Connect to the JSS MySQL database.
@@ -145,34 +140,25 @@ module JSS
145
140
  ### @return [true] the connection was successfully made.
146
141
  ###
147
142
  def connect(args = {})
148
-
149
143
  # server might come frome several places
150
144
  # if not given in the args, use #hostname to figure out
151
145
  # which
152
- @server = args[:server] ? args[:server] : hostname
146
+ @server = args[:server] ? args[:server] : hostname
153
147
 
154
148
  # settings from config if they aren't in the args
155
- args[:port] ||= JSS::CONFIG.db_server_port
156
- args[:socket] ||= JSS::CONFIG.db_server_socket
157
- args[:db_name] ||= JSS::CONFIG.db_name
149
+ args[:port] ||= JSS::CONFIG.db_server_port ? JSS::CONFIG.db_server_port : Mysql::MYSQL_TCP_PORT
150
+ args[:socket] ||= JSS::CONFIG.db_server_socket ? JSS::CONFIG.db_server_socket : DFT_SOCKET
151
+ args[:db_name] ||= JSS::CONFIG.db_name ? JSS::CONFIG.db_name : DEFAULT_DB_NAME
158
152
  args[:user] ||= JSS::CONFIG.db_username
159
153
  args[:connect_timeout] ||= JSS::CONFIG.db_connect_timeout
160
154
  args[:read_timeout] ||= JSS::CONFIG.db_read_timeout
161
155
  args[:write_timeout] ||= JSS::CONFIG.db_write_timeout
156
+ args[:charset] ||= DFT_CHARSET
162
157
 
163
158
  ### if one timeout was given, use it for all three
164
- args[:connect_timeout] ||= args[:timeout]
165
- args[:read_timeout] ||= args[:timeout]
166
- args[:write_timeout] ||= args[:timeout]
167
-
168
- ### if these weren't given, use the defaults
169
- args[:connect_timeout] ||= DFT_TIMEOUT
170
- args[:read_timeout] ||= DFT_TIMEOUT
171
- args[:write_timeout] ||= DFT_TIMEOUT
172
- args[:port] ||= Mysql::MYSQL_TCP_PORT
173
- args[:socket] ||= DFT_SOCKET
174
- args[:db_name] ||= DEFAULT_DB_NAME
175
- args[:charset] ||= DFT_CHARSET
159
+ args[:connect_timeout] ||= args[:timeout] ? args[:timeout] : DFT_TIMEOUT
160
+ args[:read_timeout] ||= args[:timeout] ? args[:timeout] : DFT_TIMEOUT
161
+ args[:write_timeout] ||= args[:timeout] ? args[:timeout] : DFT_TIMEOUT
176
162
 
177
163
  begin
178
164
  @mysql.close if connected?
@@ -189,20 +175,20 @@ module JSS
189
175
  @write_timeout = args[:write_timeout]
190
176
 
191
177
  # make sure we have a user, pw, server
192
- raise JSS::MissingDataError, "No MySQL user specified, or listed in configuration." unless args[:user]
178
+ raise JSS::MissingDataError, 'No MySQL user specified, or listed in configuration.' unless args[:user]
193
179
  raise JSS::MissingDataError, "Missing :pw (or :prompt/:stdin) for user '#{@user}'" unless args[:pw]
194
- raise JSS::MissingDataError, "No MySQL Server hostname specified, or listed in configuration." unless @server
180
+ raise JSS::MissingDataError, 'No MySQL Server hostname specified, or listed in configuration.' unless @server
195
181
 
196
182
  @pw = if args[:pw] == :prompt
197
- JSS.prompt_for_password "Enter the password for the MySQL user #{@user}@#{@server}:"
198
- elsif args[:pw].is_a?(Symbol) and args[:pw].to_s.start_with?('stdin')
199
- args[:pw].to_s =~ /^stdin(\d+)$/
200
- line = $1
201
- line ||= 2
202
- JSS.stdin line
203
- else
204
- args[:pw]
205
- end
183
+ JSS.prompt_for_password "Enter the password for the MySQL user #{@user}@#{@server}:"
184
+ elsif args[:pw].is_a?(Symbol) && args[:pw].to_s.start_with?('stdin')
185
+ args[:pw].to_s =~ /^stdin(\d+)$/
186
+ line = Regexp.last_match(1)
187
+ line ||= 2
188
+ JSS.stdin line
189
+ else
190
+ args[:pw]
191
+ end
206
192
 
207
193
  @mysql = Mysql.init
208
194
 
@@ -210,16 +196,18 @@ module JSS
210
196
  @mysql.options Mysql::OPT_READ_TIMEOUT, @read_timeout
211
197
  @mysql.options Mysql::OPT_WRITE_TIMEOUT, @write_timeout
212
198
  @mysql.charset = args[:charset]
213
- @mysql.connect @server, @user , @pw , @mysql_name, @port, @socket
199
+ @mysql.connect @server, @user, @pw, @mysql_name, @port, @socket
214
200
 
215
201
  @connected = true
216
- end # reconnect
202
+
203
+ @server
204
+ end # connect
217
205
 
218
206
  ###
219
207
  ### @return [Mysql] The mysql database connection itself
220
208
  ###
221
209
  def db
222
- raise JSS::InvalidConnectionError, "No database connection. Please use JSS::DB_CNX.connect" unless JSS::DB_CNX.connected?
210
+ raise JSS::InvalidConnectionError, 'No database connection. Please use JSS::DB_CNX.connect' unless JSS::DB_CNX.connected?
223
211
  @mysql
224
212
  end
225
213
 
@@ -246,7 +234,7 @@ module JSS
246
234
  ###
247
235
  ### @return [Boolean] does the server host a MySQL server?
248
236
  ###
249
- def valid_server? (server, port = DFT_PORT)
237
+ def valid_server?(server, port = DFT_PORT)
250
238
  mysql = Mysql.init
251
239
  mysql.options Mysql::OPT_CONNECT_TIMEOUT, 5
252
240
 
@@ -254,14 +242,14 @@ module JSS
254
242
  # this connection should get an access denied error if there is
255
243
  # a mysql server there. I'm assuming no one will use this username
256
244
  # and pw for anything real
257
- mysql.connect server, "notArealUser", "definatelyNotA#{$$}password", "not_a_db", port
245
+ mysql.connect server, 'notArealUser', "definatelyNotA#{$PROCESS_ID}password", 'not_a_db', port
258
246
 
259
247
  rescue Mysql::ServerError::AccessDeniedError
260
248
  return true
261
249
  rescue
262
250
  return false
263
251
  end
264
- return false
252
+ false
265
253
  end
266
254
 
267
255
  ### The server to which we are connected, or will
@@ -277,10 +265,9 @@ module JSS
277
265
  srvr = JSS::CONFIG.db_server_name
278
266
  # otherwise, assume its on the JSS server to which this client talks
279
267
  srvr ||= JSS::Client.jss_server
280
- return srvr
268
+ srvr
281
269
  end
282
270
 
283
-
284
271
  #### Aliases
285
272
 
286
273
  alias connected? connected
@@ -298,5 +285,3 @@ module JSS
298
285
  end
299
286
 
300
287
  end # module
301
-
302
-
@@ -1,46 +1,45 @@
1
1
  ### Copyright 2016 Pixar
2
- ###
2
+ ###
3
3
  ### Licensed under the Apache License, Version 2.0 (the "Apache License")
4
4
  ### with the following modification; you may not use this file except in
5
5
  ### compliance with the Apache License and the following modification to it:
6
6
  ### Section 6. Trademarks. is deleted and replaced with:
7
- ###
7
+ ###
8
8
  ### 6. Trademarks. This License does not grant permission to use the trade
9
9
  ### names, trademarks, service marks, or product names of the Licensor
10
10
  ### and its affiliates, except as required to comply with Section 4(c) of
11
11
  ### the License and to reproduce the content of the NOTICE file.
12
- ###
12
+ ###
13
13
  ### You may obtain a copy of the Apache License at
14
- ###
14
+ ###
15
15
  ### http://www.apache.org/licenses/LICENSE-2.0
16
- ###
16
+ ###
17
17
  ### Unless required by applicable law or agreed to in writing, software
18
18
  ### distributed under the Apache License with the above modification is
19
19
  ### distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20
20
  ### KIND, either express or implied. See the Apache License for the specific
21
21
  ### language governing permissions and limitations under the Apache License.
22
- ###
22
+ ###
23
23
  ###
24
24
 
25
25
  ###
26
26
  class String
27
-
28
- ### Convert the strings "true" and "false"
27
+
28
+ ### Convert the strings "true" and "false"
29
29
  ### (after stripping whitespace and downcasing)
30
30
  ### to TrueClass and FalseClass respectively
31
- ###
31
+ ###
32
32
  ### Return nil if any other string.
33
33
  ###
34
34
  ### @return [Boolean,nil] the boolean value
35
35
  ###
36
36
  def jss_to_bool
37
- case self.strip.downcase
38
- when "true" then return true
39
- when "false" then return false
40
- else return nil
37
+ case strip.downcase
38
+ when 'true' then true
39
+ when 'false' then false
41
40
  end # case
42
41
  end # to bool
43
-
42
+
44
43
  ### Convert a string to a Time object
45
44
  ###
46
45
  ### returns nil if not parsable by JSS::parse_datetime
@@ -48,12 +47,17 @@ class String
48
47
  ### @return [Time] the time represented by the string.
49
48
  ###
50
49
  def jss_to_time
51
- begin
52
- JSS.parse_time self
53
- rescue
54
- return nil
55
- end
50
+ JSS.parse_time self
51
+ rescue
52
+ return nil
53
+ end
54
+
55
+ ### Convert a String to a Pathname object
56
+ ###
57
+ ### @return [Pathname]
58
+ ###
59
+ def jss_to_pathname
60
+ Pathname.new self
56
61
  end
57
62
 
58
-
59
- end # class
63
+ end # class
@@ -26,6 +26,6 @@
26
26
  module JSS
27
27
 
28
28
  ### The version of the JSS ruby gem
29
- VERSION = '0.6.5'
29
+ VERSION = '0.6.6'
30
30
 
31
31
  end # module
@@ -0,0 +1,52 @@
1
+ ### Copyright 2016 Pixar
2
+ ###
3
+ ### Licensed under the Apache License, Version 2.0 (the "Apache License")
4
+ ### with the following modification; you may not use this file except in
5
+ ### compliance with the Apache License and the following modification to it:
6
+ ### Section 6. Trademarks. is deleted and replaced with:
7
+ ###
8
+ ### 6. Trademarks. This License does not grant permission to use the trade
9
+ ### names, trademarks, service marks, or product names of the Licensor
10
+ ### and its affiliates, except as required to comply with Section 4(c) of
11
+ ### the License and to reproduce the content of the NOTICE file.
12
+ ###
13
+ ### You may obtain a copy of the Apache License at
14
+ ###
15
+ ### http://www.apache.org/licenses/LICENSE-2.0
16
+ ###
17
+ ### Unless required by applicable law or agreed to in writing, software
18
+ ### distributed under the Apache License with the above modification is
19
+ ### distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20
+ ### KIND, either express or implied. See the Apache License for the specific
21
+ ### language governing permissions and limitations under the Apache License.
22
+ ###
23
+ ###
24
+
25
+ require 'ruby-jss'
26
+ require 'immutable-struct'
27
+
28
+
29
+ # The JSSWebHooks module
30
+ #
31
+ module JSSWebHooks
32
+
33
+ # load in some sample JSON files, one per event type
34
+ @sample_jsons = {}
35
+
36
+ sample_json_dir = Pathname.new(__FILE__).parent + 'webhooks/data/sample_jsons'
37
+ sample_json_dir.children.each do |jf|
38
+ event = jf.basename.to_s.chomp(jf.extname).to_sym
39
+ @sample_jsons[event] = jf.read
40
+ end
41
+
42
+ def self.sample_jsons
43
+ @sample_jsons
44
+ end
45
+
46
+ end # module
47
+
48
+ require 'jss/webhooks/configuration'
49
+ require 'jss/webhooks/event_objects'
50
+ require 'jss/webhooks/event'
51
+
52
+ JSSWebHooks::Event::Handlers.load_handlers
@@ -0,0 +1,269 @@
1
+
2
+ # WebHooks for ruby-jss
3
+
4
+ - [Introduction](#introduction)
5
+ - [The Framework](#the-framework)
6
+ - [Event Handlers](#event-handlers)
7
+ - [Internal Handlers](#internal-handlers)
8
+ - [External Handlers](#external-handlers)
9
+ - [Putting it together](#putting-it-together)
10
+ - [Events and Event objects](#events-and-event-objects)
11
+ - [The Server](#the-server)
12
+ - [Installing JSSWebHooks into ruby-jss](#installing-jsswebhooks-into-ruby-jss)
13
+ - [TODOs](#todos)
14
+
15
+ <!-- TOC depthFrom:2 depthTo:6 withLinks:1 updateOnSave:0 orderedList:0 -->
16
+
17
+
18
+ <!-- /TOC -->
19
+
20
+ ## Introduction
21
+
22
+ JSSWebHooks is a sub-module of ruby-jss which implements both a framework for
23
+ working with JSS Webhook events, and a simple http server, based on Sinatra and
24
+ Webrick, for handling those events.
25
+
26
+ You do not need to be a Ruby programmer to make use of this framework! "Event Handlers"
27
+ can be written in any language and used by the web server included with the module.
28
+ See _Event Handlers_ and _The Server_ below for more info.
29
+
30
+ JSSWebHooks is still in early development. While the basics seem to work,
31
+ there's much to do before it can be released in the ruby-jss gem.
32
+
33
+ For details about the JSS Webhooks API, and the JSON data it passes, please see
34
+ [Bryson Tyrrell's excellent
35
+ documentation.](https://unofficial-jss-api-docs.atlassian.net/wiki/display/JRA/Webhooks+API)
36
+
37
+ **Note:** when creating WebHooks in your JSS to be handled by the framework, you must
38
+ specify JSON in the 'Content Type' section. This framework doesn't support XML
39
+ formated WebHook data.
40
+
41
+ ## The Framework
42
+
43
+ The JSSWebHooks framework abstracts WebHook events and their parts as Ruby
44
+ classes. When the JSON payload of a JSS WebHook POST request is passed into the
45
+ `JSSWebHooks::Event.parse_event` method, an instance of the appropriate subclass
46
+ of `JSSWebHooks::Event` is returned, for example
47
+ `JSSWebHooks::ComputerInventoryCompletedEvent`
48
+
49
+ Each event instance contains these important attributes:
50
+
51
+ * **webhook:** A read-only instance of `JSSWebHook::Event::WebHook`
52
+ representing the WebHook stored in the JSS which cause the POST request. This
53
+ object has attributes matching those in the "webhook" dict. of the POSTed
54
+ JSON.
55
+
56
+ * **event_object:** A read-only instance of a `JSSWebHook::EventObject::<Class>`
57
+ representing the 'event object' that accompanies the event that triggered the
58
+ WebHook. It comes from the 'object' dict of the POSTed JSON, and different
59
+ events come with different objects attached. For example, the
60
+ ComputerInventoryCompleted event comes with a "computer" object, containing
61
+ data about the JSS computer that completed inventory.
62
+
63
+ This is not full `JSS::Computer` instance from the REST API, but rather a group
64
+ of named attributes about that computer. At the moment the JSSWebHooks
65
+ framework makes no attempt to use the event object to create a `JSS::Computer`
66
+ instance but the handlers written for the event could easily do so if needed.
67
+
68
+ * **event_json:** The JSON content from the POST request, parsed into
69
+ a Ruby hash with symbolized keys (meaning the JSON key "deviceName" becomes
70
+ the symbol :deviceName)
71
+
72
+ * **raw_json:** A String containing the raw JSON from the POST
73
+ request.
74
+
75
+ * **handlers:** An Array of custom plugins for working with the
76
+ event. See _Event Handlers_, below.
77
+
78
+
79
+ ### Event Handlers
80
+
81
+ A handler is a file containing code to run when a webhook event occurs. These
82
+ files are located in a specified directory, /Library/Application
83
+ Support/JSSWebHooks/ by default, and are loaded at runtime. It's up to the Casper
84
+ administrator to create these handlers to perform desired tasks. Each class of
85
+ event can have as many handlers as desired, all will be executed when the event's
86
+ `handle` method is called.
87
+
88
+ Handler files must begin with the name of the event they handle, e.g.
89
+ ComputerAdded, followed by: nothing, a dot, a dash, or an underscore. Hander
90
+ filenames are case-insensitive.
91
+
92
+ All of these filenames work as handlers for ComputerAdded events:
93
+
94
+ - ComputerAdded
95
+ - computeradded.sh
96
+ - COMPUTERAdded_notify_team
97
+ - Computeradded-update-ldap
98
+
99
+ There are two kinds of handlers:
100
+
101
+ #### Internal Handlers
102
+
103
+ These handlers are _non-executable_ files containing Ruby code. The code is
104
+ loaded at runtime and executed in the context of the JSSWebHooks Framework when
105
+ called by an event.
106
+
107
+ Internal handlers must be defined as a [ruby code block](http://rubylearning.com/satishtalim/ruby_blocks.html) passed to the
108
+ `JSSWebHooks.event_handler` method. The block must take one parameter, the
109
+ JSSWebHooks::Event subclass instance being handled. Here's a simple example of
110
+ a handler for a JSSWebHooks::ComputerAddedEvent
111
+
112
+ ```ruby
113
+ JSSWebHooks.event_handler do |event|
114
+ cname = event.event_object.deviceName
115
+ uname = event.event_object.realName
116
+ puts "Computer '#{cname}' was just added to the JSS for user #{uname}."
117
+ end
118
+ ```
119
+
120
+ In this example, the codeblock takes one parameter, which it expects to be
121
+ a JSSWebHooks::ComputerAddedEvent instance, and uses it in the variable "event".
122
+ It then extracts the "deviceName" and "realName" values from the event_object
123
+ contained in the event, and uses them to send a message to stdout.
124
+
125
+ Internal handlers **must not** be executable files. Executability is how the
126
+ framework determines if a handler is internal or external.
127
+
128
+ #### External Handlers
129
+
130
+ External handlers are _executable_ files that are executed when called by an
131
+ event. They can be written in any language, but they must accept raw JSON on
132
+ their standard input. It's up to them to parse that JSON and react to it as
133
+ desired. In this case the JSSWebHooks framework is merely a conduit for passing
134
+ the Posted JSON to the executable program.
135
+
136
+ Here's a simple example using bash and [jq](https://stedolan.github.io/jq/) to
137
+ do the same as the ruby example above:
138
+
139
+ ```bash
140
+ #!/bin/bash
141
+ JQ="/usr/local/bin/jq"
142
+ while read line ; do JSON="$JSON $line" ; done
143
+ cname=`echo $JSON | "$JQ" -r '.event.deviceName'`
144
+ uname=`echo $JSON | "$JQ" -r '.event.realName'`
145
+ echo "Computer '${cname}' was just added to the JSS for user ${uname}."
146
+ ```
147
+
148
+ External handlers **must** be executable files. Executability is how the
149
+ framework determines if a handler is internal or external.
150
+
151
+ See ruby-jss/lib/jss/webhooks/data/sample_handlers/RestAPIOperation-executable
152
+ for a more detailed bash example that handles RestAPIOperation events.
153
+
154
+ ### Putting it together
155
+
156
+ Here's a commented sample of ruby code that uses the framework to process a
157
+ ComputerAdded event:
158
+
159
+ ```ruby
160
+ # load in the framework
161
+ require 'jss/webhooks'
162
+
163
+ # The framework comes with sample JSON files for each event type.
164
+ # In reality, a webserver would extract this from the data POSTed from the JSS
165
+ posted_json = JSSWebHooks.sample_jsons[:ComputerAdded]
166
+
167
+ # Create JSSWebHooks::Event::ComputerAddedEvent instance for the event
168
+ event = JSSWebHooks::Event.parse_event posted_json
169
+
170
+ # Call the events #handle method, which will execute any ComputerAdded
171
+ # handlers that were in the Handler directory when the framework was loaded.
172
+ event.handle
173
+ ```
174
+
175
+ Of course, you can use the framework without using the built-in #handle method,
176
+ and if you don't have any handlers in the directory, it won't do anything
177
+ anyway. Instead you are welcome to use the Event objects as desired in your own
178
+ Ruby code.
179
+
180
+ ### Events and Event objects
181
+
182
+ Here are the Event classes supported by the framework and the EventObject classes
183
+ they contain.
184
+ For details about the attributes of each EventObject, see [The Unofficial JSS API
185
+ Docs](https://unofficial-jss-api-docs.atlassian.net/wiki/display/JRA/Webhooks+API)
186
+
187
+ Each Event class is a subclass of `JSSWebHooks::Event`, where all of their
188
+ functionality is defined.
189
+
190
+ The EventObject classes aren't suclasses, but are dynamically-defined members of
191
+ the `JSSWebHooks::EventObjects` module.
192
+
193
+ | Event Classes | Event Object Classes |
194
+ | -------------- | ------------ |
195
+ | JSSWebHooks::ComputerAddedEvent | JSSWebHooks::EventObjects::Computer |
196
+ | JSSWebHooks::ComputerCheckInEvent | JSSWebHooks::EventObjects::Computer |
197
+ | JSSWebHooks::ComputerInventoryCompletedEvent | JSSWebHooks::EventObjects::Computer |
198
+ | JSSWebHooks::ComputerPolicyFinishedEvent | JSSWebHooks::EventObjects::Computer |
199
+ | JSSWebHooks::ComputerPushCapabilityChangedEvent | JSSWebHooks::EventObjects::Computer |
200
+ | JSSWebHooks::JSSShutdownEvent | JSSWebHooks::EventObjects::JSS |
201
+ | JSSWebHooks::JSSStartupEvent | JSSWebHooks::EventObjects::JSS |
202
+ | JSSWebHooks::MobilDeviceCheckinEvent | JSSWebHooks::EventObjects::MobileDevice |
203
+ | JSSWebHooks::MobilDeviceCommandCompletedEvent | JSSWebHooks::EventObjects::MobileDevice |
204
+ | JSSWebHooks::MobilDeviceEnrolledEvent | JSSWebHooks::EventObjects::MobileDevice |
205
+ | JSSWebHooks::MobilDevicePushSentEvent | JSSWebHooks::EventObjects::MobileDevice |
206
+ | JSSWebHooks::MobilDeviceUnenrolledEvent | JSSWebHooks::EventObjects::MobileDevice |
207
+ | JSSWebHooks::PatchSoftwareTitleUpdateEvent | JSSWebHooks::EventObjects::PatchSoftwareTitleUpdate |
208
+ | JSSWebHooks::PushSentEvent | JSSWebHooks::EventObjects::Push |
209
+ | JSSWebHooks::RestAPIOperationEvent | JSSWebHooks::EventObjects::RestAPIOperation |
210
+ | JSSWebHooks::SCEPChallengeEvent | JSSWebHooks::EventObjects::SCEPChallenge |
211
+ | JSSWebHooks::SmartGroupComputerMembershipChangeEvent | JSSWebHooks::EventObjects::SmartGroup |
212
+ | JSSWebHooks::SmartGroupMobileDeviveMembershipChangeEvent | JSSWebHooks::EventObjects::SmartGroup |
213
+
214
+
215
+ ## The Server
216
+
217
+ JSSWebHooks comes with a simple http server that uses the JSSWebHooks framework
218
+ to handle all incoming webhook POST requests from the JSS via a single URL.
219
+
220
+ To use it you'll need to install the [sinatra](http://www.sinatrarb.com/
221
+ ) ruby gem (`sudo gem install sinatra`).
222
+
223
+ After that, just run the `jss-webhook-server` command located in the bin
224
+ directory for ruby-jss and then point your WebHooks at:
225
+ http://_my_hostname_/handle_webhook_event
226
+
227
+ It will then process all incoming webhook POST requests using whatever handlers
228
+ you have installed.
229
+
230
+ To automate it on a dedicated machine, just make a LaunchDaemon plist to run
231
+ that command and keep it running.
232
+
233
+ ## Installing JSSWebHooks into ruby-jss
234
+
235
+ Until JSSWebHooks is officially released as part of ruby-jss, here's how to get
236
+ it up and running:
237
+
238
+ 0. Write a handler or two (see _Handlers_ above) and put them into
239
+ /Library/Application Support/JSSWebHooks/
240
+ 0. Clone ruby-jss from github into some path like /path/to/github/clone/
241
+ 1. If you don't already have it, install the ruby-jss gem `sudo gem install ruby-jss`
242
+ 2. Install sinata: `sudo gem install sinatra`
243
+ 3. Install immutable-struct: `sudo gem install immutable-struct`
244
+ 4. From /path/to/github/clone/lib/jss/ copy the webhooks folder **and** webhooks.rb
245
+ and into /Library/Ruby/Gems/2.0.0/gems/ruby-jss-_version_/lib/jss/ or
246
+ where-ever your gems are installed.
247
+ 5. From /path/to/github/clone/bin/ copy 'jss-webhook-server' into
248
+ /Library/Ruby/Gems/2.0.0/gems/ruby-jss-_version_/bin/
249
+
250
+ Then fire up `irb` and `require jss/webhooks` to start playing around. (remember
251
+ the sample JSON strings available in `JSSWebHooks.sample_jsons`)
252
+
253
+ OR
254
+
255
+ run /Library/Ruby/Gems/2.0.0/gems/ruby-jss-_version_/bin/jss-webhook-server and
256
+ point some WebHooks at your machine.
257
+
258
+
259
+ ## TODOs
260
+
261
+ - Add SSL support to the server
262
+ - Better (any!) thread management for handlers
263
+ - Logging and Debug options
264
+ - handler reloading for individual, or all, Event subclasses
265
+ - Generate the YARD docs
266
+ - better namespace protection for internal handlers
267
+ - Use and improve the configuration stuff.
268
+ - write proper documentation beyond this README
269
+ - I'm sure there's more to do...