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.
@@ -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