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