lakitu 1.0.2 → 1.0.3
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.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +9 -0
- data/README.md +39 -19
- data/bin/lakitu +6 -0
- data/lakitu.gemspec +1 -1
- data/lib/lakitu.rb +59 -3
- data/lib/lakitu/configurer.rb +19 -0
- data/lib/lakitu/file_operator.rb +70 -0
- data/lib/lakitu/generator.rb +50 -0
- data/lib/lakitu/options.rb +78 -0
- data/lib/lakitu/provider.rb +11 -0
- data/lib/lakitu/providers/aws.rb +43 -0
- data/lib/lakitu/version.rb +2 -2
- data/spec/configurer_spec.rb +29 -0
- data/spec/file_operator_spec.rb +57 -0
- data/spec/generator_spec.rb +50 -0
- data/spec/lakitu_spec.rb +28 -0
- data/spec/options_spec.rb +34 -0
- data/spec/providers/aws_spec.rb +22 -0
- data/spec/spec_helper.rb +152 -0
- metadata +36 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a39c34d03f0b881eb80772851d0d81bd5b290525
|
4
|
+
data.tar.gz: 22a3bb290adbaec68dfeb32b8077a51b8c92be62
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3dd6856ef1ca80e7f1f553aa055a742d157597e698a09c01e3e93f199403b8e5c333466e487bbc6e103be4afc3277cc45d630257ef733765560744a4154a61e3
|
7
|
+
data.tar.gz: 608987d97b1e3539c6d1e613b649223cb263ba4eb98ca18b1f5fe1cdd8321f47ba43dafa845bef6c755efff930e0cde6cd8eda15ad3cb136f363af9bb864fd21
|
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.0.0-p598
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,31 +1,51 @@
|
|
1
1
|
# Lakitu
|
2
|
+

|
2
3
|
|
3
|
-
|
4
|
+
Generate an SSH config from AWS (or potentially other cloud provider) APIs.
|
4
5
|
|
5
|
-
##
|
6
|
+
## Prework
|
7
|
+
To use Lakitu effectively, you will need the following:
|
6
8
|
|
7
|
-
|
9
|
+
1. A modern Ruby (2.0+). You may need RVM for this.
|
10
|
+
2. A modern `bash-completion` package installed via `brew`, `apt-get`, or `yum`.
|
11
|
+
3. AWS profiles configured according to the [official documentation](http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#cli-multiple-profiles) (or via `aws configure`).
|
8
12
|
|
9
|
-
|
10
|
-
gem 'lakitu'
|
11
|
-
```
|
12
|
-
|
13
|
-
And then execute:
|
13
|
+
If you don't have 1-3 above, you will not get the most out of this project (and you are missing out on some other really cool stuff).
|
14
14
|
|
15
|
-
|
15
|
+
## Usage
|
16
|
+
```
|
17
|
+
$ gem install lakitu
|
18
|
+
$ lakitu generate
|
19
|
+
$ lakitu configure
|
20
|
+
$ lakitu edit
|
21
|
+
```
|
16
22
|
|
17
|
-
|
23
|
+
## Periodic Refresh
|
24
|
+
Simply add the following line to your `.bashrc`
|
25
|
+
```
|
26
|
+
lakitu generate &>~/.lakitu.log &
|
27
|
+
```
|
18
28
|
|
19
|
-
|
29
|
+
If you installed lakitu via RVM, you should add the following to your shell profile instead:
|
30
|
+
```
|
31
|
+
rvm default do lakitu generate &>~/.lakitu.log &
|
32
|
+
```
|
20
33
|
|
21
|
-
|
34
|
+
`lakitu generate` waits for a refresh interval to pass before re-generating the SSH config. This means you can safely add it to your `.bashrc` or equivalent, and your ssh config will refresh in the background only when that interval has passed.
|
22
35
|
|
23
|
-
|
36
|
+
## Another One?
|
37
|
+
There are several projects that will generate an SSH config from AWS instance data. This one aims to provide the following advantages:
|
24
38
|
|
25
|
-
|
39
|
+
- **Ease of use**: With a properly configured environment, it will detect your configured profiles for supported providers.
|
40
|
+
- **Configurability**: Easily edit a simple config file to control host alias formatting, profiles to skip, etc.
|
41
|
+
- **Automation**: Periodically refreshes the ssh config (via bashrc), and takes no action unless the config appears to be stale.
|
42
|
+
- **Extensibility**: A simple Provider API allows easy support for new providers.
|
43
|
+
- **Reliability**: A robust test suite ensures that Lakitu probably won't cause your machine to combust unexpectedly.
|
26
44
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
45
|
+
## Other Options
|
46
|
+
You could also check out the folowing if this is too much hubbub for you:
|
47
|
+
- https://rubygems.org/gems/knife-ec2-ssh-config
|
48
|
+
- https://rubygems.org/gems/ec2-ssh
|
49
|
+
- https://rubygems.org/gems/aws_ssh
|
50
|
+
- https://rubygems.org/gems/aws-ssh
|
51
|
+
- [An SSH tip for modern AWS patrons](http://codeinthehole.com/writing/an-ssh-tip-for-modern-aws-patrons/)
|
data/bin/lakitu
ADDED
data/lakitu.gemspec
CHANGED
@@ -5,7 +5,7 @@ require 'lakitu/version'
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "lakitu"
|
8
|
-
spec.version =
|
8
|
+
spec.version = LakituVersion::VERSION
|
9
9
|
spec.authors = ["Bryan Conrad"]
|
10
10
|
spec.email = ["bkconrad@gmail.com"]
|
11
11
|
spec.summary = %q{Ride the clouds}
|
data/lib/lakitu.rb
CHANGED
@@ -1,5 +1,61 @@
|
|
1
|
-
|
1
|
+
$LOAD_PATH << File.dirname(File.expand_path(__FILE__))
|
2
|
+
require "logger"
|
3
|
+
require "yaml"
|
4
|
+
require "thor"
|
5
|
+
|
6
|
+
class Lakitu < Thor
|
7
|
+
LOCAL_SSHCONFIG_PATH = File.expand_path '~/.ssh/local.sshconfig'
|
8
|
+
MANAGED_SSH_CONFIG_TOKEN = "# Managed by Lakitu"
|
9
|
+
OPTIONS_FILE_PATH = File.expand_path "~/.lakitu.yml"
|
10
|
+
SSH_PATH = File.expand_path '~/.ssh'
|
11
|
+
SSHCONFIG_PATH = File.expand_path '~/.ssh/config'
|
12
|
+
EDIT_FILE_COMMAND = "$EDITOR #{OPTIONS_FILE_PATH}"
|
13
|
+
DEFAULT_FORMAT = "%{profile}-%{name}-%{id}"
|
14
|
+
EDIT_LOCAL_CONFIG_COMMAND = "$EDITOR #{LOCAL_SSHCONFIG_PATH}"
|
15
|
+
|
16
|
+
class_options %w( force -f ) => :boolean
|
17
|
+
class_options %w( verbose -v ) => :boolean
|
18
|
+
|
19
|
+
desc "generate [options]", "Generate the ssh config"
|
20
|
+
def generate
|
21
|
+
Lakitu::Options.merge options
|
22
|
+
Lakitu::FileOperator.backup_ssh_config!
|
23
|
+
Lakitu::FileOperator.write_ssh_config! Lakitu::Generator.generate if Lakitu::FileOperator::should_overwrite
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "configure [options]", "Open Lakitu's config file in the system editor"
|
27
|
+
def configure
|
28
|
+
Lakitu::Options.merge options
|
29
|
+
Lakitu::Configurer.find_or_create_config
|
30
|
+
Lakitu::Configurer.edit
|
31
|
+
end
|
2
32
|
|
3
|
-
|
4
|
-
|
33
|
+
desc "edit", "edit #{Lakitu::LOCAL_SSHCONFIG_PATH} and generate config after"
|
34
|
+
def edit
|
35
|
+
Lakitu::Options.options[:force] = true
|
36
|
+
invoke :generate if Lakitu::Configurer.edit_local
|
37
|
+
end
|
38
|
+
|
39
|
+
@@logger = nil
|
40
|
+
def self.logger
|
41
|
+
unless @@logger
|
42
|
+
@@logger = ::Logger.new STDOUT
|
43
|
+
logger.level = Lakitu::Options.options.verbose ? ::Logger::DEBUG : ::Logger::INFO
|
44
|
+
logger.formatter = proc do |severity, datetime, progname, msg|
|
45
|
+
"#{severity}: #{msg}\n"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
@@logger
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.logger= arg
|
52
|
+
@@logger = arg
|
53
|
+
end
|
5
54
|
end
|
55
|
+
|
56
|
+
require "lakitu/version"
|
57
|
+
require "lakitu/configurer"
|
58
|
+
require "lakitu/file_operator"
|
59
|
+
require "lakitu/generator"
|
60
|
+
require "lakitu/options"
|
61
|
+
require "lakitu/providers/aws"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
module Lakitu::Configurer
|
3
|
+
def self.find_or_create_config
|
4
|
+
unless Lakitu::FileOperator.lakitu_config_exists?
|
5
|
+
Lakitu.logger.debug "Creating new config file"
|
6
|
+
Lakitu::FileOperator.write_lakitu_config Lakitu::Options.default_config
|
7
|
+
end
|
8
|
+
|
9
|
+
Lakitu::FileOperator.read_lakitu_config
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.edit
|
13
|
+
system Lakitu::EDIT_FILE_COMMAND
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.edit_local
|
17
|
+
system Lakitu::EDIT_LOCAL_CONFIG_COMMAND
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'ostruct'
|
3
|
+
module Lakitu::FileOperator
|
4
|
+
def self.key_path key_name
|
5
|
+
expected_key_path = File.join Lakitu::SSH_PATH, "#{key_name}.pem"
|
6
|
+
return nil unless File.exist? expected_key_path
|
7
|
+
Lakitu.logger.debug "Found key at #{expected_key_path}"
|
8
|
+
expected_key_path
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.write_ssh_config! content
|
12
|
+
raise RuntimeError.new("Won't overwrite unmanaged ssh config") unless ssh_config_is_managed? or !File.exist?(Lakitu::SSHCONFIG_PATH)
|
13
|
+
Lakitu.logger.info "Writing ssh config to #{Lakitu::SSHCONFIG_PATH}"
|
14
|
+
File.write Lakitu::SSHCONFIG_PATH, content
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.local_ssh_config
|
18
|
+
return File.read(Lakitu::LOCAL_SSHCONFIG_PATH) if File.exist?(Lakitu::LOCAL_SSHCONFIG_PATH)
|
19
|
+
return ""
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.should_overwrite
|
23
|
+
return true unless ssh_config_is_managed?
|
24
|
+
ssh_config_is_stale?
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.backup_ssh_config!
|
28
|
+
return unless File.exist? Lakitu::SSHCONFIG_PATH
|
29
|
+
unless ssh_config_is_managed?
|
30
|
+
Lakitu.logger.debug "SSH config is unmanaged"
|
31
|
+
if File.exist? Lakitu::LOCAL_SSHCONFIG_PATH
|
32
|
+
Lakitu.logger.fatal "Can't back up unmanaged ssh config: #{Lakitu::LOCAL_SSHCONFIG_PATH} already exists."
|
33
|
+
exit 1
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
Lakitu.logger.info "Moving #{Lakitu::SSHCONFIG_PATH} to #{Lakitu::LOCAL_SSHCONFIG_PATH}"
|
38
|
+
FileUtils.mv Lakitu::SSHCONFIG_PATH, Lakitu::LOCAL_SSHCONFIG_PATH
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.ssh_config_is_managed?
|
43
|
+
return false unless File.exist? Lakitu::SSHCONFIG_PATH
|
44
|
+
File.read(Lakitu::SSHCONFIG_PATH).include? Lakitu::MANAGED_SSH_CONFIG_TOKEN
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.ssh_config_is_stale?
|
48
|
+
ssh_config_age_minutes = (Time.now - File.mtime(Lakitu::SSHCONFIG_PATH)) / 60
|
49
|
+
effectively_stale = (ssh_config_age_minutes > options.refresh_interval_minutes or options.force)
|
50
|
+
Lakitu.logger.debug "SSH Config modified #{ssh_config_age_minutes} minutes ago, threshold: #{options.refresh_interval_minutes}"
|
51
|
+
Lakitu.logger.info "SSH config is still fresh, use --force to force generation" unless effectively_stale
|
52
|
+
return !!effectively_stale
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.lakitu_config_exists?
|
56
|
+
File.exist? Lakitu::OPTIONS_FILE_PATH
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.read_lakitu_config
|
60
|
+
File.read Lakitu::OPTIONS_FILE_PATH
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.write_lakitu_config content
|
64
|
+
File.write Lakitu::OPTIONS_FILE_PATH, content
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.options
|
68
|
+
Lakitu::Options.options
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'ostruct'
|
3
|
+
module Lakitu::Generator
|
4
|
+
CLAUSE_TEMPLATE=<<-EOF
|
5
|
+
Host <%= host %><% if keyfile %>
|
6
|
+
IdentityFile <%= keyfile %>
|
7
|
+
<% end %>
|
8
|
+
HostName <%= public_ip || private_ip %>
|
9
|
+
EOF
|
10
|
+
|
11
|
+
def self.generate
|
12
|
+
([ Lakitu::MANAGED_SSH_CONFIG_TOKEN, Lakitu::FileOperator::local_ssh_config ] + instances.map do |instance|
|
13
|
+
instance[:host] = format_for(instance[:provider], instance[:profile]) % instance
|
14
|
+
key_path = Lakitu::FileOperator.key_path instance[:key]
|
15
|
+
instance[:keyfile] = key_path if key_path
|
16
|
+
ERB.new(CLAUSE_TEMPLATE).result(OpenStruct.new(instance).instance_eval { binding })
|
17
|
+
end).join("\n")
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.instances
|
21
|
+
result = Lakitu::Provider.providers.map do |provider_class|
|
22
|
+
Lakitu.logger.debug "Getting instances for #{provider_class.name}"
|
23
|
+
get_instances(provider_class.new)
|
24
|
+
end.flatten
|
25
|
+
Lakitu.logger.info "Found #{result.length} instances"
|
26
|
+
result
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def self.get_instances provider
|
32
|
+
provider.profiles.map do |profile|
|
33
|
+
Lakitu.logger.debug "Profile: #{profile}"
|
34
|
+
provider.regions.map do |region|
|
35
|
+
Lakitu.logger.debug " Region: #{region}"
|
36
|
+
result = provider.instances(profile, region)
|
37
|
+
Lakitu.logger.debug " Found #{(result.length rescue 0)} instances"
|
38
|
+
result
|
39
|
+
end
|
40
|
+
end.flatten
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.format_for provider, profile
|
44
|
+
(options[:providers][provider.to_sym][profile.to_sym][:format] rescue nil) || Lakitu::DEFAULT_FORMAT
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.options
|
48
|
+
Lakitu::Options::options
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
module Lakitu::Options
|
3
|
+
DEFAULTS = {
|
4
|
+
refresh_interval_minutes: 10
|
5
|
+
}
|
6
|
+
|
7
|
+
PROFILE_DEFAULTS = {
|
8
|
+
ignore: false,
|
9
|
+
format: "%{profile}-%{name}-%{id}"
|
10
|
+
}
|
11
|
+
|
12
|
+
@@options = nil
|
13
|
+
|
14
|
+
def self.options
|
15
|
+
unless @@options
|
16
|
+
@@options = OpenStruct.new(DEFAULTS.merge config_options)
|
17
|
+
end
|
18
|
+
@@options
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.options= arg
|
22
|
+
@@options = arg ? OpenStruct.new(arg) : arg
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.merge arg
|
26
|
+
@@options = OpenStruct.new(options.to_h.merge arg)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.config_options
|
30
|
+
return { } unless File.exist?(Lakitu::OPTIONS_FILE_PATH)
|
31
|
+
deep_symbolize_keys(::YAML::load(File.read(Lakitu::OPTIONS_FILE_PATH)) || {})
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.default_config
|
35
|
+
create_provider_defaults
|
36
|
+
YAML.dump(deep_stringify_keys(options.to_h))
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def self.deep_stringify_keys object
|
41
|
+
case object
|
42
|
+
when Hash
|
43
|
+
object.each_with_object({}) do |(key, value), result|
|
44
|
+
result[key.to_s] = deep_stringify_keys(value)
|
45
|
+
end
|
46
|
+
when Array
|
47
|
+
object.map {|e| deep_stringify_keys(e) }
|
48
|
+
else
|
49
|
+
object
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.deep_symbolize_keys object
|
54
|
+
case object
|
55
|
+
when Hash
|
56
|
+
object.each_with_object({}) do |(key, value), result|
|
57
|
+
result[key.to_sym] = deep_symbolize_keys(value)
|
58
|
+
end
|
59
|
+
when Array
|
60
|
+
object.map {|e| deep_symbolize_keys(e) }
|
61
|
+
else
|
62
|
+
object
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.create_provider_defaults
|
67
|
+
options[:providers] ||= {}
|
68
|
+
Lakitu::Provider.providers.each do |provider_class|
|
69
|
+
provider_key = provider_class.name.downcase.split('::').last.to_sym
|
70
|
+
next if options[:providers][provider_key]
|
71
|
+
result = provider_class.new.profiles.inject({}) do |memo, profile|
|
72
|
+
memo[profile.to_sym] = PROFILE_DEFAULTS.dup
|
73
|
+
memo
|
74
|
+
end
|
75
|
+
options[:providers][provider_key] = result
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'aws-sdk-resources'
|
2
|
+
require 'iniparse'
|
3
|
+
require 'lakitu/provider'
|
4
|
+
|
5
|
+
class Lakitu::Provider::Aws < Lakitu::Provider
|
6
|
+
def profiles
|
7
|
+
IniParse.parse(File.read(File.expand_path('~/.aws/credentials'))).to_hash.keys.reject() do |x| x == '__anonymous__' end
|
8
|
+
end
|
9
|
+
|
10
|
+
def instances profile, region
|
11
|
+
result = ec2(profile, region)
|
12
|
+
.instances
|
13
|
+
.to_a
|
14
|
+
.select { |x| x.state.name == "running" }
|
15
|
+
.map { |x| to_hash x }
|
16
|
+
|
17
|
+
result.each do |x|
|
18
|
+
x[:profile] = profile
|
19
|
+
x[:region] = region
|
20
|
+
x[:provider] = 'aws'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def regions
|
25
|
+
[ 'us-east-1', 'us-west-1', 'us-west-2' ]
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def ec2 profile, region
|
31
|
+
::Aws::EC2::Resource.new(region: region, profile: profile)
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_hash instance
|
35
|
+
{
|
36
|
+
id: instance.id,
|
37
|
+
name: (instance.tags.select do |x| x.key == 'Name' end.first.value rescue 'blank'),
|
38
|
+
key: instance.key_name,
|
39
|
+
private_ip: instance.private_ip_address,
|
40
|
+
public_ip: instance.public_ip_address
|
41
|
+
}
|
42
|
+
end
|
43
|
+
end
|
data/lib/lakitu/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
|
2
|
-
VERSION = "1.0.
|
1
|
+
class LakituVersion
|
2
|
+
VERSION = "1.0.3"
|
3
3
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
describe Lakitu::Configurer do
|
2
|
+
subject { described_class }
|
3
|
+
before :each do
|
4
|
+
mock_config
|
5
|
+
allow(File).to receive(:write).and_return(true)
|
6
|
+
end
|
7
|
+
|
8
|
+
it "creates config file when it doesn't exist" do
|
9
|
+
mock_no_options
|
10
|
+
expect(File).to receive(:write).with(Lakitu::OPTIONS_FILE_PATH, Lakitu::Options.default_config).and_return(true)
|
11
|
+
expect(File).to receive(:read).with(Lakitu::OPTIONS_FILE_PATH).and_return(Lakitu::Options.default_config)
|
12
|
+
expect(subject.find_or_create_config).to eq Lakitu::Options.default_config
|
13
|
+
end
|
14
|
+
|
15
|
+
it "uses existing config file when it exists" do
|
16
|
+
mock_options
|
17
|
+
expect(subject.find_or_create_config).to eq OPTIONS_CONTENT
|
18
|
+
end
|
19
|
+
|
20
|
+
it "opens config file in default editor" do
|
21
|
+
expect(subject).to receive(:system).with(Lakitu::EDIT_FILE_COMMAND)
|
22
|
+
subject.edit
|
23
|
+
end
|
24
|
+
|
25
|
+
it "opens local config file in default editor" do
|
26
|
+
expect(subject).to receive(:system).with(Lakitu::EDIT_LOCAL_CONFIG_COMMAND)
|
27
|
+
subject.edit_local
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
describe Lakitu::FileOperator do
|
2
|
+
subject { described_class }
|
3
|
+
before :each do
|
4
|
+
allow(File).to receive(:write).and_return(true)
|
5
|
+
end
|
6
|
+
|
7
|
+
it "moves unmanaged ssh configs to local.sshconfig" do
|
8
|
+
mock_no_ssh_keys
|
9
|
+
mock_unmanaged_sshconfig
|
10
|
+
mock_no_local_sshconfig
|
11
|
+
expect(FileUtils).to receive(:mv).with(Lakitu::SSHCONFIG_PATH, Lakitu::LOCAL_SSHCONFIG_PATH)
|
12
|
+
subject.backup_ssh_config!
|
13
|
+
end
|
14
|
+
|
15
|
+
context "when config is managed" do
|
16
|
+
before :each do mock_managed_sshconfig end
|
17
|
+
|
18
|
+
context "and ssh config is fresh" do
|
19
|
+
before :each do mock_fresh_sshconfig end
|
20
|
+
it "will not overwrite the config" do expect(subject.should_overwrite).to be false end
|
21
|
+
|
22
|
+
context "and force is true" do
|
23
|
+
before :each do subject.send(:options)[:force] = true end
|
24
|
+
it "will overwrite the config" do expect(subject.should_overwrite).to be true end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "and ssh config is stale" do
|
29
|
+
before :each do mock_stale_sshconfig end
|
30
|
+
it "will overwrite the config" do expect(subject.should_overwrite).to be true end
|
31
|
+
end
|
32
|
+
|
33
|
+
it "does not move ssh configs to local.sshconfig" do
|
34
|
+
expect(FileUtils).not_to receive(:mv).with(Lakitu::SSHCONFIG_PATH, Lakitu::LOCAL_SSHCONFIG_PATH)
|
35
|
+
subject.backup_ssh_config!
|
36
|
+
end
|
37
|
+
|
38
|
+
it "writes the result to ~/.ssh/config" do
|
39
|
+
expect(FileUtils).not_to receive(:mv).with(Lakitu::SSHCONFIG_PATH, Lakitu::LOCAL_SSHCONFIG_PATH)
|
40
|
+
expect(File).to receive(:write).with(Lakitu::SSHCONFIG_PATH, "test")
|
41
|
+
subject.write_ssh_config! "test"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "when config is unmanaged" do
|
46
|
+
before :each do mock_unmanaged_sshconfig end
|
47
|
+
it "will overwrite the config" do expect(subject.should_overwrite).to be true end
|
48
|
+
|
49
|
+
it "exits when local.sshconfig exists" do
|
50
|
+
mock_no_ssh_keys
|
51
|
+
mock_local_sshconfig_exists
|
52
|
+
expect(FileUtils).not_to receive(:mv).with(Lakitu::SSHCONFIG_PATH, Lakitu::LOCAL_SSHCONFIG_PATH)
|
53
|
+
expect(subject).to receive(:exit).with(1)
|
54
|
+
subject.backup_ssh_config!
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
describe Lakitu::Generator do
|
2
|
+
subject { described_class }
|
3
|
+
before :each do
|
4
|
+
stub_aws
|
5
|
+
mock_config
|
6
|
+
allow(File).to receive(:write).and_return(true)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "invokes all providers, for all regions, over all profiles" do
|
10
|
+
expect(Lakitu::Provider.providers.length).to be > 0
|
11
|
+
Lakitu::Provider.providers.each do |provider_class|
|
12
|
+
provider = provider_class.new
|
13
|
+
provider.profiles.each do |profile|
|
14
|
+
provider.regions.each do |region|
|
15
|
+
expect_any_instance_of(provider_class).to receive(:instances).with(profile, region).exactly(:once)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
subject.instances
|
21
|
+
end
|
22
|
+
|
23
|
+
it "merges instance lists from the providers" do
|
24
|
+
WRANGLED_INSTANCE_DATA_COMPLETE.each do |x|
|
25
|
+
expect(subject.instances).to include x
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it "generates ssh config content from templates" do
|
30
|
+
mock_no_local_sshconfig
|
31
|
+
mock_no_ssh_keys
|
32
|
+
mock_options
|
33
|
+
Lakitu::Options.options = nil
|
34
|
+
result = subject.generate
|
35
|
+
expect(result).to include SSH_CONFIG_RESULT
|
36
|
+
expect(result).to include "Host formattest"
|
37
|
+
end
|
38
|
+
|
39
|
+
it "reads ~/.ssh/*.sshconfig files" do
|
40
|
+
mock_no_ssh_keys
|
41
|
+
mock_local_sshconfig
|
42
|
+
expect(subject.generate).to include LOCAL_SSHCONFIG
|
43
|
+
end
|
44
|
+
|
45
|
+
it "looks up keys in ~/.ssh/" do
|
46
|
+
mock_ssh_keys
|
47
|
+
mock_no_local_sshconfig
|
48
|
+
expect(subject.generate).to include "IdentityFile #{File.expand_path("~/.ssh/testkey.pem")}"
|
49
|
+
end
|
50
|
+
end
|
data/spec/lakitu_spec.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
describe Lakitu do
|
2
|
+
subject { described_class.new }
|
3
|
+
it "delegates generate!" do
|
4
|
+
expect(Lakitu::FileOperator).to receive(:backup_ssh_config!).and_return(nil)
|
5
|
+
expect(Lakitu::Generator).to receive(:generate).and_return("test")
|
6
|
+
expect(Lakitu::FileOperator).to receive(:write_ssh_config!).and_return(nil)
|
7
|
+
expect(Lakitu::FileOperator).to receive(:should_overwrite).and_return(true)
|
8
|
+
subject.generate
|
9
|
+
end
|
10
|
+
|
11
|
+
it "delegates configure" do
|
12
|
+
expect(Lakitu::Configurer).to receive(:find_or_create_config).and_return(true)
|
13
|
+
expect(Lakitu::Configurer).to receive(:edit).and_return("test")
|
14
|
+
subject.configure
|
15
|
+
end
|
16
|
+
|
17
|
+
it "delegates edit" do
|
18
|
+
expect(Lakitu::Configurer).to receive(:edit_local).and_return(true)
|
19
|
+
expect(subject).to receive(:invoke).with(:generate).and_return(true)
|
20
|
+
subject.edit
|
21
|
+
end
|
22
|
+
|
23
|
+
it "creates a logger" do
|
24
|
+
Lakitu.logger = nil
|
25
|
+
Lakitu::Options.options.verbose = false
|
26
|
+
expect(Lakitu.logger).to be_a(Logger)
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
describe Lakitu::Options do
|
2
|
+
subject { described_class }
|
3
|
+
before :each do
|
4
|
+
Lakitu::Options.options= nil
|
5
|
+
allow(File).to receive(:write).and_return(true)
|
6
|
+
end
|
7
|
+
|
8
|
+
it "provides default values for options" do
|
9
|
+
mock_no_options
|
10
|
+
expect(described_class.options.refresh_interval_minutes).to eq 10
|
11
|
+
end
|
12
|
+
|
13
|
+
it "overwrites defaults with config values" do
|
14
|
+
mock_options
|
15
|
+
expect(described_class.options.refresh_interval_minutes).to eq 5
|
16
|
+
end
|
17
|
+
|
18
|
+
it "overwrites config values with command line arguments" do
|
19
|
+
mock_options
|
20
|
+
Lakitu::Options.merge refresh_interval_minutes: 1
|
21
|
+
expect(described_class.options.refresh_interval_minutes).to eq 1
|
22
|
+
end
|
23
|
+
|
24
|
+
it "includes providers and profiles in default config" do
|
25
|
+
mock_no_options
|
26
|
+
mock_config
|
27
|
+
stub_aws
|
28
|
+
result = YAML.load(subject.default_config)
|
29
|
+
expect(result).to be_a Hash
|
30
|
+
expect(result['refresh_interval_minutes']).to eql 10
|
31
|
+
expect(result['providers']).to be_a Hash
|
32
|
+
expect(result['providers']['aws']['client1']['ignore']).to be false
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
RSpec.describe Lakitu::Provider::Aws do
|
2
|
+
subject { Lakitu::Provider::Aws.new }
|
3
|
+
context "with a valid credentials config" do
|
4
|
+
before :each do
|
5
|
+
mock_config
|
6
|
+
end
|
7
|
+
|
8
|
+
it "gets a list of profiles" do
|
9
|
+
expect(subject.profiles).to eq ['default', 'client1']
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context "with a given list of instances" do
|
14
|
+
before do
|
15
|
+
stub_aws
|
16
|
+
end
|
17
|
+
|
18
|
+
it "gets a list of instances for a given profile" do
|
19
|
+
expect(subject.instances('default', 'us-east-1')).to eq(WRANGLED_INSTANCE_DATA_COMPLETE)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'simplecov'
|
3
|
+
SimpleCov.start
|
4
|
+
|
5
|
+
require './lib/lakitu.rb'
|
6
|
+
|
7
|
+
# silence the logger
|
8
|
+
RSpec.configure do |config|
|
9
|
+
config.before do
|
10
|
+
Lakitu.logger = Logger.new(false)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def stub_aws
|
15
|
+
Aws.config[:ec2] = {
|
16
|
+
stub_responses: {
|
17
|
+
describe_instances: {
|
18
|
+
reservations: [{
|
19
|
+
instances: [
|
20
|
+
{ instance_id: 'i-abcd1234', public_ip_address: '1.2.3.3', key_name: 'testkey', state: { name: 'stopped' }, tags: [ { key: 'Name', value: 'deadman' } ] },
|
21
|
+
{ instance_id: 'i-deadbeef', public_ip_address: '1.2.3.4', key_name: 'testkey', state: { name: 'running' }, tags: [ { key: 'Name', value: 'test' } ] },
|
22
|
+
{ instance_id: 'i-beeff00d', private_ip_address: '1.2.3.5', key_name: 'testkey2', state: { name: 'running' }, tags: [ { key: 'Name', value: 'test2' } ] }
|
23
|
+
]}
|
24
|
+
]
|
25
|
+
}
|
26
|
+
}
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
WRANGLED_INSTANCE_DATA_COMPLETE=[
|
31
|
+
{
|
32
|
+
id: 'i-deadbeef',
|
33
|
+
name: 'test',
|
34
|
+
key: 'testkey',
|
35
|
+
profile: 'default',
|
36
|
+
region: 'us-east-1',
|
37
|
+
provider: 'aws',
|
38
|
+
public_ip: '1.2.3.4',
|
39
|
+
private_ip: nil
|
40
|
+
},
|
41
|
+
{
|
42
|
+
id: 'i-beeff00d',
|
43
|
+
name: 'test2',
|
44
|
+
key: 'testkey2',
|
45
|
+
profile: 'default',
|
46
|
+
region: 'us-east-1',
|
47
|
+
provider: 'aws',
|
48
|
+
public_ip: nil,
|
49
|
+
private_ip: '1.2.3.5'
|
50
|
+
}
|
51
|
+
]
|
52
|
+
|
53
|
+
AWS_CREDENTIALS_CONTENT=<<EOF
|
54
|
+
[default]
|
55
|
+
aws_access_key_id = ACCESSKEY
|
56
|
+
aws_secret_access_key = SECRETKEY
|
57
|
+
region = us-west-2
|
58
|
+
|
59
|
+
[client1]
|
60
|
+
aws_access_key_id = ACCESSKEY
|
61
|
+
aws_secret_access_key = SECRETKEY
|
62
|
+
region = us-east-1
|
63
|
+
EOF
|
64
|
+
|
65
|
+
def mock_config
|
66
|
+
allow(File).to receive(:read).and_call_original
|
67
|
+
allow(File).to receive(:read).with(File.expand_path('~/.aws/credentials')).and_return(AWS_CREDENTIALS_CONTENT).at_least(:once)
|
68
|
+
end
|
69
|
+
|
70
|
+
LOCAL_SSHCONFIG=<<EOF
|
71
|
+
User myusername
|
72
|
+
EOF
|
73
|
+
|
74
|
+
def mock_local_sshconfig_exists
|
75
|
+
expect(File).to receive(:exist?).with(File.expand_path('~/.ssh/local.sshconfig')).and_return(true).at_least(:once)
|
76
|
+
end
|
77
|
+
|
78
|
+
def mock_local_sshconfig
|
79
|
+
expect(File).to receive(:exist?).with(File.expand_path('~/.ssh/local.sshconfig')).and_return(true).at_least(:once)
|
80
|
+
expect(File).to receive(:read).with(File.expand_path('~/.ssh/local.sshconfig')).and_return(LOCAL_SSHCONFIG).at_least(:once)
|
81
|
+
end
|
82
|
+
|
83
|
+
def mock_no_local_sshconfig
|
84
|
+
expect(File).to receive(:exist?).with(File.expand_path('~/.ssh/local.sshconfig')).and_return(false).at_least(:once)
|
85
|
+
end
|
86
|
+
|
87
|
+
SSH_CONFIG_RESULT=<<EOF
|
88
|
+
Host default-test-i-deadbeef
|
89
|
+
HostName 1.2.3.4
|
90
|
+
|
91
|
+
Host default-test2-i-beeff00d
|
92
|
+
HostName 1.2.3.5
|
93
|
+
EOF
|
94
|
+
|
95
|
+
UNMANAGED_SSH_CONFIG=<<EOF
|
96
|
+
Host default-test-i-deadbeef
|
97
|
+
HostName 1.2.3.4
|
98
|
+
EOF
|
99
|
+
|
100
|
+
def mock_unmanaged_sshconfig
|
101
|
+
expect(File).to receive(:exist?).with(File.expand_path('~/.ssh/config')).and_return(true).at_least(:once)
|
102
|
+
expect(File).to receive(:read).with(File.expand_path('~/.ssh/config')).and_return(UNMANAGED_SSH_CONFIG).at_least(:once)
|
103
|
+
end
|
104
|
+
|
105
|
+
MANAGED_SSH_CONFIG=<<EOF
|
106
|
+
# Managed by Lakitu
|
107
|
+
Host default-test-i-deadbeef
|
108
|
+
HostName 1.2.3.4
|
109
|
+
EOF
|
110
|
+
|
111
|
+
def mock_managed_sshconfig
|
112
|
+
expect(File).to receive(:exist?).with(File.expand_path('~/.ssh/config')).and_return(true).at_least(:once)
|
113
|
+
expect(File).to receive(:read).with(File.expand_path('~/.ssh/config')).and_return(MANAGED_SSH_CONFIG).at_least(:once)
|
114
|
+
end
|
115
|
+
|
116
|
+
def mock_fresh_sshconfig
|
117
|
+
expect(File).to receive(:mtime).with(Lakitu::SSHCONFIG_PATH).and_return(Time.now).at_least(:once)
|
118
|
+
end
|
119
|
+
|
120
|
+
def mock_stale_sshconfig
|
121
|
+
expect(File).to receive(:mtime).with(Lakitu::SSHCONFIG_PATH).and_return(Time.now - 60*60*24).at_least(:once)
|
122
|
+
end
|
123
|
+
|
124
|
+
def mock_ssh_keys
|
125
|
+
expect(File).to receive(:exist?).with(File.expand_path('~/.ssh/testkey.pem')).and_return(true).at_least(:once)
|
126
|
+
expect(File).to receive(:exist?).with(File.expand_path('~/.ssh/testkey2.pem')).and_return(true).at_least(:once)
|
127
|
+
end
|
128
|
+
|
129
|
+
def mock_no_ssh_keys
|
130
|
+
allow(File).to receive(:exist?).with(File.expand_path('~/.ssh/testkey.pem')).and_return(false).at_least(:once)
|
131
|
+
allow(File).to receive(:exist?).with(File.expand_path('~/.ssh/testkey2.pem')).and_return(false).at_least(:once)
|
132
|
+
end
|
133
|
+
|
134
|
+
OPTIONS_CONTENT=<<EOF
|
135
|
+
---
|
136
|
+
verbose: true
|
137
|
+
force: true
|
138
|
+
refresh_interval_minutes: 5
|
139
|
+
providers:
|
140
|
+
aws:
|
141
|
+
client1:
|
142
|
+
format: "formattest"
|
143
|
+
EOF
|
144
|
+
def mock_options
|
145
|
+
allow(File).to receive(:exist?).and_call_original
|
146
|
+
expect(File).to receive(:exist?).with(Lakitu::OPTIONS_FILE_PATH).and_return(true).at_least(:once)
|
147
|
+
expect(File).to receive(:read).with(Lakitu::OPTIONS_FILE_PATH).and_return(OPTIONS_CONTENT).at_least(:once)
|
148
|
+
end
|
149
|
+
|
150
|
+
def mock_no_options
|
151
|
+
expect(File).to receive(:exist?).with(Lakitu::OPTIONS_FILE_PATH).and_return(false).at_least(:once)
|
152
|
+
end
|
metadata
CHANGED
@@ -1,58 +1,75 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lakitu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bryan Conrad
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-04-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ~>
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1.7'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ~>
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.7'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ~>
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '10.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ~>
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '10.0'
|
41
41
|
description: ''
|
42
42
|
email:
|
43
43
|
- bkconrad@gmail.com
|
44
|
-
executables:
|
44
|
+
executables:
|
45
|
+
- lakitu
|
45
46
|
extensions: []
|
46
47
|
extra_rdoc_files: []
|
47
48
|
files:
|
48
|
-
-
|
49
|
+
- .gitignore
|
50
|
+
- .rspec
|
51
|
+
- .ruby-version
|
49
52
|
- Gemfile
|
50
53
|
- LICENSE.txt
|
51
54
|
- README.md
|
52
55
|
- Rakefile
|
56
|
+
- bin/lakitu
|
53
57
|
- lakitu.gemspec
|
54
58
|
- lib/lakitu.rb
|
59
|
+
- lib/lakitu/configurer.rb
|
60
|
+
- lib/lakitu/file_operator.rb
|
61
|
+
- lib/lakitu/generator.rb
|
62
|
+
- lib/lakitu/options.rb
|
63
|
+
- lib/lakitu/provider.rb
|
64
|
+
- lib/lakitu/providers/aws.rb
|
55
65
|
- lib/lakitu/version.rb
|
66
|
+
- spec/configurer_spec.rb
|
67
|
+
- spec/file_operator_spec.rb
|
68
|
+
- spec/generator_spec.rb
|
69
|
+
- spec/lakitu_spec.rb
|
70
|
+
- spec/options_spec.rb
|
71
|
+
- spec/providers/aws_spec.rb
|
72
|
+
- spec/spec_helper.rb
|
56
73
|
homepage: ''
|
57
74
|
licenses:
|
58
75
|
- MIT
|
@@ -63,19 +80,25 @@ require_paths:
|
|
63
80
|
- lib
|
64
81
|
required_ruby_version: !ruby/object:Gem::Requirement
|
65
82
|
requirements:
|
66
|
-
- -
|
83
|
+
- - '>='
|
67
84
|
- !ruby/object:Gem::Version
|
68
85
|
version: '0'
|
69
86
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
87
|
requirements:
|
71
|
-
- -
|
88
|
+
- - '>='
|
72
89
|
- !ruby/object:Gem::Version
|
73
90
|
version: '0'
|
74
91
|
requirements: []
|
75
92
|
rubyforge_project:
|
76
|
-
rubygems_version: 2.
|
93
|
+
rubygems_version: 2.4.3
|
77
94
|
signing_key:
|
78
95
|
specification_version: 4
|
79
96
|
summary: Ride the clouds
|
80
|
-
test_files:
|
81
|
-
|
97
|
+
test_files:
|
98
|
+
- spec/configurer_spec.rb
|
99
|
+
- spec/file_operator_spec.rb
|
100
|
+
- spec/generator_spec.rb
|
101
|
+
- spec/lakitu_spec.rb
|
102
|
+
- spec/options_spec.rb
|
103
|
+
- spec/providers/aws_spec.rb
|
104
|
+
- spec/spec_helper.rb
|