xolo-admin 1.0.0

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.
@@ -0,0 +1,117 @@
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
+ # main module
11
+ module Xolo
12
+
13
+ module Admin
14
+
15
+ # Storage and access to a history of progress-data from
16
+ # long-running Xolo server processes.
17
+ #
18
+ # E.g.. when you run 'xadm delete-title' you'll get a live
19
+ # progress updates of everything happening. That log is stored for
20
+ # a while (3 days) on the server.
21
+ #
22
+ # Later, esp if you used --quiet and didn't see the progress or any
23
+ # errors initially
24
+ # you'll be able to re-view the progress log, if it still exists
25
+ # on the server
26
+ #
27
+ module ProgressHistory
28
+
29
+ # Constants
30
+ ##########################
31
+ ##########################
32
+
33
+ APP_SUPPORT_DIR = '~/Library/Application Support/xadm/'
34
+ PROGRESS_HISTORY_FILENAME = 'com.pixar.xolo.admin.progress_history.yaml'
35
+
36
+ # prog files on the server last 3 days, add an extra to
37
+ # account for timing of daily cleanup on the server.
38
+ # if the file is already gone from the server, we'll tell
39
+ # the user
40
+ PROGRESS_FILE_LIFETIME = 4 * 24 * 3600
41
+
42
+ # Module Methods
43
+ ##########################
44
+ ##########################
45
+
46
+ # when this module is included
47
+ def self.included(includer)
48
+ Xolo.verbose_include includer, self
49
+ end
50
+
51
+ # when this module is extended
52
+ def self.extended(extender)
53
+ Xolo.verbose_extend extender, self
54
+ end
55
+
56
+ # Instance Methods
57
+ ##########################
58
+ ##########################
59
+
60
+ # @return [Pathname] The expanded path tot he prog. history dir for this user
61
+ ###########################
62
+ def app_support_dir
63
+ return @app_support_dir if @app_support_dir
64
+
65
+ @app_support_dir = Pathname.new(APP_SUPPORT_DIR).expand_path
66
+ @app_support_dir.mkpath
67
+ @app_support_dir
68
+ end
69
+
70
+ # @return [Pathname] The prog. history file for this user
71
+ ###########################
72
+ def progress_history_file
73
+ @progress_history_file ||= (app_support_dir + PROGRESS_HISTORY_FILENAME)
74
+ end
75
+
76
+ # The current history of progress streams for this user
77
+ # keys are Time objects when the entry was created
78
+ # values are sub-hashes with :command, and :url keys
79
+ # the command being the xadm command, e.g. 'delete-version'
80
+ # and the URL being the url to the progress stream file on the server.
81
+ #
82
+ # before returning the hash, any expired entries are removed
83
+ #
84
+ # @return [Hash] the current progress history for this user
85
+ ###################
86
+ def progress_history
87
+ progress_history_file.pix_touch
88
+ history = YAML.load progress_history_file.read
89
+ history ||= {}
90
+
91
+ now = Time.now
92
+ history.delete_if { |k, _v| now - k > PROGRESS_FILE_LIFETIME }
93
+
94
+ history
95
+ end
96
+
97
+ # Add an entry to the progress history
98
+ #
99
+ # @prarm url_path [String] the xolo server path to the progress file.
100
+ #
101
+ # @return [void]
102
+ ######################
103
+ def add_progress_history_entry(url_path)
104
+ history = progress_history
105
+ history[Time.now] = {
106
+ command: cli_cmd.command,
107
+ url_path: url_path
108
+ }
109
+
110
+ progress_history_file.pix_atomic_write YAML.dump(history)
111
+ end
112
+
113
+ end # module
114
+
115
+ end # module Admin
116
+
117
+ end # module Xolo
@@ -0,0 +1,285 @@
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
+ # main module
11
+ module Xolo
12
+
13
+ module Admin
14
+
15
+ # A title used by xadm.
16
+ #
17
+ # These are instantiated with data from the server
18
+ # (for existing Titles) or from the xadm CLI opts
19
+ # or walkthru process.
20
+ #
21
+ # This class also defines how xadm communicates
22
+ # title data to and from the server.
23
+ class Title < Xolo::Core::BaseClasses::Title
24
+
25
+ # Constants
26
+ #############################
27
+ #############################
28
+
29
+ # This is the server path for dealing with titles
30
+ # POST to add a new one
31
+ # GET to get a list of titles
32
+ # GET .../<title> to get the data for a single title
33
+ # PUT .../<title> to update a title with new data
34
+ # DELETE .../<title> to delete a title and its version
35
+ SERVER_ROUTE = '/titles'
36
+
37
+ UPLOAD_ICON_ROUTE = 'ssvc-icon'
38
+
39
+ TARGET_TITLE_PLACEHOLDER = 'TARGET_TITLE_PH'
40
+
41
+ # Class Methods
42
+ #############################
43
+ #############################
44
+
45
+ # @return [Hash{Symbol: Hash}] The ATTRIBUTES that are available as CLI & walkthru options
46
+ def self.cli_opts
47
+ @cli_opts ||= ATTRIBUTES.select { |_k, v| v[:cli] }
48
+ end
49
+
50
+ # @return [Array<String>] The currently known titles names on the server
51
+ #############################
52
+ def self.all_titles(cnx)
53
+ resp = cnx.get SERVER_ROUTE
54
+ resp.body.map { |t| t[:title] }
55
+ end
56
+
57
+ # @return [Array<Xolo::Admin::Title>] The currently known titles on the server
58
+ #############################
59
+ def self.all_title_objects(cnx)
60
+ resp = cnx.get SERVER_ROUTE
61
+ resp.body.map { |td| Xolo::Admin::Title.new td }
62
+ end
63
+
64
+ # Does a title exist on the server?
65
+ # @param title [String] the title
66
+ # @param cnx [Faraday::Connection] The connection to use, must be logged in already
67
+ # @return [Boolean]
68
+ #############################
69
+ def self.exist?(title, cnx)
70
+ all_titles(cnx).include? title
71
+ end
72
+
73
+ # Fetch a title from the server
74
+ # @param title [String] the title to fetch
75
+ # @param cnx [Faraday::Connection] The connection to use, must be logged in already
76
+ # @return [Xolo::Admin::Title]
77
+ ####################
78
+ def self.fetch(title, cnx)
79
+ resp = cnx.get "#{SERVER_ROUTE}/#{title}"
80
+
81
+ new resp.body
82
+ end
83
+
84
+ # Delete a title from the server
85
+ # @param title [String] the title to delete
86
+ # @param cnx [Faraday::Connection] The connection to use, must be logged in already
87
+ # @return [Hash] the response data
88
+ ####################
89
+ def self.delete(title, cnx)
90
+ resp = cnx.delete "#{SERVER_ROUTE}/#{title}"
91
+ resp.body
92
+ end
93
+
94
+ # the latest version of a title in Xolo
95
+ # @param title [String] the title we care about
96
+ # @param cnx [Faraday::Connection] The connection to use, must be logged in already
97
+ # @return [void]
98
+ ####################
99
+ def self.latest_version(title, cnx)
100
+ resp = cnx.get "#{SERVER_ROUTE}/#{title}"
101
+ resp.body[:version_order].first
102
+ end
103
+
104
+ # Is the current admin allowed to set a title's release groups to 'all'?
105
+ # @param cnx [Faraday::Connection] The connection to use, must be logged in already
106
+ # @return [Boolean]
107
+ ####################
108
+ def self.release_to_all_allowed?(cnx)
109
+ resp = cnx.get '/auth/release_to_all_allowed'
110
+ resp.body
111
+ end
112
+
113
+ # Attributes
114
+ ######################
115
+ ######################
116
+
117
+ # Constructor
118
+ ######################
119
+ ######################
120
+
121
+ # Read in the contents of any version script given
122
+ def initialize(data_hash)
123
+ super
124
+ # @self_service_icon = nil if @self_service_icon == Xolo::ITEM_UPLOADED
125
+
126
+ return unless version_script
127
+ return if version_script == Xolo::ITEM_UPLOADED
128
+
129
+ @version_script = version_script.read if version_script.respond_to?(:read)
130
+ end
131
+
132
+ # Instance Methods
133
+ #############################
134
+ #############################
135
+
136
+ # Add this title to the server
137
+ # @param cnx [Faraday::Connection] The connection to use, must be logged in already
138
+ # @return [Hash] the response body from the server
139
+ ####################
140
+ def add(cnx)
141
+ resp = cnx.post SERVER_ROUTE, to_h
142
+ resp.body
143
+ end
144
+
145
+ # Update this title to the server
146
+ # @param cnx [Faraday::Connection] The connection to use, must be logged in already
147
+ # @return [Hash] the response body from the server
148
+ ####################
149
+ def update(cnx)
150
+ resp = cnx.put "#{SERVER_ROUTE}/#{title}", to_h
151
+ resp.body
152
+ end
153
+
154
+ # Release a version of this title.
155
+ # @param cnx [Faraday::Connection] The connection to use, must be logged in already
156
+ # @param version [String] the version to release
157
+ # @return [Hash] the response body from the server
158
+ ####################
159
+ def release(cnx, version:)
160
+ resp = cnx.patch "#{SERVER_ROUTE}/#{title}/release/#{version}", {}
161
+ resp.body
162
+ end
163
+
164
+ # Repair this title, and optionally all its versions
165
+ # @param cnx [Faraday::Connection] The connection to use, must be logged in already
166
+ # @param versions [Boolean] if true, repair all versions of this title
167
+ # @return [Hash] the response body from the server
168
+ ####################
169
+ def repair(cnx, versions: false)
170
+ resp = cnx.post "#{SERVER_ROUTE}/#{title}/repair", { repair_versions: versions }
171
+ resp.body
172
+ end
173
+
174
+ # Delete this title from the server
175
+ # @param cnx [Faraday::Connection] The connection to use, must be logged in already
176
+ # @return [Hash] the response data
177
+ ####################
178
+ def delete(cnx)
179
+ self.class.delete title, cnx
180
+ end
181
+
182
+ # Freeze the one or more computers for this title
183
+ # @param computers [Array<String>] the computers to freeze
184
+ # @param cnx [Faraday::Connection] The connection to use, must be logged in already
185
+ # @return [Hash] the response data
186
+ ####################
187
+ def freeze(targets, users = false, cnx)
188
+ data = { targets: targets, users: users }
189
+ resp = cnx.put "#{SERVER_ROUTE}/#{title}/freeze", data
190
+ resp.body
191
+ end
192
+
193
+ # Thaw the one or more computers for this title
194
+ # @param computers [Array<String>] the computers to freeze
195
+ # @param cnx [Faraday::Connection] The connection to use, must be logged in already
196
+ # @return [Hash] the response data
197
+ ####################
198
+ def thaw(targets, users = false, cnx)
199
+ data = { targets: targets, users: users }
200
+ resp = cnx.put "#{SERVER_ROUTE}/#{title}/thaw", data
201
+ resp.body
202
+ end
203
+
204
+ # Fetch the frozen computers for this title
205
+ # @param cnx [Faraday::Connection] The connection to use, must be logged in already
206
+ # @return [Hash{String => String}] computer name => user name
207
+ ####################
208
+ def frozen(cnx)
209
+ resp = cnx.get "#{SERVER_ROUTE}/#{title}/frozen"
210
+ resp.body
211
+ end
212
+
213
+ # Fetch a hash of URLs for the GUI pages for this title
214
+ # @param cnx [Faraday::Connection] The connection to use, must be logged in already
215
+ # @return [Hash{String => String}] page_name => url
216
+ ####################
217
+ def gui_urls(cnx)
218
+ resp = cnx.get "#{SERVER_ROUTE}/#{title}/urls"
219
+ resp.body
220
+ end
221
+
222
+ # The change log is a list of hashes, each with keys:
223
+ # :time, :admin, :ipaddr, :version (may be nil), :action
224
+ #
225
+ # @param cnx [Faraday::Connection] The connection to use, must be logged in already
226
+ # @return [Array<Hash>] The change log for this title
227
+ ####################
228
+ def changelog(cnx)
229
+ resp = cnx.get "#{SERVER_ROUTE}/#{title}/changelog"
230
+ resp.body
231
+ end
232
+
233
+ # Upload an icon for self service.
234
+ # At this point, the self_service_icon attribute
235
+ # should contain the local file path.
236
+ #
237
+ # @param upload_cnx [Xolo::Admin::Connection] The server connection
238
+ #
239
+ # @return [Faraday::Response] The server response
240
+ ##################################
241
+ def upload_self_service_icon(upload_cnx)
242
+ return unless self_service_icon.is_a? Pathname
243
+
244
+ unless self_service_icon.readable?
245
+ raise Xolo::NoSuchItemError,
246
+ "Can't upload self service icon '#{self_service_icon}': file doesn't exist or is not readable"
247
+ end
248
+
249
+ # upfile = Faraday::UploadIO.new(
250
+ # self_service_icon.to_s,
251
+ # 'application/octet-stream',
252
+ # self_service_icon.basename.to_s
253
+ # )
254
+
255
+ mimetype = `/usr/bin/file --brief --mime-type #{Shellwords.escape self_service_icon.expand_path.to_s}`.chomp
256
+ upfile = Faraday::Multipart::FilePart.new(self_service_icon.expand_path.to_s, mimetype)
257
+ content = { file: upfile }
258
+ # route = "#{UPLOAD_ICON_ROUTE}/#{title}"
259
+ route = "#{SERVER_ROUTE}/#{title}/#{UPLOAD_ICON_ROUTE}"
260
+
261
+ upload_cnx.post(route) { |req| req.body = content }
262
+ end
263
+
264
+ # Get the patch report data for this title
265
+ # It's the JPAPI report data with each hash having a frozen: key added
266
+ #
267
+ # @param cnx [Faraday::Connection] The connection to use, must be logged in already
268
+ # @return [Array<Hash>] Data for each computer with any version of this title installed
269
+ ##################################
270
+ def patch_report_data(cnx)
271
+ resp = cnx.get "#{SERVER_ROUTE}/#{title}/patch_report"
272
+ resp.body
273
+ end
274
+
275
+ # Add more data to our hash
276
+ ###########################
277
+ def to_h
278
+ super
279
+ end
280
+
281
+ end # class Title
282
+
283
+ end # module Admin
284
+
285
+ end # module Xolo
@@ -0,0 +1,57 @@
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
+ # main module
11
+ module Xolo
12
+
13
+ module Admin
14
+
15
+ # Methods that process the xadm commands and their options
16
+ #
17
+ module TitleEditor
18
+
19
+ # Constants
20
+ ##########################
21
+ ##########################
22
+
23
+ TITLE_EDITOR_ROUTE_BASE = '/title-editor'
24
+
25
+ # Xolo server route to the list of titles
26
+ TITLES_ROUTE = "#{TITLE_EDITOR_ROUTE_BASE}/titles"
27
+
28
+ # Module Methods
29
+ ##########################
30
+ ##########################
31
+
32
+ # when this module is included
33
+ def self.included(includer)
34
+ Xolo.verbose_include includer, self
35
+ end
36
+
37
+ # when this module is extended
38
+ def self.extended(extender)
39
+ Xolo.verbose_extend extender, self
40
+ end
41
+
42
+ # Instance Methods
43
+ ##########################
44
+ ##########################
45
+
46
+ # Perhaps not needed for anything, but used for initial connection testing
47
+ # @return [Array<String>] the titles of all Title objects in the Title Editor
48
+ #######################
49
+ def ted_titles
50
+ server_cnx.get(TITLES_ROUTE).body
51
+ end
52
+
53
+ end # module
54
+
55
+ end # module Admin
56
+
57
+ end # module Xolo