deployment_pipeline 0.0.0

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