leap_cli 1.5.1 → 1.5.6

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