Module: D3::Admin::Interactive

Extended by:
Interactive
Included in:
Interactive
Defined in:
lib/d3/admin/interactive.rb

Overview

This module contains methods for interacting with the user in the terminal prompting for data related to administering d3 packages.

These methods all return a string of user input, possibly an empty string.

Constant Summary collapse

UNSET =
'n'.freeze
DFT_EDITOR =
'/usr/bin/nano -L'.freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.get_auto_groups(default = nil) ⇒ String

Prompt the admin for one or more auto-groups for this installer

Parameters:

  • default (nil, String, Array<String>) (defaults to: nil)

    The groups to use

Returns:

  • (String)


637
638
639
640
641
642
643
644
645
646
# File 'lib/d3/admin/interactive.rb', line 637

def get_auto_groups(default = nil)
  desc = <<-END_DESC
AUTO-INSTALL GROUPS
Enter a comma-separated list of JSS Computer Group names whose members should
have this package installed automatically when it is made live.
Enter 'v' to view a list of computer groups.
Enter '#{D3::STANDARD_AUTO_GROUP}' to install on all machines.
  END_DESC
  get_groups desc, :auto_groups, default
end

.get_basename(default = nil) ⇒ Object

get a basename from the user



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/d3/admin/interactive.rb', line 265

def get_basename(default = nil)
  desc = <<-END_DESC
BASENAME
Enter a basename.
Enter 'v' to view a list of all basenames in d3 and
the newest edition for each.
  END_DESC

  input = 'v'
  while input == 'v'
    input = prompt_for_data(desc: desc, prompt: 'Basename', required: true)
    D3::Admin::Report.show_all_basenames_and_editions if input == 'v'
  end
  input
end

.get_category(default = 'n') ⇒ String

Get a JSS Category from the user

Parameters:

  • default (String) (defaults to: 'n')

    the category used when the user hits return

Returns:

  • (String)

    the category entered by the user



748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
# File 'lib/d3/admin/interactive.rb', line 748

def get_category(default = 'n')
  desc = <<-END_DESC
CATEGORY
Enter the JSS category name for this package.
Enter:
   - 'v' to view all JSS categories
   - 'n' for no category
  END_DESC

  result = 'v'
  while result == 'v'
    result = prompt_for_data(desc: desc, prompt: 'Category', default: default, required: true)
    D3.less_text JSS::Category.all_names.sort_by(&:downcase).join("\n") if result == 'v'
  end
  return nil if result == 'n'
  result
end

.get_computer(default = nil) ⇒ String

What computer are we generating a receipt report for?

Returns:

  • (String)

    A computer name in the JSS



945
946
947
948
949
950
951
952
953
954
955
956
957
# File 'lib/d3/admin/interactive.rb', line 945

def get_computer(default = nil)
  desc = <<-END_DESC
COMPUTER NAME
Enter the name of a computer Jamf Pro.
Enter 'v' to view a list available computer names.
END_DESC
  input = 'v'
  while input == 'v'
    input = prompt_for_data(desc: desc, prompt: 'Computer name', default: nil, required: true)
    D3::Admin::Report.show_available_computers_for_reports if input == 'v'
  end
  input
end

.get_config_target(default = 'all') ⇒ String

Get the config target

Parameters:

  • default (String) (defaults to: 'all')

    the default value when hitting return

Returns:

  • (String)

    the prefix to use



965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
# File 'lib/d3/admin/interactive.rb', line 965

def get_config_target(default = 'all')
  desc = <<-END_DESC
CONFIGURATION
Which setting would you like to configure?
  jss - the JSS and credentials (stored in your keychain)
  db  - the MySQL server and credentials (stored in your keychain)
  dist - the master distribution point RW password (stored in your keychain)
  workspace - the folder in which to build .pkgs and .dmgs
  editor - the shell command for editing package descriptions
  pkg-id-prefix - the prefix for the .pkg identifier when building .pkgs
  signing-identity - optional Developer ID Installer certificate issued by Apple
  signing-options - optional string of signing options to pass to pkgbuild.
  all - all of the above
  display - show current configuration

  END_DESC
  prompt_for_data(opt: :pkg_identifier_prefix, desc: desc, default: default, required: true)
end

.get_cpu_type(default = 'x86') ⇒ Symbol?

Get a the CPU-type limitation for this package

Parameters:

  • default (String) (defaults to: 'x86')

    the default limitation if the user hits return

Returns:

  • (Symbol, nil)

    :ppc, :intel, or nil



733
734
735
736
737
738
739
740
# File 'lib/d3/admin/interactive.rb', line 733

def get_cpu_type(default = 'x86')
  desc = <<-END_DESC
LIMIT TO CPU TYPE
Should this packge be limited to certain CPU types?
Enter 'ppc' or 'x86'  or 'none' for neither.
  END_DESC
  prompt_for_data(desc: desc, opt: :cpu_type, default: default, required: true)
end

.get_description(current_desc = '') ⇒ String

Get a multiline description from the user using the editor of their choice: nano, vi, emacs, or ENV

Parameters:

  • desc (String)

    the description to start with

Returns:

  • (String)

    the desired description



352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
# File 'lib/d3/admin/interactive.rb', line 352

def get_description(current_desc = '')
  # do we have a current desc to display and possibly keep?
  current_desc_review = ''
  unless current_desc.to_s.empty?
    current_desc_review = "\n----- Current Description -----\n#{current_desc}\n-------------------------------\n\n"
  end

  if prefd_editor = D3::Admin::Prefs.prefs[:editor]
    prefd_editor_choice = "\n   - 'e' to edit using '#{prefd_editor}' "
  else
    prefd_editor_choice = ''
  end

  # the blurb to show the user
  input_desc = <<-END_DESC
DESCRIPTION
Create a multi-line description of this package:
   - what does the installed thing do?
   - where did it come from, where to get updates?
   - who maintains it in your environment?
   - any other info useful to d3 and Jamf Pro admins.
(don't just say "installs foo" when "foo" is the basename)
  #{current_desc_review}Enter:#{prefd_editor_choice}
   - 'n' to edit using 'nano'
   - 'v' to edit using 'vi' or 'vim'
   - 'm' to edit using 'emacs'
   - 'b' to have a blank description
Anything else will edit with the EDITOR for your environment
or '#{DFT_EDITOR}' if none is set.
  END_DESC

  # show it, get response
  puts input_desc
  choice = Readline.readline('Your choice (hit return to keep current desc.): ', false)

  # keep or empty?
  return current_desc if choice.empty?

  return '' if choice.casecmp('b').zero?

  # make a tem file, save current into it
  desc_tmp_file = Pathname.new Tempfile.new('d3_description_')
  desc_tmp_file.jss_save current_desc

  # which editor?
  if choice.casecmp('e').zero?
    cmd = prefd_editor
  elsif choice.casecmp('v').zero?
    cmd = '/usr/bin/vim'
  elsif choice.casecmp('m').zero?
    cmd = '/usr/bin/emacs'
  elsif choice.casecmp('n').zero?
    cmd = '/usr/bin/nano -L'
  else
    cmd = ENV['EDITOR']
  end
  cmd ||= DFT_EDITOR

  system "#{cmd} '#{desc_tmp_file}'"

  result = desc_tmp_file.read.chomp
  desc_tmp_file.delete
  result.chomp
end

.get_editor(default = '/usr/bin/nano') ⇒ String

get the shell command for editing package descriptions

Parameters:

  • default (String) (defaults to: '/usr/bin/nano')

    the default value when hitting return

Returns:

  • (String)

    the command to use



990
991
992
993
994
995
996
997
998
999
1000
1001
1002
# File 'lib/d3/admin/interactive.rb', line 990

def get_editor(default = '/usr/bin/nano')
  desc = <<-END_DESC
EDITOR
Enter the shell command to use during --walkthru
for editing package descriptions
e.g.  /usr/bin/vim, /usr/bin/emacs

Note: if the command launches a GUI editor, make sure the
shell command stays running until the document is closed.
Most such editors have an option for that.
END_DESC
  prompt_for_data(desc: desc, default: default, prompt: 'Command', required: true)
end

.get_excluded_groups(default = nil) ⇒ Object

Prompt the admin for one or more auto-groups for this installer

Parameters:

  • default (nil, String, Array<String>) (defaults to: nil)

    The groups to



651
652
653
654
655
656
657
658
659
# File 'lib/d3/admin/interactive.rb', line 651

def get_excluded_groups(default = nil)
  desc = <<-END_PROMPT
EXCLUDED GROUPS
Enter a comma-separated list of JSS Computer Group names
whose members should not get this installed without force.
Enter 'v' to view list of computer groups.
  END_PROMPT
  get_groups desc, :excluded_groups, default
end

.get_existing_package(default = nil) ⇒ String?

Ask the user for an edition or basename of an existing package.

Parameters:

  • default (String, nil) (defaults to: nil)

    the name to offer as default

Returns:

  • (String, nil)

    the edition or basename entered, or nil



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/d3/admin/interactive.rb', line 225

def get_existing_package(default = nil)
  desc = <<-END_DESC
EXISTING PACKAGE
Enter a package edition or basename for an existing d3 package.
If a basename, the currently live package for that basename will be used.
Enter:
   - 'v' to view a list of all packages with the basenames and editions in d3.
  END_DESC

  input = 'v'
  while input == 'v'
    input = prompt_for_data(desc: desc, prompt: 'Edition or Basename', default: default, required: true)
    D3::Admin::Report.show_all_basenames_and_editions if input == 'v'
  end
  input
end

.get_expiration(default = 0) ⇒ Integer

Get an expiration period (# of days) from the user

Parameters:

  • default (Integer) (defaults to: 0)

    the # to use when the user types a return.

Returns:

  • (Integer)

    the value to use as the expiration



852
853
854
855
856
857
858
859
860
861
862
# File 'lib/d3/admin/interactive.rb', line 852

def get_expiration(default = 0)
  desc = <<-END_DESC
EXPIRATION
On machines that allow package expiration,
should this package be removed after some
number of days without being used?
Enter the number of days, or 0 for no expiration.
  END_DESC

  prompt_for_data(desc: desc, prompt: 'Expiration days', default: default, required: true)
end

.get_expiration_paths(default = 'n') ⇒ Array<Pathname>

Get the path to the executable(s) to monitor for expiration

Parameters:

  • default (String, Pathname, Array<String,Pathname>) (defaults to: 'n')

    the path(s)

Returns:

  • (Array<Pathname>)

    the path(s) to the executable



870
871
872
873
874
875
876
877
878
879
880
# File 'lib/d3/admin/interactive.rb', line 870

def get_expiration_paths(default = 'n')
  desc = <<-END_DESC
EXPIRATION PATH(S)
Enter the path(s) to the executable(s) that must be used
to prevent expiration. Multiple paths should be separated by commas, spaces
should not be escaped. E.g.
/Applications/Google Chrome.app/Contents/MacOS/Google Chrome, /Applications/Firefox.app/Contents/MacOS/firefox
Enter 'n' for none
  END_DESC
  prompt_for_data(desc: desc, prompt: 'Expiration Path(s)', default: default, required: true)
end

.get_filename(default = nil) ⇒ Object

get a package name from user



297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/d3/admin/interactive.rb', line 297

def get_filename(default = nil)
  desc = <<-END_DESC
INSTALLER FILENAME
Enter a unique name for this package's installer file
on the master distribution point. The file will be
renamed to this name on the distribution point.
Enter 'v' to see a list of existing pkg filenames in the JSS
  END_DESC
  input = 'v'
  while input == 'v'
    input = prompt_for_data(opt: :filename, desc: desc, default: default, required: true)
    D3::Admin::Report.show_existing_package_ids if input == 'v'
  end
  input
end

.get_groups(desc, opt, default = nil) ⇒ String?

Prompt the admin for one or more groups

Parameters:

  • default (String, Array<String>) (defaults to: nil)

    The groups to use

Returns:

  • (String, nil)


701
702
703
704
705
706
707
708
# File 'lib/d3/admin/interactive.rb', line 701

def get_groups(desc, opt, default = nil)
  result = 'v'
  while result == 'v'
    result = prompt_for_data(opt: opt, desc: desc, default: default, required: true)
    D3.less_text JSS::ComputerGroup.all_names.sort_by(&:downcase).join("\n") if result == 'v'
  end
  result
end

.get_jss_package_for_import(default = nil) ⇒ String

Ask the user for an id or name of an existing JSS package to import into d3

Returns:

  • (String)

    the name or id entered, or nil



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/d3/admin/interactive.rb', line 247

def get_jss_package_for_import(default = nil)
  desc = <<-END_DESC
IMPORT JSS PACKAGE
Enter a package id or display-name for
an existing JSS package to import into d3.
Enter:
   - 'v' to view a list of all JSS package names not in d3.
  END_DESC

  input = 'v'
  while input == 'v'
    input = prompt_for_data(desc: desc, prompt: 'JSS id or display name', default: default, required: true)
    D3::Admin::Report.show_pkgs_available_for_import if input == 'v'
  end
  input
end

.get_keep_in_jss(default = 'n') ⇒ String

when deleting a pkg, should it be kept in the JSS?

Parameters:

  • default (String) (defaults to: 'n')

    the default answer when user hits return

Returns:

  • (String)

    the users response



907
908
909
910
911
912
913
914
915
# File 'lib/d3/admin/interactive.rb', line 907

def get_keep_in_jss(default = 'n')
  desc = <<-END_DESC
KEEP THE PACKAGE IN JAMF PRO?
When deleting a package, should it be kept as a Jamf Pro package
and only deleted from d3?
Enter 'y' or 'n'
  END_DESC
  prompt_for_data(desc: desc, prompt: 'Keep in JSS? (y/n)', default: default, required: true)
end

.get_keep_scripts(default = 'n') ⇒ String

when deleting a pkg, should its pre- and post- scripts be kept?

Parameters:

  • default (String) (defaults to: 'n')

    the default answer when user hits return

Returns:

  • (String)

    the users response



888
889
890
891
892
893
894
895
896
897
898
899
# File 'lib/d3/admin/interactive.rb', line 888

def get_keep_scripts(default = 'n')
  desc = <<-END_DESC
KEEP ASSOCIATED SCRIPTS IN JAMF PRO?
When deleting a package, should any associated scripts
(pre-install, post-install, pre-remove, post-remove) be kept in Jamf Pro?

NOTE: If any other d3 packages or policies are using the scripts
they won't be deleted. The other users of the scripts will be reported.
Enter 'y' or 'n'
  END_DESC
  prompt_for_data(desc: desc, prompt: 'Delete Scripts? (y/n)', default: default, required: true)
end

.get_menu_choice(header, items) ⇒ Integer, String

Display a menu of numbered choices, and return the user’s choice, or ‘x’ if the user is done choosing.

Parameters:

  • header (String)

    The text to show above the numbered menu

  • items (Array<String>)

    the items of the menu, in order.

Returns:

  • (Integer, String)

    The index of the chosen chosen, or ‘x’



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/d3/admin/interactive.rb', line 64

def get_menu_choice(header, items)
  # add a 1-based number and ) to the start of each line, like 1),  and 2)...
  items.each_index { |i| items[i] = "#{i + 1}) #{items[i]}" }
  menu_count = items.count
  menu_count_display = "(1-#{menu_count}, x=done, ^c=cancel)"

  menu = "#{header}\n#{items.join("\n")}"

  # clear the screen between displays of the menu, so its always at the top.
  system 'clear' or system 'cls'
  puts menu
  choice = ''
  while choice == ''
    choice = Readline.readline("Which to change? #{menu_count_display}: ", false)
    break if choice == 'x'

    # they chose a number..
    if choice =~ /^\d+$/
      # map it to one of the editing options
      choice = choice.to_i - 1
      # but they might have chosen a higher number than allowws
      choice = '' unless (0..(menu_count - 1)).cover? choice
    else
      choice = ''
    end

    # tell them they made a bad choice
    if choice == ''
      puts "\n******* Sorry, invalid choice.\n"
      next
    end
  end # while choice == ""

  choice
end

.get_oses(default = []) ⇒ String?

Get a list of allowed OSes for this pkg

Parameters:

  • default (String, Array) (defaults to: [])

    An array or comma-separated list of OSes selected when the user hits return

Returns:

  • (String, nil)

    A comma-separated list of allowed OSes



717
718
719
720
721
722
723
724
725
# File 'lib/d3/admin/interactive.rb', line 717

def get_oses(default = [])
  desc = <<-END_DESC
LIMIT TO OS's
Enter a comma-separated list of OS's allowed to
install this package, e.g. '10.8.5, 10.9.5, 10.10.x'
Use '>=' to set a minimum OS, e.g. '>=10.8.5'
  END_DESC
  prompt_for_data(desc: desc, opt: :oses, default: default, required: true)
end

.get_package_build_type(default = D3::Admin::DFT_PKG_TYPE) ⇒ Symbol

If we’re builting a pkg, should we build a .pkg, or a .dmg?

Returns:

  • (Symbol)

    :pkg or :dmg



438
439
440
441
442
443
444
445
# File 'lib/d3/admin/interactive.rb', line 438

def get_package_build_type(default = D3::Admin::DFT_PKG_TYPE)
  desc = <<-END_DESC
PACKAGE BUILD TYPE
Looks like we need to build the installer from a package-root.
Should we build a .pkg or .dmg?  ( p = pkg, d = dmg )
  END_DESC
  prompt_for_data(opt: :package_build_type, desc: desc, default: default, required: true)
end

.get_package_name(default = nil) ⇒ Object

get a package name from user



282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/d3/admin/interactive.rb', line 282

def get_package_name(default = nil)
  desc = <<-END_DESC
JSS PACKAGE NAME
Enter a unique name for this package in d3 and Jamf Pro.
Enter 'v' to view a list of package names currently in d3.
  END_DESC
  input = 'v'
  while input == 'v'
    input = prompt_for_data(opt: :package_name, desc: desc, default: default, required: true)
    D3::Admin::Report.show_existing_package_ids if input == 'v'
  end
  input
end

.get_pkg_identifier(default = nil) ⇒ String

Get the pkg identifier for building .pkgs

Parameters:

  • default (String) (defaults to: nil)

    the default value when hitting return

Returns:

  • (String)

    the prefix to use



453
454
455
456
457
458
459
460
# File 'lib/d3/admin/interactive.rb', line 453

def get_pkg_identifier(default = nil)
  desc = <<-END_DESC
PKG IDENTIFIER
Enter the Apple .pkg indentifier for building a .pkg.
E.g. com.mycompany.myapp
  END_DESC
  prompt_for_data(opt: :pkg_identifier_prefix, desc: desc, default: default, required: true)
end

.get_pkg_identifier_prefix(default = D3::Admin::DFT_PKG_ID_PREFIX) ⇒ String

Get the pkg identifier prefex for building .pkgs When building .pkgs, this string is prefixed to the basename to create the Apple Pkg identifier. For example if the value is com.pixar.d3, then when building a pkg with the basename “foo” the identifier will be com.pixar.d3.foo

This value is saved in the admin prefs for future use.

Parameters:

  • default (String) (defaults to: D3::Admin::DFT_PKG_ID_PREFIX)

    the default value when hitting return

Returns:

  • (String)

    the prefix to use



475
476
477
478
479
480
481
482
483
# File 'lib/d3/admin/interactive.rb', line 475

def get_pkg_identifier_prefix(default = D3::Admin::DFT_PKG_ID_PREFIX)
  desc = <<-END_DESC
PKG IDENTIFIER PREFIX
Enter the prefix to prepend to a basename to create an Apple .pkg indentifier.
E.g. If you enter 'com.mycompany', then when you build a .pkg with basename 'foo'
the default .pkg identifier  will be 'com.mycompany.foo'
  END_DESC
  prompt_for_data(opt: :pkg_identifier_prefix, desc: desc, default: default, required: true)
end

.get_pkg_preserve_owners(default = 'n') ⇒ String

Ask if the pkg should preserve source ownership, or apply OS defaults

Parameters:

  • default (String) (defaults to: 'n')

    the default answer when user hits return

Returns:

  • (String)

    the users response



548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
# File 'lib/d3/admin/interactive.rb', line 548

def get_pkg_preserve_owners(default = 'n')
  desc = <<-END_DESC
PRESERVE SOURCE OWNERSHIP
When building a .pkg, the OS generally sets the ownership and permissions
of the payload to match OS standards, e.g. Apps owned by 'root' with group
'admin' or 'wheel'

If desired you can preserve the current ownership and permissions of the source
folder contents when the payload is installed. This is generally not recomended.

Should we override the OS and preserve the ownership on
the source folder when the item is installed on the client?
Enter 'y' or 'n'
  END_DESC
  prompt_for_data(desc: desc, prompt: 'Preserve ownership (y/n)', default: default, required: true)
end

.get_post_install_script(default = nil) ⇒ Pathname, ...

Get a post-install script, either local file, JSS id, or JSS name

Parameters:

  • default (String) (defaults to: nil)

    the name of an existing JSS script to use

Returns:

  • (Pathname, Integer, nil)

    The local script file, or the JSS id of the chosen script



583
584
585
# File 'lib/d3/admin/interactive.rb', line 583

def get_post_install_script(default = nil)
  get_script 'POST-INSTALL SCRIPT', :post_install, default
end

.get_post_remove_script(default = nil) ⇒ Pathname, ...

Get a post-remove script, either local file, JSS id, or JSS name

Parameters:

  • default (String) (defaults to: nil)

    the name of an existing JSS script to use

Returns:

  • (Pathname, Integer, nil)

    The local script file, or the JSS id of the chosen script



605
606
607
# File 'lib/d3/admin/interactive.rb', line 605

def get_post_remove_script(default = nil)
  get_script 'POST-REMOVE SCRIPT', :post_remove, default
end

.get_pre_install_script(default = nil) ⇒ Pathname, ...

Get a pre-install script, either local file, JSS id, or JSS name

Parameters:

  • default (String) (defaults to: nil)

    the name of an existing JSS script to use

Returns:

  • (Pathname, Integer, nil)

    The local script file, or the JSS id of the chosen script



572
573
574
# File 'lib/d3/admin/interactive.rb', line 572

def get_pre_install_script(default = nil)
  get_script 'PRE-INSTALL SCRIPT', :pre_install, default
end

.get_pre_remove_script(default = nil) ⇒ Pathname, ...

Get a pre-remove script, either local file, JSS id, or JSS name

Parameters:

  • default (String) (defaults to: nil)

    the name of an existing JSS script to use

Returns:

  • (Pathname, Integer, nil)

    The local script file, or the JSS id of the chosen script



594
595
596
# File 'lib/d3/admin/interactive.rb', line 594

def get_pre_remove_script(default = nil)
  get_script 'PRE-REMOVE SCRIPT', :pre_remove, default
end

.get_prohibiting_processes(default = 'n') ⇒ Regexp?

Get a pattern to match for the prohibiting processes If this matches a line of output from ‘/bin/ps -A -c -o comm` at install time, then graceful quit will be attempted. Strings must match a whole line, Regexps will work with any match.

Parameters:

  • default (String, Array<String>, Regexp) (defaults to: 'n')

    the default pattern when hitting return

Returns:

  • (Regexp, nil)

    the pattern to match



776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
# File 'lib/d3/admin/interactive.rb', line 776

def get_prohibiting_processes(default = 'n')
  desc = <<-END_DESC
PROHIBITING PROCESSES
Enter a comma separated string of process name(s) as they appear in the
of the output of `/bin/ps -A -c -o comm`.

Example: Safari, Google Chrome, cfprefsd

If a process is running at install time, the installer will
quit any background processes automatically, and may prompt the user
to quit GUI applications gracefully. Matching is case sensitive.

Enter 'n' for none.
  END_DESC

  result = prompt_for_data(desc: desc, prompt: 'Prohibiting Processes', opt: :prohibiting_processes, default: default, required: true)
  return nil if result == 'n'
  result
end

.get_reboot(default = 'n') ⇒ String

Ask if this package needs a reboot

Parameters:

  • default (String) (defaults to: 'n')

    the default answer when user hits return

Returns:

  • (String)

    the users response



834
835
836
837
838
839
840
841
842
843
844
# File 'lib/d3/admin/interactive.rb', line 834

def get_reboot(default = 'n')
  desc = <<-END_DESC
REBOOT REQUIRED (PUPPIES!)
Does this package require a reboot after installation?
If so, it will be added to the Puppy Queue when installed
with 'd3 install', and the user will be notified to log
out as soon as possible.
Enter 'y' or 'n'
  END_DESC
  prompt_for_data(desc: desc, prompt: 'Requires reboot? (y/n)', default: default, required: true)
end

.get_removable(default = 'y') ⇒ String

Ask if this package is uninstallable

Parameters:

  • default (String) (defaults to: 'y')

    the default answer when user hits return

Returns:

  • (String)

    the users response



802
803
804
805
806
807
808
809
# File 'lib/d3/admin/interactive.rb', line 802

def get_removable(default = 'y')
  desc = <<-END_DESC
REMOVABLE
Can this package be uninstalled?
Enter 'y' or 'n'
  END_DESC
  prompt_for_data(desc: desc, prompt: 'Removable? (y/n)', default: default, required: true)
end

.get_remove_first(default = 'y') ⇒ String

Ask if we should ininstall older versions of this basename before installing this one

Parameters:

  • default (String) (defaults to: 'y')

    the default answer when user hits return

Returns:

  • (String)

    the users response



818
819
820
821
822
823
824
825
826
# File 'lib/d3/admin/interactive.rb', line 818

def get_remove_first(default = 'y')
  desc = <<-END_DESC
UNINSTALL OLDER VERSIONS
Should older versions of this basename be uninstalled
(if they are removable) before attempting to install this package?
Enter 'y' or 'n'
  END_DESC
  prompt_for_data(desc: desc, prompt: 'Remove older installs first? (y/n)', default: default, required: true)
end

.get_revision(default = nil) ⇒ String

Get a revision from the user

Parameters:

  • default (String) (defaults to: nil)

    the rev to use when the user types a return.

Returns:

  • (String)

    the value to use as the rev



334
335
336
337
338
339
340
341
342
343
# File 'lib/d3/admin/interactive.rb', line 334

def get_revision(default = nil)
  desc = <<-END_DESC
REVISION
Enter a Package revision for this package.
This is an integer representing a new packaging of
an existing version of a given basename.
  END_DESC

  prompt_for_data(opt: :revision, desc: desc, default: default, required: true)
end

.get_script(heading, opt, default = nil) ⇒ Pathname, ...

Get a script, either local file, JSS id, or JSS name

Parameters:

  • default (String) (defaults to: nil)

    the name of an existing JSS script to use

Returns:

  • (Pathname, Integer, nil)

    The local script file, or the JSS id of the chosen script



616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
# File 'lib/d3/admin/interactive.rb', line 616

def get_script(heading, opt, default = nil)
  desc = <<-END_DESC
#{heading}
Enter a path to a local file containing the script
or the name or id of an existing script in the JSS.
Enter 'v' to view a list of scripts in the JSS.
  END_DESC

  result = 'v'
  while result == 'v'
    result = prompt_for_data(opt: opt, desc: desc, default: default, required: true)
    D3.less_text JSS::Script.all_names.sort_by(&:downcase).join("\n") if result == 'v'
  end
  result
end

.get_search_target(default = false) ⇒ String

Prompt the admin for text to search for package searchs

Returns:

  • (String)

    whatever the admin typed



665
666
667
668
669
670
671
672
673
674
675
# File 'lib/d3/admin/interactive.rb', line 665

def get_search_target(default = false)
  desc = <<-END_PROMPT
SEARCH TEXT
Enter text to use in matching basenames or computer group names.
Matching a basename will list all packages with the basename.
Matching a group name will list all packages auto-installed or
excluded for the group. (RegExp's OK)
Enter 'all' to list all packages in d3.
  END_PROMPT
  prompt_for_data(desc: desc, prompt: "Text to match or 'all'").chomp
end

.get_show_type(default = D3::Admin::Report::DFT_SHOW_TYPE) ⇒ String

what kind of package list are we showing?

Parameters:

  • default (String) (defaults to: D3::Admin::Report::DFT_SHOW_TYPE)

    the default answer when user hits return

Returns:

  • (String)

    the chosen report type



923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
# File 'lib/d3/admin/interactive.rb', line 923

def get_show_type(default = D3::Admin::Report::DFT_SHOW_TYPE)
  desc = <<-END_DESC
SERVER PACKAGE LIST
Enter the type of list you'd like to generate about packages in d3.

One of:
  all        - all packages in d3
  pilot      -  packages newer than live
  live       - live packages
  deprecated - old packages that used to be live
  skipped    - old packages that were never made live
  missing    - packages in d3, but not Jamf Pro
  auto       - packages auto-installed for a given computer group
  excluded   - packages not available to a given computer group
END_DESC
  prompt_for_data(desc: desc, prompt: 'Show packages', default: default, required: true)
end

.get_signing_identity(default = ) ⇒ String

Get the optional Apple Developer signing ID .pkgs can be codesigned with a certificate from Apple The Developer ID Installer certificate must be in the login.keychain of the user operating d3admin unless otherwise specified in signing_options

This value is saved in the admin prefs for future use.

Parameters:

  • default (String) (defaults to: )

    the default value when hitting return

Returns:

  • (String)

    The Apple Developer signing ID to use



497
498
499
500
501
502
503
504
505
# File 'lib/d3/admin/interactive.rb', line 497

def get_signing_identity(default = D3::Admin::Prefs.prefs[:signing_identity])
  desc = <<-END_DESC
SIGNING IDENTITY
Enter the common name of your Apple Developer signing ID to create signed Apple .pkgs.
E.g. If you enter 'Developer ID Installer: My Company (A12BC34DE56)', then that string will be passed
as the option for pkgbuild --sign. D3 will not attempt to sign unless this option is set.'
  END_DESC
  prompt_for_data(opt: :signing_identity, desc: desc, default: default, required: false)
end

.get_signing_options(default = ) ⇒ String

Get any arguments and options to pass to pkgbuild A signing identity must be defined for these options to be used.

This value is saved in the admin prefs for future use.

Parameters:

  • default (String) (defaults to: )

    the default value when hitting return

Returns:

  • (String)

    The string of arguments and options to pass to pkgbuild



516
517
518
519
520
521
522
523
# File 'lib/d3/admin/interactive.rb', line 516

def get_signing_options(default = D3::Admin::Prefs.prefs[:signing_options])
  desc = <<-END_DESC
SIGNING OPTIONS
Enter optional arguments and options to pass to pkgbuild. These options are ignored unless a signing identity is defined.
E.g. --keychain '/Users/d3/Library/Keychain' --cert 'My Awesome Authority' --timestamp
  END_DESC
  prompt_for_data(opt: :signing_options, desc: desc, default: default, required: false)
end

.get_source_path(default = false) ⇒ Pathname

Get the local path to the package being added to d3 Also sets @build_installer, and @build_installer_type if the source is a root-folder rather than a .pkg or .dmg

Returns:

  • (Pathname)

    the local path to the pkg source



422
423
424
425
426
427
428
429
430
431
432
# File 'lib/d3/admin/interactive.rb', line 422

def get_source_path(default = false)
  desc = <<-END_DESC
SOURCE
Enter the path to a .pkg or .dmg installer
or a 'root' folder from which to build one.
END_DESC

  # dragging in items from the finder will esacpe spaces in the path with \'s
  # in the shell this is good, but ruby is interpreting the \'s, so lets remove them.
  prompt_for_data(opt: :source_path, desc: desc, default: default, required: true).strip.gsub(/\\ /, ' ')
end

.get_status_for_filter(with_frozen = false) ⇒ Object

get auto



677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
# File 'lib/d3/admin/interactive.rb', line 677

def get_status_for_filter(with_frozen = false)
  if with_frozen
    frozen_line = "\nUse 'frozen' to limit to frozen receipts"
    frozen_title = 'OR FROZEN'
  else
    frozen_line = ''
    frozen_title = ''
  end

  desc = <<-END_PROMPT
LIMIT TO STATUS#{frozen_title}
Enter a comma-separate list of statuses for limiting the list.
Valid Statuses are: #{D3::Basename::STATUSES_FOR_FILTERS.join(', ')}#{frozen_line}
Enter 'all' to show all statuses
  END_PROMPT
  prompt_for_data(desc: desc, prompt: 'Statuses', default: 'all').chomp
end

.get_value(option_or_get_method, default = nil, validate_method = nil) ⇒ Object

Call one of the get_ methods and do the matching validity check, if desired, repeatedly until a valid value is supplied.

Parameters:

  • option_or_get_method (Symbol)

    a key of the OPTIONS hash, or the symbol representing the ‘get’ method to call

  • default (String) (defaults to: nil)

    the default value when the user hits return

  • validate_method (Symbol) (defaults to: nil)

    the symbol representing the Admin::Validate method to use in validating the input. This method must raise an exeption if the input is invalid, and return the (possibly modified) value when it’s valid.

Returns:

  • (Object)

    the validated data from the user



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/d3/admin/interactive.rb', line 115

def get_value(option_or_get_method, default = nil, validate_method = nil)
  # if the option_or_get_method is one of the keys in OPTIONS, then use OPTIONS[get_method][:get] if it exists
  if D3::Admin::OPTIONS.keys.include?(option_or_get_method)
    get_method = D3::Admin::OPTIONS[option_or_get_method][:get]
    # if we weren't giving a validate method, get it from the OPTIONS
    validate_method ||= D3::Admin::OPTIONS[option_or_get_method][:validate]
  end
  # otherwise we should have been given a symbolic method name.
  get_method ||= option_or_get_method

  valid = :start
  validated = nil
  until valid === true # Keep the ===, trust me. Don't listen to rubocop.
    puts "\nSorry: #{validated}, Try again.\n" unless valid === :start

    value_input = self.send get_method, default

    # no check method? just return the value
    return value_input if validate_method.nil?

    (valid, validated) = D3::Admin::Validate.validate(value_input, validate_method)

  end # until valid === true
  validated
end

.get_version(default = nil) ⇒ String

Get a version from the user

Parameters:

  • default (String) (defaults to: nil)

    the value to use when the user types a return.

Returns:

  • (String)

    the value to use as the version



318
319
320
321
322
323
324
325
326
# File 'lib/d3/admin/interactive.rb', line 318

def get_version(default = nil)
  desc = <<-END_DESC
VERSION
Enter a version for this package.
All spaces will be converted to underscores.
  END_DESC

  prompt_for_data(opt: :version, desc: desc, default: default, required: true)
end

.get_workspace(default = ) ⇒ Pathname

Get the desired local workspace for building pkgs Defaults to ENV

Parameters:

  • default (Pathname, String) (defaults to: )

    the default choice when typing return

Returns:

  • (Pathname)

    the path to the workspace



532
533
534
535
536
537
538
539
# File 'lib/d3/admin/interactive.rb', line 532

def get_workspace(default = ENV['HOME'])
  desc = <<-END_DESC
PACKAGE BUILD WORKSPACE
Enter the path to a folder where we can build packages.
This will be stored between uses of d3admin.
  END_DESC
  Pathname.new prompt_for_data(opt: :workspace, desc: desc, default: default, required: true)
end

.prompt_for_data(desc: nil, prompt: nil, opt: nil, default: :no_default, required: true) ⇒ String

Prompt for user input for an option and return the response.

A Description of the option is displayed, followed by a prompt. If a default value is provided, the prompt includes the text

(Hit return for #{default_value})

If the option is defined in D3::Admin::OPTIONS, the data for the option is used, if not provided in the args.

If the option is defined as unsettable, a line “Enter ‘n’ for none.” is also displayed before the prompt and a value of ‘n’ will cause the method to return nil.

If no prompt is given in the args, the :label is used from D3::Admin::OPTIONS

If no default value is given in the args, the one from D3::Admin::OPTIONS is used. If required is true, the input can’t be an empty string.

Note: watch out for nil vs false in default values

Parameters:

  • desc (String) (defaults to: nil)

    A multi-line description of the value to be entered.

  • prompt (String) (defaults to: nil)

    The beginning text of the line on which the user enters data

  • opt (Symbol) (defaults to: nil)

    The option that is being prompted for, one of the keys of D3::Admin::OPTIONS

  • default (Object) (defaults to: :no_default)

    The default value that will be used if the user just types a return (i.e. an empty string is entered). For options that are pkg attributes, this should be in the format stored by D3::Package objects e.g. an array of groups, a Boolean, nil. The :display_conversion for that option from D3::Admin::OPTIONS will be used to generate the diaplay version (e.g. a comma-separated string) The symbol :no_default means don’t offer a default value.

  • required (Boolean) (defaults to: true)

    re-prompt until a non-empty string is entered.

Returns:

  • (String)

    The data entered by the user, possibly an empty string



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/d3/admin/interactive.rb', line 179

def prompt_for_data(desc: nil, prompt: nil, opt: nil, default: :no_default, required: true)
  unset_line = nil
  default_display = default

  # look up some info about this option, if needed
  if opt
    opt_def = D3::Admin::OPTIONS[opt]
    if opt_def
      prompt ||= opt_def[:label]
      unset_line = "Enter '#{UNSET}' for none." if opt_def[:unsetable]
      default = opt_def[:default] if opt_def[:default] and default == :no_default
      default_display = opt_def[:display_conversion].call(default) if opt_def[:display_conversion]
    end
  end # if args[:opt]

  # some values are special for displaying
  default_display = case default_display
                    when :no_default then ''
                    when D3::Admin::DFT_REQUIRED then '' # the '---Required---' should only be visible in the menu, not the prompt
                    when D3::Admin::DFT_NONE then UNSET
                    else default_display.to_s
                    end

  data_entered = ''
  puts "\n#{desc}" if desc
  prompt ||= 'Please enter a value'
  hit_return = default_display.empty? ? '' : " (Hit return for '#{default_display}' )"
  prompt_line = "#{prompt}#{hit_return}: "

  while true do
    data_entered = Readline.readline(prompt_line, false)
    data_entered = default_display if data_entered == ''
    break unless required && data_entered.empty?
  end
  # if 'n' was typed for an unsettable option, return nil
  return nil if opt_def && opt_def[:unsetable] && data_entered == UNSET
  data_entered.strip
end

Instance Method Details

#get_auto_groups(default = nil) ⇒ String

Prompt the admin for one or more auto-groups for this installer

Parameters:

  • default (nil, String, Array<String>) (defaults to: nil)

    The groups to use

Returns:

  • (String)


637
638
639
640
641
642
643
644
645
646
# File 'lib/d3/admin/interactive.rb', line 637

def get_auto_groups(default = nil)
  desc = <<-END_DESC
AUTO-INSTALL GROUPS
Enter a comma-separated list of JSS Computer Group names whose members should
have this package installed automatically when it is made live.
Enter 'v' to view a list of computer groups.
Enter '#{D3::STANDARD_AUTO_GROUP}' to install on all machines.
  END_DESC
  get_groups desc, :auto_groups, default
end

#get_basename(default = nil) ⇒ Object

get a basename from the user



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/d3/admin/interactive.rb', line 265

def get_basename(default = nil)
  desc = <<-END_DESC
BASENAME
Enter a basename.
Enter 'v' to view a list of all basenames in d3 and
the newest edition for each.
  END_DESC

  input = 'v'
  while input == 'v'
    input = prompt_for_data(desc: desc, prompt: 'Basename', required: true)
    D3::Admin::Report.show_all_basenames_and_editions if input == 'v'
  end
  input
end

#get_category(default = 'n') ⇒ String

Get a JSS Category from the user

Parameters:

  • default (String) (defaults to: 'n')

    the category used when the user hits return

Returns:

  • (String)

    the category entered by the user



748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
# File 'lib/d3/admin/interactive.rb', line 748

def get_category(default = 'n')
  desc = <<-END_DESC
CATEGORY
Enter the JSS category name for this package.
Enter:
   - 'v' to view all JSS categories
   - 'n' for no category
  END_DESC

  result = 'v'
  while result == 'v'
    result = prompt_for_data(desc: desc, prompt: 'Category', default: default, required: true)
    D3.less_text JSS::Category.all_names.sort_by(&:downcase).join("\n") if result == 'v'
  end
  return nil if result == 'n'
  result
end

#get_computer(default = nil) ⇒ String

What computer are we generating a receipt report for?

Returns:

  • (String)

    A computer name in the JSS



945
946
947
948
949
950
951
952
953
954
955
956
957
# File 'lib/d3/admin/interactive.rb', line 945

def get_computer(default = nil)
  desc = <<-END_DESC
COMPUTER NAME
Enter the name of a computer Jamf Pro.
Enter 'v' to view a list available computer names.
END_DESC
  input = 'v'
  while input == 'v'
    input = prompt_for_data(desc: desc, prompt: 'Computer name', default: nil, required: true)
    D3::Admin::Report.show_available_computers_for_reports if input == 'v'
  end
  input
end

#get_config_target(default = 'all') ⇒ String

Get the config target

Parameters:

  • default (String) (defaults to: 'all')

    the default value when hitting return

Returns:

  • (String)

    the prefix to use



965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
# File 'lib/d3/admin/interactive.rb', line 965

def get_config_target(default = 'all')
  desc = <<-END_DESC
CONFIGURATION
Which setting would you like to configure?
  jss - the JSS and credentials (stored in your keychain)
  db  - the MySQL server and credentials (stored in your keychain)
  dist - the master distribution point RW password (stored in your keychain)
  workspace - the folder in which to build .pkgs and .dmgs
  editor - the shell command for editing package descriptions
  pkg-id-prefix - the prefix for the .pkg identifier when building .pkgs
  signing-identity - optional Developer ID Installer certificate issued by Apple
  signing-options - optional string of signing options to pass to pkgbuild.
  all - all of the above
  display - show current configuration

  END_DESC
  prompt_for_data(opt: :pkg_identifier_prefix, desc: desc, default: default, required: true)
end

#get_cpu_type(default = 'x86') ⇒ Symbol?

Get a the CPU-type limitation for this package

Parameters:

  • default (String) (defaults to: 'x86')

    the default limitation if the user hits return

Returns:

  • (Symbol, nil)

    :ppc, :intel, or nil



733
734
735
736
737
738
739
740
# File 'lib/d3/admin/interactive.rb', line 733

def get_cpu_type(default = 'x86')
  desc = <<-END_DESC
LIMIT TO CPU TYPE
Should this packge be limited to certain CPU types?
Enter 'ppc' or 'x86'  or 'none' for neither.
  END_DESC
  prompt_for_data(desc: desc, opt: :cpu_type, default: default, required: true)
end

#get_description(current_desc = '') ⇒ String

Get a multiline description from the user using the editor of their choice: nano, vi, emacs, or ENV

Parameters:

  • desc (String)

    the description to start with

Returns:

  • (String)

    the desired description



352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
# File 'lib/d3/admin/interactive.rb', line 352

def get_description(current_desc = '')
  # do we have a current desc to display and possibly keep?
  current_desc_review = ''
  unless current_desc.to_s.empty?
    current_desc_review = "\n----- Current Description -----\n#{current_desc}\n-------------------------------\n\n"
  end

  if prefd_editor = D3::Admin::Prefs.prefs[:editor]
    prefd_editor_choice = "\n   - 'e' to edit using '#{prefd_editor}' "
  else
    prefd_editor_choice = ''
  end

  # the blurb to show the user
  input_desc = <<-END_DESC
DESCRIPTION
Create a multi-line description of this package:
   - what does the installed thing do?
   - where did it come from, where to get updates?
   - who maintains it in your environment?
   - any other info useful to d3 and Jamf Pro admins.
(don't just say "installs foo" when "foo" is the basename)
  #{current_desc_review}Enter:#{prefd_editor_choice}
   - 'n' to edit using 'nano'
   - 'v' to edit using 'vi' or 'vim'
   - 'm' to edit using 'emacs'
   - 'b' to have a blank description
Anything else will edit with the EDITOR for your environment
or '#{DFT_EDITOR}' if none is set.
  END_DESC

  # show it, get response
  puts input_desc
  choice = Readline.readline('Your choice (hit return to keep current desc.): ', false)

  # keep or empty?
  return current_desc if choice.empty?

  return '' if choice.casecmp('b').zero?

  # make a tem file, save current into it
  desc_tmp_file = Pathname.new Tempfile.new('d3_description_')
  desc_tmp_file.jss_save current_desc

  # which editor?
  if choice.casecmp('e').zero?
    cmd = prefd_editor
  elsif choice.casecmp('v').zero?
    cmd = '/usr/bin/vim'
  elsif choice.casecmp('m').zero?
    cmd = '/usr/bin/emacs'
  elsif choice.casecmp('n').zero?
    cmd = '/usr/bin/nano -L'
  else
    cmd = ENV['EDITOR']
  end
  cmd ||= DFT_EDITOR

  system "#{cmd} '#{desc_tmp_file}'"

  result = desc_tmp_file.read.chomp
  desc_tmp_file.delete
  result.chomp
end

#get_editor(default = '/usr/bin/nano') ⇒ String

get the shell command for editing package descriptions

Parameters:

  • default (String) (defaults to: '/usr/bin/nano')

    the default value when hitting return

Returns:

  • (String)

    the command to use



990
991
992
993
994
995
996
997
998
999
1000
1001
1002
# File 'lib/d3/admin/interactive.rb', line 990

def get_editor(default = '/usr/bin/nano')
  desc = <<-END_DESC
EDITOR
Enter the shell command to use during --walkthru
for editing package descriptions
e.g.  /usr/bin/vim, /usr/bin/emacs

Note: if the command launches a GUI editor, make sure the
shell command stays running until the document is closed.
Most such editors have an option for that.
END_DESC
  prompt_for_data(desc: desc, default: default, prompt: 'Command', required: true)
end

#get_excluded_groups(default = nil) ⇒ Object

Prompt the admin for one or more auto-groups for this installer

Parameters:

  • default (nil, String, Array<String>) (defaults to: nil)

    The groups to



651
652
653
654
655
656
657
658
659
# File 'lib/d3/admin/interactive.rb', line 651

def get_excluded_groups(default = nil)
  desc = <<-END_PROMPT
EXCLUDED GROUPS
Enter a comma-separated list of JSS Computer Group names
whose members should not get this installed without force.
Enter 'v' to view list of computer groups.
  END_PROMPT
  get_groups desc, :excluded_groups, default
end

#get_existing_package(default = nil) ⇒ String?

Ask the user for an edition or basename of an existing package.

Parameters:

  • default (String, nil) (defaults to: nil)

    the name to offer as default

Returns:

  • (String, nil)

    the edition or basename entered, or nil



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/d3/admin/interactive.rb', line 225

def get_existing_package(default = nil)
  desc = <<-END_DESC
EXISTING PACKAGE
Enter a package edition or basename for an existing d3 package.
If a basename, the currently live package for that basename will be used.
Enter:
   - 'v' to view a list of all packages with the basenames and editions in d3.
  END_DESC

  input = 'v'
  while input == 'v'
    input = prompt_for_data(desc: desc, prompt: 'Edition or Basename', default: default, required: true)
    D3::Admin::Report.show_all_basenames_and_editions if input == 'v'
  end
  input
end

#get_expiration(default = 0) ⇒ Integer

Get an expiration period (# of days) from the user

Parameters:

  • default (Integer) (defaults to: 0)

    the # to use when the user types a return.

Returns:

  • (Integer)

    the value to use as the expiration



852
853
854
855
856
857
858
859
860
861
862
# File 'lib/d3/admin/interactive.rb', line 852

def get_expiration(default = 0)
  desc = <<-END_DESC
EXPIRATION
On machines that allow package expiration,
should this package be removed after some
number of days without being used?
Enter the number of days, or 0 for no expiration.
  END_DESC

  prompt_for_data(desc: desc, prompt: 'Expiration days', default: default, required: true)
end

#get_expiration_paths(default = 'n') ⇒ Array<Pathname>

Get the path to the executable(s) to monitor for expiration

Parameters:

  • default (String, Pathname, Array<String,Pathname>) (defaults to: 'n')

    the path(s)

Returns:

  • (Array<Pathname>)

    the path(s) to the executable



870
871
872
873
874
875
876
877
878
879
880
# File 'lib/d3/admin/interactive.rb', line 870

def get_expiration_paths(default = 'n')
  desc = <<-END_DESC
EXPIRATION PATH(S)
Enter the path(s) to the executable(s) that must be used
to prevent expiration. Multiple paths should be separated by commas, spaces
should not be escaped. E.g.
/Applications/Google Chrome.app/Contents/MacOS/Google Chrome, /Applications/Firefox.app/Contents/MacOS/firefox
Enter 'n' for none
  END_DESC
  prompt_for_data(desc: desc, prompt: 'Expiration Path(s)', default: default, required: true)
end

#get_filename(default = nil) ⇒ Object

get a package name from user



297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/d3/admin/interactive.rb', line 297

def get_filename(default = nil)
  desc = <<-END_DESC
INSTALLER FILENAME
Enter a unique name for this package's installer file
on the master distribution point. The file will be
renamed to this name on the distribution point.
Enter 'v' to see a list of existing pkg filenames in the JSS
  END_DESC
  input = 'v'
  while input == 'v'
    input = prompt_for_data(opt: :filename, desc: desc, default: default, required: true)
    D3::Admin::Report.show_existing_package_ids if input == 'v'
  end
  input
end

#get_groups(desc, opt, default = nil) ⇒ String?

Prompt the admin for one or more groups

Parameters:

  • default (String, Array<String>) (defaults to: nil)

    The groups to use

Returns:

  • (String, nil)


701
702
703
704
705
706
707
708
# File 'lib/d3/admin/interactive.rb', line 701

def get_groups(desc, opt, default = nil)
  result = 'v'
  while result == 'v'
    result = prompt_for_data(opt: opt, desc: desc, default: default, required: true)
    D3.less_text JSS::ComputerGroup.all_names.sort_by(&:downcase).join("\n") if result == 'v'
  end
  result
end

#get_jss_package_for_import(default = nil) ⇒ String

Ask the user for an id or name of an existing JSS package to import into d3

Returns:

  • (String)

    the name or id entered, or nil



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/d3/admin/interactive.rb', line 247

def get_jss_package_for_import(default = nil)
  desc = <<-END_DESC
IMPORT JSS PACKAGE
Enter a package id or display-name for
an existing JSS package to import into d3.
Enter:
   - 'v' to view a list of all JSS package names not in d3.
  END_DESC

  input = 'v'
  while input == 'v'
    input = prompt_for_data(desc: desc, prompt: 'JSS id or display name', default: default, required: true)
    D3::Admin::Report.show_pkgs_available_for_import if input == 'v'
  end
  input
end

#get_keep_in_jss(default = 'n') ⇒ String

when deleting a pkg, should it be kept in the JSS?

Parameters:

  • default (String) (defaults to: 'n')

    the default answer when user hits return

Returns:

  • (String)

    the users response



907
908
909
910
911
912
913
914
915
# File 'lib/d3/admin/interactive.rb', line 907

def get_keep_in_jss(default = 'n')
  desc = <<-END_DESC
KEEP THE PACKAGE IN JAMF PRO?
When deleting a package, should it be kept as a Jamf Pro package
and only deleted from d3?
Enter 'y' or 'n'
  END_DESC
  prompt_for_data(desc: desc, prompt: 'Keep in JSS? (y/n)', default: default, required: true)
end

#get_keep_scripts(default = 'n') ⇒ String

when deleting a pkg, should its pre- and post- scripts be kept?

Parameters:

  • default (String) (defaults to: 'n')

    the default answer when user hits return

Returns:

  • (String)

    the users response



888
889
890
891
892
893
894
895
896
897
898
899
# File 'lib/d3/admin/interactive.rb', line 888

def get_keep_scripts(default = 'n')
  desc = <<-END_DESC
KEEP ASSOCIATED SCRIPTS IN JAMF PRO?
When deleting a package, should any associated scripts
(pre-install, post-install, pre-remove, post-remove) be kept in Jamf Pro?

NOTE: If any other d3 packages or policies are using the scripts
they won't be deleted. The other users of the scripts will be reported.
Enter 'y' or 'n'
  END_DESC
  prompt_for_data(desc: desc, prompt: 'Delete Scripts? (y/n)', default: default, required: true)
end

#get_menu_choice(header, items) ⇒ Integer, String

Display a menu of numbered choices, and return the user’s choice, or ‘x’ if the user is done choosing.

Parameters:

  • header (String)

    The text to show above the numbered menu

  • items (Array<String>)

    the items of the menu, in order.

Returns:

  • (Integer, String)

    The index of the chosen chosen, or ‘x’



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/d3/admin/interactive.rb', line 64

def get_menu_choice(header, items)
  # add a 1-based number and ) to the start of each line, like 1),  and 2)...
  items.each_index { |i| items[i] = "#{i + 1}) #{items[i]}" }
  menu_count = items.count
  menu_count_display = "(1-#{menu_count}, x=done, ^c=cancel)"

  menu = "#{header}\n#{items.join("\n")}"

  # clear the screen between displays of the menu, so its always at the top.
  system 'clear' or system 'cls'
  puts menu
  choice = ''
  while choice == ''
    choice = Readline.readline("Which to change? #{menu_count_display}: ", false)
    break if choice == 'x'

    # they chose a number..
    if choice =~ /^\d+$/
      # map it to one of the editing options
      choice = choice.to_i - 1
      # but they might have chosen a higher number than allowws
      choice = '' unless (0..(menu_count - 1)).cover? choice
    else
      choice = ''
    end

    # tell them they made a bad choice
    if choice == ''
      puts "\n******* Sorry, invalid choice.\n"
      next
    end
  end # while choice == ""

  choice
end

#get_oses(default = []) ⇒ String?

Get a list of allowed OSes for this pkg

Parameters:

  • default (String, Array) (defaults to: [])

    An array or comma-separated list of OSes selected when the user hits return

Returns:

  • (String, nil)

    A comma-separated list of allowed OSes



717
718
719
720
721
722
723
724
725
# File 'lib/d3/admin/interactive.rb', line 717

def get_oses(default = [])
  desc = <<-END_DESC
LIMIT TO OS's
Enter a comma-separated list of OS's allowed to
install this package, e.g. '10.8.5, 10.9.5, 10.10.x'
Use '>=' to set a minimum OS, e.g. '>=10.8.5'
  END_DESC
  prompt_for_data(desc: desc, opt: :oses, default: default, required: true)
end

#get_package_build_type(default = D3::Admin::DFT_PKG_TYPE) ⇒ Symbol

If we’re builting a pkg, should we build a .pkg, or a .dmg?

Returns:

  • (Symbol)

    :pkg or :dmg



438
439
440
441
442
443
444
445
# File 'lib/d3/admin/interactive.rb', line 438

def get_package_build_type(default = D3::Admin::DFT_PKG_TYPE)
  desc = <<-END_DESC
PACKAGE BUILD TYPE
Looks like we need to build the installer from a package-root.
Should we build a .pkg or .dmg?  ( p = pkg, d = dmg )
  END_DESC
  prompt_for_data(opt: :package_build_type, desc: desc, default: default, required: true)
end

#get_package_name(default = nil) ⇒ Object

get a package name from user



282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/d3/admin/interactive.rb', line 282

def get_package_name(default = nil)
  desc = <<-END_DESC
JSS PACKAGE NAME
Enter a unique name for this package in d3 and Jamf Pro.
Enter 'v' to view a list of package names currently in d3.
  END_DESC
  input = 'v'
  while input == 'v'
    input = prompt_for_data(opt: :package_name, desc: desc, default: default, required: true)
    D3::Admin::Report.show_existing_package_ids if input == 'v'
  end
  input
end

#get_pkg_identifier(default = nil) ⇒ String

Get the pkg identifier for building .pkgs

Parameters:

  • default (String) (defaults to: nil)

    the default value when hitting return

Returns:

  • (String)

    the prefix to use



453
454
455
456
457
458
459
460
# File 'lib/d3/admin/interactive.rb', line 453

def get_pkg_identifier(default = nil)
  desc = <<-END_DESC
PKG IDENTIFIER
Enter the Apple .pkg indentifier for building a .pkg.
E.g. com.mycompany.myapp
  END_DESC
  prompt_for_data(opt: :pkg_identifier_prefix, desc: desc, default: default, required: true)
end

#get_pkg_identifier_prefix(default = D3::Admin::DFT_PKG_ID_PREFIX) ⇒ String

Get the pkg identifier prefex for building .pkgs When building .pkgs, this string is prefixed to the basename to create the Apple Pkg identifier. For example if the value is com.pixar.d3, then when building a pkg with the basename “foo” the identifier will be com.pixar.d3.foo

This value is saved in the admin prefs for future use.

Parameters:

  • default (String) (defaults to: D3::Admin::DFT_PKG_ID_PREFIX)

    the default value when hitting return

Returns:

  • (String)

    the prefix to use



475
476
477
478
479
480
481
482
483
# File 'lib/d3/admin/interactive.rb', line 475

def get_pkg_identifier_prefix(default = D3::Admin::DFT_PKG_ID_PREFIX)
  desc = <<-END_DESC
PKG IDENTIFIER PREFIX
Enter the prefix to prepend to a basename to create an Apple .pkg indentifier.
E.g. If you enter 'com.mycompany', then when you build a .pkg with basename 'foo'
the default .pkg identifier  will be 'com.mycompany.foo'
  END_DESC
  prompt_for_data(opt: :pkg_identifier_prefix, desc: desc, default: default, required: true)
end

#get_pkg_preserve_owners(default = 'n') ⇒ String

Ask if the pkg should preserve source ownership, or apply OS defaults

Parameters:

  • default (String) (defaults to: 'n')

    the default answer when user hits return

Returns:

  • (String)

    the users response



548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
# File 'lib/d3/admin/interactive.rb', line 548

def get_pkg_preserve_owners(default = 'n')
  desc = <<-END_DESC
PRESERVE SOURCE OWNERSHIP
When building a .pkg, the OS generally sets the ownership and permissions
of the payload to match OS standards, e.g. Apps owned by 'root' with group
'admin' or 'wheel'

If desired you can preserve the current ownership and permissions of the source
folder contents when the payload is installed. This is generally not recomended.

Should we override the OS and preserve the ownership on
the source folder when the item is installed on the client?
Enter 'y' or 'n'
  END_DESC
  prompt_for_data(desc: desc, prompt: 'Preserve ownership (y/n)', default: default, required: true)
end

#get_post_install_script(default = nil) ⇒ Pathname, ...

Get a post-install script, either local file, JSS id, or JSS name

Parameters:

  • default (String) (defaults to: nil)

    the name of an existing JSS script to use

Returns:

  • (Pathname, Integer, nil)

    The local script file, or the JSS id of the chosen script



583
584
585
# File 'lib/d3/admin/interactive.rb', line 583

def get_post_install_script(default = nil)
  get_script 'POST-INSTALL SCRIPT', :post_install, default
end

#get_post_remove_script(default = nil) ⇒ Pathname, ...

Get a post-remove script, either local file, JSS id, or JSS name

Parameters:

  • default (String) (defaults to: nil)

    the name of an existing JSS script to use

Returns:

  • (Pathname, Integer, nil)

    The local script file, or the JSS id of the chosen script



605
606
607
# File 'lib/d3/admin/interactive.rb', line 605

def get_post_remove_script(default = nil)
  get_script 'POST-REMOVE SCRIPT', :post_remove, default
end

#get_pre_install_script(default = nil) ⇒ Pathname, ...

Get a pre-install script, either local file, JSS id, or JSS name

Parameters:

  • default (String) (defaults to: nil)

    the name of an existing JSS script to use

Returns:

  • (Pathname, Integer, nil)

    The local script file, or the JSS id of the chosen script



572
573
574
# File 'lib/d3/admin/interactive.rb', line 572

def get_pre_install_script(default = nil)
  get_script 'PRE-INSTALL SCRIPT', :pre_install, default
end

#get_pre_remove_script(default = nil) ⇒ Pathname, ...

Get a pre-remove script, either local file, JSS id, or JSS name

Parameters:

  • default (String) (defaults to: nil)

    the name of an existing JSS script to use

Returns:

  • (Pathname, Integer, nil)

    The local script file, or the JSS id of the chosen script



594
595
596
# File 'lib/d3/admin/interactive.rb', line 594

def get_pre_remove_script(default = nil)
  get_script 'PRE-REMOVE SCRIPT', :pre_remove, default
end

#get_prohibiting_processes(default = 'n') ⇒ Regexp?

Get a pattern to match for the prohibiting processes If this matches a line of output from ‘/bin/ps -A -c -o comm` at install time, then graceful quit will be attempted. Strings must match a whole line, Regexps will work with any match.

Parameters:

  • default (String, Array<String>, Regexp) (defaults to: 'n')

    the default pattern when hitting return

Returns:

  • (Regexp, nil)

    the pattern to match



776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
# File 'lib/d3/admin/interactive.rb', line 776

def get_prohibiting_processes(default = 'n')
  desc = <<-END_DESC
PROHIBITING PROCESSES
Enter a comma separated string of process name(s) as they appear in the
of the output of `/bin/ps -A -c -o comm`.

Example: Safari, Google Chrome, cfprefsd

If a process is running at install time, the installer will
quit any background processes automatically, and may prompt the user
to quit GUI applications gracefully. Matching is case sensitive.

Enter 'n' for none.
  END_DESC

  result = prompt_for_data(desc: desc, prompt: 'Prohibiting Processes', opt: :prohibiting_processes, default: default, required: true)
  return nil if result == 'n'
  result
end

#get_reboot(default = 'n') ⇒ String

Ask if this package needs a reboot

Parameters:

  • default (String) (defaults to: 'n')

    the default answer when user hits return

Returns:

  • (String)

    the users response



834
835
836
837
838
839
840
841
842
843
844
# File 'lib/d3/admin/interactive.rb', line 834

def get_reboot(default = 'n')
  desc = <<-END_DESC
REBOOT REQUIRED (PUPPIES!)
Does this package require a reboot after installation?
If so, it will be added to the Puppy Queue when installed
with 'd3 install', and the user will be notified to log
out as soon as possible.
Enter 'y' or 'n'
  END_DESC
  prompt_for_data(desc: desc, prompt: 'Requires reboot? (y/n)', default: default, required: true)
end

#get_removable(default = 'y') ⇒ String

Ask if this package is uninstallable

Parameters:

  • default (String) (defaults to: 'y')

    the default answer when user hits return

Returns:

  • (String)

    the users response



802
803
804
805
806
807
808
809
# File 'lib/d3/admin/interactive.rb', line 802

def get_removable(default = 'y')
  desc = <<-END_DESC
REMOVABLE
Can this package be uninstalled?
Enter 'y' or 'n'
  END_DESC
  prompt_for_data(desc: desc, prompt: 'Removable? (y/n)', default: default, required: true)
end

#get_remove_first(default = 'y') ⇒ String

Ask if we should ininstall older versions of this basename before installing this one

Parameters:

  • default (String) (defaults to: 'y')

    the default answer when user hits return

Returns:

  • (String)

    the users response



818
819
820
821
822
823
824
825
826
# File 'lib/d3/admin/interactive.rb', line 818

def get_remove_first(default = 'y')
  desc = <<-END_DESC
UNINSTALL OLDER VERSIONS
Should older versions of this basename be uninstalled
(if they are removable) before attempting to install this package?
Enter 'y' or 'n'
  END_DESC
  prompt_for_data(desc: desc, prompt: 'Remove older installs first? (y/n)', default: default, required: true)
end

#get_revision(default = nil) ⇒ String

Get a revision from the user

Parameters:

  • default (String) (defaults to: nil)

    the rev to use when the user types a return.

Returns:

  • (String)

    the value to use as the rev



334
335
336
337
338
339
340
341
342
343
# File 'lib/d3/admin/interactive.rb', line 334

def get_revision(default = nil)
  desc = <<-END_DESC
REVISION
Enter a Package revision for this package.
This is an integer representing a new packaging of
an existing version of a given basename.
  END_DESC

  prompt_for_data(opt: :revision, desc: desc, default: default, required: true)
end

#get_script(heading, opt, default = nil) ⇒ Pathname, ...

Get a script, either local file, JSS id, or JSS name

Parameters:

  • default (String) (defaults to: nil)

    the name of an existing JSS script to use

Returns:

  • (Pathname, Integer, nil)

    The local script file, or the JSS id of the chosen script



616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
# File 'lib/d3/admin/interactive.rb', line 616

def get_script(heading, opt, default = nil)
  desc = <<-END_DESC
#{heading}
Enter a path to a local file containing the script
or the name or id of an existing script in the JSS.
Enter 'v' to view a list of scripts in the JSS.
  END_DESC

  result = 'v'
  while result == 'v'
    result = prompt_for_data(opt: opt, desc: desc, default: default, required: true)
    D3.less_text JSS::Script.all_names.sort_by(&:downcase).join("\n") if result == 'v'
  end
  result
end

#get_search_target(default = false) ⇒ String

Prompt the admin for text to search for package searchs

Returns:

  • (String)

    whatever the admin typed



665
666
667
668
669
670
671
672
673
674
675
# File 'lib/d3/admin/interactive.rb', line 665

def get_search_target(default = false)
  desc = <<-END_PROMPT
SEARCH TEXT
Enter text to use in matching basenames or computer group names.
Matching a basename will list all packages with the basename.
Matching a group name will list all packages auto-installed or
excluded for the group. (RegExp's OK)
Enter 'all' to list all packages in d3.
  END_PROMPT
  prompt_for_data(desc: desc, prompt: "Text to match or 'all'").chomp
end

#get_show_type(default = D3::Admin::Report::DFT_SHOW_TYPE) ⇒ String

what kind of package list are we showing?

Parameters:

  • default (String) (defaults to: D3::Admin::Report::DFT_SHOW_TYPE)

    the default answer when user hits return

Returns:

  • (String)

    the chosen report type



923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
# File 'lib/d3/admin/interactive.rb', line 923

def get_show_type(default = D3::Admin::Report::DFT_SHOW_TYPE)
  desc = <<-END_DESC
SERVER PACKAGE LIST
Enter the type of list you'd like to generate about packages in d3.

One of:
  all        - all packages in d3
  pilot      -  packages newer than live
  live       - live packages
  deprecated - old packages that used to be live
  skipped    - old packages that were never made live
  missing    - packages in d3, but not Jamf Pro
  auto       - packages auto-installed for a given computer group
  excluded   - packages not available to a given computer group
END_DESC
  prompt_for_data(desc: desc, prompt: 'Show packages', default: default, required: true)
end

#get_signing_identity(default = ) ⇒ String

Get the optional Apple Developer signing ID .pkgs can be codesigned with a certificate from Apple The Developer ID Installer certificate must be in the login.keychain of the user operating d3admin unless otherwise specified in signing_options

This value is saved in the admin prefs for future use.

Parameters:

  • default (String) (defaults to: )

    the default value when hitting return

Returns:

  • (String)

    The Apple Developer signing ID to use



497
498
499
500
501
502
503
504
505
# File 'lib/d3/admin/interactive.rb', line 497

def get_signing_identity(default = D3::Admin::Prefs.prefs[:signing_identity])
  desc = <<-END_DESC
SIGNING IDENTITY
Enter the common name of your Apple Developer signing ID to create signed Apple .pkgs.
E.g. If you enter 'Developer ID Installer: My Company (A12BC34DE56)', then that string will be passed
as the option for pkgbuild --sign. D3 will not attempt to sign unless this option is set.'
  END_DESC
  prompt_for_data(opt: :signing_identity, desc: desc, default: default, required: false)
end

#get_signing_options(default = ) ⇒ String

Get any arguments and options to pass to pkgbuild A signing identity must be defined for these options to be used.

This value is saved in the admin prefs for future use.

Parameters:

  • default (String) (defaults to: )

    the default value when hitting return

Returns:

  • (String)

    The string of arguments and options to pass to pkgbuild



516
517
518
519
520
521
522
523
# File 'lib/d3/admin/interactive.rb', line 516

def get_signing_options(default = D3::Admin::Prefs.prefs[:signing_options])
  desc = <<-END_DESC
SIGNING OPTIONS
Enter optional arguments and options to pass to pkgbuild. These options are ignored unless a signing identity is defined.
E.g. --keychain '/Users/d3/Library/Keychain' --cert 'My Awesome Authority' --timestamp
  END_DESC
  prompt_for_data(opt: :signing_options, desc: desc, default: default, required: false)
end

#get_source_path(default = false) ⇒ Pathname

Get the local path to the package being added to d3 Also sets @build_installer, and @build_installer_type if the source is a root-folder rather than a .pkg or .dmg

Returns:

  • (Pathname)

    the local path to the pkg source



422
423
424
425
426
427
428
429
430
431
432
# File 'lib/d3/admin/interactive.rb', line 422

def get_source_path(default = false)
  desc = <<-END_DESC
SOURCE
Enter the path to a .pkg or .dmg installer
or a 'root' folder from which to build one.
END_DESC

  # dragging in items from the finder will esacpe spaces in the path with \'s
  # in the shell this is good, but ruby is interpreting the \'s, so lets remove them.
  prompt_for_data(opt: :source_path, desc: desc, default: default, required: true).strip.gsub(/\\ /, ' ')
end

#get_status_for_filter(with_frozen = false) ⇒ Object

get auto



677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
# File 'lib/d3/admin/interactive.rb', line 677

def get_status_for_filter(with_frozen = false)
  if with_frozen
    frozen_line = "\nUse 'frozen' to limit to frozen receipts"
    frozen_title = 'OR FROZEN'
  else
    frozen_line = ''
    frozen_title = ''
  end

  desc = <<-END_PROMPT
LIMIT TO STATUS#{frozen_title}
Enter a comma-separate list of statuses for limiting the list.
Valid Statuses are: #{D3::Basename::STATUSES_FOR_FILTERS.join(', ')}#{frozen_line}
Enter 'all' to show all statuses
  END_PROMPT
  prompt_for_data(desc: desc, prompt: 'Statuses', default: 'all').chomp
end

#get_value(option_or_get_method, default = nil, validate_method = nil) ⇒ Object

Call one of the get_ methods and do the matching validity check, if desired, repeatedly until a valid value is supplied.

Parameters:

  • option_or_get_method (Symbol)

    a key of the OPTIONS hash, or the symbol representing the ‘get’ method to call

  • default (String) (defaults to: nil)

    the default value when the user hits return

  • validate_method (Symbol) (defaults to: nil)

    the symbol representing the Admin::Validate method to use in validating the input. This method must raise an exeption if the input is invalid, and return the (possibly modified) value when it’s valid.

Returns:

  • (Object)

    the validated data from the user



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/d3/admin/interactive.rb', line 115

def get_value(option_or_get_method, default = nil, validate_method = nil)
  # if the option_or_get_method is one of the keys in OPTIONS, then use OPTIONS[get_method][:get] if it exists
  if D3::Admin::OPTIONS.keys.include?(option_or_get_method)
    get_method = D3::Admin::OPTIONS[option_or_get_method][:get]
    # if we weren't giving a validate method, get it from the OPTIONS
    validate_method ||= D3::Admin::OPTIONS[option_or_get_method][:validate]
  end
  # otherwise we should have been given a symbolic method name.
  get_method ||= option_or_get_method

  valid = :start
  validated = nil
  until valid === true # Keep the ===, trust me. Don't listen to rubocop.
    puts "\nSorry: #{validated}, Try again.\n" unless valid === :start

    value_input = self.send get_method, default

    # no check method? just return the value
    return value_input if validate_method.nil?

    (valid, validated) = D3::Admin::Validate.validate(value_input, validate_method)

  end # until valid === true
  validated
end

#get_version(default = nil) ⇒ String

Get a version from the user

Parameters:

  • default (String) (defaults to: nil)

    the value to use when the user types a return.

Returns:

  • (String)

    the value to use as the version



318
319
320
321
322
323
324
325
326
# File 'lib/d3/admin/interactive.rb', line 318

def get_version(default = nil)
  desc = <<-END_DESC
VERSION
Enter a version for this package.
All spaces will be converted to underscores.
  END_DESC

  prompt_for_data(opt: :version, desc: desc, default: default, required: true)
end

#get_workspace(default = ) ⇒ Pathname

Get the desired local workspace for building pkgs Defaults to ENV

Parameters:

  • default (Pathname, String) (defaults to: )

    the default choice when typing return

Returns:

  • (Pathname)

    the path to the workspace



532
533
534
535
536
537
538
539
# File 'lib/d3/admin/interactive.rb', line 532

def get_workspace(default = ENV['HOME'])
  desc = <<-END_DESC
PACKAGE BUILD WORKSPACE
Enter the path to a folder where we can build packages.
This will be stored between uses of d3admin.
  END_DESC
  Pathname.new prompt_for_data(opt: :workspace, desc: desc, default: default, required: true)
end

#prompt_for_data(desc: nil, prompt: nil, opt: nil, default: :no_default, required: true) ⇒ String

Prompt for user input for an option and return the response.

A Description of the option is displayed, followed by a prompt. If a default value is provided, the prompt includes the text

(Hit return for #{default_value})

If the option is defined in D3::Admin::OPTIONS, the data for the option is used, if not provided in the args.

If the option is defined as unsettable, a line “Enter ‘n’ for none.” is also displayed before the prompt and a value of ‘n’ will cause the method to return nil.

If no prompt is given in the args, the :label is used from D3::Admin::OPTIONS

If no default value is given in the args, the one from D3::Admin::OPTIONS is used. If required is true, the input can’t be an empty string.

Note: watch out for nil vs false in default values

Parameters:

  • desc (String) (defaults to: nil)

    A multi-line description of the value to be entered.

  • prompt (String) (defaults to: nil)

    The beginning text of the line on which the user enters data

  • opt (Symbol) (defaults to: nil)

    The option that is being prompted for, one of the keys of D3::Admin::OPTIONS

  • default (Object) (defaults to: :no_default)

    The default value that will be used if the user just types a return (i.e. an empty string is entered). For options that are pkg attributes, this should be in the format stored by D3::Package objects e.g. an array of groups, a Boolean, nil. The :display_conversion for that option from D3::Admin::OPTIONS will be used to generate the diaplay version (e.g. a comma-separated string) The symbol :no_default means don’t offer a default value.

  • required (Boolean) (defaults to: true)

    re-prompt until a non-empty string is entered.

Returns:

  • (String)

    The data entered by the user, possibly an empty string



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/d3/admin/interactive.rb', line 179

def prompt_for_data(desc: nil, prompt: nil, opt: nil, default: :no_default, required: true)
  unset_line = nil
  default_display = default

  # look up some info about this option, if needed
  if opt
    opt_def = D3::Admin::OPTIONS[opt]
    if opt_def
      prompt ||= opt_def[:label]
      unset_line = "Enter '#{UNSET}' for none." if opt_def[:unsetable]
      default = opt_def[:default] if opt_def[:default] and default == :no_default
      default_display = opt_def[:display_conversion].call(default) if opt_def[:display_conversion]
    end
  end # if args[:opt]

  # some values are special for displaying
  default_display = case default_display
                    when :no_default then ''
                    when D3::Admin::DFT_REQUIRED then '' # the '---Required---' should only be visible in the menu, not the prompt
                    when D3::Admin::DFT_NONE then UNSET
                    else default_display.to_s
                    end

  data_entered = ''
  puts "\n#{desc}" if desc
  prompt ||= 'Please enter a value'
  hit_return = default_display.empty? ? '' : " (Hit return for '#{default_display}' )"
  prompt_line = "#{prompt}#{hit_return}: "

  while true do
    data_entered = Readline.readline(prompt_line, false)
    data_entered = default_display if data_entered == ''
    break unless required && data_entered.empty?
  end
  # if 'n' was typed for an unsettable option, return nil
  return nil if opt_def && opt_def[:unsetable] && data_entered == UNSET
  data_entered.strip
end