Class: Upgrade

Inherits:
Object
  • Object
show all
Defined in:
lib/upgrade.rb

Overview

Processes upgrade for a C# code repositoryy

Constant Summary collapse

UPGRADE_BRANCH =
'upgrade'
BACKUP_BRANCH =
'upgradeBackupDoNotDelete'
VERSION_UPGRADE_COMMIT =
'Versions updated'
VERSION_UPGRADE_FAIL_BUILD_COMMIT =
'Versions updated, build failed'

Instance Method Summary collapse

Constructor Details

#initialize(versions, rollback = false) ⇒ Upgrade

Returns a new instance of Upgrade.



14
15
16
17
# File 'lib/upgrade.rb', line 14

def initialize versions, rollback=false
  @versions = versions
  @rollback = rollback
end

Instance Method Details

#checkout_upgrade_branchObject



19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/upgrade.rb', line 19

def checkout_upgrade_branch

  # obtain an upgrade branch
  if (GithubApi.DoesBranchExist('origin', UPGRADE_BRANCH) != GlobalConstants::EMPTY)
    puts 'Checking out existing upgrade branch...'
    return false if !GithubApi.CheckoutExistingBranch(UPGRADE_BRANCH) == GlobalConstants::EMPTY
  else
    puts 'Checking out new upgrade branch...'
    return false if !GithubApi.CheckoutNewBranch(UPGRADE_BRANCH) == GlobalConstants::EMPTY
  end

  return true
end

#create_upgrade_tagObject



33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/upgrade.rb', line 33

def create_upgrade_tag
  versioning = SemverVersioning.new
  semver_file = @config_map..semver.file
  if @config_map..should_publish_nuget && semver_file != nil && semver_file != GlobalConstants::EMPTY
    semver_file.capitalize
    ver_tag = versioning.get_current_version @config_map..semver.location, semver_file
    return "upgrade-#{ver_tag}"
  else
    utc_now = DateTime.now.utc
    date_tag = utc_now.strftime(DATE_FORMAT)
    return "upgrade-#{date_tag}"
  end
end

#Do(config_map, nuget_targets, is_local_run = false) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/upgrade.rb', line 47

def Do config_map, nuget_targets, is_local_run=false

  @config_map = config_map
  @repo_url = @config_map..repo_url
  @branch = @config_map..branch

  # checkout repo and branch
  return false if !GithubApi.CheckoutRepoAfresh @repo_url, @branch

  tag_creator = method(:create_upgrade_tag)

  # TODO: Need to check if node has failed before invoking rollback
  if @rollback
    puts "Starting Rollback..."
    rollback = new Rollback @repo_url, 'origin', @branch, BACKUP_BRANCH, @config_map.
    tag_creator = rollback.method(:create_rollback_tag)
    return false if !rollback.Do
  end

  # When upgrade branch exists we are coming from some failure state and dont need to backup the branch again
  if (GithubApi.DoesBranchExist('origin', UPGRADE_BRANCH) == GlobalConstants::EMPTY)
    GithubApi.CreateNewBranch(BACKUP_BRANCH, @branch)
    GithubApi.ForcePushBranch('origin', BACKUP_BRANCH)
  end

  # make upgrade branch
  return false if !checkout_upgrade_branch

  # use local nuget path for restore if provided
  set_local_nuget_target nuget_targets

  # replace versions in package config files
  puts GlobalConstants::UPGRADE_PROGRESS + 'Replacing package versions...'
  pkg_files = Dir.glob '**/packages.config'
  if !replace_package_versions(pkg_files)
    puts GlobalConstants::UPGRADE_PROGRESS + 'Package version replacement failed.'
    return false
  end

  # replace versions in project references
  puts GlobalConstants::UPGRADE_PROGRESS + 'Replacing project versions...'
  proj_files = Dir.glob '**/*.csproj'
  if !replace_project_versions(proj_files)
    puts GlobalConstants::UPGRADE_PROGRESS + 'Project version replacement failed.'
    return false
  end

  # Check in manifest if project publish nuget? If yes, increment .semver
  # QUESTION: Should this method increment semver even if there is no nuget published?
  puts GlobalConstants::UPGRADE_PROGRESS + 'Upgrading semver...'
  semver_inc_done = increment_semver_if_publish is_local_run
  nuget_targets << Dir.pwd + '/build_artifacts' if @config_map..should_publish_nuget

  # Tag commit
  recent_commit_hash = GithubApi.GetRecentCommitHash(@branch)
  #rollback_tag = tag_creator.call()

  #GithubApi.TagLocal(recent_commit_hash, rollback_tag, "Autoupgrade Tag")

  # do rake build to test for compilation errors. This needs ENV vars set, passed in via config
  set_project_env_vars @config_map..env_vars
  output = system 'rake'
  if output.to_s == 'false'
    puts GlobalConstants::UPGRADE_PROGRESS + ' Rake Error: There were errors during rake run.'
    # save state
    GithubApi.CommitChanges( VERSION_UPGRADE_FAIL_BUILD_COMMIT)

    return false
  end

  # update version map with nuget versions after build success
  if semver_inc_done
    nuget_versions = update_version_map '/build_artifacts/*.nupkg'
    @versions.merge! nuget_versions
    puts GlobalConstants::UPGRADE_PROGRESS + 'Semver upgraded. Version map updated.'
  end

  if is_local_run
    puts GlobalConstants::UPGRADE_PROGRESS + 'Local run. No branch update or teamcity build triggered'
  else
    puts GlobalConstants::UPGRADE_PROGRESS + 'Branch update in progress...'
    return false if !update_branch
    # kick off teamcity build
    puts GlobalConstants::UPGRADE_PROGRESS + 'Teamcity Project being triggered...'
    TeamCityApi.trigger_build @config_map..build_configuration_id, ENV['tc_un'], ENV['tc_pwd']
  end

  true
end

#id_from_hint_path(path) ⇒ Object



264
265
266
267
268
269
270
271
# File 'lib/upgrade.rb', line 264

def id_from_hint_path path
  name = path.split('\\')[2].split GlobalConstants::DOT
  name_without_ver = GlobalConstants::EMPTY
  name.all? {|i|
    name_without_ver += i.to_s + GlobalConstants::DOT if i.to_i == 0
  }
  name_without_ver.chomp GlobalConstants::DOT
end

#increment_semver_if_publish(is_local_run) ⇒ Object



286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/upgrade.rb', line 286

def increment_semver_if_publish is_local_run

  if is_local_run
    auto_update_semver @config_map.project_name, @config_map..semver.location, @config_map..semver.file, @config_map..semver.dimension
    return true
  else
    if @config_map..should_publish_nuget
      semver_file = @config_map..semver.file
      semver_file.capitalize if (semver_file != nil && semver_file != GlobalConstants::EMPTY)

      auto_update_semver @config_map.project_name, @config_map..semver.location, semver_file, @config_map..semver.dimension
      return true
    else
      puts GlobalConstants::UPGRADE_PROGRESS + 'Project does not publish nuget.'
    end
  end

  false
end

#replace_package_versions(pkg_files) ⇒ Object



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/upgrade.rb', line 189

def replace_package_versions pkg_files

  begin
    # iterate each package file, replace version numbers and save
    pkg_files.each{ |file|
      puts "Finding packages in: #{Dir.pwd}/#{file}..."
      doc = Nokogiri::XML File.read(file)
      nodes = doc.xpath "//*[@id]"
      nodes.each { |node|
        node['version'] = @versions[node['id']] if @versions.has_key?(node['id'])
      }
      File.write file, doc.to_xml
    }
  rescue
    puts $!
    return false
  end

  return true

end

#replace_project_versions(proj_files) ⇒ Object

Typical block of reference node change looks like:

Before:
<Reference Include="MassTransit, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b8e0e9f2f1e657fa, processorArchitecture=MSIL">
  <HintPath>..\packages\MassTransit.3.0.14\lib\net45\MassTransit.dll</HintPath>
  <Private>True</Private>
</Reference>
After: (file version removed, hint path version number updated)
<Reference Include="MassTransit">
  <HintPath>..\packages\MassTransit.3.0.15\lib\net45\MassTransit.dll</HintPath>
  <Private>True</Private>
</Reference>


224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/upgrade.rb', line 224

def replace_project_versions proj_files

  begin
    # iterate each package file, replace version numbers and save
    proj_files.each{ |file|
      puts "Updating references in: #{file}..."
      doc = Nokogiri::XML File.read file
      nodes = doc.search 'Reference'
      nodes.each { |node|
        ref_val = node['Include']
        # grab  the identifier
        id = ref_val.split(',')[0]
        # clean out file version
        node['Include'] = id

        # replace version in hint path
        hint_path = node.search 'HintPath'
        if hint_path && hint_path[0] != nil
          hint_path_value = hint_path[0].children.to_s
          # this identifier is not the same as the node['Include'] one.
          # For ex., Runtime, Core and Storage assemblies will be referred to from within other packages like Management, Test etc
          hint_path_id = id_from_hint_path hint_path_value
          if @versions.has_key? hint_path_id
            hint_path_parts = hint_path_value.split '\\'
            hint_path_parts[2] = hint_path_id + GlobalConstants::DOT + @versions[hint_path_id]
            hint_path[0].children = hint_path_parts.join '\\'
          end
        end
      }
      File.write file, doc.to_xml
    }
  rescue
    puts $!
    return false
  end

  return true

end

#set_local_nuget_target(nuget_targets) ⇒ Object



137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/upgrade.rb', line 137

def set_local_nuget_target nuget_targets
  num_paths = 1;
  nuget_targets.each { |target|
    nuget_targets_file = Dir.pwd + '/src/.nuget/Nuget.Config'
    doc = Nokogiri::XML(File.read nuget_targets_file)
    node_parent = doc.at_css 'packageSources'
    node = Nokogiri::XML::Node.new('add', doc)
    node['key'] = "local_nuget_source#{num_paths}"
    node['value'] = target
    node_parent.add_child node
    File.write nuget_targets_file, doc.to_xml
    num_paths  += 1
  }
end

#set_project_env_vars(envs) ⇒ Object



273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/upgrade.rb', line 273

def set_project_env_vars envs
  ENV['AI_InstrumentationKey'] = envs.AI_InstrumentationKey if envs.respond_to? 'AI_InstrumentationKey'
  ENV['AppClientId'] = envs.AppClientId if envs.respond_to? 'AppClientId'
  ENV['ConfigSettingsTable'] = envs.ConfigSettingsTable if envs.respond_to? 'ConfigSettingsTable'
  ENV['env'] = envs.env if envs.respond_to? 'env'
  ENV['RuntimePath'] = envs.RuntimePath if envs.respond_to? 'RuntimePath'
  ENV['ServiceName'] = envs.ServiceName if envs.respond_to? 'ServiceName'
  ENV['SettingsAccount'] = envs.SettingsAccount if envs.respond_to? 'SettingsAccount'
  ENV['SettingsAccountKey'] = envs.SettingsAccountKey if envs.respond_to? 'SettingsAccountKey'
  ENV['should_update_settings_connstr'] = envs.should_update_settings_connstr if envs.respond_to? 'should_update_settings_connstr'
  ENV['unitestconnectionString'] = envs.unitestconnectionString if envs.respond_to? 'unitestconnectionString'
end

#update_branchObject



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/upgrade.rb', line 152

def update_branch

  # see if any files changed and commit
  git_status = GithubApi.HaveLocalChanges
  git_status = git_status != nil || git_status != GlobalConstants::EMPTY

  puts GlobalConstants::UPGRADE_PROGRESS + "Local changes are present: #{git_status}"
  if git_status
    puts GlobalConstants::UPGRADE_PROGRESS + 'Local version changes are being committed'
    return false if !GithubApi.CommitChanges('Versions updated')
  end
  
  # rebase and push the branch
  puts GlobalConstants::UPGRADE_PROGRESS + 'Rebasing and pushing update branch...'
  GithubApi.CheckoutLocal @branch
  GithubApi.RebaseLocal UPGRADE_BRANCH

  # if push fails, do a pull --rebase of the branch and fail the upgrade.
  # Upstream commits need to accounted for and full upgrade cycle must be triggered
  # Build failure email will inform concerned team
  git_status = GithubApi.PushBranch(@repo_url, @branch) == GlobalConstants::EMPTY
  if git_status
    puts GlobalConstants::UPGRADE_PROGRESS + "Version upgrade changes have been rebased with #{@repo_url}/#{@branch} and pushed"
  else
    GithubApi.PullWithRebase @repo_url, @branch
    GithubApi.PushBranch @repo_url, @branch
    puts GlobalConstants::UPGRADE_PROGRESS + "Push after version upgrade failed for #{@repo_url}/#{@branch}. Pull with rebase done and pushed"
    return false
  end

  # delete upgrade branch both local and remote
  GithubApi.DeleteLocalBranch UPGRADE_BRANCH
  GithubApi.DeleteRemoteBranch @repo_url, UPGRADE_BRANCH

  true
end

#update_version_map(path) ⇒ Object



306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/upgrade.rb', line 306

def update_version_map path
  path = File.join(Dir.pwd, path)
  nugets = Dir.glob path
  versions = {}
  nugets.each { |nuget|
    full_name = File.basename nuget
    full_name = full_name.sub! '.nupkg', ''
    full_name = full_name.sub! '.symbols', '' if full_name.include? '.symbols'
    dot_pos = full_name.index GlobalConstants::DOT
    nuget_name = full_name[0..dot_pos-1]
    nuget_version = full_name[dot_pos+1..full_name.length]
    versions[nuget_name] = nuget_version
  }
  versions
end