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,221 @@
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
+
8
+ require 'singleton'
9
+
10
+ module Windoo
11
+
12
+ # A class for working with pre-defined settings & preferences for Windoo
13
+ #
14
+ # This is a singleton class, only one instance can exist at a time.
15
+ #
16
+ # When the module loads, that instance is created, and is used to provide default
17
+ # values throughout Windoo. It can be accessed via Windoo.config in applications.
18
+ #
19
+ # @note Many values in Windoo will also have a hard-coded default, if not defined
20
+ # in the configuration.
21
+ #
22
+ # When the Windoo::Configuration instance is created, the {GLOBAL_CONF} file (/etc/windoo.conf)
23
+ # is examined if it exists, and the items in it are loaded into the attributes.
24
+ #
25
+ # Then the user-specific {USER_CONF} file (~/.windoo.conf) is examined if it exists, and
26
+ # any attributes defined there will override those values from the {GLOBAL_CONF}.
27
+ #
28
+ # The file format is one attribute per line, thus:
29
+ # attr_name: value
30
+ #
31
+ # Lines that don't start with a known attribute name followed by a colon are ignored.
32
+ # If an attribute is defined more than once, the last one wins.
33
+ #
34
+ # See {CONF_KEYS} for the available attributes, and how they are converted to the appropriate
35
+ # Ruby class when loaded.
36
+ #
37
+ # At any point, the attributes can read or changed using standard Ruby getter/setter methods
38
+ # matching the name of the attribute,
39
+ # e.g.
40
+ #
41
+ # # read the current title_editor_server_name configuration value
42
+ # Windoo.config.title_editor_server_name # => 'foobar.appcatalog.jamfcloud.com'
43
+ #
44
+ # # sets the title_editor_server_name to a new value
45
+ # Windoo.config.title_editor_server_name = 'baz.appcatalog.jamfcloud.com'
46
+ #
47
+ #
48
+ # The current settings may be saved to the GLOBAL_CONF file, the USER_CONF file, or an arbitrary
49
+ # file using {#save}. The argument to {#save} should be either :user, :global, or a String or
50
+ # Pathname file path.
51
+ # NOTE: This overwrites any existing file with the current values of the Configuration object.
52
+ #
53
+ # To re-load the configuration use {#reload}. This clears the current settings, and re-reads
54
+ # both the global and user files. If a pathname is provided, e.g.
55
+ # Windoo.config.reload '/path/to/other/file'
56
+ # the current settings are cleared and reloaded from that other file.
57
+ #
58
+ # To view the current settings, use {#print}.
59
+ #
60
+ class Configuration
61
+
62
+ include Singleton
63
+
64
+ # Class Constants
65
+ #####################################
66
+
67
+ # The filename for storing the config, globally or user-level.
68
+ # The first matching file is used - the array provides
69
+ # backward compatibility with earlier versions.
70
+ # Saving will always happen to the first filename
71
+ CONF_FILENAME = 'windoo.conf'
72
+
73
+ # The Pathname to the machine-wide preferences plist
74
+ GLOBAL_CONF = Pathname.new "/etc/#{CONF_FILENAME}"
75
+
76
+ # The Pathname to the user-specific preferences plist
77
+ USER_CONF = Pathname.new("~/.#{CONF_FILENAME}").expand_path
78
+
79
+ # The attribute keys we maintain, and the type they should be stored as
80
+ CONF_KEYS = {
81
+ title_editor_server_name: :to_s,
82
+ title_editor_server_port: :to_i,
83
+ title_editor_ssl_version: :to_s,
84
+ title_editor_verify_cert: :to_bool,
85
+ title_editor_username: :to_s,
86
+ title_editor_open_timeout: :to_i,
87
+ title_editor_timeout: :to_i
88
+ }
89
+
90
+ # Attributes
91
+ #####################################
92
+
93
+ # automatically create accessors for all the CONF_KEYS
94
+ CONF_KEYS.keys.each { |k| attr_accessor k }
95
+
96
+ # Constructor
97
+ #####################################
98
+
99
+ # Initialize!
100
+ #
101
+ def initialize
102
+ read GLOBAL_CONF
103
+ read USER_CONF
104
+ end
105
+
106
+ # Public Instance Methods
107
+ #####################################
108
+
109
+ # Clear all values
110
+ #
111
+ # @return [void]
112
+ #
113
+ def clear_all
114
+ CONF_KEYS.keys.each { |k| send "#{k}=", nil }
115
+ end
116
+
117
+ # Clear the settings and reload the prefs files, or another file if provided
118
+ #
119
+ # @param file[String,Pathname] a non-standard prefs file to load
120
+ #
121
+ # @return [void]
122
+ #
123
+ def reload(file = nil)
124
+ clear_all
125
+ if file
126
+ read file
127
+ return true
128
+ end
129
+ read GLOBAL_CONF
130
+ read USER_CONF
131
+ true
132
+ end
133
+
134
+ # Save the prefs into a file
135
+ #
136
+ # @param file[Symbol,String,Pathname] either :user, :global, or an arbitrary file to save.
137
+ #
138
+ # @return [void]
139
+ #
140
+ def save(file)
141
+ path =
142
+ case file
143
+ when :global then GLOBAL_CONF
144
+ when :user then USER_CONF
145
+ else Pathname.new(file)
146
+ end
147
+
148
+ # file already exists? read it in and update the values.
149
+ if path.readable?
150
+ data = path.read
151
+
152
+ # go thru the known attributes/keys
153
+ CONF_KEYS.keys.sort.each do |k|
154
+ curr_val = send(k)
155
+
156
+ # if the key exists, update it.
157
+ if data =~ /^\s*#{k}:/
158
+ data.sub!(/^\s*#{k}:.*$/, "#{k}: #{curr_val}")
159
+
160
+ # if not, add it to the end unless it's nil
161
+ else
162
+ data += "\n#{k}: #{curr_val}" unless curr_val.nil?
163
+ end # if data =~ /^#{k}:/
164
+ end # each do |k|
165
+
166
+ else # not readable, make a new file
167
+ data = ''
168
+ CONF_KEYS.keys.sort.each do |k|
169
+ data << "#{k}: #{send k}\n" unless send(k).nil?
170
+ end
171
+ end # if path readable
172
+
173
+ # make sure we end with a newline, the save it.
174
+ data << "\n" unless data.end_with?("\n")
175
+ path.x_save data
176
+ end # read file
177
+
178
+ # Print out the current settings to stdout
179
+ #
180
+ # @return [void]
181
+ #
182
+ def print
183
+ CONF_KEYS.keys.sort.each { |k| puts "#{k}: #{send k}" }
184
+ end
185
+
186
+ # Private Instance Methods
187
+ #####################################
188
+ private
189
+
190
+ # Read in any prefs file
191
+ #
192
+ # @param file[String,Pathname] the file to read
193
+ #
194
+ # @return [void]
195
+ #
196
+ def read(file)
197
+ file = Pathname.new file
198
+ return unless file.readable?
199
+
200
+ file.read.each_line do |line|
201
+ # skip blank lines and those starting with #
202
+ next if line =~ /^\s*(#|$)/
203
+
204
+ # parse the line
205
+ next unless line.strip =~ /^\s*(\w+?):\s*(\S.*)$/
206
+
207
+ attr = Regexp.last_match(1).to_sym
208
+ next unless CONF_KEYS.key? attr
209
+
210
+ setter = "#{attr}=".to_sym
211
+ value = Regexp.last_match(2).strip
212
+ # convert the value to the correct class
213
+ value &&= value.send(CONF_KEYS[attr])
214
+
215
+ send(setter, value)
216
+ end # do line
217
+ end # read file
218
+
219
+ end # class Configuration
220
+
221
+ end # module Windoo
@@ -0,0 +1,152 @@
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
+
8
+ # frozen_string_literal: true
9
+
10
+ module Windoo
11
+
12
+ class Connection
13
+
14
+ # This module defines methods used for interacting with the TitleEditor API.
15
+ # This includes sending HTTP requests and handling responses
16
+ module Actions
17
+
18
+ def self.included(includer)
19
+ Windoo.verbose_include(includer, self)
20
+ end
21
+
22
+ # @param rsrc[String] the resource path to get
23
+ #
24
+ # @return [Object] The parsed JSON from the result of the request
25
+ #
26
+ def get(rsrc)
27
+ validate_connected
28
+
29
+ resp = cnx.get(rsrc)
30
+ @last_http_response = resp
31
+ return resp.body if resp.success?
32
+
33
+ handle_http_error resp
34
+ end # get
35
+
36
+ # Create a new API resource via POST
37
+ #
38
+ # @param rsrc[String] the API resource being created, the URL part after 'JSSResource/'
39
+ #
40
+ # @param content[String] the content specifying the new object.
41
+ #
42
+ # @return [Object] The parsed JSON from the result of the request
43
+ #
44
+ def post(rsrc, content)
45
+ validate_connected
46
+
47
+ # send the data
48
+ resp = cnx.post(rsrc) { |req| req.body = content }
49
+ @last_http_response = resp
50
+ return resp.body if resp.success?
51
+
52
+ handle_http_error resp
53
+ end # post
54
+
55
+ # Update an existing API resource via PUT
56
+ #
57
+ # @param rsrc[String] the API resource being changed, the URL part after 'JSSResource/'
58
+ #
59
+ # @param content[String] the content specifying the changes.
60
+ #
61
+ # @return [Object] The parsed JSON from the result of the request
62
+ #
63
+ def put(rsrc, content)
64
+ validate_connected
65
+
66
+ # send the data
67
+ resp = cnx.put(rsrc) { |req| req.body = content }
68
+ @last_http_response = resp
69
+ return resp.body if resp.success?
70
+
71
+ handle_http_error resp
72
+ end # put
73
+
74
+ # Delete a resource from the API
75
+ #
76
+ # @param rsrc[String] the resource to delete
77
+ #
78
+ # @return [Object] The parsed JSON from the result of the request
79
+ #
80
+ def delete(rsrc)
81
+ validate_connected
82
+
83
+ # send the data
84
+ resp = cnx.delete(rsrc)
85
+ @last_http_response = resp
86
+ return resp.body if resp.success?
87
+
88
+ handle_http_error resp
89
+ end # delete_rsrc
90
+
91
+ #############################
92
+ private
93
+
94
+ # Parses the given http response
95
+ # and raises a Jamf::APIError with a useful error message.
96
+ #
97
+ # @return [void]
98
+ #
99
+ def handle_http_error(resp)
100
+ return if resp.success?
101
+
102
+ case resp.status
103
+ when 404
104
+ err = Windoo::NoSuchItemError
105
+ msg = 'Not Found'
106
+
107
+ when 401
108
+ if resp.body[:errors].find { |e| e[:description] =~ /token not found/i }
109
+ err = Windoo::InvalidTokenError
110
+ msg = 'Connection Token is not valid.'
111
+
112
+ elsif resp.body[:errors].find { |e| e[:description] =~ /expired token/i }
113
+ err = Windoo::InvalidTokenError
114
+ msg = 'Connection Token has expired.'
115
+ else
116
+ err = Windoo::PermissionError
117
+ msg = 'You are not authorized to do that.'
118
+ end
119
+ when (500..599)
120
+ err = Windoo::ConnectionError
121
+ msg = 'There was an internal server error'
122
+
123
+ else
124
+ err = Windoo::ConnectionError
125
+ msg = "There was a error processing your request, status: #{resp.status}"
126
+
127
+ end # case
128
+ msg = "#{msg}\nStatus: #{resp.status}\nResponse Body:\n#{parse_http_error_body(resp)}"
129
+ raise err, msg
130
+ end
131
+
132
+ # get the body of the error response in a readable format
133
+ def parse_http_error_body(resp)
134
+ err_text = +''
135
+ resp.body[:errors].map do |err_hash|
136
+ err_text << "Code: #{err_hash[:code]}\n"
137
+ err_hash.each do |k, v|
138
+ next if k == :code
139
+
140
+ err_text << " #{k}: #{v}\n"
141
+ end
142
+ end
143
+ err_text.chomp
144
+ rescue StandardError
145
+ resp.body
146
+ end
147
+
148
+ end # module Actions
149
+
150
+ end # class Connection
151
+
152
+ end # module Windoo
@@ -0,0 +1,156 @@
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
+ # frozen_string_literal: true
7
+
8
+ module Windoo
9
+
10
+ class Connection
11
+
12
+ # This module defines general attributes of a connection object
13
+ #
14
+ # These attributes actually come from the token:
15
+ # base_url, host, port, user, keep_alive?, ssl_version,
16
+ # verify_cert?, pw_fallback
17
+ #
18
+ # There are convience getters defined for them below
19
+ #############################################################
20
+ module Attributes
21
+
22
+ def self.included(includer)
23
+ Windoo.verbose_include(includer, self)
24
+ end
25
+
26
+ # @return [String] the name of this connection, an arbitrary string
27
+ attr_reader :name
28
+
29
+ # @return [Integer] Seconds before an http request times out
30
+ attr_reader :timeout
31
+
32
+ # @return [Integer] Seconds before an http connection open times out
33
+ attr_reader :open_timeout
34
+
35
+ # @return [Windoo::Connection::Token] the token used for connecting
36
+ attr_reader :token
37
+
38
+ # @return [Faraday::Response] The response from the most recent API call
39
+ attr_reader :last_http_response
40
+
41
+ # @return [Time] when this connection was connected
42
+ attr_reader :connect_time
43
+ alias login_time connect_time
44
+
45
+ # @return [Faraday::Connection] The underlying connection object
46
+ attr_reader :cnx
47
+
48
+ # @return [Boolean] are we connected right now?
49
+ attr_reader :connected
50
+ alias connected? connected
51
+
52
+ # Set the name, but always note if we are the default connection
53
+ #
54
+ # Reset the response timeout for the rest connection
55
+ #
56
+ # @param timeout[Integer] the new timeout in seconds
57
+ #
58
+ # @return [void]
59
+ #
60
+ def name=(newname)
61
+ @name = default? ? "#{newname} (default)" : newname
62
+ end
63
+
64
+ # Reset the response timeout for the rest connection
65
+ #
66
+ # @param timeout[Integer] the new timeout in seconds
67
+ #
68
+ # @return [void]
69
+ #
70
+ def timeout=(new_timeout)
71
+ validate_connection
72
+ @timeout = new_timeout.to_i
73
+ @cnx.options[:timeout] = @timeout if @cnx
74
+ end
75
+
76
+ # Reset the open-connection timeout for the rest connection
77
+ #
78
+ # @param timeout[Integer] the new timeout in seconds
79
+ #
80
+ # @return [void]
81
+ #
82
+ def open_timeout=(new_timeout)
83
+ validate_connection
84
+ @open_timeout = new_timeout.to_i
85
+ @cnx.options[:open_timeout] = @open_timeout if @cnx
86
+ end
87
+
88
+ # @return [URI::HTTPS] the base URL to the server
89
+ def base_url
90
+ validate_connection
91
+ @token&.base_url
92
+ end
93
+
94
+ # @return [String] the hostname of the Jamf Pro server API connection
95
+ def host
96
+ validate_connection
97
+ @token&.host
98
+ end
99
+ alias server host
100
+ alias hostname host
101
+
102
+ # @return [Integer] The port of the Jamf Pro server API connection
103
+ def port
104
+ validate_connection
105
+ @token&.port
106
+ end
107
+
108
+ # @return [String] the username who's connected to the JSS API
109
+ def user
110
+ validate_connection
111
+ @token&.user
112
+ end
113
+
114
+ # @return [Boolean] Is the connection token being automatically refreshed?
115
+ def keep_alive?
116
+ validate_connection
117
+ @token&.keep_alive?
118
+ end
119
+
120
+ # @return [Boolean] If keep_alive is true, is the password Cached in memory
121
+ # to use if the refresh fails?
122
+ def pw_fallback?
123
+ validate_connection
124
+ @token&.pw_fallback?
125
+ end
126
+
127
+ # @return [String] SSL version used for the connection
128
+ def ssl_version
129
+ validate_connection
130
+ @token&.ssl_version
131
+ end
132
+
133
+ # @return [Boolean] Should the SSL certifcate from the server be verified?
134
+ def verify_cert?
135
+ validate_connection
136
+ @token&.verify_cert?
137
+ end
138
+ alias verify_cert verify_cert?
139
+
140
+ # @return [Hash] the ssl version and verify cert, to pass into faraday connections
141
+ def ssl_options
142
+ validate_connection
143
+ @token&.ssl_options
144
+ end
145
+
146
+ # raise an error if no token yet
147
+ # @return [void]
148
+ def validate_connection
149
+ raise Windoo::NotConnectedError, 'Not connected, use #connect first' unless connected?
150
+ end
151
+
152
+ end # module
153
+
154
+ end # class Connection
155
+
156
+ end # module Windoo