windoo 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES.md +9 -0
  3. data/LICENSE.txt +177 -0
  4. data/README.md +222 -0
  5. data/lib/windoo/base_classes/array_manager.rb +335 -0
  6. data/lib/windoo/base_classes/criteria_manager.rb +327 -0
  7. data/lib/windoo/base_classes/criterion.rb +226 -0
  8. data/lib/windoo/base_classes/json_object.rb +472 -0
  9. data/lib/windoo/configuration.rb +221 -0
  10. data/lib/windoo/connection/actions.rb +152 -0
  11. data/lib/windoo/connection/attributes.rb +156 -0
  12. data/lib/windoo/connection/connect.rb +402 -0
  13. data/lib/windoo/connection/constants.rb +55 -0
  14. data/lib/windoo/connection/token.rb +489 -0
  15. data/lib/windoo/connection.rb +92 -0
  16. data/lib/windoo/converters.rb +31 -0
  17. data/lib/windoo/exceptions.rb +34 -0
  18. data/lib/windoo/mixins/api_collection.rb +408 -0
  19. data/lib/windoo/mixins/constants.rb +43 -0
  20. data/lib/windoo/mixins/default_connection.rb +75 -0
  21. data/lib/windoo/mixins/immutable.rb +34 -0
  22. data/lib/windoo/mixins/loading.rb +38 -0
  23. data/lib/windoo/mixins/patch/component.rb +102 -0
  24. data/lib/windoo/mixins/software_title/extension_attribute.rb +106 -0
  25. data/lib/windoo/mixins/utility.rb +23 -0
  26. data/lib/windoo/objects/capability.rb +82 -0
  27. data/lib/windoo/objects/capability_manager.rb +52 -0
  28. data/lib/windoo/objects/component.rb +99 -0
  29. data/lib/windoo/objects/component_criteria_manager.rb +26 -0
  30. data/lib/windoo/objects/component_criterion.rb +66 -0
  31. data/lib/windoo/objects/extension_attribute.rb +149 -0
  32. data/lib/windoo/objects/kill_app.rb +92 -0
  33. data/lib/windoo/objects/kill_app_manager.rb +89 -0
  34. data/lib/windoo/objects/patch.rb +235 -0
  35. data/lib/windoo/objects/patch_manager.rb +240 -0
  36. data/lib/windoo/objects/requirement.rb +85 -0
  37. data/lib/windoo/objects/requirement_manager.rb +52 -0
  38. data/lib/windoo/objects/software_title.rb +407 -0
  39. data/lib/windoo/validate.rb +548 -0
  40. data/lib/windoo/version.rb +15 -0
  41. data/lib/windoo/zeitwerk_config.rb +158 -0
  42. data/lib/windoo.rb +56 -0
  43. metadata +141 -0
@@ -0,0 +1,407 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the terms set forth in the LICENSE.txt file available at
4
+ # at the root of this project.
5
+ #
6
+
7
+ # frozen_string_literal: true
8
+
9
+ module Windoo
10
+
11
+ # A Software Title in the Title Editor
12
+ #
13
+ # NOTE: SoftwareTitles cannot be enabled when created.
14
+ # You must call 'enable' on them after creating any
15
+ # necessary sub-objects.
16
+ #
17
+ class SoftwareTitle < Windoo::BaseClasses::JSONObject
18
+
19
+ # Mixins
20
+ ######################
21
+
22
+ include Windoo::Mixins::APICollection
23
+ include Windoo::Mixins::SoftwareTitle::ExtensionAttribute
24
+
25
+ # Constants
26
+ ######################
27
+
28
+ LOCAL_TITLE_EDITOR_SOURCE_NAME = 'Local'
29
+ LOCAL_TITLE_EDITOR_SOURCE_ID = 0
30
+
31
+ RSRC_PATH = 'softwaretitles'
32
+
33
+ CONTAINER_CLASS = nil
34
+
35
+ # Public Class Methods
36
+ ######################
37
+
38
+ # Software Titles are the only collection resource that
39
+ # has an endpoint that returns summary list.
40
+ #
41
+ # All others, patches, components, ext attrs, etc...
42
+ # can only be individually accessed using their
43
+ # primary identifier, so the .all and .valid_id methods
44
+ # are not applicable to them.
45
+ #
46
+ # .all returns summmary Hashes for all Software Titles
47
+ # in the Title Editor
48
+ # the Hash keys are:
49
+ #
50
+ # :softwareTitleId=>1,
51
+ # :enabled=>false,
52
+ # :name=>"Test",
53
+ # :publisher=>"Some Company",
54
+ # :appName=>nil,
55
+ # :bundleId=>nil,
56
+ # :lastModified=>"2022-09-10T22:06:39Z",
57
+ # :currentVersion=>"5.0.1",
58
+ # :requirements=>3,
59
+ # :patches=>0, # Number of enabled patches, not existing patches?
60
+ # :id=>"com.somecompany.test",
61
+ # :sourceId=>0,
62
+ # :source=>"Local"
63
+ #
64
+ # @return [Array<Hash>]
65
+ ####
66
+ def self.all(cnx: Windoo.cnx)
67
+ cnx.get(self::RSRC_PATH)
68
+ end
69
+
70
+ # @return [Array<String>] The ids (unique names) of all titles
71
+ #####
72
+ def self.all_ids(cnx: Windoo.cnx)
73
+ all(cnx: cnx).map { |t| t[:id] }
74
+ end
75
+
76
+ # @return [Array<Integer>] The all_softwareTitleIds (numeric id numbers) of all titles
77
+ #####
78
+ def self.all_softwareTitleIds(cnx: Windoo.cnx)
79
+ all(cnx: cnx).map { |t| t[:softwareTitleId] }
80
+ end
81
+
82
+ # Override the method from APICollection, because
83
+ # SoftwareTitles can be looked up by both the primary_ident
84
+ # (softwareTitleId) and secondary (id)
85
+ #
86
+ # @return [Windoo::SoftwareTitle]
87
+ ####
88
+ def self.fetch(ident = nil, cnx: Windoo.cnx, **key_and_ident)
89
+ unless ident || !key_and_ident.empty?
90
+ raise ArgumentError,
91
+ "ident, or 'key: ident' is required to fetch #{self.class}"
92
+ end
93
+
94
+ id =
95
+ if ident
96
+ valid_id ident, raise_if_not_found: true, cnx: cnx
97
+ else
98
+ key, ident = key_and_ident.first
99
+
100
+ # Dont call valid_id if we are fetching based on the primary_id_key
101
+ # just used the value provided. The API will complain if it
102
+ # doesn't exist
103
+ key == primary_id_key ? ident : valid_id(ident, key: key, raise_if_not_found: true, cnx: cnx)
104
+ end
105
+
106
+ init_data = cnx.get("#{self::RSRC_PATH}/#{id}")
107
+ init_data[:cnx] = cnx
108
+ init_data[:fetching] = true
109
+
110
+ new(**init_data)
111
+ end
112
+
113
+ # @param ident [Integer, String] the identifier value to search for
114
+ #
115
+ # @param key [Symbol] if given, Only look for the value in this key.
116
+ #
117
+ # @return [Integer, nil] given any identifier, return the matching primary id
118
+ # or nil if no match
119
+ ####
120
+ def self.valid_id(ident, key: nil, raise_if_not_found: false, cnx: Windoo.cnx)
121
+ matched_summary =
122
+ if key
123
+ all(cnx: cnx).select { |summary| summary[key] == ident }.first
124
+ else
125
+ find_summary_for_ident(ident, cnx: cnx)
126
+ end
127
+
128
+ value = matched_summary ? matched_summary[primary_id_key] : nil
129
+
130
+ raise Windoo::NoSuchItemError, "No #{self} found for identifier '#{ident}'" if raise_if_not_found && value.nil?
131
+
132
+ value
133
+ end
134
+
135
+ ####
136
+ def self.find_summary_for_ident(ident, cnx: Windoo.cnx)
137
+ all(cnx: cnx).each do |summary|
138
+ ident_keys.each do |key|
139
+ return summary if summary[key] == ident
140
+ end
141
+ end
142
+ nil
143
+ end
144
+ private_class_method :find_summary_for_ident
145
+
146
+ # Get the 'autofill patches' for a given software title
147
+ #
148
+ # @param ident [String, Integer] An identifier for a software title
149
+ #
150
+ # @return [Array<Hash>] the autofill patch data
151
+ ####
152
+ def self.autofill_patches(ident, cnx: Windoo.cnx)
153
+ id = valid_id ident, raise_if_not_found: true, cnx: cnx
154
+
155
+ cnx.get("#{self::RSRC_PATH}/#{id}/patches/autofill")
156
+ end
157
+
158
+ # Get the 'autofill requirements' for a given software title
159
+ #
160
+ # @param ident [String, Integer] An identifier for a software title
161
+ #
162
+ # @return [Array<Hash>] the autofill requirement data
163
+ ####
164
+ def self.autofill_requirements(ident, cnx: Windoo.cnx)
165
+ id = valid_id ident, raise_if_not_found: true, cnx: cnx
166
+
167
+ cnx.get("#{self::RSRC_PATH}/#{id}/requirements/autofill")
168
+ end
169
+
170
+ # Attributes
171
+ ######################
172
+
173
+ # Attributes not defined in the superclasses
174
+
175
+ JSON_ATTRIBUTES = {
176
+
177
+ # @!attribute softwareTitleId
178
+ # @return [Integer] The id of this title in the Title Editor
179
+ softwareTitleId: {
180
+ class: :Integer,
181
+ # primary means this is the one used to fetch via API calls
182
+ identifier: :primary,
183
+ readonly: true,
184
+ do_not_send: true
185
+ },
186
+
187
+ # @!attribute id
188
+ # @return [String] A string, unique any patch source (in this case
189
+ # the TitleEditor), that identifies this Software Title.
190
+ # Can be thought of as the unique name on the Title Editor.
191
+ # Not to be confused with the 'name' attribute, which is more
192
+ # of a Display Name, and is not unique
193
+ id: {
194
+ class: :String,
195
+ # true means this is a unique value in and can be used to find a valid
196
+ # primary identifier.
197
+ identifier: true,
198
+ # required means this value is required to create or update this
199
+ # object on the server(s)
200
+ required: true
201
+ },
202
+
203
+ # @!attribute enabled
204
+ # @return [Boolean] Is this title enabled, and available to be subscribed to?
205
+ enabled: {
206
+ class: :Boolean
207
+ },
208
+
209
+ # @!attribute name
210
+ # @return [String] The name of this title in the Title Editor. NOT UNIQUE,
211
+ # and not an identfier. See 'id'.
212
+ name: {
213
+ class: :String,
214
+ required: true
215
+ },
216
+
217
+ # @!attribute publisher
218
+ # @return [String] The publisher of this software
219
+ publisher: {
220
+ class: :String,
221
+ required: true
222
+ },
223
+
224
+ # @attribute appName
225
+ # @return [String] Currently not used by the Title Editor
226
+ # or Jamf Pro Patch Titles.
227
+ # There is no matching data in the Web UI.
228
+ # These data exist in the killApps associated with Patches
229
+ appName: {
230
+ class: :String
231
+ },
232
+
233
+ # @attribute bundleId
234
+ # @return [String] Currently not used by the Title Editor
235
+ # or Jamf Pro Patch Titles.
236
+ # There is no matching data in the Web UI.
237
+ # These data exist in the killApps associated with Patches
238
+ bundleId: {
239
+ class: :String
240
+ },
241
+
242
+ # @!attribute lastModified
243
+ # @return [Time] When was the title last modified, in UTC?
244
+ # @note This timestamp is only valid as of the last time
245
+ # you fetched this SoftwareTitle or updated one of its
246
+ # immediate attributes (i.e. not arrays of other API
247
+ # objects like requirements or patches.)
248
+ # To be sure of the most recent time, accounting for
249
+ # potential updates from other places (like the Web UI)
250
+ # you should re-fetch the Title
251
+ lastModified: {
252
+ class: :Time,
253
+
254
+ # for classes (like Time) that are not Symbols (like :String)
255
+ # This is the Class method to call on them to convert the
256
+ # raw API data into the ruby value we want. The API data
257
+ # will be passed as the sole param to this method.
258
+ # For most, it will be :new, but for, e.g., Time, it is
259
+ # :parse
260
+ to_ruby: :to_time,
261
+
262
+ # The method to call on the value when converting to
263
+ # data to be sent to the API.
264
+ # e.g. on Time values, convert to iso8601
265
+ # to_api: :iso8601
266
+
267
+ # attributes with this set to true are never
268
+ # sent to the server when creating or updating
269
+ do_not_send: true,
270
+ readonly: true
271
+ },
272
+
273
+ # @!attribute currentVersion
274
+ # @return [String] the version number of the most recent patch
275
+ currentVersion: {
276
+ class: :String,
277
+ required: true
278
+ },
279
+
280
+ # This value only appears in the .all summary hash, not in the
281
+ # full instance init_data.
282
+ #
283
+ # _!attribute source
284
+ # _return [String] The name of the Patch Source that ultimately
285
+ # hosts this title definition. If hosted by our TitleEditor
286
+ # directly, this is LOCAL_TITLE_EDITOR_SOURCE_NAME
287
+ #
288
+ # @todo implement external patches.
289
+ # source: {
290
+ # class: :String
291
+ # },
292
+
293
+ # @!attribute sourceId
294
+ # @return [Integer] The id of the Patch Source that ultimately
295
+ # hosts this title definition. If hosted by our TitleEditor
296
+ # directly, this is LOCAL_TITLE_EDITOR_SOURCE_ID
297
+ #
298
+ # @todo implement external patches.
299
+ # sourceId: {
300
+ # class: :Integer,
301
+ # do_not_send: true
302
+ # },
303
+
304
+ # @!attribute requirements
305
+ # @return [Array<Windoo::Requirement>] The requirements - criteria that
306
+ # define which computers have the software installed.
307
+ requirements: {
308
+ class: Windoo::RequirementManager,
309
+ do_not_send: true,
310
+ readonly: true
311
+ },
312
+
313
+ # @!attribute patches
314
+ # @return [Array<Windoo::Patch>] The patches available for this title
315
+ patches: {
316
+ class: Windoo::PatchManager,
317
+ do_not_send: true,
318
+ readonly: true
319
+ },
320
+
321
+ # @!attribute extensionAttributes
322
+ # @return [Windoo::ExtensionAttribute] The Extension Attribute used by this title.
323
+ # NOTE: See the module Windoo::Mixins::SoftwareTitle::ExtentionAttribute
324
+ extensionAttribute: {
325
+ class: Windoo::ExtensionAttribute,
326
+ do_not_send: true,
327
+ readonly: true
328
+ }
329
+ }.freeze
330
+
331
+ # Construcor
332
+ ######################
333
+ def initialize(**init_data)
334
+ super
335
+
336
+ @requirements = Windoo::RequirementManager.new @requirements, container: self
337
+ @patches = Windoo::PatchManager.new @patches, container: self
338
+ end
339
+
340
+ # Public Instance Methods
341
+ ###################################
342
+
343
+ # Get the 'autofill patches' for this software title
344
+ # @return [Array<Hash>] the autofill patch data
345
+ def autofill_patches
346
+ id = send self.class.primary_id_key
347
+ self.class.autofill_patches id
348
+ end
349
+
350
+ # Get the 'autofill requirements' for this software title
351
+ # @return [Array<Hash>] the autofill requirement data
352
+ def autofill_requirements
353
+ id = send self.class.primary_id_key
354
+ self.class.autofill_requirements id
355
+ end
356
+
357
+ # Enable this SoftwareTitle
358
+ def enable
359
+ return if enabled?
360
+
361
+ if requirements.empty? || patches.all_enabled.empty?
362
+ raise Windoo::MissingDataError,
363
+ 'SoftwareTitles must have at least one requirement and one enabled patch before they can be enabled'
364
+ end
365
+
366
+ self.enabled = true
367
+ :enabled
368
+ end
369
+
370
+ # Disable this SoftwareTitle
371
+ def disable
372
+ return unless enabled?
373
+
374
+ self.enabled = false
375
+ :disabled
376
+ end
377
+
378
+ # Update our modification timestamp from other objects
379
+ # to the current time when they are changed on the server.
380
+ def update_modification_time
381
+ @lastModified = Time.now.utc
382
+ end
383
+
384
+ # Private Instance Methods
385
+ ##########################################
386
+ private
387
+
388
+ # See the section 'REQUIRED ITEMS WHEN MIXING IN'
389
+ # in the APICollection mixin.
390
+ def handle_create_response(post_response, container_id: nil)
391
+ @softwareTitleId = post_response[:softwareTitleId]
392
+
393
+ @sourceId = post_response[:sourceId]
394
+ @enabled = post_response[:enabled]
395
+
396
+ @softwareTitleId
397
+ end
398
+
399
+ # See the section 'REQUIRED ITEMS WHEN MIXING IN'
400
+ # in the APICollection mixin.
401
+ def handle_update_response(_put_response)
402
+ @softwareTitleId
403
+ end
404
+
405
+ end # class SoftwareTitle
406
+
407
+ end # Module Windoo