class Upgrade

Processes upgrade for a C# code repositoryy

end

Constants

BACKUP_BRANCH
UPGRADE_BRANCH
VERSION_UPGRADE_COMMIT
VERSION_UPGRADE_FAIL_BUILD_COMMIT

Public Class Methods

new(versions, rollback=false) click to toggle source
# File lib/upgrade.rb, line 14
def initialize versions, rollback=false
  @versions = versions
  @rollback = rollback
end

Public Instance Methods

Do(config_map, nuget_targets, is_local_run=false) click to toggle source
# File lib/upgrade.rb, line 47
def Do config_map, nuget_targets, is_local_run=false

  @config_map = config_map
  @repo_url = @config_map.metadata.repo_url
  @branch = @config_map.metadata.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.metadata
    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.metadata.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.metadata.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)
    clear_project_env_vars

    return false
  end
  clear_project_env_vars

  # 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.metadata.build_configuration_id, ENV['tc_un'], ENV['tc_pwd']
  end

  true
end
checkout_upgrade_branch() click to toggle source
# 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
clear_project_env_vars() click to toggle source
# File lib/upgrade.rb, line 288
def clear_project_env_vars
  ENV['AI_InstrumentationKey'] = '' if ENV.respond_to? 'AI_InstrumentationKey'
  ENV['AppClientId'] = '' if ENV.respond_to? 'AppClientId'
  ENV['ConfigSettingsTable'] = '' if ENV.respond_to? 'ConfigSettingsTable'
  ENV['env'] = '' if ENV.respond_to? 'env'
  ENV['RuntimePath'] = '' if ENV.respond_to? 'RuntimePath'
  ENV['ServiceName'] = '' if ENV.respond_to? 'ServiceName'
  ENV['SettingsAccount'] = '' if ENV.respond_to? 'SettingsAccount'
  ENV['SettingsAccountKey'] = '' if ENV.respond_to? 'SettingsAccountKey'
  ENV['should_update_settings_connstr'] = '' if ENV.respond_to? 'should_update_settings_connstr'
  ENV['unitestconnectionString'] = '' if ENV.respond_to? 'unitestconnectionString'
end
create_upgrade_tag() click to toggle source
# File lib/upgrade.rb, line 33
def create_upgrade_tag
  versioning = SemverVersioning.new
  semver_file = @config_map.metadata.semver.file
  if @config_map.metadata.should_publish_nuget && semver_file != nil && semver_file != GlobalConstants::EMPTY
    semver_file.capitalize
    ver_tag = versioning.get_current_version @config_map.metadata.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
id_from_hint_path(path) click to toggle source
# File lib/upgrade.rb, line 266
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) click to toggle source
# File lib/upgrade.rb, line 301
def increment_semver_if_publish is_local_run

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

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

  false
end
replace_package_versions(pkg_files) click to toggle source
# File lib/upgrade.rb, line 191
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) click to toggle source
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>

end

# File lib/upgrade.rb, line 226
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) click to toggle source
# File lib/upgrade.rb, line 139
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) click to toggle source
# File lib/upgrade.rb, line 275
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_branch() click to toggle source
# File lib/upgrade.rb, line 154
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) click to toggle source
# File lib/upgrade.rb, line 321
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