a2zdeploy 1.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.
@@ -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