lakitu 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 155a2c909441c1801e8d3e8d1812662e8d01e387
4
- data.tar.gz: efec47e44e4caf032c1dbade96842605753aba94
3
+ metadata.gz: a39c34d03f0b881eb80772851d0d81bd5b290525
4
+ data.tar.gz: 22a3bb290adbaec68dfeb32b8077a51b8c92be62
5
5
  SHA512:
6
- metadata.gz: 0c56082dde0a6c3489871c7b85d315a7d27efbb2ce8dd6f48ccff127a2b796ce6671c23084d6567591b4af56d3ba3f91d08b93308f9fb46bd33805b5e360a83b
7
- data.tar.gz: 1e6e3fbec46fc6d332a35b0d42b39e10a7a0046d42a017ec629adf853c1cb2c5ecb3f235c62722ba17f3dcab426e63fb634eabb48c20693b7eaaa6abd7b94266
6
+ metadata.gz: 3dd6856ef1ca80e7f1f553aa055a742d157597e698a09c01e3e93f199403b8e5c333466e487bbc6e103be4afc3277cc45d630257ef733765560744a4154a61e3
7
+ data.tar.gz: 608987d97b1e3539c6d1e613b649223cb263ba4eb98ca18b1f5fe1cdd8321f47ba43dafa845bef6c755efff930e0cde6cd8eda15ad3cb136f363af9bb864fd21
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ -r ./spec/spec_helper.rb
2
+ -I ./lib/lakitu
@@ -0,0 +1 @@
1
+ ruby-2.0.0-p598
data/Gemfile CHANGED
@@ -2,3 +2,12 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in lakitu.gemspec
4
4
  gemspec
5
+
6
+ gem "thor", "~>0.19"
7
+ gem 'aws-sdk', '~> 2'
8
+ gem "iniparse"
9
+
10
+ group :development do
11
+ gem "rspec", "~>3.2"
12
+ gem "simplecov"
13
+ end
data/README.md CHANGED
@@ -1,31 +1,51 @@
1
1
  # Lakitu
2
+ ![CircleCI build status](https://circleci.com/gh/bkconrad/lakitu.png?style=shield&circle-token=a622a5a71cc7a5ad0662d05e164a67f8ddbdaff1)
2
3
 
3
- TODO: Write a gem description
4
+ Generate an SSH config from AWS (or potentially other cloud provider) APIs.
4
5
 
5
- ## Installation
6
+ ## Prework
7
+ To use Lakitu effectively, you will need the following:
6
8
 
7
- Add this line to your application's Gemfile:
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
- ```ruby
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
- $ bundle
15
+ ## Usage
16
+ ```
17
+ $ gem install lakitu
18
+ $ lakitu generate
19
+ $ lakitu configure
20
+ $ lakitu edit
21
+ ```
16
22
 
17
- Or install it yourself as:
23
+ ## Periodic Refresh
24
+ Simply add the following line to your `.bashrc`
25
+ ```
26
+ lakitu generate &>~/.lakitu.log &
27
+ ```
18
28
 
19
- $ gem install lakitu
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
- ## Usage
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
- TODO: Write usage instructions here
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
- ## Contributing
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
- 1. Fork it ( https://github.com/[my-github-username]/lakitu/fork )
28
- 2. Create your feature branch (`git checkout -b my-new-feature`)
29
- 3. Commit your changes (`git commit -am 'Add some feature'`)
30
- 4. Push to the branch (`git push origin my-new-feature`)
31
- 5. Create a new Pull Request
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/)
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ require "rubygems"
3
+ require "thor"
4
+ require "#{File.dirname(__FILE__)}/../lib/lakitu.rb"
5
+
6
+ Lakitu.start
@@ -5,7 +5,7 @@ require 'lakitu/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "lakitu"
8
- spec.version = Lakitu::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}
@@ -1,5 +1,61 @@
1
- require "lakitu/version"
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
- module Lakitu
4
- # Your code goes here...
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,11 @@
1
+ class Lakitu::Provider
2
+ @@providers = []
3
+
4
+ def self.providers
5
+ @@providers
6
+ end
7
+
8
+ def self.inherited provider
9
+ @@providers.push provider
10
+ end
11
+ 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
@@ -1,3 +1,3 @@
1
- module Lakitu
2
- VERSION = "1.0.2"
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
@@ -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
@@ -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.2
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-01-15 00:00:00.000000000 Z
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
- - ".gitignore"
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.2.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
- has_rdoc:
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