taketo 0.0.1 → 0.0.2

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.
Files changed (40) hide show
  1. data/Gemfile +4 -4
  2. data/Gemfile.lock +11 -11
  3. data/LICENSE.txt +1 -1
  4. data/README.md +12 -4
  5. data/Rakefile +3 -6
  6. data/VERSION +1 -1
  7. data/bin/taketo +48 -27
  8. data/features/commands.feature +45 -0
  9. data/features/config_validation.feature +18 -0
  10. data/features/connect_to_server.feature +43 -1
  11. data/lib/taketo/commands/ssh_command.rb +6 -14
  12. data/lib/taketo/config_validator.rb +43 -0
  13. data/lib/taketo/constructs/command.rb +32 -0
  14. data/lib/taketo/constructs/config.rb +4 -2
  15. data/lib/taketo/constructs/environment.rb +5 -3
  16. data/lib/taketo/constructs/project.rb +4 -2
  17. data/lib/taketo/constructs/server.rb +28 -1
  18. data/lib/taketo/constructs_factory.rb +9 -0
  19. data/lib/taketo/dsl.rb +24 -24
  20. data/lib/taketo/support/key_error.rb +4 -0
  21. data/lib/taketo/support/named_nodes_collection.rb +26 -0
  22. data/lib/taketo/support.rb +2 -0
  23. data/lib/taketo/taketo_argv_parser.rb +48 -0
  24. data/lib/taketo.rb +2 -1
  25. data/spec/lib/{commands → taketo/commands}/ssh_command_spec.rb +8 -14
  26. data/spec/lib/taketo/config_validator_spec.rb +41 -0
  27. data/spec/lib/taketo/constructs/command_spec.rb +22 -0
  28. data/spec/lib/{constructs → taketo/constructs}/config_spec.rb +2 -2
  29. data/spec/lib/{constructs → taketo/constructs}/environment_spec.rb +2 -2
  30. data/spec/lib/{constructs → taketo/constructs}/project_spec.rb +2 -2
  31. data/spec/lib/taketo/constructs/server_spec.rb +84 -0
  32. data/spec/lib/{constructs_factory_spec.rb → taketo/constructs_factory_spec.rb} +14 -1
  33. data/spec/lib/{dsl_spec.rb → taketo/dsl_spec.rb} +45 -7
  34. data/spec/lib/{support → taketo/support}/eval_delegator_spec.rb +1 -1
  35. data/spec/lib/taketo/support/named_nodes_collection_spec.rb +29 -0
  36. data/spec/lib/taketo/taketo_argv_parser_spec.rb +92 -0
  37. data/spec/spec_helper.rb +8 -4
  38. data/spec/support/helpers/dsl_spec_helper.rb +11 -2
  39. metadata +25 -14
  40. data/spec/lib/constructs/server_spec.rb +0 -41
data/Gemfile CHANGED
@@ -1,9 +1,9 @@
1
1
  source :rubygems
2
2
 
3
3
  group :development do
4
- gem 'rspec'
5
- gem 'rake'
6
- gem 'aruba'
7
- gem 'simplecov', :require => false
4
+ gem "rspec", "~> 2.10"
5
+ gem "rake", "~> 0.9"
6
+ gem "aruba", "~> 0.4"
7
+ gem 'simplecov', '~> 0.6', :require => false
8
8
  end
9
9
 
data/Gemfile.lock CHANGED
@@ -21,14 +21,14 @@ GEM
21
21
  json (1.7.3)
22
22
  multi_json (1.3.6)
23
23
  rake (0.9.2.2)
24
- rspec (2.10.0)
25
- rspec-core (~> 2.10.0)
26
- rspec-expectations (~> 2.10.0)
27
- rspec-mocks (~> 2.10.0)
28
- rspec-core (2.10.1)
29
- rspec-expectations (2.10.0)
24
+ rspec (2.11.0)
25
+ rspec-core (~> 2.11.0)
26
+ rspec-expectations (~> 2.11.0)
27
+ rspec-mocks (~> 2.11.0)
28
+ rspec-core (2.11.1)
29
+ rspec-expectations (2.11.1)
30
30
  diff-lcs (~> 1.1.3)
31
- rspec-mocks (2.10.1)
31
+ rspec-mocks (2.11.1)
32
32
  simplecov (0.6.4)
33
33
  multi_json (~> 1.0)
34
34
  simplecov-html (~> 0.5.3)
@@ -38,7 +38,7 @@ PLATFORMS
38
38
  ruby
39
39
 
40
40
  DEPENDENCIES
41
- aruba
42
- rake
43
- rspec
44
- simplecov
41
+ aruba (~> 0.4)
42
+ rake (~> 0.9)
43
+ rspec (~> 2.10)
44
+ simplecov (~> 0.6)
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 Vladimir
1
+ Copyright (c) 2012 Vladimir Yarotsky
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -2,6 +2,7 @@ Take Me To
2
2
  ==========
3
3
 
4
4
  [![Build Status](https://secure.travis-ci.org/v-yarotsky/taketo.png)](http://travis-ci.org/v-yarotsky/taketo)
5
+ [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/v-yarotsky/taketo)
5
6
 
6
7
  A tiny helper utility to make access to servers eaiser for different projects and environments.
7
8
 
@@ -13,19 +14,22 @@ puts a config into ```~/.taketo.rc.rb```:
13
14
  ```ruby
14
15
  project :my_project do
15
16
  environment :staging do
16
- server :s1 do
17
+ server :server do
17
18
  host "1.2.3.4"
18
19
  port 10001
19
20
  user "app"
20
21
  localtion "/var/app"
22
+ env :TERM => "xterm-256color"
23
+ command :console do
24
+ execute "rails c"
25
+ end
21
26
  end
22
27
  end
23
28
  end
24
29
  ```
25
30
 
26
- Then execute:
27
-
28
- ```taketo my_project staging s1 [command to execute, bash by default]```
31
+ Then execute ```taketo my_project staging server -c console``` to execute the "rails c" with corresponding environment variables set on desired server
32
+ or just ```taketo my_project staging server``` to open bash
29
33
 
30
34
  To-Do:
31
35
  ------
@@ -36,6 +40,10 @@ To-Do:
36
40
  The Changelog:
37
41
  --------------
38
42
 
43
+ ### v0.0.2 (21.07.2012) ###
44
+ * Add ability to define environment variables
45
+ * Add support for server commands
46
+
39
47
  ### v0.0.1 (13.06.2012) ###
40
48
  * Initial release
41
49
  * Support for simplest configs
data/Rakefile CHANGED
@@ -11,19 +11,16 @@ rescue Bundler::BundlerError => e
11
11
  end
12
12
 
13
13
  require 'rake'
14
- require 'rake/testtask'
14
+ require 'rspec/core/rake_task'
15
15
  require 'cucumber'
16
16
  require 'cucumber/rake/task'
17
17
 
18
- Rake::TestTask.new do |t|
19
- t.test_files = Dir.glob('spec/**/*_spec.rb')
20
- t.verbose = true
21
- end
18
+ RSpec::Core::RakeTask.new
22
19
 
23
20
  Cucumber::Rake::Task.new(:features) do |t|
24
21
  t.cucumber_opts = "features --tags ~@wip --format pretty -x"
25
22
  t.fork = false
26
23
  end
27
24
 
28
- task :default => [:test, :features]
25
+ task :default => [:spec, :features]
29
26
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.0.2
data/bin/taketo CHANGED
@@ -6,10 +6,14 @@ begin
6
6
  require 'taketo'
7
7
  require 'taketo/constructs_factory'
8
8
  require 'taketo/commands'
9
+ require 'taketo/taketo_argv_parser'
9
10
  require 'optparse'
10
11
  rescue LoadError => e #development
11
- $: << File.expand_path('../../lib', __FILE__)
12
- require 'bundler/setup'
12
+ raise if $loaded
13
+ $:.unshift File.expand_path('../../lib', __FILE__)
14
+ require 'bundler'
15
+ Bundler.setup(:default)
16
+ $loaded = true
13
17
  retry
14
18
  end
15
19
 
@@ -25,48 +29,65 @@ def parse_options
25
29
 
26
30
  OptionParser.new do |opts|
27
31
  opts.banner = <<-DESC
28
- A tiny helper utility to make access to servers
29
- eaiser for different projects and environments.
32
+ A tiny helper utility to make access to servers
33
+ eaiser for different projects and environments.
30
34
  DESC
31
35
 
32
- opts.on("-c CONFIG", "--config") do |c|
36
+ opts.on("-f CONFIG", "--config") do |c|
33
37
  options[:config] = c
34
38
  end
35
39
 
36
40
  opts.on("--dry-run") do |v|
37
41
  options[:dry_run] = v
38
42
  end
43
+
44
+ opts.on("--debug") do |d|
45
+ options[:debug] = d
46
+ end
47
+
48
+ opts.on("-c COMMAND", "--command") do |c|
49
+ raise OptionParser::MissingArgument if String(c).strip.empty?
50
+ options[:command] = c
51
+ end
39
52
  end.parse!
40
53
 
41
54
  options
42
55
  end
43
56
 
44
- def parse_config(options)
45
- factory = Taketo::ConstructsFactory.new
46
-
47
- Taketo::DSL.new(factory).configure do
48
- config_text = File.read(options[:config])
49
- eval config_text, binding, options[:config], 1
57
+ def parse_config(config)
58
+ DSL.new.configure(config).tap do |config|
59
+ ConfigValidator.new(config).validate!
50
60
  end
51
61
  end
52
-
53
- options = parse_options
54
- config = parse_config(options)
55
-
56
- project, environment, server = ARGV.shift(3).map(&:to_sym)
57
62
 
58
- server = begin
59
- config.projects.fetch(project).environments.fetch(environment).servers.fetch(server)
60
- rescue KeyError => e
61
- raise ArgumentError, e
63
+ def remote_command(server, options)
64
+ command_name = options[:command] or return default_command(options)
65
+ server.command_by_name(command_name.to_sym) { default_command(options) }
62
66
  end
63
67
 
64
- remote_command = ARGV.join(" ")
65
- remote_command = "bash" if remote_command.empty?
66
- command_to_execute = Commands::SSHCommand.new(server).render(remote_command)
67
- if options[:dry_run]
68
- puts command_to_execute
69
- else
70
- system command_to_execute
68
+ def default_command(options)
69
+ cmd = Constructs::Command.new(:default)
70
+ cmd.command = options.fetch(:command) { "bash" }
71
+ cmd
71
72
  end
73
+
74
+ begin
75
+ options = parse_options
76
+ config = parse_config(options[:config])
77
+
78
+ server = TaketoArgvParser.new(config, ARGV).parse
72
79
 
80
+ server_command = remote_command(server, options)
81
+ command_to_execute = Commands::SSHCommand.new(server).render(server_command)
82
+
83
+ if options[:dry_run]
84
+ puts command_to_execute
85
+ else
86
+ system command_to_execute
87
+ end
88
+ rescue SystemExit
89
+ # Do nothing
90
+ rescue Exception => e
91
+ STDERR.puts "An error occurred: #{e.message}"
92
+ raise if options && options[:debug]
93
+ end
@@ -0,0 +1,45 @@
1
+ Feature:
2
+ In order to be able to quickly run commands on my servers
3
+ As a developer
4
+ I want to have ability to create command shortcuts via config
5
+ or specify command directly
6
+
7
+ Scenario: Run explicit command
8
+ When I have the following config in "/tmp/taketo_test_cfg.rb"
9
+ """
10
+ project :slots do
11
+ environment :staging do
12
+ server :s1 do
13
+ host "1.2.3.4"
14
+ location "/var/apps/slots"
15
+ end
16
+ end
17
+ end
18
+ """
19
+ And I successfully run `taketo --config=/tmp/taketo_test_cfg.rb --dry-run --command "TERM=xterm-256color bash" slots staging s1`
20
+ Then the output should contain
21
+ """
22
+ ssh -t 1.2.3.4 "cd /var/apps/slots; RAILS_ENV=staging TERM=xterm-256color bash"
23
+ """
24
+
25
+ Scenario: Run command defined in config
26
+ When I have the following config in "/tmp/taketo_test_cfg.rb"
27
+ """
28
+ project :slots do
29
+ environment :staging do
30
+ server :s1 do
31
+ host "1.2.3.4"
32
+ location "/var/apps/slots"
33
+ command :console do
34
+ execute "rails c"
35
+ end
36
+ end
37
+ end
38
+ end
39
+ """
40
+ And I successfully run `taketo --config=/tmp/taketo_test_cfg.rb --dry-run --command console slots staging s1`
41
+ Then the output should contain
42
+ """
43
+ ssh -t 1.2.3.4 "cd /var/apps/slots; RAILS_ENV=staging rails c"
44
+ """
45
+
@@ -0,0 +1,18 @@
1
+ Feature:
2
+ In order to be able to use the piece of software
3
+ As a user
4
+ I want to be guided if there are any errors in my configuration
5
+
6
+ Scenario Outline: Config validation
7
+ When I have the following config in "/tmp/taketo_test_cfg.rb"
8
+ """
9
+ <config>
10
+ """
11
+ And I run `taketo --config=/tmp/taketo_test_cfg.rb`
12
+ Then the stderr should contain "<error>"
13
+
14
+ Examples:
15
+ | config | error |
16
+ | | There are no projects. Add some to your config (~/.taketo.rc.rb by default) |
17
+ | project(:foo) {} | There is no environments for the following projects: foo |
18
+ | project(:foo) { environment(:bar) {}} | There is no servers for the following environments in project foo: bar |
@@ -21,5 +21,47 @@ Feature:
21
21
  And I successfully run `taketo --config=/tmp/taketo_test_cfg.rb --dry-run slots staging s1`
22
22
  Then the output should contain
23
23
  """
24
- ssh -t "deployer"@"1.2.3.4" "cd '/var/apps/slots'; RAILS_ENV=staging bash"
24
+ ssh -t deployer@1.2.3.4 "cd /var/apps/slots; RAILS_ENV=staging bash"
25
25
  """
26
+
27
+ Scenario: SSH to the only server
28
+ When I have the following config in "/tmp/taketo_test_cfg.rb"
29
+ """
30
+ project :slots do
31
+ environment :staging do
32
+ server :s1 do
33
+ host "1.2.3.4"
34
+ location "/var/apps/slots"
35
+ end
36
+ end
37
+ end
38
+ """
39
+ And I successfully run `taketo --config=/tmp/taketo_test_cfg.rb --dry-run`
40
+ Then the output should contain
41
+ """
42
+ ssh -t 1.2.3.4 "cd /var/apps/slots; RAILS_ENV=staging bash"
43
+ """
44
+
45
+ Scenario: Set environment variables
46
+ When I have the following config in "/tmp/taketo_test_cfg.rb"
47
+ """
48
+ project :slots do
49
+ environment :staging do
50
+ server :s1 do
51
+ host "1.2.3.4"
52
+ location "/var/apps/slots"
53
+ env :FOO => "the value"
54
+ end
55
+ end
56
+ end
57
+ """
58
+ And I successfully run `taketo --config=/tmp/taketo_test_cfg.rb --dry-run`
59
+ Then the output should contain
60
+ """
61
+ RAILS_ENV=staging
62
+ """
63
+ And the output should contain
64
+ """
65
+ FOO=the\ value
66
+ """
67
+
@@ -1,26 +1,26 @@
1
1
  require 'forwardable'
2
+ require 'shellwords'
2
3
 
3
4
  module Taketo
4
5
  module Commands
5
6
  class SSHCommand
6
7
  extend Forwardable
7
-
8
- def_delegators :@server, :host, :port, :username, :default_location
8
+ include Shellwords
9
9
 
10
10
  def initialize(server, options = {})
11
11
  @server = server
12
12
  @environment = @server.environment
13
13
  end
14
14
 
15
- def render(command = "bash")
16
- %Q[ssh -t #{port} #{username}#{host} "#{[location, environment, command].compact.join(" ")}"].gsub(/ +/, " ")
15
+ def render(command)
16
+ %Q[ssh -t #{port} #{username}#{host} "#{command.render(@server)}"].squeeze(" ")
17
17
  end
18
18
 
19
19
  def host
20
20
  unless @server.host
21
21
  raise ArgumentError, "host for server #{@server.name} in #{@environment.name} is not defined!"
22
22
  end
23
- %Q["#{@server.host}"]
23
+ shellescape @server.host
24
24
  end
25
25
 
26
26
  def port
@@ -28,15 +28,7 @@ module Taketo
28
28
  end
29
29
 
30
30
  def username
31
- %Q["#{@server.username}"@] if @server.username
32
- end
33
-
34
- def location
35
- %Q[cd '#{@server.default_location}';] if @server.default_location
36
- end
37
-
38
- def environment
39
- %Q[RAILS_ENV=#{@environment.name}]
31
+ %Q[#{shellescape @server.username}@] if @server.username
40
32
  end
41
33
  end
42
34
  end
@@ -0,0 +1,43 @@
1
+ module Taketo
2
+ class ConfigError < StandardError; end
3
+
4
+ class ConfigValidator
5
+ def initialize(config)
6
+ @config = config
7
+ end
8
+
9
+ def validate!
10
+ ensure_projects_exist
11
+ ensure_environments_exist
12
+ ensure_servers_exist
13
+ end
14
+
15
+ private
16
+
17
+ def ensure_projects_exist
18
+ if @config.projects.empty?
19
+ raise ConfigError,
20
+ "There are no projects. Add some to your config (~/.taketo.rc.rb by default)"
21
+ end
22
+ end
23
+
24
+ def ensure_environments_exist
25
+ projects_without_environments = @config.projects.select { |project| project.environments.empty? }
26
+ if projects_without_environments.any?
27
+ raise ConfigError,
28
+ "There is no environments for the following projects: #{projects_without_environments.map(&:name).join(", ")}"
29
+ end
30
+ end
31
+
32
+ def ensure_servers_exist
33
+ @config.projects.each do |project|
34
+ environments_without_servers = project.environments.select { |environment| environment.servers.empty? }
35
+ if environments_without_servers.any?
36
+ raise ConfigError,
37
+ "There is no servers for the following environments in project #{project.name}: #{environments_without_servers.map(&:name).join(", ")}"
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+
@@ -0,0 +1,32 @@
1
+ require 'shellwords'
2
+
3
+ module Taketo
4
+ module Constructs
5
+ class Command
6
+ include Shellwords
7
+
8
+ attr_reader :name
9
+ attr_accessor :command
10
+
11
+ def initialize(name)
12
+ @name = name
13
+ end
14
+
15
+ def render(server)
16
+ %Q[#{location(server)} #{environment_variables(server)} #{command}].squeeze(" ")
17
+ end
18
+
19
+ private
20
+
21
+ def location(server)
22
+ %Q[cd #{shellescape server.default_location};] if server.default_location
23
+ end
24
+
25
+ def environment_variables(server)
26
+ server.environment_variables.map { |k, v| %Q[#{k}=#{shellescape v}] }.join(" ")
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+
@@ -1,14 +1,16 @@
1
+ require 'taketo/support'
2
+
1
3
  module Taketo
2
4
  module Constructs
3
5
  class Config
4
6
  attr_reader :projects
5
7
 
6
8
  def initialize
7
- @projects = {}
9
+ @projects = Taketo::Support::NamedNodesCollection.new
8
10
  end
9
11
 
10
12
  def append_project(project)
11
- @projects[project.name] = project
13
+ @projects << project
12
14
  end
13
15
  end
14
16
  end
@@ -1,3 +1,5 @@
1
+ require 'taketo/support'
2
+
1
3
  module Taketo
2
4
  module Constructs
3
5
  class Environment
@@ -5,12 +7,12 @@ module Taketo
5
7
 
6
8
  def initialize(name)
7
9
  @name = name
8
- @servers = {}
10
+ @servers = Taketo::Support::NamedNodesCollection.new
9
11
  end
10
12
 
11
13
  def append_server(server)
12
- server.environment = self
13
- @servers[server.name] = server
14
+ server.environment = self
15
+ @servers << server
14
16
  end
15
17
  end
16
18
  end
@@ -1,3 +1,5 @@
1
+ require 'taketo/support'
2
+
1
3
  module Taketo
2
4
  module Constructs
3
5
  class Project
@@ -5,11 +7,11 @@ module Taketo
5
7
 
6
8
  def initialize(name)
7
9
  @name = name
8
- @environments = {}
10
+ @environments = Taketo::Support::NamedNodesCollection.new
9
11
  end
10
12
 
11
13
  def append_environment(environment)
12
- @environments[environment.name] = environment
14
+ @environments << environment
13
15
  end
14
16
  end
15
17
  end
@@ -1,12 +1,39 @@
1
1
  module Taketo
2
2
  module Constructs
3
3
  class Server
4
- attr_reader :name
4
+ class CommandNotFoundError < StandardError; end
5
+
6
+ attr_reader :name, :environment_variables, :commands
5
7
  attr_accessor :host, :port, :username, :default_location, :environment
6
8
 
7
9
  def initialize(name)
8
10
  @name = name
11
+ @environment_variables = {}
12
+ @commands = []
13
+ end
14
+
15
+ def env(env_variables)
16
+ @environment_variables.merge!(env_variables)
17
+ end
18
+
19
+ def append_command(command)
20
+ @commands << command
9
21
  end
22
+
23
+ def environment=(environment)
24
+ env(:RAILS_ENV => environment.name.to_s)
25
+ @environment = environment
26
+ end
27
+
28
+ def command_by_name(command_name)
29
+ @commands.detect { |c| c.name == command_name } or
30
+ if block_given?
31
+ yield
32
+ else
33
+ raise CommandNotFoundError, "Command #{command_name} not found for server #{name}"
34
+ end
35
+ end
36
+
10
37
  end
11
38
  end
12
39
  end
@@ -2,6 +2,11 @@ require 'taketo/constructs'
2
2
 
3
3
  module Taketo
4
4
  class ConstructsFactory
5
+
6
+ def create(type, *args)
7
+ send("create_#{type}", *args)
8
+ end
9
+
5
10
  def create_config
6
11
  Constructs::Config.new
7
12
  end
@@ -17,6 +22,10 @@ module Taketo
17
22
  def create_server(*args)
18
23
  Constructs::Server.new(*args)
19
24
  end
25
+
26
+ def create_command(*args)
27
+ Constructs::Command.new(*args)
28
+ end
20
29
  end
21
30
  end
22
31