leap_cli 1.5.1 → 1.5.6

Sign up to get free protection for your applications and to get access to all the features.
data/lib/leap_cli.rb CHANGED
@@ -7,6 +7,7 @@ require 'leap/platform.rb'
7
7
  require 'leap_cli/version.rb'
8
8
  require 'leap_cli/constants.rb'
9
9
  require 'leap_cli/requirements.rb'
10
+ require 'leap_cli/exceptions.rb'
10
11
 
11
12
  require 'leap_cli/leapfile.rb'
12
13
  require 'core_ext/hash'
@@ -125,7 +125,7 @@ module LeapCli
125
125
  end
126
126
 
127
127
  # all other records
128
- manager.environments.each do |env|
128
+ manager.environment_names.each do |env|
129
129
  next if env == 'local'
130
130
  nodes = manager.nodes[:environment => env]
131
131
  next unless nodes.any?
@@ -0,0 +1,20 @@
1
+ module LeapCli; module Commands
2
+
3
+ desc 'Database commands.'
4
+ command :db do |db|
5
+ db.desc 'Destroy all the databases.'
6
+ db.command :destroy do |destroy|
7
+ destroy.action do |global_options,options,args|
8
+ say 'You are about to permanently destroy all database data.'
9
+ return unless agree("Continue? ")
10
+ nodes = manager.nodes[:services => 'couchdb']
11
+ ssh_connect(nodes, connect_options(options)) do |ssh|
12
+ ssh.run('/etc/init.d/bigcouch stop && test ! -z "$(ls /opt/bigcouch/var/lib/ 2> /dev/null)" && rm -r /opt/bigcouch/var/lib/* && echo "db destroyed" || echo "db already destroyed"')
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ end; end
@@ -38,7 +38,7 @@ module LeapCli
38
38
  end
39
39
  end
40
40
 
41
- compile_hiera_files(nodes)
41
+ compile_hiera_files
42
42
 
43
43
  ssh_connect(nodes, connect_options(options)) do |ssh|
44
44
  ssh.leap.log :checking, 'node' do
@@ -54,7 +54,7 @@ module LeapCli
54
54
  end
55
55
  unless options[:sync]
56
56
  ssh.leap.log :applying, "puppet" do
57
- ssh.puppet.apply(:verbosity => LeapCli.log_level, :tags => tags(options), :force => options[:force])
57
+ ssh.puppet.apply(:verbosity => [LeapCli.log_level,5].min, :tags => tags(options), :force => options[:force])
58
58
  end
59
59
  end
60
60
  end
@@ -15,6 +15,11 @@ module LeapCli; module Commands
15
15
  c.flag 'print', :desc => 'What attributes to print (optional)'
16
16
  c.switch 'disabled', :desc => 'Include disabled nodes in the list.', :negatable => false
17
17
  c.action do |global_options,options,args|
18
+ if global_options[:color]
19
+ colors = ['cyan', 'white']
20
+ else
21
+ colors = [nil, nil]
22
+ end
18
23
  puts
19
24
  if options['disabled']
20
25
  manager.load(:include_disabled => true) # reload, with disabled nodes
@@ -23,11 +28,11 @@ module LeapCli; module Commands
23
28
  print_node_properties(manager.filter(args), options['print'])
24
29
  else
25
30
  if args.any?
26
- NodeTable.new(manager.filter(args)).run
31
+ NodeTable.new(manager.filter(args), colors).run
27
32
  else
28
- TagTable.new('SERVICES', manager.services).run
29
- TagTable.new('TAGS', manager.tags).run
30
- NodeTable.new(manager.nodes).run
33
+ TagTable.new('SERVICES', manager.services, colors).run
34
+ TagTable.new('TAGS', manager.tags, colors).run
35
+ NodeTable.new(manager.nodes, colors).run
31
36
  end
32
37
  end
33
38
  end
@@ -57,20 +62,21 @@ module LeapCli; module Commands
57
62
 
58
63
  class TagTable
59
64
  include CommandLineReporter
60
- def initialize(heading, tag_list)
65
+ def initialize(heading, tag_list, colors)
61
66
  @heading = heading
62
67
  @tag_list = tag_list
68
+ @colors = colors
63
69
  end
64
70
  def run
65
71
  tags = @tag_list.keys.sort
66
72
  max_width = [20, (tags+[@heading]).inject(0) {|max,i| [i.size,max].max}].max
67
73
  table :border => false do
68
- row :header => true, :color => 'cyan' do
74
+ row :color => @colors[0] do
69
75
  column @heading, :align => 'right', :width => max_width
70
76
  column "NODES", :width => HighLine::SystemExtensions.terminal_size.first - max_width - 2, :padding => 2
71
77
  end
72
78
  tags.each do |tag|
73
- row do
79
+ row :color => @colors[1] do
74
80
  column tag
75
81
  column @tag_list[tag].node_list.keys.sort.join(', ')
76
82
  end
@@ -85,8 +91,9 @@ module LeapCli; module Commands
85
91
  #
86
92
  class NodeTable
87
93
  include CommandLineReporter
88
- def initialize(node_list)
94
+ def initialize(node_list, colors)
89
95
  @node_list = node_list
96
+ @colors = colors
90
97
  end
91
98
  def run
92
99
  rows = @node_list.keys.sort.collect do |node_name|
@@ -102,13 +109,13 @@ module LeapCli; module Commands
102
109
  max_service_width = (rows.map{|i|i[1]} + ["SERVICES"]).inject(0) {|max,i| [i.size+padding+padding,max].max}
103
110
  max_tag_width = (rows.map{|i|i[2]} + ["TAGS"] ).inject(0) {|max,i| [i.size,max].max}
104
111
  table :border => false do
105
- row :header => true, :color => 'cyan' do
112
+ row :color => @colors[0] do
106
113
  column "NODES", :align => 'right', :width => max_node_width
107
114
  column "SERVICES", :width => max_service_width, :padding => 2
108
115
  column "TAGS", :width => max_tag_width
109
116
  end
110
117
  rows.each do |r|
111
- row do
118
+ row :color => @colors[1] do
112
119
  column r[0]
113
120
  column r[1]
114
121
  column r[2]
@@ -14,10 +14,11 @@ module LeapCli; module Commands
14
14
  c.action do |global, options, args|
15
15
  directory = File.expand_path(args.first)
16
16
  create_provider_directory(global, directory)
17
- options[:domain] ||= ask("The primary domain of the provider: ") {|q| q.default = 'example.org'}
18
- options[:name] ||= ask("The name of the provider: ") {|q| q.default = 'Example'}
19
- options[:platform] ||= ask("File path of the leap_platform directory: ") {|q| q.default = File.expand_path('../leap_platform', directory)}
20
- options[:contacts] ||= ask("Default email address contacts: ") {|q| q.default = 'root@' + options[:domain]}
17
+ options[:domain] ||= ask_string("The primary domain of the provider: ") {|q| q.default = 'example.org'}
18
+ options[:name] ||= ask_string("The name of the provider: ") {|q| q.default = 'Example'}
19
+ options[:platform] ||= ask_string("File path of the leap_platform directory: ") {|q| q.default = File.expand_path('../leap_platform', directory)}
20
+ options[:platform] = "./" + options[:platform] unless options[:platform] =~ /^\//
21
+ options[:contacts] ||= ask_string("Default email address contacts: ") {|q| q.default = 'root@' + options[:domain]}
21
22
  options[:platform] = relative_path(options[:platform])
22
23
  create_initial_provider_files(directory, global, options)
23
24
  end
@@ -27,6 +28,21 @@ module LeapCli; module Commands
27
28
 
28
29
  DEFAULT_REPO = 'https://leap.se/git/leap_platform.git'
29
30
 
31
+ #
32
+ # don't let the user specify any of the following: y, yes, n, no
33
+ # they must actually input a real string
34
+ #
35
+ def ask_string(str, &block)
36
+ while true
37
+ value = ask(str, &block)
38
+ if value =~ /^(y|yes|n|no)$/i
39
+ say "`#{value}` is not a valid value. Try again"
40
+ else
41
+ return value
42
+ end
43
+ end
44
+ end
45
+
30
46
  #
31
47
  # creates a new provider directory
32
48
  #
@@ -77,7 +93,7 @@ module LeapCli; module Commands
77
93
  end
78
94
 
79
95
  def relative_path(path)
80
- Pathname.new(path).relative_path_from(Pathname.new(Path.provider)).to_s
96
+ Pathname.new(File.expand_path(path)).relative_path_from(Pathname.new(Path.provider)).to_s
81
97
  end
82
98
 
83
99
  def leapfile_content(options)
@@ -22,8 +22,7 @@ module LeapCli; module Commands
22
22
  add.action do |global_options,options,args|
23
23
  # argument sanity checks
24
24
  name = args.first
25
- assert! name, 'No <node-name> specified.'
26
- assert! name =~ /^[0-9a-z-]+$/, "illegal characters used in node name '#{name}'"
25
+ assert_valid_node_name!(name, options[:local])
27
26
  assert_files_missing! [:node_config, name]
28
27
 
29
28
  # create and seed new node
@@ -33,12 +32,14 @@ module LeapCli; module Commands
33
32
  end
34
33
  seed_node_data(node, args[1..-1])
35
34
  validate_ip_address(node)
36
-
37
- # write the file
38
- write_file! [:node_config, name], node.dump_json + "\n"
39
- node['name'] = name
40
- if file_exists? :ca_cert, :ca_key
41
- generate_cert_for_node(manager.reload_node(node))
35
+ begin
36
+ write_file! [:node_config, name], node.dump_json + "\n"
37
+ node['name'] = name
38
+ if file_exists? :ca_cert, :ca_key
39
+ generate_cert_for_node(manager.reload_node!(node))
40
+ end
41
+ rescue LeapCli::ConfigError => exc
42
+ remove_node_files(name)
42
43
  end
43
44
  end
44
45
  end
@@ -64,10 +65,9 @@ module LeapCli; module Commands
64
65
  ssh_connect_options = connect_options(options).merge({:bootstrap => true, :echo => options[:echo]})
65
66
  ssh_connect(node, ssh_connect_options) do |ssh|
66
67
  if node.vagrant?
67
- ssh.install_authorized_keys2
68
- else
69
- ssh.install_authorized_keys
68
+ ssh.install_insecure_vagrant_key
70
69
  end
70
+ ssh.install_authorized_keys
71
71
  ssh.install_prerequisites
72
72
  ssh.leap.capture(facter_cmd) do |response|
73
73
  if response[:exitcode] == 0
@@ -89,6 +89,7 @@ module LeapCli; module Commands
89
89
  mv.action do |global_options,options,args|
90
90
  node = get_node_from_args(args)
91
91
  new_name = args.last
92
+ assert_valid_node_name!(new_name, node.vagrant?)
92
93
  ensure_dir [:node_files_dir, new_name]
93
94
  Leap::Platform.node_files.each do |path|
94
95
  rename_file! [path, node.name], [path, new_name]
@@ -103,9 +104,7 @@ module LeapCli; module Commands
103
104
  node.command :rm do |rm|
104
105
  rm.action do |global_options,options,args|
105
106
  node = get_node_from_args(args)
106
- (Leap::Platform.node_files + [:node_files_dir]).each do |path|
107
- remove_file! [path, node.name]
108
- end
107
+ remove_node_files(node.name)
109
108
  if node.vagrant?
110
109
  vagrant_command("destroy --force", [node.name])
111
110
  end
@@ -237,8 +236,14 @@ module LeapCli; module Commands
237
236
  end
238
237
  end
239
238
 
239
+ def remove_node_files(node_name)
240
+ (Leap::Platform.node_files + [:node_files_dir]).each do |path|
241
+ remove_file! [path, node_name]
242
+ end
243
+ end
244
+
240
245
  #
241
- # conversations:
246
+ # conversions:
242
247
  #
243
248
  # "x,y,z" => ["x","y","z"]
244
249
  #
@@ -273,4 +278,13 @@ module LeapCli; module Commands
273
278
  end
274
279
  end
275
280
 
281
+ def assert_valid_node_name!(name, local=false)
282
+ assert! name, 'No <node-name> specified.'
283
+ if local
284
+ assert! name =~ /^[0-9a-z]+$/, "illegal characters used in node name '#{name}' (note: Vagrant does not allow hyphens or underscores)"
285
+ else
286
+ assert! name =~ /^[0-9a-z-]+$/, "illegal characters used in node name '#{name}' (note: Linux does not allow underscores)"
287
+ end
288
+ end
289
+
276
290
  end; end
@@ -46,7 +46,7 @@ module LeapCli; module Commands
46
46
  assert_config! 'provider.ca.client_certificates.unlimited_prefix'
47
47
  assert_config! 'provider.ca.client_certificates.limited_prefix'
48
48
  template = read_file! Path.find_file(:test_client_openvpn_template)
49
- manager.environments.each do |env|
49
+ manager.environment_names.each do |env|
50
50
  vpn_nodes = manager.nodes[:environment => env][:services => 'openvpn']['openvpn.allow_limited' => true]
51
51
  if vpn_nodes.any?
52
52
  generate_test_client_cert(provider.ca.client_certificates.limited_prefix) do |key, cert|
@@ -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/leap-debian.box"]
159
+ lines << %[ config.vm.box_url = "https://downloads.leap.se/platform/leap-debian.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/leap-debian.box"]
173
+ lines << %[ config.vm.box_url = "https://downloads.leap.se/platform/leap-debian.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"]]
@@ -1,3 +1,4 @@
1
+ # encoding: utf-8
1
2
  #
2
3
  # MACROS
3
4
  # these are methods available when eval'ing a value in the .json configuration
@@ -5,6 +6,8 @@
5
6
  # This module is included in Config::Object
6
7
  #
7
8
 
9
+ require 'base32'
10
+
8
11
  module LeapCli; module Config
9
12
  module Macros
10
13
  ##
@@ -22,7 +25,7 @@ module LeapCli; module Config
22
25
  # grab an environment appropriate provider
23
26
  #
24
27
  def provider
25
- global.providers[@node.environment] || global.provider
28
+ global.env(@node.environment).provider
26
29
  end
27
30
 
28
31
  #
@@ -60,9 +63,9 @@ module LeapCli; module Config
60
63
  filepath = Path.find_file(filename)
61
64
  if filepath
62
65
  if filepath =~ /\.erb$/
63
- ERB.new(File.read(filepath), nil, '%<>').result(binding)
66
+ ERB.new(File.read(filepath, :encoding => 'UTF-8'), nil, '%<>').result(binding)
64
67
  else
65
- File.read(filepath)
68
+ File.read(filepath, :encoding => 'UTF-8')
66
69
  end
67
70
  else
68
71
  raise FileMissing.new(Path.named_path(filename), options)
@@ -129,6 +132,16 @@ module LeapCli; module Config
129
132
  @manager.secrets.set(name, Util::Secret.generate(length), @node[:environment])
130
133
  end
131
134
 
135
+ # inserts a base32 encoded secret
136
+ def base32_secret(name, length=20)
137
+ @manager.secrets.set(name, Base32.encode(Util::Secret.generate(length)), @node[:environment])
138
+ end
139
+
140
+ # Picks a random obfsproxy port from given range
141
+ def rand_range(name, range)
142
+ @manager.secrets.set(name, rand(range), @node[:environment])
143
+ end
144
+
132
145
  #
133
146
  # inserts an hexidecimal secret string, generating it if needed.
134
147
  #
@@ -343,14 +356,14 @@ module LeapCli; module Config
343
356
  hash = {}
344
357
  keys = Dir.glob(Path.named_path([:user_ssh, '*']))
345
358
  keys.sort.each do |keyfile|
346
- ssh_type, ssh_key = File.read(keyfile).strip.split(" ")
359
+ ssh_type, ssh_key = File.read(keyfile, :encoding => 'UTF-8').strip.split(" ")
347
360
  name = File.basename(File.dirname(keyfile))
348
361
  hash[name] = {
349
362
  "type" => ssh_type,
350
363
  "key" => ssh_key
351
364
  }
352
365
  end
353
- ssh_type, ssh_key = File.read(Path.named_path(:monitor_pub_key)).strip.split(" ")
366
+ ssh_type, ssh_key = File.read(Path.named_path(:monitor_pub_key), :encoding => 'UTF-8').strip.split(" ")
354
367
  hash[Leap::Platform.monitor_username] = {
355
368
  "type" => ssh_type,
356
369
  "key" => ssh_key
@@ -402,5 +415,16 @@ module LeapCli; module Config
402
415
  end
403
416
  end
404
417
 
418
+ #
419
+ # wrap something that might fail in `try`. e.g.
420
+ #
421
+ # "= try{ nodes[:services => 'tor'].first.ip_address } "
422
+ #
423
+ def try(&block)
424
+ yield
425
+ rescue NoMethodError
426
+ nil
427
+ end
428
+
405
429
  end
406
430
  end; end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'json/pure'
2
4
 
3
5
  if $ruby_version < [1,9]
@@ -7,16 +9,24 @@ end
7
9
  module LeapCli
8
10
  module Config
9
11
 
12
+ class Environment
13
+ attr_accessor :services, :tags, :provider
14
+ end
15
+
10
16
  #
11
17
  # A class to manage all the objects in all the configuration files.
12
18
  #
13
19
  class Manager
14
20
 
21
+ def initialize
22
+ @environments = {} # hash of `Environment` objects, keyed by name.
23
+ end
24
+
15
25
  ##
16
26
  ## ATTRIBUTES
17
27
  ##
18
28
 
19
- attr_reader :services, :tags, :nodes, :provider, :providers, :common, :secrets
29
+ attr_reader :nodes, :common, :secrets
20
30
  attr_reader :base_services, :base_tags, :base_provider, :base_common
21
31
 
22
32
  #
@@ -30,10 +40,24 @@ module LeapCli
30
40
  # returns an Array of all the environments defined for this provider.
31
41
  # the returned array includes nil (for the default environment)
32
42
  #
33
- def environments
34
- @environments ||= [nil] + self.tags.collect {|name, tag| tag['environment']}.compact
43
+ def environment_names
44
+ @environment_names ||= [nil] + env.tags.collect {|name, tag| tag['environment']}.compact
35
45
  end
36
46
 
47
+ #
48
+ # Returns the appropriate environment variable
49
+ #
50
+ def env(env=nil)
51
+ env ||= 'default'
52
+ e = @environments[env] ||= Environment.new
53
+ yield e if block_given?
54
+ e
55
+ end
56
+
57
+ def services; env('default').services; end
58
+ def tags; env('default').tags; end
59
+ def provider; env('default').provider; end
60
+
37
61
  ##
38
62
  ## IMPORT EXPORT
39
63
  ##
@@ -46,34 +70,43 @@ module LeapCli
46
70
 
47
71
  # load base
48
72
  @base_services = load_all_json(Path.named_path([:service_config, '*'], Path.provider_base), Config::Tag)
49
- @base_tags = load_all_json(Path.named_path([:tag_config, '*'], Path.provider_base), Config::Tag)
50
- @base_common = load_json(Path.named_path(:common_config, Path.provider_base), Config::Object)
51
- @base_provider = load_json(Path.named_path(:provider_config, Path.provider_base), Config::Provider)
73
+ @base_tags = load_all_json(Path.named_path([:tag_config, '*'], Path.provider_base), Config::Tag)
74
+ @base_common = load_json( Path.named_path(:common_config, Path.provider_base), Config::Object)
75
+ @base_provider = load_json( Path.named_path(:provider_config, Path.provider_base), Config::Provider)
52
76
 
53
77
  # load provider
54
- provider_path = Path.named_path(:provider_config, @provider_dir)
55
- common_path = Path.named_path(:common_config, @provider_dir)
56
- Util::assert_files_exist!(provider_path, common_path)
57
- @services = load_all_json(Path.named_path([:service_config, '*'], @provider_dir), Config::Tag)
58
- @tags = load_all_json(Path.named_path([:tag_config, '*'], @provider_dir), Config::Tag)
59
- @nodes = load_all_json(Path.named_path([:node_config, '*'], @provider_dir), Config::Node)
60
- @common = load_json(common_path, Config::Object)
61
- @provider = load_json(provider_path, Config::Provider)
62
- @secrets = load_json(Path.named_path(:secrets_config, @provider_dir), Config::Secrets)
63
-
64
- ### BEGIN HACK
65
- ### remove this after it is likely that no one has any old-style secrets.json
66
- if @secrets['webapp_secret_token']
67
- @secrets = Config::Secrets.new
68
- Util::log :warning, "Creating all new secrets.json (new version is scoped by environment). Make sure to do a full deploy so that new secrets take effect."
78
+ @nodes = load_all_json(Path.named_path([:node_config, '*'], @provider_dir), Config::Node)
79
+ @common = load_json( Path.named_path(:common_config, @provider_dir), Config::Object)
80
+ @secrets = load_json( Path.named_path(:secrets_config, @provider_dir), Config::Secrets)
81
+ @common.inherit_from! @base_common
82
+
83
+ # load provider services, tags, and provider.json, DEFAULT environment
84
+ log 3, :loading, 'default environment.........'
85
+ env('default') do |e|
86
+ e.services = load_all_json(Path.named_path([:service_config, '*'], @provider_dir), Config::Tag, :no_dots => true)
87
+ e.tags = load_all_json(Path.named_path([:tag_config, '*'], @provider_dir), Config::Tag, :no_dots => true)
88
+ e.provider = load_json( Path.named_path(:provider_config, @provider_dir), Config::Provider, :assert => true)
89
+ e.services.inherit_from! @base_services
90
+ e.tags.inherit_from! @base_tags
91
+ e.provider.inherit_from! @base_provider
92
+ validate_provider(e.provider)
93
+ end
94
+
95
+ # load provider services, tags, and provider.json, OTHER environments
96
+ environment_names.each do |ename|
97
+ next unless ename
98
+ log 3, :loading, '%s environment.........' % ename
99
+ env(ename) do |e|
100
+ e.services = load_all_json(Path.named_path([:service_env_config, '*', ename], @provider_dir), Config::Tag)
101
+ e.tags = load_all_json(Path.named_path([:tag_env_config, '*', ename], @provider_dir), Config::Tag)
102
+ e.provider = load_json( Path.named_path([:provider_env_config, ename], @provider_dir), Config::Provider)
103
+ e.services.inherit_from! env.services
104
+ e.tags.inherit_from! env.tags
105
+ e.provider.inherit_from! env.provider
106
+ validate_provider(e.provider)
107
+ end
69
108
  end
70
- ### END HACK
71
109
 
72
- # inherit
73
- @services.inherit_from! base_services
74
- @tags.inherit_from! base_tags
75
- @common.inherit_from! base_common
76
- @provider.inherit_from! base_provider
77
110
  @nodes.each do |name, node|
78
111
  Util::assert! name =~ /^[0-9a-z-]+$/, "Illegal character(s) used in node name '#{name}'"
79
112
  @nodes[name] = apply_inheritance(node)
@@ -82,19 +115,6 @@ module LeapCli
82
115
  unless options[:include_disabled]
83
116
  remove_disabled_nodes
84
117
  end
85
-
86
- # load optional environment specific providers
87
- validate_provider(@provider)
88
- @providers = {}
89
- environments.each do |env|
90
- if Path.defined?(:provider_env_config)
91
- provider_path = Path.named_path([:provider_env_config, env], @provider_dir)
92
- providers[env] = load_json(provider_path, Config::Provider)
93
- providers[env].inherit_from! @provider
94
- validate_provider(providers[env])
95
- end
96
- end
97
-
98
118
  end
99
119
 
100
120
  #
@@ -202,6 +222,11 @@ module LeapCli
202
222
  # returns a single Config::Object that corresponds to a Node.
203
223
  #
204
224
  def node(name)
225
+ if name =~ /\./
226
+ # probably got a fqdn, since periods are not allowed in node names.
227
+ # so, take the part before the first period as the node name
228
+ name = name.split('.').first
229
+ end
205
230
  @nodes[name]
206
231
  end
207
232
 
@@ -219,18 +244,19 @@ module LeapCli
219
244
  nodes.each_node &block
220
245
  end
221
246
 
222
- def reload_node(node)
223
- @nodes[node.name] = apply_inheritance(node)
247
+ def reload_node!(node)
248
+ @nodes[node.name] = apply_inheritance!(node)
224
249
  end
225
250
 
226
251
  private
227
252
 
228
- def load_all_json(pattern, object_class)
253
+ def load_all_json(pattern, object_class, options={})
229
254
  results = Config::ObjectList.new
230
255
  Dir.glob(pattern).each do |filename|
256
+ next if options[:no_dots] && File.basename(filename) !~ /^[^\.]*\.json$/
231
257
  obj = load_json(filename, object_class)
232
258
  if obj
233
- name = File.basename(filename).sub(/\.json$/,'')
259
+ name = File.basename(filename).force_encoding('utf-8').sub(/^([^\.]+).*\.json$/,'\1')
234
260
  obj['name'] ||= name
235
261
  results[name] = obj
236
262
  end
@@ -238,7 +264,10 @@ module LeapCli
238
264
  results
239
265
  end
240
266
 
241
- def load_json(filename, object_class)
267
+ def load_json(filename, object_class, options={})
268
+ if options[:assert]
269
+ Util::assert_files_exist!(filename)
270
+ end
242
271
  if !File.exists?(filename)
243
272
  return object_class.new(self)
244
273
  end
@@ -252,7 +281,7 @@ module LeapCli
252
281
  # https://www.ietf.org/rfc/rfc4627.txt
253
282
  #
254
283
  buffer = StringIO.new
255
- File.open(filename, "rb") do |f|
284
+ File.open(filename, "rb", :encoding => 'UTF-8') do |f|
256
285
  while (line = f.gets)
257
286
  next if line =~ /^\s*\/\//
258
287
  buffer << line
@@ -300,22 +329,36 @@ module LeapCli
300
329
  #
301
330
  # makes a node inherit options from appropriate the common, service, and tag json files.
302
331
  #
303
- def apply_inheritance(node)
332
+ def apply_inheritance(node, throw_exceptions=false)
304
333
  new_node = Config::Node.new(self)
305
334
  name = node.name
306
335
 
336
+ # Guess the environment of the node from the tag names.
337
+ # (Technically, this is wrong: a tag that sets the environment might not be
338
+ # named the same as the environment. This code assumes that it is).
339
+ node_env = self.env
340
+ if node['tags']
341
+ node['tags'].to_a.each do |tag|
342
+ if self.environment_names.include?(tag)
343
+ node_env = self.env(tag)
344
+ end
345
+ end
346
+ end
347
+
307
348
  # inherit from common
308
349
  new_node.deep_merge!(@common)
309
350
 
310
351
  # inherit from services
311
352
  if node['services']
312
353
  node['services'].to_a.each do |node_service|
313
- service = @services[node_service]
354
+ service = node_env.services[node_service]
314
355
  if service.nil?
315
- log 0, :error, 'in node "%s": the service "%s" does not exist.' % [node['name'], node_service]
356
+ msg = 'in node "%s": the service "%s" does not exist.' % [node['name'], node_service]
357
+ log 0, :error, msg
358
+ raise LeapCli::ConfigError.new(node, "error " + msg) if throw_exceptions
316
359
  else
317
360
  new_node.deep_merge!(service)
318
- service.node_list.add(name, new_node)
361
+ self.services[node_service].node_list.add(name, new_node)
319
362
  end
320
363
  end
321
364
  end
@@ -326,12 +369,14 @@ module LeapCli
326
369
  end
327
370
  if node['tags']
328
371
  node['tags'].to_a.each do |node_tag|
329
- tag = @tags[node_tag]
372
+ tag = node_env.tags[node_tag]
330
373
  if tag.nil?
331
- log 0, :error, 'in node "%s": the tag "%s" does not exist.' % [node['name'], node_tag]
374
+ msg = 'in node "%s": the tag "%s" does not exist.' % [node['name'], node_tag]
375
+ log 0, :error, msg
376
+ raise LeapCli::ConfigError.new(node, "error " + msg) if throw_exceptions
332
377
  else
333
378
  new_node.deep_merge!(tag)
334
- tag.node_list.add(name, new_node)
379
+ self.tags[node_tag].node_list.add(name, new_node)
335
380
  end
336
381
  end
337
382
  end
@@ -341,6 +386,10 @@ module LeapCli
341
386
  return new_node
342
387
  end
343
388
 
389
+ def apply_inheritance!(node)
390
+ apply_inheritance(node, true)
391
+ end
392
+
344
393
  def remove_disabled_nodes
345
394
  @disabled_nodes = Config::ObjectList.new
346
395
  @nodes.each do |name, node|
@@ -350,12 +399,12 @@ module LeapCli
350
399
  @disabled_nodes[name] = node
351
400
  if node['services']
352
401
  node['services'].to_a.each do |node_service|
353
- @services[node_service].node_list.delete(node.name)
402
+ self.services[node_service].node_list.delete(node.name)
354
403
  end
355
404
  end
356
405
  if node['tags']
357
406
  node['tags'].to_a.each do |node_tag|
358
- @tags[node_tag].node_list.delete(node.name)
407
+ self.tags[node_tag].node_list.delete(node.name)
359
408
  end
360
409
  end
361
410
  end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'erb'
2
4
  require 'json/pure' # pure ruby implementation is required for our sorted trick to work.
3
5
 
@@ -96,7 +98,9 @@ module LeapCli
96
98
  #
97
99
  def get!(key)
98
100
  key = key.to_s
99
- if key =~ /\./
101
+ if self.has_key?(key)
102
+ fetch_value(key)
103
+ elsif key =~ /\./
100
104
  # for keys with with '.' in them, we start from the root object (@node).
101
105
  keys = key.split('.')
102
106
  value = @node.get!(keys.first)
@@ -105,8 +109,6 @@ module LeapCli
105
109
  else
106
110
  value
107
111
  end
108
- elsif self.has_key?(key)
109
- fetch_value(key)
110
112
  else
111
113
  raise NoMethodError.new(key, "No method '#{key}' for #{self.class}")
112
114
  end
@@ -182,7 +182,14 @@ module LeapCli
182
182
  end
183
183
 
184
184
  def tsort_each_child(node_name, &block)
185
- self[node_name].test_dependencies.each(&block)
185
+ if self[node_name]
186
+ self[node_name].test_dependencies.each do |test_me_first|
187
+ if self[test_me_first] # TODO: in the future, allow for ability to optionally pull in all dependencies.
188
+ # not just the ones that pass the node filter.
189
+ yield(test_me_first)
190
+ end
191
+ end
192
+ end
186
193
  end
187
194
 
188
195
  def names_in_test_dependency_order
@@ -1,3 +1,4 @@
1
+ # encoding: utf-8
1
2
  #
2
3
  # A class for the secrets.json file
3
4
  #
@@ -0,0 +1,11 @@
1
+ module LeapCli
2
+
3
+ class ConfigError < StandardError
4
+ attr_accessor :node
5
+ def initialize(node, msg)
6
+ @node = node
7
+ super(msg)
8
+ end
9
+ end
10
+
11
+ end
data/lib/leap_cli/log.rb CHANGED
@@ -80,6 +80,7 @@ module LeapCli
80
80
  if title
81
81
  prefix_options = case title
82
82
  when :error then ['error', :red, :bold]
83
+ when :fatal_error then ['fatal error', :red, :bold]
83
84
  when :warning then ['warning:', :yellow, :bold]
84
85
  when :info then ['info', :cyan, :bold]
85
86
  when :updated then ['updated', :cyan, :bold]
@@ -13,38 +13,53 @@ task :install_authorized_keys, :max_hosts => MAX_HOSTS do
13
13
  end
14
14
 
15
15
  #
16
- # for vagrant nodes, we don't overwrite authorized_keys, because we want to keep the insecure vagrant key.
17
- # instead we install to authorized_keys2, which is also used by sshd.
16
+ # for vagrant nodes, we install insecure vagrant key to authorized_keys2, since deploy
17
+ # will overwrite authorized_keys.
18
18
  #
19
- # why?
20
- # without it, it might be impossible to re-initialize a node.
21
- #
22
- # ok, why is that?
23
- # when we init a vagrant node, we force it to use the insecure vagrant key, and not the user's keys
24
- # (so re-initialization would be impossible if authorized_keys doesn't include insecure key).
25
- #
26
- # ok, why force the insecure vagrant key in the first place?
19
+ # why force the insecure vagrant key?
27
20
  # if we don't do this, then first time initialization might fail if the user has many keys
28
21
  # (ssh will bomb out before it gets to the vagrant key).
29
22
  # and it really doesn't make sense to ask users to pin the insecure vagrant key in their
30
23
  # .ssh/config files.
31
24
  #
32
- task :install_authorized_keys2, :max_hosts => MAX_HOSTS do
33
- leap.log :updating, "authorized_keys2" do
25
+ task :install_insecure_vagrant_key, :max_hosts => MAX_HOSTS do
26
+ leap.log :installing, "insecure vagrant key" do
34
27
  leap.mkdirs '/root/.ssh'
35
- upload LeapCli::Path.named_path(:authorized_keys), '/root/.ssh/authorized_keys2', :mode => '600'
28
+ key_file = File.expand_path('../../../vendor/vagrant_ssh_keys/vagrant.pub', File.dirname(__FILE__))
29
+ upload key_file, '/root/.ssh/authorized_keys2', :mode => '600'
36
30
  end
37
31
  end
38
32
 
33
+ BAD_APT_GET_UPDATE = /(BADSIG|NO_PUBKEY|KEYEXPIRED|REVKEYSIG|NODATA)/
34
+
39
35
  task :install_prerequisites, :max_hosts => MAX_HOSTS do
36
+ apt_get = "DEBIAN_FRONTEND=noninteractive apt-get -q -y -o DPkg::Options::=--force-confold"
40
37
  leap.mkdirs LeapCli::PUPPET_DESTINATION
38
+ run "echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen"
41
39
  leap.log :updating, "package list" do
42
- run "apt-get update"
40
+ run "apt-get update" do |channel, stream, data|
41
+ # sadly exitcode is unreliable measure if apt-get update hit a failure.
42
+ if data =~ BAD_APT_GET_UPDATE
43
+ LeapCli::Util.bail! do
44
+ LeapCli::Util.log :fatal_error, "in `apt-get update`: #{data}", :host => channel[:host]
45
+ end
46
+ else
47
+ logger.log(1, data, channel[:host])
48
+ end
49
+ end
50
+ end
51
+ leap.log :updating, "server time" do
52
+ run "( test -f /etc/init.d/ntp && /etc/init.d/ntp stop ) || true"
53
+ run "test -f /usr/sbin/ntpdate || #{apt_get} install ntpdate"
54
+ leap.log :running, "ntpdate..." do
55
+ run "test -f /usr/sbin/ntpdate && ntpdate 0.debian.pool.ntp.org 1.debian.pool.ntp.org 2.debian.pool.ntp.org 3.debian.pool.ntp.org"
56
+ end
57
+ run "( test -f /etc/init.d/ntp && /etc/init.d/ntp start ) || true"
43
58
  end
44
59
  leap.log :installing, "required packages" do
45
- run "DEBIAN_FRONTEND=noninteractive apt-get -q -y -o DPkg::Options::=--force-confold install #{leap.required_packages}"
60
+ run "#{apt_get} install #{leap.required_packages}"
46
61
  end
47
- run "echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen; locale-gen"
62
+ #run "locale-gen"
48
63
  leap.mkdirs("/etc/leap", "/srv/leap")
49
64
  leap.mark_initialized
50
65
  end
data/lib/leap_cli/util.rb CHANGED
@@ -193,13 +193,13 @@ module LeapCli
193
193
  def read_file!(filepath)
194
194
  filepath = Path.named_path(filepath)
195
195
  assert_files_exist!(filepath)
196
- File.read(filepath)
196
+ File.read(filepath, :encoding => 'UTF-8')
197
197
  end
198
198
 
199
199
  def read_file(filepath)
200
200
  filepath = Path.named_path(filepath)
201
201
  if file_exists?(filepath)
202
- File.read(filepath)
202
+ File.read(filepath, :encoding => 'UTF-8')
203
203
  end
204
204
  end
205
205
 
@@ -219,7 +219,7 @@ module LeapCli
219
219
  write_file!(filepath, content)
220
220
  end
221
221
  else
222
- File.open(filepath, File::RDWR|File::CREAT, 0600) do |f|
222
+ File.open(filepath, File::RDWR|File::CREAT, 0600, :encoding => 'UTF-8') do |f|
223
223
  f.flock(File::LOCK_EX)
224
224
  old_content = f.read
225
225
  new_content = yield(old_content)
@@ -286,7 +286,7 @@ module LeapCli
286
286
  end
287
287
  end
288
288
 
289
- File.open(filepath, 'w', 0600) do |f|
289
+ File.open(filepath, 'w', 0600, :encoding => 'UTF-8') do |f|
290
290
  f.write contents
291
291
  end
292
292
 
@@ -13,7 +13,7 @@ module LeapCli; module Util; module RemoteCommand
13
13
  node_list = parse_node_list(nodes)
14
14
 
15
15
  cap = new_capistrano
16
- cap.logger = LeapCli::Logger.new(:level => LeapCli.log_level)
16
+ cap.logger = LeapCli::Logger.new(:level => [LeapCli.log_level,3].min)
17
17
  user = options[:user] || 'root'
18
18
  cap.set :user, user
19
19
  cap.set :ssh_options, ssh_options # ssh options common to all nodes
@@ -31,7 +31,7 @@ module LeapCli; module Util; module RemoteCommand
31
31
  end
32
32
 
33
33
  node_list.each do |name, node|
34
- cap.server node.name, :dummy_arg, node_options(node, options[:ssh_options])
34
+ cap.server node.domain.full, :dummy_arg, node_options(node, options[:ssh_options])
35
35
  end
36
36
 
37
37
  yield cap
@@ -48,9 +48,34 @@ module LeapCli; module Util; module RemoteCommand
48
48
  #
49
49
  # For available options, see http://net-ssh.github.com/net-ssh/classes/Net/SSH.html#method-c-start
50
50
  #
51
+ # Capistrano has some very evil behavior in it's ssh.rb:
52
+ #
53
+ # ssh_options = Net::SSH.configuration_for(
54
+ # server.host, ssh_options.fetch(:config, true)
55
+ # ).merge(ssh_options)
56
+ # # Once we've loaded the config, we don't need Net::SSH to do it again.
57
+ # ssh_options[:config] = false
58
+ #
59
+ # Net:SSH is supposed to call Net::SSH.configuration_for, but Capistrano is doing it
60
+ # in advance and then disabling loading of configs.
61
+ #
62
+ # The result of this is the following: if you have IdentityFile in your ~/.ssh/config
63
+ # file, then the above code will transform the ssh_options by reading ~/.ssh/config
64
+ # and adding the keys specified via IdentityFile to ssh_options...
65
+ # AND IT WILL SET :keys_only TO TRUE.
66
+ #
67
+ # The problem is that :keys_only will disable Net:SSH's ability to use ssh-agent.
68
+ # With :keys_only set to true, it will not consult the ssh-agent at all.
69
+ #
70
+ # So nice of capistrano to parse ~/.ssh/config for us, but then add flags to the
71
+ # ssh_options that prevent's these options from being useful.
72
+ #
73
+ # The current hackaround is to force :keys_only to be false. This allows the config
74
+ # to be read and also allows ssh-agent to still be used.
75
+ #
51
76
  def ssh_options
52
77
  {
53
- :config => false,
78
+ :keys_only => false, # Don't you dare change this.
54
79
  :global_known_hosts_file => path(:known_hosts),
55
80
  :user_known_hosts_file => '/dev/null',
56
81
  :paranoid => true
@@ -67,13 +92,12 @@ module LeapCli; module Util; module RemoteCommand
67
92
  # return {:password => password_proc}
68
93
  #
69
94
  def node_options(node, ssh_options_override=nil)
70
- ssh_options_override ||= {}
71
95
  {
72
96
  :ssh_options => {
73
97
  # :host_key_alias => node.name, << incompatible with ports in known_hosts
74
98
  :host_name => node.ip_address,
75
99
  :port => node.ssh.port
76
- }.merge(contingent_ssh_options_for_node(node)).merge(ssh_options_override)
100
+ }.merge(contingent_ssh_options_for_node(node)).merge(ssh_options_override||{})
77
101
  }
78
102
  end
79
103
 
@@ -1,3 +1,4 @@
1
+ # encoding: utf-8
1
2
  #
2
3
  # A simple secret generator
3
4
  #
@@ -1,7 +1,7 @@
1
1
  module LeapCli
2
2
  unless defined?(LeapCli::VERSION)
3
- VERSION = '1.5.1'
4
- COMPATIBLE_PLATFORM_VERSION = '0.3.0'..'1.99'
3
+ VERSION = '1.5.6'
4
+ COMPATIBLE_PLATFORM_VERSION = '0.5.2'..'1.99'
5
5
  SUMMARY = 'Command line interface to the LEAP platform'
6
6
  DESCRIPTION = 'The command "leap" can be used to manage a bevy of servers running the LEAP platform from the comfort of your own home.'
7
7
  LOAD_PATHS = ['lib', 'vendor/certificate_authority/lib', 'vendor/rsync_command/lib']
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: leap_cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.1
4
+ version: 1.5.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-03-17 00:00:00.000000000 Z
12
+ date: 2014-06-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest
@@ -114,7 +114,7 @@ dependencies:
114
114
  requirements:
115
115
  - - ~>
116
116
  - !ruby/object:Gem::Version
117
- version: 2.13.5
117
+ version: 2.15.5
118
118
  type: :runtime
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
@@ -122,7 +122,7 @@ dependencies:
122
122
  requirements:
123
123
  - - ~>
124
124
  - !ruby/object:Gem::Version
125
- version: 2.13.5
125
+ version: 2.15.5
126
126
  - !ruby/object:Gem::Dependency
127
127
  name: net-ssh
128
128
  requirement: !ruby/object:Gem::Requirement
@@ -203,6 +203,22 @@ dependencies:
203
203
  - - ! '>='
204
204
  - !ruby/object:Gem::Version
205
205
  version: '0'
206
+ - !ruby/object:Gem::Dependency
207
+ name: base32
208
+ requirement: !ruby/object:Gem::Requirement
209
+ none: false
210
+ requirements:
211
+ - - ! '>='
212
+ - !ruby/object:Gem::Version
213
+ version: '0'
214
+ type: :runtime
215
+ prerelease: false
216
+ version_requirements: !ruby/object:Gem::Requirement
217
+ none: false
218
+ requirements:
219
+ - - ! '>='
220
+ - !ruby/object:Gem::Version
221
+ version: '0'
206
222
  - !ruby/object:Gem::Dependency
207
223
  name: activemodel
208
224
  requirement: !ruby/object:Gem::Requirement
@@ -244,6 +260,7 @@ files:
244
260
  - lib/leap_cli/commands/ca.rb
245
261
  - lib/leap_cli/commands/pre.rb
246
262
  - lib/leap_cli/commands/compile.rb
263
+ - lib/leap_cli/commands/db.rb
247
264
  - lib/leap_cli/commands/user.rb
248
265
  - lib/leap_cli/commands/node.rb
249
266
  - lib/leap_cli/commands/clean.rb
@@ -265,6 +282,7 @@ files:
265
282
  - lib/leap_cli/requirements.rb
266
283
  - lib/leap_cli/path.rb
267
284
  - lib/leap_cli/leapfile.rb
285
+ - lib/leap_cli/exceptions.rb
268
286
  - lib/leap_cli/log.rb
269
287
  - lib/leap_cli/util.rb
270
288
  - lib/leap_cli/config/manager.rb