leap_cli 1.5.6 → 1.6.2

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