summaryrefslogtreecommitdiff
path: root/lib/leap_cli
diff options
context:
space:
mode:
Diffstat (limited to 'lib/leap_cli')
-rw-r--r--lib/leap_cli/commands/clean.rb6
-rw-r--r--lib/leap_cli/commands/compile.rb16
-rw-r--r--lib/leap_cli/commands/deploy.rb21
-rw-r--r--lib/leap_cli/commands/node.rb (renamed from lib/leap_cli/commands/bootstrap.rb)100
-rw-r--r--lib/leap_cli/commands/pre.rb5
-rw-r--r--lib/leap_cli/commands/shell.rb12
-rw-r--r--lib/leap_cli/commands/user.rb19
-rw-r--r--lib/leap_cli/commands/util.rb159
-rw-r--r--lib/leap_cli/config/manager.rb28
-rw-r--r--lib/leap_cli/path.rb189
-rw-r--r--lib/leap_cli/remote/plugin.rb35
-rw-r--r--lib/leap_cli/remote/tasks.rb36
-rw-r--r--lib/leap_cli/util.rb134
13 files changed, 547 insertions, 213 deletions
diff --git a/lib/leap_cli/commands/clean.rb b/lib/leap_cli/commands/clean.rb
index ed9c901..8847b7d 100644
--- a/lib/leap_cli/commands/clean.rb
+++ b/lib/leap_cli/commands/clean.rb
@@ -4,11 +4,11 @@ module LeapCli
desc 'Removes all files generated with the "compile" command'
command :clean do |c|
c.action do |global_options,options,args|
- Dir.glob(named_path(:hiera, '*')).each do |file|
+ Dir.glob(path([:hiera, '*'])).each do |file|
remove_file! file
end
- remove_file! named_path(:authorized_keys)
- remove_file! named_path(:known_hosts)
+ remove_file! path(:authorized_keys)
+ remove_file! path(:known_hosts)
end
end
diff --git a/lib/leap_cli/commands/compile.rb b/lib/leap_cli/commands/compile.rb
index 429d1c5..c5bb93e 100644
--- a/lib/leap_cli/commands/compile.rb
+++ b/lib/leap_cli/commands/compile.rb
@@ -5,20 +5,14 @@ module LeapCli
desc 'Compile json files to hiera configs'
command :compile do |c|
c.action do |global_options,options,args|
- manager.load(Path.provider)
- ensure_dir(Path.hiera)
- manager.export(Path.hiera)
- update_authorized_keys
- update_known_hosts
+ update_compiled_ssh_configs # this must come first, hiera configs import these files.
+ manager.export Path.named_path(:hiera_dir) # generate a hiera .yaml config for each node
end
end
- def update_authorized_keys
- buffer = StringIO.new
- Dir.glob(named_path(:user_ssh, '*')).each do |keyfile|
- buffer << File.read(keyfile)
- end
- write_file!(:authorized_keys, buffer.string)
+ def update_compiled_ssh_configs
+ update_authorized_keys
+ update_known_hosts
end
end
diff --git a/lib/leap_cli/commands/deploy.rb b/lib/leap_cli/commands/deploy.rb
index 9ec984c..c5efed5 100644
--- a/lib/leap_cli/commands/deploy.rb
+++ b/lib/leap_cli/commands/deploy.rb
@@ -6,12 +6,21 @@ module LeapCli
arg_name '<node filter>'
command :deploy do |c|
c.action do |global_options,options,args|
- nodes = manager.filter(args)
- say "Deploying to these nodes: #{nodes.keys.join(', ')}"
- if agree "Continue? "
- say "deploy not yet implemented"
- else
- say "OK. Bye."
+ nodes = manager.filter!(args)
+ if nodes.size > 1
+ say "Deploying to these nodes: #{nodes.keys.join(', ')}"
+ unless agree "Continue? "
+ quit! "OK. Bye."
+ end
+ end
+ leap_root = '/root/leap'
+ ssh_connect(nodes) do |ssh|
+ ssh.leap.mkdir_leap leap_root
+ ssh.leap.rsync_update do |server|
+ node = manager.node(server.host)
+ {:source => Path.named_path([:hiera, node.name]), :dest => "#{leap_root}/config/#{node.name}.yaml"}
+ end
+ ssh.apply_puppet
end
end
end
diff --git a/lib/leap_cli/commands/bootstrap.rb b/lib/leap_cli/commands/node.rb
index 11188fb..46c8fb6 100644
--- a/lib/leap_cli/commands/bootstrap.rb
+++ b/lib/leap_cli/commands/node.rb
@@ -3,6 +3,10 @@ require 'tempfile'
module LeapCli; module Commands
+ ##
+ ## COMMANDS
+ ##
+
#desc 'Create a new configuration for a node'
#command :'new-node' do |c|
# c.action do |global_options,options,args|
@@ -13,12 +17,14 @@ module LeapCli; module Commands
arg_name '<node-name>', :optional => false, :multiple => false
command :'init-node' do |c|
c.action do |global_options,options,args|
- node_name = args.first
- node = manager.node(node_name)
- assert!(node, "Node '#{node_name}' not found.")
- progress("Pinging #{node.name}")
- assert_run!("ping -W 1 -c 1 #{node.ip_address}", "Could not ping #{node_name} (address #{node.ip_address}). Try again, we only send a single ping.")
- install_public_host_key(node)
+ node = get_node_from_args(args)
+ ping_node(node)
+ save_public_host_key(node)
+ update_compiled_ssh_configs
+ ssh_connect(node, :bootstrap => true) do |ssh|
+ ssh.install_authorized_keys
+ ssh.install_prerequisites
+ end
end
end
@@ -29,21 +35,65 @@ module LeapCli; module Commands
end
desc 'not yet implemented'
+ arg_name '<node-name>', :optional => false, :multiple => false
command :'rm-node' do |c|
c.action do |global_options,options,args|
+ remove_file!()
end
end
+ ##
+ ## PUBLIC HELPERS
+ ##
+
+ #
+ # generates the known_hosts file.
+ #
+ # we do a 'late' binding on the hostnames and ip part of the ssh pub key record in order to allow
+ # for the possibility that the hostnames or ip has changed in the node configuration.
+ #
+ def update_known_hosts
+ buffer = StringIO.new
+ manager.nodes.values.each do |node|
+ hostnames = [node.name, node.domain.internal, node.domain.full, node.ip_address].join(',')
+ pub_key = read_file([:node_ssh_pub_key,node.name])
+ if pub_key
+ buffer << [hostnames, pub_key].join(' ')
+ end
+ end
+ write_file!(:known_hosts, buffer.string)
+ end
+
+ def get_node_from_args(args)
+ node_name = args.first
+ node = manager.node(node_name)
+ assert!(node, "Node '#{node_name}' not found.")
+ node
+ end
+
+ private
+
+ ##
+ ## PRIVATE HELPERS
+ ##
+
#
# saves the public ssh host key for node into the provider directory.
#
# see `man sshd` for the format of known_hosts
#
- def install_public_host_key(node)
+ def save_public_host_key(node)
progress("Fetching public SSH host key for #{node.name}")
public_key, key_type = get_public_key_for_ip(node.ip_address)
- if key_in_known_hosts?(public_key, [node.name, node.ip_address, node.domain.name])
- progress("Public ssh host key for #{node.name} is already trusted (key found in known_hosts)")
+ pub_key_path = Path.named_path([:node_ssh_pub_key, node.name])
+ if Path.exists?(pub_key_path)
+ if file_content_equals?(pub_key_path, node_pub_key_contents(key_type, public_key))
+ progress("Public SSH host key for #{node.name} has not changed")
+ else
+ bail!("WARNING: The public SSH host key we just fetched for #{node.name} doesn't match what we have saved previously. Remove the file #{pub_key_path} if you really want to change it.")
+ end
+ elsif key_in_known_hosts?(public_key, [node.name, node.ip_address, node.domain.name])
+ progress("Public SSH host key for #{node.name} is trusted (key found in your ~/.ssh/known_hosts)")
else
fingerprint, bits = ssh_key_fingerprint(key_type, public_key)
puts
@@ -55,12 +105,9 @@ module LeapCli; module Commands
bail!
else
puts
- # we write the file without ipaddress or hostname, because these might change later, but we want to keep the same key.
- write_file!([:node_ssh_pub_key, node.name], [key_type, public_key].join(' '))
- update_known_hosts
+ write_file!([:node_ssh_pub_key, node.name], node_pub_key_contents(key_type, public_key))
end
end
-
end
def get_public_key_for_ip(address)
@@ -93,6 +140,10 @@ module LeapCli; module Commands
#
# gets a fingerprint for a key string
#
+ # i think this could better be done this way:
+ # blob = Net::SSH::Buffer.from(:key, key).to_s
+ # fingerprint = OpenSSL::Digest::MD5.hexdigest(blob).scan(/../).join(":")
+ #
def ssh_key_fingerprint(type, key)
assert_bin!('ssh-keygen')
file = Tempfile.new('leap_cli_public_key_')
@@ -110,22 +161,19 @@ module LeapCli; module Commands
end
end
+ def ping_node(node)
+ progress("Pinging #{node.name}")
+ assert_run!("ping -W 1 -c 1 #{node.ip_address}", "Could not ping #{node.name} (address #{node.ip_address}). Try again, we only send a single ping.")
+ end
+
#
- # generates the known_hosts file.
+ # returns a string that can be used for the contents of the files/nodes/x/x_ssh_key.pub file
#
- # we do a 'late' binding on the hostnames and ip part of the ssh pub key record in order to allow
- # for the possibility that the hostnames or ip has changed in the node configuration.
+ # We write the file without ipaddress or hostname, because these might change later.
+ # The ip and host is added at when compiling the combined known_hosts file.
#
- def update_known_hosts
- buffer = StringIO.new
- manager.nodes.values.each do |node|
- hostnames = [node.name, node.domain.internal, node.domain.full, node.ip_address].join(',')
- pub_key = read_file([:node_ssh_pub_key,node.name])
- if pub_key
- buffer << [hostnames, pub_key].join(' ')
- end
- end
- write_file!(:known_hosts, buffer.string)
+ def node_pub_key_contents(key_type, public_key)
+ [key_type, public_key].join(' ')
end
end; end \ No newline at end of file
diff --git a/lib/leap_cli/commands/pre.rb b/lib/leap_cli/commands/pre.rb
index ada6a6a..d80a9c2 100644
--- a/lib/leap_cli/commands/pre.rb
+++ b/lib/leap_cli/commands/pre.rb
@@ -20,6 +20,11 @@ module LeapCli
# set verbosity
#
LeapCli.log_level = global[:verbose].to_i
+ if LeapCli.log_level > 1
+ ENV['GLI_DEBUG'] = "true"
+ else
+ ENV['GLI_DEBUG'] = "false"
+ end
#
# require a root directory
diff --git a/lib/leap_cli/commands/shell.rb b/lib/leap_cli/commands/shell.rb
new file mode 100644
index 0000000..df392bd
--- /dev/null
+++ b/lib/leap_cli/commands/shell.rb
@@ -0,0 +1,12 @@
+module LeapCli; module Commands
+
+ desc 'Log in to the specified node with an interactive shell'
+ arg_name '<node-name>', :optional => false, :multiple => false
+ command :shell, :ssh do |c|
+ c.action do |global_options,options,args|
+ node = get_node_from_args(args)
+ exec "ssh -l root -o 'HostName=#{node.ip_address}' -o 'HostKeyAlias=#{node.name}' -o 'UserKnownHostsFile=#{path(:known_hosts)}' -o 'StrictHostKeyChecking=yes' #{node.name}"
+ end
+ end
+
+end; end \ No newline at end of file
diff --git a/lib/leap_cli/commands/user.rb b/lib/leap_cli/commands/user.rb
index 00c4b62..7be91c8 100644
--- a/lib/leap_cli/commands/user.rb
+++ b/lib/leap_cli/commands/user.rb
@@ -1,13 +1,14 @@
require 'gpgme'
#
-# notes:
+# perhaps we want to verify that the key files are actually the key files we expect.
+# we could use 'file' for this:
#
-# file ~/.gnupg/00440025.asc
-# /home/elijah/.gnupg/00440025.asc: PGP public key block
+# > file ~/.gnupg/00440025.asc
+# ~/.gnupg/00440025.asc: PGP public key block
#
-# file ~/.ssh/id_rsa.pub
-# /home/elijah/.ssh/id_rsa.pub: OpenSSH RSA public key
+# > file ~/.ssh/id_rsa.pub
+# ~/.ssh/id_rsa.pub: OpenSSH RSA public key
#
module LeapCli
@@ -103,5 +104,13 @@ module LeapCli
return `gpg --armor --export-options export-minimal --export #{key_id}`.strip
end
+ def update_authorized_keys
+ buffer = StringIO.new
+ Dir.glob(path([:user_ssh, '*'])).each do |keyfile|
+ buffer << File.read(keyfile)
+ end
+ write_file!(:authorized_keys, buffer.string)
+ end
+
end
end \ No newline at end of file
diff --git a/lib/leap_cli/commands/util.rb b/lib/leap_cli/commands/util.rb
index b5a102f..803fe88 100644
--- a/lib/leap_cli/commands/util.rb
+++ b/lib/leap_cli/commands/util.rb
@@ -1,34 +1,143 @@
-module LeapCli
- module Commands
- extend self
- extend LeapCli::Util
+module LeapCli; module Commands
- #
- # keeps prompting the user for a numbered choice, until they pick a good one or bail out.
- #
- # block is yielded and is responsible for rendering the choices.
- #
- def numbered_choice_menu(msg, items, &block)
- while true
- say("\n" + msg + ':')
- items.each_with_index &block
- say("q. quit")
- index = ask("number 1-#{items.length}> ")
- if index.empty?
- next
- elsif index =~ /q/
+ extend self
+ extend LeapCli::Util
+
+ def path(name)
+ Path.named_path(name)
+ end
+
+ #
+ # keeps prompting the user for a numbered choice, until they pick a good one or bail out.
+ #
+ # block is yielded and is responsible for rendering the choices.
+ #
+ def numbered_choice_menu(msg, items, &block)
+ while true
+ say("\n" + msg + ':')
+ items.each_with_index &block
+ say("q. quit")
+ index = ask("number 1-#{items.length}> ")
+ if index.empty?
+ next
+ elsif index =~ /q/
+ bail!
+ else
+ i = index.to_i - 1
+ if i < 0 || i >= items.length
bail!
else
- i = index.to_i - 1
- if i < 0 || i >= items.length
- bail!
- else
- return i
- end
+ return i
end
end
end
+ end
+
+ #
+ #
+ #
+ # FYI
+ # Capistrano::Logger::IMPORTANT = 0
+ # Capistrano::Logger::INFO = 1
+ # Capistrano::Logger::DEBUG = 2
+ # Capistrano::Logger::TRACE = 3
+ #
+ def ssh_connect(nodes, options={}, &block)
+ node_list = parse_node_list(nodes)
+
+ cap = new_capistrano
+ cap.logger.level = LeapCli.log_level
+ user = options[:user] || 'root'
+ cap.set :user, user
+ cap.set :ssh_options, ssh_options
+ cap.set :use_sudo, false # we may want to change this in the future
+
+ # supply drop options
+ cap.set :puppet_source, [Path.platform, 'puppet'].join('/')
+ cap.set :puppet_destination, '/root/leap'
+ #cap.set :puppet_command, 'puppet apply'
+ cap.set :puppet_lib, "puppet/modules"
+ cap.set :puppet_parameters, '--confdir=puppet puppet/manifests/site.pp'
+ #cap.set :puppet_stream_output, false
+ #puppet apply --confdir=puppet puppet/manifests/site.pp | grep -v 'warning:.*is deprecated'
+ #puppet_cmd = "cd #{puppet_destination} && #{sudo_cmd} #{puppet_command} --modulepath=#{puppet_lib} #{puppet_parameters}"
+
+ #
+ # allow password authentication when we are bootstraping a single node.
+ #
+ if options[:bootstrap] && node_list.size == 1
+ hostname = node_list.values.first.name
+ cap.set(:password) { ask("SSH password for #{user}@#{hostname}> ") } # only called if needed
+ # this can be used instead to hide echo -- Capistrano::CLI.password_prompt
+ end
+
+ node_list.each do |name, node|
+ cap.server node.name, :dummy_arg, node_options(node)
+ end
+ yield cap
+ end
+
+
+ private
+
+
+ #
+ # For available options, see http://net-ssh.github.com/net-ssh/classes/Net/SSH.html#method-c-start
+ #
+ def ssh_options
+ {
+ :config => false,
+ :user_known_hosts_file => path(:known_hosts),
+ :paranoid => true
+ }
+ end
+
+ #
+ # For notes on advanced ways to set server-specific options, see
+ # http://railsware.com/blog/2011/11/02/advanced-server-definitions-in-capistrano/
+ #
+ def node_options(node)
+ password_proc = Proc.new {Capistrano::CLI.password_prompt "Root SSH password for #{node.name}"} # only called if needed
+ {
+ :password => password_proc,
+ :ssh_options => {
+ :host_key_alias => node.name,
+ :host_name => node.ip_address,
+ :port => node.ssh.port
+ }
+ }
+ end
+ def new_capistrano
+ # load once the library files
+ @capistrano_enabled ||= begin
+ require 'capistrano'
+ #require 'capistrano/cli'
+ require 'leap_cli/remote/plugin'
+ Capistrano.plugin :leap, LeapCli::Remote::Plugin
+ true
+ end
+
+ # create capistrano instance
+ cap = Capistrano::Configuration.new
+
+ # add tasks to capistrano instance
+ cap.load File.dirname(__FILE__) + '/../remote/tasks.rb'
+
+ return cap
end
-end
+
+ def parse_node_list(nodes)
+ if nodes.is_a? Config::Object
+ Config::ObjectList.new(node_list)
+ elsif nodes.is_a? Config::ObjectList
+ nodes
+ elsif nodes.is_a? String
+ manager.filter!(nodes)
+ else
+ bail! "argument error"
+ end
+ end
+
+end; end
diff --git a/lib/leap_cli/config/manager.rb b/lib/leap_cli/config/manager.rb
index 432ba0b..79ae5b8 100644
--- a/lib/leap_cli/config/manager.rb
+++ b/lib/leap_cli/config/manager.rb
@@ -18,12 +18,16 @@ module LeapCli
#
# load .json configuration files
#
- def load(dir)
- @services = load_all_json("#{dir}/services/*.json")
- @tags = load_all_json("#{dir}/tags/*.json")
- @common = load_all_json("#{dir}/common.json")['common']
- @provider = load_all_json("#{dir}/provider.json")['provider']
- @nodes = load_all_json("#{dir}/nodes/*.json")
+ def load(provider_dir=Path.provider)
+ @services = load_all_json(Path.named_path( [:service_config, '*'], provider_dir ))
+ @tags = load_all_json(Path.named_path( [:tag_config, '*'], provider_dir ))
+ @common = load_all_json(Path.named_path( :common_config, provider_dir ))['common']
+ @provider = load_all_json(Path.named_path( :provider_config, provider_dir ))['provider']
+ @nodes = load_all_json(Path.named_path( [:node_config, '*'], provider_dir ))
+
+ Util::assert!(@provider, "Failed to load provider.json")
+ Util::assert!(@common, "Failed to load common.json")
+
@nodes.each do |name, node|
@nodes[name] = apply_inheritance(node)
end
@@ -32,11 +36,10 @@ module LeapCli
#
# save compiled hiera .yaml files
#
- def export(dir)
+ def export(dir=Path.named_path(:hiera_dir))
existing_files = Dir.glob(dir + '/*.yaml')
updated_files = []
@nodes.each do |name, node|
- # not sure if people will approve of this change:
filepath = "#{dir}/#{name}.yaml"
updated_files << filepath
Util::write_file!(filepath, node.to_yaml)
@@ -86,6 +89,15 @@ module LeapCli
end
#
+ # same as filter(), but exits if there is no matching nodes
+ #
+ def filter!(filters)
+ node_list = filter(filters)
+ Util::assert! node_list.any?, "Could not match any nodes from '#{filters}'"
+ return node_list
+ end
+
+ #
# returns a single Config::Object that corresponds to a Node.
#
def node(name)
diff --git a/lib/leap_cli/path.rb b/lib/leap_cli/path.rb
index f3cbad9..9b4e3c9 100644
--- a/lib/leap_cli/path.rb
+++ b/lib/leap_cli/path.rb
@@ -1,69 +1,166 @@
require 'fileutils'
-module LeapCli
- module Path
+module LeapCli; module Path
- def self.root
- @root ||= File.expand_path("#{provider}/..")
- end
+ NAMED_PATHS = {
+ # directories
+ :hiera_dir => 'hiera',
+ :files_dir => 'files',
+ :nodes_dir => 'nodes',
+ :services_dir => 'services',
+ :tags_dir => 'tags',
- def self.platform
- @platform ||= File.expand_path("#{root}/leap_platform")
- end
+ # input config files
+ :common_config => 'common.json',
+ :provider_config => 'provider.json',
+ :node_config => 'nodes/#{arg}.json',
+ :service_config => 'services/#{arg}.json',
+ :tag_config => 'tags/#{arg}.json',
- def self.provider
- @provider ||= if @root
- File.expand_path("#{root}/provider")
- else
- find_in_directory_tree('provider.json')
- end
- end
+ # output files
+ :user_ssh => 'users/#{arg}/#{arg}_ssh.pub',
+ :user_pgp => 'users/#{arg}/#{arg}_pgp.pub',
+ :hiera => 'hiera/#{arg}.yaml',
+ :node_ssh_pub_key => 'files/nodes/#{arg}/#{arg}_ssh_key.pub',
+ :known_hosts => 'files/ssh/known_hosts',
+ :authorized_keys => 'files/ssh/authorized_keys'
+ }
- def self.hiera
- @hiera ||= "#{provider}/hiera"
- end
+ #
+ # required file structure
+ #
+ # Option 1 -- A project directory with platform and provider directories
+ #
+ # -: $root
+ # :-- leap_platform
+ # '-: provider
+ # '-- provider.json
+ #
+ # $root can be any name
+ #
+ # Option 2 -- A stand alone provider directory
+ #
+ # -: $provider
+ # '-- provider.json
+ #
+ # $provider can be any name. Some commands are not available.
+ #
+ # In either case, the 'leap' command must be run from inside the provider directory or
+ # you must specify root directory with --root=dir.
+ #
- def self.files
- @files ||= "#{provider}/files"
- end
+ def self.root
+ @root ||= File.expand_path("#{provider}/..")
+ end
+
+ def self.platform
+ @platform ||= File.expand_path("#{root}/leap_platform")
+ end
+
+ def self.platform_provider
+ "#{platform}/provider"
+ end
- def self.ok?
- provider != '/'
+ def self.provider
+ @provider ||= if @root
+ File.expand_path("#{root}/provider")
+ else
+ find_in_directory_tree('provider.json')
end
+ end
+
+ def self.ok?
+ provider != '/'
+ end
+
+ def self.set_root(root_path)
+ @root = File.expand_path(root_path)
+ raise "No such directory '#{@root}'" unless File.directory?(@root)
+ end
- def self.set_root(root_path)
- @root = File.expand_path(root_path)
- raise "No such directory '#{@root}'" unless File.directory?(@root)
+ #
+ # all the places we search for a file when using find_file.
+ # this is perhaps too many places.
+ #
+ def self.search_path
+ @search_path ||= begin
+ search_path = []
+ [Path.platform_provider, Path.provider].each do |provider|
+ files_dir = named_path(:files_dir, provider)
+ search_path << provider
+ search_path << named_path(:files_dir, provider)
+ search_path << named_path(:nodes_dir, files_dir)
+ search_path << named_path(:services_dir, files_dir)
+ search_path << named_path(:tags_dir, files_dir)
+ end
+ search_path
end
+ end
- def self.find_file(name, filename)
- path = [Path.files, filename].join('/')
+ #
+ # tries to find a file somewhere with 'filename', under a directory 'name' if possible.
+ #
+ def self.find_file(name, filename)
+ # named path?
+ if filename.is_a? Symbol
+ path = named_path([filename, name], platform_provider)
return path if File.exists?(path)
- path = [Path.files, name, filename].join('/')
+ path = named_path([filename, name], provider)
return path if File.exists?(path)
- path = [Path.files, 'nodes', name, filename].join('/')
- return path if File.exists?(path)
- path = [Path.files, 'services', name, filename].join('/')
+ end
+
+ # otherwise, lets search
+ search_path.each do |path_root|
+ path = [path_root, name, filename].join('/')
return path if File.exists?(path)
- path = [Path.files, 'tags', name, filename].join('/')
+ end
+ search_path.each do |path_root|
+ path = [path_root, filename].join('/')
return path if File.exists?(path)
+ end
+
+ # give up
+ return nil
+ end
+
+ #
+ # Three ways of calling:
+ #
+ # - named_path [:user_ssh, 'bob'] ==> 'users/bob/bob_ssh.pub'
+ # - named_path :known_hosts ==> 'files/ssh/known_hosts'
+ # - named_path '/tmp/x' ==> '/tmp/x'
+ #
+ def self.named_path(name, provider_dir=Path.provider)
+ if name.is_a? Array
+ name, arg = name
+ else
+ arg = nil
+ end
- # give up
- return nil
+ if name.is_a? Symbol
+ Util::assert!(NAMED_PATHS[name], "Error, I don't know the path for :#{name} (with argument '#{arg}')")
+ filename = eval('"' + NAMED_PATHS[name] + '"')
+ return provider_dir + '/' + filename
+ else
+ return name
end
+ end
- private
+ def self.exists?(name, provider_dir=nil)
+ File.exists?(named_path(name, provider_dir))
+ end
+
+ private
- def self.find_in_directory_tree(filename)
- search_dir = Dir.pwd
- while search_dir != "/"
- Dir.foreach(search_dir) do |f|
- return search_dir if f == filename
- end
- search_dir = File.dirname(search_dir)
+ def self.find_in_directory_tree(filename)
+ search_dir = Dir.pwd
+ while search_dir != "/"
+ Dir.foreach(search_dir) do |f|
+ return search_dir if f == filename
end
- return search_dir
+ search_dir = File.dirname(search_dir)
end
-
+ return search_dir
end
-end
+
+end; end
diff --git a/lib/leap_cli/remote/plugin.rb b/lib/leap_cli/remote/plugin.rb
new file mode 100644
index 0000000..22ffe34
--- /dev/null
+++ b/lib/leap_cli/remote/plugin.rb
@@ -0,0 +1,35 @@
+#
+# these methods are made available in capistrano tasks as 'leap.method_name'
+#
+
+module LeapCli; module Remote; module Plugin
+
+ def mkdir_leap(base_dir)
+ run "mkdir -p #{base_dir}/config && chown -R root #{base_dir} && chmod -R ag-rwx,u+rwX #{base_dir}"
+ end
+
+ #
+ # takes a block, yielded a server, that should return {:source => '', :dest => ''}
+ #
+ def rsync_update
+ SupplyDrop::Util.thread_pool_size = puppet_parallel_rsync_pool_size
+ servers = SupplyDrop::Util.optionally_async(find_servers, puppet_parallel_rsync)
+ failed_servers = servers.map do |server|
+ #p server
+ #p server.options
+ # build rsync command
+ _paths = yield server
+ _source = _paths[:source]
+ _user = server.user || fetch(:user, ENV['USER'])
+ _dest = SupplyDrop::Rsync.remote_address(_user, server.host, _paths[:dest])
+ _opts = {:ssh => ssh_options.merge(server.options[:ssh_options]||{})}
+ rsync_cmd = SupplyDrop::Rsync.command(_source, _dest, _opts)
+
+ # run command
+ logger.debug rsync_cmd
+ server.host unless system rsync_cmd
+ end.compact
+ raise "rsync failed on #{failed_servers.join(',')}" if failed_servers.any?
+ end
+
+end; end; end \ No newline at end of file
diff --git a/lib/leap_cli/remote/tasks.rb b/lib/leap_cli/remote/tasks.rb
new file mode 100644
index 0000000..e524133
--- /dev/null
+++ b/lib/leap_cli/remote/tasks.rb
@@ -0,0 +1,36 @@
+#
+# This file is evaluated just the same as a typical capistrano "deploy.rb"
+# For DSL manual, see https://github.com/capistrano/capistrano/wiki
+#
+
+require 'supply_drop'
+
+MAX_HOSTS = 10
+
+task :install_authorized_keys, :max_hosts => MAX_HOSTS do
+ run 'mkdir -p /root/.ssh && chmod 700 /root/.ssh'
+ upload LeapCli::Path.named_path(:authorized_keys), '/root/.ssh/authorized_keys', :mode => '600'
+end
+
+task :install_prerequisites, :max_hosts => MAX_HOSTS do
+ puppet.bootstrap.ubuntu
+ #
+ # runs this:
+ # run "mkdir -p #{puppet_destination}"
+ # run "#{sudo} apt-get update"
+ # run "#{sudo} apt-get install -y puppet rsync"
+ #
+end
+
+#task :update_platform, :max_hosts => MAX_HOSTS do
+# puppet.update_code
+#end
+
+#task :mk_leap_dir, :max_hosts => MAX_HOSTS do
+# run 'mkdir -p /root/leap/config && chown -R root /root/leap && chmod -R ag-rwx,u+rwX /root/leap'
+#end
+
+task :apply_puppet, :max_hosts => MAX_HOSTS do
+ raise "now such directory #{puppet_source}" unless File.directory?(puppet_source)
+ puppet.apply
+end
diff --git a/lib/leap_cli/util.rb b/lib/leap_cli/util.rb
index 6095b2b..503f865 100644
--- a/lib/leap_cli/util.rb
+++ b/lib/leap_cli/util.rb
@@ -2,13 +2,6 @@ require 'md5'
module LeapCli
- class FileMissing < Exception
- attr_reader :file_path
- def initialize(file_path)
- @file_path = file_path
- end
- end
-
module Util
extend self
@@ -29,8 +22,8 @@ module LeapCli
# quit with a message that we are bailing out.
#
def bail!(message="")
- say(message)
- say("Bailing out.")
+ puts(message)
+ puts("Bailing out.")
raise SystemExit.new
#ENV['GLI_DEBUG'] = "false"
#exit_now!(message)
@@ -40,7 +33,7 @@ module LeapCli
# quit with no message
#
def quit!(message='')
- say(message)
+ puts(message)
raise SystemExit.new
end
@@ -111,76 +104,58 @@ module LeapCli
end
end
- NAMED_PATHS = {
- :user_ssh => 'users/#{arg}/#{arg}_ssh.pub',
- :user_pgp => 'users/#{arg}/#{arg}_pgp.pub',
- :hiera => 'hiera/#{arg}.yaml',
- :node_ssh_pub_key => 'files/nodes/#{arg}/#{arg}_ssh_key.pub',
- :known_hosts => 'files/ssh/known_hosts',
- :authorized_keys => 'files/ssh/authorized_keys'
- }
-
- def read_file!(*args)
- begin
- try_to_read_file!(*args)
- rescue FileMissing => exc
- bail!("File '%s' does not exist." % exc.file_path)
- end
- end
-
- def read_file(*args)
- begin
- try_to_read_file!(*args)
- rescue FileMissing => exc
- return nil
- end
- end
+ ##
+ ## FILE READING, WRITING, DELETING, and MOVING
+ ##
#
- # Three ways to call:
+ # All file read and write methods support using named paths in the place of an actual file path.
+ #
+ # To call using a named path, use a symbol in the place of filepath, like so:
+ #
+ # read_file(:known_hosts)
+ #
+ # In some cases, the named path will take an argument. In this case, set the filepath to be an array:
#
- # - write_file!(file_path, file_contents)
- # - write_file!(named_path, file_contents)
- # - write_file!(named_path, file_contents, argument) -- deprecated
- # - write_file!([named_path, argument], file_contents)
+ # write_file!([:user_ssh, 'bob'], ssh_key_str)
#
+ # To resolve a named path, use the shortcut helper 'path()'
#
- def write_file!(*args)
- if args.first.is_a? Symbol
- write_named_file!(*args)
- elsif args.first.is_a? Array
- write_named_file!(args.first[0], args.last, args.first[1])
+ # path([:user_ssh, 'bob']) ==> files/users/bob/bob_ssh_pub.key
+ #
+
+ def read_file!(filepath)
+ filepath = Path.named_path(filepath)
+ if !File.exists?(filepath)
+ bail!("File '%s' does not exist." % exc.file_path)
else
- write_to_path!(*args)
+ File.read(filepath)
end
end
- def remove_file!(file_path)
- if File.exists?(file_path)
- File.unlink(file_path)
- progress_removed(file_path)
+ def read_file(filepath)
+ filepath = Path.named_path(filepath)
+ if !File.exists?(filepath)
+ nil
+ else
+ File.read(filepath)
end
end
- #
- # saves a named file.
- #
- def write_named_file!(name, contents, arg=nil)
- fullpath = named_path(name, arg)
- write_to_path!(fullpath, contents)
- end
-
- def named_path(name, arg=nil)
- assert!(NAMED_PATHS[name], "Error, I don't know the path for :#{name} (with argument '#{arg}')")
- filename = eval('"' + NAMED_PATHS[name] + '"')
- fullpath = Path.provider + '/' + filename
+ def remove_file!(filepath)
+ filepath = Path.named_path(filepath)
+ if File.exists?(filepath)
+ File.unlink(filepath)
+ progress_removed(filepath)
+ end
end
- def write_to_path!(filepath, contents)
+ def write_file!(filepath, contents)
+ filepath = Path.named_path(filepath)
ensure_dir File.dirname(filepath)
existed = File.exists?(filepath)
if existed
- if file_content_is?(filepath, contents)
+ if file_content_equals?(filepath, contents)
progress_nochange filepath
return
end
@@ -197,9 +172,20 @@ module LeapCli
end
end
- private
+ #def rename_file(filepath)
+ #end
- def file_content_is?(filepath, contents)
+ #private
+
+ ##
+ ## PRIVATE HELPER METHODS
+ ##
+
+ #
+ # compares md5 fingerprints to see if the contents of a file match the string we have in memory
+ #
+ def file_content_equals?(filepath, contents)
+ filepath = Path.named_path(filepath)
output = `md5sum '#{filepath}'`.strip
if $?.to_i == 0
return output.split(" ").first == MD5.md5(contents).to_s
@@ -208,24 +194,6 @@ module LeapCli
end
end
- #
- # trys to read a file, raise exception if the file doesn't exist.
- #
- def try_to_read_file!(*args)
- if args.first.is_a? Symbol
- file_path = named_path(args.first)
- elsif args.first.is_a? Array
- file_path = named_path(*args.first)
- else
- file_path = args.first
- end
- if !File.exists?(file_path)
- raise FileMissing.new(file_path)
- else
- File.read(file_path)
- end
- end
-
end
end