Ten wpis to część 2 z 3 serii ATM Deployment
- Simple deployment process in middle-size project, #1: general overview
- Simple deployment process in middle-size project, #2: team city
- Simple deployment process in middle-size project, #3: Octopus
The previous article depicted general rules that we adhere to in our ATM project. This time I’d like to show how TeamCity server fits into our processes and how I configured it. Our experience with this setups shows that this is the probably simplest configuration that makes us feel comfortable and prepared for unexpected changes. It’s very flexible and subsequent requests from team leader and other users can be addressed in short time, without too much pain 🙂 Feel free to comment what you’d do better – as I stated in the beginning, I’m not DevOps master and happily embrace each piece of advice you could provide me with…
I’ve thought that probably best way I can assure the description of the configuration will be accurate and understandable is the gallery of screenshots with descriptions to each one. I hope this idea will work fine. If you think that different layout of the pictures would be more suitable for this article, also let me know in the comments.
1 z 39

We're starting with blank, freshly installed TeamCity (TC) server. It's installed on virtual machine. On the same machine, one build agent resides and is attached to the TC. All we need is an administrator account. It will allow us to access Administration pane in the top right corner. In order to do this, we have to open an internet browser, navigate to the TC main page (http://tdgdatcatm001/overview.html here), and press "Administration" link.

We can see here all tools needed to tune up the server, but first things first - we will start from "Create project" link.

As we know, we're working on the project called ATM. We have Github repository that contains the code, so probably TC would create the project based on that, but "Manually" is the simpler option so I chose this one. We don't have other projects so "Parent project" will be blank. Textbox "Name" should contain the name of the project and ProjectID will be set automatically (but we have the option to change it). A description is optional, may be left blank. In the end, press "Create" button to save the new project.

Now we can click the newly created project link. We'll switch to the project page where we'll be able to set up most important features. Right after we click the project, the page with the menu will be shown.

This menu, that usually can be found on the left side of the page, contains many different links, but we're interested in only a few of them. Simplicity first. Now we can go to the "Parameters" section.

Each project and build configuration can have its own set of parameters. But in our solution all of the projects/build configurations use the same set, so we're able to put them on the top-level (project level) where they will be accessible for every child project and build configuration. Today we have nine parameters that contain variables like security keys, URLs, paths and so on. Be noted that storing secret keys here is not an ideal solution, as they may be peeked by other administrators. However only I have admin privileges, so I decided to put here all information that I use in the projects. In the project we will be able to use the parameters in the form %NAME% - for example %ATM Nunit Console%. Then the server will change their occurrences to their values. Note that all parameters belonging to the project cannot be modified in the child items. I will back to this later.

So, we have parameters and now we will add our first build configuration just by pressing "Create build configuration" in General Settings section of the ATM project.

We would like to have at least two build configurations. The first one, to be triggered after each commit pushed to the repository. This would ensure that the added code can be built and all unit tests are green. Second build configuration will be more tricky, as we want it to be triggered once a day. TC would gather all the changes from the workday, build the code, deploy the libraries to test virtual machines and run all integration tests. This seems quite complex, but we will see that it's not that hard to set up.

So, we're adding the first configuration named "Build ATM & Tools". Textboxes on this screen are pretty self-explanatory: "Name", automatical "Build configuration ID", "Description". One thing that is more tricky here is "Build number format" which will describe all subsequent builds. As our ATM project is now in version 0.5, we set this option to 0.5.%build.counter%. Of course, build.counter is TC variable that depicts the number of current commit. Build counter will start from 1 by default but we can change if we have a specific reason. Now it's set to 292, but that's because I'm describing configuration process after some time from finishing it. If everything is OK, we can press Save and go to Version Control Settings pane.

On the screen, we can see one VCS attached, but for the fresh build configuration we can attach new one (or already set for other build configuration - VCS roots can be reusable in multiple projects).

This tab has plenty of parameters. The type depends on our versioning system. In ATM we use Git on Github server so we set appropriate type and named it "Github". Name, of course, may not be related to the VCS server name. ID is set automatically. Fetch URL and Push URL are just links to our repository. We set default branch to master - it's our main branch, so it was an obvious choice. Of course in other build configurations, for example for long living parallel branches, we can set it to another branch. Branch specification here may not be set minimalistic here, but it works perfectly fine. It states that we want to observe (and build) all branches but "mobimon" branch which is supported by another build configuration.

Next part of the tab from previous screen (edit/new VCS root). We selected default username style, submodules and set authentication method to "Password". Username and password are obvious 🙂 We don't provide the path to Git, as it's in PATH environment variable. The Clean policy "Always" makes building a bit longer but prevents from unexpected problems in the future.

Such information should be displayed if VCS root has been set up correctly. We can now go to Triggers tab.

When we press "Add new trigger" button, the new window will pop up where we can choose a type of the new trigger. This build configuration should be triggered after each commit pushed to the repository, so we select "Finish build trigger" and confirm the choice.

Another window is shown, where we select build configuration and branch filter. The latter can be modified manually or using the icon of a magic wand. We'd like all branches to be built, so +:* is a proper choice. Plus sign means "include" and asterisks means "all branches". In case we'd like to build only specific branches, we could list them one under another, for example: +:branch1 +:branch2 -:fix-*, which means that branch1/2 will be built, but all branches with names starting with "fix-" won't. Frankly speaking, I haven't done anything to resolve this warning about snapshots, as everything works OK anyway. So, we have trigger configured and can now go to Build Features section.

There are at least few useful build features predefined in TeamCity, and one can install many more. But we need only one for our purposes - AssemblyInfo patcher. This feature changes AssemblyInfo.cs files according to the defined pattern before the build starts and reverts the changes when it's finished. In order to add build feature, just press the button 🙂

... and define information that TC will put into AssemblyInfo files. In our project, it's just a build number which, as you remember is in format 0.5.%build.counter%. How can we benefit from this? It's simple. We have the service method that returns current version number basing on assembly version which, in turn, is based on information from AssemblyInfo.cs 🙂

AssemblyInfo.cs example looks like this. AssemblyVersion and AssemblyFileVersion are the attributes which values will be changed by TC. There is also AssemblyInformationalVersion attribute, but we don't use it. So, the initial version 1.0.0.0 each time will be changed to, for example, 0.5.245 which is extremely useful in the testing process. Much easier to say that bug occurs in version X, but not the Y, right?

Configuration parameters that we will use in our configuration are inherited from the parent project.

Finally, we can go to build steps configuration. Press "Add build step" to add the first stage of the configuration process.

This is something that we want to have in the end. Ten build steps related to our three solutions stored in the repository. For each solution, we need to get NuGet packages, rebuild it and run unit tests (if any). So steps 1-7 are related to c# projects. Additionally, we have one web solution that is handled by steps 8-10. I will describe in details only steps marked by the red rectangle, as the others are just copies of them.

So, how to get NuGet packages? I think NuGet installer is built in TC by default. So when "Add build step" was pressed, this runner type should be selected. A new window will be shown, where we can name the build step, confirm that we want it to be run only if the previous step finished successfully and, most important - provide a path to the solution file. We can just enter a relative path or click the icon on the right and choose the .sln file. That's basically it. Press save and add new build step.

When all packages are in place, we can build the solution. The easiest way is to select Visual Studio runner type, enter solution file path, use predefined target "Rebuild" and also predefined configuration "Release" that will work in most of the cases. The interesting addition here is "Octopus packaging" part. It triggers Octopack library referenced in the projects under ATM solution. Octopack prepares NuGet packages that can be later export to desired systems - Octopus in our case. This step is optional in this build configuration, as we don't use the prepared packages here. But in Nightly Deploy configuration that will be described later, it will make our lives easier. In order to setup this stage, just provide some marker that will differentiate subsequent packages - in our case build.number variable is such marker.

All projects built, now we can run unit tests. We use NUnit runner type. This is a plugin that can be easily installed (I will show plugins page later). The most important thing here is "Path to NUnit console tool". It can be found in Program Files (x86) folder. Our path is shown on slide No.6 - we store it in Configuration Parameters section. The runner will run tests from predefined libraries. The path can be very complex, but we have all tests stored in one library so we put its direct address here. If we would insert here many paths with wildcards (for example, if each project has the corresponding unit test project), then we should remember to put the path to the libraries that we don't want to run tests from. For example libraries from "obj" folder (as on the screen). We also should remember that references between test projects can lead to unexpected issues - as there may be copies of the libraries in folders where you don't expect to be.

Now npm part. Node Package Manager must be installed on agent's machine. Then we can select command line runner type and run "npm" command with parameter "cache clean -f", where "-f" means "force" and is a source of constant warnings - "are you sure to do this forcefully?" 🙂 I don't know whether this step is always needed, but we had some issues with builds failing because of npm, and this step makes the situation better.

Installation is very similar - just another set of parameters. Also "force" option - due to the same reasons as described on the previous screen.

And build, also the same. Type is changed to "Custom script" and I suspect that I wanted to merge all three steps into one, but now I'm not sure why exactly I changed it from the "Command line". Any way we can do this as we want. And that's it - everything is properly (more or less) set and is ready for the first commit 🙂

This is how our project should look like on the main page - one project, one build configuration, some builds for some branches.

We're ready for another build configuration. Its purpose is to gather all changes from today, build them, create deployment packages, trigger deployment process and run integration tests. It should be done after midnight. So, we can name it appropriately, prepare the same build number format as in the previous case and save it.

This build configuration has the different set of build steps. I won't describe those I depicted earlier and this time I will show only those related to packages, deployment and integration tests.

So, we have ATM solution built. Two projects - ATM.ReceivingServices, ATM.Service are packed by Octopack plugin I described before. Now we can easily add the new step to push ATM.ReceivingServices package to Octopus server. In order to do this, we have to choose OctopusDeploy runner type, provide it with URL to Octopus server (again we have it defined in configuration parameters) and with the password. The path to the package is a bit tricky - Octopack produces a package in \obj\octopacked folder with default name built from two strings - project name and identifier which, in our case is build.number variable. The extension is "nupkg" and knowing this we can put a valid path to the package in the appropriate field. Notice that field is named "Package paths" which means that we can pass multiple packages here. We will definitely change this in the future because now we have one step for each package which was initially made due to investigations and some issues.

In order to push Web project to Octopus server, we need to do everything in very similar fashion, but this time Package path leads to zipped contain of the web project folder. The statement under the text box describes how to use "=>" operator.

So, we have all packages in place - Octopus server. Now we can remotely trigger the deployment process. Octopus has quite impressive REST API through which it can be controlled, but we use tool dedicated for TC which makes the process easy. We have to just choose appropriate runner type, provide the runner with URL, password and Octopus specific data. The data consists of: name of the project defined in Octopus - ATM in this case. Release number. As you can see, we use the same variable across the TC and this is a good thing - simplifies everything. Channel is also something defined in Octopus, the same as Environment name. We will describe all these items in next article. Command line arguments are: "--package=ATM:%build.number% --packageVersion=%build.number%" I found this pattern on the Internet and haven't experimented with it - as it works, it's perfectly fine for me and should also work for you.

The last step is to run integration tests. The tab is very similar to the one with unit tests. We put this step as the last one because integration tests connect to the deployment machine, and we have to deploy the project first, run it and then run tests on the working system.

So, we have TC working. Project with two build configurations makes us feel quite comfortable and this simple setup suits us very well in the daily development routines. One thing that left is notification after a broken build. Simplest version, without customized rules, is shown on this screen. Nothing complicated, just provide valid credentials and after each broken build code, contributors will be notified 🙂

The last thing I'd like to show is the plugin pane. It's nothing special, but we need to know that TC has plenty of add-ins that can be installed just by uploading them into the plugins directory. In order to build configuration presented in this article, we need at least Octopus Deploy integration.