xolo-admin 1.0.1 → 2.0.2

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.
@@ -126,6 +126,13 @@ module Xolo
126
126
  # skip things not given on the command line
127
127
  next unless cli_cmd_opts["#{key}_given"]
128
128
 
129
+ if deets[:add_only] && edit_command?
130
+ raise ArgumentError, "Option '--#{key.to_s.gsub '_', '-'}' is only valid for 'add-' commands."
131
+
132
+ elsif deets[:edit_only] && add_command?
133
+ raise ArgumentError, "Option '--#{key.to_s.gsub '_', '-'}' is only valid for 'edit-' commands."
134
+ end
135
+
129
136
  # skip things that shouldn't be validated
130
137
  next unless deets[:validate]
131
138
 
@@ -217,10 +224,22 @@ module Xolo
217
224
  raise_invalid_data_error val, err
218
225
  end
219
226
 
220
- # validate a title display-name. Must be 3+ chars long
227
+ # validate the title type
221
228
  #
222
229
  # @param val [Object] The value to validate
223
230
  #
231
+ # @return [String] The valid value
232
+ ########################
233
+ def validate_type(val)
234
+ val = val.to_s.strip.downcase.to_sym
235
+ return val if Xolo::TITLE_TYPES.include? val
236
+
237
+ raise_invalid_data_error val, TITLE_ATTRS[:type][:invalid_msg]
238
+ end
239
+
240
+ # validate a title display-name. Must be 3+ chars long
241
+ #
242
+ # @param val [Object] The value to validate
224
243
  #
225
244
  # @return [String] The valid value
226
245
  ##########################
@@ -267,7 +286,7 @@ module Xolo
267
286
  val = val.to_s.strip
268
287
  return val if val.end_with? Xolo::DOTAPP
269
288
 
270
- raise_invalid_data_error val, TITLE_ATTRS[:app_bundle_id][:invalid_msg]
289
+ raise_invalid_data_error val, TITLE_ATTRS[:app_name][:invalid_msg]
271
290
  end
272
291
 
273
292
  # validate a title app_bundle_id. Must include at least one dot
@@ -296,6 +315,32 @@ module Xolo
296
315
  raise_invalid_data_error val, TITLE_ATTRS[:version_script][:invalid_msg]
297
316
  end
298
317
 
318
+ # validate a patch source name. Must exist in Jamf Pro and have available titles
319
+ #
320
+ # @param val [Object] The value to validate
321
+ #
322
+ # @return [Pathname] The valid value
323
+ ##########################
324
+ def validate_patch_source(val)
325
+ val = val.to_s
326
+ return val if jamf_patch_sources_with_available_titles.include? val
327
+
328
+ raise_invalid_data_error val, TITLE_ATTRS[:patch_source][:invalid_msg]
329
+ end
330
+
331
+ # validate a patch title id.
332
+ #
333
+ # @param val [Object] The value to validate
334
+ #
335
+ # @return [Pathname] The valid value
336
+ ##########################
337
+ def validate_title_id(val)
338
+ val = val.to_s
339
+ return val if jamf_available_titles.any? { |t| t[:name_id] == val }
340
+
341
+ raise_invalid_data_error val, TITLE_ATTRS[:title_id][:invalid_msg]
342
+ end
343
+
299
344
  # validate a title uninstall script:
300
345
  # - a path to a script which must start with '#!'
301
346
  # OR
@@ -525,9 +570,10 @@ module Xolo
525
570
  ##########################
526
571
  def validate_pkg_to_upload(val)
527
572
  val = Pathname.new val.to_s.strip
573
+ show_debug "Validating pkg_to_upload: '#{val}'. Checking if file? #{val.file?}, readable? #{val.readable?}, extname '#{val.extname}'"
528
574
  return val if val.file? && val.readable? && (Xolo::OK_PKG_EXTS.include? val.extname)
529
575
 
530
- raise_invalid_data_error val, VERSION_ATTRS[:jamf_pkg_file][:invalid_msg]
576
+ raise_invalid_data_error val, VERSION_ATTRS[:pkg_to_upload][:invalid_msg]
531
577
  end
532
578
 
533
579
  # Version Attributes
@@ -620,7 +666,7 @@ module Xolo
620
666
  val = val.to_s.strip
621
667
  return val if val =~ /\A\S+@\S+\.\S+\z/
622
668
 
623
- raise_invalid_data_error val, VERSION_ATTRS[:contact_email][:invalid_msg]
669
+ raise_invalid_data_error val, TITLE_ATTRS[:contact_email][:invalid_msg]
624
670
  end
625
671
 
626
672
  # expand Xolo::Admin::Version::USE_TITLE_FOR_KILLAPP into the proper killall string
@@ -790,6 +836,8 @@ module Xolo
790
836
  def validate_internal_consistency(opts)
791
837
  return unless add_command? || edit_command?
792
838
 
839
+ show_debug "Validating internal consistency for opts: #{opts.to_h}"
840
+
793
841
  if version_command?
794
842
  validate_version_consistency opts
795
843
  elsif title_command?
@@ -806,6 +854,12 @@ module Xolo
806
854
  validate_min_os_and_max_os(opts)
807
855
  end
808
856
 
857
+ # given our current options, are we working with a subscribed or managed title?
858
+ #
859
+ def current_title_type(opts)
860
+ opts[:subscribed] ? Xolo::SUBSCRIBED : Xolo::MANAGED
861
+ end
862
+
809
863
  # @param opts [OpenStruct] the current options
810
864
  #
811
865
  # @return [void]
@@ -814,86 +868,175 @@ module Xolo
814
868
  # if we are just listing the versions, nothing to do
815
869
  return if cli_cmd.command == Xolo::Admin::Options::LIST_VERSIONS_CMD
816
870
 
817
- # order of these matters
818
- validate_scope_targets_and_exclusions(opts)
819
- validate_title_consistency_app_and_script(opts)
871
+ # order of these matters in some places
872
+
873
+ #### Sub vs Managed
874
+
875
+ # complain about using options not meant for the chosen type
876
+ # e.g. can't use display name for subscribed titles
877
+ validate_subbed_v_managed(opts)
878
+
879
+ #### Subscribed Title Validations ####
880
+
881
+ # if subscribed, both patch_source and title_id must be given
882
+ validate_title_consistency_patch_source_and_title_id(opts)
883
+
884
+ # if subscribed, the title_id must exist in the patch_source
885
+ validate_title_consistency_title_id_exists(opts)
886
+
887
+ #### Managed Title Validations ####
888
+
889
+ # if managed, must provide display name and publisher, but not allowed if subscribing
890
+ validate_title_consistency_display_name_and_publisher(opts)
891
+
892
+ # if managed must use version_script, or app
820
893
  validate_title_consistency_app_or_script(opts)
894
+
895
+ # if managed, can't have both app and version_script
896
+ validate_title_consistency_app_and_script(opts)
897
+
898
+ # if managed and using app_name & bundle id, must have both
821
899
  validate_title_consistency_app_name_and_id(opts)
900
+
901
+ ### General Validations ###
902
+
903
+ # scope targets and exclusions can't overlap
904
+ validate_scope_targets_and_exclusions(opts)
905
+
906
+ # if uninstall script, can't have uninstall ids, and vice versa
822
907
  validate_title_consistency_uninstall(opts)
908
+
909
+ # if expiration > 0, must have at least one expiration path
823
910
  validate_title_consistency_expire_paths(opts)
911
+
912
+ # if targeting 'all' can't be in self-service
824
913
  validate_title_consistency_no_all_in_ssvc(opts)
914
+
915
+ # if self-service category given, must be in self-service
825
916
  validate_title_consistency_ssvc_needs_category(opts)
826
917
  end # title_consistency(opts)
827
918
 
828
- # groups that will be scope targets (pilot or release) cannot
829
- # also be in the exclusions.
919
+ # Complain about using options not meant for the chosen title type
920
+ def validate_subbed_v_managed(opts)
921
+ bad_opts = []
922
+ ttl_type = current_title_type(opts)
923
+
924
+ Xolo::Admin::Title.cli_opts.each do |key, deets|
925
+ next unless opts[key]
926
+ next unless deets[:title_type]
927
+ next if deets[:title_type] == ttl_type
928
+
929
+ # ignore values not used for this command type
930
+ next if add_command? && deets[:edit_only]
931
+ next if edit_command? && deets[:add_only]
932
+
933
+ # ignore values not given on the commandline
934
+ next if !walkthru? && !opts["#{key}_given"]
935
+
936
+ # display_name is stored for subscribed titles, but only editable for managed
937
+ next if key == :display_name && ttl_type == Xolo::SUBSCRIBED
938
+
939
+ bad_opts << (walkthru? ? deets[:label] : "--#{key.to_s.gsub('_', '-')}")
940
+ end
941
+ return if bad_opts.empty?
942
+
943
+ raise_consistency_error "Cannot be used with a #{ttl_type} title: #{bad_opts.join(', ')}"
944
+ end
945
+
946
+ # If subscribing to a title both patch_source and title_id must be given
830
947
  #
831
948
  # @param opts [OpenStruct] the current options
832
949
  #
833
950
  # @return [void]
834
951
  #######
835
- def validate_scope_targets_and_exclusions(opts)
836
- # require 'pp'
837
- # puts 'Opts Are:'
838
- # pp opts.to_h
839
- # pp caller
952
+ def validate_title_consistency_patch_source_and_title_id(opts)
953
+ return if current_title_type(opts) == Xolo::MANAGED
954
+ return if opts[:title_id] && opts[:patch_source]
840
955
 
841
- if title_command?
842
- excls = opts.excluded_groups
843
- tgts = opts.release_groups
844
- tgt_type = :release
845
- elsif version_command?
846
- @title_for_version_validation ||= Xolo::Admin::Title.fetch cli_cmd.title, server_cnx
847
- excls = @title_for_version_validation.excluded_groups
848
- tgts = opts.pilot_groups
849
- tgt_type = :pilot
850
- else
851
- excls = nil
852
- tgts = nil
956
+ msg =
957
+ if walkthru?
958
+ 'To subscribe to a title, both Patch Source and Title ID must be given.'
959
+ else
960
+ 'To subscribe to a title, both --patch-source and --title-id must be given.'
961
+ end
962
+ raise_consistency_error msg
963
+ end
964
+
965
+ # If subscribing to a title, the title_id must exist in the given patch_source
966
+ #
967
+ # @param opts [OpenStruct] the current options
968
+ #
969
+ # @return [void]
970
+ #######
971
+ def validate_title_consistency_title_id_exists(opts)
972
+ return unless cli_cmd.command == Xolo::Admin::Options::ADD_TITLE_CMD
973
+ return if current_title_type(opts) == Xolo::MANAGED
974
+ return unless opts[:title_id] && opts[:patch_source]
975
+ return if jamf_available_titles.any? do |t|
976
+ t[:name_id] == opts[:title_id] && t[:source_name] == opts[:patch_source]
853
977
  end
854
- return unless excls && tgts
855
978
 
856
- in_both = excls & tgts
857
- return if in_both.empty?
979
+ msg =
980
+ if walkthru?
981
+ 'The given Title ID does not exist in the given Patch Source.'
982
+ else
983
+ 'The given --title-id does not exist in the given --patch-source.'
984
+ end
985
+ raise_consistency_error msg
986
+ end
858
987
 
859
- raise_consistency_error "These groups are in both #{tgt_type}_groups and the title's excluded_groups: '#{in_both.join "', '"}'"
988
+ # If managing this title, display_name and publisher must be given
989
+ #################
990
+ def validate_title_consistency_display_name_and_publisher(opts)
991
+ return if current_title_type(opts) == Xolo::SUBSCRIBED
992
+ return if opts[:display_name] && opts[:publisher]
993
+
994
+ msg =
995
+ if walkthru?
996
+ 'Display Name and Publisher must both be given for a managed title.'
997
+ else
998
+ '--display-name and --publisher must both be given for a managed title.'
999
+ end
1000
+ raise_consistency_error msg
860
1001
  end
861
1002
 
862
- # if app_name or app_bundle_id is given, can't use --version-script
1003
+ # if managed must use version_script, or app
863
1004
  #
864
1005
  # @param opts [OpenStruct] the current options
865
1006
  #
866
1007
  # @return [void]
867
1008
  #######
868
- def validate_title_consistency_app_and_script(opts)
869
- return unless opts[:version_script] && (opts[:app_bundle_id] || opts[:app_name])
1009
+ def validate_title_consistency_app_or_script(opts)
1010
+ return if current_title_type(opts) == Xolo::SUBSCRIBED
1011
+ return if opts[:version_script] || (opts[:app_bundle_id] && opts[:app_name])
870
1012
 
871
1013
  msg =
872
1014
  if walkthru?
873
- 'Version Script cannot be used with App Name & App Bundle ID'
1015
+ 'Managed titles must provide Version Script OR App Name & App Bundle ID'
874
1016
  else
875
- '--version-script cannot be used with --app-name & --app-bundle-id'
1017
+ 'Managed titles must provide --version-script OR --app-name & --app-bundle-id'
876
1018
  end
877
1019
 
878
1020
  raise_consistency_error msg
879
1021
  end
880
1022
 
881
- # but either version_script or appname and bundle id must be given
1023
+ # if app_name or app_bundle_id is given, can't use --version-script, and vice versa
882
1024
  #
883
1025
  # @param opts [OpenStruct] the current options
884
1026
  #
885
1027
  # @return [void]
886
1028
  #######
887
- def validate_title_consistency_app_or_script(opts)
888
- return if opts[:version_script]
889
- return if opts[:app_name] || opts[:app_bundle_id]
1029
+ def validate_title_consistency_app_and_script(opts)
1030
+ return if current_title_type(opts) == Xolo::SUBSCRIBED
1031
+ return unless opts[:version_script] && (opts[:app_bundle_id] || opts[:app_name])
890
1032
 
891
1033
  msg =
892
1034
  if walkthru?
893
- 'Either App Name & App Bundle ID. or Version Script must be given.'
1035
+ 'Version Script cannot be used with App Name & App Bundle ID'
894
1036
  else
895
- 'Must provide either --app-name & --app-bundle-id OR --version-script'
1037
+ '--version-script cannot be used with --app-name & --app-bundle-id'
896
1038
  end
1039
+
897
1040
  raise_consistency_error msg
898
1041
  end
899
1042
 
@@ -904,6 +1047,7 @@ module Xolo
904
1047
  # @return [void]
905
1048
  #######
906
1049
  def validate_title_consistency_app_name_and_id(opts)
1050
+ return if current_title_type(opts) == Xolo::SUBSCRIBED
907
1051
  return if opts[:version_script]
908
1052
  return if opts[:app_name] && opts[:app_bundle_id]
909
1053
 
@@ -916,6 +1060,40 @@ module Xolo
916
1060
  raise_consistency_error msg
917
1061
  end
918
1062
 
1063
+ # groups that will be scope targets (pilot or release) cannot
1064
+ # also be in the exclusions.
1065
+ #
1066
+ # @param opts [OpenStruct] the current options
1067
+ #
1068
+ # @return [void]
1069
+ #######
1070
+ def validate_scope_targets_and_exclusions(opts)
1071
+ # require 'pp'
1072
+ # puts 'Opts Are:'
1073
+ # pp opts.to_h
1074
+ # pp caller
1075
+
1076
+ if title_command?
1077
+ excls = opts.excluded_groups
1078
+ tgts = opts.release_groups
1079
+ tgt_type = :release
1080
+ elsif version_command?
1081
+ @title_for_version_validation ||= Xolo::Admin::Title.fetch cli_cmd.title, server_cnx
1082
+ excls = @title_for_version_validation.excluded_groups
1083
+ tgts = opts.pilot_groups
1084
+ tgt_type = :pilot
1085
+ else
1086
+ excls = nil
1087
+ tgts = nil
1088
+ end
1089
+ return unless excls && tgts
1090
+
1091
+ in_both = excls & tgts
1092
+ return if in_both.empty?
1093
+
1094
+ raise_consistency_error "These groups are in both #{tgt_type}_groups and the title's excluded_groups: '#{in_both.join "', '"}'"
1095
+ end
1096
+
919
1097
  # if expiration is > 0, there must be at least one expiration path
920
1098
  #
921
1099
  # @param opts [OpenStruct] the current options
@@ -86,7 +86,9 @@ module Xolo
86
86
  ####################
87
87
  def self.fetch(title, version, cnx)
88
88
  resp = cnx.get server_route(title, version)
89
- new resp.body
89
+ vers_obj = new resp.body
90
+ vers_obj.cnx = cnx
91
+ vers_obj
90
92
  rescue Faraday::ResourceNotFound
91
93
  raise Xolo::NoSuchItemError, "No such version '#{version}'"
92
94
  end
@@ -123,6 +125,9 @@ module Xolo
123
125
  ######################
124
126
  ######################
125
127
 
128
+ # the server connection used to fetch this version
129
+ attr_accessor :cnx
130
+
126
131
  # Constructor
127
132
  ######################
128
133
  ######################
@@ -131,13 +136,31 @@ module Xolo
131
136
  #############################
132
137
  #############################
133
138
 
134
- # The server route for this version, after it exists on the server
139
+ # TODO:
140
+ # when instantiating a title or version object,
141
+ # embed the connection used to fetch it in a @cnx attribute/instance variable
142
+ # and use that for all future interactions with the server for that object.
143
+ #
144
+ # Then also use that to implement the method below, fetching the title object
145
+ # for this version when desired.
146
+ #
147
+ # Then we can use THAT to know if this title is subscribed or not
148
+ # which affects how we deal with adding (we can't) or editing
149
+ #
150
+ # Also TODO: implement a way to trigger a subcribed 'add-title'
151
+ # in case the xolo server misses a webhook event
152
+
153
+ ################
154
+ # def title_object(refresh: false, cnx:
155
+ # @title_object = nil if refresh
156
+ # @title_object ||= Xolo::Admin::Title.fetch(title, cnx)
157
+ # end
135
158
 
136
159
  # Add this version to the server
137
160
  # @param cnx [Faraday::Connection] The connection to use, must be logged in already
138
161
  # @return [Hash] the response from the server
139
162
  ####################
140
- def add(cnx)
163
+ def add(cnx = self.cnx)
141
164
  resp = cnx.post self.class.server_route(title), to_h
142
165
  resp.body
143
166
  end
@@ -146,7 +169,7 @@ module Xolo
146
169
  # @param cnx [Faraday::Connection] The connection to use, must be logged in already
147
170
  # @return [Hash] the response from the server
148
171
  ####################
149
- def update(cnx)
172
+ def update(cnx = self.cnx)
150
173
  resp = cnx.put self.class.server_route(title, version), to_h
151
174
  resp.body
152
175
  end
@@ -155,7 +178,7 @@ module Xolo
155
178
  # @param cnx [Faraday::Connection] The connection to use, must be logged in already
156
179
  # @return [Hash] the response body from the server
157
180
  ####################
158
- def repair(cnx)
181
+ def repair(cnx = self.cnx)
159
182
  resp = cnx.post "#{self.class.server_route(title, version)}/repair"
160
183
  resp.body
161
184
  end
@@ -164,7 +187,7 @@ module Xolo
164
187
  # @param cnx [Faraday::Connection] The connection to use, must be logged in already
165
188
  # @return [Hash] the response from the server
166
189
  ####################
167
- def delete(cnx)
190
+ def delete(cnx = self.cnx)
168
191
  self.class.delete title, version, cnx
169
192
  # already returns resp.body
170
193
  end
@@ -173,14 +196,14 @@ module Xolo
173
196
  # @param cnx [Faraday::Connection] The connection to use, must be logged in already
174
197
  # @return [Hash{String => String}] page_name => url
175
198
  ####################
176
- def gui_urls(cnx)
199
+ def gui_urls(cnx = self.cnx)
177
200
  resp = cnx.get "#{self.class.server_route(title, version)}/urls"
178
201
  resp.body
179
202
  end
180
203
 
181
- # Upload a .pkg (or zipped bundle pkg) for this version
182
- # At this point, the jamf_pkg_file attribute
183
- # will containt the local file path.
204
+ # Upload a .pkg for this version
205
+ # At this point, the pkg_to_upload attribute
206
+ # will contain the local file path.
184
207
  #
185
208
  # @param upload_cnx [Xolo::Admin::Connection] The server connection
186
209
  #
@@ -192,13 +215,6 @@ module Xolo
192
215
  # route = "#{UPLOAD_PKG_ROUTE}/#{title}/#{version}"
193
216
  route = "#{self.class.server_route(title, version)}/#{UPLOAD_PKG_ROUTE}"
194
217
 
195
- # TODO: Update this to the more modern correct class
196
- # upfile = Faraday::UploadIO.new(
197
- # pkg_to_upload.to_s,
198
- # 'application/octet-stream',
199
- # pkg_to_upload.basename.to_s
200
- # )
201
-
202
218
  upfile = Faraday::Multipart::FilePart.new(pkg_to_upload.expand_path.to_s, 'application/octet-stream')
203
219
 
204
220
  content = { file: upfile }
@@ -211,11 +227,30 @@ module Xolo
211
227
  # @param cnx [Faraday::Connection] The connection to use, must be logged in already
212
228
  # @return [Array<Hash>] Data for each computer with this version of this title installed
213
229
  ##################################
214
- def patch_report_data(cnx)
230
+ def patch_report_data(cnx = self.cnx)
215
231
  resp = cnx.get "#{self.class.server_route(title, version)}/patch_report"
216
232
  resp.body
217
233
  end
218
234
 
235
+ # @return [Xolo::Admin::Title] the title for this version
236
+ ################
237
+ def title_object(cnx = self.cnx, refresh: false)
238
+ @title_object = nil if refresh
239
+ @title_object ||= Xolo::Admin::Title.fetch(title, cnx)
240
+ end
241
+
242
+ # @return [Boolean] whether this version is from a subscribed title or not
243
+ #############
244
+ def subscribed?
245
+ title_object.subscribed?
246
+ end
247
+
248
+ # @return [Boolean] whether this version is from a managed title or not
249
+ #############
250
+ def managed?
251
+ title_object.managed?
252
+ end
253
+
219
254
  end # class Title
220
255
 
221
256
  end # module Admin