a2zdeploy 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a8017e6d8f0f0c6438495ba272799199501f109f
4
+ data.tar.gz: 4f0c0454bb8994e30a189a281b7ba8d14f7512e6
5
+ SHA512:
6
+ metadata.gz: 2e31bd3a670188f9648f35d469e5439171a3371a56bfb5f79564396d55363c87ee1d41e94254cd55540ed60b67b64ca051c74f0b550dcc0f9886853dbbefe104
7
+ data.tar.gz: c24aa5b993777be8b6e2556ca0ac816c73f4f0217899bcb98a077f682c9de527d8652d31d29e2bbd7a37c274c77ef7df1be31773fa3d09651ffcb6e82854ffbd
@@ -0,0 +1,12 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'a2zdeploy'
3
+ s.version = '1.0.0'
4
+ s.date = '2015-12-18'
5
+ s.summary = 'Given a project dependency chain, updates versions, builds, tests, manages service and cloud settings as well as build environment configuration'
6
+ s.description = 'Automated Upgrades Gem. Provides version upgrades, build and deployment configuration management'
7
+ s.authors = ['Suresh Batta']
8
+ s.email = 'subatta@hotmail.com'
9
+ s.files = Dir['{lib,spec}/**/*'] + ['a2zdeploy.gemspec', 'rakefile.rb']
10
+ s.homepage = 'http://rubygems.org/gems/a2zdeploy'
11
+ s.license = 'MIT'
12
+ end
@@ -0,0 +1,67 @@
1
+ =begin
2
+ Defines a simple dependency tree in a has map and allows accessing top and GlobalConstants::NEXT item in the tree.
3
+ - The keys 'GlobalConstants::ROOT, GlobalConstants::NEXT, GlobalConstants::PREVIOUS and GlobalConstants::PROJECT' are self-descriptive and symbols
4
+ - GlobalConstants::ROOT's GlobalConstants::PREVIOUS is always nil, so are all leaf node GlobalConstants::NEXT
5
+ {
6
+ "GlobalConstants::ROOT" => {
7
+ "GlobalConstants::PROJECT" => "Ontology",
8
+ "GlobalConstants::NEXT" => "FhirWalker",
9
+ "GlobalConstants::PREVIOUS" => nil,
10
+ "metadata" => "[json or another hash]"
11
+ },
12
+ "FhirWalker" => {
13
+ "GlobalConstants::PROJECT" => "Portal",
14
+ "GlobalConstants::NEXT" => "EventTracking",
15
+ "GlobalConstants::PREVIOUS" => "Ontology",
16
+ "metadata" => "[json or another hash]"
17
+ }
18
+ }
19
+ =end
20
+
21
+ require_relative 'globalconstants'
22
+
23
+ class DependencyTree
24
+
25
+ def initialize dependency_map
26
+ @dependency_map = dependency_map
27
+ end
28
+
29
+ def root
30
+ return nil if (@dependency_map.nil? || @dependency_map.class.to_s != GlobalConstants::HASH)
31
+
32
+ if @dependency_map.has_key?(GlobalConstants::ROOT)
33
+ if @dependency_map[GlobalConstants::ROOT].has_key? GlobalConstants::PROJECT
34
+ @dependency_map[GlobalConstants::ROOT][GlobalConstants::PROJECT]
35
+ end
36
+ end
37
+ end
38
+
39
+ def next_node current
40
+ return nil if current.to_s.strip.length == 0
41
+ return nil if @dependency_map.nil? || @dependency_map.class.to_s != GlobalConstants::HASH
42
+ return nil if @dependency_map[current] == nil
43
+
44
+ if @dependency_map[current].has_key? GlobalConstants::NEXT
45
+ @dependency_map[current][GlobalConstants::NEXT]
46
+ end
47
+ end
48
+
49
+ def previous_node current
50
+ return nil if current.to_s.strip.length == 0
51
+ return nil if (@dependency_map.nil? || @dependency_map.class.to_s != GlobalConstants::HASH)
52
+
53
+ if @dependency_map[current].has_key? GlobalConstants::PREVIOUS
54
+ @dependency_map[current][GlobalConstants::PREVIOUS]
55
+ end
56
+ end
57
+
58
+ def traverse
59
+ current = GlobalConstants::ROOT
60
+ yield @dependency_map[current][GlobalConstants::PROJECT] if @dependency_map.has_key?(current) && @dependency_map[current].has_key?(GlobalConstants::PROJECT)
61
+ while current != nil
62
+ current = next_node(current)
63
+ yield @dependency_map[current][GlobalConstants::PROJECT] if @dependency_map.has_key?(current) && @dependency_map[current].has_key?(GlobalConstants::PROJECT)
64
+ end
65
+ end
66
+
67
+ end
@@ -0,0 +1,140 @@
1
+ =begin
2
+ Provides API accessors for operations over github repos
3
+ This module has several methods that interface with Git and github
4
+ Unless otherwise returned specifically with a status,, commands that don't fail return an empty string - ''
5
+ =end
6
+
7
+ require 'addressable/uri'
8
+ require 'pathname'
9
+ require 'fileutils'
10
+ require_relative 'globalconstants'
11
+
12
+ module GithubApi
13
+
14
+ def GithubApi.CheckoutNewBranch branch
15
+ puts "Checking out new branch #{branch}..."
16
+ `git checkout -b #{branch}`
17
+ end
18
+
19
+ def GithubApi.CheckoutExistingBranch branch
20
+ puts "Checking out existing branch #{branch}..."
21
+ `git checkout #{branch}`
22
+
23
+ # check if checkout succeeded
24
+ actual_branch = `git rev-parse --abbrev-ref HEAD`
25
+
26
+ return actual_branch.chomp! == branch
27
+ end
28
+
29
+ def GithubApi.DoesBranchExist remote, branch
30
+ puts "Checking if branch #{branch} existing at #{remote}..."
31
+ `git ls-remote --heads #{remote} #{branch}`
32
+ end
33
+
34
+ def GithubApi.RebaseLocal branch
35
+ puts "Rebasing #{branch} with checked out branch..."
36
+ `git rebase #{branch}`
37
+ end
38
+
39
+ def GithubApi.CheckoutLocal branch
40
+ puts "Checking out local branch: #{branch}..."
41
+ `git checkout #{branch}`
42
+ end
43
+
44
+ def GithubApi.PushBranch remote, branch
45
+ puts "Pushing #{branch} to #{remote}..."
46
+ `git push #{remote} #{branch}`
47
+ end
48
+
49
+ def GithubApi.HaveLocalChanges
50
+ `git status -s`
51
+ end
52
+
53
+ def GithubApi.DeleteLocalBranch branch
54
+ `git branch -D #{branch}`
55
+ end
56
+
57
+ def GithubApi.DeleteRemoteBranch remote, branch
58
+ status = GithubApi.DoesBranchExist remote, branch
59
+ `git push #{remote} :#{branch}` if status.chomp! == GlobalConstants::EMPTY
60
+ end
61
+
62
+ def GithubApi.PullWithRebase remote, branch
63
+ `git pull --rebase #{@repo_url} #{@branch}`
64
+ end
65
+
66
+ def GithubApi.CommitChanges comment
67
+ status = `git add .`
68
+ if status == GlobalConstants::EMPTY
69
+ status = `git add -u`
70
+ else
71
+ return false
72
+ end
73
+ if status == GlobalConstants::EMPTY
74
+ status = `git commit -m "#{comment}"`
75
+ else
76
+ return false
77
+ end
78
+ return status != GlobalConstants::EMPTY
79
+ end
80
+
81
+ # we do NOT want to switch to parent folder but stay in current repo dir when we exit this method
82
+ def GithubApi.CheckoutRepoAfresh repo_url, branch
83
+ repo = GithubApi.ProjectNameFromRepo repo_url
84
+ return false if repo == GlobalConstants::EMPTY
85
+
86
+ # clear repo folder if it already exists
87
+ if File.directory? repo
88
+ puts 'Repository already exists! Cleaning...'
89
+ FileUtils.rm_rf repo
90
+ end
91
+
92
+ # clone to local
93
+ puts 'Cloning repo to local...'
94
+ begin
95
+ # also tests for valid repo, this will cout if cmd fails, no need for additional message
96
+ cmd_out = system "git clone #{repo_url}"
97
+ return false if cmd_out.to_s == 'false'
98
+ rescue
99
+ puts "Clone repo for #{repo_url} failed"
100
+ puts $!
101
+ return false
102
+ end
103
+
104
+ # checkout requested branch if it's not the default branch checked out when cloned
105
+ Dir.chdir repo
106
+ puts "Checking out requested branch: #{branch}"
107
+ `git fetch`
108
+
109
+ cmd_out = GithubApi.CheckoutExistingBranch branch
110
+
111
+ return cmd_out
112
+ end
113
+
114
+ def GithubApi.ProjectNameFromRepo repo_url
115
+ puts "Repo Url provided: #{repo_url}. Parsing..."
116
+ repo = GlobalConstants::EMPTY
117
+ begin
118
+ uri = Addressable::URI.parse repo_url
119
+ rescue
120
+ puts "repo_url: #{repo_url} parse failed"
121
+ return repo
122
+ end
123
+
124
+ if uri.nil?
125
+ puts 'Invalid repo_url provided'
126
+ return repo
127
+ end
128
+
129
+ directory = Pathname.new(uri.path).basename
130
+ if directory.nil?
131
+ puts 'No directory provided in repo_url'
132
+ return repo
133
+ end
134
+
135
+ repo = directory.to_s.gsub uri.extname, repo
136
+ puts "Repository name parsed: #{repo}"
137
+
138
+ repo
139
+ end
140
+ end
@@ -0,0 +1,9 @@
1
+ module GlobalConstants
2
+ HASH = 'Hash'
3
+ EMPTY = ''
4
+ SPEC = 'spec'
5
+ ROOT = 'root'
6
+ PREVIOUS = 'previous'
7
+ NEXT = 'next'
8
+ PROJECT = 'project'
9
+ end
@@ -0,0 +1,32 @@
1
+ require 'net/http'
2
+ require 'nokogiri'
3
+
4
+ def is_package_published packageName, packageVersion, timeout
5
+ packageLocationUri = "http://nuget2.relayhealth.com/nuget/Carnegie/Packages(Id='#{packageName}',Version='#{packageVersion}')"
6
+ counter = timeout
7
+ i = 0
8
+ found = false;
9
+
10
+ while i < counter
11
+ response = Net::HTTP.get_response(URI packageLocationUri)
12
+ xmldoc = Nokogiri::XML response.body
13
+ entry = xmldoc.css "entry id"
14
+ if entry.to_s.include? packageLocationUri
15
+ puts "Found #{packageName}-#{packageVersion}"
16
+ found = true
17
+ break
18
+ end
19
+ sleep 1
20
+ i += 1
21
+ end
22
+
23
+ if found == false
24
+ puts "Not found #{packageName}-#{packageVersion}"
25
+ end
26
+
27
+ return found
28
+ end
29
+
30
+ # Sample Usage
31
+ # a = is_package_published("RelayHealth.DataPlatform.Framework", "24.5.12", 60)
32
+ # puts a
@@ -0,0 +1,29 @@
1
+ =begin
2
+ Provides API accessors for TeamCity
3
+ =end
4
+
5
+ require 'net/http'
6
+ require 'builder'
7
+
8
+ def trigger_build buildConfigurationId, username, password
9
+ configContent = create_build_trigger_config buildConfigurationId
10
+ uri = URI.parse "http://teamcity.relayhealth.com"
11
+ http = Net::HTTP.new uri.host, uri.port
12
+ request = Net::HTTP::Post.new "/httpAuth/app/rest/buildQueue"
13
+ request.body = configContent
14
+ request.content_type = 'application/xml'
15
+ request.basic_auth username, password
16
+ response = http.request request
17
+ end
18
+
19
+
20
+ def create_build_trigger_config buildConfigurationId
21
+ xml = Builder::XmlMarkup.new :indent => 2
22
+ xml.build{
23
+ xml.triggeringOptions "cleanSources" => "true", "rebuildAllDependencies" => "true", "queueAtTop" => "true"
24
+ xml.buildType "id" => "#{buildConfigurationId}"
25
+ }
26
+ end
27
+
28
+ # Sample Usage
29
+ # trigger_build("DataPlatform_DataPlatformOntology_ADevelopBuildDataPlatformOntology_2", "username", "password")
@@ -0,0 +1,254 @@
1
+ =begin
2
+
3
+ Processes upgrade for a repository that is deployed as a cloud service.
4
+
5
+ Inputs:
6
+ 1. Project Manifest that describes repo/project to be processed
7
+ a. repo name, repo url, branch name
8
+ b.
9
+ 2. Change Manifest that describes what references have been upgraded due to framework upgrade.
10
+ Can be as simple as dictionary<string, string> of 'Package:[new version]'
11
+ example: When TestService is upgraded, we'll know what references have changed.
12
+ These can be used to replace packages.config and project references
13
+ =end
14
+
15
+ require 'nokogiri'
16
+ require 'json'
17
+ require_relative 'github_api'
18
+ require_relative 'globalconstants'
19
+ require_relative 'version'
20
+
21
+ class UpgradePackages
22
+
23
+ UPGRADE_BRANCH = 'upgrade'
24
+
25
+ # versions need to be passed in as they are based on a repo_url and branch that's source, likely DP solution
26
+ def initialize repo_url, branch, versions, config_map
27
+ @repo_url = repo_url
28
+ @branch = branch
29
+ @versions = versions
30
+ @config_map = config_map
31
+ end
32
+
33
+ def validate_inputs
34
+ return false if @repo_url.to_s.strip.length == 0
35
+ return false if @branch.to_s.strip.length == 0
36
+ if (@versions.nil? || @versions.class.to_s != GlobalConstants::HASH)
37
+ puts 'Version map must be a ' + GlobalConstants::HASH
38
+ return false
39
+ end
40
+ if (@config_map.nil? || @config_map.class.to_s != GlobalConstants::HASH)
41
+ puts 'Config map must be a ' + GlobalConstants::HASH
42
+ return false
43
+ end
44
+
45
+ # fail if env vars were not supplied
46
+ if (!@config_map.has_key? 'env_vars')
47
+ puts 'Environment variables not supplied. Cannot continue!'
48
+ return false
49
+ end
50
+
51
+ # fail if metadata was not supplied
52
+ if (!@config_map.has_key? 'metadata')
53
+ puts 'Metadata variables not supplied. Cannot continue!'
54
+ return false
55
+ end
56
+
57
+ # We should do more specific test of which environment variables are we expecting or which metatdata are we expecting
58
+ #if project publishes nuget we need to check if major /minor/patch incrmeented but not all 3
59
+ return true
60
+ end
61
+
62
+ def checkout_upgrade_branch
63
+ # obtain an upgrade branch
64
+ puts 'Getting upgrade branch...'
65
+ if (GithubApi.DoesBranchExist('origin', UPGRADE_BRANCH) != GlobalConstants::EMPTY)
66
+ puts 'Checking out existing upgrade branch...'
67
+ return false if !GithubApi.CheckoutExistingBranch UPGRADE_BRANCH == GlobalConstants::EMPTY
68
+ else
69
+ puts 'Checking out new upgrade branch...'
70
+ return false if !GithubApi.CheckoutNewBranch UPGRADE_BRANCH == GlobalConstants::EMPTY
71
+ end
72
+
73
+ return true
74
+ end
75
+
76
+ def Do
77
+ return false if !validate_inputs
78
+
79
+ # checkout repo and branch
80
+ return false if !GithubApi.CheckoutRepoAfresh @repo_url, @branch
81
+
82
+ return false if !checkout_upgrade_branch
83
+
84
+ # replace versions in package config files
85
+ pkg_files = Dir.glob '**/packages.config'
86
+ if (replace_package_versions(pkg_files) == false)
87
+ puts "Package version replacement failed."
88
+ return false
89
+ end
90
+
91
+ # replace versions in project references
92
+ proj_files = Dir.glob '**/*.csproj'
93
+ if (replace_project_versions(proj_files) == false)
94
+ puts "Project version replacement failed."
95
+ return false
96
+ end
97
+
98
+ # Check in manifest if project publish nuget? If yes, increment .semver
99
+ increment_semver_if_publish
100
+
101
+ # do rake build to test for compilation errors. This needs ENV vars set, passed in via config
102
+ set_project_env_vars @config_map['env_vars']
103
+ output = system 'rake'
104
+ if output.to_s == 'false'
105
+ puts '~~/\~~\/~~ Rake Error: There were errors during rake run. ~~\/~~/\~~'
106
+ # save state
107
+ GithubApi.CommitChanges( 'Versions updated, build failed')
108
+
109
+ return false
110
+ end
111
+
112
+ # see if any files changed and commit
113
+ git_status = GithubApi.HaveLocalChanges
114
+ if (git_status != nil || git_status != GlobalConstants::EMPTY)
115
+ puts 'Local version changes have been committed'
116
+ return false if !GithubApi.CommitChanges( 'Versions updated')
117
+ end
118
+
119
+ # rebase and push the branch
120
+ puts 'Rebasing and pushing...'
121
+ GithubApi.CheckoutLocal @branch
122
+ GithubApi.RebaseLocal UPGRADE_BRANCH
123
+
124
+ # if push fails, do a pull --rebase of the branch and fail the upgrade.
125
+ # Upstream commits need to accounted for and full upgrade cycle must be triggered
126
+ # Build failure email will inform concerned team
127
+ git_status = GithubApi.PushBranch(@repo_url, @branch) == GlobalConstants::EMPTY
128
+ if git_status
129
+ puts "Version upgrade changes have been rebased with #{@repo_url}/#{@branch} and pushed"
130
+ else
131
+ GithubApi.PullWithRebase @repo_url, @branch
132
+ GithubApi.PushBranch @repo_url, @branch
133
+ puts "Push after version upgrade failed for #{@repo_url}/#{@branch}. Pull with rebase done and pushed"
134
+ return false
135
+ end
136
+
137
+ # delete upgrade branch both local and remote
138
+ GithubApi.DeleteLocalBranch UPGRADE_BRANCH
139
+ GithubApi.DeleteRemoteBranch @repo_url, UPGRADE_BRANCH
140
+
141
+ # handle configuration changes. The settings file, update hash or TeamCity Params list is passed in through Ctor
142
+
143
+ true
144
+ end
145
+
146
+ def replace_package_versions pkg_files
147
+ begin
148
+ # iterate each package file, replace version numbers and save
149
+ pkg_files.each{ |file|
150
+ puts "Finding packages in: #{Dir.pwd}/#{file}..."
151
+ doc = Nokogiri::XML File.read(file)
152
+ nodes = doc.xpath "//*[@id]"
153
+ nodes.each { |node|
154
+ if (@versions.has_key?(node['id']))
155
+ node['version'] = @versions[node['id']]
156
+ #puts "#{node['id']} #{node['version']} -- #{@versions[node['id']]}"
157
+ end
158
+ }
159
+
160
+ File.write file, doc.to_xml
161
+ }
162
+ rescue
163
+ puts $!
164
+ return false
165
+ end
166
+ return true
167
+ end
168
+
169
+ =begin
170
+ Typical block of reference node change looks like:
171
+ Before:
172
+ <Reference Include="MassTransit, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b8e0e9f2f1e657fa, processorArchitecture=MSIL">
173
+ <HintPath>..\packages\MassTransit.3.0.14\lib\net45\MassTransit.dll</HintPath>
174
+ <Private>True</Private>
175
+ </Reference>
176
+ After: (file version removed, hint path version number updated)
177
+ <Reference Include="MassTransit">
178
+ <HintPath>..\packages\MassTransit.3.0.15\lib\net45\MassTransit.dll</HintPath>
179
+ <Private>True</Private>
180
+ </Reference>
181
+ =end
182
+ def replace_project_versions proj_files
183
+ begin
184
+ # iterate each package file, replace version numbers and save
185
+ proj_files.each{ |file|
186
+ puts "Updating references in: #{file}..."
187
+ doc = Nokogiri::XML File.read file
188
+ nodes = doc.search 'Reference'
189
+ nodes.each { |node|
190
+ ref_val = node['Include']
191
+ # grab the identifier
192
+ id = ref_val.split(',')[0]
193
+ # clean out file version
194
+ node['Include'] = id
195
+
196
+ # replace version in hint path
197
+ hint_path = node.search 'HintPath'
198
+ if hint_path && hint_path[0] != nil
199
+ hint_path_value = hint_path[0].children.to_s
200
+ # this identifier is not the same as the node['Include'] one.
201
+ # For ex., Runtime, Core and Storage assemblies will be referred to from within other packages like Management, Test etc
202
+ hint_path_id = id_from_hint_path hint_path_value
203
+ if @versions.has_key? hint_path_id
204
+ hint_path_parts = hint_path_value.split '\\'
205
+ hint_path_parts[2] = hint_path_id + '.' + @versions[hint_path_id]
206
+ hint_path[0].children = hint_path_parts.join '\\'
207
+ end
208
+ end
209
+ }
210
+ File.write file, doc.to_xml
211
+ }
212
+ rescue
213
+ puts $!
214
+ return false
215
+ end
216
+ return true
217
+ end
218
+
219
+ def id_from_hint_path path
220
+ name = path.split('\\')[2].split '.'
221
+ name_without_ver = GlobalConstants::EMPTY
222
+ name.all? {|i|
223
+ if i.to_i == 0
224
+ name_without_ver += i.to_s + '.'
225
+ end
226
+ }
227
+ name_without_ver.chomp '.'
228
+ end
229
+
230
+ def set_project_env_vars envs
231
+ envs.keys.each { | key |
232
+ ENV[key] = envs[key]
233
+ }
234
+ end
235
+
236
+ def increment_semver_if_publish
237
+ if !is_team_city_run
238
+ # local run
239
+ auto_update_local_semver
240
+ else
241
+ should_publish_nuget = @config_map['metadata']['ShouldPublishNuget'].downcase
242
+ if should_publish_nuget.eql? 'y'
243
+ semver_file = @config_map['metadata']['SemverFile']
244
+ if (semver_file != nil && semver_file != GlobalConstants::EMPTY)
245
+ semver_file.capitalize
246
+ end
247
+ semver_dimension = @config_map['metadata']['SemverDimension']
248
+ auto_update_semver @config_map['project'], @config_map['metadata']['SemverLocation'], semver_file, semver_dimension
249
+ else
250
+ puts '******** Project does not publish nuget.**********'
251
+ end
252
+ end
253
+ end
254
+ end