simp-cli 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/LICENSE +27 -0
- data/README.md +48 -0
- data/Rakefile +142 -0
- data/bin/simp +5 -0
- data/lib/simp/cli.rb +88 -0
- data/lib/simp/cli/commands/bootstrap.rb +275 -0
- data/lib/simp/cli/commands/check.rb +163 -0
- data/lib/simp/cli/commands/cleancerts.rb +114 -0
- data/lib/simp/cli/commands/config.rb +235 -0
- data/lib/simp/cli/commands/doc.rb +14 -0
- data/lib/simp/cli/commands/passgen.rb +128 -0
- data/lib/simp/cli/commands/puppeteval.rb +82 -0
- data/lib/simp/cli/commands/runpuppet.rb +95 -0
- data/lib/simp/cli/config/item.rb +456 -0
- data/lib/simp/cli/config/item/add_ldap_to_hiera.rb +43 -0
- data/lib/simp/cli/config/item/answers_yaml_file_writer.rb +58 -0
- data/lib/simp/cli/config/item/certificates.rb +39 -0
- data/lib/simp/cli/config/item/client_nets.rb +65 -0
- data/lib/simp/cli/config/item/common_runlevel_default.rb +32 -0
- data/lib/simp/cli/config/item/dns_search.rb +48 -0
- data/lib/simp/cli/config/item/dns_servers.rb +57 -0
- data/lib/simp/cli/config/item/failover_log_servers.rb +27 -0
- data/lib/simp/cli/config/item/gateway.rb +32 -0
- data/lib/simp/cli/config/item/grub_password.rb +51 -0
- data/lib/simp/cli/config/item/hostname.rb +24 -0
- data/lib/simp/cli/config/item/hostname_conf.rb +48 -0
- data/lib/simp/cli/config/item/ipaddress.rb +46 -0
- data/lib/simp/cli/config/item/is_master_yum_server.rb +23 -0
- data/lib/simp/cli/config/item/ldap_base_dn.rb +38 -0
- data/lib/simp/cli/config/item/ldap_bind_dn.rb +34 -0
- data/lib/simp/cli/config/item/ldap_bind_hash.rb +28 -0
- data/lib/simp/cli/config/item/ldap_bind_pw.rb +24 -0
- data/lib/simp/cli/config/item/ldap_master.rb +33 -0
- data/lib/simp/cli/config/item/ldap_root_dn.rb +42 -0
- data/lib/simp/cli/config/item/ldap_root_hash.rb +35 -0
- data/lib/simp/cli/config/item/ldap_sync_dn.rb +24 -0
- data/lib/simp/cli/config/item/ldap_sync_hash.rb +28 -0
- data/lib/simp/cli/config/item/ldap_sync_pw.rb +26 -0
- data/lib/simp/cli/config/item/ldap_uri.rb +43 -0
- data/lib/simp/cli/config/item/log_servers.rb +27 -0
- data/lib/simp/cli/config/item/netmask.rb +39 -0
- data/lib/simp/cli/config/item/network_conf.rb +63 -0
- data/lib/simp/cli/config/item/network_dhcp.rb +27 -0
- data/lib/simp/cli/config/item/network_interface.rb +41 -0
- data/lib/simp/cli/config/item/network_setup_nic.rb +28 -0
- data/lib/simp/cli/config/item/ntp_servers.rb +69 -0
- data/lib/simp/cli/config/item/puppet_autosign.rb +66 -0
- data/lib/simp/cli/config/item/puppet_ca.rb +31 -0
- data/lib/simp/cli/config/item/puppet_ca_port.rb +28 -0
- data/lib/simp/cli/config/item/puppet_conf.rb +98 -0
- data/lib/simp/cli/config/item/puppet_fileserver.rb +104 -0
- data/lib/simp/cli/config/item/puppet_hosts_entry.rb +44 -0
- data/lib/simp/cli/config/item/puppet_server.rb +30 -0
- data/lib/simp/cli/config/item/puppet_server_ip.rb +25 -0
- data/lib/simp/cli/config/item/puppetdb_port.rb +25 -0
- data/lib/simp/cli/config/item/puppetdb_server.rb +26 -0
- data/lib/simp/cli/config/item/remove_ldap_from_hiera.rb +47 -0
- data/lib/simp/cli/config/item/rename_fqdn_yaml.rb +40 -0
- data/lib/simp/cli/config/item/rsync_base.rb +37 -0
- data/lib/simp/cli/config/item/rsync_server.rb +44 -0
- data/lib/simp/cli/config/item/rsync_timeout.rb +26 -0
- data/lib/simp/cli/config/item/set_grub_password.rb +19 -0
- data/lib/simp/cli/config/item/simp_yum_servers.rb +30 -0
- data/lib/simp/cli/config/item/use_auditd.rb +19 -0
- data/lib/simp/cli/config/item/use_fips.rb +46 -0
- data/lib/simp/cli/config/item/use_iptables.rb +22 -0
- data/lib/simp/cli/config/item/use_ldap.rb +19 -0
- data/lib/simp/cli/config/item/use_selinux.rb +32 -0
- data/lib/simp/cli/config/item/yum_repositories.rb +75 -0
- data/lib/simp/cli/config/item_list_factory.rb +236 -0
- data/lib/simp/cli/config/questionnaire.rb +86 -0
- data/lib/simp/cli/config/utils.rb +128 -0
- data/lib/simp/cli/lib/utils.rb +114 -0
- data/lib/simp/simp.rb +77 -0
- data/spec/lib/simp/cli/commands/config_spec.rb +42 -0
- data/spec/lib/simp/cli/config/item/add_ldap_to_hiera_spec.rb +58 -0
- data/spec/lib/simp/cli/config/item/answers_yaml_file_writer_spec.rb +86 -0
- data/spec/lib/simp/cli/config/item/certificates_spec.rb +50 -0
- data/spec/lib/simp/cli/config/item/client_nets_spec.rb +66 -0
- data/spec/lib/simp/cli/config/item/common_runlevel_default_spec.rb +27 -0
- data/spec/lib/simp/cli/config/item/dns_search_spec.rb +74 -0
- data/spec/lib/simp/cli/config/item/dns_servers_spec.rb +76 -0
- data/spec/lib/simp/cli/config/item/failover_log_servers_spec.rb +49 -0
- data/spec/lib/simp/cli/config/item/files/FakeCA/cacertkey +1 -0
- data/spec/lib/simp/cli/config/item/files/FakeCA/gencerts_nopass.sh +10 -0
- data/spec/lib/simp/cli/config/item/files/autosign.conf.new +11 -0
- data/spec/lib/simp/cli/config/item/files/autosign.conf.used +15 -0
- data/spec/lib/simp/cli/config/item/files/fileserver.conf +41 -0
- data/spec/lib/simp/cli/config/item/files/hosts +2 -0
- data/spec/lib/simp/cli/config/item/files/hosts.old_puppet_entry +3 -0
- data/spec/lib/simp/cli/config/item/files/puppet.conf +25 -0
- data/spec/lib/simp/cli/config/item/files/puppet.your.domain.yaml +21 -0
- data/spec/lib/simp/cli/config/item/files/resolv.conf__multiple +10 -0
- data/spec/lib/simp/cli/config/item/files/resolv.conf__single +4 -0
- data/spec/lib/simp/cli/config/item/files/rsyncd.conf +225 -0
- data/spec/lib/simp/cli/config/item/gateway_spec.rb +23 -0
- data/spec/lib/simp/cli/config/item/grub_password_spec.rb +24 -0
- data/spec/lib/simp/cli/config/item/hostname_conf_spec.rb +27 -0
- data/spec/lib/simp/cli/config/item/hostname_spec.rb +22 -0
- data/spec/lib/simp/cli/config/item/ipaddress_spec.rb +40 -0
- data/spec/lib/simp/cli/config/item/is_master_yum_server_spec.rb +29 -0
- data/spec/lib/simp/cli/config/item/ldap_base_dn_spec.rb +23 -0
- data/spec/lib/simp/cli/config/item/ldap_bind_dn_spec.rb +23 -0
- data/spec/lib/simp/cli/config/item/ldap_bind_hash_spec.rb +23 -0
- data/spec/lib/simp/cli/config/item/ldap_bind_pw_spec.rb +21 -0
- data/spec/lib/simp/cli/config/item/ldap_master_spec.rb +37 -0
- data/spec/lib/simp/cli/config/item/ldap_root_dn_spec.rb +23 -0
- data/spec/lib/simp/cli/config/item/ldap_root_hash_spec.rb +23 -0
- data/spec/lib/simp/cli/config/item/ldap_sync_dn_spec.rb +22 -0
- data/spec/lib/simp/cli/config/item/ldap_sync_hash_spec.rb +23 -0
- data/spec/lib/simp/cli/config/item/ldap_sync_pw_spec.rb +21 -0
- data/spec/lib/simp/cli/config/item/ldap_uri_spec.rb +32 -0
- data/spec/lib/simp/cli/config/item/log_servers_spec.rb +49 -0
- data/spec/lib/simp/cli/config/item/netmask_spec.rb +28 -0
- data/spec/lib/simp/cli/config/item/network_conf_spec.rb +63 -0
- data/spec/lib/simp/cli/config/item/network_dhcp_spec.rb +11 -0
- data/spec/lib/simp/cli/config/item/network_interface_spec.rb +26 -0
- data/spec/lib/simp/cli/config/item/network_setup_nic_spec.rb +29 -0
- data/spec/lib/simp/cli/config/item/ntp_servers_spec.rb +43 -0
- data/spec/lib/simp/cli/config/item/puppet_autosign_spec.rb +55 -0
- data/spec/lib/simp/cli/config/item/puppet_ca_port_spec.rb +23 -0
- data/spec/lib/simp/cli/config/item/puppet_ca_spec.rb +22 -0
- data/spec/lib/simp/cli/config/item/puppet_conf_spec.rb +110 -0
- data/spec/lib/simp/cli/config/item/puppet_fileserver_spec.rb +53 -0
- data/spec/lib/simp/cli/config/item/puppet_hosts_entry_spec.rb +85 -0
- data/spec/lib/simp/cli/config/item/puppet_server_ip_spec.rb +24 -0
- data/spec/lib/simp/cli/config/item/puppet_server_spec.rb +22 -0
- data/spec/lib/simp/cli/config/item/puppetdb_port_spec.rb +25 -0
- data/spec/lib/simp/cli/config/item/puppetdb_server_spec.rb +25 -0
- data/spec/lib/simp/cli/config/item/remove_ldap_from_hiera_spec.rb +58 -0
- data/spec/lib/simp/cli/config/item/rename_fqdn_yaml_spec.rb +63 -0
- data/spec/lib/simp/cli/config/item/rsync_base_spec.rb +28 -0
- data/spec/lib/simp/cli/config/item/rsync_server_spec.rb +41 -0
- data/spec/lib/simp/cli/config/item/rsync_timeout_spec.rb +21 -0
- data/spec/lib/simp/cli/config/item/set_grub_password_spec.rb +29 -0
- data/spec/lib/simp/cli/config/item/simp_yum_servers_spec.rb +41 -0
- data/spec/lib/simp/cli/config/item/spec_helper.rb +22 -0
- data/spec/lib/simp/cli/config/item/use_auditd_spec.rb +29 -0
- data/spec/lib/simp/cli/config/item/use_fips_spec.rb +29 -0
- data/spec/lib/simp/cli/config/item/use_iptables_spec.rb +29 -0
- data/spec/lib/simp/cli/config/item/use_ldap_spec.rb +29 -0
- data/spec/lib/simp/cli/config/item/use_selinux_spec.rb +24 -0
- data/spec/lib/simp/cli/config/item/yum_repositories_spec.rb +94 -0
- data/spec/lib/simp/cli/config/item_spec.rb +106 -0
- data/spec/lib/simp/cli/config/spec_helper.rb +1 -0
- data/spec/lib/simp/cli/config/utils_spec.rb +131 -0
- data/spec/lib/simp/cli/spec_helper.rb +1 -0
- data/spec/spec_helper.rb +91 -0
- metadata +391 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Simp::Cli::Commands; end
|
|
2
|
+
|
|
3
|
+
class Simp::Cli::Commands::Doc < Simp::Cli
|
|
4
|
+
def self.run(args = Array.new)
|
|
5
|
+
raise "Package 'simp-doc' is not installed, cannot continue" unless system("rpm -q --quiet simp-doc")
|
|
6
|
+
pupdoc = %x{rpm -ql simp-doc | grep html/index.html$ | head -1}.strip.chomp
|
|
7
|
+
raise "Could not find the SIMP documentation. Please ensure that you can access '#{pupdoc}'." unless File.exists?(pupdoc)
|
|
8
|
+
exec("links #{pupdoc}")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.help
|
|
12
|
+
puts "Show SIMP documentation in elinks"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
module Simp::Cli::Commands; end
|
|
2
|
+
|
|
3
|
+
class Simp::Cli::Commands::Passgen < Simp::Cli
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
|
|
6
|
+
@target_dir = '/etc/puppet/modules/site/files/gen_passwd'
|
|
7
|
+
@show_list = false
|
|
8
|
+
@show_users = Array.new
|
|
9
|
+
@set_users = Array.new
|
|
10
|
+
@remove_users = Array.new
|
|
11
|
+
|
|
12
|
+
@opt_parser = OptionParser.new do |opts|
|
|
13
|
+
opts.banner = "\n === The SIMP Passgen Tool === "
|
|
14
|
+
opts.separator ""
|
|
15
|
+
opts.separator "The SIMP Passgen Tool is a simple password control utility. It allows the"
|
|
16
|
+
opts.separator "viewing, setting, and removal of user passwords."
|
|
17
|
+
opts.separator ""
|
|
18
|
+
opts.separator "OPTIONS:\n"
|
|
19
|
+
|
|
20
|
+
opts.on("-d", "--dir DIRECTORY", "Where the passgen passwords are stored.") do |dir|
|
|
21
|
+
@target_dir = dir
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
opts.on("-l", "--list", "List possible usernames upon whic to operate") do
|
|
25
|
+
@show_list = true
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
opts.on("-u", "--user USER1[,USER2,USER3]", Array, "Show password(s) for USERNAME") do |name|
|
|
29
|
+
@show_users = name
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
opts.on("-s", "--set USER1[,USER2,USER3]", Array, "Set password for USERNAME") do |name|
|
|
33
|
+
@set_users = name
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
opts.on("-r", "--remove USER1[,USER2,USER3]", Array, "Remove all passwords for USERNAME") do |name|
|
|
37
|
+
@remove_users = name
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
opts.on("-h", "--help", "Print this message.") do
|
|
41
|
+
puts opts
|
|
42
|
+
exit 0
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.run(args = Array.new)
|
|
47
|
+
super
|
|
48
|
+
|
|
49
|
+
raise "The SIMP Passgen Tool requires at least one argument to work" if args.empty?
|
|
50
|
+
raise "Target directory '#{@target_dir}' does not exist" unless File.directory?(@target_dir)
|
|
51
|
+
|
|
52
|
+
begin
|
|
53
|
+
Dir.chdir(@target_dir) do
|
|
54
|
+
@user_names = Dir.glob("*").map { |x| x = File.basename(x, '.last') }.sort.uniq.select do |name|
|
|
55
|
+
File.ftype("#{@target_dir}/#{name}").eql?("file")
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
rescue => err
|
|
59
|
+
raise "Error occured while accessing '#{@target_dir}':\n Causing Error: #{err}"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
if @show_list
|
|
63
|
+
puts "Usernames:\n\t#{@user_names.join("\n\t")}"
|
|
64
|
+
puts
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
@show_users.each do |user|
|
|
68
|
+
if @user_names.include?(user)
|
|
69
|
+
Dir.chdir(@target_dir) do
|
|
70
|
+
puts "Username: #{user}"
|
|
71
|
+
current_password = File.open("#{@target_dir}/#{user}", 'r').gets
|
|
72
|
+
puts " Current: #{current_password}"
|
|
73
|
+
last_password = nil
|
|
74
|
+
last_password_file = "#{@target_dir}/#{user}.last"
|
|
75
|
+
if File.exists?(last_password_file)
|
|
76
|
+
last_password = File.open(last_password_file, 'r').gets
|
|
77
|
+
end
|
|
78
|
+
puts " Previous: #{lass_password}" if last_password
|
|
79
|
+
end
|
|
80
|
+
else
|
|
81
|
+
raise "Invalid username '#{user}' selected.\n\n Valid: #{@user_names.join(', ')}"
|
|
82
|
+
end
|
|
83
|
+
puts
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
@set_users.each do |user|
|
|
87
|
+
password_filename = "#{@target_dir}/#{user}"
|
|
88
|
+
|
|
89
|
+
puts "Username: #{user}"
|
|
90
|
+
password = Utils.get_password
|
|
91
|
+
if File.exists?(password_filename)
|
|
92
|
+
if Utils.yes_or_no("Would you like to rotate the old password?", false)
|
|
93
|
+
begin
|
|
94
|
+
FileUtils.mv(password_filename, password_filename + '.last')
|
|
95
|
+
rescue => err
|
|
96
|
+
raise "Error occurred while moving '#{password_filename}' to '#{password_filename + '.last'}'\n Causing Error: #{err}"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
begin
|
|
101
|
+
File.open(password_filename, 'w') { |file| file.puts password }
|
|
102
|
+
rescue => err
|
|
103
|
+
raise "Error occurred while writing '#{password_filename}'\n Causing Error: #{err}"
|
|
104
|
+
end
|
|
105
|
+
puts
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
@remove_users.each do |user|
|
|
109
|
+
password_filename = "#{@target_dir}/#{user}"
|
|
110
|
+
|
|
111
|
+
if File.exists?(password_filename)
|
|
112
|
+
if Utils.yes_or_no("Are you sure you want to remove all entries for #{user}?", false)
|
|
113
|
+
show_password(user)
|
|
114
|
+
|
|
115
|
+
last_password_filename = password_filename + '.last'
|
|
116
|
+
if File.exists?(last_password_filename)
|
|
117
|
+
File.delete(last_password_filename)
|
|
118
|
+
puts "#{last_password_filename} deleted"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
File.delete(password_filename)
|
|
122
|
+
puts "#{password_filename} deleted"
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
puts
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module Simp::Cli::Commands; end
|
|
2
|
+
|
|
3
|
+
class Simp::Cli::Commands::Puppeteval < Simp::Cli
|
|
4
|
+
require 'facter'
|
|
5
|
+
|
|
6
|
+
def self.help
|
|
7
|
+
puts "This tool gathers metric information for a Puppet run that it will run."
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.run(args = Array.new)
|
|
11
|
+
|
|
12
|
+
data = {
|
|
13
|
+
:facter => Facter.to_hash,
|
|
14
|
+
:puppet_tags => ['--test','--evaltrace','--summarize'],
|
|
15
|
+
:cpuinfo => [],
|
|
16
|
+
:meminfo => {},
|
|
17
|
+
:summarize => {},
|
|
18
|
+
:evaltrace => []
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
proc_hash = Hash.new
|
|
22
|
+
File.open('/proc/cpuinfo', 'r').each do |line|
|
|
23
|
+
if line =~ /(.*)\s*: (.*)/
|
|
24
|
+
proc_hash[$1.strip] = $2
|
|
25
|
+
elsif line =~ /\A\s*\z/
|
|
26
|
+
data[:cpuinfo] << proc_hash
|
|
27
|
+
proc_hash = Hash.new
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
data[:cpuinfo] << proc_hash if !proc_hash.empty?
|
|
31
|
+
|
|
32
|
+
File.open('/proc/meminfo', 'r').each do |line|
|
|
33
|
+
if line =~ /(.*):\s*([0-9]*( kB)?)/
|
|
34
|
+
data[:meminfo][$1] = $2
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Wait for puppet to not be currently running...
|
|
39
|
+
puppet_running = true
|
|
40
|
+
found_puppet = false
|
|
41
|
+
while puppet_running do
|
|
42
|
+
ps_output = %x{/bin/ps aux | /bin/grep puppet}.each_line do |l|
|
|
43
|
+
if l =~ /\/usr\/s?bin\/puppet(d|\s+agent)/
|
|
44
|
+
sleep(2)
|
|
45
|
+
found_puppet = true
|
|
46
|
+
break
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
puppet_running = found_puppet
|
|
50
|
+
found_puppet = false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
PTY.spawn("/usr/bin/puppet agent --test --evaltrace --summarize 2> /dev/null") do |read, write, pid|
|
|
54
|
+
begin
|
|
55
|
+
at_summary = false
|
|
56
|
+
read.each do |line|
|
|
57
|
+
if at_summary
|
|
58
|
+
if line =~ /\A(\S+.*):/
|
|
59
|
+
summary_hash = $1
|
|
60
|
+
data[:summarize][summary_hash] = Hash.new
|
|
61
|
+
elsif line =~ /\s+(\S+.*): ([0-9\.]*)/
|
|
62
|
+
data[:summarize][summary_hash][$1] = $2
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
if line =~ /info: (.*): Evaluated in (.*) seconds/
|
|
66
|
+
data[:evaltrace] << { :resource => $1, :time => $2 }
|
|
67
|
+
elsif line =~ /notice: Finished catalog run in ([0-9\.]*) seconds/
|
|
68
|
+
data[:evaltrace] << { :resource => "catalog run", :time => $1 }
|
|
69
|
+
at_summary = true
|
|
70
|
+
end
|
|
71
|
+
puts line
|
|
72
|
+
end
|
|
73
|
+
rescue Errno::EIO
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
FileUtils.mkdir_p("/var/tmp/simp_mit/")
|
|
78
|
+
File.open( "/var/tmp/simp_mit/simp_#{Time.now.to_i}_#{Socket.gethostname}.yml", 'w') do |file|
|
|
79
|
+
YAML::dump(data, file)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module Simp::Cli::Commands; end
|
|
2
|
+
|
|
3
|
+
class Simp::Cli::Commands::Runpuppet < Simp::Cli
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
|
|
6
|
+
@conf_dir = File.expand_path('~/.simp')
|
|
7
|
+
@host_file = "#{@conf_dir}/hosts"
|
|
8
|
+
@gen_host_list = true
|
|
9
|
+
@max_parallel = 10
|
|
10
|
+
@timeout = -1
|
|
11
|
+
|
|
12
|
+
@opt_parser = OptionParser.new do |opts|
|
|
13
|
+
opts.banner = "\n === The SIMP RunPuppet Tool ==="
|
|
14
|
+
opts.separator ""
|
|
15
|
+
opts.separator "The SIMP RunPuppet Tool allows you to run the Puppet agent on a list of hosts."
|
|
16
|
+
opts.separator ""
|
|
17
|
+
opts.separator "Some requirements to use the tool:"
|
|
18
|
+
opts.separator " * the user must have SSH access to all of the list hosts"
|
|
19
|
+
opts.separator " * the user cannot be root"
|
|
20
|
+
opts.separator " * each target host must be able to run, with sudo, the following commands:"
|
|
21
|
+
opts.separator " - /usr/sbin/puppetd"
|
|
22
|
+
opts.separator " - /usr/sbin/puppetca"
|
|
23
|
+
opts.separator ""
|
|
24
|
+
opts.separator "OPTIONS:\n"
|
|
25
|
+
|
|
26
|
+
opts.on("-H", "--hosts FILE", "FILE containing a list of hosts that Puppet should be run on") do |file|
|
|
27
|
+
@host_file = file
|
|
28
|
+
@gen_host_list = false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
opts.on("-p", "--par NUM", "Maximum number of parallel threads. Defaults to 10.") do |num|
|
|
32
|
+
@max_parallel = num
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
opts.on("-t", "--timeout SEC", "Set timeout to SEC seconds. Defaults to -1 (no timeout).",
|
|
36
|
+
"\033[1mWARNING\033[0m: If your Puppet run takes more than SEC seconds, very bad things can happen!",
|
|
37
|
+
"(i.e. your Puppet run will be killed)") do |sec|
|
|
38
|
+
@timeout = sec
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
opts.on("-h", "--help", "Print this message") do
|
|
42
|
+
puts opts
|
|
43
|
+
exit 0
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.run(args = Array.new)
|
|
48
|
+
super
|
|
49
|
+
|
|
50
|
+
raise Simp::Runpuppet::Error.new("SIMP RunPuppet cannot be run as 'root'.") if Process.uid == 0
|
|
51
|
+
|
|
52
|
+
host_list = Array.new
|
|
53
|
+
if @gen_host_list
|
|
54
|
+
host_list = %x{cd /;sudo /usr/sbin/puppetca --list --all}.split("\n").map do |host|
|
|
55
|
+
host.split(/\(.*\)/).first.split(/\s+/).last.delete('"')
|
|
56
|
+
end
|
|
57
|
+
else
|
|
58
|
+
File.open(@host_file).each_line do |line|
|
|
59
|
+
host_list << line.chomp
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
host_list.compact!
|
|
63
|
+
|
|
64
|
+
system("echo '#{ "Please review the lists of hosts to run puppet on:\n - #{host_list.join("\n - ")}" }' | less -F")
|
|
65
|
+
|
|
66
|
+
if Utils.yes_or_no("Run Puppet on all of the listed hosts?", false)
|
|
67
|
+
host_errors = Array.new
|
|
68
|
+
if @gen_host_list
|
|
69
|
+
File.open(@host_file, 'w') do
|
|
70
|
+
host_list.each { |host| file.puts host }
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
puts "This may take some time..."
|
|
75
|
+
%x{pssh -f -t #{@timeout} -p #{@max_parallel} -h #{@host_file} -OStrictHostKeyChecking=no "cd /; sudo /usr/sbin/puppetd --test"}.each_line do |line|
|
|
76
|
+
puts line
|
|
77
|
+
if line =~ /\[\d+\].*\[FAILURE\]\s([A-Za-z0-9\-\.]+).*/
|
|
78
|
+
host_errors << $1
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
if host_errors.empty?
|
|
83
|
+
puts "Successfully ran Puppet for the #{host_list.size} hosts listed in #{@host_file}."
|
|
84
|
+
else
|
|
85
|
+
timestamp = Time.new.strftime("%Y%m%d%H%M")
|
|
86
|
+
filepath = File.expand_path("#{@conf_dir}/pssh_error#{timestamp}")
|
|
87
|
+
FileUtils.mkpath(File.dirname(filepath))
|
|
88
|
+
File.open(filepath, 'w') do file
|
|
89
|
+
host_errors.each { |err| file.puts err }
|
|
90
|
+
end
|
|
91
|
+
raise "Errors while running Puppet, outputting list of hosts with errors to #{@conf_dir}/pssh_error#{timestamp}"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
require 'highline/import'
|
|
2
|
+
require 'puppet'
|
|
3
|
+
require 'yaml'
|
|
4
|
+
require File.expand_path( 'utils', File.dirname(__FILE__) )
|
|
5
|
+
require 'highline'
|
|
6
|
+
|
|
7
|
+
module Simp; end
|
|
8
|
+
class Simp::Cli; end
|
|
9
|
+
module Simp::Cli::Config
|
|
10
|
+
class Item
|
|
11
|
+
attr_accessor :key, :value, :description, :fact
|
|
12
|
+
attr_accessor :skip_query, :skip_apply, :skip_yaml, :silent
|
|
13
|
+
attr_accessor :die_on_apply_fail, :allow_user_apply
|
|
14
|
+
attr_accessor :config_items
|
|
15
|
+
attr_accessor :next_items_tree
|
|
16
|
+
attr_accessor :fail_on_missing_answer
|
|
17
|
+
|
|
18
|
+
def initialize(key = nil, description = nil)
|
|
19
|
+
@key = key # answers file key for the config Item
|
|
20
|
+
@description = description # A text description of the Item
|
|
21
|
+
@value = nil # value (decided by user)
|
|
22
|
+
@fact = nil # Facter fact to query OS value
|
|
23
|
+
|
|
24
|
+
@skip_query = false # skip the query and use the default_value
|
|
25
|
+
@skip_apply = false # skip the apply
|
|
26
|
+
@skip_yaml = false # skip yaml output
|
|
27
|
+
@silent = false # no output to stdout/Highline
|
|
28
|
+
@die_on_apply_fail = false # halt simp config if apply fails
|
|
29
|
+
@allow_user_apply = false # allow non-superuser to apply
|
|
30
|
+
@fail_on_missing_answer = false # error out if @value is not pre-populated
|
|
31
|
+
|
|
32
|
+
@config_items = {} # a hash of all previous Config::Items
|
|
33
|
+
# a Hash of additional Items that this Item may need to add to the Queue
|
|
34
|
+
# the keys of the Has are used to look up the queue
|
|
35
|
+
# format:
|
|
36
|
+
# 'answer1' => [ Item1, Item2, .. ]
|
|
37
|
+
# 'answer2' => [ Item3, Item4, .. ]
|
|
38
|
+
@next_items_tree = {}
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# methods used to infer Item#value
|
|
42
|
+
# --------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
# value of item as read from OS (via Facter)
|
|
45
|
+
def os_value
|
|
46
|
+
Facter.value( @fact ) unless @fact.nil?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# value of Item as read from puppet (via Hiera) #TODO: not used yet
|
|
51
|
+
def puppet_value; nil; end
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# value of Item as recommended by Very Clever Logic (tm)
|
|
55
|
+
def recommended_value; nil; end
|
|
56
|
+
# --------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# String in yaml answer file format, with comments (if any)
|
|
60
|
+
def to_yaml_s
|
|
61
|
+
fail '@key is empty' if "#{@key}".empty?
|
|
62
|
+
|
|
63
|
+
x = "=== #{@key} ===\n"
|
|
64
|
+
x += "#{(description || 'FIXME: NO DESCRIPTION GIVEN')}\n"
|
|
65
|
+
|
|
66
|
+
# comment every line that describes the item:
|
|
67
|
+
x = x.each_line.map{ |y| "# #{y}" }.join
|
|
68
|
+
|
|
69
|
+
# add yaml (but stripped of frontmatter and first indent)
|
|
70
|
+
# TODO: should we be using SafeYAML? http://danieltao.com/safe_yaml/
|
|
71
|
+
x += { @key => @value }.to_yaml.gsub(/^---\s*\n/m, '').gsub(/^ /, '' )
|
|
72
|
+
x += "\n"
|
|
73
|
+
|
|
74
|
+
if @skip_yaml
|
|
75
|
+
x.gsub( /^/, '### ' )
|
|
76
|
+
else
|
|
77
|
+
x
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# --------------------------------------------------------------------------
|
|
83
|
+
# Pretty stdout/stdin methods
|
|
84
|
+
# --------------------------------------------------------------------------
|
|
85
|
+
# print a pretty banner to describe an item
|
|
86
|
+
def print_banner
|
|
87
|
+
return if @silent
|
|
88
|
+
say_blue "=== #{@key} ===", ['BOLD']
|
|
89
|
+
say_blue description
|
|
90
|
+
say_blue " - os value: #{os_value}" if os_value
|
|
91
|
+
say_blue " - os value: #{puppet_value}" if puppet_value
|
|
92
|
+
say_blue " - recommended value: #{recommended_value}" if recommended_value
|
|
93
|
+
say_blue " - chosen value: #{@value}" if @value
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# print a pretty summary of the Item's key+value, printed to stdout
|
|
98
|
+
def print_summary
|
|
99
|
+
return if @silent
|
|
100
|
+
fail '@key is empty' if "#{@key}".empty?
|
|
101
|
+
say( "#{@key} = '<%= color( %q{#{value}}, BOLD )%>'\n" )
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# choose @value of Item
|
|
106
|
+
def query
|
|
107
|
+
extra = query_status
|
|
108
|
+
|
|
109
|
+
if @value.nil? && @fail_on_missing_answer
|
|
110
|
+
say( "<%= color(%q(FATAL: no answer for '), RED) %>" +
|
|
111
|
+
"<%= color(%q{#{extra}#{@key}}, BOLD, RED)%>" +
|
|
112
|
+
"<%= color(%q('; exiting!), RED)%>\n" )
|
|
113
|
+
exit 1
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
if !@skip_query && @value.nil?
|
|
117
|
+
print_banner
|
|
118
|
+
@value = query_ask
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# summarize the item's status after the query is complete
|
|
122
|
+
say( "#{extra}#{@key} = '<%= color( %q{#{@value}}, BOLD )%>'\n" ) unless @silent
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def query_status
|
|
127
|
+
extra = ''
|
|
128
|
+
if !@value.nil?
|
|
129
|
+
extra = "<%= color( %q{(answered)}, CYAN, BOLD)%> "
|
|
130
|
+
elsif @skip_query
|
|
131
|
+
extra = "<%= color( %q{(noninteractive)}, CYAN, BOLD)%> "
|
|
132
|
+
@value = default_value
|
|
133
|
+
end
|
|
134
|
+
extra
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
# ask an interactive question (via stdout/stdin)
|
|
139
|
+
def query_ask
|
|
140
|
+
# NOTE: This trailing space at the end of the String obliquely instructs
|
|
141
|
+
# Highline to keep the prompt on the same line as the question. If the
|
|
142
|
+
# String did not end with a space or tab, Highline would move the input
|
|
143
|
+
# prompt to the next line (which, for our purposes, looks confusing)
|
|
144
|
+
value = ask( "<%= color('#{@key}', WHITE, BOLD) %>: ",
|
|
145
|
+
highline_question_type ) do |q|
|
|
146
|
+
q.default = default_value unless default_value.to_s.empty?
|
|
147
|
+
|
|
148
|
+
# validate input via the validate() method
|
|
149
|
+
q.validate = lambda{ |x| validate( x )}
|
|
150
|
+
|
|
151
|
+
# if the answer is not valid, construct a reply:
|
|
152
|
+
q.responses[:not_valid] = "<%= color( %q{Invalid answer!}, RED ) %>\n"
|
|
153
|
+
q.responses[:not_valid] += "<%= color( %q{#{ (not_valid_message || description) }}, YELLOW) %>\n"
|
|
154
|
+
q.responses[:not_valid] += "#{q.question} |#{q.default}|"
|
|
155
|
+
|
|
156
|
+
query_extras q
|
|
157
|
+
end
|
|
158
|
+
value
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# returns the default answer to Item#query
|
|
163
|
+
def default_value
|
|
164
|
+
@value || recommended_value
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def query_extras( q ); q; end
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# returns true if x is a valid value
|
|
172
|
+
def validate( _x )
|
|
173
|
+
msg = 'ERROR: Item.validate() not implemented!'
|
|
174
|
+
msg += "\nTODO: cover common type-based validations?"
|
|
175
|
+
msg += "\nTODO: Offer validation objects?"
|
|
176
|
+
fail msg
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def next_items
|
|
181
|
+
@next_items_tree.fetch( @value, [] )
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# optional message to show users when invalid input is entered
|
|
186
|
+
def not_valid_message; nil; end
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
# A helper method that highline can use to cast String answers to the ask
|
|
190
|
+
# in query(). nil means don't cast, Date casts into a date, etc.
|
|
191
|
+
# A lambda can be used for sanitization.
|
|
192
|
+
#
|
|
193
|
+
# Descendants of Item are very likely to override this method.
|
|
194
|
+
def highline_question_type; nil; end
|
|
195
|
+
|
|
196
|
+
# convenience_method to print formatted information
|
|
197
|
+
def say_blue( msg, options=[] )
|
|
198
|
+
options = options.unshift( '' ) unless options.empty?
|
|
199
|
+
say "<%= color(%q{#{msg}}, CYAN #{options.join(', ')}) %>\n" unless @silent
|
|
200
|
+
end
|
|
201
|
+
def say_yellow( msg, options=[] )
|
|
202
|
+
options = options.unshift( '' ) unless options.empty?
|
|
203
|
+
say "<%= color(%q{#{msg}}, YELLOW #{options.join(', ')}) %>\n" unless @silent
|
|
204
|
+
end
|
|
205
|
+
def say_red( msg, options=[] )
|
|
206
|
+
options = options.unshift( '' ) unless options.empty?
|
|
207
|
+
say "<%= color(%q{#{msg}}, RED #{options.join(', ')}) %>\n" unless @silent
|
|
208
|
+
end
|
|
209
|
+
def say_green( msg, options=[] )
|
|
210
|
+
options = options.unshift( '' ) unless options.empty?
|
|
211
|
+
say "<%= color(%q{#{msg}}, GREEN #{options.join(', ')}) %>\n" unless @silent
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def safe_apply; nil; end
|
|
216
|
+
def apply; nil; end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# A Item that asks for lists instead of Strings
|
|
222
|
+
#
|
|
223
|
+
# note that @value is a Strin
|
|
224
|
+
class YesNoItem < Item
|
|
225
|
+
def not_valid_message
|
|
226
|
+
"enter 'yes' or 'no'"
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def validate( v )
|
|
230
|
+
return true if (v.class == TrueClass || v.class == FalseClass)
|
|
231
|
+
( v =~ /^(y(es)?|true|false|no?)$/i ) ? true : false
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# NOTE: Highline should transform the input to a boolean but doesn't. Why?
|
|
235
|
+
# REJECTED: Override #query_ask using Highline's #agree? *** no, can't bool
|
|
236
|
+
def highline_question_type
|
|
237
|
+
lambda do |str|
|
|
238
|
+
return true if ( str =~ /^(y(es)?|true)$/i ? true : false || str.class == TrueClass )
|
|
239
|
+
return false if ( str =~ /^(n(o)?|false)$/i ? true : false || str.class == FalseClass )
|
|
240
|
+
nil
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# NOTE: when used from query_ask, the highline_question_type lamba doesn't
|
|
245
|
+
# always cast internal type of @value to a boolean. As a workaround, we
|
|
246
|
+
# cast it here before it is committed to the super's YAML output.
|
|
247
|
+
def to_yaml_s
|
|
248
|
+
_value = @value
|
|
249
|
+
@value = highline_question_type.call @value
|
|
250
|
+
x = super
|
|
251
|
+
@value = _value
|
|
252
|
+
x
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def next_items
|
|
256
|
+
@next_items_tree.fetch( highline_question_type.call( @value ), [] )
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
# An Item that asks for Passwords, with:
|
|
264
|
+
# - special validation
|
|
265
|
+
# - invisible input
|
|
266
|
+
# - optional password generation
|
|
267
|
+
class PasswordItem < Item
|
|
268
|
+
attr_accessor :can_generate, :generate_by_default
|
|
269
|
+
def initialize
|
|
270
|
+
super
|
|
271
|
+
@can_generate = true
|
|
272
|
+
@generate_by_default = true
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def query_extras( q )
|
|
277
|
+
q.echo = '*' # don't print password characters to stdout
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def encrypt( password, salt=nil )
|
|
282
|
+
say_yellow 'WARNING: password not encrypted; override in child class'
|
|
283
|
+
password
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def query_generate_password
|
|
288
|
+
password = false
|
|
289
|
+
default = @generate_by_default ? 'yes' : 'no'
|
|
290
|
+
if agree( "auto-generate a password? " ){ |q| q.default = default }
|
|
291
|
+
password = Simp::Cli::Config::Utils.generate_password
|
|
292
|
+
say "<%= color( %q{#{''.ljust(80,'-')}}, GREEN)%>\n"
|
|
293
|
+
say '<%= color( %q{NOTE: }, GREEN, BOLD)%>' +
|
|
294
|
+
"<%= color( %q{ the generated password is: }) %>\n"
|
|
295
|
+
say "\n"
|
|
296
|
+
say "<%= color( %q{ #{password}}, YELLOW, BOLD )%> "
|
|
297
|
+
say "\n"
|
|
298
|
+
say "\n"
|
|
299
|
+
say 'Please remember it!'
|
|
300
|
+
say "<%= color( %q{#{''.ljust(80,'-')}}, GREEN)%>\n"
|
|
301
|
+
say_blue '*** Press enter to continue ***' , ['BOLD', 'BLINK']
|
|
302
|
+
ask ''
|
|
303
|
+
end
|
|
304
|
+
password
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
# ask for the password twice (and verify that both match)
|
|
309
|
+
def query_ask
|
|
310
|
+
password = false
|
|
311
|
+
password = query_generate_password if @can_generate
|
|
312
|
+
|
|
313
|
+
while !password
|
|
314
|
+
answers = []
|
|
315
|
+
[0,1].each{ |x|
|
|
316
|
+
say "please enter a password:" if x == 0
|
|
317
|
+
say "please confirm the password:" if x == 1
|
|
318
|
+
answers[x] = super
|
|
319
|
+
}
|
|
320
|
+
if answers.first == answers.last
|
|
321
|
+
password = answers.first
|
|
322
|
+
else
|
|
323
|
+
say_yellow( 'WARNING: passwords did not match! Please try again.' )
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
encrypt password
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def validate x
|
|
332
|
+
result = true
|
|
333
|
+
begin
|
|
334
|
+
Simp::Cli::Config::Utils.validate_password x
|
|
335
|
+
rescue Simp::Cli::Config::PasswordError => e
|
|
336
|
+
say_yellow "WARNING: Invalid Password: #{e.message}"
|
|
337
|
+
result = false
|
|
338
|
+
end
|
|
339
|
+
result
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
# A Item that asks for lists instead of Strings
|
|
345
|
+
#
|
|
346
|
+
# note that @value is now an Array
|
|
347
|
+
class ListItem < Item
|
|
348
|
+
attr_accessor :allow_empty_list
|
|
349
|
+
|
|
350
|
+
def initialize
|
|
351
|
+
super
|
|
352
|
+
@allow_empty_list = false
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def not_valid_message
|
|
356
|
+
extra = 'hit enter to skip'
|
|
357
|
+
extra = "hit enter to accept default value" if default_value
|
|
358
|
+
"enter a comma or space-delimited list (#{extra})"
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def query_extras( q )
|
|
362
|
+
# NOTE: this is a hack to massage Array input to/from a highline query.
|
|
363
|
+
# It would probably be better (but more complex) to provide native Array
|
|
364
|
+
# support for highline.
|
|
365
|
+
# TODO: Override #query_ask using Highline's #gather?
|
|
366
|
+
q.default = q.default.join( " " ) if q.default.is_a? Array
|
|
367
|
+
reminder = ::HighLine.color( not_valid_message,
|
|
368
|
+
::HighLine.const_get('YELLOW') )
|
|
369
|
+
q.question = "#{reminder}\n#{q.question}"
|
|
370
|
+
q
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def highline_question_type
|
|
374
|
+
# Convert the String (delimited by comma and/or whitespace) answer into an array
|
|
375
|
+
lambda { |str|
|
|
376
|
+
str = str.split(/,\s*|,?\s+/) if str.is_a? String
|
|
377
|
+
str
|
|
378
|
+
}
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
# validate the list and each item in the list
|
|
382
|
+
def validate( list )
|
|
383
|
+
# reuse the highline lambda to santize input
|
|
384
|
+
return true if (@allow_empty_list && list.nil?)
|
|
385
|
+
list = highline_question_type.call( list ) if !list.is_a? Array
|
|
386
|
+
return false if !list.is_a?(Array)
|
|
387
|
+
return false if (!@allow_empty_list && list.empty? )
|
|
388
|
+
list.each{ |item|
|
|
389
|
+
return false if !validate_item( item )
|
|
390
|
+
}
|
|
391
|
+
true
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
# validate a single list item
|
|
395
|
+
def validate_item( x )
|
|
396
|
+
fail 'not implemented!'
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
# mixin that provides common logic for safe_apply()
|
|
402
|
+
module SafeApplying
|
|
403
|
+
def safe_apply
|
|
404
|
+
extra = ''
|
|
405
|
+
not_root_msg = ''
|
|
406
|
+
if !@allow_user_apply
|
|
407
|
+
not_root_msg = ENV.fetch('USER') == 'root' ? '' : ' [**user is not root**] '
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
if @skip_apply || (not_root_msg.size != 0)
|
|
411
|
+
extra = "<%= color( %q{(skipping apply#{not_root_msg})}, MAGENTA, BOLD)%> "
|
|
412
|
+
say( "#{extra}#{@key}" ) unless @silent
|
|
413
|
+
if !(@value.nil? || @value.class == TrueClass || @value.class == FalseClass || @value.empty?)
|
|
414
|
+
say( "= '<%= color( %q{#{@value}}, BOLD )%>'\n" ) unless @silent
|
|
415
|
+
end
|
|
416
|
+
else
|
|
417
|
+
extra = "<%= color( %q{(applying changes)}, GREEN, BOLD)%> "
|
|
418
|
+
say( "#{extra}for #{@key}\n" ) unless @silent
|
|
419
|
+
begin
|
|
420
|
+
result = apply
|
|
421
|
+
if result
|
|
422
|
+
extra = "<%= color( %q{(change applied)}, GREEN, BOLD)%> "
|
|
423
|
+
else
|
|
424
|
+
extra = "<%= color( %q{(change failed)}, RED, BOLD)%> "
|
|
425
|
+
end
|
|
426
|
+
say( "#{extra}for #{@key}\n" ) unless @silent
|
|
427
|
+
rescue Exception => e
|
|
428
|
+
extra = "<%= color( %q{(change failed)}, RED, BOLD) %> "
|
|
429
|
+
say( "#{extra}for #{@key}:\n#{e.message}" )
|
|
430
|
+
say "<%= color( %q{#{e.message.to_s.gsub( /^/, ' ' )}}, RED) %> \n"
|
|
431
|
+
|
|
432
|
+
# Some failures should be punished by death
|
|
433
|
+
fail e if @die_on_apply_fail
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
# A special Item that is never interactive, but applies some configuration
|
|
441
|
+
class ActionItem < Item
|
|
442
|
+
include Simp::Cli::Config::SafeApplying
|
|
443
|
+
|
|
444
|
+
def initialize
|
|
445
|
+
super
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
# internal method to change the system (returns the result of the apply)
|
|
449
|
+
def apply; nil; end
|
|
450
|
+
|
|
451
|
+
# don't be interactive!
|
|
452
|
+
def validate( x ); true; end
|
|
453
|
+
def query; nil; end
|
|
454
|
+
def to_yaml_s; nil; end
|
|
455
|
+
end
|
|
456
|
+
end
|