ruby-jss 0.6.5 → 0.6.6

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of ruby-jss might be problematic. Click here for more details.

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...