ruby-jss 0.6.7 → 0.7.0

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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 84de41e5f3101ed3bf86feb96016d34cb92ed9f7
4
- data.tar.gz: 3dcde380f730a98fa04dfa64e8af60a7dda3c1eb
3
+ metadata.gz: 0b5245057f32987e52e0753f330b6b2fdd3b5a26
4
+ data.tar.gz: 8028f472e0694cfc2dd1fbf93914369482fdddd5
5
5
  SHA512:
6
- metadata.gz: 4566f6731f57ea24c99814fe43552fc20816a941e57155d1d6c9012af5ab74e7f81849287f53f514eff179e5e4dd7b94c57cd8c9f4c30b5391c88735a831771b
7
- data.tar.gz: 1d04c57374e60a2c1d8989e302196aef07468ba6a6d3b6d467043c2b08d6c218801ce072b61fc1bcedea84a2689725d1a22e00335f917415ead177cb01f6c8f9
6
+ metadata.gz: c4b8aaa54395cf65ccd910b15694edef02d70899169ef3f433f61721b2729d5b7af31558990bb88e2b48ef829079cf2f0bcf81fbe49f463059977e4ff39aef84
7
+ data.tar.gz: 8577fd4833e3f93c6582612210ca63dd2ec9da3249f1e199f1471c12569b5e7bdd247e42d1acb818f779a06ef57b5f7c97906995a1717c9548b906a1f44fceea
data/CHANGES.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Change History
2
2
 
3
+ ## v0.7.0 2017-02-01
4
+
5
+ - JSS::NetworkSegment - many bugfixes and cleanup. I didn't really have a proper grasp of IP CIDR masks before and how the (don't) related to the IP ranges used by Network Segments in the JSS. They CIDRs and full netmasks can still be used to set the ending addresses of NetworkSegment objects, but the #cidr method is gone, since it was meaningless for segments that didn't match subnet-ranges.
6
+ - subnect-update, the example command in the bin directory, has been renamed to negseg-update. It's also been cleaned up and uses the new functionality of JSS::NetworkSegment.
7
+ - JSS::DBConnection - fixed a bug where meaningless IO 'stream closed' errors would appear when closing the DB connection.
8
+
3
9
  ## v0.6.7 2017-01-03
4
10
 
5
11
  - Added class JSS::WebHook, which requires Jamf Pro 9.97 or higher.
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/ruby
2
+ require 'jss/webhooks/server_app'
3
+ JSSWebHooks::Server.run!
@@ -0,0 +1,387 @@
1
+ #!/usr/bin/ruby
2
+
3
+ ### Copyright 2017 Pixar
4
+
5
+ ###
6
+ ### Licensed under the Apache License, Version 2.0 (the "Apache License")
7
+ ### with the following modification; you may not use this file except in
8
+ ### compliance with the Apache License and the following modification to it:
9
+ ### Section 6. Trademarks. is deleted and replaced with:
10
+ ###
11
+ ### 6. Trademarks. This License does not grant permission to use the trade
12
+ ### names, trademarks, service marks, or product names of the Licensor
13
+ ### and its affiliates, except as required to comply with Section 4(c) of
14
+ ### the License and to reproduce the content of the NOTICE file.
15
+ ###
16
+ ### You may obtain a copy of the Apache License at
17
+ ###
18
+ ### http://www.apache.org/licenses/LICENSE-2.0
19
+ ###
20
+ ### Unless required by applicable law or agreed to in writing, software
21
+ ### distributed under the Apache License with the above modification is
22
+ ### distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
23
+ ### KIND, either express or implied. See the Apache License for the specific
24
+ ### language governing permissions and limitations under the Apache License.
25
+
26
+ # == Synopsis
27
+ # Add, remove, or change the Network Segments in the JSS based on data from an input file
28
+ # in CSV, tab, or other delimited format.
29
+ #
30
+ # == Usage
31
+ # subnet-update [-t | -d delimiter] [-h] file
32
+ #
33
+ #
34
+ # == Author
35
+ # Chris Lasell <chrisl@pixar.com>
36
+ #
37
+ # == Copyright
38
+ # Copyright (c) 2014 Pixar Animation Studios
39
+ ##############################
40
+
41
+ # Libraries
42
+ ##############################
43
+ require 'ruby-jss'
44
+ require 'getoptlong'
45
+ require 'english'
46
+ load '/Users/chrisl/git/gemdev/ruby-jss/lib/jss/api_object/network_segment.rb'
47
+
48
+ # The app object
49
+ ##############################
50
+ class App
51
+
52
+ # Constants
53
+ ##############################
54
+
55
+ PROG_NAME = File.basename($PROGRAM_NAME)
56
+
57
+ USAGE = "Usage: #{PROG_NAME} [options] [--help] /path/to/file".freeze
58
+
59
+ POTENTIAL_COLUMNS = %i(name starting ending cidr mask).freeze
60
+
61
+ DEFAULT_CACHE_FILE = Pathname.new('~/.last_subnet_update').expand_path
62
+
63
+ DEFAULT_DELIMITER = "\t".freeze
64
+
65
+ DEFAULT_COLUMNS = [:name, :starting, :ending].freeze
66
+
67
+ DEFAULT_MANUAL_PREFIX = 'Manual-'.freeze
68
+
69
+ # define the cli opts
70
+ CLI_OPTS = GetoptLong.new(
71
+ ['--help', '-H', GetoptLong::NO_ARGUMENT],
72
+ ['--delimiter', '--delim', '-d', GetoptLong::REQUIRED_ARGUMENT],
73
+ ['--header', '-h', GetoptLong::NO_ARGUMENT],
74
+ ['--columns', '-c', GetoptLong::OPTIONAL_ARGUMENT],
75
+ ['--manual-prefix', '-m', GetoptLong::OPTIONAL_ARGUMENT],
76
+ ['--cache', GetoptLong::REQUIRED_ARGUMENT],
77
+ ['--debug', GetoptLong::NO_ARGUMENT],
78
+ ['--server', '-S', GetoptLong::OPTIONAL_ARGUMENT],
79
+ ['--port', '-P', GetoptLong::OPTIONAL_ARGUMENT],
80
+ ['--user', '-U', GetoptLong::OPTIONAL_ARGUMENT],
81
+ ['--no-verify-cert', '-V', GetoptLong::NO_ARGUMENT],
82
+ ['--timeout', '-T', GetoptLong::OPTIONAL_ARGUMENT],
83
+ ['--no-op', '-N', GetoptLong::NO_ARGUMENT]
84
+ )
85
+
86
+ attr_reader :debug
87
+
88
+ def initialize(_args)
89
+ @getpass = $stdin.tty? ? :prompt : :stdin
90
+ set_defaults
91
+ parse_cli
92
+ check_opts
93
+ end # init
94
+
95
+ def set_defaults
96
+ @debug = false
97
+ @delim = DEFAULT_DELIMITER
98
+ @header = false
99
+ @columns = DEFAULT_COLUMNS
100
+ @cache_file = DEFAULT_CACHE_FILE
101
+ @manual_prefix = DEFAULT_MANUAL_PREFIX
102
+ @user = JSS::CONFIG.api_username
103
+ @server = JSS::CONFIG.api_server_name
104
+ end
105
+
106
+ def parse_cli
107
+ # parse the cli opts
108
+ CLI_OPTS.each do |opt, arg|
109
+ case opt
110
+ when '--help' then show_help
111
+ when '--delimiter' then @delim = arg
112
+ when '--header' then @header = true
113
+ when '--columns' then @columns = arg.split(',').map(&:to_sym)
114
+ when '--manual-prefix' then @manual_prefix = arg
115
+ when '--cache' then @cache_file = Pathname.new arg
116
+ when '--debug' then @debug = true
117
+ when '--server' then @server = arg
118
+ when '--port' then @port = arg
119
+ when '--user'then @user = arg
120
+ when '--no-verify-cert' then @verify_cert = false
121
+ when '--timeout' then @timeout = arg
122
+ when '--no-op' then @noop = true
123
+ end # case
124
+ end # each opt arg
125
+ @columns = nil if @columns && @columns.empty?
126
+ @file = Pathname.new ARGV.shift.to_s
127
+ end # parse_cli
128
+
129
+ def check_opts
130
+ raise JSS::MissingDataError, 'No JSS Username provided or found in the JSS gem config.' unless @user
131
+ raise JSS::MissingDataError, 'No JSS Server provided or found in the JSS gem config.' unless @server
132
+ raise ArgumentError, "No input file specified.\n#{USAGE}" unless @file
133
+ raise "Input file doesn't exist or is not readable: #{@file}" unless @file.readable?
134
+ end
135
+
136
+ # Go!
137
+ def run
138
+ unless data_file_changed?
139
+ puts "File hasn't changed since last time, no changes to make!"
140
+ return
141
+ end
142
+
143
+ connect_to_jss
144
+
145
+ @parsed_data = parse_file
146
+
147
+ update_network_segments
148
+ cache_latest_data
149
+ end # run
150
+
151
+ def connect_to_jss
152
+ JSS::API.connect(
153
+ server: @server,
154
+ port: @port,
155
+ verify_cert: @verify_cert,
156
+ user: @user,
157
+ pw: @getpass,
158
+ stdin_line: 1,
159
+ timeout: @timeout
160
+ )
161
+ end
162
+
163
+ def show_help
164
+ puts <<-FULLHELP
165
+ Update the JSS Network Segments from a delimited file of subnet information.
166
+ CAUTION: This script can delete Network Segments from your JSS.
167
+ See the --no-op option
168
+ #{USAGE}
169
+
170
+ Options:
171
+ -d, --delimiter - The field delimiter in the file, defaults to tab.
172
+ -c, --columns [col1,col2,col3]
173
+ - The column order in file, must include 'name', 'starting',
174
+ and either 'ending', 'mask', or 'cidr'
175
+ -h, --header - The first line of the file is a header line,
176
+ defining the columns
177
+ -m, --manual-prefix - Network Segment names in the file and the JSS with this
178
+ prefix are ignored. Defaults to 'Manual-'
179
+ --cache /path/.. - Where read/save the input data for comparison between runs.
180
+ Defaults to ~/.last_subnet_update
181
+ -S, --server srvr - specify the JSS API server name
182
+ -P, --port portnum - specify the JSS API port
183
+ -U, --user username - specify the JSS API user
184
+ -V, --no-verify-cert - Allow self-signed, unverified SSL certificate
185
+ -T, --timeout secs - specify the JSS API timeout
186
+ -N, --no-op - Don't make any changes in the JSS, just report what would
187
+ have be changed.
188
+ -H, --help - show this help
189
+ --debug - show the ruby backtrace when errors occur
190
+
191
+ This program parses the input file line by line (possibly accounting for a header line).
192
+ Each line defines the name and IP-range of a network segment.
193
+
194
+ - If a segment in the file doesn't exist in the JSS, it is created in the JSS.
195
+ - If a segment's range is different in the file, it is updated in the JSS.
196
+ - If a segment in the JSS doesn't exist in the file, it is deleted from the JSS.
197
+
198
+ Any network segments with names starting with the given --manual-prefix are ignored.
199
+ The default manual-prefix is 'Manual-' so, e.g. segments named 'Manual-isolated'
200
+ and 'Manual-special-servers' in the JSS won't be touched.
201
+
202
+ Input File:
203
+ - The file must contain three columns, separated by the --delimiter,
204
+ with these names, in any order:
205
+ - 'name' (the network segment name)
206
+ - 'starting' (the starting IP address of the network segment)
207
+ - ONE of:
208
+ - 'ending' (the ending IP address of the network segment)
209
+ - 'cidr' (the network range of the segment as a CIDR bitmask, e.g. '24')
210
+ - 'mask' (the network range of the segment as an IP mask, e.g. '255.255.255.0')
211
+ Notes:
212
+ - The --columns option is a comma-separted list of the three
213
+ column names above indicating the column-order in the file.
214
+
215
+ - If --columns are not provided, and --header is specified, the first line
216
+ is assumed to contain the column names, separated by the delimiter
217
+
218
+ - If --header is provided with --columns, the first line of the file is ignored.
219
+
220
+ - The raw data from the file is cached and compared to the input file at
221
+ the next run. If the data is identical, no changes are made.
222
+
223
+ - If no API connection settings are provided, they will be read from
224
+ /etc/ruby-jss.conf and ~/.ruby-jss.conf. See the ruby-jss docs for details.
225
+
226
+ - The password for the connection will be read from STDIN or prompted if needed
227
+
228
+ FULLHELP
229
+ exit 0
230
+ end
231
+
232
+ # parse the incoming data file into an Hash of Hashes,
233
+ # Top level keys are the NetSeg names,
234
+ # Subhashes have keys :starting, and :ending
235
+ # Exclude any with names starting with @manual_prefix
236
+ #
237
+ # @return [Hash<Hash>] The lines of the file, as hashes
238
+ #
239
+ def parse_file
240
+ puts 'Parsing the data file'
241
+ # split the data into an array by newline/return chars.
242
+ # this means files saved by excel or windows will work.
243
+ lines = @raw_data.split(/[\n\r]+/)
244
+
245
+ # remove the first line if its a header, and parse it into the columns
246
+ # if needed
247
+ if @header
248
+ header = lines.shift
249
+ @columns ||= header.split(/\s*#{@delim}\s*/).map(&:to_sym)
250
+ end
251
+
252
+ check_columns
253
+
254
+ parsed_data = {}
255
+ lines.each do |line|
256
+ parsed_line = parse_a_data_line line
257
+ next unless parsed_line
258
+ name = parsed_line.delete :name
259
+ parsed_data[name] = parsed_line
260
+ end
261
+ parsed_names = parsed_data.keys
262
+ jss_names = JSS::NetworkSegment.all_names.reject { |jss_name| jss_name.start_with? @manual_prefix }
263
+ @segments_to_add = parsed_names - jss_names
264
+ @segments_to_delete = jss_names - parsed_names
265
+ @segments_to_check_for_changes = parsed_names - @segments_to_add - @segments_to_delete
266
+ parsed_data
267
+ end # parse_file
268
+
269
+ def check_columns
270
+ raise "Columns must include 'name' and 'starting'" unless \
271
+ @columns.include?(:name) && \
272
+ @columns.include?(:starting)
273
+ raise "Columns must include either 'ending', 'cidr', or 'mask'" unless \
274
+ @columns.include?(:ending) || \
275
+ @columns.include?(:cidr) || \
276
+ @columns.include?(:mask)
277
+ @use_cidr = (@columns.include?(:cidr) || @columns.include?(:mask))
278
+ end
279
+
280
+ def parse_a_data_line(line)
281
+ parts = line.split(@delim).map(&:strip)
282
+ name = parts[@columns.index(:name)]
283
+ starting = parts[@columns.index(:starting)]
284
+ ending = parts[@columns.index(:ending)]
285
+ unless name && starting && ending
286
+ puts "Skipping invalid line: #{line}"
287
+ return nil
288
+ end
289
+ if name.start_with? @manual_prefix
290
+ puts "Ignoring line with manual_prefix: #{line}"
291
+ return nil
292
+ end
293
+ { name: name, starting: starting, ending: ending }
294
+ end
295
+
296
+ def data_file_changed?
297
+ # read in the file
298
+ @raw_data = @file.read
299
+ return true unless @cache_file.exist?
300
+ @raw_data != @cache_file.read
301
+ end
302
+
303
+ def cache_latest_data
304
+ return if @noop
305
+ @cache_file.jss_save @raw_data
306
+ end
307
+
308
+ def update_network_segments
309
+ puts 'Applying changes'
310
+ add_segments
311
+ delete_segments
312
+ update_segments
313
+ puts 'Done!'
314
+ end # update_network_segments
315
+
316
+ def add_segments
317
+ @segments_to_add.each do |seg|
318
+ seg_data = @parsed_data[seg]
319
+ if @noop
320
+ connector = @use_cidr ? '/' : '->'
321
+ puts "Without --no-op this would: Add segment named '#{seg}', #{seg_data[:starting]}#{connector}#{seg_data[:ending]}"
322
+ next
323
+ end # if noop
324
+
325
+ ender = @use_cidr ? :cidr : :ending_address
326
+ new_seg = JSS::NetworkSegment.new(
327
+ :id => :new,
328
+ :name => seg,
329
+ :starting_address => seg_data[:starting],
330
+ ender => seg_data[:ending]
331
+ )
332
+ new_seg.create
333
+ puts "Added Network Segment '#{new_seg.name}' to the JSS"
334
+ end # @segments_to_add.each do |seg|
335
+ end # add_segments
336
+
337
+ def delete_segments
338
+ @segments_to_delete.each do |seg|
339
+ if @noop
340
+ puts "Without --no-op this would: Delete segment named '#{seg}',"
341
+ next
342
+ end # if noop
343
+ JSS::NetworkSegment.new(name: seg).delete
344
+ puts "Deleted Network Segment '#{seg}' from the JSS"
345
+ end # @segments_to_delete.each do |seg|
346
+ end # delete_segments
347
+
348
+ def update_segments
349
+ @segments_to_check_for_changes.each do |seg|
350
+ seg_data = @parsed_data[seg]
351
+ data_start = IPAddr.new(seg_data[:starting])
352
+ data_end = if @use_cidr
353
+ IPAddr.new("#{seg_data[:starting]}/#{seg_data[:ending]}").to_range.end.mask 32
354
+ else
355
+ IPAddr.new(seg_data[:ending])
356
+ end
357
+
358
+ this_seg = JSS::NetworkSegment.new name: seg
359
+ data_range = data_start..data_end
360
+ next if this_seg.range == data_range
361
+
362
+ if @noop
363
+ connector = @use_cidr ? '/' : '->'
364
+ puts "Without --no-op this would: Update segment named '#{seg}', #{seg_data[:starting]}#{connector}#{seg_data[:ending]}"
365
+ next
366
+ end # if noop
367
+
368
+ this_seg.set_ip_range starting_address: data_start, ending_address: data_end
369
+ this_seg.save
370
+ puts "Updated Network Segment '#{seg}' to range #{data_start} - #{data_end}"
371
+ end # @segments_to_check_for_changes.each do |seg|
372
+ end # update segments
373
+
374
+ end # app
375
+
376
+ ##############################
377
+ # create the app and go
378
+ begin
379
+ debug = ARGV.include? '--debug'
380
+ app = App.new(ARGV)
381
+ app.run
382
+ rescue
383
+ # handle exceptions not handled elsewhere
384
+ puts "An error occurred: #{$ERROR_INFO}"
385
+ puts 'Backtrace:' if debug
386
+ puts $ERROR_POSITION if debug
387
+ end
@@ -60,8 +60,13 @@ module JSS
60
60
  ### * ip_address
61
61
  ### * udid
62
62
  ### * mac_addresses
63
- ### * location data from the Locatable module
64
- ### * purchasing data from the Purchasable module
63
+ ### * location data via the Locatable module
64
+ ### * purchasing data via the Purchasable module
65
+ ### * Extension Attribute values via the Extendable module
66
+ ### Note: as with other 'recon' generated values, Ext. Attrs.
67
+ ### populated by scripts cannot be modified via the API.
68
+ ### (the change would be overwritten the next time the machine
69
+ ### did a recon)
65
70
  ###
66
71
  ### After making any changes, you must call #update to send those
67
72
  ### changes to the server.
@@ -1,63 +1,56 @@
1
1
  ### Copyright 2017 Pixar
2
2
 
3
- ###
3
+ ###
4
4
  ### Licensed under the Apache License, Version 2.0 (the "Apache License")
5
5
  ### with the following modification; you may not use this file except in
6
6
  ### compliance with the Apache License and the following modification to it:
7
7
  ### Section 6. Trademarks. is deleted and replaced with:
8
- ###
8
+ ###
9
9
  ### 6. Trademarks. This License does not grant permission to use the trade
10
10
  ### names, trademarks, service marks, or product names of the Licensor
11
11
  ### and its affiliates, except as required to comply with Section 4(c) of
12
12
  ### the License and to reproduce the content of the NOTICE file.
13
- ###
13
+ ###
14
14
  ### You may obtain a copy of the Apache License at
15
- ###
15
+ ###
16
16
  ### http://www.apache.org/licenses/LICENSE-2.0
17
- ###
17
+ ###
18
18
  ### Unless required by applicable law or agreed to in writing, software
19
19
  ### distributed under the Apache License with the above modification is
20
20
  ### distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21
21
  ### KIND, either express or implied. See the Apache License for the specific
22
22
  ### language governing permissions and limitations under the Apache License.
23
- ###
23
+ ###
24
24
  ###
25
25
 
26
26
  ###
27
27
  module JSS
28
28
 
29
- #####################################
30
29
  ### Module Variables
31
30
  #####################################
32
31
 
33
- #####################################
34
32
  ### Module Methods
35
33
  #####################################
36
34
 
37
- #####################################
38
35
  ### Classes
39
36
  #####################################
40
37
 
41
- ###
42
38
  ### A Network Segment in the JSS
43
39
  ###
44
- ### @see JSS::APIObject
45
40
  ###
46
- class NetworkSegment < JSS::APIObject
41
+ class NetworkSegment < JSS::APIObject
47
42
 
48
- #####################################
49
43
  ### Mix Ins
50
44
  #####################################
51
45
  include JSS::Creatable
52
46
  include JSS::Updatable
53
47
  include Comparable
54
48
 
55
- #####################################
56
49
  ### Class Constants
57
50
  #####################################
58
51
 
59
52
  ### the REST resource base
60
- RSRC_BASE = "networksegments"
53
+ RSRC_BASE = 'networksegments'.freeze
61
54
 
62
55
  ### the hash key used for the JSON list output of all objects in the JSS
63
56
  RSRC_LIST_KEY = :network_segments
@@ -67,41 +60,124 @@ module JSS
67
60
  RSRC_OBJECT_KEY = :network_segment
68
61
 
69
62
  ### these keys, as well as :id and :name, are present in valid API JSON data for this class
70
- VALID_DATA_KEYS = [:distribution_point, :starting_address, :override_departments ]
71
-
72
- #####################################
73
- ### Class Variables
74
- #####################################
75
-
76
- @@network_ranges = nil
63
+ VALID_DATA_KEYS = [:distribution_point, :starting_address, :override_departments].freeze
77
64
 
78
- #####################################
79
65
  ### Class Methods
80
66
  #####################################
81
67
 
68
+ ### All NetworkSegments in the jss as IPAddr object Ranges representing the
69
+ ### Segment, e.g. with starting = 10.24.9.1 and ending = 10.24.15.254
70
+ ### the range looks like:
71
+ ### <IPAddr: IPv4:10.24.9.1/255.255.255.255>..#<IPAddr: IPv4:10.24.15.254/255.255.255.255>
82
72
  ###
83
- ### All NetworkSegments in the jss as IPAddr objects representing the
84
- ### subnet as a masked IPv4 address.
85
- ###
86
- ### Using the #include? and #to_range methods on those
87
- ### objects is very useful.
73
+ ### Using the #include? method on those Ranges is very useful.
88
74
  ###
89
- ### @return [Hash{Integer => IPAddr}] the network segments as masked IPv4 addresses
75
+ ### @return [Hash{Integer => Range}] the network segments as IPv4 address Ranges
90
76
  ###
91
77
  def self.network_ranges(refresh = false)
92
- @@network_ranges = nil if refresh
93
- return @@network_ranges if @@network_ranges
94
- @@network_ranges = {}
95
- self.all.each{|ns| @@network_ranges[ns[:id]] = IPAddr.jss_masked_v4addr(ns[:starting_address], ns[:ending_address])}
96
- @@network_ranges
78
+ @network_ranges = nil if refresh
79
+ return @network_ranges if @network_ranges
80
+ @network_ranges = {}
81
+ all(refresh).each { |ns| @network_ranges[ns[:id]] = IPAddr.new(ns[:starting_address])..IPAddr.new(ns[:ending_address]) }
82
+ @network_ranges
97
83
  end # def network_segments
98
84
 
99
- ###
100
85
  ### An alias for {NetworkSegment.network_ranges}
101
86
  ###
102
- def self.subnets(refresh = false); self.network_ranges refresh; end
87
+ ### DEPRECATED: This will be going away in a future release.
88
+ ###
89
+ ### @see {NetworkSegment::network_ranges}
90
+ ###
91
+ def self.subnets(refresh = false)
92
+ network_ranges refresh
93
+ end
103
94
 
95
+ ### Given a starting address & ending address, mask, or cidr,
96
+ ### return a Range object of IPAddr objects.
97
+ ###
98
+ ### starting_address: must be provided, and may be a masked address,
99
+ ### in which case nothing else is needed.
100
+ ###
101
+ ### If starting_address: is an unmasked address, then one of ending_address:
102
+ ### cidr: or mask: must be provided.
103
+ ###
104
+ ### If given, ending_address: overrides mask:, cidr:, and a masked starting_address:
105
+ ###
106
+ ### These give the same result:
107
+ ###
108
+ ### ip_range starting_address: '192.168.1.0', ending_address: '192.168.1.255'
109
+ ### ip_range starting_address: '192.168.1.0', mask: '255.255.255.0'
110
+ ### ip_range starting_address: '192.168.1.0', cidr: 24
111
+ ### ip_range starting_address: '192.168.1.0/24'
112
+ ### ip_range starting_address: '192.168.1.0/255.255.255.0'
113
+ ###
114
+ ### All the above will produce:
115
+ ### #<IPAddr: IPv4:192.168.1.0/255.255.255.255>..#<IPAddr: IPv4:192.168.1.255/255.255.255.255>
104
116
  ###
117
+ ### An exception is raised if the starting address is above the ending address.
118
+ ###
119
+ ### @param starting_address[String] The starting address, possibly masked
120
+ ###
121
+ ### @param ending_address[String] The ending address. If given, it overrides mask:,
122
+ ### cidr: and a masked starting_address:
123
+ ###
124
+ ### @param mask[String] The subnet mask to apply to the starting address to get
125
+ ### the ending address
126
+ ###
127
+ ### @param cidr[String, Integer] he cidr value to apply to the starting address to get
128
+ ### the ending address
129
+ ###
130
+ ### @return [Range<IPAddr>] the valid Range
131
+ ###
132
+ def self.ip_range(starting_address: nil, ending_address: nil, mask: nil, cidr: nil)
133
+ raise JSS::MissingDataError, 'starting_address: must be provided' unless starting_address
134
+
135
+ starting_address = masked_starting_address(starting_address: starting_address, mask: mask, cidr: cidr)
136
+
137
+ if ending_address
138
+ startip = IPAddr.new starting_address.split('/').first
139
+ endip = IPAddr.new ending_address.to_s
140
+ validate_ip_range(startip, endip)
141
+ else
142
+ raise ArgumentError, 'Must provide ending_address:, mask:, cidr: or a masked starting_address:' unless starting_address.include? '/'
143
+ subnet = IPAddr.new starting_address
144
+ startip = subnet.to_range.first.mask 32
145
+ endip = subnet.to_range.last.mask 32
146
+ end
147
+
148
+ startip..endip
149
+ end
150
+
151
+ ### If we are given a mask or cidr, append them to the starting_address
152
+ ###
153
+ ### @param starting[String] The starting address, possibly masked
154
+ ###
155
+ ### @param mask[String] The subnet mask to apply to the starting address to get
156
+ ### the ending address
157
+ ###
158
+ ### @param cidr[String, Integer] he cidr value to apply to the starting address to get
159
+ ### the ending address
160
+ ###
161
+ ### @return [String] the starting with the mask or cidr appended
162
+ ###
163
+ def self.masked_starting_address(starting_address: nil, mask: nil, cidr: nil)
164
+ starting_address = "#{starting}/#{mask || cidr}" if mask || cidr
165
+ starting_address.to_s
166
+ end
167
+
168
+ ### Raise an exception if a given starting ip is higher than a given ending ip
169
+ ###
170
+ ### @param startip[String] The starting ip
171
+ ###
172
+ ### @param endip[String] The ending ip
173
+ ###
174
+ ### @return [void]
175
+ ###
176
+ def self.validate_ip_range(startip, endip)
177
+ return nil if IPAddr.new(startip.to_s) <= IPAddr.new(endip.to_s)
178
+ raise JSS::InvalidDataError, "Starting IP #{startip} is higher than ending ip #{endip} "
179
+ end
180
+
105
181
  ### Find the ids of the network segments that contain a given IP address.
106
182
  ###
107
183
  ### Even tho IPAddr.include? will take a String or an IPAddr
@@ -112,38 +188,38 @@ module JSS
112
188
  ###
113
189
  ### @return [Array<Integer>] the ids of the NetworkSegments containing the given ip
114
190
  ###
115
- def self.network_segment_for_ip(ip)
191
+ def self.network_segments_for_ip(ip)
116
192
  ok_ip = IPAddr.new(ip)
117
193
  matches = []
118
- self.network_ranges.each{ |id, subnet| matches << id if subnet.include?(ok_ip) }
194
+ network_ranges.each { |id, subnet| matches << id if subnet.include?(ok_ip) }
119
195
  matches
120
196
  end
121
197
 
122
- ###
198
+ def self.network_segment_for_ip(ip)
199
+ network_segments_for_ip(ip)
200
+ end
201
+
123
202
  ### Find the current network segment ids for the machine running this code
124
203
  ###
125
204
  ### @return [Array<Integer>] the NetworkSegment ids for this machine right now.
126
205
  ###
127
- def self.my_network_segment
206
+ def self.my_network_segments
128
207
  network_segment_for_ip JSS::Client.my_ip_address
129
208
  end
130
209
 
210
+ def self.my_network_segment
211
+ my_network_segments
212
+ end
131
213
 
132
-
133
- #####################################
134
214
  ### Attributes
135
215
  #####################################
136
216
 
137
-
138
217
  ### @return [IPAddr] starting IP adresss
139
218
  attr_reader :starting_address
140
219
 
141
220
  ### @return [IPAddr] ending IP adresss
142
221
  attr_reader :ending_address
143
222
 
144
- ### @return [Integer] the CIDR
145
- attr_reader :cidr
146
-
147
223
  ### @return [String] building for this segment. Must be one of the buildings in the JSS
148
224
  attr_reader :building
149
225
 
@@ -168,108 +244,127 @@ module JSS
168
244
  ### @return [Boolean] should machines checking in from this segment update their building
169
245
  attr_reader :override_buildings
170
246
 
171
- ### @return [String] the unique identifier for this subnet, regardless of the JSS id
172
- attr_reader :uid
173
-
174
- ### @return [IPAddr] the IPAddr object representing this network segment, created from the uid
175
- attr_reader :subnet
176
-
247
+ ### Instantiate a NetworkSegment
177
248
  ###
178
- ### @see APIObject#initialize
249
+ ### @see_also JSS::NetworkSegment.ip_range for how starting and ending
250
+ ### addresses can be provided when using id: :new
179
251
  ###
180
- def initialize(args = {} )
181
-
252
+ def initialize(args = {})
182
253
  super args
183
254
 
184
255
  if args[:id] == :new
185
- raise MissingDataError, "Missing :starting_address." unless args[:starting_address]
186
- raise MissingDataError, "Missing :ending_address or :cidr." unless args[:ending_address] or args[:cidr]
187
- @init_data[:starting_address] = args[:starting_address]
188
- @init_data[:ending_address] = args[:ending_address]
189
- @init_data[:cidr] = args[:cidr].to_i
256
+ range = self.class.ip_range(
257
+ starting_address: args[:starting_address],
258
+ ending_address: args[:ending_address],
259
+ mask: args[:mask],
260
+ cidr: args[:cidr]
261
+ )
262
+ @init_data[:starting_address] = range.begin.to_s
263
+ @init_data[:ending_address] = range.end.to_s
190
264
  end
191
265
 
266
+ @starting_address = IPAddr.new @init_data[:starting_address]
267
+ @ending_address = IPAddr.new @init_data[:ending_address]
268
+
192
269
  @building = @init_data[:building]
193
270
  @department = @init_data[:department]
194
271
  @distribution_point = @init_data[:distribution_point]
195
272
  @netboot_server = @init_data[:netboot_server]
196
273
  @override_buildings = @init_data[:override_buildings]
197
274
  @override_departments = @init_data[:override_departments]
198
- @starting_address = IPAddr.new @init_data[:starting_address]
199
275
  @swu_server = @init_data[:swu_server]
200
276
  @url = @init_data[:url]
277
+ end # init
201
278
 
202
- ### by now, we must have either an ending address or a cidr
203
- ### along with a starting address, so figure out the other one.
204
- if @init_data[:ending_address]
205
- @ending_address = IPAddr.new @init_data[:ending_address]
206
- @cidr = IPAddr.jss_cidr_from_ends(@starting_address,@ending_address)
207
- else
208
- @cidr = @init_data[:cidr].to_i if @init_data[:cidr]
209
- @ending_address = IPAddr.jss_ending_address(@starting_address, @cidr)
210
- end # if args[:cidr]
211
-
212
- ### we now have all our data, make our unique identifier, the startingaddr/cidr
213
- @uid = "#{@starting_address}/#{@cidr}"
214
-
215
- ### the IPAddr object for this whole net segment
216
- @subnet = IPAddr.new @uid
217
-
218
- end #init
279
+ ### a Range built from the start and end addresses.
280
+ ### To be used for finding inclusion and overlaps.
281
+ ###
282
+ ### @return [Range<IPAddr>] the range of IPAddrs for this segment.
283
+ ###
284
+ def range
285
+ @starting_address..@ending_address
286
+ end
219
287
 
288
+ ### Does this network segment overlap with another?
220
289
  ###
221
- ### Thanks to Comparable, we can tell if we're equal or not.
290
+ ### @param other_segment[JSS::NetworkSegment] the other segment to check
222
291
  ###
223
- ### See Comparable#<=>
292
+ ### @return [Boolean] Does the other segment overlap this one?
293
+ ###
294
+ def overlap?(other_segment)
295
+ raise TypeError, 'Argument must be a JSS::NetworkSegment' unless \
296
+ other_segment.is_a? JSS::NetworkSegment
297
+ other_range = other_segment.range
298
+ range.include?(other_range.begin) || range.include?(other_range.end)
299
+ end
300
+
301
+ ### Does this network segment include an address or another segment?
302
+ ### Inclusion means the other is completely inside this one.
224
303
  ###
225
- ### @return [-1,0,1] ar we less than, equal or greater than the other?
304
+ ### @param thing[JSS::NetworkSegment, String, IPAddr] the other thing to check
226
305
  ###
227
- def <=> (other)
228
- self.subnet <=> other.subnet
306
+ ### @return [Boolean] Does this segment include the other?
307
+ ###
308
+ def include?(thing)
309
+ if thing.is_a? JSS::NetworkSegment
310
+ @starting_address <= thing.range.begin && @ending_address >= thing.range.end
311
+ else
312
+ thing = IPAddr.new thing.to_s
313
+ range.include? thing
314
+ end
229
315
  end
230
316
 
317
+ ### Does this network segment equal another?
318
+ ### equality means the ranges are equal
319
+ ###
320
+ ### @param other_segment[JSS::NetworkSegment] the other segment to check
231
321
  ###
322
+ ### @return [Boolean] Does this segment include the other?
323
+ ###
324
+ def ==(other)
325
+ raise TypeError, 'Argument must be a JSS::NetworkSegment' unless \
326
+ other.is_a? JSS::NetworkSegment
327
+ range == other.range
328
+ end
329
+
232
330
  ### Set the building
233
331
  ###
234
332
  ### @param newval[String, Integer] the new building by name or id, must be in the JSS
235
333
  ###
236
334
  ### @return [void]
237
335
  ###
238
- def building= (newval)
239
- new = JSS::Building.all.select{|b| b[:id] == newval or b[:name] == newval }[0]
336
+ def building=(newval)
337
+ new = JSS::Building.all.select { |b| (b[:id] == newval) || (b[:name] == newval) }[0]
240
338
  raise JSS::MissingDataError, "No building matching '#{newval}'" unless new
241
339
  @building = new[:name]
242
340
  @need_to_update = true
243
341
  end
244
342
 
245
- ###
246
343
  ### set the override buildings option
247
344
  ###
248
345
  ### @param newval[Boolean] the new override buildings option
249
346
  ###
250
347
  ### @return [void]
251
348
  ###
252
- def override_buildings= (newval)
253
- raise JSS::InvalidDataError, "New value must be boolean true or false" unless JSS::TRUE_FALSE.include? newval
349
+ def override_buildings=(newval)
350
+ raise JSS::InvalidDataError, 'New value must be boolean true or false' unless JSS::TRUE_FALSE.include? newval
254
351
  @override_buildings = newval
255
352
  @need_to_update = true
256
353
  end
257
354
 
258
- ###
259
355
  ### set the department
260
356
  ###
261
357
  ### @param newval[String, Integer] the new dept by name or id, must be in the JSS
262
358
  ###
263
359
  ### @return [void]
264
360
  ###
265
- def department= (newval)
266
- new = JSS::Department.all.select{|b| b[:id] == newval or b[:name] == newval }[0]
361
+ def department=(newval)
362
+ new = JSS::Department.all.select { |b| (b[:id] == newval) || (b[:name] == newval) }[0]
267
363
  raise JSS::MissingDataError, "No department matching '#{newval}' in the JSS" unless new
268
364
  @department = new[:name]
269
365
  @need_to_update = true
270
366
  end
271
367
 
272
- ###
273
368
  ### set the override depts option
274
369
  ###
275
370
  ### @param newval[Boolean] the new setting
@@ -277,139 +372,145 @@ module JSS
277
372
  ### @return [void]
278
373
  ###
279
374
  ###
280
- def override_departments= (newval)
281
- raise JSS::InvalidDataError, "New value must be boolean true or false" unless JSS::TRUE_FALSE.include? newval
375
+ def override_departments=(newval)
376
+ raise JSS::InvalidDataError, 'New value must be boolean true or false' unless JSS::TRUE_FALSE.include? newval
282
377
  @override_departments = newval
283
378
  @need_to_update = true
284
379
  end
285
380
 
286
- ###
287
381
  ### set the distribution_point
288
382
  ###
289
383
  ### @param newval[String, Integer] the new dist. point by name or id, must be in the JSS
290
384
  ###
291
385
  ### @return [void]
292
386
  ###
293
- def distribution_point= (newval)
294
- new = JSS::DistributionPoint.all.select{|b| b[:id] == newval or b[:name] == newval }[0]
387
+ def distribution_point=(newval)
388
+ new = JSS::DistributionPoint.all.select { |b| (b[:id] == newval) || (b[:name] == newval) }[0]
295
389
  raise JSS::MissingDataError, "No distribution_point matching '#{newval}' in the JSS" unless new
296
390
  @distribution_point = new[:name]
297
391
  @need_to_update = true
298
392
  end
299
393
 
300
- ###
301
394
  ### set the netboot_server
302
395
  ###
303
396
  ### @param newval[String, Integer] the new netboot server by name or id, must be in the JSS
304
397
  ###
305
398
  ### @return [void]
306
399
  ###
307
- def netboot_server= (newval)
308
- new = JSS::NetbootServer.all.select{|b| b[:id] == newval or b[:name] == newval }[0]
400
+ def netboot_server=(newval)
401
+ new = JSS::NetbootServer.all.select { |b| (b[:id] == newval) || (b[:name] == newval) }[0]
309
402
  raise JSS::MissingDataError, "No netboot_server matching '#{newval}' in the JSS" unless new
310
403
  @netboot_server = new[:name]
311
404
  @need_to_update = true
312
405
  end
313
406
 
314
- ###
315
407
  ### set the sw update server
316
408
  ###
317
409
  ### @param newval[String, Integer] the new server by name or id, must be in the JSS
318
410
  ###
319
411
  ### @return [void]
320
412
  ###
321
- def swu_server= (newval)
322
- new = JSS::SoftwareUpdateServer.all.select{|b| b[:id] == newval or b[:name] == newval }[0]
413
+ def swu_server=(newval)
414
+ new = JSS::SoftwareUpdateServer.all.select { |b| (b[:id] == newval) || (b[:name] == newval) }[0]
323
415
  raise JSS::MissingDataError, "No swu_server matching '#{newval}' in the JSS" unless new
324
416
  @swu_server = new[:name]
325
417
  @need_to_update = true
326
418
  end
327
419
 
328
- ###
329
420
  ### set the starting address
330
421
  ###
331
422
  ### @param newval[String, IPAddr] the new starting address
332
423
  ###
333
424
  ### @return [void]
334
425
  ###
335
- def starting_address= (newval)
336
- @starting_address = IPAddr.new newval # this will raise an error if the IP addr isn't valid
337
- raise JSS::InvalidDataError, "New starting address #{@starting_address} is higher than ending address #{@ending_address}" if @starting_address > @ending_address
338
- @cidr = IPAddr.jss_cidr_from_ends(@starting_address ,@ending_address)
339
- @uid = "#{@starting_address}/#{@cidr}"
340
- @subnet = IPAddr.new @uid
426
+ def starting_address=(newval)
427
+ self.class.validate_ip_range(newval, @ending_address)
428
+ @starting_address = IPAddr.new newval.to_s
341
429
  @need_to_update = true
342
430
  end
343
431
 
344
- ###
345
432
  ### set the ending address
346
433
  ###
347
434
  ### @param newval[String, IPAddr] the new ending address
348
435
  ###
349
436
  ### @return [void]
350
437
  ###
351
- def ending_address= (newval)
352
- @ending_address = IPAddr.new newval # this will raise an error if the IP addr isn't valid
353
- raise JSS::InvalidDataError, "New ending address #{@ending_address} is lower than starting address #{@starting_address}" if @ending_address < @starting_address
354
- @cidr = IPAddr.jss_cidr_from_ends(@starting_address,@ending_address)
355
- @uid = "#{@starting_address}/#{@cidr}"
356
- @subnet = IPAddr.new @uid
438
+ def ending_address=(newval)
439
+ self.class.validate_ip_range(@starting_address, newval)
440
+ @ending_address = IPAddr.new newval.to_s
357
441
  @need_to_update = true
358
442
  end
359
443
 
444
+ ### set the ending address by applying a new cidr (e.g. 24)
445
+ ### or mask (e.g. 255.255.255.0)
360
446
  ###
361
- ### set the cidr
362
- ###
363
- ### @param newval[String, IPAddr] the new cidr
447
+ ### @param newval[String, Integer] the new cidr or mask
364
448
  ###
365
449
  ### @return [void]
366
450
  ###
367
- def cidr= (newval)
368
- @cidr = newval
369
- @ending_address = IPAddr.jss_ending_address(@starting_address, @cidr)
370
- @uid = "#{@starting_address}/#{@cidr}"
371
- @subnet = IPAddr.new @uid
451
+ def cidr=(newval)
452
+ new_end = IPAddr.new("#{@starting_address}/#{newval}").to_range.end.mask 32
453
+ self.class.validate_ip_range(@starting_address, new_end)
454
+ @ending_address = new_end
372
455
  @need_to_update = true
373
456
  end
374
457
 
458
+ ### set a new starting and ending addr at the same time.
459
+ ###
460
+ ### @see_also NetworkSegment.ip_range for how to specify the starting
461
+ ### and ending addresses.
462
+ ###
463
+ ### @param starting_address[String] The starting address, possibly masked
375
464
  ###
376
- ### is a given address in this network segment?
465
+ ### @param ending_address[String] The ending address
377
466
  ###
378
- ### @param some_addr[IPAddr,String] the IP address to check
467
+ ### @param mask[String] The subnet mask to apply to the starting address to get
468
+ ### the ending address
379
469
  ###
380
- ### @return [Boolean]
470
+ ### @param cidr[String, Integer] he cidr value to apply to the starting address to get
471
+ ### the ending address
381
472
  ###
382
- def include? (some_addr)
383
- @subnet.include? IPAddr.new(some_addr)
473
+ ### @return [void]
474
+ ###
475
+ def set_ip_range(starting_address: nil, ending_address: nil, mask: nil, cidr: nil)
476
+ range = self.class.ip_range(
477
+ starting_address: starting_address,
478
+ ending_address: ending_address,
479
+ mask: mask,
480
+ cidr: cidr
481
+ )
482
+ @starting_address = range.first
483
+ @ending_address = range.last
484
+ @need_to_update = true
384
485
  end
385
486
 
386
-
387
487
  ### aliases
388
- alias identifier uid
389
- alias range subnet
390
-
391
488
  ######################
489
+ alias mask= cidr=
490
+ alias to_range range
491
+
392
492
  ### private methods
493
+ ######################
393
494
  private
394
495
 
395
- ###
396
496
  ### the xml formated data for adding or updating this in the JSS
397
497
  ###
398
498
  def rest_xml
399
499
  doc = REXML::Document.new APIConnection::XML_HEADER
400
- ns = doc.add_element "network_segment"
500
+ ns = doc.add_element 'network_segment'
401
501
  ns.add_element('building').text = @building
402
502
  ns.add_element('department').text = @department
403
503
  ns.add_element('distribution_point').text = @distribution_point
404
- ns.add_element('ending_address').text = @ending_address
504
+ ns.add_element('ending_address').text = @ending_address.to_s
405
505
  ns.add_element('name').text = @name
406
506
  ns.add_element('netboot_server').text = @netboot_server
407
507
  ns.add_element('override_buildings').text = @override_buildings
408
508
  ns.add_element('override_departments').text = @override_departments
409
- ns.add_element('starting_address').text = @starting_address
509
+ ns.add_element('starting_address').text = @starting_address.to_s
410
510
  ns.add_element('swu_server').text = @swu_server
411
- return doc.to_s
412
- end #rest_xml
511
+ doc.to_s
512
+ end # rest_xml
413
513
 
414
514
  end # class NetworkSegment
515
+
415
516
  end # module