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