taketo 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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