simp-cli 1.0.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (150) hide show
  1. checksums.yaml +15 -0
  2. data/LICENSE +27 -0
  3. data/README.md +48 -0
  4. data/Rakefile +142 -0
  5. data/bin/simp +5 -0
  6. data/lib/simp/cli.rb +88 -0
  7. data/lib/simp/cli/commands/bootstrap.rb +275 -0
  8. data/lib/simp/cli/commands/check.rb +163 -0
  9. data/lib/simp/cli/commands/cleancerts.rb +114 -0
  10. data/lib/simp/cli/commands/config.rb +235 -0
  11. data/lib/simp/cli/commands/doc.rb +14 -0
  12. data/lib/simp/cli/commands/passgen.rb +128 -0
  13. data/lib/simp/cli/commands/puppeteval.rb +82 -0
  14. data/lib/simp/cli/commands/runpuppet.rb +95 -0
  15. data/lib/simp/cli/config/item.rb +456 -0
  16. data/lib/simp/cli/config/item/add_ldap_to_hiera.rb +43 -0
  17. data/lib/simp/cli/config/item/answers_yaml_file_writer.rb +58 -0
  18. data/lib/simp/cli/config/item/certificates.rb +39 -0
  19. data/lib/simp/cli/config/item/client_nets.rb +65 -0
  20. data/lib/simp/cli/config/item/common_runlevel_default.rb +32 -0
  21. data/lib/simp/cli/config/item/dns_search.rb +48 -0
  22. data/lib/simp/cli/config/item/dns_servers.rb +57 -0
  23. data/lib/simp/cli/config/item/failover_log_servers.rb +27 -0
  24. data/lib/simp/cli/config/item/gateway.rb +32 -0
  25. data/lib/simp/cli/config/item/grub_password.rb +51 -0
  26. data/lib/simp/cli/config/item/hostname.rb +24 -0
  27. data/lib/simp/cli/config/item/hostname_conf.rb +48 -0
  28. data/lib/simp/cli/config/item/ipaddress.rb +46 -0
  29. data/lib/simp/cli/config/item/is_master_yum_server.rb +23 -0
  30. data/lib/simp/cli/config/item/ldap_base_dn.rb +38 -0
  31. data/lib/simp/cli/config/item/ldap_bind_dn.rb +34 -0
  32. data/lib/simp/cli/config/item/ldap_bind_hash.rb +28 -0
  33. data/lib/simp/cli/config/item/ldap_bind_pw.rb +24 -0
  34. data/lib/simp/cli/config/item/ldap_master.rb +33 -0
  35. data/lib/simp/cli/config/item/ldap_root_dn.rb +42 -0
  36. data/lib/simp/cli/config/item/ldap_root_hash.rb +35 -0
  37. data/lib/simp/cli/config/item/ldap_sync_dn.rb +24 -0
  38. data/lib/simp/cli/config/item/ldap_sync_hash.rb +28 -0
  39. data/lib/simp/cli/config/item/ldap_sync_pw.rb +26 -0
  40. data/lib/simp/cli/config/item/ldap_uri.rb +43 -0
  41. data/lib/simp/cli/config/item/log_servers.rb +27 -0
  42. data/lib/simp/cli/config/item/netmask.rb +39 -0
  43. data/lib/simp/cli/config/item/network_conf.rb +63 -0
  44. data/lib/simp/cli/config/item/network_dhcp.rb +27 -0
  45. data/lib/simp/cli/config/item/network_interface.rb +41 -0
  46. data/lib/simp/cli/config/item/network_setup_nic.rb +28 -0
  47. data/lib/simp/cli/config/item/ntp_servers.rb +69 -0
  48. data/lib/simp/cli/config/item/puppet_autosign.rb +66 -0
  49. data/lib/simp/cli/config/item/puppet_ca.rb +31 -0
  50. data/lib/simp/cli/config/item/puppet_ca_port.rb +28 -0
  51. data/lib/simp/cli/config/item/puppet_conf.rb +98 -0
  52. data/lib/simp/cli/config/item/puppet_fileserver.rb +104 -0
  53. data/lib/simp/cli/config/item/puppet_hosts_entry.rb +44 -0
  54. data/lib/simp/cli/config/item/puppet_server.rb +30 -0
  55. data/lib/simp/cli/config/item/puppet_server_ip.rb +25 -0
  56. data/lib/simp/cli/config/item/puppetdb_port.rb +25 -0
  57. data/lib/simp/cli/config/item/puppetdb_server.rb +26 -0
  58. data/lib/simp/cli/config/item/remove_ldap_from_hiera.rb +47 -0
  59. data/lib/simp/cli/config/item/rename_fqdn_yaml.rb +40 -0
  60. data/lib/simp/cli/config/item/rsync_base.rb +37 -0
  61. data/lib/simp/cli/config/item/rsync_server.rb +44 -0
  62. data/lib/simp/cli/config/item/rsync_timeout.rb +26 -0
  63. data/lib/simp/cli/config/item/set_grub_password.rb +19 -0
  64. data/lib/simp/cli/config/item/simp_yum_servers.rb +30 -0
  65. data/lib/simp/cli/config/item/use_auditd.rb +19 -0
  66. data/lib/simp/cli/config/item/use_fips.rb +46 -0
  67. data/lib/simp/cli/config/item/use_iptables.rb +22 -0
  68. data/lib/simp/cli/config/item/use_ldap.rb +19 -0
  69. data/lib/simp/cli/config/item/use_selinux.rb +32 -0
  70. data/lib/simp/cli/config/item/yum_repositories.rb +75 -0
  71. data/lib/simp/cli/config/item_list_factory.rb +236 -0
  72. data/lib/simp/cli/config/questionnaire.rb +86 -0
  73. data/lib/simp/cli/config/utils.rb +128 -0
  74. data/lib/simp/cli/lib/utils.rb +114 -0
  75. data/lib/simp/simp.rb +77 -0
  76. data/spec/lib/simp/cli/commands/config_spec.rb +42 -0
  77. data/spec/lib/simp/cli/config/item/add_ldap_to_hiera_spec.rb +58 -0
  78. data/spec/lib/simp/cli/config/item/answers_yaml_file_writer_spec.rb +86 -0
  79. data/spec/lib/simp/cli/config/item/certificates_spec.rb +50 -0
  80. data/spec/lib/simp/cli/config/item/client_nets_spec.rb +66 -0
  81. data/spec/lib/simp/cli/config/item/common_runlevel_default_spec.rb +27 -0
  82. data/spec/lib/simp/cli/config/item/dns_search_spec.rb +74 -0
  83. data/spec/lib/simp/cli/config/item/dns_servers_spec.rb +76 -0
  84. data/spec/lib/simp/cli/config/item/failover_log_servers_spec.rb +49 -0
  85. data/spec/lib/simp/cli/config/item/files/FakeCA/cacertkey +1 -0
  86. data/spec/lib/simp/cli/config/item/files/FakeCA/gencerts_nopass.sh +10 -0
  87. data/spec/lib/simp/cli/config/item/files/autosign.conf.new +11 -0
  88. data/spec/lib/simp/cli/config/item/files/autosign.conf.used +15 -0
  89. data/spec/lib/simp/cli/config/item/files/fileserver.conf +41 -0
  90. data/spec/lib/simp/cli/config/item/files/hosts +2 -0
  91. data/spec/lib/simp/cli/config/item/files/hosts.old_puppet_entry +3 -0
  92. data/spec/lib/simp/cli/config/item/files/puppet.conf +25 -0
  93. data/spec/lib/simp/cli/config/item/files/puppet.your.domain.yaml +21 -0
  94. data/spec/lib/simp/cli/config/item/files/resolv.conf__multiple +10 -0
  95. data/spec/lib/simp/cli/config/item/files/resolv.conf__single +4 -0
  96. data/spec/lib/simp/cli/config/item/files/rsyncd.conf +225 -0
  97. data/spec/lib/simp/cli/config/item/gateway_spec.rb +23 -0
  98. data/spec/lib/simp/cli/config/item/grub_password_spec.rb +24 -0
  99. data/spec/lib/simp/cli/config/item/hostname_conf_spec.rb +27 -0
  100. data/spec/lib/simp/cli/config/item/hostname_spec.rb +22 -0
  101. data/spec/lib/simp/cli/config/item/ipaddress_spec.rb +40 -0
  102. data/spec/lib/simp/cli/config/item/is_master_yum_server_spec.rb +29 -0
  103. data/spec/lib/simp/cli/config/item/ldap_base_dn_spec.rb +23 -0
  104. data/spec/lib/simp/cli/config/item/ldap_bind_dn_spec.rb +23 -0
  105. data/spec/lib/simp/cli/config/item/ldap_bind_hash_spec.rb +23 -0
  106. data/spec/lib/simp/cli/config/item/ldap_bind_pw_spec.rb +21 -0
  107. data/spec/lib/simp/cli/config/item/ldap_master_spec.rb +37 -0
  108. data/spec/lib/simp/cli/config/item/ldap_root_dn_spec.rb +23 -0
  109. data/spec/lib/simp/cli/config/item/ldap_root_hash_spec.rb +23 -0
  110. data/spec/lib/simp/cli/config/item/ldap_sync_dn_spec.rb +22 -0
  111. data/spec/lib/simp/cli/config/item/ldap_sync_hash_spec.rb +23 -0
  112. data/spec/lib/simp/cli/config/item/ldap_sync_pw_spec.rb +21 -0
  113. data/spec/lib/simp/cli/config/item/ldap_uri_spec.rb +32 -0
  114. data/spec/lib/simp/cli/config/item/log_servers_spec.rb +49 -0
  115. data/spec/lib/simp/cli/config/item/netmask_spec.rb +28 -0
  116. data/spec/lib/simp/cli/config/item/network_conf_spec.rb +63 -0
  117. data/spec/lib/simp/cli/config/item/network_dhcp_spec.rb +11 -0
  118. data/spec/lib/simp/cli/config/item/network_interface_spec.rb +26 -0
  119. data/spec/lib/simp/cli/config/item/network_setup_nic_spec.rb +29 -0
  120. data/spec/lib/simp/cli/config/item/ntp_servers_spec.rb +43 -0
  121. data/spec/lib/simp/cli/config/item/puppet_autosign_spec.rb +55 -0
  122. data/spec/lib/simp/cli/config/item/puppet_ca_port_spec.rb +23 -0
  123. data/spec/lib/simp/cli/config/item/puppet_ca_spec.rb +22 -0
  124. data/spec/lib/simp/cli/config/item/puppet_conf_spec.rb +110 -0
  125. data/spec/lib/simp/cli/config/item/puppet_fileserver_spec.rb +53 -0
  126. data/spec/lib/simp/cli/config/item/puppet_hosts_entry_spec.rb +85 -0
  127. data/spec/lib/simp/cli/config/item/puppet_server_ip_spec.rb +24 -0
  128. data/spec/lib/simp/cli/config/item/puppet_server_spec.rb +22 -0
  129. data/spec/lib/simp/cli/config/item/puppetdb_port_spec.rb +25 -0
  130. data/spec/lib/simp/cli/config/item/puppetdb_server_spec.rb +25 -0
  131. data/spec/lib/simp/cli/config/item/remove_ldap_from_hiera_spec.rb +58 -0
  132. data/spec/lib/simp/cli/config/item/rename_fqdn_yaml_spec.rb +63 -0
  133. data/spec/lib/simp/cli/config/item/rsync_base_spec.rb +28 -0
  134. data/spec/lib/simp/cli/config/item/rsync_server_spec.rb +41 -0
  135. data/spec/lib/simp/cli/config/item/rsync_timeout_spec.rb +21 -0
  136. data/spec/lib/simp/cli/config/item/set_grub_password_spec.rb +29 -0
  137. data/spec/lib/simp/cli/config/item/simp_yum_servers_spec.rb +41 -0
  138. data/spec/lib/simp/cli/config/item/spec_helper.rb +22 -0
  139. data/spec/lib/simp/cli/config/item/use_auditd_spec.rb +29 -0
  140. data/spec/lib/simp/cli/config/item/use_fips_spec.rb +29 -0
  141. data/spec/lib/simp/cli/config/item/use_iptables_spec.rb +29 -0
  142. data/spec/lib/simp/cli/config/item/use_ldap_spec.rb +29 -0
  143. data/spec/lib/simp/cli/config/item/use_selinux_spec.rb +24 -0
  144. data/spec/lib/simp/cli/config/item/yum_repositories_spec.rb +94 -0
  145. data/spec/lib/simp/cli/config/item_spec.rb +106 -0
  146. data/spec/lib/simp/cli/config/spec_helper.rb +1 -0
  147. data/spec/lib/simp/cli/config/utils_spec.rb +131 -0
  148. data/spec/lib/simp/cli/spec_helper.rb +1 -0
  149. data/spec/spec_helper.rb +91 -0
  150. 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