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.
@@ -94,6 +94,22 @@ module Xolo
94
94
  ENDDESC
95
95
  },
96
96
 
97
+ # @!attribute pw
98
+ # @return [String]
99
+ ssl_verify: {
100
+ label: 'Verify SSL Cert',
101
+ type: :boolean,
102
+ invalid_msg: '',
103
+ validate: :validate_boolean,
104
+ default: true,
105
+ desc: <<~ENDDESC
106
+ If your Xolo server is using a self-signed SSL certificate,
107
+ set this to false, to disable SSL verification.
108
+
109
+ Defaults to true, which is recommended for production servers.
110
+ ENDDESC
111
+ },
112
+
97
113
  # @!attribute pw
98
114
  # @return [String]
99
115
  no_gui: {
@@ -79,6 +79,17 @@ module Xolo
79
79
  raise Xolo::AuthenticationError, 'Invalid username or password'
80
80
  end
81
81
 
82
+ # @return [Hash] the SSL options for Faraday connections
83
+ ##################
84
+ def ssl_opts
85
+ # true if nil or true, false if false
86
+ verify = config.ssl_verify.nil? || config.ssl_verify
87
+
88
+ {
89
+ verify: verify
90
+ }
91
+ end
92
+
82
93
  # @pararm host [String] an alternate hostname to use, defaults to config.hostname
83
94
  #
84
95
  # @return [URI] The server base URL
@@ -107,7 +118,7 @@ module Xolo
107
118
  @server_cnx = nil if host
108
119
  return @server_cnx if @server_cnx
109
120
 
110
- @server_cnx = Faraday.new(server_url(host: host)) do |cnx|
121
+ @server_cnx = Faraday.new(server_url(host: host), ssl: ssl_opts) do |cnx|
111
122
  cnx.options[:timeout] = TIMEOUT
112
123
  cnx.options[:open_timeout] = OPEN_TIMEOUT
113
124
 
@@ -145,7 +156,7 @@ module Xolo
145
156
 
146
157
  req_opts = { on_data: streaming_proc }
147
158
 
148
- @streaming_cnx = Faraday.new(server_url(host: host), request: req_opts) do |cnx|
159
+ @streaming_cnx = Faraday.new(server_url(host: host), request: req_opts, ssl: ssl_opts) do |cnx|
149
160
  cnx.options[:timeout] = TIMEOUT
150
161
  cnx.options[:open_timeout] = OPEN_TIMEOUT
151
162
  cnx.use Xolo::Admin::CookieJar
@@ -168,7 +179,7 @@ module Xolo
168
179
  @upload_cnx = nil if host
169
180
  return @upload_cnx if @upload_cnx
170
181
 
171
- @upload_cnx = Faraday.new(server_url(host: host)) do |cnx|
182
+ @upload_cnx = Faraday.new(server_url(host: host), ssl: ssl_opts) do |cnx|
172
183
  cnx.options[:timeout] = TIMEOUT
173
184
  cnx.options[:open_timeout] = OPEN_TIMEOUT
174
185
 
@@ -163,7 +163,7 @@ module Xolo
163
163
  def ssvc_na
164
164
  tgt_all = walkthru_cmd_opts[:release_groups]&.include?(Xolo::TARGET_ALL)
165
165
 
166
- "N/A if Target Group is '#{Xolo::TARGET_ALL}'" if tgt_all
166
+ "N/A if Release Group is '#{Xolo::TARGET_ALL}'" if tgt_all
167
167
  end
168
168
 
169
169
  # @return [String, nil] If a string, a reason why the given menu item is not available now.
@@ -515,6 +515,8 @@ module Xolo
515
515
  convert = send deets[:readline]
516
516
  convert << Xolo::NONE unless deets[:required]
517
517
  convert << Xolo::X
518
+ # if we're doing release groups, make sure the list includes 'all',
519
+ convert << Xolo::TARGET_ALL if deets[:label] == Xolo::Admin::Title::ATTRIBUTES[:release_groups][:label]
518
520
  validate = nil
519
521
  end
520
522
  true
@@ -1079,7 +1079,7 @@ module Xolo
1079
1079
  end
1080
1080
 
1081
1081
  # run the cleanup
1082
- # get the /test route to do whatever testing it does
1082
+ # get the /test route to do whatever testing it does.
1083
1083
  # during testing - this will return all kinds of things.
1084
1084
  #
1085
1085
  # @return [void]
@@ -1104,11 +1104,7 @@ module Xolo
1104
1104
  display_progress resp[:progress_stream_url_path]
1105
1105
  end
1106
1106
 
1107
- # test uploads
1108
- # 1.2 gb
1109
- large_file = '/dist/caspershare/Packages-DEACTIVATED/SecUpd2020-006HighSierra.pkg'
1110
-
1111
- pkg_to_upload = Pathname.new large_file
1107
+ pkg_to_upload = Pathname.new '/path/to/some/file.pkg'
1112
1108
  puts "Uploading Test File #{pkg_to_upload.size} bytes... "
1113
1109
  upload_test_file(pkg_to_upload)
1114
1110
 
@@ -79,6 +79,8 @@ module Xolo
79
79
  resp = cnx.get "#{SERVER_ROUTE}/#{title}"
80
80
 
81
81
  new resp.body
82
+ rescue Faraday::ResourceNotFound
83
+ raise Xolo::NoSuchItemError, "No such title '#{title}'"
82
84
  end
83
85
 
84
86
  # Delete a title from the server
@@ -196,7 +196,7 @@ module Xolo
196
196
  if cli_cmd.command == Xolo::Admin::Options::ADD_TITLE_CMD
197
197
  err =
198
198
  if title_exists
199
- 'already exists in Xolo'
199
+ "title '#{val}' already exists in Xolo"
200
200
  elsif val !~ /\A[a-z0-9-][a-z0-9-]+\z/
201
201
  TITLE_ATTRS[:title][:invalid_msg]
202
202
  else
@@ -207,7 +207,7 @@ module Xolo
207
207
  elsif Xolo::Admin::Options::MUST_EXIST_COMMANDS.include?(cli_cmd.command)
208
208
  return val if title_exists
209
209
 
210
- err = "doesn't exist in Xolo"
210
+ err = "title '#{val}' doesn't exist in Xolo"
211
211
 
212
212
  # any other command
213
213
  else
@@ -975,7 +975,7 @@ module Xolo
975
975
  if walkthru?
976
976
  "Cannot be in Self Service when Target Group is '#{Xolo::TARGET_ALL}'"
977
977
  else
978
- "--self-service cannot be used when --target-groups contains '#{Xolo::TARGET_ALL}'"
978
+ "--self-service cannot be used when --release-groups contains '#{Xolo::TARGET_ALL}'"
979
979
  end
980
980
  raise_consistency_error msg
981
981
  end
@@ -87,6 +87,8 @@ module Xolo
87
87
  def self.fetch(title, version, cnx)
88
88
  resp = cnx.get server_route(title, version)
89
89
  new resp.body
90
+ rescue Faraday::ResourceNotFound
91
+ raise Xolo::NoSuchItemError, "No such version '#{version}'"
90
92
  end
91
93
 
92
94
  # Deploy a version to desired computers and groups via MDM
@@ -0,0 +1,238 @@
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 BaseClasses
14
+
15
+ # A class for working with pre-defined settings & preferences for Xolo
16
+ #
17
+ # This is the base class for Xolo::Server::Configuration and Xolo::Admin::Configuration
18
+ #
19
+ # The subclasses must 'include Singleton', making them use the Singleton Pattern, meaning
20
+ # only one instance can exist at a time.
21
+ #
22
+ # This parent class provides methods for loading and saving YAML files with configuration
23
+ # and prefs settings in the Server or Admin context.
24
+ #
25
+ # The YAML files store a Hash of keys and values relevent to the context
26
+ # they are used in.
27
+ #
28
+ # Subclasses must define these constants and related methods:
29
+ #
30
+ # Constant CONF_FILENAME [String]
31
+ # The filename (not the full path) of the config yaml file to read and write.
32
+ #
33
+ # Instance method #conf_file [Pathname]
34
+ # The full expanded absolute path to the config yaml file.
35
+ #
36
+ # Constant KEYS [Hash{Symbol: Hash}]
37
+ # The keys of this Hash are the keys that may be found in the yaml file.
38
+ # The Hash values here define the value in the YAML file, with these keys:
39
+ #
40
+ # required: [Boolean] Is this key/value required in the YAML file? The server
41
+ # will not start if a required key is missing.
42
+ #
43
+ # type: [Symbol] The data-type of the value, one of the types supported by
44
+ # Optimist: :boolean, :string, :integer, :float, :io, :date
45
+ # See https://github.com/ManageIQ/optimist/wiki/Option-Types
46
+ #
47
+ # default: [Object] If this key doesn't exist in the YAML file, this is the
48
+ # value used by Xolo.
49
+ #
50
+ # desc: [String] A full description of what this value is, how it is used,
51
+ # possible values, etc. This is presented in help and/or walkthru.
52
+ #
53
+ # load_method: [Symbol] The name of a method in the Configuration Instance
54
+ # to which the YAML value will be passed to convert it into the real value
55
+ # to be used. For example, some YAML values might contain a command to be
56
+ # executed, a pathname to be read, or a raw value to be used. These
57
+ # values should be passed to the :data_from_command_file_or_string method
58
+ # which will return the value to actually be used (the stdout of a command,
59
+ # the contents of a file, or a raw value)
60
+ #
61
+ # private: [Boolean] If true, the value is never presented in logs or normal
62
+ # output. it is replaced with <private>. Use this for sensitive secrets.
63
+ #
64
+ #
65
+ #
66
+ class Configuration
67
+
68
+ # Mixins
69
+ #####################################
70
+ #####################################
71
+
72
+ # Constants
73
+ #####################################
74
+ #####################################
75
+
76
+ PIPE = '|'
77
+
78
+ PRIVATE = '<private>'
79
+
80
+ # Class Methods
81
+ #####################################
82
+ #####################################
83
+
84
+ def self.inherited(child_class)
85
+ Xolo.verbose_inherit child_class, self
86
+ end
87
+
88
+ # Attributes
89
+ #####################################
90
+ #####################################
91
+
92
+ # @return [Hash{Symbol: Object}] The data as read directly from the YAML file
93
+ attr_reader :raw_data
94
+
95
+ # @return
96
+
97
+ # Constructor
98
+ #####################################
99
+ #####################################
100
+
101
+ # # Initialize!
102
+ def initialize
103
+ keys.each_key { |attr| self.class.attr_accessor attr }
104
+ load_from_file
105
+ end
106
+
107
+ # Public Instance Methods
108
+ #####################################
109
+ #####################################
110
+
111
+ ###############
112
+ def to_h
113
+ data = {}
114
+ keys.each_key do |key|
115
+ data[key] = send(key)
116
+ end
117
+ data
118
+ end
119
+
120
+ ###############
121
+ def to_h_private
122
+ data = to_h
123
+ keys.each do |key, deets|
124
+ data[key] = PRIVATE if deets[:private]
125
+ end
126
+ data
127
+ end
128
+
129
+ # Save new data (or raw_data) to the config file. We don't save the
130
+ # actual instance variables, as they may be expanded from
131
+ # commands or file paths, and its the commands or file paths
132
+ # we want to save.
133
+ #
134
+ # @param data [Hash{Symbol: Object}] The data to save to the config file
135
+ #
136
+ # @return [void]
137
+ ###############
138
+ def save_to_file(data: nil)
139
+ data ||= raw_data
140
+ conf_file.parent.mkpath unless conf_file.parent.directory?
141
+ conf_file.pix_save YAML.dump(data)
142
+ conf_file.chmod 0o600
143
+ end
144
+
145
+ # If the given string starts with a pipe (|) then
146
+ # remove the pipe and execute the remainder, returning
147
+ # its stdout.
148
+ #
149
+ # If the given string is a path to an executable file path, return
150
+ # its stdout.
151
+ #
152
+ # If the given string is a path to a readable file path, return
153
+ # its contents.
154
+ #
155
+ # Otherwise, the string is the desired data, so just return it.
156
+ #
157
+ # @param str [String] a command, file path, or string
158
+ #
159
+ # @param enforce_secure_mode [Boolean] If true, and the str is
160
+ # a path to a file, make sure that the file mode is 700 or 600.
161
+ #
162
+ # @return [String] The std output of the command, file contents, or string
163
+ #
164
+ ###############
165
+ def data_from_command_file_or_string(str, enforce_secure_mode: false)
166
+ # if it starts with a pipe, it's a command and args
167
+ # remove the pipe and execute the rest, returning stdout
168
+ return `#{str.delete_prefix(PIPE)}`.chomp if str.start_with? PIPE
169
+
170
+ path = Pathname.new(str)
171
+ return str unless path.file?
172
+
173
+ # this will return, e.g. "600" "775" etc as strings
174
+ mode = format '%o', (path.stat.mode & 0o777)
175
+
176
+ if path.executable?
177
+ if enforce_secure_mode && !mode.end_with?('00')
178
+ raise Xolo::Core::Exceptions::PermissionError,
179
+ "Executable file #{str} must be mode 0700, 0500, or 0100"
180
+ end
181
+ `#{path.to_s.shellescape}`.chomp
182
+
183
+ elsif path.readable?
184
+ if enforce_secure_mode && mode != '600'
185
+ raise Xolo::Core::Exceptions::PermissionError,
186
+ "Readable file #{str} must be mode 0600"
187
+ end
188
+ path.read.chomp
189
+
190
+ else
191
+ str
192
+ end
193
+ end
194
+
195
+ # Private Instance Methods
196
+ #####################################
197
+ #####################################
198
+ private
199
+
200
+ # Simpler access to the KEYS constant in subclasses
201
+ #
202
+ # @return [Hash]
203
+ #
204
+ ###############
205
+ def keys
206
+ self.class::KEYS
207
+ end
208
+
209
+ # Load in the values from the config file
210
+ #
211
+ # @return [void]
212
+ #
213
+ ###############
214
+ ###############
215
+ def load_from_file
216
+ conf_file.parent.mkpath unless conf_file.parent.directory?
217
+
218
+ unless conf_file.readable?
219
+ @raw_data = {}
220
+ return
221
+ end
222
+
223
+ @raw_data = YAML.load conf_file.read
224
+ @raw_data.each do |k, v|
225
+ next unless keys[k]
226
+
227
+ v = send(keys[k][:load_method], v) if keys[k][:load_method]
228
+ send "#{k}=", v
229
+ end
230
+ end
231
+
232
+ end # class Configuration
233
+
234
+ end # BaseClasses
235
+
236
+ end # Core
237
+
238
+ end # Xolo
@@ -0,0 +1,112 @@
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
+ module BaseClasses
16
+
17
+ # The base class for dealing with Titles and Versions/Patches in the
18
+ # Xolo Server, Admin, and Client modules.
19
+ #
20
+ # The base class for "xolo objects stored on the xolo server", i.e.
21
+ # Titles and Versions/Patches - whether they are being used on the server,
22
+ # in xadm, or in the client.
23
+ #
24
+ # This class holds stuff common to all no matter where or how they are used.
25
+ #
26
+ # See also {Xolo::Core::BaseClasses::Title} and {Xolo::Core::BaseClasses::Version}
27
+ #############################
28
+ class ServerObject
29
+
30
+ # Mixins
31
+ #############################
32
+ #############################
33
+
34
+ extend Xolo::Core::JSONWrappers
35
+
36
+ include Xolo::Core::JSONWrappers
37
+
38
+ # Constants
39
+ #############################
40
+ #############################
41
+
42
+ # Attributes
43
+ #############################
44
+ #############################
45
+
46
+ # Constructor
47
+ ######################
48
+ ######################
49
+ def initialize(data_hash)
50
+ # log_debug "Instantiating a #{self.class}..."
51
+
52
+ self.class::ATTRIBUTES.each do |attr, deets|
53
+ val = data_hash[attr]
54
+
55
+ # log_debug "Initializing, setting ATTR '#{attr}' => '#{val}' (#{val.class})"
56
+
57
+ # anything not nil, esp empty arrays, needs to be set
58
+ next if val.nil?
59
+
60
+ # convert timestamps to Time objects if needed,
61
+ # All the other values shouldn't need converting
62
+ # when taking in JSON or xadm opts.
63
+ val = Time.parse(val.to_s) if deets[:type] == :time && !val.is_a?(Time)
64
+
65
+ # call the setter
66
+ send "#{attr}=", val
67
+ end
68
+ end
69
+
70
+ # Instance Methods
71
+ ######################
72
+ ######################
73
+
74
+ # Convert to a Hash for sending between xadm and the Xolo Server,
75
+ # or installing on clients.
76
+ #
77
+ # Only the values defined in ATTRIBUTES are sent, because all other
78
+ # other attributes are meant only for the local context, i.e.
79
+ # on the server, via xadm, or via 'xolo'.
80
+ #
81
+ # @return [String] The attributes of this title as JSON
82
+ #####################
83
+ def to_h
84
+ hash = {}
85
+ self.class::ATTRIBUTES.each do |attr, deets|
86
+ hash[attr] = send attr
87
+
88
+ # ensure multi values are arrays, even if they are empty
89
+ hash[attr] = [hash[attr]].compact if deets[:multi] && !hash[attr].is_a?(Array)
90
+ end
91
+ hash
92
+ end
93
+
94
+ # Convert to a JSON object for sending between xadm and the Xolo Server
95
+ # or storage on the server.
96
+ #
97
+ # Always make it 'pretty', i.e. human readable, since it often
98
+ # gets stored in files that humans will look at
99
+ #
100
+ # @return [String] The attributes of this title as JSON
101
+ #####################
102
+ def to_json(*_args)
103
+ JSON.pretty_generate to_h
104
+ end
105
+
106
+ end # class Title
107
+
108
+ end # module BaseClasses
109
+
110
+ end # module Core
111
+
112
+ end # module Xolo