{"id":474955,"date":"2024-05-21T11:22:42","date_gmt":"2024-05-21T10:22:42","guid":{"rendered":"https:\/\/blog.jetbrains.com\/?post_type=teamcity&#038;p=474955"},"modified":"2024-07-02T17:53:08","modified_gmt":"2024-07-02T16:53:08","slug":"deploying-to-multiple-targets","status":"publish","type":"teamcity","link":"https:\/\/blog.jetbrains.com\/zh-hans\/teamcity\/2024\/05\/deploying-to-multiple-targets","title":{"rendered":"Deploying to Multiple Targets With Ease"},"content":{"rendered":"\n<p>Have you ever been faced with a situation where you needed to deploy your system to many different environments? For many of us, this is probably just staging or production. When using TeamCity to do this, we just create a build chain as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"648\" height=\"82\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2024\/05\/teamcity-build-chain.png\" alt=\"\" class=\"wp-image-474957\"\/><\/figure>\n\n\n\n<p>But what if the number of deployment targets is higher, like ten or more?&nbsp;<\/p>\n\n\n\n<p>In this case, we might end up with a build chain ending with a set of <em>Publish to env*<\/em> deployment build configurations:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img decoding=\"async\" loading=\"lazy\" width=\"705\" height=\"191\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2024\/05\/teamcity-publish-to-env.png\" alt=\"\" class=\"wp-image-474969\" style=\"aspect-ratio:3.6910994764397906;width:705px;height:auto\"\/><\/figure>\n\n\n\n<p>In such cases, it\u2019s always better to have a combining build configuration that has dependencies on all the <em>Publish<\/em> tasks:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"961\" height=\"188\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2024\/05\/publish-different-env.png\" alt=\"\" class=\"wp-image-474980\"\/><\/figure>\n\n\n\n<p>Unfortunately, due to the current limitations of TeamCity, this combined <em>Publish<\/em> build configuration can\u2019t be both <a href=\"https:\/\/www.jetbrains.com\/help\/teamcity\/composite-build-configuration.html\" target=\"_blank\" rel=\"noopener\">composite<\/a> and deployment at the same time. We\u2019ve already had <a href=\"https:\/\/youtrack.jetbrains.com\/issue\/TW-54097\" target=\"_blank\" rel=\"noopener\">this feature requested<\/a> by some users. <\/p>\n\n\n\n<p>In this case, the build won\u2019t be shown as running if any of the <em>Publish to env*<\/em> dependencies are still running, which is a bit inconvenient.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Maintaining the configuration<\/h2>\n\n\n\n<p>If we\u2019re using the Kotlin DSL, we can program such a build chain relatively easily, as we can just create a loop that will generate all these <em>Publish to env*<\/em> build configurations. You can see an example of similar Kotlin DSL code in <a href=\"https:\/\/blog.jetbrains.com\/teamcity\/2023\/12\/simple-fork-join-framework-with-matrix-builds\/\">one of our previous posts<\/a>.<\/p>\n\n\n\n<p>If we\u2019re not using the Kotlin DSL, then we have to create the build configurations via the UI. Usually, in such cases, creating a <a href=\"https:\/\/www.jetbrains.com\/help\/teamcity\/build-configuration-template.html\" target=\"_blank\" rel=\"noopener\">template<\/a> first would be quite helpful. Maintaining this type of deployment configuration is much simpler with templates. However, even with a template, it still takes a lot of clicks to change anything in the configuration.<\/p>\n\n\n\n<p>Moreover, whenever we add a new environment, we still need to create a new build configuration and add a dependency to it from the <em>Publish<\/em> build configuration.&nbsp;<\/p>\n\n\n\n<p>All of this feels cumbersome, especially if the number of deployment targets is quite large. Is there really no better way?<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Using Matrix builds<\/h2>\n\n\n\n<p>If we\u2019ve already defined a template for the actual <em>Publish to env*<\/em> build configurations, then we\u2019ve already generalized our publishing scripts such that they now accept a parameter denoting a particular environment and perform publishing based on that. In this case, a much better approach would be to use the <a href=\"https:\/\/www.jetbrains.com\/help\/teamcity\/matrix-build.html\" target=\"_blank\" rel=\"noopener\">Matrix Build feature<\/a>.<\/p>\n\n\n\n<p>To do this, we can add a Matrix Build feature to the <em>Publish<\/em> build configuration with a matrix parameter with the desired name and an array of values:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"849\" height=\"428\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2024\/05\/matrix-build-config.png\" alt=\"\" class=\"wp-image-474991\"\/><\/figure>\n\n\n\n<p>The <em>Publish <\/em>build configuration won\u2019t need dependencies on <em>Publish to env* <\/em>build configurations anymore. Instead, it will have a dependency on <em>Test<\/em>. The deployment steps will be copied from a <em>Publish to env*<\/em> template.&nbsp;<\/p>\n\n\n\n<p>In regards to the configuration of the dependencies, the resulting build chain will look the same as in the beginning:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img decoding=\"async\" loading=\"lazy\" width=\"829\" height=\"69\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2024\/05\/build-package-test-publish.png\" alt=\"\" class=\"wp-image-475002\" style=\"aspect-ratio:12.014492753623188;width:841px;height:auto\"\/><\/figure>\n\n\n\n<p>However, if we trigger a Publish build configuration, its build will be transformed into a composite build with automatically generated dependencies on a set of parallel <em>[env*]<\/em> tasks, one for each value of the matrix parameter:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><img decoding=\"async\" loading=\"lazy\" width=\"920\" height=\"202\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2024\/05\/teamcity-composite-build.png\" alt=\"\" class=\"wp-image-475013\" style=\"aspect-ratio:4.554455445544554;width:838px;height:auto\"\/><\/figure>\n\n\n\n<p>If you look closer at this screenshot, you can see that the <em>Publish<\/em> build is shown as running even though its dependencies are not finished yet. So, apparently a build <em>can<\/em> be both composite and deployment at the same time. This looks like a nice solution, at least for some of the users who voted for the <a href=\"https:\/\/youtrack.jetbrains.com\/issue\/TW-54097\" target=\"_blank\" rel=\"noopener\">composite deployment build feature<\/a> ticket.<\/p>\n\n\n\n<p>With the Matrix Build feature, the settings become much simpler. Now, there\u2019s no need to have a template or a dedicated build configuration for each environment.&nbsp;<\/p>\n\n\n\n<p>At the same time, adding a new environment is as simple as adding a new value to the matrix parameter (provided that our scripts understand this value).&nbsp;<\/p>\n\n\n\n<p>The presentation of the results looks nicer too:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"608\" height=\"355\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2024\/05\/matrix-build-results.png\" alt=\"\" class=\"wp-image-475024\"\/><\/figure>\n\n\n\n<p>Matrix Builds also allow us to retain flexibility. For instance, if an <em>[env*]<\/em> build fails, we can retry this individual build or better yet, we can rerun the <em>Publish<\/em> build itself with the <em>Rebuild failed dependencies<\/em> option:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"890\" height=\"472\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2024\/05\/run-custom-build-teamcity.png\" alt=\"\" class=\"wp-image-475037\"\/><\/figure>\n\n\n\n<p>If our publish build requires an artifact prepared by the <em>Build package<\/em> task, we can just add an artifact dependency to the <em>Publish<\/em> build itself. When it is executed, the same dependency will be copied to the auto-generated <em>[env*]<\/em> builds so they all use the same artifact.<\/p>\n\n\n\n<p>Finally, these <em>[env*]<\/em> build configurations won\u2019t clutter our view as they won\u2019t be visible by default. If necessary, one can always navigate to them and analyze their history.<\/p>\n\n\n\n<p>Once again, the <a href=\"https:\/\/blog.jetbrains.com\/teamcity\/2023\/12\/simple-fork-join-framework-with-matrix-builds\/\">Matrix Build feature<\/a> proves to be quite useful. If you\u2019re using TeamCity 2023.11+, please give it a try and tell us how it can be improved!<\/p>\n","protected":false},"author":136,"featured_media":475064,"comment_status":"closed","ping_status":"closed","template":"","categories":[808],"tags":[83],"cross-post-tag":[],"acf":[],"_links":{"self":[{"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/teamcity\/474955"}],"collection":[{"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/teamcity"}],"about":[{"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/types\/teamcity"}],"author":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/users\/136"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/comments?post=474955"}],"version-history":[{"count":8,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/teamcity\/474955\/revisions"}],"predecessor-version":[{"id":490021,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/teamcity\/474955\/revisions\/490021"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/media\/475064"}],"wp:attachment":[{"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/media?parent=474955"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/categories?post=474955"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/tags?post=474955"},{"taxonomy":"cross-post-tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/zh-hans\/wp-json\/wp\/v2\/cross-post-tag?post=474955"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}