xolo-admin 1.0.0 → 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.
@@ -0,0 +1,52 @@
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
+ module Xolo
9
+
10
+ module Core
11
+
12
+ module Exceptions
13
+
14
+ # General errors
15
+
16
+ class MissingDataError < RuntimeError; end
17
+
18
+ class InvalidDataError < RuntimeError; end
19
+
20
+ class NoSuchItemError < RuntimeError; end
21
+
22
+ class UnsupportedError < RuntimeError; end
23
+
24
+ class KeychainError < RuntimeError; end
25
+
26
+ class ActionRequiredError < RuntimeError; end
27
+
28
+ # Connections & Access
29
+
30
+ class ConnectionError < RuntimeError; end
31
+
32
+ class NotConnectedError < ConnectionError; end
33
+
34
+ class TimeoutError < ConnectionError; end
35
+
36
+ class AuthenticationError < ConnectionError; end
37
+
38
+ class PermissionError < ConnectionError; end
39
+
40
+ class InvalidTokenError < ConnectionError; end
41
+
42
+ class ServerError < ConnectionError; end
43
+
44
+ # Parsing errors
45
+
46
+ class DisallowedYAMLDumpClass; end
47
+
48
+ end # module Exceptions
49
+
50
+ end # module Core
51
+
52
+ end # module Xolo
@@ -0,0 +1,43 @@
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 Core
14
+
15
+ # constants and methods for consistent JSON processing on the server
16
+ module JSONWrappers
17
+
18
+ # when this module is extended
19
+ def self.extended(extender)
20
+ Xolo.verbose_extend extender, self
21
+ end
22
+
23
+ # when this module is included
24
+ def self.included(includer)
25
+ Xolo.verbose_include includer, self
26
+ end
27
+
28
+ # A wrapper for JSON.parse that always uses :symbolize_names
29
+ # def self.parse_json(str)
30
+ # JSON.parse str, symbolize_names: true
31
+ # end
32
+
33
+ # A wrapper for JSON.parse that always uses :symbolize_names
34
+ # and ensures UTF-8 encoding
35
+ def parse_json(str)
36
+ JSON.parse str.force_encoding('UTF-8'), symbolize_names: true
37
+ end
38
+
39
+ end # JSON
40
+
41
+ end # Core
42
+
43
+ end # module Xolo
@@ -0,0 +1,59 @@
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 Xolo
10
+
11
+ module Core
12
+
13
+ module Loading
14
+
15
+ # touch this file to make mixins send text to stderr as things load
16
+ # or get mixed in
17
+ VERBOSE_LOADING_FILE = Pathname.new('/tmp/xolo-verbose-loading')
18
+
19
+ # Or, set this ENV var to also make mixins send text to stderr
20
+ VERBOSE_LOADING_ENV = 'XOLO_VERBOSE_LOADING'
21
+
22
+ def self.extended(extender)
23
+ Xolo.verbose_extend extender, self
24
+ end
25
+
26
+ # Only look at the filesystem once.
27
+ def verbose_loading?
28
+ return @verbose_loading unless @verbose_loading.nil?
29
+
30
+ @verbose_loading = VERBOSE_LOADING_FILE.file?
31
+ @verbose_loading ||= ENV.include? VERBOSE_LOADING_ENV
32
+ @verbose_loading
33
+ end
34
+
35
+ # Send a message to stderr if verbose loading is enabled
36
+ def load_msg(msg)
37
+ warn msg if verbose_loading?
38
+ end
39
+
40
+ # Mention that a module is being included into something
41
+ def verbose_include(includer, includee)
42
+ load_msg "--> #{includer} is including #{includee}"
43
+ end
44
+
45
+ # Mention that a module is being extended into something
46
+ def verbose_extend(extender, extendee)
47
+ load_msg "--> #{extender} is extending #{extendee}"
48
+ end
49
+
50
+ # Mention that a module is being extended into something
51
+ def verbose_inherit(child_class, parent_class)
52
+ load_msg "--> #{child_class} is a Subclass inheriting from #{parent_class}"
53
+ end
54
+
55
+ end # module
56
+
57
+ end # module
58
+
59
+ end # module Xolo
@@ -0,0 +1,292 @@
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
+ require 'io/console'
10
+
11
+ module Xolo
12
+
13
+ module Core
14
+
15
+ # Methods for formattng and sending output to stdout
16
+ # Should be included in classes as needed
17
+ #
18
+ # NOTE: Help output is auto-generated by 'optimist'
19
+ # The methods here are mostly for presenting info like
20
+ # columnizd lists and reports and the like.
21
+ module Output
22
+
23
+ # Constants
24
+ #############################
25
+ #############################
26
+
27
+ # This is used when we are not outputting to a terminal
28
+ # usually we're being piped or not running in a terminal
29
+ # so the lines should be as long as they want
30
+ DEFAULT_LINE_WIDTH = 2000
31
+
32
+ # Module methods
33
+ #
34
+ # These are available as module methods but not as 'helper'
35
+ # methods in sinatra routes & views.
36
+ #
37
+ ##############################
38
+ ##############################
39
+
40
+ # when this module is included
41
+ ##############################
42
+ def self.included(includer)
43
+ Xolo.verbose_include includer, self
44
+ end
45
+
46
+ # when this module is extended
47
+ def self.extended(extender)
48
+ Xolo.verbose_extend extender, self
49
+ end
50
+
51
+ # Instance methods
52
+ #
53
+ # These are available directly in sinatra routes and views
54
+ #
55
+ ##############################
56
+ ##############################
57
+
58
+ # @return [Integer] how many rows high is our terminal?
59
+ #########################
60
+ def terminal_height
61
+ IO.console.winsize.first
62
+ end
63
+
64
+ # @return [Integer] how many columns wide is our terminal?
65
+ #########################
66
+ def terminal_width
67
+ IO.console.winsize.last
68
+ end
69
+
70
+ # @return [Integer] how wide is our word wrap? terminal-width minus 5
71
+ #########################
72
+ def terminal_word_wrap
73
+ @terminal_word_wrap ||= terminal_width - 5
74
+ end
75
+
76
+ # format a multi-line value by prepending the desired indentation
77
+ # to all but the first line, which is expected to be indented in-place where
78
+ # its being used.
79
+ #
80
+ # @param value [String] the value to format
81
+ # @param indent [Integer] the number of spaces to indent all but the first line
82
+ # @return [String] the formatted value
83
+ #######################
84
+ def format_multiline_indent(value, indent:)
85
+ value = value.to_s
86
+ return value unless value.include? "\n"
87
+
88
+ lines = value.split("\n")
89
+ lines[1..-1].each { |line| line.prepend ' ' * indent }
90
+ lines.join("\n")
91
+ end
92
+
93
+ # TODO: Move this out of Xolo::Core
94
+ # Display a list of items in as many columns as possible
95
+ # based on terminal width, e.g. with 3 cols:
96
+ #
97
+ # a thing another thing third thing
98
+ # thing2 line2 thing third line 2
99
+ # line3 thing another one and yet a 3rd
100
+ # oh my line4 4 line ok? yes, this is it
101
+ #
102
+ # and if the list is longer than terminal height,
103
+ # pipe it through 'less'
104
+ #
105
+ # @param header [String] A string to display at the top prepended with a '#'
106
+ # and appended with a newline and a line of ######'s of the same length.
107
+ #
108
+ # @param list [Array<String>] the items to list
109
+ #
110
+ # @return [void]
111
+ ############################
112
+ def list_in_cols(header, list)
113
+ longest_list_item = list.map(&:size).max
114
+ use_columns = (longest_list_item + 5) < terminal_width
115
+
116
+ list_to_display = use_columns ? highline_cli.list(list, :columns_down) : list.join("\n")
117
+
118
+ output = +"# #{header}\n"
119
+ output << '#' * (terminal_width - 5)
120
+ output << "\n"
121
+ output << list_to_display
122
+
123
+ show_text output
124
+ end
125
+
126
+ # Generate a report of rowed/columned data, either fixed-width or tab-delimited.
127
+ #
128
+ # Title and header lines are pre-pended with '# ' for easier exclusion
129
+ # when using the report as input for some other program.
130
+ # If the :type is :fixed, so will the column header line.
131
+ # (however, for parsing this data, try using the --json option)
132
+ #
133
+ # @param lines [Array<Array>] the rows and columns of data
134
+ #
135
+ # @param type [Symbol] :fixed or :tab, defaults to :fixed
136
+ #
137
+ # @params title [String] a descriptive text or title, shown above the
138
+ # column headers. Every line is pre-pended with '# '.
139
+ # Only used on :fixed reports.
140
+ #
141
+ # @params header_row [Array<String>] the column headers. optional.
142
+ #
143
+ # @return [String] the formatted report.
144
+ #
145
+ ############################
146
+ def generate_report(lines, type: :fixed, header_row: [], title: nil)
147
+ return Xolo::BLANK if lines.pix_empty?
148
+
149
+ raise ArgumentError, 'The first argument must be an Array' unless lines.is_a?(Array)
150
+ raise ArgumentError, 'The header_row must be an Array' unless header_row.is_a? Array
151
+
152
+ # tab delim is easy
153
+ if type == :tab
154
+ report_tab = header_row.join("\t")
155
+ lines.each { |line| report_tab += "\n#{line.join("\t")}" }
156
+ return report_tab.strip
157
+ end # if :tab
158
+
159
+ # below here, fixed width
160
+
161
+ line_width, format_str = width_and_format(lines, header_row)
162
+
163
+ # title if given
164
+ report = title ? +"# #{title}\n" : +''
165
+
166
+ unless header_row.empty?
167
+ unless header_row.size == lines[0].size
168
+ raise ArgumentError,
169
+ "Header row must have #{lines[0].count} items"
170
+ end
171
+
172
+ # then the header line if given
173
+ report += format_str % header_row
174
+ # add a separator
175
+ report += '#' + ('-' * (line_width - 1)) + "\n"
176
+ end
177
+ # add the rows
178
+ lines.each { |line| report += format_str % line }
179
+
180
+ report
181
+ end # generate report
182
+
183
+ # Given an Array of Arrays representing rows and columns of data,
184
+ # figure out the appropriate line-width for the longest line
185
+ # and the printf format string to create the columns
186
+ #
187
+ #
188
+ # @param lines [Array<Array>] The rows and columns of data
189
+ #
190
+ # @param header_row [Array] An optional header row to include in the
191
+ # width calculation.
192
+ #
193
+ # @return [Array<Integer, String>] the line width and format string
194
+ #
195
+ ############################
196
+ def width_and_format(lines, header_row = [])
197
+ # below here, fixed width
198
+ format_str = +''
199
+ line_width = 0
200
+ header_row[0] = "# #{header_row[0]}"
201
+
202
+ col_widths(lines, header_row).each do |w|
203
+ # make sure there's a space between columns
204
+ col_width = w + 1
205
+
206
+ # add the column to the printf format
207
+ format_str += "%-#{col_width}s"
208
+ line_width += col_width
209
+ end
210
+ format_str += "\n"
211
+
212
+ # if needed, limit the total line width for the header the width of the terminal
213
+ max_width = $stdout.tty? ? terminal_word_wrap : DEFAULT_LINE_WIDTH
214
+ line_width = max_width if line_width > max_width
215
+
216
+ [line_width, format_str]
217
+ end
218
+
219
+ # Given an Array of Arrays representing rows and columns of data
220
+ # figure out the widest width of each column and return an array
221
+ # of integers representing those widths
222
+ #
223
+ # @param data [Array<Array>] The rows and columns of data
224
+ #
225
+ # @param header_row [Array] An optional header row to include in the
226
+ # width calculation.
227
+ #
228
+ # @return [Array<Integer>] the max widths of each column of data.
229
+ #
230
+ ############################
231
+ def col_widths(data, header_row = [])
232
+ widths = header_row.map { |c| c.to_s.length }
233
+ data.each do |row|
234
+ row.each_index do |col|
235
+ this_width = row[col].to_s.length
236
+ widths[col] = this_width if this_width > widths[col].to_i
237
+ end # do field
238
+ end # do line
239
+ widths
240
+ end
241
+
242
+ # Should a given string be displayed via /usr/bin/less?
243
+ # true if stdout is a tty AND the string is > (terminal height - 2)
244
+ # The - 2 accounts for the final newline and an extra line at the
245
+ # bottom of the terminal, for better visual results
246
+ #########################
247
+ def use_less?(text)
248
+ $stdout.tty? && text.lines.size > (terminal_height - 2)
249
+ end
250
+
251
+ # Send a string to the terminal, possibly piping it through 'less'
252
+ # if the number of lines is greater than the number of terminal lines
253
+ #
254
+ # @param text[String] the text to send to the terminal
255
+ #
256
+ # @param show_help[Boolean] should the text have a line at the top
257
+ # showing basic 'less' key commands.
258
+ #
259
+ # @result [void]
260
+ #
261
+ ############################
262
+ def show_text(text, show_help = true)
263
+ unless use_less?(text)
264
+ puts text
265
+ return
266
+ end
267
+
268
+ if show_help
269
+ help = "# -- Using /usr/bin/less: ' ' next, 'b' prev, 'q' exit, 'h' help --"
270
+ text = "#{help}\n#{text}"
271
+ end
272
+
273
+ # point stdout through less, print, then restore stdout
274
+ less = IO.popen('/usr/bin/less', 'w')
275
+
276
+ begin
277
+ less.puts text
278
+
279
+ # this catches the quitting of 'less' before all the output
280
+ # is displayed
281
+ rescue Errno::EPIPE
282
+ true
283
+ ensure
284
+ less&.close
285
+ end
286
+ end
287
+
288
+ end
289
+
290
+ end
291
+
292
+ end # module
@@ -0,0 +1,21 @@
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
+ # main module
9
+ module Xolo
10
+
11
+ module Core
12
+
13
+ module Version
14
+
15
+ VERSION = '1.0.1'.freeze
16
+
17
+ end
18
+
19
+ end
20
+
21
+ end # module
data/lib/xolo/core.rb ADDED
@@ -0,0 +1,46 @@
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
+ # Load the core xolo functionality
10
+ #
11
+ # This is done automatically when you `require 'xolo/admin'` or
12
+ # `require 'xolo/server'`
13
+
14
+ # Ruby Standard Libraries
15
+ ######
16
+ require 'English'
17
+ require 'date'
18
+ require 'time'
19
+ require 'pathname'
20
+ require 'json'
21
+
22
+ # Other Gems to include at this level
23
+ require 'pixar-ruby-extensions'
24
+
25
+ # Internal requires - order matters
26
+ require 'xolo/core/loading'
27
+ require 'xolo/core/version'
28
+ require 'xolo/core/constants'
29
+ require 'xolo/core/exceptions'
30
+
31
+ # The main module
32
+ module Xolo
33
+
34
+ extend Xolo::Core::Loading
35
+ include Xolo::Core::Version
36
+ include Xolo::Core::Constants
37
+ include Xolo::Core::Exceptions
38
+
39
+ end # module Xolo
40
+
41
+ require 'xolo/core/json_wrappers'
42
+ require 'xolo/core/base_classes/configuration'
43
+ require 'xolo/core/base_classes/server_object'
44
+ require 'xolo/core/base_classes/title'
45
+ require 'xolo/core/base_classes/version'
46
+ require 'xolo/core/output'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xolo-admin
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Lasell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-09-28 00:00:00.000000000 Z
11
+ date: 2025-10-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -70,14 +70,12 @@ description: |
70
70
  == Xolo
71
71
  Xolo (sorta pronounced 'show-low') is an HTTPS server and set of command-line tools for macOS that provide automatable access to the software deployment and patch management aspects of {Jamf Pro}[https://www.jamf.com/products/jamf-pro/] and the {Jamf Title Editor}[https://learn.jamf.com/en-US/bundle/title-editor/page/About_Title_Editor.html]. It enhances Jamf Pro's abilities in many ways:
72
72
 
73
- - Management of titles and versions/patches is scriptable and automatable, allowing developers and admins to integrate with CI/CD workflows.
74
- - Simplifies and standardizes the complex, multistep manual process of managing titles and patches using the Title Editor and Patch Management web interfaces.
75
- - Client installs can be performed by remotely via ssh and/or MDM
76
- - Automated pre-release piloting of new versions/patches
77
- - Titles can be expired (auto-uninstalled) after a period of disuse, reclaiming unused licenses.
78
- - And more!
79
-
80
- "Xolo" is the short name for the Mexican hairless dog breed {'xoloitzcuintle'}[https://en.wikipedia.org/wiki/Xoloitzcuintle] (show-low-itz-kwint-leh), as personified by Dante in the 2017 Pixar film _Coco_.
73
+ * Management of titles and versions/patches is scriptable and automatable, allowing developers and admins to integrate with CI/CD workflows.
74
+ * Simplifies and standardizes the complex, multistep manual process of managing titles and patches using the Title Editor and Patch Management web interfaces.
75
+ * Client installs can be performed by remotely via ssh and/or MDM
76
+ * Automated pre-release piloting of new versions/patches
77
+ * Titles can be expired (auto-uninstalled) after a period of disuse, reclaiming unused licenses.
78
+ * And more!
81
79
 
82
80
  The xolo-admin gem packages the code needed to run 'xadm', the command-line tool for system administrators to deploy and maintain software titles using Xolo.
83
81
  email: xolo@pixar.com
@@ -91,6 +89,8 @@ files:
91
89
  - LICENSE.txt
92
90
  - README.md
93
91
  - bin/xadm
92
+ - data/client/xolo
93
+ - lib/optimist_with_insert_blanks.rb
94
94
  - lib/xolo-admin.rb
95
95
  - lib/xolo/admin.rb
96
96
  - lib/xolo/admin/command_line.rb
@@ -108,6 +108,17 @@ files:
108
108
  - lib/xolo/admin/title_editor.rb
109
109
  - lib/xolo/admin/validate.rb
110
110
  - lib/xolo/admin/version.rb
111
+ - lib/xolo/core.rb
112
+ - lib/xolo/core/base_classes/configuration.rb
113
+ - lib/xolo/core/base_classes/server_object.rb
114
+ - lib/xolo/core/base_classes/title.rb
115
+ - lib/xolo/core/base_classes/version.rb
116
+ - lib/xolo/core/constants.rb
117
+ - lib/xolo/core/exceptions.rb
118
+ - lib/xolo/core/json_wrappers.rb
119
+ - lib/xolo/core/loading.rb
120
+ - lib/xolo/core/output.rb
121
+ - lib/xolo/core/version.rb
111
122
  homepage: https://pixaranimationstudios.github.io/xolo-home/
112
123
  licenses:
113
124
  - LicenseRef-LICENSE.txt