CI / CD Process Update: Preparation and Planning

image

In 2020, it is probably quite difficult to find a project in a stack description that would not have one of the following words: IaC, microservices, kubernetes, docker, aws / azure / gcloud, blockchain, ML, VR and so on. And that's great! Progress does not stand still. We are growing, our projects are growing with us, more convenient and functional tools appear that solve modern problems.

Hello. So I wanted to start this article. But then I reviewed some things, talked with my colleagues, and realized that I would be wrong. There are still projects that are already 15+ years old, with managers and participants who are old believers, and accordingly these projects have an ancient technology stack that is quite difficult to maintain in an existing zoo. And for some reason, this project cannot be globally updated (the customer is a starover, there is no update, the project is very large, and the migration is delayed, or everyone is happy with everything), and you have to support it. Even more unpleasant when a similar project is still actively being developed. It's like a snowball. The customer and the public require features, the code requires delivery, the servers require attention and care ... But the bitpack - so in general, ceased to support mercury. Just such a case is proposed for consideration.

What will be considered: converting mercurial-git, moving CI from CruiseControl.NET to TeamCity, from git-deployment to Octopus with a small description of the whole process.

The text turned out a lot, so it will be divided into separate parts to facilitate perception. There will be a table of contents.
Part 1: what is, why it is not like, planning, a little bash. I would call this part near-technical.
Part 2: teamcity.
Part 3: octopus deploy.
Part 4: behind the scenes. Unpleasant moments, plans for the future, possibly FAQ. Most likely, it can also be called near-technical.

I would not call this a guide to repetition for many reasons: insufficient immersion in the project processes due to lack of time, insufficient practice of such things, a huge monolith in which all subprojects are closely intertwined, and a bunch of other nuances that make you burn, wonder, put up with them , but in no case do not please. And also, due to the features of the project (it is quite unique), some steps will be tailored exclusively for this case.

Classic introduction


We had a mercurial repository, 300+ (open!) Brunches, ccnet, another ccnet + git (for deployments), and a whole host of project modules with our own configs and individual clients, four environments and a whole host of pools in IIS, as well as cmd scripts, SQL, more than five hundred domains, two dozen builds, and active development in addition. Not that all this was necessary, but it worked, and it was inconvenient and long. The only thing that caused me concern was the presence of other projects requiring my attention. Nothing in the process of working with tasks of this magnitude is more dangerous than the lack of concentration and interruption.

I knew that sooner or later I would have to pay attention to other tasks, and that is why I spent a huge amount of time studying the existing infrastructure so that subsequently at least there would not be unresolved issues.

Unfortunately, I can’t give a full description of the project, due to the NDA, so general technical points will be considered. All names related to the project will also be painted over. I apologize for the black smear on the screenshots.

One of the key features of the project is that it has a number of modules that have one core, but differ in its configurations. There are also modules with a “special” approach, designed for resellers and especially large customers. One module can serve more than one client. A client should be understood as a separate organization or group of people who gain access to a specific module. Each client gets access on his own domain, has his own design and his own unique default settings. The client is identified by the domain that it uses.

The general scheme of this part of the project can be represented as follows:


As you can see, the core is used the same everywhere, and this can be used.

Reasons why the task arose to review and update CI / CD processes:

  1. CruiseControl.NET was used as a build system. Working with him is a dubious pleasure in principle. XML configs on multiple screens, a bunch of dependencies and linking configs to each other, lack of modern solutions to modern problems.
  2. Some developers (mainly leads) on the project must have access to build servers, and they sometimes like to change ccnet configs which should not be changed. Guess what happens next. You need to have a simple and convenient management of rights in the CI system, without taking away from the developers access to the server. Simply put, there is no text config - there is nowhere to climb with playful hands.
  3. - … CCNET, git. (. ).
  4. , . , . 
  5. - , — ( ) .
  6. . ? ? ? .
  7. Suboptimal use of resources and an outdated process of building and delivering code.

Figure to paragraph 3:


At the planning stage, it was decided to use Teamcity as a build system, and Octopus as a deployment system. The customer’s "iron" infrastructure has remained unchanged: separate dedicated servers for dev, test, staging and production, as well as reseller servers (mainly production environments).

The customer was provided with Proof of Concept on the example of one of the modules, an action plan was written, preparatory work was carried out (installation, configuration, etc.). There is probably no sense in describing this. How to install team city can be read on the official website. I will dwell separately on some formulated requirements for the new system:

  1. Easy maintenance with all the consequences (backups, updates, problem solving, if any).
  2. Universality. Ideally, develop a general scheme according to which all modules are assembled and delivered, and use it as a template.
  3. Minimize time for adding new build configurations and maintenance. Clients are added / removed. Sometimes it becomes necessary to make new setups. It is necessary that when creating a new module there should be no delay with the configuration of its delivery.

At about this point, we remembered the discontinuation of support for mercurial repositories with a bitbucket, and the requirement was added to transfer the repository to git while preserving branches and history of commits.

Preparation: repository conversion


It would seem that someone clearly solved this problem before us and instead of us. You just need to find a ready-made working solution. Fast export turned out to be not so fast. Plus, it didn't work. Unfortunately, there are no logs and screenshots left. The fact is that he could not. Bitbucket does not provide its own converter (but could). A couple more googled methods, and also by. I decided to write my own scripts, anyway this is not the only mercurial repository, it will come in handy in the future. Early developments will be presented here (because they still remain in the same form). The logic of work looks something like this:

  1. We take the extension mercurial hggit as a basis. 
  2. We get a list of all branches of the mercurial repository. 
  3. We convert branch names (thanks to mercurial and the developers for the spaces in the branch names, special thanks for the umlauts and other characters that add joy to life).
  4. Make bookmarks (hg bookmark) and push into the intermediate repository. What for? Because bitbucket, and because you cannot create a bookmark with the same name as the name of the branch (for example, staging). 
  5. In the new (already git) repository, remove the postfix from the branch name and migrate hgignore to gitignore. 
  6. Push to the main repository.

Listing commands according to points:

  1. $ cd /path/to/hg/repo
    $ cat << EOF >> ./.hg/hgrc
    [extensions]
    hggit=
    EOF
    
  2. $ hg branches > ../branches
    
  3. #!/usr/bin/env bash
    hgBranchList="./branches"
    sed -i 's/:.*//g' ${hgBranchList}
    symbolsToDelete=$(awk '{print $NF}' FS=" " ${hgBranchList} > sym_to_del.tmp)
     
    i=0
    while read hgBranch; do
      hgBranches[$i]=${hgBranch}
      i=$((i+1))
    done <${hgBranchList}
     
    i=0
    while read str; do
      strToDel[$i]=${str}
      i=$((i+1))
    done < ./sym_to_del.tmp
     
    for i in ${!strToDel[@]}
    do
      echo ${hgBranches[$i]} | sed "s/${strToDel[$i]}//" >> result.tmp
    done
     
    sed -i 's/[ \t]*$//' result.tmp
    sed 's/^/"/g' result.tmp > branches_hg
    sed -i 's/$/"/g' branches_hg
    sed 's/ /-/g' result.tmp > branches_git
    sed -i 's/-\/-/\//g' branches_git
    sed -i 's/-\//\//g' branches_git
    sed -i 's/\/-/\//g' branches_git
    sed -i 's/---/-/g' branches_git
    sed -i 's/--/-/g' branches_git
    rm sym_to_del.tmp
    rm result.tmp
    
  4. #!/usr/bin/env bash
    gitBranchList="./branches_git"
    hgBranchList="./branches_hg"
    hgRepo="/repos/reponame"
     
    i=0
    while read hgBranch; do
      hgBranches[$i]=${hgBranch}
      i=$((i+1))
    done <${hgBranchList}
     
    i=0
    while read gitBranch; do
      gitBranches[$i]=${gitBranch}
      i=$((i+1))
    done <${gitBranchList}
     
    cd ${hgRepo}
    for i in ${!gitBranches[@]}
    do
      hg bookmark -r "${hgBranches[$i]}" "${gitBranches[$i]}-cnv"
    done
     
    hg push git+ssh://git@bitbucket.org:username/reponame-temp.git
    echo "Done."
    
  5. #!/bin/bash
    # clone repo, run git branch -a, delete remotes/origin words to leave only branch names, delete -cnv postfix, delete default branch string because we can't delete it
    repo="/repos/repo"
    gitBranchList="./branches_git"
    defaultBranch="default-cnv"
    while read gitBranch; do   gitBranches[$i]=${gitBranch};   i=$((i+1)); done < $gitBranchList
    cd $repo
    for i in ${!gitBranches[@]}; do git checkout ${gitBranches[$i]}-cnv; done
    git checkout $defaultBranch
    for i in ${!gitBranches[@]}; do
        git branch -m ${gitBranches[$i]}-cnv ${gitBranches[$i]}
        git push origin :${gitBranches[$i]}-cnv ${gitBranches[$i]}
        git push origin -u ${gitBranches[$i]}
    done
    
  6. #!/bin/bash
    # clone repo, run git branch -a, delete remotes/origin words to leave only branch names, delete -cnv postfix, delete default branch string because we can't delete it
    repo="/repos/repo"
    gitBranchList="./branches_git"
    defaultBranch="default"
    while read gitBranch; do   gitBranches[$i]=${gitBranch};   i=$((i+1)); done < $gitBranchList
    cd $repo
    for i in ${!gitBranches[@]}; do
        git checkout ${gitBranches[$i]}
        sed -i '1d' .hgignore
        mv .hgignore .gitignore
        git add .
        git commit -m "Migrated ignore file"
    done
    

I’ll try to explain the meaning of some actions, namely the use of an intermediate repository. Initially, in the repository after conversion, the branch names contain the postfix “-cnv”. This is due to the hg bookmark feature. You need to remove this postfix, and also make gitignore files instead of hgignore. All this adds history, and therefore increases the size of the repository (and unreasonably). As another example, I can cite the following: try to create a repository, and push the first commit to 300M with the code into it. Then add it to gitignore, and push it without it. He will remain in history. Now try to remove it from the history (git filter-branch). With a certain number of commits, the resulting repository size will not decrease, but increase. This is solved by optimization, but it cannot be initiated on bitbucket.It is carried out only when importing the repository. Therefore, all rough operations are carried out with an intermediate, and then import into a new one is done. The size of the intermediate repository in the final was 1.15G, and the size of the resulting 350M. 

Conclusion


The entire migration process was divided into several stages, namely:

  • preparation (demo to the customer using an example of one build, software installation, repository conversion, updating existing ccnet configs);
  • CI / CD configuration for dev environment and its tests (the old system remains working in parallel);
  • CI / CD settings for the remaining environments (in parallel with the existing "good") and tests;
  • migration dev, staging and test;
  • Migrating production and transferring domains from old IIS instances to new ones.

The description of the successfully completed preparatory phase is currently being completed. Some grandiose success was not achieved here, except that the existing system did not break down and the mercurial repository in git was migrated. However, without a description of these processes, there will be no context for more technical parts. The next part will describe the process of setting up a CI system based on teamcity.

All Articles