deployment_pipeline 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source :rubygems
2
+ gem "thor","0.14.6"
3
+ gem "pg"
4
+ gem "active_support"
5
+ gem "i18n"
6
+ gem "chronic"
7
+ gem "rdiscount"
8
+ gem "rugged","0.16.0"
9
+ gem "progressbar","0.11.0"
10
+ gem "hpricot"
11
+
12
+ #testing related
13
+ gem "rspec"
14
+ gem "rr","1.0.4"
15
+
16
+
@@ -0,0 +1,40 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ active_support (3.0.0)
5
+ activesupport (= 3.0.0)
6
+ activesupport (3.0.0)
7
+ chronic (0.6.7)
8
+ diff-lcs (1.1.3)
9
+ hpricot (0.8.6)
10
+ i18n (0.6.0)
11
+ pg (0.13.2)
12
+ progressbar (0.11.0)
13
+ rdiscount (1.6.8)
14
+ rr (1.0.4)
15
+ rspec (2.10.0)
16
+ rspec-core (~> 2.10.0)
17
+ rspec-expectations (~> 2.10.0)
18
+ rspec-mocks (~> 2.10.0)
19
+ rspec-core (2.10.1)
20
+ rspec-expectations (2.10.0)
21
+ diff-lcs (~> 1.1.3)
22
+ rspec-mocks (2.10.1)
23
+ rugged (0.16.0)
24
+ thor (0.14.6)
25
+
26
+ PLATFORMS
27
+ ruby
28
+
29
+ DEPENDENCIES
30
+ active_support
31
+ chronic
32
+ hpricot
33
+ i18n
34
+ pg
35
+ progressbar (= 0.11.0)
36
+ rdiscount
37
+ rr (= 1.0.4)
38
+ rspec
39
+ rugged (= 0.16.0)
40
+ thor (= 0.14.6)
@@ -0,0 +1,90 @@
1
+
2
+ Deployment Pipeline (Let's make Continuous Deployment painless)
3
+ -------------------
4
+
5
+ In the life of Deployment Manager of an agile aggressive team<sup>1</sup>, there comes a day when he/she needs to manage releases and communicate with
6
+ stake holders about features being released and also control what goes live on production. Why control? because in any practical scenario, feature which is ready
7
+ and accepted by product managers doesn't guarantee its readiness for the business and marketing and quite often non-technical teams need a buffer time before
8
+ a feature is released. Deployment Pipeline is here to help you manage these releases and keep non-technical teams and users better informed about whats going on.
9
+
10
+
11
+ ### Ideal Scenario:
12
+
13
+ 1. Your Product Managers write feature/bug stories and prioritize them anytime during the day.
14
+ 2. Your engineering team believes in Continuous Integration and all engineers commit to Master branch (or Trunk) several times a day.
15
+ 3. If the build (CI) is green, that tag from Master Branch might get pushed to QA environment for stories to be delivered.
16
+ 4. Once all stories are accepted, (QA) Staging Tag is deployed to production, Happy Ending of the day!
17
+
18
+ ### Practical Scenario:
19
+ 1. You have 5 Product Managers who requests feature/bug stories and prioritize them anytime during the day.
20
+ 2. You have 10 engineers working on 5 stories and commits to Master branch (or Trunk) several times a day.
21
+ 3. Build is green only 60% of time during the day.
22
+ 4. By the time build is green there are commits to 4 finished & intermediate commits to an 1 un-finished story.
23
+ 5. QA delivers the stories(4) which has been finished and 3 are accepted and 1 is rejected.
24
+ 6. Among 3 accepted stories Marketing Team takes a call to hold 1 story even though its ready.
25
+ 7. Now we have 2 production ready, 1 held by marketing, 1 rejected, 1 un-finished.
26
+ 8. Commits related to 2 prod ready are shuffled between commits related to all non-ready stories.
27
+ 9. Among 2 ready-to-deploy , 1 is marked urgent but it's commits are part of the day when CI build was RED (not necessarily due to this commit).
28
+ 10. What and how would you deploy today? (cherry-pick commits for a feature with no green build? FAIL) You post-pone release!
29
+ - ==Day Rolls Over==
30
+ 11. 3 new stories requested by Product Managers.
31
+ 12. An engineer finishes 2 of new stories quickly whose commits goes to master. 3rd new story might take long to finish, but gets its commits pushed to master.
32
+ 13. Build is green and a tag on master is pushed to QA environment.
33
+ 14. 2 new stories are delivered and accepted.
34
+ 15. ...
35
+ 16. Which staging tag on master would you deploy to production? At any given time there are commits from un-finished/un-delivered stories.
36
+
37
+
38
+ ### Solutions:
39
+ 1. Sure you can use [feature toggle](http://martinfowler.com/bliki/FeatureToggle.html), but it only makes sense for long running (for weeks) set of stories. When every story starts to have a feature toggle, then system gets polluted with
40
+ if-else everywhere, which again is difficult to manage and error prone
41
+ 2. You can also ask engineers to have separate feature branches for each stories and rebase with master often. This brings in its own [over heads](http://martinfowler.com/bliki/FeatureBranch.html)...
42
+ * Time spent in merging changes
43
+ * It needs a single controller of release branch who pulls the changes and makes sure what goes live is vetted. (This controller can soon become a bottleneck in the process)
44
+ * Engineers work in isolation and can not commit intermediate commits unless feature is complete.
45
+ 3. Use All-Accepted Marker : This is a commit on master below which all stories have been accepted and there are few commits (shuffled with other un-finished) above the marker that can be cleanly cherry-picked.
46
+
47
+
48
+ #### Workflow for All-Accepted Marker Deployment:
49
+ 1. Find a suitable commit below which all stories are accepted
50
+ 2. Branch out to new "Release" Branch
51
+ 3. Inform stake-holders about what features are being released and locked down release marker
52
+ 4. Cherry-pick related commits from above the marker to release branch
53
+ 5. Build the release branch and wait for it to be green
54
+ 6. Deploy release branch to production
55
+ 7. Automate this entire process
56
+
57
+ ### Deployment Pipline @ Work :
58
+ ####Getting Started:
59
+ <pre><code>
60
+ developers-machine:~/workspace/repository (master)$ <b>pipeline help</b>
61
+ Tasks:
62
+ pipeline help [TASK] # Describe available tasks or one specific task
63
+ pipeline release_plan # Prepares a release plan
64
+ pipeline setup # Setup Deployment Pipeline Tool
65
+ pipeline status # lists all stories with their status
66
+ pipeline suitable_release # Suggests a release commit to be picked and also includes a release plan
67
+
68
+ Options:
69
+ [--config=CONFIG] # A ruby file that defines relevant constants & configs. accepts ENV $PIPELINE_CONFIG
70
+ # Default: /Users/dev_home/.pipeline_config
71
+ </code></pre>
72
+
73
+
74
+ ####Find Suitable Commit for All-Accepted Marker:
75
+ <pre><code>
76
+ developers-machine:~/workspace/repository (master)$ <b>pipeline help</b>
77
+ Tasks:
78
+ pipeline help [TASK] # Describe available tasks or one specific task
79
+ pipeline release_plan # Prepares a release plan
80
+ pipeline setup # Setup Deployment Pipeline Tool
81
+ pipeline status # lists all stories with their status
82
+ pipeline suitable_release # Suggests a release commit to be picked and also includes a release plan
83
+
84
+ Options:
85
+ [--config=CONFIG] # A ruby file that defines relevant constants & configs. accepts ENV $PIPELINE_CONFIG
86
+ # Default: /Users/dev_home/.pipeline_config
87
+ </code></pre>
88
+
89
+ <sub>\[**1**\]: Team which is motivated for [release-often philosophy](http://radar.oreilly.com/2009/03/continuous-deployment-5-eas.html) so much that it releases to production multiple times a day. It uses DVCS like **[Git](http://git-scm.com)** and agile story tracker like **[PIVOTAL TRACKER](http://www.pivotaltracker.com)**. It has adopted TDD & **[Continious Integration](http://en.wikipedia.org/wiki/Continuous_integration)** as way of life. Every engineer [commits to master all the time](http://martinfowler.com/bliki/FeatureBranch.html#PromiscuousIntegrationVsContinuousIntegration).
90
+ </sub>
@@ -0,0 +1,202 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__) + '/../lib/pipeline'
4
+
5
+ class PipelineCmd < Thor
6
+ class_option :config, :type => :string,
7
+ :desc => "A ruby file that defines relevant constants & configs. accepts ENV $PIPELINE_CONFIG",
8
+ :default => ENV["PIPELINE_CONFIG"] || "#{ENV['HOME']}/.pipeline_config"
9
+ def initialize(args=[], options={}, config={})
10
+ super
11
+ load(self.options[:config])
12
+ end
13
+
14
+ desc "setup", "Setup Deployment Pipeline Tool"
15
+ method_option :force, :type => :boolean, :default => false, :desc => "Force operation"
16
+ def setup
17
+ #stub
18
+ puts "Setup complete!"
19
+ end
20
+
21
+
22
+ desc "status", "lists all stories with their status"
23
+ method_option :repository_path, :default => "#{Dir.pwd}", :desc => "Git repository path"
24
+ method_option :branch, :default => "master", :desc => "Git branch to consider"
25
+ method_option :commit_range, :type => :string, :desc => "Range of commits, eg.\"last_release_tag1..HEAD\""
26
+ method_option :html, :type => :boolean, :default => false, :desc => "pretty html output"
27
+ def status
28
+
29
+ c_range = options[:commit_range]
30
+ c_range = c_range.split("..")
31
+
32
+ md_msg = ""
33
+
34
+ code_repo = CodeRepository.new(options[:repository_path],c_range[0],c_range[1])
35
+ tracker = PivotalTracker.new
36
+
37
+ stories,untagged_commits = tracker.extract_story_ids(code_repo.commits.reverse)
38
+
39
+
40
+ tracker.load_stories(stories.keys,true)
41
+
42
+ stories_by_status = {}
43
+ tracker.stories.each {|story|
44
+ stories_by_status[story[:status]] ||= []
45
+ stories_by_status[story[:status]] << " * (#{story[:type]}) [#{story[:name]}](#{story[:url]}) requested by **#{story[:requested_by]}** owned by #{code_repo.contributors(stories[story[:id]]).join(', ')} "
46
+ }
47
+
48
+ stories_by_status.each_pair do |status,stories|
49
+ md_msg << "\n#{status}: \n\n"
50
+ stories.each {|s| md_msg << " #{s} \n"}
51
+
52
+ end
53
+
54
+
55
+
56
+ if untagged_commits.length > 0
57
+ md_msg << "\n Following commits have not been tagged, (**why lah?**) \n\n"
58
+ untagged_commits.each do |c|
59
+ md_msg << " * #{c.oid} #{c.message.gsub("\n"," ")[0...140]} by **#{c.author[:name]}** \n"
60
+ end
61
+ end
62
+
63
+ puts "STATUS:"
64
+ puts md_msg
65
+ if options[:html] == true
66
+ markdown = RDiscount.new(md_msg)
67
+ temp_html_file = "/tmp/tmp_msg_#{Time.now.to_i}.html"
68
+ File.open(temp_html_file, 'w') {|f| f.write(markdown.to_html) }
69
+ system("open #{temp_html_file}")
70
+ end
71
+ end
72
+
73
+ desc "suitable_release", "Suggests a release commit to be picked and also includes a release plan"
74
+ method_option :repository_path, :default => "#{Dir.pwd}", :desc => "Git repository path"
75
+ method_option :branch, :default => "master", :desc => "Git branch to consider"
76
+ method_option :last_release_commit, :type => :string, :desc => "Commit id (SHA) of previous release "
77
+ def suitable_release
78
+ last_release_commit = options[:last_release_commit]
79
+ code_repo = CodeRepository.new(options[:repository_path],last_release_commit)
80
+ tracker = PivotalTracker.new
81
+
82
+ suitable_release_sha = last_release_commit
83
+
84
+ code_repo.commits.reverse.each do |commit|
85
+ puts "Analysing... #{commit.oid}"
86
+ #Check if all stories associated to this commit has been accepted, if yes consider the commit , else break
87
+ #Consider the commit if its untagged anyway
88
+ commit_suitable = true
89
+ story_ids,untagged = tracker.extract_story_ids([commit]) #this will return story_ids and untagged such that they are mutually exclusive.
90
+ story_ids.keys.each { |story_id|
91
+ story = tracker.story_obj(story_id)
92
+ if story.nil? || story[:status] != "accepted"
93
+ puts "Unknown story :#{story_id} " if story.nil?
94
+ puts "Unsuitable commit because story status is #{story[:status]}"
95
+ commit_suitable = false
96
+ break
97
+ end
98
+
99
+ suitable_release_sha = commit.oid
100
+ puts "Stories accepted for #{commit.oid}, hence considered"
101
+ }
102
+ unless untagged.empty?
103
+ puts "Commit is untagged hence considered"
104
+ suitable_release_sha = untagged.first.oid
105
+ end
106
+
107
+ break unless commit_suitable
108
+ end
109
+
110
+
111
+ if suitable_release_sha == last_release_commit
112
+ puts "No suitable commit to pick for release"
113
+ else
114
+ puts "Commit that can be pickup up for release is: #{suitable_release_sha}"
115
+ end
116
+ end
117
+
118
+
119
+ desc "release_plan", "Prepares a release plan"
120
+ method_option :repository_path, :default => "#{Dir.pwd}", :desc => "Git repository path"
121
+ method_option :branch, :default => "master", :desc => "Git branch to consider"
122
+ method_option :last_release_commit, :type => :string, :desc => "Commit id (SHA) of previous release "
123
+ method_option :target_release_commit, :type => :string, :desc => "Commit id (SHA) of intended release "
124
+ method_option :html, :type => :boolean, :default => false, :desc => "pretty html output"
125
+ def release_plan
126
+ last_release_commit = options[:last_release_commit]
127
+ target_release_commit = options[:target_release_commit]
128
+ code_repo = CodeRepository.new(options[:repository_path],last_release_commit)
129
+ tracker = PivotalTracker.new
130
+ stories,untagged_commits = tracker.extract_story_ids(code_repo.commits.reverse)
131
+
132
+ commits_ready = []
133
+ stories_included = []
134
+
135
+ all_commits = code_repo.commits.reverse
136
+ commits_ready << all_commits.shift
137
+ while all_commits.length > 0 && commits_ready.last.oid != target_release_commit
138
+ commits_ready << all_commits.shift
139
+ end
140
+
141
+ commits_ready.shift #can ignore first commit, its the last release commit
142
+ stories_ready = []
143
+ commits_to_cherry_pick = []
144
+ sha_list =commits_ready.map(&:oid)
145
+ sha_list.each {|c|
146
+ s = stories.select {|k,v| v.include?(c)}
147
+ s.each_pair{|k,v|
148
+ other_commits = v - [c]
149
+ other_commits.each {|oc|
150
+ if !sha_list.include?(oc)
151
+ commits_to_cherry_pick << oc
152
+ end
153
+ }
154
+ }
155
+ stories_ready += s.keys
156
+ }
157
+ stories_ready.uniq!
158
+ tracker.load_stories(stories_ready,true)
159
+
160
+ #cherry-picks have to be arranged in order which appears in repository
161
+ ordered_cherry_picks = []
162
+ all_commits.each {|c|
163
+ ordered_cherry_picks << c.oid if commits_to_cherry_pick.include?(c.oid)
164
+ }
165
+
166
+
167
+ md_msg = ""
168
+
169
+ md_msg << "Release can be locked at commit #{sha_list.last} \n\n"
170
+
171
+ md_msg << "Stories being released are:\n\n"
172
+ tracker.stories.each {|story|
173
+ md_msg << " * (#{story[:type]}) [#{story[:name]}](#{story[:url]}) requested by **#{story[:requested_by]}** owned by #{code_repo.contributors(stories[story[:id]]).join(', ')} "
174
+ md_msg << "(current status:<font color='red'>#{story[:status]}</font>)" if story[:status] != 'accepted'
175
+ md_msg << " \n"
176
+ }
177
+
178
+
179
+ md_msg << "\n\nCommits that needs to be cherry-picked as they are part of above stories \n\n"
180
+ ordered_cherry_picks.reverse.each {|c|
181
+ c = code_repo.lookup(c)
182
+ md_msg << " * #{c.oid} #{c.message.gsub("\n"," ")[0...140]} by #{c.author[:name]} \n"
183
+ }
184
+
185
+ puts md_msg
186
+
187
+ if options[:html] == true
188
+ markdown = RDiscount.new(md_msg)
189
+ temp_html_file = "/tmp/tmp_msg_#{Time.now.to_i}.html"
190
+ File.open(temp_html_file, 'w') {|f| f.write(markdown.to_html) }
191
+ system("open #{temp_html_file}")
192
+ end
193
+
194
+
195
+ end
196
+
197
+ end
198
+
199
+
200
+
201
+
202
+ PipelineCmd.start
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = "deployment_pipeline"
5
+ gem.description = "Deployment Pipeline makes Continuous Deployment super easy."
6
+ gem.homepage = "https://github.com/parolkar/deployment_pipeline"
7
+ gem.summary = gem.description
8
+ gem.version = "0.0.0"
9
+ gem.authors = ["Abhishek Parolkar"]
10
+ gem.email = "abhishek@parolkar.com"
11
+ gem.has_rdoc = false
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
15
+ gem.require_paths = ['lib']
16
+ gem.add_dependency "rugged","0.16.0"
17
+ gem.add_dependency "thor","0.14.6"
18
+ gem.add_dependency "rdiscount"
19
+ gem.add_dependency "progressbar","0.11.0"
20
+ gem.add_dependency "hpricot"
21
+ end
22
+
Binary file
@@ -0,0 +1,45 @@
1
+ require 'rugged'
2
+
3
+ class CodeRepository
4
+ attr_reader :commits
5
+ def initialize(repository_path,from_commit,to_commit = nil)
6
+ @commits = []
7
+ @repo = Rugged::Repository.new(repository_path)
8
+
9
+ to_commit ||= get_head_ish
10
+ walker = @repo.walk(to_commit)
11
+
12
+ walker.each { |c|
13
+ @commits << c
14
+ break if c.oid == from_commit
15
+ }
16
+ end
17
+
18
+ def contributors(commit_list)
19
+
20
+ return [] if commit_list.empty?
21
+ author_names = []
22
+ @commits.each do |c|
23
+ next unless @commits.map(&:oid).include?(c.oid)
24
+ author = c.author
25
+ names = author[:name]
26
+ names = names.split("&")
27
+ names.each{|n|
28
+ n =n.strip
29
+ author_names << n unless author_names.include?(n)
30
+ }
31
+ end
32
+ author_names
33
+ end
34
+
35
+ def get_head_ish
36
+ @repo.head.target
37
+ end
38
+
39
+ def lookup(oid)
40
+ @repo.lookup(oid)
41
+ end
42
+ end
43
+
44
+
45
+
@@ -0,0 +1,17 @@
1
+ require 'logger'
2
+ require 'rubygems'
3
+ require 'thor'
4
+ require 'pg'
5
+ require 'date'
6
+ require 'chronic'
7
+ require 'active_support/all'
8
+ require "benchmark"
9
+ require 'net/smtp'
10
+ require 'progressbar'
11
+ require 'rdiscount'
12
+ require 'hpricot'
13
+ require 'net/http'
14
+ require 'uri'
15
+
16
+ require File.dirname(__FILE__) + '/code_repository'
17
+ require File.dirname(__FILE__) + '/tracker'
@@ -0,0 +1,102 @@
1
+
2
+ class Tracker
3
+ STORY_TAG_PATTERN ||=/\[\w*\s*\#(\d*)\]/ #Default example [fixes #30922794] or [#30972795]
4
+ attr_reader :stories
5
+
6
+ def initialize(tracker_ids,api_key)
7
+ raise("Missing tracker information") if (@api_key.empty? || @tracker_ids.empty?)
8
+ @tag_pattern = STORY_TAG_PATTERN
9
+ @stories = []
10
+ @story_ids = []
11
+ end
12
+ def extract_story_ids(commits_list)
13
+ raise("Must implement #extract_story_ids in #{self.class.to_s}")
14
+ end
15
+ def load_stories(story_id_list = nil)
16
+ raise("Must implement #load_stories in #{self.class.to_s}")
17
+ end
18
+ def story_obj(story_id)
19
+ raise("Must implement #story_obj in #{self.class.to_s}")
20
+ end
21
+ end
22
+
23
+ class PivotalTracker < Tracker
24
+ def initialize(tracker_ids = PIVOTAL_TRACKER_PROJECT_IDS,api_key = PIVOTAL_TRACKER_TOKEN)
25
+ @tracker_ids = tracker_ids
26
+ @api_key = api_key
27
+ super(@tracker_ids,@api_key)
28
+ end
29
+
30
+ def extract_story_ids(commits_list)
31
+ stories = {}
32
+ untagged_commits = []
33
+ commits_list.each { |c|
34
+ stories_related_to_commit = c.message.scan(@tag_pattern)
35
+ if stories_related_to_commit.empty?
36
+ untagged_commits << c
37
+ else
38
+
39
+ stories_related_to_commit.flatten.each {|s|
40
+ @story_ids << s
41
+ stories[s] ||= []
42
+ stories[s] << c.oid.to_s
43
+ }
44
+ end
45
+ }
46
+ return stories,untagged_commits
47
+ end
48
+
49
+ def load_stories(story_id_list = nil,show_progress = false)
50
+ unless story_id_list.nil?
51
+ @story_ids = story_id_list
52
+ end
53
+ @stories = []
54
+ if show_progress
55
+ progressbar = ProgressBar.new("Fetching...", @story_ids.length * @tracker_ids.length)
56
+ progressbar.bar_mark = '='
57
+ end
58
+ @story_ids.each do |story_id|
59
+ story = story_obj(story_id,progressbar)
60
+ if story
61
+ @stories << story
62
+ else
63
+ puts "WARNING: Story(#{story_id}) not found in any tracker(#{@tracker_ids.inspect})"
64
+ end
65
+ end
66
+ progressbar.finish if show_progress
67
+ @stories
68
+ end
69
+
70
+ def story_obj(story_id,progress_bar = nil)
71
+ @tracker_ids.each { |tracker_id|
72
+ obj = fetch_story_data("/projects/#{tracker_id}/stories/#{story_id}",@api_key)
73
+ story_obj = obj.at('story')
74
+ unless story_obj.nil?
75
+ story = {
76
+ :id => story_id,
77
+ :name => story_obj.at('name').innerHTML,
78
+ :type => story_obj.at('story_type').innerHTML,
79
+ :requested_by => story_obj.at('requested_by').innerHTML,
80
+ :url => story_obj.at('url').innerHTML,
81
+ :status => story_obj.at('current_state').innerHTML
82
+ }
83
+ return story
84
+ end
85
+ progress_bar.inc if progress_bar
86
+ }
87
+ return nil
88
+ end
89
+
90
+ def fetch_story_data(obj_path,token)
91
+ url = "http://www.pivotaltracker.com/services/v3/#{obj_path}"
92
+ resource_uri = URI.parse(url)
93
+ response = Net::HTTP.start(resource_uri.host, resource_uri.port) do |http|
94
+ http.get(resource_uri.path, {'X-TrackerToken' => "#{token}"})
95
+ end
96
+ doc = Hpricot(response.body)
97
+ rescue
98
+ doc = Hpricot("<xml></xml>")
99
+ end
100
+
101
+ end
102
+
@@ -0,0 +1,14 @@
1
+ # This is a config for Deployment Pipeline Project
2
+
3
+ PIVOTAL_TRACKER_TOKEN = "your_cryptic_pivotal_token"
4
+ PIVOTAL_TRACKER_PROJECT_IDS = ["proj_id1","proj_id2"]
5
+
6
+ #ActiveRecord::Base.establish_connection(
7
+ # :adapter => "postgres",
8
+ # :host => "localhost",
9
+ # :database => "appdb",
10
+ # :username => "appuser",
11
+ # :password => "secret"
12
+ #)
13
+
14
+ # STORY_TAG_PATTERN=/\[\w*\s*\#(\d*)\]/ #This regexp will extract ids of stories from commit
@@ -0,0 +1,30 @@
1
+ require "spec_helper"
2
+
3
+ describe CodeRepository do
4
+ let(:repository_path) {"/some/repo/path"}
5
+ let(:from_commit) {"shashashashashasahsahsah1"}
6
+ let(:commit1) {
7
+ commit = {}
8
+ stub(commit).oid {"shasha1"}
9
+ stub(commit).message {"[#1234] strory1"}
10
+ stub(commit).author {{:name => "Tom Cruise & Rajinikanth"}}
11
+ commit
12
+ }
13
+ before do
14
+ mock.instance_of(CodeRepository).get_head_ish {"shashasha"}
15
+ mock(Rugged::Repository).new(repository_path) {|repo|
16
+ stub(repo).walk { [commit1] }
17
+ }
18
+ end
19
+
20
+ it "should have commits" do
21
+ cr = CodeRepository.new(repository_path,from_commit)
22
+ cr.commits.should include(commit1)
23
+ end
24
+
25
+ it "should provide list of all contributors" do
26
+ cr = CodeRepository.new(repository_path,from_commit)
27
+ commiters = cr.contributors([commit1])
28
+ commiters.should include("Rajinikanth")
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ require 'rspec'
2
+ require "rr"
3
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/pipeline')
4
+
5
+ RSpec.configure do |config|
6
+ # Use color in STDOUT
7
+ config.color_enabled = true
8
+
9
+ # Use color not only in STDOUT but also in pagers and files
10
+ config.tty = true
11
+
12
+ # Use the specified formatter
13
+ config.formatter = :documentation # :progress, :html, :textmate
14
+
15
+ config.mock_with :rr
16
+
17
+ end
@@ -0,0 +1,48 @@
1
+ require "spec_helper"
2
+
3
+ describe Tracker do
4
+
5
+ it "should act like abstract base class to help developers implement more trackers" do
6
+
7
+ class FooTracker < Tracker
8
+ def initialize()
9
+ @tracker_ids = ['tracker_id']
10
+ @api_key = 'api_key'
11
+ super(@tracker_ids,@api_key)
12
+ end
13
+ end
14
+
15
+ foo_tracker = FooTracker.new()
16
+ expect {foo_tracker.story_obj("1234")}.to raise_error(RuntimeError, /Must implement #story_obj in FooTracker/)
17
+ end
18
+
19
+ end
20
+
21
+
22
+ describe PivotalTracker do
23
+ PIVOTAL_TRACKER_PROJECT_IDS = ['test_traker_id']
24
+ PIVOTAL_TRACKER_TOKEN = "api_key"
25
+ describe "#story_obj" do
26
+ it "should fetch story information using pivotal api" do
27
+ mock(Net::HTTP).start(anything,anything) {|response|
28
+ stub(response).body {
29
+ "<story>
30
+ <name>Story Name</name>
31
+ <story_type>feature</story_type>
32
+ <requested_by>Tom</requested_by>
33
+ <url>/path</url>
34
+ <current_state>finished</current_state>
35
+ </story>
36
+ "
37
+ }
38
+ response
39
+ }
40
+ pivotal_tracker = PivotalTracker.new()
41
+ story = pivotal_tracker.story_obj('1234')
42
+
43
+ story[:name].should == "Story Name"
44
+ end
45
+ end
46
+ end
47
+
48
+
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: deployment_pipeline
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Abhishek Parolkar
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rugged
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - '='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.16.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - '='
28
+ - !ruby/object:Gem::Version
29
+ version: 0.16.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: thor
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - '='
36
+ - !ruby/object:Gem::Version
37
+ version: 0.14.6
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - '='
44
+ - !ruby/object:Gem::Version
45
+ version: 0.14.6
46
+ - !ruby/object:Gem::Dependency
47
+ name: rdiscount
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: progressbar
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - '='
68
+ - !ruby/object:Gem::Version
69
+ version: 0.11.0
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - '='
76
+ - !ruby/object:Gem::Version
77
+ version: 0.11.0
78
+ - !ruby/object:Gem::Dependency
79
+ name: hpricot
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ description: Deployment Pipeline makes Continuous Deployment super easy.
95
+ email: abhishek@parolkar.com
96
+ executables:
97
+ - pipeline
98
+ extensions: []
99
+ extra_rdoc_files: []
100
+ files:
101
+ - Gemfile
102
+ - Gemfile.lock
103
+ - README.md
104
+ - bin/pipeline
105
+ - deployment_pipeline.gemspec
106
+ - extra/rugged-0.16.0.gem
107
+ - lib/code_repository.rb
108
+ - lib/pipeline.rb
109
+ - lib/tracker.rb
110
+ - sample.pipeline_config
111
+ - spec/code_repository_spec.rb
112
+ - spec/spec_helper.rb
113
+ - spec/tracker_spec.rb
114
+ homepage: https://github.com/parolkar/deployment_pipeline
115
+ licenses: []
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ! '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubyforge_project:
134
+ rubygems_version: 1.8.24
135
+ signing_key:
136
+ specification_version: 3
137
+ summary: Deployment Pipeline makes Continuous Deployment super easy.
138
+ test_files:
139
+ - spec/code_repository_spec.rb
140
+ - spec/spec_helper.rb
141
+ - spec/tracker_spec.rb