factorylabs-auto_tagger 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +6 -0
- data/.gitignore +6 -0
- data/CHANGELOG +30 -0
- data/MIT-LICENSE +20 -0
- data/README.md +176 -0
- data/Rakefile +46 -0
- data/VERSION +1 -0
- data/auto_tagger.gemspec +83 -0
- data/bin/autotag +38 -0
- data/features/autotag.feature +39 -0
- data/features/deployment.feature +29 -0
- data/features/step_definitions/autotag_steps.rb +41 -0
- data/features/step_definitions/deployment_steps.rb +53 -0
- data/features/support/env.rb +8 -0
- data/features/support/step_helpers.rb +136 -0
- data/features/templates/cap_ext_deploy.erb +32 -0
- data/features/templates/deploy.erb +34 -0
- data/features/templates/stage.erb +1 -0
- data/geminstaller.yml +7 -0
- data/lib/auto_tagger.rb +10 -0
- data/lib/auto_tagger/auto_tagger.rb +26 -0
- data/lib/auto_tagger/capistrano_helper.rb +39 -0
- data/lib/auto_tagger/commander.rb +15 -0
- data/lib/auto_tagger/recipes.rb +54 -0
- data/lib/auto_tagger/repository.rb +42 -0
- data/lib/auto_tagger/stage_manager.rb +23 -0
- data/lib/auto_tagger/tag.rb +41 -0
- data/spec/auto_tagger/auto_tagger_spec.rb +85 -0
- data/spec/auto_tagger/capistrano_helper_spec.rb +146 -0
- data/spec/auto_tagger/commander_spec.rb +17 -0
- data/spec/auto_tagger/repository_spec.rb +72 -0
- data/spec/auto_tagger/stage_manager_spec.rb +34 -0
- data/spec/auto_tagger/tag_spec.rb +66 -0
- data/spec/spec_helper.rb +7 -0
- metadata +116 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
Feature: Deployment
|
2
|
+
In order to get auto-tagging goodness
|
3
|
+
As a ruby and git ninja
|
4
|
+
I want cap to do all the work for me
|
5
|
+
|
6
|
+
Scenario: user deploys with single file deploy.rb with 3 stages
|
7
|
+
Given a three-stage app using single deploy file
|
8
|
+
And a ci tag
|
9
|
+
When I deploy to staging
|
10
|
+
Then a tag should be added to git
|
11
|
+
|
12
|
+
Scenario: user deploys with single file deploy.rb with no stages
|
13
|
+
Given a one-stage app using single deploy file with the following environments:
|
14
|
+
|production |
|
15
|
+
When I deploy
|
16
|
+
Then a "production" tag should be added to git
|
17
|
+
|
18
|
+
Scenario: user deploys with single file deploy.rb stages with dashes in the name
|
19
|
+
Given a one-stage app using single deploy file with the following environments:
|
20
|
+
|staging |
|
21
|
+
|secret-production |
|
22
|
+
When I deploy to secret-production
|
23
|
+
Then a "secret-production" tag should be added to git
|
24
|
+
|
25
|
+
Scenario: user deploys with cap-ext multistage
|
26
|
+
Given a three-stage app using cap-multistage
|
27
|
+
And a ci tag
|
28
|
+
When I deploy to staging
|
29
|
+
Then a tag should be added to git
|
@@ -0,0 +1,41 @@
|
|
1
|
+
Given /^a repo$/ do
|
2
|
+
puts
|
3
|
+
helpers = StepHelpers.new
|
4
|
+
helpers.create_git_repo
|
5
|
+
helpers.create_app
|
6
|
+
@tags = helpers.tags
|
7
|
+
puts
|
8
|
+
end
|
9
|
+
|
10
|
+
When /^I run autotag with no arguments$/ do
|
11
|
+
puts
|
12
|
+
helpers = StepHelpers.new
|
13
|
+
@output = helpers.run_autotag
|
14
|
+
@tags = helpers.tags
|
15
|
+
puts
|
16
|
+
end
|
17
|
+
|
18
|
+
When /^I run autotag with "([^\"]*)"$/ do |args|
|
19
|
+
puts
|
20
|
+
helpers = StepHelpers.new
|
21
|
+
@output = helpers.run_autotag(args)
|
22
|
+
@tags = helpers.tags
|
23
|
+
puts
|
24
|
+
end
|
25
|
+
|
26
|
+
Then /^I should see "([^\"]*)"$/ do |text|
|
27
|
+
@output.should =~ /#{Regexp.escape(text)}/
|
28
|
+
end
|
29
|
+
|
30
|
+
Then /^no tags should be created$/ do
|
31
|
+
@tags.strip.should == ""
|
32
|
+
end
|
33
|
+
|
34
|
+
Then /^a "([^\"]*)" tag should be added to git$/ do |stage|
|
35
|
+
helpers = StepHelpers.new
|
36
|
+
helpers.tags.starts_with?(stage).should be_true
|
37
|
+
end
|
38
|
+
|
39
|
+
Then /^a "([^\"]*)" tag should be created$/ do |prefix|
|
40
|
+
@tags.strip.split("\n").any?{|tag| tag.starts_with?("demo")}.should be_true
|
41
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
Given /^a three-stage app using single deploy file$/ do
|
2
|
+
puts
|
3
|
+
helpers = StepHelpers.new
|
4
|
+
helpers.create_app_with_single_deploy_file([:ci, :staging, :production])
|
5
|
+
puts
|
6
|
+
@tags = helpers.tags
|
7
|
+
end
|
8
|
+
|
9
|
+
Given /^a one\-stage app using single deploy file with the following environments:$/ do |table|
|
10
|
+
puts
|
11
|
+
helpers = StepHelpers.new
|
12
|
+
helpers.create_app_with_single_deploy_file table.raw.map(&:first)
|
13
|
+
puts
|
14
|
+
@tags = helpers.tags
|
15
|
+
end
|
16
|
+
|
17
|
+
Given /^a three-stage app using cap-multistage$/ do
|
18
|
+
puts
|
19
|
+
helpers = StepHelpers.new
|
20
|
+
helpers.create_app_with_cap_ext_multistage
|
21
|
+
puts
|
22
|
+
@tags = helpers.tags
|
23
|
+
end
|
24
|
+
|
25
|
+
Given /^a ci tag$/ do
|
26
|
+
puts
|
27
|
+
helpers = StepHelpers.new
|
28
|
+
helpers.autotag("ci")
|
29
|
+
@tags = helpers.tags
|
30
|
+
puts
|
31
|
+
end
|
32
|
+
|
33
|
+
When /^I deploy to (.*)$/ do |environment|
|
34
|
+
puts
|
35
|
+
helpers = StepHelpers.new
|
36
|
+
helpers.deploy(environment)
|
37
|
+
puts
|
38
|
+
end
|
39
|
+
|
40
|
+
When /^I deploy$/ do
|
41
|
+
puts
|
42
|
+
helpers = StepHelpers.new
|
43
|
+
helpers.deploy
|
44
|
+
puts
|
45
|
+
end
|
46
|
+
|
47
|
+
Then /^a tag should be added to git$/ do
|
48
|
+
puts
|
49
|
+
helpers = StepHelpers.new
|
50
|
+
new_tags = helpers.tags
|
51
|
+
@tags.length.should < new_tags.length
|
52
|
+
puts
|
53
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
class StepHelpers
|
2
|
+
|
3
|
+
attr_reader :test_files_dir, :app_dir, :repo_dir
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@test_files_dir = File.join(Dir.pwd, "test_files")
|
7
|
+
@app_dir = File.join(test_files_dir, "app")
|
8
|
+
@repo_dir = File.join(test_files_dir, "repo")
|
9
|
+
end
|
10
|
+
|
11
|
+
def reset
|
12
|
+
FileUtils.rm_r(test_files_dir) if File.exists?(test_files_dir)
|
13
|
+
FileUtils.mkdir_p(test_files_dir)
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_git_repo
|
17
|
+
FileUtils.mkdir_p repo_dir
|
18
|
+
`cd #{repo_dir} && git --bare init`
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_app_with_single_deploy_file(environments)
|
22
|
+
create_git_repo
|
23
|
+
create_app
|
24
|
+
capify_app
|
25
|
+
create_single_deploy_file(environments)
|
26
|
+
end
|
27
|
+
|
28
|
+
def run_autotag(args = nil)
|
29
|
+
cmd = "cd #{app_dir} && ../../bin/autotag"
|
30
|
+
cmd += " #{args}" if args
|
31
|
+
`#{cmd}`
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_app_with_cap_ext_multistage
|
35
|
+
create_git_repo
|
36
|
+
create_app
|
37
|
+
capify_app
|
38
|
+
create_cap_ext_multistage_deploy_files
|
39
|
+
end
|
40
|
+
|
41
|
+
def deploy(stage = nil)
|
42
|
+
deploy_command = "cap "
|
43
|
+
deploy_command += "#{stage} " if stage
|
44
|
+
deploy_command += "deploy"
|
45
|
+
|
46
|
+
run_commands [
|
47
|
+
"cd #{app_dir}",
|
48
|
+
"cap deploy:setup",
|
49
|
+
deploy_command
|
50
|
+
]
|
51
|
+
end
|
52
|
+
|
53
|
+
def autotag(stage)
|
54
|
+
system "cd #{app_dir} && git tag #{stage}/#{Time.now.utc.strftime('%Y%m%d%H%M%S')} && git push origin --tags"
|
55
|
+
end
|
56
|
+
|
57
|
+
def tags
|
58
|
+
system "cd #{app_dir} && git fetch origin --tags"
|
59
|
+
tags = `cd #{app_dir} && git tag`
|
60
|
+
puts tags
|
61
|
+
tags
|
62
|
+
end
|
63
|
+
|
64
|
+
def create_app
|
65
|
+
FileUtils.mkdir_p app_dir
|
66
|
+
run_commands [
|
67
|
+
"cd #{app_dir}",
|
68
|
+
"touch README",
|
69
|
+
"git init",
|
70
|
+
"git add .",
|
71
|
+
%Q{git commit -m "first commit"},
|
72
|
+
"git remote add origin file://#{repo_dir}",
|
73
|
+
"git push origin master"
|
74
|
+
]
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def capify_app
|
80
|
+
run_commands [
|
81
|
+
"cd #{app_dir}",
|
82
|
+
"mkdir config",
|
83
|
+
"capify ."
|
84
|
+
]
|
85
|
+
end
|
86
|
+
|
87
|
+
def create_single_deploy_file(environments)
|
88
|
+
repository = repo_dir
|
89
|
+
deploy_to = File.join(test_files_dir, "deployed")
|
90
|
+
git_location = `which git`.strip
|
91
|
+
user = Etc.getlogin
|
92
|
+
|
93
|
+
path = File.expand_path(File.join(__FILE__, "..", "..", "templates", "deploy.erb"))
|
94
|
+
#puts "TRACE %s:%s" % [__FILE__, __LINE__]
|
95
|
+
#puts path
|
96
|
+
#puts File.exists?(path)
|
97
|
+
|
98
|
+
template = ERB.new File.read(path)
|
99
|
+
output = template.result(binding)
|
100
|
+
File.open(File.join(app_dir, "config", "deploy.rb"), 'w') {|f| f.write(output) }
|
101
|
+
end
|
102
|
+
|
103
|
+
def create_cap_ext_multistage_deploy_files
|
104
|
+
repository = repo_dir
|
105
|
+
deploy_to = File.join(test_files_dir, "deployed")
|
106
|
+
git_location = `which git`.strip
|
107
|
+
user = Etc.getlogin
|
108
|
+
environments = [:ci, :staging, :production]
|
109
|
+
|
110
|
+
path = File.expand_path(File.join(__FILE__, "..", "..", "templates", "cap_ext_deploy.erb"))
|
111
|
+
|
112
|
+
template = ERB.new File.read(path)
|
113
|
+
output = template.result(binding)
|
114
|
+
File.open(File.join(app_dir, "config", "deploy.rb"), 'w') {|f| f.write(output) }
|
115
|
+
|
116
|
+
%w(staging production).each do |stage|
|
117
|
+
create_cap_ext_multistage_deploy_stage_file(stage)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def create_cap_ext_multistage_deploy_stage_file(stage)
|
122
|
+
deploy_subdir = File.join(app_dir, "config", "deploy")
|
123
|
+
FileUtils.mkdir_p deploy_subdir
|
124
|
+
|
125
|
+
template_path = File.expand_path(File.join(__FILE__, "..", "..", "templates", "stage.erb"))
|
126
|
+
template = ERB.new File.read(template_path)
|
127
|
+
output = template.result(binding)
|
128
|
+
|
129
|
+
File.open(File.join(deploy_subdir, "#{stage}.rb"), 'w') {|f| f.write(output) }
|
130
|
+
end
|
131
|
+
|
132
|
+
def run_commands(commands)
|
133
|
+
puts `#{commands.join(" && ")}`
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "recipes", "release_tagger"))
|
2
|
+
|
3
|
+
set :autotagger_stages, [:ci, :staging, :production]
|
4
|
+
set :stages, [:staging, :production]
|
5
|
+
set :default_stage, :staging
|
6
|
+
|
7
|
+
require 'capistrano/ext/multistage'
|
8
|
+
|
9
|
+
set :application, "deployed"
|
10
|
+
set :user, "<%= user %>"
|
11
|
+
set :repository, "file://<%= repository %>"
|
12
|
+
set :scm, :git
|
13
|
+
set :scm_command, "<%= git_location %>"
|
14
|
+
|
15
|
+
set :deploy_to, "<%= deploy_to %>"
|
16
|
+
|
17
|
+
set :run_method, :run
|
18
|
+
set :ssh_options, { :forward_agent => true }
|
19
|
+
default_run_options[:pty] = true
|
20
|
+
|
21
|
+
role :app, "localhost" # => this must be mapped
|
22
|
+
|
23
|
+
namespace :deploy do
|
24
|
+
task :restart do
|
25
|
+
puts "restarting..."
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
before "deploy:update_code", "release_tagger:set_branch"
|
30
|
+
after "deploy", "release_tagger:create_tag"
|
31
|
+
after "deploy", "release_tagger:write_tag_to_shared"
|
32
|
+
after "deploy", "release_tagger:print_latest_tags"
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "recipes", "release_tagger"))
|
2
|
+
|
3
|
+
set :autotagger_stages, ["<%= environments.join('", "') %>"]
|
4
|
+
|
5
|
+
set :application, "deployed"
|
6
|
+
set :user, "<%= user %>"
|
7
|
+
set :repository, "file://<%= repository %>"
|
8
|
+
set :deploy_to, "<%= deploy_to %>"
|
9
|
+
set :scm, :git
|
10
|
+
set :scm_command, "<%= git_location %>"
|
11
|
+
|
12
|
+
set :run_method, :run
|
13
|
+
set :ssh_options, { :forward_agent => true }
|
14
|
+
default_run_options[:pty] = true
|
15
|
+
|
16
|
+
role :app, "localhost" # => this must be mapped
|
17
|
+
|
18
|
+
|
19
|
+
namespace :deploy do
|
20
|
+
task :restart do
|
21
|
+
puts "restarting..."
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
<% environments.each do |environment| %>
|
26
|
+
task "<%= environment %>" do
|
27
|
+
set :stage, "<%= environment %>"
|
28
|
+
end
|
29
|
+
<% end %>
|
30
|
+
|
31
|
+
before "deploy:update_code", "release_tagger:set_branch"
|
32
|
+
after "deploy", "release_tagger:create_tag"
|
33
|
+
after "deploy", "release_tagger:write_tag_to_shared"
|
34
|
+
after "deploy", "release_tagger:print_latest_tags"
|
@@ -0,0 +1 @@
|
|
1
|
+
# nothing to see here...
|
data/geminstaller.yml
ADDED
data/lib/auto_tagger.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
class AutoTagger
|
2
|
+
|
3
|
+
class EnvironmentCannotBeBlankError < StandardError; end
|
4
|
+
|
5
|
+
attr_reader :stage, :repository, :working_directory
|
6
|
+
|
7
|
+
def initialize(stage, path = nil)
|
8
|
+
raise EnvironmentCannotBeBlankError if stage.to_s.strip == ""
|
9
|
+
@working_directory = File.expand_path(path ||= Dir.pwd)
|
10
|
+
@repository = Repository.new(@working_directory)
|
11
|
+
@stage = stage
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_tag(commit = nil)
|
15
|
+
repository.tags.fetch
|
16
|
+
new_tag = repository.tags.create(stage, commit)
|
17
|
+
repository.tags.push
|
18
|
+
new_tag
|
19
|
+
end
|
20
|
+
|
21
|
+
def latest_tag
|
22
|
+
repository.tags.fetch
|
23
|
+
repository.tags.latest_from(stage)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class CapistranoHelper
|
2
|
+
|
3
|
+
attr_reader :variables, :stage, :working_directory
|
4
|
+
|
5
|
+
def initialize(variables)
|
6
|
+
@stage_manager = StageManager.new(variables[:autotagger_stages])
|
7
|
+
@variables = variables
|
8
|
+
@stage = variables[:stage]
|
9
|
+
@working_directory = variables[:working_directory] || Dir.pwd
|
10
|
+
end
|
11
|
+
|
12
|
+
def previous_stage
|
13
|
+
@stage_manager.previous_stage(stage)
|
14
|
+
end
|
15
|
+
|
16
|
+
def branch
|
17
|
+
if variables.has_key?(:head)
|
18
|
+
variables[:branch]
|
19
|
+
elsif variables.has_key?(:tag)
|
20
|
+
variables[:tag]
|
21
|
+
elsif previous_stage && (latest = AutoTagger.new(previous_stage, working_directory).latest_tag)
|
22
|
+
latest
|
23
|
+
else
|
24
|
+
variables[:branch]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def release_tag_entries
|
29
|
+
entries = []
|
30
|
+
@stage_manager.stages.each do |stage|
|
31
|
+
tagger = AutoTagger.new(stage, working_directory)
|
32
|
+
tag = tagger.latest_tag
|
33
|
+
commit = tagger.repository.commit_for(tag)
|
34
|
+
entries << "#{stage.to_s.ljust(10, " ")} #{tag.to_s.ljust(30, " ")} #{commit.to_s}"
|
35
|
+
end
|
36
|
+
entries
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Commander
|
2
|
+
class << self
|
3
|
+
def execute(path, cmd)
|
4
|
+
`#{command_in_context(path, cmd)}`
|
5
|
+
end
|
6
|
+
|
7
|
+
def execute!(path, cmd)
|
8
|
+
system command_in_context(path, cmd)
|
9
|
+
end
|
10
|
+
|
11
|
+
def command_in_context(path, cmd)
|
12
|
+
"cd #{path} && #{cmd}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|