leap_cli 1.5.6 → 1.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/bin/leap +29 -6
  2. data/lib/leap/platform.rb +36 -1
  3. data/lib/leap_cli/commands/ca.rb +97 -20
  4. data/lib/leap_cli/commands/compile.rb +49 -8
  5. data/lib/leap_cli/commands/db.rb +13 -4
  6. data/lib/leap_cli/commands/deploy.rb +138 -29
  7. data/lib/leap_cli/commands/env.rb +76 -0
  8. data/lib/leap_cli/commands/facts.rb +10 -3
  9. data/lib/leap_cli/commands/inspect.rb +2 -2
  10. data/lib/leap_cli/commands/list.rb +10 -10
  11. data/lib/leap_cli/commands/node.rb +7 -132
  12. data/lib/leap_cli/commands/node_init.rb +169 -0
  13. data/lib/leap_cli/commands/pre.rb +4 -27
  14. data/lib/leap_cli/commands/ssh.rb +152 -0
  15. data/lib/leap_cli/commands/test.rb +22 -13
  16. data/lib/leap_cli/commands/user.rb +12 -4
  17. data/lib/leap_cli/commands/vagrant.rb +4 -4
  18. data/lib/leap_cli/config/filter.rb +175 -0
  19. data/lib/leap_cli/config/manager.rb +130 -61
  20. data/lib/leap_cli/config/node.rb +32 -0
  21. data/lib/leap_cli/config/object.rb +69 -44
  22. data/lib/leap_cli/config/object_list.rb +44 -39
  23. data/lib/leap_cli/config/secrets.rb +24 -12
  24. data/lib/leap_cli/config/tag.rb +7 -0
  25. data/lib/{core_ext → leap_cli/core_ext}/boolean.rb +0 -0
  26. data/lib/{core_ext → leap_cli/core_ext}/hash.rb +0 -0
  27. data/lib/{core_ext → leap_cli/core_ext}/json.rb +0 -0
  28. data/lib/{core_ext → leap_cli/core_ext}/nil.rb +0 -0
  29. data/lib/{core_ext → leap_cli/core_ext}/string.rb +0 -0
  30. data/lib/leap_cli/core_ext/yaml.rb +29 -0
  31. data/lib/leap_cli/exceptions.rb +24 -0
  32. data/lib/leap_cli/leapfile.rb +60 -10
  33. data/lib/{lib_ext → leap_cli/lib_ext}/capistrano_connections.rb +0 -0
  34. data/lib/{lib_ext → leap_cli/lib_ext}/gli.rb +0 -0
  35. data/lib/leap_cli/log.rb +1 -1
  36. data/lib/leap_cli/logger.rb +18 -1
  37. data/lib/leap_cli/markdown_document_listener.rb +1 -1
  38. data/lib/leap_cli/override/json.rb +11 -0
  39. data/lib/leap_cli/path.rb +20 -6
  40. data/lib/leap_cli/remote/leap_plugin.rb +2 -2
  41. data/lib/leap_cli/remote/puppet_plugin.rb +1 -1
  42. data/lib/leap_cli/remote/rsync_plugin.rb +1 -1
  43. data/lib/leap_cli/remote/tasks.rb +1 -1
  44. data/lib/leap_cli/ssh_key.rb +63 -1
  45. data/lib/leap_cli/util/remote_command.rb +19 -2
  46. data/lib/leap_cli/util/secret.rb +1 -1
  47. data/lib/leap_cli/util/x509.rb +3 -2
  48. data/lib/leap_cli/util.rb +11 -3
  49. data/lib/leap_cli/version.rb +2 -2
  50. data/lib/leap_cli.rb +24 -14
  51. data/vendor/certificate_authority/lib/certificate_authority/certificate.rb +85 -29
  52. data/vendor/certificate_authority/lib/certificate_authority/distinguished_name.rb +5 -0
  53. data/vendor/certificate_authority/lib/certificate_authority/extensions.rb +406 -41
  54. data/vendor/certificate_authority/lib/certificate_authority/key_material.rb +0 -34
  55. data/vendor/certificate_authority/lib/certificate_authority/serial_number.rb +6 -0
  56. data/vendor/certificate_authority/lib/certificate_authority/signing_request.rb +36 -1
  57. metadata +25 -24
  58. data/lib/leap_cli/commands/shell.rb +0 -89
  59. data/lib/leap_cli/config/macros.rb +0 -430
  60. data/lib/leap_cli/constants.rb +0 -7
  61. data/lib/leap_cli/requirements.rb +0 -19
  62. data/lib/lib_ext/markdown_document_listener.rb +0 -122
@@ -0,0 +1,169 @@
1
+ #
2
+ # Node initialization.
3
+ # Most of the fun stuff is in tasks.rb.
4
+ #
5
+
6
+ module LeapCli; module Commands
7
+
8
+ desc 'Node management'
9
+ command :node do |node|
10
+ node.desc 'Bootstraps a node or nodes, setting up SSH keys and installing prerequisite packages'
11
+ node.long_desc "This command prepares a server to be used with the LEAP Platform by saving the server's SSH host key, " +
12
+ "copying the authorized_keys file, installing packages that are required for deploying, and registering important facts. " +
13
+ "Node init must be run before deploying to a server, and the server must be running and available via the network. " +
14
+ "This command only needs to be run once, but there is no harm in running it multiple times."
15
+ node.arg_name 'FILTER'
16
+ node.command :init do |init|
17
+ init.switch 'echo', :desc => 'If set, passwords are visible as you type them (default is hidden)', :negatable => false
18
+ init.flag :port, :desc => 'Override the default SSH port.', :arg_name => 'PORT'
19
+ init.flag :ip, :desc => 'Override the default SSH IP address.', :arg_name => 'IPADDRESS'
20
+
21
+ init.action do |global,options,args|
22
+ assert! args.any?, 'You must specify a FILTER'
23
+ finished = []
24
+ manager.filter!(args).each_node do |node|
25
+ is_node_alive(node, options)
26
+ save_public_host_key(node, global, options) unless node.vagrant?
27
+ update_compiled_ssh_configs
28
+ ssh_connect_options = connect_options(options).merge({:bootstrap => true, :echo => options[:echo]})
29
+ ssh_connect(node, ssh_connect_options) do |ssh|
30
+ if node.vagrant?
31
+ ssh.install_insecure_vagrant_key
32
+ end
33
+ ssh.install_authorized_keys
34
+ ssh.install_prerequisites
35
+ unless node.vagrant?
36
+ ssh.leap.log(:checking, "SSH host keys") do
37
+ ssh.leap.capture(get_ssh_keys_cmd) do |response|
38
+ update_local_ssh_host_keys(node, response[:data]) if response[:exitcode] == 0
39
+ end
40
+ end
41
+ end
42
+ ssh.leap.log(:updating, "facts") do
43
+ ssh.leap.capture(facter_cmd) do |response|
44
+ if response[:exitcode] == 0
45
+ update_node_facts(node.name, response[:data])
46
+ else
47
+ log :failed, "to run facter on #{node.name}"
48
+ end
49
+ end
50
+ end
51
+ end
52
+ finished << node.name
53
+ end
54
+ log :completed, "initialization of nodes #{finished.join(', ')}"
55
+ end
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ ##
62
+ ## PRIVATE HELPERS
63
+ ##
64
+
65
+ def is_node_alive(node, options)
66
+ address = options[:ip] || node.ip_address
67
+ port = options[:port] || node.ssh.port
68
+ log :connecting, "to node #{node.name}"
69
+ assert_run! "nc -zw3 #{address} #{port}",
70
+ "Failed to reach #{node.name} (address #{address}, port #{port}). You can override the configured IP address and port with --ip or --port."
71
+ end
72
+
73
+ #
74
+ # saves the public ssh host key for node into the provider directory.
75
+ #
76
+ # see `man sshd` for the format of known_hosts
77
+ #
78
+ def save_public_host_key(node, global, options)
79
+ log :fetching, "public SSH host key for #{node.name}"
80
+ address = options[:ip] || node.ip_address
81
+ port = options[:port] || node.ssh.port
82
+ host_keys = get_public_keys_for_ip(address, port)
83
+ pub_key_path = Path.named_path([:node_ssh_pub_key, node.name])
84
+
85
+ if Path.exists?(pub_key_path)
86
+ if host_keys.include? SshKey.load(pub_key_path)
87
+ log :trusted, "- Public SSH host key for #{node.name} matches previously saved key", :indent => 1
88
+ else
89
+ bail! do
90
+ log :error, "The public SSH host keys we just fetched for #{node.name} doesn't match what we have saved previously.", :indent => 1
91
+ log "Delete the file #{pub_key_path} if you really want to remove the trusted SSH host key.", :indent => 2
92
+ end
93
+ end
94
+ else
95
+ known_key = host_keys.detect{|k|k.in_known_hosts?(node.name, node.ip_address, node.domain.name)}
96
+ if known_key
97
+ log :trusted, "- Public SSH host key for #{node.name} is trusted (key found in your ~/.ssh/known_hosts)"
98
+ else
99
+ public_key = SshKey.pick_best_key(host_keys)
100
+ if public_key.nil?
101
+ bail!("We got back #{host_keys.size} host keys from #{node.name}, but we can't support any of them.")
102
+ else
103
+ say(" This is the SSH host key you got back from node \"#{node.name}\"")
104
+ say(" Type -- #{public_key.bits} bit #{public_key.type.upcase}")
105
+ say(" Fingerprint -- " + public_key.fingerprint)
106
+ say(" Public Key -- " + public_key.key)
107
+ if !global[:yes] && !agree(" Is this correct? ")
108
+ bail!
109
+ else
110
+ known_key = public_key
111
+ end
112
+ end
113
+ end
114
+ puts
115
+ write_file! [:node_ssh_pub_key, node.name], known_key.to_s
116
+ end
117
+ end
118
+
119
+ #
120
+ # Get the public host keys for a host using ssh-keyscan.
121
+ # Return an array of SshKey objects, one for each key.
122
+ #
123
+ def get_public_keys_for_ip(address, port=22)
124
+ assert_bin!('ssh-keyscan')
125
+ output = assert_run! "ssh-keyscan -p #{port} #{address}", "Could not get the public host key from #{address}:#{port}. Maybe sshd is not running?"
126
+ if output.empty?
127
+ bail! :failed, "ssh-keyscan returned empty output."
128
+ end
129
+
130
+ if output =~ /No route to host/
131
+ bail! :failed, 'ssh-keyscan: no route to %s' % address
132
+ else
133
+ keys = SshKey.parse_keys(output)
134
+ if keys.empty?
135
+ bail! "ssh-keyscan got zero host keys back (that we understand)! Output was: #{output}"
136
+ else
137
+ return keys
138
+ end
139
+ end
140
+ end
141
+
142
+ # run on the server to generate a string suitable for passing to SshKey.parse_keys()
143
+ def get_ssh_keys_cmd
144
+ "/bin/grep ^HostKey /etc/ssh/sshd_config | /usr/bin/awk '{print $2 \".pub\"}' | /usr/bin/xargs /bin/cat"
145
+ end
146
+
147
+ #
148
+ # Sometimes the ssh host keys on the server will be better than what we have
149
+ # stored locally. In these cases, ask the user if they want to upgrade.
150
+ #
151
+ def update_local_ssh_host_keys(node, remote_keys_string)
152
+ remote_keys = SshKey.parse_keys(remote_keys_string)
153
+ return unless remote_keys.any?
154
+ current_key = SshKey.load(Path.named_path([:node_ssh_pub_key, node.name]))
155
+ best_key = SshKey.pick_best_key(remote_keys)
156
+ return unless best_key && current_key
157
+ if current_key != best_key
158
+ say(" One of the SSH host keys for node '#{node.name}' is better than what you currently have trusted.")
159
+ say(" Current key: #{current_key.summary}")
160
+ say(" Better key: #{best_key.summary}")
161
+ if agree(" Do you want to use the better key? ")
162
+ write_file! [:node_ssh_pub_key, node.name], best_key.to_s
163
+ end
164
+ else
165
+ log(3, "current host key does not need updating")
166
+ end
167
+ end
168
+
169
+ end; end
@@ -20,8 +20,8 @@ module LeapCli; module Commands
20
20
  desc 'Skip prompts and assume "yes"'
21
21
  switch :yes, :negatable => false
22
22
 
23
- desc 'Enable debugging library (leap_cli development only)'
24
- switch :debug, :negatable => false
23
+ desc 'Print full stack trace for exceptions and load `debugger` gem if installed.'
24
+ switch [:d, :debug], :negatable => false
25
25
 
26
26
  desc 'Disable colors in output'
27
27
  default_value true
@@ -31,12 +31,7 @@ module LeapCli; module Commands
31
31
  #
32
32
  # set verbosity
33
33
  #
34
- LeapCli.log_level = global[:verbose].to_i
35
- if LeapCli.log_level > 1
36
- ENV['GLI_DEBUG'] = "true"
37
- else
38
- ENV['GLI_DEBUG'] = "false"
39
- end
34
+ LeapCli.set_log_level(global[:verbose].to_i)
40
35
 
41
36
  #
42
37
  # load Leapfile
@@ -53,13 +48,6 @@ module LeapCli; module Commands
53
48
  bail! { log :missing, "platform directory '#{Path.platform}'" }
54
49
  end
55
50
 
56
- if LeapCli.leapfile.platform_branch && LeapCli::Util.is_git_directory?(Path.platform)
57
- branch = LeapCli::Util.current_git_branch(Path.platform)
58
- if branch != LeapCli.leapfile.platform_branch
59
- bail! "Wrong branch for #{Path.platform}. Was '#{branch}', should be '#{LeapCli.leapfile.platform_branch}'. Edit Leapfile to disable this check."
60
- end
61
- end
62
-
63
51
  #
64
52
  # set log file
65
53
  #
@@ -68,18 +56,7 @@ module LeapCli; module Commands
68
56
  log_version
69
57
  LeapCli.log_in_color = global[:color]
70
58
 
71
- #
72
- # load all the nodes everything
73
- #
74
- manager
75
-
76
- #
77
- # check requirements
78
- #
79
- REQUIREMENTS.each do |key|
80
- assert_config! key
81
- end
82
-
59
+ true
83
60
  end
84
61
 
85
62
  private
@@ -0,0 +1,152 @@
1
+ module LeapCli; module Commands
2
+
3
+ desc 'Log in to the specified node with an interactive shell.'
4
+ arg_name 'NAME' #, :optional => false, :multiple => false
5
+ command :ssh do |c|
6
+ c.flag 'ssh', :desc => "Pass through raw options to ssh (e.g. `--ssh '-F ~/sshconfig'`)."
7
+ c.flag 'port', :arg_name => 'SSH_PORT', :desc => 'Override default SSH port used when trying to connect to the server. Same as `--ssh "-p SSH_PORT"`.'
8
+ c.action do |global_options,options,args|
9
+ exec_ssh(:ssh, options, args)
10
+ end
11
+ end
12
+
13
+ desc 'Log in to the specified node with an interactive shell using mosh (requires node to have mosh.enabled set to true).'
14
+ arg_name 'NAME'
15
+ command :mosh do |c|
16
+ c.flag 'ssh', :desc => "Pass through raw options to ssh (e.g. `--ssh '-F ~/sshconfig'`)."
17
+ c.flag 'port', :arg_name => 'SSH_PORT', :desc => 'Override default SSH port used when trying to connect to the server. Same as `--ssh "-p SSH_PORT"`.'
18
+ c.action do |global_options,options,args|
19
+ exec_ssh(:mosh, options, args)
20
+ end
21
+ end
22
+
23
+ desc 'Creates an SSH port forward (tunnel) to the node NAME. REMOTE_PORT is the port on the remote node that the tunnel will connect to. LOCAL_PORT is the optional port on your local machine. For example: `leap tunnel couch1:5984`.'
24
+ arg_name '[LOCAL_PORT:]NAME:REMOTE_PORT'
25
+ command :tunnel do |c|
26
+ c.flag 'ssh', :desc => "Pass through raw options to ssh (e.g. --ssh '-F ~/sshconfig')."
27
+ c.flag 'port', :arg_name => 'SSH_PORT', :desc => 'Override default SSH port used when trying to connect to the server. Same as `--ssh "-p SSH_PORT"`.'
28
+ c.action do |global_options,options,args|
29
+ local_port, node, remote_port = parse_tunnel_arg(args.first)
30
+ options[:ssh] = [options[:ssh], "-N -L 127.0.0.1:#{local_port}:0.0.0.0:#{remote_port}"].join(' ')
31
+ log("Forward port localhost:#{local_port} to #{node.name}:#{remote_port}")
32
+ if is_port_available?(local_port)
33
+ exec_ssh(:ssh, options, [node.name])
34
+ end
35
+ end
36
+ end
37
+
38
+ protected
39
+
40
+ #
41
+ # allow for ssh overrides of all commands that use ssh_connect
42
+ #
43
+ def connect_options(options)
44
+ connect_options = {:ssh_options=>{}}
45
+ if options[:port]
46
+ connect_options[:ssh_options][:port] = options[:port]
47
+ end
48
+ if options[:ip]
49
+ connect_options[:ssh_options][:host_name] = options[:ip]
50
+ end
51
+ return connect_options
52
+ end
53
+
54
+ def ssh_config_help_message
55
+ puts ""
56
+ puts "Are 'too many authentication failures' getting you down?"
57
+ puts "Then we have the solution for you! Add something like this to your ~/.ssh/config file:"
58
+ puts " Host *.#{manager.provider.domain}"
59
+ puts " IdentityFile ~/.ssh/id_rsa"
60
+ puts " IdentitiesOnly=yes"
61
+ puts "(replace `id_rsa` with the actual private key filename that you use for this provider)"
62
+ end
63
+
64
+ require 'socket'
65
+ def is_port_available?(port)
66
+ TCPServer.open('127.0.0.1', port) {}
67
+ true
68
+ rescue Errno::EACCES
69
+ bail!("You don't have permission to bind to port #{port}.")
70
+ rescue Errno::EADDRINUSE
71
+ bail!("Local port #{port} is already in use. Specify LOCAL_PORT to pick another.")
72
+ rescue Exception => exc
73
+ bail!(exc.to_s)
74
+ end
75
+
76
+ private
77
+
78
+ def exec_ssh(cmd, cli_options, args)
79
+ node = get_node_from_args(args, :include_disabled => true)
80
+ port = node.ssh.port
81
+ options = [
82
+ "-o 'HostName=#{node.ip_address}'",
83
+ # "-o 'HostKeyAlias=#{node.name}'", << oddly incompatible with ports in known_hosts file, so we must not use this or non-standard ports break.
84
+ "-o 'GlobalKnownHostsFile=#{path(:known_hosts)}'",
85
+ "-o 'UserKnownHostsFile=/dev/null'"
86
+ ]
87
+ if node.vagrant?
88
+ options << "-i #{vagrant_ssh_key_file}" # use the universal vagrant insecure key
89
+ options << "-o IdentitiesOnly=yes" # force the use of the insecure vagrant key
90
+ options << "-o 'StrictHostKeyChecking=no'" # blindly accept host key and don't save it (since userknownhostsfile is /dev/null)
91
+ else
92
+ options << "-o 'StrictHostKeyChecking=yes'"
93
+ end
94
+ if !node.supported_ssh_host_key_algorithms.empty?
95
+ options << "-o 'HostKeyAlgorithms=#{node.supported_ssh_host_key_algorithms}'"
96
+ end
97
+ username = 'root'
98
+ if LeapCli.log_level >= 3
99
+ options << "-vv"
100
+ elsif LeapCli.log_level >= 2
101
+ options << "-v"
102
+ end
103
+ if cli_options[:port]
104
+ port = cli_options[:port]
105
+ end
106
+ if cli_options[:ssh]
107
+ options << cli_options[:ssh]
108
+ end
109
+ ssh = "ssh -l #{username} -p #{port} #{options.join(' ')}"
110
+ if cmd == :ssh
111
+ command = "#{ssh} #{node.domain.full}"
112
+ elsif cmd == :mosh
113
+ command = "MOSH_TITLE_NOPREFIX=1 mosh --ssh \"#{ssh}\" #{node.domain.full}"
114
+ end
115
+ log 2, command
116
+
117
+ # exec the shell command in a subprocess
118
+ pid = fork { exec "#{command}" }
119
+
120
+ Signal.trap("SIGINT") do
121
+ Process.kill("KILL", pid)
122
+ Process.wait(pid)
123
+ exit(0)
124
+ end
125
+
126
+ # wait for shell to exit so we can grab the exit status
127
+ _, status = Process.waitpid2(pid)
128
+
129
+ if status.exitstatus == 255
130
+ ssh_config_help_message
131
+ elsif status.exitstatus != 0
132
+ exit(status.exitstatus)
133
+ end
134
+ end
135
+
136
+ def parse_tunnel_arg(arg)
137
+ if arg.count(':') == 1
138
+ node_name, remote = arg.split(':')
139
+ local = nil
140
+ elsif arg.count(':') == 2
141
+ local, node_name, remote = arg.split(':')
142
+ else
143
+ bail!('Argument NAME:REMOTE_PORT required.')
144
+ end
145
+ node = get_node_from_args([node_name], :include_disabled => true)
146
+ remote = remote.to_i
147
+ local = local || remote
148
+ local = local.to_i
149
+ return [local, node, remote]
150
+ end
151
+
152
+ end; end
@@ -1,15 +1,9 @@
1
1
  module LeapCli; module Commands
2
2
 
3
3
  desc 'Run tests.'
4
- command :test do |test|
5
- test.desc 'Creates files needed to run tests.'
6
- test.command :init do |init|
7
- init.action do |global_options,options,args|
8
- generate_test_client_openvpn_configs
9
- end
10
- end
11
-
12
- test.desc 'Run tests.'
4
+ command [:test, :t] do |test|
5
+ test.desc 'Run the test suit on FILTER nodes.'
6
+ test.arg_name 'FILTER', :optional => true
13
7
  test.command :run do |run|
14
8
  run.switch 'continue', :desc => 'Continue over errors and failures (default is --no-continue).', :negatable => true
15
9
  run.action do |global_options,options,args|
@@ -19,13 +13,28 @@ module LeapCli; module Commands
19
13
  end
20
14
  manager.filter!(args).names_in_test_dependency_order.each do |node_name|
21
15
  node = manager.nodes[node_name]
22
- ssh_connect(node) do |ssh|
23
- ssh.run(test_cmd(options))
16
+ begin
17
+ ssh_connect(node) do |ssh|
18
+ ssh.run(test_cmd(options))
19
+ end
20
+ rescue Capistrano::CommandError => exc
21
+ if options[:continue]
22
+ exit_status(1)
23
+ else
24
+ bail!
25
+ end
24
26
  end
25
27
  end
26
28
  end
27
29
  end
28
30
 
31
+ test.desc 'Creates files needed to run tests.'
32
+ test.command :init do |init|
33
+ init.action do |global_options,options,args|
34
+ generate_test_client_openvpn_configs
35
+ end
36
+ end
37
+
29
38
  test.default_command :run
30
39
  end
31
40
 
@@ -33,9 +42,9 @@ module LeapCli; module Commands
33
42
 
34
43
  def test_cmd(options)
35
44
  if options[:continue]
36
- "#{PUPPET_DESTINATION}/bin/run_tests --continue"
45
+ "#{Leap::Platform.leap_dir}/bin/run_tests --continue"
37
46
  else
38
- "#{PUPPET_DESTINATION}/bin/run_tests"
47
+ "#{Leap::Platform.leap_dir}/bin/run_tests"
39
48
  end
40
49
  end
41
50
 
@@ -1,4 +1,3 @@
1
- require 'gpgme'
2
1
 
3
2
  #
4
3
  # perhaps we want to verify that the key files are actually the key files we expect.
@@ -75,8 +74,10 @@ module LeapCli
75
74
  if `which ssh-add`.strip.any?
76
75
  `ssh-add -L 2> /dev/null`.split("\n").compact.each do |line|
77
76
  key = SshKey.load(line)
78
- key.comment = 'ssh-agent'
79
- ssh_keys << key unless ssh_keys.include?(key)
77
+ if key
78
+ key.comment = 'ssh-agent'
79
+ ssh_keys << key unless ssh_keys.include?(key)
80
+ end
80
81
  end
81
82
  end
82
83
  ssh_keys.compact!
@@ -98,13 +99,20 @@ module LeapCli
98
99
  # let the the user choose among the gpg public keys that we encounter, or just pick the key if there is only one.
99
100
  #
100
101
  def pick_pgp_key
102
+ begin
103
+ return unless `which gpg`.strip.any?
104
+ require 'gpgme'
105
+ rescue LoadError
106
+ return
107
+ end
108
+
101
109
  secret_keys = GPGME::Key.find(:secret)
102
110
  if secret_keys.empty?
103
111
  log "Skipping OpenPGP setup because I could not find any OpenPGP keys for you"
104
112
  return nil
105
113
  end
106
114
 
107
- assert_bin! 'gpg'
115
+ secret_keys.select!{|key| !key.expired}
108
116
 
109
117
  if secret_keys.length > 1
110
118
  key_index = numbered_choice_menu('Choose your OpenPGP public key', secret_keys) do |key, i|
@@ -1,11 +1,11 @@
1
- require 'ipaddr'
1
+ autoload :IPAddr, 'ipaddr'
2
2
  require 'fileutils'
3
3
 
4
4
  module LeapCli; module Commands
5
5
 
6
6
  desc "Manage local virtual machines."
7
7
  long_desc "This command provides a convient way to manage Vagrant-based virtual machines. If FILTER argument is missing, the command runs on all local virtual machines. The Vagrantfile is automatically generated in 'test/Vagrantfile'. If you want to run vagrant commands manually, cd to 'test'."
8
- command :local do |local|
8
+ command [:local, :l] do |local|
9
9
  local.desc 'Starts up the virtual machine(s)'
10
10
  local.arg_name 'FILTER', :optional => true #, :multiple => false
11
11
  local.command :start do |start|
@@ -156,7 +156,7 @@ module LeapCli; module Commands
156
156
  if node.vagrant?
157
157
  lines << %[ config.vm.define :#{node.name} do |config|]
158
158
  lines << %[ config.vm.box = "leap-wheezy"]
159
- lines << %[ config.vm.box_url = "https://downloads.leap.se/platform/leap-debian.box"]
159
+ lines << %[ config.vm.box_url = "https://downloads.leap.se/platform/vagrant/virtualbox/leap-wheezy.box"]
160
160
  lines << %[ config.vm.network :hostonly, "#{node.ip_address}", :netmask => "#{netmask}"]
161
161
  lines << %[ config.vm.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]]
162
162
  lines << %[ config.vm.customize ["modifyvm", :id, "--name", "#{node.name}"]]
@@ -170,7 +170,7 @@ module LeapCli; module Commands
170
170
  if node.vagrant?
171
171
  lines << %[ config.vm.define :#{node.name} do |config|]
172
172
  lines << %[ config.vm.box = "leap-wheezy"]
173
- lines << %[ config.vm.box_url = "https://downloads.leap.se/platform/leap-debian.box"]
173
+ lines << %[ config.vm.box_url = "https://downloads.leap.se/platform/vagrant/virtualbox/leap-wheezy.box"]
174
174
  lines << %[ config.vm.network :private_network, ip: "#{node.ip_address}"]
175
175
  lines << %[ config.vm.provider "virtualbox" do |v|]
176
176
  lines << %[ v.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]]
@@ -0,0 +1,175 @@
1
+ #
2
+ # Many leap_cli commands accept a list of filters to select a subset of nodes for the command to
3
+ # be applied to. This class is a helper for manager to run these filters.
4
+ #
5
+ # Classes other than Manager should not use this class.
6
+ #
7
+ # Filter rules:
8
+ #
9
+ # * A filter consists of a list of tokens
10
+ # * A token may be a service name, tag name, environment name, or node name.
11
+ # * Each token may be optionally prefixed with a plus sign.
12
+ # * Multiple tokens with a plus are treated as an OR condition,
13
+ # but treated as an AND condition with the plus sign.
14
+ #
15
+ # For example
16
+ #
17
+ # * openvpn +development => all nodes with service 'openvpn' AND environment 'development'
18
+ # * openvpn seattle => all nodes with service 'openvpn' OR tag 'seattle'.
19
+ #
20
+ # There can only be one environment specified. Typically, there are also tags
21
+ # for each environment name. These name are treated as environments, not tags.
22
+ #
23
+ module LeapCli
24
+ module Config
25
+ class Filter
26
+
27
+ #
28
+ # filter -- array of strings, each one a filter
29
+ # options -- hash, possible keys include
30
+ # :nopin -- disregard environment pinning
31
+ # :local -- if false, disallow local nodes
32
+ #
33
+ # A nil value in the filters array indicates
34
+ # the default environment. This is in order to support
35
+ # calls like `manager.filter(environments)`
36
+ #
37
+ def initialize(filters, options, manager)
38
+ @filters = filters.nil? ? [] : filters.dup
39
+ @environments = []
40
+ @options = options
41
+ @manager = manager
42
+
43
+ # split filters by pulling out items that happen
44
+ # to be environment names.
45
+ if LeapCli.leapfile.environment.nil? || @options[:nopin]
46
+ @environments = []
47
+ else
48
+ @environments = [LeapCli.leapfile.environment]
49
+ end
50
+ @filters.select! do |filter|
51
+ if filter.nil?
52
+ @environments << nil unless @environments.include?(nil)
53
+ false
54
+ else
55
+ filter_text = filter.sub(/^\+/,'')
56
+ if is_environment?(filter_text)
57
+ if filter_text == LeapCli.leapfile.environment
58
+ # silently ignore already pinned environments
59
+ elsif (filter =~ /^\+/ || @filters.first == filter) && !@environments.empty?
60
+ LeapCli::Util.bail! do
61
+ LeapCli::Util.log "Environments are exclusive: no node is in two environments." do
62
+ LeapCli::Util.log "Tried to filter on '#{@environments.join('\' AND \'')}' AND '#{filter_text}'"
63
+ end
64
+ end
65
+ else
66
+ @environments << filter_text
67
+ end
68
+ false
69
+ else
70
+ true
71
+ end
72
+ end
73
+ end
74
+
75
+ # don't let the first filter have a + prefix
76
+ if @filters[0] =~ /^\+/
77
+ @filters[0] = @filters[0][1..-1]
78
+ end
79
+ end
80
+
81
+ # actually run the filter, returns a filtered list of nodes
82
+ def nodes()
83
+ if @filters.empty?
84
+ return nodes_for_empty_filter
85
+ else
86
+ return nodes_for_filter
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ def nodes_for_empty_filter
93
+ node_list = @manager.nodes
94
+ if @environments.any?
95
+ node_list = node_list[ @environments.collect{|e|[:environment, env_to_filter(e)]} ]
96
+ end
97
+ if @options[:local] === false
98
+ node_list = node_list[:environment => '!local']
99
+ end
100
+ node_list
101
+ end
102
+
103
+ def nodes_for_filter
104
+ node_list = Config::ObjectList.new
105
+ @filters.each do |filter|
106
+ if filter =~ /^\+/
107
+ keep_list = nodes_for_name(filter[1..-1])
108
+ node_list.delete_if do |name, node|
109
+ if keep_list[name]
110
+ false
111
+ else
112
+ true
113
+ end
114
+ end
115
+ else
116
+ node_list.merge!(nodes_for_name(filter))
117
+ end
118
+ end
119
+ node_list
120
+ end
121
+
122
+ private
123
+
124
+ #
125
+ # returns a set of nodes corresponding to a single name,
126
+ # where name could be a node name, service name, or tag name.
127
+ #
128
+ # For services and tags, we only include nodes for the
129
+ # environments that are active
130
+ #
131
+ def nodes_for_name(name)
132
+ if node = @manager.nodes[name]
133
+ return Config::ObjectList.new(node)
134
+ elsif @environments.empty?
135
+ if @manager.services[name]
136
+ return @manager.env('_all_').services[name].node_list
137
+ elsif @manager.tags[name]
138
+ return @manager.env('_all_').tags[name].node_list
139
+ else
140
+ LeapCli::Util.log :warning, "filter '#{name}' does not match any node names, tags, services, or environments."
141
+ return Config::ObjectList.new
142
+ end
143
+ else
144
+ node_list = Config::ObjectList.new
145
+ if @manager.services[name]
146
+ @environments.each do |env|
147
+ node_list.merge!(@manager.env(env).services[name].node_list)
148
+ end
149
+ elsif @manager.tags[name]
150
+ @environments.each do |env|
151
+ node_list.merge!(@manager.env(env).tags[name].node_list)
152
+ end
153
+ else
154
+ LeapCli::Util.log :warning, "filter '#{name}' does not match any node names, tags, services, or environments."
155
+ end
156
+ return node_list
157
+ end
158
+ end
159
+
160
+ #
161
+ # when pinning, we use the name 'default' to specify nodes
162
+ # without an environment set, but when filtering, we need to filter
163
+ # on :environment => nil.
164
+ #
165
+ def env_to_filter(environment)
166
+ environment == 'default' ? nil : environment
167
+ end
168
+
169
+ def is_environment?(text)
170
+ text == 'default' || @manager.environment_names.include?(text)
171
+ end
172
+
173
+ end
174
+ end
175
+ end