xolo-server 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.
- checksums.yaml +4 -4
- data/README.md +42 -4
- data/bin/xoloserver +3 -0
- data/data/client/xolo +1160 -0
- data/lib/optimist_with_insert_blanks.rb +1216 -0
- data/lib/xolo/core/base_classes/configuration.rb +238 -0
- data/lib/xolo/core/base_classes/server_object.rb +112 -0
- data/lib/xolo/core/base_classes/title.rb +648 -0
- data/lib/xolo/core/base_classes/version.rb +601 -0
- data/lib/xolo/core/constants.rb +81 -0
- data/lib/xolo/core/exceptions.rb +52 -0
- data/lib/xolo/core/json_wrappers.rb +43 -0
- data/lib/xolo/core/loading.rb +59 -0
- data/lib/xolo/core/output.rb +292 -0
- data/lib/xolo/core/version.rb +21 -0
- data/lib/xolo/core.rb +46 -0
- data/lib/xolo/server/configuration.rb +1 -2
- data/lib/xolo/server/helpers/jamf_pro.rb +1 -0
- data/lib/xolo/server/mixins/title_jamf_access.rb +7 -12
- data/lib/xolo/server/mixins/title_ted_access.rb +21 -5
- data/lib/xolo/server/mixins/version_jamf_access.rb +23 -17
- data/lib/xolo/server/mixins/version_ted_access.rb +9 -4
- data/lib/xolo/server/routes.rb +6 -23
- data/lib/xolo/server/version.rb +1 -3
- metadata +21 -10
|
@@ -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
|