[{"data":1,"prerenderedAt":7202},["Reactive",2],{"navigation":3,"aAII9Cz3yR":204,"tags-DevOps":397},[4,192,200],{"title":5,"_path":6,"children":7,"icon":191},"Blog","/posts",[8,11,14,17,20,23,26,29,32,35,38,41,44,47,50,53,56,59,62,65,68,71,74,77,80,83,86,89,92,95,98,101,104,107,110,113,116,119,122,125,128,131,134,137,140,143,146,149,152,155,158,161,164,167,170,173,176,179,182,185,188],{"title":9,"_path":10},"Testing your API with REST Client","/posts/testing-your-api-with-rest-client",{"title":12,"_path":13},"HTML templating in Xamarin","/posts/html-templating-in-xamarin",{"title":15,"_path":16},"Goodbye Azure Portal, Welcome Azure CLI","/posts/welcome-azure-cli",{"title":18,"_path":19},"Coming across Gitpod","/posts/gitpod",{"title":21,"_path":22},"Handle token retrieval while querying an API","/posts/delegating-handler",{"title":24,"_path":25},"Clean up your local git branches.","/posts/cleaning-git-branches",{"title":27,"_path":28},"Automate configuration of Teams Tab SSO with PowerShell.","/posts/teams-sso-powershell",{"title":30,"_path":31},"How to do a technology watch? - Part 1","/posts/technology-watch-part1",{"title":33,"_path":34},"How to do a technology watch? - Part 2","/posts/technology-watch-part2",{"title":36,"_path":37},"You almost no longer need Key Vault references for Azure Functions.","/posts/azure-functions-custom-configuration",{"title":39,"_path":40},"How to do a technology watch? - Part 3","/posts/technology-watch-part3",{"title":42,"_path":43},"Forget DevOps, the future is already here!","/posts/devops-future",{"title":45,"_path":46},"Week 9, 2021 - Tips I learned this week","/posts/w09-2021-tips-learned-this-week",{"title":48,"_path":49},"Week 12, 2021 - Tips I learned this week","/posts/w12-2021-tips-learned-this-week",{"title":51,"_path":52},"Week 14, 2021 - Tips I learned this week","/posts/w14-2021-tips-learned-this-week",{"title":54,"_path":55},"Once upon a time in .NET","/posts/once-upon-a-time-in-dotnet",{"title":57,"_path":58},"Install your applications with winget","/posts/winget-import",{"title":60,"_path":61},"Customize your applications when installing them with winget","/posts/winget-override",{"title":63,"_path":64},"Week 22, 2021 - Tips I learned this week","/posts/w22-2021-tips-learned-this-week",{"title":66,"_path":67},"How to connect to an Azure SQL Database from C# using Azure AD","/posts/sqlclient-active-directory-authent",{"title":69,"_path":70},"Producing packages for Windows Package Manager","/posts/wingetcreate",{"title":72,"_path":73},"4 tips about GitHub Actions environment variables and contexts","/posts/github-actions-var-and-context",{"title":75,"_path":76},"AzureWebJobsStorage, the secret you don't need in your Function App.","/posts/azure-functions-without-azurewebjobsstorage",{"title":78,"_path":79},"ASP.NET Core - Lost in configuration","/posts/lost-in-configuration",{"title":81,"_path":82},"Week 39, 2021 - Tips I learned this week","/posts/w39-2021-tips-learned-this-week",{"title":84,"_path":85},"Week 41, 2021 - Tips I learned this week","/posts/w41-2021-tips-learned-this-week",{"title":87,"_path":88},"Migrating and open-sourcing my blog","/posts/migrating-blog",{"title":90,"_path":91},"Week 45, 2021 - Tips I learned this week","/posts/w45-2021-tips-learned-this-week",{"title":93,"_path":94},"Organize your GitHub stars with Astral","/posts/astral",{"title":96,"_path":97},"Pulumi with an Azure Blob Storage backend","/posts/pulumi-azure-backend",{"title":99,"_path":100},"IaC Hot Reload with Pulumi Watch","/posts/pulumi-watch",{"title":102,"_path":103},"Week 2, 2022 - Tips I learned this week","/posts/w02-2022-tips-learned-this-week",{"title":105,"_path":106},"Week 3, 2022 - Tips I learned this week","/posts/w03-2022-tips-learned-this-week",{"title":108,"_path":109},"Week 5, 2022 - Tips I learned this week","/posts/w05-2022-tips-learned-this-week",{"title":111,"_path":112},"How to provision an Azure SQL Database with Active Directory authentication","/posts/sqldatabase-active-directory-authent",{"title":114,"_path":115},"Why will I choose Pulumi over Terraform for my next project?","/posts/pulumi-vs-terraform",{"title":117,"_path":118},"Week 19, 2022 - Tips I learned this week","/posts/w19-2022-tips-learned-this-week",{"title":120,"_path":121},"Week 20, 2022 - Tips I learned this week","/posts/w20-2022-tips-learned-this-week",{"title":123,"_path":124},"Keeping secrets secure when using API Clients","/posts/http-clients-secrets",{"title":126,"_path":127},"What made me want to be a developer?","/posts/be-a-developer",{"title":129,"_path":130},"What can we do when stuck with a programming problem?","/posts/get-unstuck",{"title":132,"_path":133},"How did I automate the setup of my developer Windows laptop?","/posts/automate-developer-machine",{"title":135,"_path":136},"Discussion about API clients","/posts/http-clients",{"title":138,"_path":139},"Week 46, 2022 - Tips I learned this week","/posts/w46-2022-tips-learned-this-week",{"title":141,"_path":142},"When Pulumi met Nuke: a .NET love story","/posts/when-pulumi-met-nuke",{"title":144,"_path":145},"A year of learning and sharing - Dev Retro 2022","/posts/2022-retro",{"title":147,"_path":148},"Perform Dynamic Execution of an npm Package","/posts/pnpm-dlx",{"title":150,"_path":151},"Manage multiple Node.js versions","/posts/pnpm-env",{"title":153,"_path":154},"Introducing the Vue.js CI/CD series","/posts/vuecicd-introduction",{"title":156,"_path":157},"Execute commands using your project dependencies","/posts/pnpm-exec",{"title":159,"_path":160},"Vue.js CI/CD: Continuous Integration","/posts/vuecicd-ci",{"title":162,"_path":163},"Who is using pnpm?","/posts/pnpm-who-is-using",{"title":165,"_path":166},"Create an Azure-Ready GitHub Repository using Pulumi","/posts/azure-ready-github-repository",{"title":168,"_path":169},"Deploying to Azure from Azure DevOps without secrets","/posts/ado-workload-identity-federation",{"title":171,"_path":172},"Effortlessly Configure GitHub Repositories for Azure Deployment via OIDC","/posts/scripting-azure-ready-github-repository",{"title":174,"_path":175},"Playing with the .NET 8 Web API template","/posts/playing-with-dotnet8",{"title":177,"_path":178},"Another year of sharing and learning - Dev Retro 2023","/posts/2023-retro",{"title":180,"_path":181},"Week 4, 2024 - Tips I learned this week","/posts/w04-2024-tips-learned-this-week",{"title":183,"_path":184},"Using dependency injection with Azure .NET SDK","/posts/azure-sdk-di",{"title":186,"_path":187},"Having Fun With IT Event Calendars","/posts/it-event-calendars",{"title":189,"_path":190},"Call your Azure AD B2C protected API with authenticated HTTP requests from your JetBrains IDE","/posts/http-clients-oauth2","i-heroicons-newspaper",{"title":193,"_path":194,"children":195,"icon":199},"Goodies","/goodies",[196],{"title":197,"_path":198},"My Git Cheat Sheet","/goodies/gitcheatsheet","i-heroicons-gift-solid",{"title":201,"_path":202,"icon":203},"About","/about","i-heroicons-user-circle-solid",[205,207,209,211,214,217,220,223,226,229,231,234,237,240,242,244,247,250,253,255,258,261,264,267,270,273,276,279,282,285,287,289,292,294,297,300,303,305,308,310,313,316,319,322,325,327,329,332,335,338,341,344,347,350,353,356,359,361,363,366,369,372,375,377,380,383,385,388,391,394],[206,206],"tooling",[208,208],"vscode",[210,210],"rest",[212,213],"http","HTTP",[215,216],"razor","Razor",[218,219],"xamarin","Xamarin",[221,222],"templating","Templating",[224,225],"azure-cli","Azure CLI",[227,228],"azure","Azure",[230,230],"shell",[232,233],"github","GitHub",[235,236],"asp-net-core","ASP.NET Core",[238,239],"net",".NET",[241,241],"git",[243,243],"nushell",[245,246],"microsoft-teams","Microsoft Teams",[248,249],"powershell","PowerShell",[251,252],"azure-active-directory","Azure Active Directory",[254,254],"learning",[256,257],"azure-functions","Azure Functions",[259,260],"azure-key-vault","Azure Key Vault",[262,263],"configuration","Configuration",[265,266],"devops","DevOps",[268,269],"it","IT",[271,272],"tips-learned-this-week","tips learned this week",[274,275],"windows-terminal","Windows Terminal",[277,278],"azure-pipelines","Azure Pipelines",[280,281],"application-insights","Application Insights",[283,284],"azure-iot","Azure IoT",[286,286],"records",[288,288],"refit",[290,291],"development-box-setup","development box setup",[293,293],"winget",[295,296],"package-manager","package manager",[298,299],"azure-sql-database","Azure SQL Database",[301,302],"azure-sdk","Azure SDK",[304,304],"wingetcreate",[306,307],"github-actions","GitHub Actions",[309,309],"jq",[311,312],"pulumi","Pulumi",[314,315],"iac","IaC",[317,318],"azure-storage","Azure Storage",[320,321],"azure-signalr","Azure SignalR",[323,324],"visio","Visio",[326,326],"csharp",[328,328],"jest",[330,331],"statiq","Statiq",[333,334],"open-source","open source",[336,337],"visual-studio","Visual Studio",[339,340],"vue-js","Vue.js",[342,343],"azure-devops","Azure DevOps",[345,346],"vite","Vite",[348,349],"code-analysis","Code analysis",[351,352],"diagram","Diagram",[354,355],"terraform","Terraform",[357,358],"typescript","TypeScript",[360,360],"thoughts",[362,362],"pnpm",[364,365],"nuke","Nuke",[367,368],"pipelines","Pipelines",[370,371],"cicd","CI/CD",[373,374],"openid-connect","OpenID Connect",[376,376],"security",[378,379],"github-cli","GitHub CLI",[381,382],"microsoft-entra-id","Microsoft Entra ID",[384,384],"advent",[386,387],"finops","FinOps",[389,390],"anglesharp","AngleSharp",[392,393],"oauth2","OAuth2",[395,396],"azure-ad-b2c","Azure AD B2C",[398,753,1000,5437,6719],{"_path":160,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":159,"description":402,"date":403,"image":404,"badge":406,"tags":407,"canonical":408,"body":409,"_type":748,"_id":749,"_source":750,"_file":751,"_extension":752},"posts",false,"","Post about continuous integration with Vue.js.","2023-06-22T00:00:00.000Z",{"src":405},"/images/vue-cicd.webp",{"label":266},[340,371,266],"https://bordeauxcoders.com/vuejs-cicd-continuous-integration",{"type":410,"children":411,"toc":741},"root",[412,421,427,432,437,442,462,468,473,478,508,518,530,535,540,546,568,577,589,624,634,675,698,703,709,721],{"type":413,"tag":414,"props":415,"children":417},"element","h2",{"id":416},"why-are-we-talking-about-ci-in-the-first-place",[418],{"type":419,"value":420},"text","Why are we talking about CI in the first place?",{"type":413,"tag":422,"props":423,"children":424},"p",{},[425],{"type":419,"value":426},"When working on a project, you typically focus on a specific feature at a time, making changes on a dedicated branch for that feature. When it's time for you to integrate these modifications into the project's code base, the code base has likely evolved since you began working on your feature, as other team members have also pushed their work. That's why your code changes may introduce errors in the application you are developing.",{"type":413,"tag":422,"props":428,"children":429},{},[430],{"type":419,"value":431},"Regardless of that, while implementing your feature you might have broken some tests, added a security vulnerability, reduced code quality, or simply not adhered to all code conventions used by your team. Even if your colleagues review the code of your Pull Request, they can miss some of these issues. Nonetheless, it would be more efficient for such errors to be detected automatically, enabling people to concentrate their feedback on other aspects.",{"type":413,"tag":422,"props":433,"children":434},{},[435],{"type":419,"value":436},"Continuous Integration enables us to do precisely that: automatically identify potential issues and make the integration of new changes in a project's code base less error-prone.",{"type":413,"tag":422,"props":438,"children":439},{},[440],{"type":419,"value":441},"According to Microsoft:",{"type":413,"tag":443,"props":444,"children":445},"blockquote",{},[446],{"type":413,"tag":422,"props":447,"children":448},{},[449,451,460],{"type":419,"value":450},"Continuous integration (CI) is the process of automatically building and testing code every time a team member commits code changes to ",{"type":413,"tag":452,"props":453,"children":457},"a",{"href":454,"rel":455},"https://learn.microsoft.com/en-us/devops/develop/git/what-is-version-control",[456],"nofollow",[458],{"type":419,"value":459},"version control",{"type":419,"value":461},".",{"type":413,"tag":414,"props":463,"children":465},{"id":464},"what-are-the-steps-involved-in-a-ci-pipeline",[466],{"type":419,"value":467},"What are the steps involved in a CI pipeline?",{"type":413,"tag":422,"props":469,"children":470},{},[471],{"type":419,"value":472},"We often hear discussions about the \"Build pipeline\" and the \"Release pipeline\" as if building the application was the only task performed in a continuous integration pipeline. However, this is far from the truth; the \"Build\" is an important step, but not the only one.",{"type":413,"tag":422,"props":474,"children":475},{},[476],{"type":419,"value":477},"Up until now, we have talked a lot about continuous integration for projects in general but nothing specific for Vue.js. Why is that? Because the steps for continuous integration of a Vue.js project are the same as for any other project:",{"type":413,"tag":479,"props":480,"children":481},"ul",{},[482,488,493,498,503],{"type":413,"tag":483,"props":484,"children":485},"li",{},[486],{"type":419,"value":487},"Install dependencies",{"type":413,"tag":483,"props":489,"children":490},{},[491],{"type":419,"value":492},"Build the application",{"type":413,"tag":483,"props":494,"children":495},{},[496],{"type":419,"value":497},"Perform code quality static analysis",{"type":413,"tag":483,"props":499,"children":500},{},[501],{"type":419,"value":502},"Perform security analysis",{"type":413,"tag":483,"props":504,"children":505},{},[506],{"type":419,"value":507},"Run tests",{"type":413,"tag":509,"props":510,"children":512},"callout",{"icon":511},"i-heroicons-chat-bubble-left-20-solid",[513],{"type":413,"tag":422,"props":514,"children":515},{},[516],{"type":419,"value":517},"It's good to know that, as part of the build step, an executable artifact is often generated, and then used (in the same pipeline or a CD pipeline) to deploy the application in an environment.",{"type":413,"tag":422,"props":519,"children":520},{},[521],{"type":413,"tag":522,"props":523,"children":529},"img",{"alt":524,"className":525,"src":528},"Diagram of a CI/CD pipeline.",[526,527],"rounded-lg","mx-auto","/posts/images/vuecicd_ci_pipeline.png",[],{"type":413,"tag":422,"props":531,"children":532},{},[533],{"type":419,"value":534},"Depending on your project, preferences, and available services, your continuous integration process may vary, but it should include these steps, regardless of the tools you use within them.",{"type":413,"tag":422,"props":536,"children":537},{},[538],{"type":419,"value":539},"There might be additional steps in your Continuous Integration pipeline, but the ones mentioned are the primary ones. Moreover, security is not an optional step; it should be an integral part of your continuous integration.",{"type":413,"tag":414,"props":541,"children":543},{"id":542},"leveraging-packagejson-for-your-ci-setup",[544],{"type":419,"value":545},"Leveraging package.json for your CI setup",{"type":413,"tag":422,"props":547,"children":548},{},[549,551,558,560,566],{"type":419,"value":550},"When you create a Vue.js project using ",{"type":413,"tag":552,"props":553,"children":555},"code",{"className":554},[],[556],{"type":419,"value":557},"create-vue",{"type":419,"value":559},", the generated ",{"type":413,"tag":552,"props":561,"children":563},{"className":562},[],[564],{"type":419,"value":565},"package.json",{"type":419,"value":567}," file will contain several npm scripts:",{"type":413,"tag":422,"props":569,"children":570},{},[571],{"type":413,"tag":522,"props":572,"children":576},{"alt":573,"className":574,"src":575},"Screenshot of the npm scripts section of a package.json file.",[526,527],"/posts/images/vuecicd_ci_packagejson_0.png",[],{"type":413,"tag":422,"props":578,"children":579},{},[580,582,587],{"type":419,"value":581},"You can observe that some of these scripts precisely correspond to the necessary steps for the continuous integration pipeline, such as build and unit tests. We will discuss each of them in future articles but the npm scripts in the default ",{"type":413,"tag":552,"props":583,"children":585},{"className":584},[],[586],{"type":419,"value":557},{"type":419,"value":588}," template are definitively a good starting point to set up your CI.",{"type":413,"tag":422,"props":590,"children":591},{},[592,594,600,602,607,609,615,617,622],{"type":419,"value":593},"You can see that packages used in the npm scripts are specified in the ",{"type":413,"tag":552,"props":595,"children":597},{"className":596},[],[598],{"type":419,"value":599},"devDependencies",{"type":419,"value":601}," section of the ",{"type":413,"tag":552,"props":603,"children":605},{"className":604},[],[606],{"type":419,"value":565},{"type":419,"value":608}," file. That means these packages will be available to use locally or in a CI server after executing the ",{"type":413,"tag":552,"props":610,"children":612},{"className":611},[],[613],{"type":419,"value":614},"pnpm install",{"type":419,"value":616}," command. As part of the CI, other packages may also be needed, so you should include them in the ",{"type":413,"tag":552,"props":618,"children":620},{"className":619},[],[621],{"type":419,"value":599},{"type":419,"value":623}," section as well.",{"type":413,"tag":422,"props":625,"children":626},{},[627],{"type":413,"tag":522,"props":628,"children":633},{"alt":629,"className":630,"src":631,"width":632},"Screenshot of the devdependencies section of a package.json file.",[526,527],"/posts/images/vuecicd_ci_packagejson_1.png",1000,[],{"type":413,"tag":422,"props":635,"children":636},{},[637,639,644,646,657,659,665,667,673],{"type":419,"value":638},"In your CI pipeline, I think it's a good idea to directly execute the npm scripts of the ",{"type":413,"tag":552,"props":640,"children":642},{"className":641},[],[643],{"type":419,"value":565},{"type":419,"value":645}," file rather than specifying the packages you want to run along with their corresponding flags and parameters. You can accomplish this by using the ",{"type":413,"tag":452,"props":647,"children":650},{"href":648,"rel":649},"https://pnpm.io/fr/cli/run",[456],[651],{"type":413,"tag":552,"props":652,"children":654},{"className":653},[],[655],{"type":419,"value":656},"pnpm run",{"type":419,"value":658}," command like so: ",{"type":413,"tag":552,"props":660,"children":662},{"className":661},[],[663],{"type":419,"value":664},"pnpm run build",{"type":419,"value":666}," or ",{"type":413,"tag":552,"props":668,"children":670},{"className":669},[],[671],{"type":419,"value":672},"pnpm build",{"type":419,"value":674}," (all npm scripts are aliased by pnpm by default). Of course, you'll need to add any missing npm scripts required for your CI. There are several benefits to this approach:",{"type":413,"tag":479,"props":676,"children":677},{},[678,683,688,693],{"type":413,"tag":483,"props":679,"children":680},{},[681],{"type":419,"value":682},"It simplifies the CI pipeline and makes it easier to read",{"type":413,"tag":483,"props":684,"children":685},{},[686],{"type":419,"value":687},"you won't have to modify your pipeline when you change something in an npm script (whether it's the package you use or just a parameter)",{"type":413,"tag":483,"props":689,"children":690},{},[691],{"type":419,"value":692},"the steps in your CI pipeline will be more consistent across projects (including both Vue.js and non-Vue.js projects) if you always use the same npm script names",{"type":413,"tag":483,"props":694,"children":695},{},[696],{"type":419,"value":697},"the same commands will be executed with the same parameters, whether locally or on a CI server",{"type":413,"tag":422,"props":699,"children":700},{},[701],{"type":419,"value":702},"It's important to note that you should not wait for a CI pipeline execution to detect issues in your code. The sooner you identify and resolve problems, the better. Before pushing your changes, you should run the npm scripts that test your code and perform static analysis on it.",{"type":413,"tag":414,"props":704,"children":706},{"id":705},"wrapping-it-up",[707],{"type":419,"value":708},"Wrapping it up",{"type":413,"tag":422,"props":710,"children":711},{},[712,714,719],{"type":419,"value":713},"Setting up a Continuous Integration pipeline for your Vue.js project is essential for preventing issues, maintaining code quality, ensuring security, and streamlining the development process. By leveraging the npm scripts of the ",{"type":413,"tag":552,"props":715,"children":717},{"className":716},[],[718],{"type":419,"value":565},{"type":419,"value":720}," file you can simplify your CI pipeline and ensure consistency both locally and on the CI server, as well as across your projects.",{"type":413,"tag":422,"props":722,"children":723},{},[724,726,732,733,739],{"type":419,"value":725},"Future articles in this series will delve into the details of various stages of a continuous integration pipeline (such as using ",{"type":413,"tag":552,"props":727,"children":729},{"className":728},[],[730],{"type":419,"value":731},"vue-tsc",{"type":419,"value":666},{"type":413,"tag":552,"props":734,"children":736},{"className":735},[],[737],{"type":419,"value":738},"eslint",{"type":419,"value":740}," for static analysis) and their implementation in GitLab CI or GitHub Actions pipelines.",{"title":401,"searchDepth":742,"depth":742,"links":743},2,[744,745,746,747],{"id":416,"depth":742,"text":420},{"id":464,"depth":742,"text":467},{"id":542,"depth":742,"text":545},{"id":705,"depth":742,"text":708},"markdown","content:1.posts:51.vuecicd-ci.md","content","1.posts/51.vuecicd-ci.md","md",{"_path":154,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":153,"description":754,"date":755,"image":756,"badge":757,"tags":758,"canonical":759,"body":760,"_type":748,"_id":998,"_source":750,"_file":999,"_extension":752},"This is the first article of the Vue.js CI/CD series. It will be the opportunity to explain the purpose of the series and the topics we plan to cover.","2023-06-01T00:00:00.000Z",{"src":405},{"label":266},[340,371,266],"https://bordeauxcoders.com/introducing-the-vuejs-cicd-series",{"type":410,"children":761,"toc":989},[762,766,772,777,782,788,793,825,830,837,842,847,853,858,876,882,887,904,912,925,943,960,984],{"type":413,"tag":422,"props":763,"children":764},{},[765],{"type":419,"value":754},{"type":413,"tag":414,"props":767,"children":769},{"id":768},"why-this-series",[770],{"type":419,"value":771},"Why this series?",{"type":413,"tag":422,"props":773,"children":774},{},[775],{"type":419,"value":776},"We delved deeply into CI/CD for Vue.js when preparing a DevOps practices course for students in engineering school. The course wasn't directly related to Vue.js; however, we chose to use a Vue.js application for hands-on exercises focused on implementing CI/CD pipelines. Through this process, we gained valuable insights that we now wish to share.",{"type":413,"tag":422,"props":778,"children":779},{},[780],{"type":419,"value":781},"While there are numerous blog posts on Vue.js, not many articles specifically address setting up CI/CD pipelines for Vue.js projects. Yet, having proper continuous integration and automating deployments are two aspects that should not be neglected in a project. That's the main reason why we decided to write this Vue.js CI/CD series.",{"type":413,"tag":414,"props":783,"children":785},{"id":784},"what-are-we-going-to-talk-about",[786],{"type":419,"value":787},"What are we going to talk about?",{"type":413,"tag":422,"props":789,"children":790},{},[791],{"type":419,"value":792},"As you can expect, we will cover the usual topics:",{"type":413,"tag":479,"props":794,"children":795},{},[796,801,806,811,816,820],{"type":413,"tag":483,"props":797,"children":798},{},[799],{"type":419,"value":800},"package management",{"type":413,"tag":483,"props":802,"children":803},{},[804],{"type":419,"value":805},"build & artifacts",{"type":413,"tag":483,"props":807,"children":808},{},[809],{"type":419,"value":810},"static analysis",{"type":413,"tag":483,"props":812,"children":813},{},[814],{"type":419,"value":815},"testing",{"type":413,"tag":483,"props":817,"children":818},{},[819],{"type":419,"value":376},{"type":413,"tag":483,"props":821,"children":822},{},[823],{"type":419,"value":824},"deployment",{"type":413,"tag":422,"props":826,"children":827},{},[828],{"type":419,"value":829},"Examples will be shown using different CI/CD platforms and cloud services.",{"type":413,"tag":831,"props":832,"children":834},"h3",{"id":833},"cicd-platforms",[835],{"type":419,"value":836},"CI/CD platforms",{"type":413,"tag":422,"props":838,"children":839},{},[840],{"type":419,"value":841},"We can't cover all the CI/CD platforms so we will focus on GitHub Actions and GitLab CI.",{"type":413,"tag":422,"props":843,"children":844},{},[845],{"type":419,"value":846},"Even though each platform has its unique features, the majority of the concepts we will discuss can be applied to other platforms as well. So, don't stop reading the series just because you are using a different platform 😉.",{"type":413,"tag":831,"props":848,"children":850},{"id":849},"cloud-services",[851],{"type":419,"value":852},"Cloud services",{"type":413,"tag":422,"props":854,"children":855},{},[856],{"type":419,"value":857},"There are numerous hosting options for a Vue.js application, and we will demonstrate how to deploy an application on at least the following platforms:",{"type":413,"tag":479,"props":859,"children":860},{},[861,866,871],{"type":413,"tag":483,"props":862,"children":863},{},[864],{"type":419,"value":865},"Azure Static Web App",{"type":413,"tag":483,"props":867,"children":868},{},[869],{"type":419,"value":870},"Vercel",{"type":413,"tag":483,"props":872,"children":873},{},[874],{"type":419,"value":875},"Netlify",{"type":413,"tag":414,"props":877,"children":879},{"id":878},"which-sample-application-will-we-be-using",[880],{"type":419,"value":881},"Which sample application will we be using?",{"type":413,"tag":422,"props":883,"children":884},{},[885],{"type":419,"value":886},"This series aims to discuss CI/CD for Vue.js applications so that anyone can learn how to set up a CI/CD pipeline for their Vue.js project. That's why we will use the sample code from the basic application generated when creating a new Vue.js project.",{"type":413,"tag":422,"props":888,"children":889},{},[890,892,902],{"type":419,"value":891},"And to be clear, when you start a new Vue.js project you don't want to use the Vue CLI because it is in maintenance mode. Instead, you should use ",{"type":413,"tag":452,"props":893,"children":896},{"href":894,"rel":895},"https://github.com/vuejs/create-vue",[456],[897],{"type":413,"tag":552,"props":898,"children":900},{"className":899},[],[901],{"type":419,"value":557},{"type":419,"value":903}," which is based on Vite and is the recommended way of scaffolding a Vue.js project.",{"type":413,"tag":509,"props":905,"children":906},{"icon":511},[907],{"type":413,"tag":422,"props":908,"children":909},{},[910],{"type":419,"value":911},"I think it's important to mention it because I still see new blog posts talking about creating new projects using Vue CLI.",{"type":413,"tag":422,"props":913,"children":914},{},[915,917,923],{"type":419,"value":916},"So nothing specific in the code of the application we will build and deploy, just the basic things you get when you run the ",{"type":413,"tag":552,"props":918,"children":920},{"className":919},[],[921],{"type":419,"value":922},"pnpm create vue@latest",{"type":419,"value":924}," command with:",{"type":413,"tag":479,"props":926,"children":927},{},[928,933,938],{"type":413,"tag":483,"props":929,"children":930},{},[931],{"type":419,"value":932},"TypeScript enabled ➡️ it's 2023, I don't see any valid reason why to choose Vanilla JS instead of TypeScript so if you are not using TypeScript you probably should",{"type":413,"tag":483,"props":934,"children":935},{},[936],{"type":419,"value":937},"Vitest enabled ➡️ the vite-native unit test framework you want to use to test your code",{"type":413,"tag":483,"props":939,"children":940},{},[941],{"type":419,"value":942},"ESLint enabled ➡️ because static analysis should be part of your Continuous Integration pipeline",{"type":413,"tag":422,"props":944,"children":945},{},[946,948,958],{"type":419,"value":947},"The last thing to mention: we will use the latest version of ",{"type":413,"tag":452,"props":949,"children":952},{"href":950,"rel":951},"https://pnpm.io/",[456],[953],{"type":413,"tag":552,"props":954,"children":956},{"className":955},[],[957],{"type":419,"value":362},{"type":419,"value":959}," to manage dependencies. Our preferred package manager is pnpm for various reasons, but the primary one is its remarkable speed!",{"type":413,"tag":509,"props":961,"children":962},{"icon":511},[963],{"type":413,"tag":422,"props":964,"children":965},{},[966,968,974,976,983],{"type":419,"value":967},"You can check the ",{"type":413,"tag":452,"props":969,"children":971},{"href":950,"rel":970},[456],[972],{"type":419,"value":973},"pnpm website",{"type":419,"value":975}," to read more about pnpm or have a look at our ",{"type":413,"tag":452,"props":977,"children":980},{"href":978,"rel":979},"https://bordeauxcoders.com/series/pnpm-101",[456],[981],{"type":419,"value":982},"pnpm 101 series",{"type":419,"value":461},{"type":413,"tag":422,"props":985,"children":986},{},[987],{"type":419,"value":988},"We hope you will have a great time learning about CI/CD for Vue.js application. See you in the next article.",{"title":401,"searchDepth":742,"depth":742,"links":990},[991,992,997],{"id":768,"depth":742,"text":771},{"id":784,"depth":742,"text":787,"children":993},[994,996],{"id":833,"depth":995,"text":836},3,{"id":849,"depth":995,"text":852},{"id":878,"depth":742,"text":881},"content:1.posts:49.vuecicd-introduction.md","1.posts/49.vuecicd-introduction.md",{"_path":142,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":141,"description":1001,"lead":1002,"date":1003,"image":1004,"badge":1006,"tags":1007,"body":1008,"_type":748,"_id":5435,"_source":750,"_file":5436,"_extension":752},"Today is a great time to be a developer:","Pushing an ASP.NET Core API to Azure using .NET from provisioning to deployment","2022-12-18T00:00:00.000Z",{"src":1005},"/images/trees_1.jpg",{"label":266},[239,365,312,368,315,266,228],{"type":410,"children":1009,"toc":5406},[1010,1014,1037,1042,1047,1052,1058,1063,1068,1073,1079,1084,1094,1099,1120,1129,1134,1143,1148,1157,1166,1171,1177,1183,1188,1206,1211,1234,1240,1245,1254,1259,1291,1300,1305,1322,1336,1355,1364,1370,1375,1384,1389,1394,1410,1416,1422,1427,1436,1458,1466,1471,1477,1482,1714,1719,1875,1881,1903,2030,2036,2065,2271,2277,2297,2615,2632,2661,2730,2749,2755,2761,2782,2791,2799,2811,2817,2822,2847,3500,3505,3513,3519,3524,3537,3551,3671,3688,3702,3996,4009,4015,4028,4307,4313,4326,4339,4552,4557,5178,5195,5200,5205,5211,5216,5225,5238,5247,5255,5260,5269,5274,5283,5289,5295,5300,5332,5346,5352,5357,5379,5384,5389,5395,5400],{"type":413,"tag":422,"props":1011,"children":1012},{},[1013],{"type":419,"value":1001},{"type":413,"tag":479,"props":1015,"children":1016},{},[1017,1022,1027,1032],{"type":413,"tag":483,"props":1018,"children":1019},{},[1020],{"type":419,"value":1021},"there are plenty of languages and frameworks to choose from to build an application",{"type":413,"tag":483,"props":1023,"children":1024},{},[1025],{"type":419,"value":1026},"there are very powerful IDEs and tools to help us write, analyze, refactor, test and debug code",{"type":413,"tag":483,"props":1028,"children":1029},{},[1030],{"type":419,"value":1031},"there are many nice CI/CD platforms that allow us to package and deploy our applications anywhere",{"type":413,"tag":483,"props":1033,"children":1034},{},[1035],{"type":419,"value":1036},"thanks to cloud platforms and infrastructure as code we can provision infrastructure on-demand in an automated way",{"type":413,"tag":422,"props":1038,"children":1039},{},[1040],{"type":419,"value":1041},"Yet, sometimes it seems quite complex and time-consuming to deploy an application in the cloud.",{"type":413,"tag":422,"props":1043,"children":1044},{},[1045],{"type":419,"value":1046},"As a .NET developer, do I really need to master YAML, and Domain Specific Languages like HCL to deploy a simple ASP.NET Core API in Azure? Should I forget about local debugging when developing CI/CD pipelines? Do I have to learn everything from scratch each time I use another CI/CD platform?",{"type":413,"tag":422,"props":1048,"children":1049},{},[1050],{"type":419,"value":1051},"Thanks to Nuke and Pulumi, I don't think so and that is what we are going to talk about in this article.",{"type":413,"tag":414,"props":1053,"children":1055},{"id":1054},"the-scenario",[1056],{"type":419,"value":1057},"The scenario",{"type":413,"tag":422,"props":1059,"children":1060},{},[1061],{"type":419,"value":1062},"They are already lots of great articles about Pulumi or Nuke, so I won't spend time explaining what they are and why you should use them. Instead, I will show you how you can use them together with an example.",{"type":413,"tag":422,"props":1064,"children":1065},{},[1066],{"type":419,"value":1067},"My scenario is the following: I have a very basic ASP.NET Core API that I want to deploy to Azure App Service using a CI/CD pipeline.",{"type":413,"tag":422,"props":1069,"children":1070},{},[1071],{"type":419,"value":1072},"To do that, I want to use my existing .NET skills and code everything with the language and tools I know and love.",{"type":413,"tag":414,"props":1074,"children":1076},{"id":1075},"steps-of-the-cicd-pipeline",[1077],{"type":419,"value":1078},"Steps of the CI/CD pipeline",{"type":413,"tag":422,"props":1080,"children":1081},{},[1082],{"type":419,"value":1083},"There are often two main steps (or stages or whatever you call them) in a CI / CD pipeline: the packaging and the deployment.",{"type":413,"tag":422,"props":1085,"children":1086},{},[1087],{"type":413,"tag":522,"props":1088,"children":1093},{"alt":1089,"className":1090,"src":1091,"width":1092},"Diagram with package and deploy steps.",[526,527],"/posts/images/pulumi_met_nuke_1.png",600,[],{"type":413,"tag":422,"props":1095,"children":1096},{},[1097],{"type":419,"value":1098},"To package a .NET application, we have to first restore the dependencies, then compile the application and publish it. So my Package step is composed of 3 steps.",{"type":413,"tag":509,"props":1100,"children":1101},{"icon":511},[1102],{"type":413,"tag":422,"props":1103,"children":1104},{},[1105,1107,1118],{"type":419,"value":1106},"A ",{"type":413,"tag":452,"props":1108,"children":1111},{"href":1109,"rel":1110},"https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-publish#description",[456],[1112],{"type":413,"tag":552,"props":1113,"children":1115},{"className":1114},[],[1116],{"type":419,"value":1117},"dotnet publish",{"type":419,"value":1119}," does an implicit restore and build the application so only one step could be used but I like separating these steps for clarity. Moreover it is sometimes needed, for instance when you are restoring packages from private Nuget feeds.",{"type":413,"tag":422,"props":1121,"children":1122},{},[1123],{"type":413,"tag":522,"props":1124,"children":1128},{"alt":1125,"className":1126,"src":1127,"width":632},"Diagram with restore, compile, publish and deploy steps.",[526,527],"/posts/images/pulumi_met_nuke_2.png",[],{"type":413,"tag":422,"props":1130,"children":1131},{},[1132],{"type":419,"value":1133},"I said the application needed to be deployed to Azure App Service but I don't have an existing Azure App Service resource, and I don't want to manually create one. So I also need a step to deploy the infrastructure",{"type":413,"tag":422,"props":1135,"children":1136},{},[1137],{"type":413,"tag":522,"props":1138,"children":1142},{"alt":1139,"className":1140,"src":1141,"width":632},"Diagram with restore, compile, publish, deploy and provision infra steps.",[526,527],"/posts/images/pulumi_met_nuke_3.png",[],{"type":413,"tag":422,"props":1144,"children":1145},{},[1146],{"type":419,"value":1147},"It seems fine. I will just add another optional step at the beginning to clean the temporary files I could have created on previous builds.",{"type":413,"tag":509,"props":1149,"children":1151},{"icon":1150},"i-heroicons-light-bulb",[1152],{"type":413,"tag":422,"props":1153,"children":1154},{},[1155],{"type":419,"value":1156},"If your pipeline runs on a hosted agent/runner (managed by the CI/CD platform you use), the Clean step might not be very useful but I intend to also run this pipeline locally.",{"type":413,"tag":422,"props":1158,"children":1159},{},[1160],{"type":413,"tag":522,"props":1161,"children":1165},{"alt":1162,"className":1163,"src":1164},"Diagram with clean, restore, compile, publish, deploy and provision infra steps.",[526,527],"/posts/images/pulumi_met_nuke_4.png",[],{"type":413,"tag":422,"props":1167,"children":1168},{},[1169],{"type":419,"value":1170},"Now, that we know the different steps of our pipeline, let's get to the code.",{"type":413,"tag":414,"props":1172,"children":1174},{"id":1173},"getting-started-with-the-code",[1175],{"type":419,"value":1176},"Getting started with the code",{"type":413,"tag":831,"props":1178,"children":1180},{"id":1179},"code-organization",[1181],{"type":419,"value":1182},"Code organization",{"type":413,"tag":422,"props":1184,"children":1185},{},[1186],{"type":419,"value":1187},"I put all the code in the same Git repository because:",{"type":413,"tag":479,"props":1189,"children":1190},{},[1191,1196,1201],{"type":413,"tag":483,"props":1192,"children":1193},{},[1194],{"type":419,"value":1195},"it makes sense as everything is linked",{"type":413,"tag":483,"props":1197,"children":1198},{},[1199],{"type":419,"value":1200},"it's easier to maintain (all the code in one place)",{"type":413,"tag":483,"props":1202,"children":1203},{},[1204],{"type":419,"value":1205},"it's easier to version (one tag on one commit in one repository)",{"type":413,"tag":422,"props":1207,"children":1208},{},[1209],{"type":419,"value":1210},"I chose to organize my repository with the following folders:",{"type":413,"tag":479,"props":1212,"children":1213},{},[1214,1219,1224,1229],{"type":413,"tag":483,"props":1215,"children":1216},{},[1217],{"type":419,"value":1218},"📁 src ➡️ for the application code of the API",{"type":413,"tag":483,"props":1220,"children":1221},{},[1222],{"type":419,"value":1223},"📁 infra ➡️ for the infrastructure code that provisions the App Service",{"type":413,"tag":483,"props":1225,"children":1226},{},[1227],{"type":419,"value":1228},"📁 build ➡️ for pipeline code that builds and deploys the application",{"type":413,"tag":483,"props":1230,"children":1231},{},[1232],{"type":419,"value":1233},"📁 artifacts ➡️ for the package created by the pipeline",{"type":413,"tag":831,"props":1235,"children":1237},{"id":1236},"create-the-c-projects",[1238],{"type":419,"value":1239},"Create the C# projects",{"type":413,"tag":422,"props":1241,"children":1242},{},[1243],{"type":419,"value":1244},"To create the API project, we just use the default ASP.NET Core API template in .NET 7 that creates a simple Weather API.",{"type":413,"tag":422,"props":1246,"children":1247},{},[1248],{"type":413,"tag":522,"props":1249,"children":1253},{"alt":1250,"className":1251,"src":1252},"IDE folder explorer of an ASP.NET Core API template.",[526,527],"/posts/images/pulumi_met_nuke_5.png",[],{"type":413,"tag":422,"props":1255,"children":1256},{},[1257],{"type":419,"value":1258},"I can initialize the infrastructure project using the Pulumi CLI new command with the azure C# template:",{"type":413,"tag":1260,"props":1261,"children":1265},"pre",{"className":1262,"code":1263,"language":1264,"meta":401,"style":401},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","pulumi new azure-csharp\n","bash",[1266],{"type":413,"tag":552,"props":1267,"children":1268},{"__ignoreMap":401},[1269],{"type":413,"tag":1270,"props":1271,"children":1274},"span",{"class":1272,"line":1273},"line",1,[1275,1280,1286],{"type":413,"tag":1270,"props":1276,"children":1278},{"style":1277},"--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B",[1279],{"type":419,"value":311},{"type":413,"tag":1270,"props":1281,"children":1283},{"style":1282},"--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D",[1284],{"type":419,"value":1285}," new",{"type":413,"tag":1270,"props":1287,"children":1288},{"style":1282},[1289],{"type":419,"value":1290}," azure-csharp\n",{"type":413,"tag":422,"props":1292,"children":1293},{},[1294],{"type":413,"tag":522,"props":1295,"children":1299},{"alt":1296,"className":1297,"src":1298},"IDE folder explorer focused on the infra folder containing the Pulumi project.",[526,527],"/posts/images/pulumi_met_nuke_6.png",[],{"type":413,"tag":422,"props":1301,"children":1302},{},[1303],{"type":419,"value":1304},"I will show later how to modify the code of the template to provision an App Service.",{"type":413,"tag":443,"props":1306,"children":1307},{},[1308],{"type":413,"tag":422,"props":1309,"children":1310},{},[1311,1313,1320],{"type":419,"value":1312},"You can check Pulumi ",{"type":413,"tag":452,"props":1314,"children":1317},{"href":1315,"rel":1316},"https://www.pulumi.com/docs/get-started/azure/",[456],[1318],{"type":419,"value":1319},"Getting Started with Azure",{"type":419,"value":1321}," tutorial to see how to set up your environment and start creating Azure resources in C# (or in another language).",{"type":413,"tag":422,"props":1323,"children":1324},{},[1325,1327,1334],{"type":419,"value":1326},"To initialize the build project, we can use Nuke's .NET global tool as explained in the ",{"type":413,"tag":452,"props":1328,"children":1331},{"href":1329,"rel":1330},"https://nuke.build/docs/getting-started/setup/",[456],[1332],{"type":419,"value":1333},"documentation",{"type":419,"value":1335},":",{"type":413,"tag":1260,"props":1337,"children":1339},{"className":1262,"code":1338,"language":1264,"meta":401,"style":401},"nuke :setup\n",[1340],{"type":413,"tag":552,"props":1341,"children":1342},{"__ignoreMap":401},[1343],{"type":413,"tag":1270,"props":1344,"children":1345},{"class":1272,"line":1273},[1346,1350],{"type":413,"tag":1270,"props":1347,"children":1348},{"style":1277},[1349],{"type":419,"value":364},{"type":413,"tag":1270,"props":1351,"children":1352},{"style":1282},[1353],{"type":419,"value":1354}," :setup\n",{"type":413,"tag":422,"props":1356,"children":1357},{},[1358],{"type":413,"tag":522,"props":1359,"children":1363},{"alt":1360,"className":1361,"src":1362},"IDE folder explorer focused on the build folder containing the Nuke project.",[526,527],"/posts/images/pulumi_met_nuke_7.png",[],{"type":413,"tag":831,"props":1365,"children":1367},{"id":1366},"everything-in-net",[1368],{"type":419,"value":1369},"Everything in .NET",{"type":413,"tag":422,"props":1371,"children":1372},{},[1373],{"type":419,"value":1374},"What I like about using Pulumi (in .NET) and Nuke is that all the code is just C# code.  My infrastructure project and my build project are standard .NET console applications. And I can open the 3 projects (API, infrastructure, and build) in the same solution in my preferred IDE.",{"type":413,"tag":422,"props":1376,"children":1377},{},[1378],{"type":413,"tag":522,"props":1379,"children":1383},{"alt":1380,"className":1381,"src":1382},"IDE solution explorer witht the infra, build and api projects.",[526,527],"/posts/images/pulumi_met_nuke_8.png",[],{"type":413,"tag":422,"props":1385,"children":1386},{},[1387],{"type":419,"value":1388},"Why does it matter? Because any .NET developer in a team would be able to understand and maintain this code. How many times have you seen a project slow down because the person responsible for the infrastructure code written in YAML, JSON, Bicep, or HCL was on vacation or ill? How often have you been stuck because the only few people in the team that knew how to modify the YAML pipelines were not available?",{"type":413,"tag":422,"props":1390,"children":1391},{},[1392],{"type":419,"value":1393},"But it's not a question of knowledge only. It's also because the developer experience of writing build or infrastructure code in .NET is much better than writing code in YAML or other declarative \"languages\".",{"type":413,"tag":509,"props":1395,"children":1396},{"icon":511},[1397],{"type":413,"tag":422,"props":1398,"children":1399},{},[1400,1402,1408],{"type":419,"value":1401},"I talk a lot about the benefits of using programming languages for infrastructure code in my article \"",{"type":413,"tag":452,"props":1403,"children":1406},{"href":1404,"rel":1405},"https://www.techwatching.dev/posts/pulumi-vs-terraform",[456],[1407],{"type":419,"value":114},{"type":419,"value":1409},"\" if you have not read it yet.",{"type":413,"tag":414,"props":1411,"children":1413},{"id":1412},"implementing-the-pipeline-steps-from-clean-to-publish",[1414],{"type":419,"value":1415},"Implementing the pipeline steps from Clean to Publish",{"type":413,"tag":831,"props":1417,"children":1419},{"id":1418},"the-nuke-pipeline",[1420],{"type":419,"value":1421},"The Nuke pipeline",{"type":413,"tag":422,"props":1423,"children":1424},{},[1425],{"type":419,"value":1426},"Here is what looks like the default build project after its creation:",{"type":413,"tag":422,"props":1428,"children":1429},{},[1430],{"type":413,"tag":522,"props":1431,"children":1435},{"alt":1432,"className":1433,"src":1434},"Buidl code file with the build steps opened in the IDE.",[526,527],"/posts/images/pulumi_met_nuke_9.png",[],{"type":413,"tag":422,"props":1437,"children":1438},{},[1439,1441,1447,1449,1456],{"type":419,"value":1440},"The main method is contained in a ",{"type":413,"tag":552,"props":1442,"children":1444},{"className":1443},[],[1445],{"type":419,"value":1446},"Build.cs",{"type":419,"value":1448}," file. This file contains the steps of the pipeline that are called ",{"type":413,"tag":452,"props":1450,"children":1453},{"href":1451,"rel":1452},"https://nuke.build/docs/fundamentals/targets/",[456],[1454],{"type":419,"value":1455},"Target",{"type":419,"value":1457}," in Nuke. We can set the dependencies between targets.",{"type":413,"tag":509,"props":1459,"children":1460},{"icon":1150},[1461],{"type":413,"tag":422,"props":1462,"children":1463},{},[1464],{"type":419,"value":1465},"The build project is a .NET console application so it works out of the box in any .NET IDE or from the command line. But to be more productive you can install a plugin for your IDE that will add snippets and a way to easily debug each target individually.",{"type":413,"tag":422,"props":1467,"children":1468},{},[1469],{"type":419,"value":1470},"As you see we can define properties with the attribute Parameter if we need to pass parameters to our pipeline, like the Configuration parameter.",{"type":413,"tag":831,"props":1472,"children":1474},{"id":1473},"the-clean-target",[1475],{"type":419,"value":1476},"The Clean target",{"type":413,"tag":422,"props":1478,"children":1479},{},[1480],{"type":419,"value":1481},"We can define the Clean target like that:",{"type":413,"tag":1260,"props":1483,"children":1486},{"className":1484,"code":1485,"language":326,"meta":401,"style":401},"language-csharp shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","Target Clean => _ => _  \n    .Before(Restore)  \n    .Executes(() =>  \n    {  \n        SourceDirectory.GlobDirectories(\"*/bin\", \"*/obj\").ForEach(DeleteDirectory);  \n        EnsureCleanDirectory(ArtifactsDirectory);  \n    });\n",[1487],{"type":413,"tag":552,"props":1488,"children":1489},{"__ignoreMap":401},[1490,1524,1558,1583,1596,1679,1705],{"type":413,"tag":1270,"props":1491,"children":1492},{"class":1272,"line":1273},[1493,1499,1504,1510,1515,1519],{"type":413,"tag":1270,"props":1494,"children":1496},{"style":1495},"--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8",[1497],{"type":419,"value":1498},"Target ",{"type":413,"tag":1270,"props":1500,"children":1501},{"style":1277},[1502],{"type":419,"value":1503},"Clean",{"type":413,"tag":1270,"props":1505,"children":1507},{"style":1506},"--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF",[1508],{"type":419,"value":1509}," =>",{"type":413,"tag":1270,"props":1511,"children":1512},{"style":1277},[1513],{"type":419,"value":1514}," _",{"type":413,"tag":1270,"props":1516,"children":1517},{"style":1506},[1518],{"type":419,"value":1509},{"type":413,"tag":1270,"props":1520,"children":1521},{"style":1495},[1522],{"type":419,"value":1523}," _  \n",{"type":413,"tag":1270,"props":1525,"children":1526},{"class":1272,"line":742},[1527,1532,1538,1543,1548,1553],{"type":413,"tag":1270,"props":1528,"children":1529},{"style":1506},[1530],{"type":419,"value":1531},"    .",{"type":413,"tag":1270,"props":1533,"children":1535},{"style":1534},"--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF",[1536],{"type":419,"value":1537},"Before",{"type":413,"tag":1270,"props":1539,"children":1540},{"style":1506},[1541],{"type":419,"value":1542},"(",{"type":413,"tag":1270,"props":1544,"children":1545},{"style":1495},[1546],{"type":419,"value":1547},"Restore",{"type":413,"tag":1270,"props":1549,"children":1550},{"style":1506},[1551],{"type":419,"value":1552},")",{"type":413,"tag":1270,"props":1554,"children":1555},{"style":1495},[1556],{"type":419,"value":1557},"  \n",{"type":413,"tag":1270,"props":1559,"children":1560},{"class":1272,"line":995},[1561,1565,1570,1575,1579],{"type":413,"tag":1270,"props":1562,"children":1563},{"style":1506},[1564],{"type":419,"value":1531},{"type":413,"tag":1270,"props":1566,"children":1567},{"style":1534},[1568],{"type":419,"value":1569},"Executes",{"type":413,"tag":1270,"props":1571,"children":1572},{"style":1506},[1573],{"type":419,"value":1574},"(()",{"type":413,"tag":1270,"props":1576,"children":1577},{"style":1506},[1578],{"type":419,"value":1509},{"type":413,"tag":1270,"props":1580,"children":1581},{"style":1495},[1582],{"type":419,"value":1557},{"type":413,"tag":1270,"props":1584,"children":1586},{"class":1272,"line":1585},4,[1587,1592],{"type":413,"tag":1270,"props":1588,"children":1589},{"style":1506},[1590],{"type":419,"value":1591},"    {",{"type":413,"tag":1270,"props":1593,"children":1594},{"style":1495},[1595],{"type":419,"value":1557},{"type":413,"tag":1270,"props":1597,"children":1599},{"class":1272,"line":1598},5,[1600,1605,1609,1614,1618,1623,1628,1632,1637,1642,1647,1651,1656,1661,1665,1670,1675],{"type":413,"tag":1270,"props":1601,"children":1602},{"style":1495},[1603],{"type":419,"value":1604},"        SourceDirectory",{"type":413,"tag":1270,"props":1606,"children":1607},{"style":1506},[1608],{"type":419,"value":461},{"type":413,"tag":1270,"props":1610,"children":1611},{"style":1534},[1612],{"type":419,"value":1613},"GlobDirectories",{"type":413,"tag":1270,"props":1615,"children":1616},{"style":1506},[1617],{"type":419,"value":1542},{"type":413,"tag":1270,"props":1619,"children":1620},{"style":1506},[1621],{"type":419,"value":1622},"\"",{"type":413,"tag":1270,"props":1624,"children":1625},{"style":1282},[1626],{"type":419,"value":1627},"*/bin",{"type":413,"tag":1270,"props":1629,"children":1630},{"style":1506},[1631],{"type":419,"value":1622},{"type":413,"tag":1270,"props":1633,"children":1634},{"style":1506},[1635],{"type":419,"value":1636},",",{"type":413,"tag":1270,"props":1638,"children":1639},{"style":1506},[1640],{"type":419,"value":1641}," \"",{"type":413,"tag":1270,"props":1643,"children":1644},{"style":1282},[1645],{"type":419,"value":1646},"*/obj",{"type":413,"tag":1270,"props":1648,"children":1649},{"style":1506},[1650],{"type":419,"value":1622},{"type":413,"tag":1270,"props":1652,"children":1653},{"style":1506},[1654],{"type":419,"value":1655},").",{"type":413,"tag":1270,"props":1657,"children":1658},{"style":1534},[1659],{"type":419,"value":1660},"ForEach",{"type":413,"tag":1270,"props":1662,"children":1663},{"style":1506},[1664],{"type":419,"value":1542},{"type":413,"tag":1270,"props":1666,"children":1667},{"style":1495},[1668],{"type":419,"value":1669},"DeleteDirectory",{"type":413,"tag":1270,"props":1671,"children":1672},{"style":1506},[1673],{"type":419,"value":1674},");",{"type":413,"tag":1270,"props":1676,"children":1677},{"style":1495},[1678],{"type":419,"value":1557},{"type":413,"tag":1270,"props":1680,"children":1682},{"class":1272,"line":1681},6,[1683,1688,1692,1697,1701],{"type":413,"tag":1270,"props":1684,"children":1685},{"style":1534},[1686],{"type":419,"value":1687},"        EnsureCleanDirectory",{"type":413,"tag":1270,"props":1689,"children":1690},{"style":1506},[1691],{"type":419,"value":1542},{"type":413,"tag":1270,"props":1693,"children":1694},{"style":1495},[1695],{"type":419,"value":1696},"ArtifactsDirectory",{"type":413,"tag":1270,"props":1698,"children":1699},{"style":1506},[1700],{"type":419,"value":1674},{"type":413,"tag":1270,"props":1702,"children":1703},{"style":1495},[1704],{"type":419,"value":1557},{"type":413,"tag":1270,"props":1706,"children":1708},{"class":1272,"line":1707},7,[1709],{"type":413,"tag":1270,"props":1710,"children":1711},{"style":1506},[1712],{"type":419,"value":1713},"    });\n",{"type":413,"tag":422,"props":1715,"children":1716},{},[1717],{"type":419,"value":1718},"This code deletes all the bin and obj directories of the source directory. It also deletes the content in the artifacts directory. Nuke overloads the division operator to allow us to easily define paths in the project.",{"type":413,"tag":1260,"props":1720,"children":1722},{"className":1484,"code":1721,"language":326,"meta":401,"style":401},"AbsolutePath SourceDirectory => RootDirectory / \"src\";  \n  \nAbsolutePath InfrastructureDirectory => RootDirectory / \"infra\";  \n  \nAbsolutePath ArtifactsDirectory => RootDirectory / \"artifacts\";\n",[1723],{"type":413,"tag":552,"props":1724,"children":1725},{"__ignoreMap":401},[1726,1775,1782,1827,1834],{"type":413,"tag":1270,"props":1727,"children":1728},{"class":1272,"line":1273},[1729,1734,1739,1743,1748,1753,1757,1762,1766,1771],{"type":413,"tag":1270,"props":1730,"children":1731},{"style":1495},[1732],{"type":419,"value":1733},"AbsolutePath ",{"type":413,"tag":1270,"props":1735,"children":1736},{"style":1277},[1737],{"type":419,"value":1738},"SourceDirectory",{"type":413,"tag":1270,"props":1740,"children":1741},{"style":1506},[1742],{"type":419,"value":1509},{"type":413,"tag":1270,"props":1744,"children":1745},{"style":1495},[1746],{"type":419,"value":1747}," RootDirectory ",{"type":413,"tag":1270,"props":1749,"children":1750},{"style":1506},[1751],{"type":419,"value":1752},"/",{"type":413,"tag":1270,"props":1754,"children":1755},{"style":1506},[1756],{"type":419,"value":1641},{"type":413,"tag":1270,"props":1758,"children":1759},{"style":1282},[1760],{"type":419,"value":1761},"src",{"type":413,"tag":1270,"props":1763,"children":1764},{"style":1506},[1765],{"type":419,"value":1622},{"type":413,"tag":1270,"props":1767,"children":1768},{"style":1506},[1769],{"type":419,"value":1770},";",{"type":413,"tag":1270,"props":1772,"children":1773},{"style":1495},[1774],{"type":419,"value":1557},{"type":413,"tag":1270,"props":1776,"children":1777},{"class":1272,"line":742},[1778],{"type":413,"tag":1270,"props":1779,"children":1780},{"style":1495},[1781],{"type":419,"value":1557},{"type":413,"tag":1270,"props":1783,"children":1784},{"class":1272,"line":995},[1785,1789,1794,1798,1802,1806,1810,1815,1819,1823],{"type":413,"tag":1270,"props":1786,"children":1787},{"style":1495},[1788],{"type":419,"value":1733},{"type":413,"tag":1270,"props":1790,"children":1791},{"style":1277},[1792],{"type":419,"value":1793},"InfrastructureDirectory",{"type":413,"tag":1270,"props":1795,"children":1796},{"style":1506},[1797],{"type":419,"value":1509},{"type":413,"tag":1270,"props":1799,"children":1800},{"style":1495},[1801],{"type":419,"value":1747},{"type":413,"tag":1270,"props":1803,"children":1804},{"style":1506},[1805],{"type":419,"value":1752},{"type":413,"tag":1270,"props":1807,"children":1808},{"style":1506},[1809],{"type":419,"value":1641},{"type":413,"tag":1270,"props":1811,"children":1812},{"style":1282},[1813],{"type":419,"value":1814},"infra",{"type":413,"tag":1270,"props":1816,"children":1817},{"style":1506},[1818],{"type":419,"value":1622},{"type":413,"tag":1270,"props":1820,"children":1821},{"style":1506},[1822],{"type":419,"value":1770},{"type":413,"tag":1270,"props":1824,"children":1825},{"style":1495},[1826],{"type":419,"value":1557},{"type":413,"tag":1270,"props":1828,"children":1829},{"class":1272,"line":1585},[1830],{"type":413,"tag":1270,"props":1831,"children":1832},{"style":1495},[1833],{"type":419,"value":1557},{"type":413,"tag":1270,"props":1835,"children":1836},{"class":1272,"line":1598},[1837,1841,1845,1849,1853,1857,1861,1866,1870],{"type":413,"tag":1270,"props":1838,"children":1839},{"style":1495},[1840],{"type":419,"value":1733},{"type":413,"tag":1270,"props":1842,"children":1843},{"style":1277},[1844],{"type":419,"value":1696},{"type":413,"tag":1270,"props":1846,"children":1847},{"style":1506},[1848],{"type":419,"value":1509},{"type":413,"tag":1270,"props":1850,"children":1851},{"style":1495},[1852],{"type":419,"value":1747},{"type":413,"tag":1270,"props":1854,"children":1855},{"style":1506},[1856],{"type":419,"value":1752},{"type":413,"tag":1270,"props":1858,"children":1859},{"style":1506},[1860],{"type":419,"value":1641},{"type":413,"tag":1270,"props":1862,"children":1863},{"style":1282},[1864],{"type":419,"value":1865},"artifacts",{"type":413,"tag":1270,"props":1867,"children":1868},{"style":1506},[1869],{"type":419,"value":1622},{"type":413,"tag":1270,"props":1871,"children":1872},{"style":1506},[1873],{"type":419,"value":1874},";\n",{"type":413,"tag":831,"props":1876,"children":1878},{"id":1877},"the-restore-target",[1879],{"type":419,"value":1880},"The Restore target",{"type":413,"tag":422,"props":1882,"children":1883},{},[1884,1886,1892,1894,1901],{"type":419,"value":1885},"To restore .NET dependencies, we can use the ",{"type":413,"tag":552,"props":1887,"children":1889},{"className":1888},[],[1890],{"type":419,"value":1891},"dotnet restore",{"type":419,"value":1893}," command. Nuke supports ",{"type":413,"tag":452,"props":1895,"children":1898},{"href":1896,"rel":1897},"https://nuke.build/docs/common/cli-tools/",[456],[1899],{"type":419,"value":1900},"executing CLI tools",{"type":419,"value":1902}," and has even auto-generated CLI wrappers for some common tools like dotnet CLI to use a Fluent API instead of string interpolation to pass parameters.",{"type":413,"tag":1260,"props":1904,"children":1906},{"className":1484,"code":1905,"language":326,"meta":401,"style":401},"Target Restore => _ => _  \n    .Executes(() =>  \n    {  \n        DotNetRestore(_ => _.SetProjectFile(Solution));  \n    });\n",[1907],{"type":413,"tag":552,"props":1908,"children":1909},{"__ignoreMap":401},[1910,1937,1960,1971,2023],{"type":413,"tag":1270,"props":1911,"children":1912},{"class":1272,"line":1273},[1913,1917,1921,1925,1929,1933],{"type":413,"tag":1270,"props":1914,"children":1915},{"style":1495},[1916],{"type":419,"value":1498},{"type":413,"tag":1270,"props":1918,"children":1919},{"style":1277},[1920],{"type":419,"value":1547},{"type":413,"tag":1270,"props":1922,"children":1923},{"style":1506},[1924],{"type":419,"value":1509},{"type":413,"tag":1270,"props":1926,"children":1927},{"style":1277},[1928],{"type":419,"value":1514},{"type":413,"tag":1270,"props":1930,"children":1931},{"style":1506},[1932],{"type":419,"value":1509},{"type":413,"tag":1270,"props":1934,"children":1935},{"style":1495},[1936],{"type":419,"value":1523},{"type":413,"tag":1270,"props":1938,"children":1939},{"class":1272,"line":742},[1940,1944,1948,1952,1956],{"type":413,"tag":1270,"props":1941,"children":1942},{"style":1506},[1943],{"type":419,"value":1531},{"type":413,"tag":1270,"props":1945,"children":1946},{"style":1534},[1947],{"type":419,"value":1569},{"type":413,"tag":1270,"props":1949,"children":1950},{"style":1506},[1951],{"type":419,"value":1574},{"type":413,"tag":1270,"props":1953,"children":1954},{"style":1506},[1955],{"type":419,"value":1509},{"type":413,"tag":1270,"props":1957,"children":1958},{"style":1495},[1959],{"type":419,"value":1557},{"type":413,"tag":1270,"props":1961,"children":1962},{"class":1272,"line":995},[1963,1967],{"type":413,"tag":1270,"props":1964,"children":1965},{"style":1506},[1966],{"type":419,"value":1591},{"type":413,"tag":1270,"props":1968,"children":1969},{"style":1495},[1970],{"type":419,"value":1557},{"type":413,"tag":1270,"props":1972,"children":1973},{"class":1272,"line":1585},[1974,1979,1983,1988,1992,1996,2000,2005,2009,2014,2019],{"type":413,"tag":1270,"props":1975,"children":1976},{"style":1534},[1977],{"type":419,"value":1978},"        DotNetRestore",{"type":413,"tag":1270,"props":1980,"children":1981},{"style":1506},[1982],{"type":419,"value":1542},{"type":413,"tag":1270,"props":1984,"children":1985},{"style":1277},[1986],{"type":419,"value":1987},"_",{"type":413,"tag":1270,"props":1989,"children":1990},{"style":1506},[1991],{"type":419,"value":1509},{"type":413,"tag":1270,"props":1993,"children":1994},{"style":1495},[1995],{"type":419,"value":1514},{"type":413,"tag":1270,"props":1997,"children":1998},{"style":1506},[1999],{"type":419,"value":461},{"type":413,"tag":1270,"props":2001,"children":2002},{"style":1534},[2003],{"type":419,"value":2004},"SetProjectFile",{"type":413,"tag":1270,"props":2006,"children":2007},{"style":1506},[2008],{"type":419,"value":1542},{"type":413,"tag":1270,"props":2010,"children":2011},{"style":1495},[2012],{"type":419,"value":2013},"Solution",{"type":413,"tag":1270,"props":2015,"children":2016},{"style":1506},[2017],{"type":419,"value":2018},"));",{"type":413,"tag":1270,"props":2020,"children":2021},{"style":1495},[2022],{"type":419,"value":1557},{"type":413,"tag":1270,"props":2024,"children":2025},{"class":1272,"line":1598},[2026],{"type":413,"tag":1270,"props":2027,"children":2028},{"style":1506},[2029],{"type":419,"value":1713},{"type":413,"tag":831,"props":2031,"children":2033},{"id":2032},"the-compile-target",[2034],{"type":419,"value":2035},"The Compile target",{"type":413,"tag":422,"props":2037,"children":2038},{},[2039,2041,2047,2049,2055,2057,2063],{"type":419,"value":2040},"The compile target uses the ",{"type":413,"tag":552,"props":2042,"children":2044},{"className":2043},[],[2045],{"type":419,"value":2046},"dotnet build",{"type":419,"value":2048}," command. We can start to see the benefits of using this Fluent API that provides us with autocompletion and documentation. For instance, as we already restored the dependencies in the previous step, we can set the ",{"type":413,"tag":552,"props":2050,"children":2052},{"className":2051},[],[2053],{"type":419,"value":2054},"--no-restore",{"type":419,"value":2056}," option using the ",{"type":413,"tag":552,"props":2058,"children":2060},{"className":2059},[],[2061],{"type":419,"value":2062},"EnableNoRestore",{"type":419,"value":2064}," auto-generated method.",{"type":413,"tag":1260,"props":2066,"children":2068},{"className":1484,"code":2067,"language":326,"meta":401,"style":401},"Target Compile => _ => _  \n    .DependsOn(Restore)  \n    .Executes(() =>  \n    {  \n        DotNetBuild(_ => _  \n            .SetProjectFile(Solution)  \n            .SetConfiguration(Configuration)  \n            .EnableNoRestore());  \n    });\n",[2069],{"type":413,"tag":552,"props":2070,"children":2071},{"__ignoreMap":401},[2072,2100,2128,2151,2162,2186,2214,2242,2263],{"type":413,"tag":1270,"props":2073,"children":2074},{"class":1272,"line":1273},[2075,2079,2084,2088,2092,2096],{"type":413,"tag":1270,"props":2076,"children":2077},{"style":1495},[2078],{"type":419,"value":1498},{"type":413,"tag":1270,"props":2080,"children":2081},{"style":1277},[2082],{"type":419,"value":2083},"Compile",{"type":413,"tag":1270,"props":2085,"children":2086},{"style":1506},[2087],{"type":419,"value":1509},{"type":413,"tag":1270,"props":2089,"children":2090},{"style":1277},[2091],{"type":419,"value":1514},{"type":413,"tag":1270,"props":2093,"children":2094},{"style":1506},[2095],{"type":419,"value":1509},{"type":413,"tag":1270,"props":2097,"children":2098},{"style":1495},[2099],{"type":419,"value":1523},{"type":413,"tag":1270,"props":2101,"children":2102},{"class":1272,"line":742},[2103,2107,2112,2116,2120,2124],{"type":413,"tag":1270,"props":2104,"children":2105},{"style":1506},[2106],{"type":419,"value":1531},{"type":413,"tag":1270,"props":2108,"children":2109},{"style":1534},[2110],{"type":419,"value":2111},"DependsOn",{"type":413,"tag":1270,"props":2113,"children":2114},{"style":1506},[2115],{"type":419,"value":1542},{"type":413,"tag":1270,"props":2117,"children":2118},{"style":1495},[2119],{"type":419,"value":1547},{"type":413,"tag":1270,"props":2121,"children":2122},{"style":1506},[2123],{"type":419,"value":1552},{"type":413,"tag":1270,"props":2125,"children":2126},{"style":1495},[2127],{"type":419,"value":1557},{"type":413,"tag":1270,"props":2129,"children":2130},{"class":1272,"line":995},[2131,2135,2139,2143,2147],{"type":413,"tag":1270,"props":2132,"children":2133},{"style":1506},[2134],{"type":419,"value":1531},{"type":413,"tag":1270,"props":2136,"children":2137},{"style":1534},[2138],{"type":419,"value":1569},{"type":413,"tag":1270,"props":2140,"children":2141},{"style":1506},[2142],{"type":419,"value":1574},{"type":413,"tag":1270,"props":2144,"children":2145},{"style":1506},[2146],{"type":419,"value":1509},{"type":413,"tag":1270,"props":2148,"children":2149},{"style":1495},[2150],{"type":419,"value":1557},{"type":413,"tag":1270,"props":2152,"children":2153},{"class":1272,"line":1585},[2154,2158],{"type":413,"tag":1270,"props":2155,"children":2156},{"style":1506},[2157],{"type":419,"value":1591},{"type":413,"tag":1270,"props":2159,"children":2160},{"style":1495},[2161],{"type":419,"value":1557},{"type":413,"tag":1270,"props":2163,"children":2164},{"class":1272,"line":1598},[2165,2170,2174,2178,2182],{"type":413,"tag":1270,"props":2166,"children":2167},{"style":1534},[2168],{"type":419,"value":2169},"        DotNetBuild",{"type":413,"tag":1270,"props":2171,"children":2172},{"style":1506},[2173],{"type":419,"value":1542},{"type":413,"tag":1270,"props":2175,"children":2176},{"style":1277},[2177],{"type":419,"value":1987},{"type":413,"tag":1270,"props":2179,"children":2180},{"style":1506},[2181],{"type":419,"value":1509},{"type":413,"tag":1270,"props":2183,"children":2184},{"style":1495},[2185],{"type":419,"value":1523},{"type":413,"tag":1270,"props":2187,"children":2188},{"class":1272,"line":1681},[2189,2194,2198,2202,2206,2210],{"type":413,"tag":1270,"props":2190,"children":2191},{"style":1506},[2192],{"type":419,"value":2193},"            .",{"type":413,"tag":1270,"props":2195,"children":2196},{"style":1534},[2197],{"type":419,"value":2004},{"type":413,"tag":1270,"props":2199,"children":2200},{"style":1506},[2201],{"type":419,"value":1542},{"type":413,"tag":1270,"props":2203,"children":2204},{"style":1495},[2205],{"type":419,"value":2013},{"type":413,"tag":1270,"props":2207,"children":2208},{"style":1506},[2209],{"type":419,"value":1552},{"type":413,"tag":1270,"props":2211,"children":2212},{"style":1495},[2213],{"type":419,"value":1557},{"type":413,"tag":1270,"props":2215,"children":2216},{"class":1272,"line":1707},[2217,2221,2226,2230,2234,2238],{"type":413,"tag":1270,"props":2218,"children":2219},{"style":1506},[2220],{"type":419,"value":2193},{"type":413,"tag":1270,"props":2222,"children":2223},{"style":1534},[2224],{"type":419,"value":2225},"SetConfiguration",{"type":413,"tag":1270,"props":2227,"children":2228},{"style":1506},[2229],{"type":419,"value":1542},{"type":413,"tag":1270,"props":2231,"children":2232},{"style":1495},[2233],{"type":419,"value":263},{"type":413,"tag":1270,"props":2235,"children":2236},{"style":1506},[2237],{"type":419,"value":1552},{"type":413,"tag":1270,"props":2239,"children":2240},{"style":1495},[2241],{"type":419,"value":1557},{"type":413,"tag":1270,"props":2243,"children":2245},{"class":1272,"line":2244},8,[2246,2250,2254,2259],{"type":413,"tag":1270,"props":2247,"children":2248},{"style":1506},[2249],{"type":419,"value":2193},{"type":413,"tag":1270,"props":2251,"children":2252},{"style":1534},[2253],{"type":419,"value":2062},{"type":413,"tag":1270,"props":2255,"children":2256},{"style":1506},[2257],{"type":419,"value":2258},"());",{"type":413,"tag":1270,"props":2260,"children":2261},{"style":1495},[2262],{"type":419,"value":1557},{"type":413,"tag":1270,"props":2264,"children":2266},{"class":1272,"line":2265},9,[2267],{"type":413,"tag":1270,"props":2268,"children":2269},{"style":1506},[2270],{"type":419,"value":1713},{"type":413,"tag":831,"props":2272,"children":2274},{"id":2273},"the-publish-target",[2275],{"type":419,"value":2276},"The Publish target",{"type":413,"tag":422,"props":2278,"children":2279},{},[2280,2282,2287,2289,2295],{"type":419,"value":2281},"The publish target uses the ",{"type":413,"tag":552,"props":2283,"children":2285},{"className":2284},[],[2286],{"type":419,"value":1117},{"type":419,"value":2288}," command and then creates a zip ",{"type":413,"tag":552,"props":2290,"children":2292},{"className":2291},[],[2293],{"type":419,"value":2294},"api.zip",{"type":419,"value":2296}," of the resulting package in the artifacts directory.",{"type":413,"tag":1260,"props":2298,"children":2300},{"className":1484,"code":2299,"language":326,"meta":401,"style":401},"Target Publish => _ => _  \n    .DependsOn(Clean, Compile)  \n    .Executes(() =>  \n    {  \n        DotNetPublish(_ => _  \n            .SetProject(Solution.CSharpEverything_Api)  \n            .SetConfiguration(Configuration)  \n            .EnableNoBuild()  \n            .SetOutput(ApiPackageDirectory));  \n  \n        ZipFile.CreateFromDirectory(ApiPackageDirectory, ArtifactsDirectory / \"api.zip\");  \n    });\n",[2301],{"type":413,"tag":552,"props":2302,"children":2303},{"__ignoreMap":401},[2304,2332,2368,2391,2402,2426,2463,2490,2511,2540,2548,2607],{"type":413,"tag":1270,"props":2305,"children":2306},{"class":1272,"line":1273},[2307,2311,2316,2320,2324,2328],{"type":413,"tag":1270,"props":2308,"children":2309},{"style":1495},[2310],{"type":419,"value":1498},{"type":413,"tag":1270,"props":2312,"children":2313},{"style":1277},[2314],{"type":419,"value":2315},"Publish",{"type":413,"tag":1270,"props":2317,"children":2318},{"style":1506},[2319],{"type":419,"value":1509},{"type":413,"tag":1270,"props":2321,"children":2322},{"style":1277},[2323],{"type":419,"value":1514},{"type":413,"tag":1270,"props":2325,"children":2326},{"style":1506},[2327],{"type":419,"value":1509},{"type":413,"tag":1270,"props":2329,"children":2330},{"style":1495},[2331],{"type":419,"value":1523},{"type":413,"tag":1270,"props":2333,"children":2334},{"class":1272,"line":742},[2335,2339,2343,2347,2351,2355,2360,2364],{"type":413,"tag":1270,"props":2336,"children":2337},{"style":1506},[2338],{"type":419,"value":1531},{"type":413,"tag":1270,"props":2340,"children":2341},{"style":1534},[2342],{"type":419,"value":2111},{"type":413,"tag":1270,"props":2344,"children":2345},{"style":1506},[2346],{"type":419,"value":1542},{"type":413,"tag":1270,"props":2348,"children":2349},{"style":1495},[2350],{"type":419,"value":1503},{"type":413,"tag":1270,"props":2352,"children":2353},{"style":1506},[2354],{"type":419,"value":1636},{"type":413,"tag":1270,"props":2356,"children":2357},{"style":1495},[2358],{"type":419,"value":2359}," Compile",{"type":413,"tag":1270,"props":2361,"children":2362},{"style":1506},[2363],{"type":419,"value":1552},{"type":413,"tag":1270,"props":2365,"children":2366},{"style":1495},[2367],{"type":419,"value":1557},{"type":413,"tag":1270,"props":2369,"children":2370},{"class":1272,"line":995},[2371,2375,2379,2383,2387],{"type":413,"tag":1270,"props":2372,"children":2373},{"style":1506},[2374],{"type":419,"value":1531},{"type":413,"tag":1270,"props":2376,"children":2377},{"style":1534},[2378],{"type":419,"value":1569},{"type":413,"tag":1270,"props":2380,"children":2381},{"style":1506},[2382],{"type":419,"value":1574},{"type":413,"tag":1270,"props":2384,"children":2385},{"style":1506},[2386],{"type":419,"value":1509},{"type":413,"tag":1270,"props":2388,"children":2389},{"style":1495},[2390],{"type":419,"value":1557},{"type":413,"tag":1270,"props":2392,"children":2393},{"class":1272,"line":1585},[2394,2398],{"type":413,"tag":1270,"props":2395,"children":2396},{"style":1506},[2397],{"type":419,"value":1591},{"type":413,"tag":1270,"props":2399,"children":2400},{"style":1495},[2401],{"type":419,"value":1557},{"type":413,"tag":1270,"props":2403,"children":2404},{"class":1272,"line":1598},[2405,2410,2414,2418,2422],{"type":413,"tag":1270,"props":2406,"children":2407},{"style":1534},[2408],{"type":419,"value":2409},"        DotNetPublish",{"type":413,"tag":1270,"props":2411,"children":2412},{"style":1506},[2413],{"type":419,"value":1542},{"type":413,"tag":1270,"props":2415,"children":2416},{"style":1277},[2417],{"type":419,"value":1987},{"type":413,"tag":1270,"props":2419,"children":2420},{"style":1506},[2421],{"type":419,"value":1509},{"type":413,"tag":1270,"props":2423,"children":2424},{"style":1495},[2425],{"type":419,"value":1523},{"type":413,"tag":1270,"props":2427,"children":2428},{"class":1272,"line":1681},[2429,2433,2438,2442,2446,2450,2455,2459],{"type":413,"tag":1270,"props":2430,"children":2431},{"style":1506},[2432],{"type":419,"value":2193},{"type":413,"tag":1270,"props":2434,"children":2435},{"style":1534},[2436],{"type":419,"value":2437},"SetProject",{"type":413,"tag":1270,"props":2439,"children":2440},{"style":1506},[2441],{"type":419,"value":1542},{"type":413,"tag":1270,"props":2443,"children":2444},{"style":1495},[2445],{"type":419,"value":2013},{"type":413,"tag":1270,"props":2447,"children":2448},{"style":1506},[2449],{"type":419,"value":461},{"type":413,"tag":1270,"props":2451,"children":2452},{"style":1495},[2453],{"type":419,"value":2454},"CSharpEverything_Api",{"type":413,"tag":1270,"props":2456,"children":2457},{"style":1506},[2458],{"type":419,"value":1552},{"type":413,"tag":1270,"props":2460,"children":2461},{"style":1495},[2462],{"type":419,"value":1557},{"type":413,"tag":1270,"props":2464,"children":2465},{"class":1272,"line":1707},[2466,2470,2474,2478,2482,2486],{"type":413,"tag":1270,"props":2467,"children":2468},{"style":1506},[2469],{"type":419,"value":2193},{"type":413,"tag":1270,"props":2471,"children":2472},{"style":1534},[2473],{"type":419,"value":2225},{"type":413,"tag":1270,"props":2475,"children":2476},{"style":1506},[2477],{"type":419,"value":1542},{"type":413,"tag":1270,"props":2479,"children":2480},{"style":1495},[2481],{"type":419,"value":263},{"type":413,"tag":1270,"props":2483,"children":2484},{"style":1506},[2485],{"type":419,"value":1552},{"type":413,"tag":1270,"props":2487,"children":2488},{"style":1495},[2489],{"type":419,"value":1557},{"type":413,"tag":1270,"props":2491,"children":2492},{"class":1272,"line":2244},[2493,2497,2502,2507],{"type":413,"tag":1270,"props":2494,"children":2495},{"style":1506},[2496],{"type":419,"value":2193},{"type":413,"tag":1270,"props":2498,"children":2499},{"style":1534},[2500],{"type":419,"value":2501},"EnableNoBuild",{"type":413,"tag":1270,"props":2503,"children":2504},{"style":1506},[2505],{"type":419,"value":2506},"()",{"type":413,"tag":1270,"props":2508,"children":2509},{"style":1495},[2510],{"type":419,"value":1557},{"type":413,"tag":1270,"props":2512,"children":2513},{"class":1272,"line":2265},[2514,2518,2523,2527,2532,2536],{"type":413,"tag":1270,"props":2515,"children":2516},{"style":1506},[2517],{"type":419,"value":2193},{"type":413,"tag":1270,"props":2519,"children":2520},{"style":1534},[2521],{"type":419,"value":2522},"SetOutput",{"type":413,"tag":1270,"props":2524,"children":2525},{"style":1506},[2526],{"type":419,"value":1542},{"type":413,"tag":1270,"props":2528,"children":2529},{"style":1495},[2530],{"type":419,"value":2531},"ApiPackageDirectory",{"type":413,"tag":1270,"props":2533,"children":2534},{"style":1506},[2535],{"type":419,"value":2018},{"type":413,"tag":1270,"props":2537,"children":2538},{"style":1495},[2539],{"type":419,"value":1557},{"type":413,"tag":1270,"props":2541,"children":2543},{"class":1272,"line":2542},10,[2544],{"type":413,"tag":1270,"props":2545,"children":2546},{"style":1495},[2547],{"type":419,"value":1557},{"type":413,"tag":1270,"props":2549,"children":2551},{"class":1272,"line":2550},11,[2552,2557,2561,2566,2570,2574,2578,2583,2587,2591,2595,2599,2603],{"type":413,"tag":1270,"props":2553,"children":2554},{"style":1495},[2555],{"type":419,"value":2556},"        ZipFile",{"type":413,"tag":1270,"props":2558,"children":2559},{"style":1506},[2560],{"type":419,"value":461},{"type":413,"tag":1270,"props":2562,"children":2563},{"style":1534},[2564],{"type":419,"value":2565},"CreateFromDirectory",{"type":413,"tag":1270,"props":2567,"children":2568},{"style":1506},[2569],{"type":419,"value":1542},{"type":413,"tag":1270,"props":2571,"children":2572},{"style":1495},[2573],{"type":419,"value":2531},{"type":413,"tag":1270,"props":2575,"children":2576},{"style":1506},[2577],{"type":419,"value":1636},{"type":413,"tag":1270,"props":2579,"children":2580},{"style":1495},[2581],{"type":419,"value":2582}," ArtifactsDirectory ",{"type":413,"tag":1270,"props":2584,"children":2585},{"style":1506},[2586],{"type":419,"value":1752},{"type":413,"tag":1270,"props":2588,"children":2589},{"style":1506},[2590],{"type":419,"value":1641},{"type":413,"tag":1270,"props":2592,"children":2593},{"style":1282},[2594],{"type":419,"value":2294},{"type":413,"tag":1270,"props":2596,"children":2597},{"style":1506},[2598],{"type":419,"value":1622},{"type":413,"tag":1270,"props":2600,"children":2601},{"style":1506},[2602],{"type":419,"value":1674},{"type":413,"tag":1270,"props":2604,"children":2605},{"style":1495},[2606],{"type":419,"value":1557},{"type":413,"tag":1270,"props":2608,"children":2610},{"class":1272,"line":2609},12,[2611],{"type":413,"tag":1270,"props":2612,"children":2613},{"style":1506},[2614],{"type":419,"value":1713},{"type":413,"tag":509,"props":2616,"children":2617},{"icon":1150},[2618],{"type":413,"tag":422,"props":2619,"children":2620},{},[2621,2623,2630],{"type":419,"value":2622},"If you need more compressing archives options, you can check ",{"type":413,"tag":452,"props":2624,"children":2627},{"href":2625,"rel":2626},"https://nuke.build/docs/common/compression/#compressing-archives",[456],[2628],{"type":419,"value":2629},"Nuke documentation",{"type":419,"value":2631},", they have some utilities to do more complex scenarios.",{"type":413,"tag":422,"props":2633,"children":2634},{},[2635,2637,2644,2646,2651,2653,2659],{"type":419,"value":2636},"You may have noted on the line where I set the project that I have ",{"type":413,"tag":452,"props":2638,"children":2641},{"href":2639,"rel":2640},"https://nuke.build/docs/common/solution-project-model/#strong-typed-project-access",[456],[2642],{"type":419,"value":2643},"strong-typed access to the projects in my solution",{"type":419,"value":2645},". This is possible by adding this field with the ",{"type":413,"tag":552,"props":2647,"children":2649},{"className":2648},[],[2650],{"type":419,"value":2013},{"type":419,"value":2652}," attribute and its ",{"type":413,"tag":552,"props":2654,"children":2656},{"className":2655},[],[2657],{"type":419,"value":2658},"GenerateProjects",{"type":419,"value":2660}," property set to true.",{"type":413,"tag":1260,"props":2662,"children":2664},{"className":1484,"code":2663,"language":326,"meta":401,"style":401},"[Solution(GenerateProjects = true)]  \nreadonly Solution Solution;\n",[2665],{"type":413,"tag":552,"props":2666,"children":2667},{"__ignoreMap":401},[2668,2708],{"type":413,"tag":1270,"props":2669,"children":2670},{"class":1272,"line":1273},[2671,2676,2680,2684,2688,2693,2699,2704],{"type":413,"tag":1270,"props":2672,"children":2673},{"style":1506},[2674],{"type":419,"value":2675},"[",{"type":413,"tag":1270,"props":2677,"children":2678},{"style":1277},[2679],{"type":419,"value":2013},{"type":413,"tag":1270,"props":2681,"children":2682},{"style":1506},[2683],{"type":419,"value":1542},{"type":413,"tag":1270,"props":2685,"children":2686},{"style":1277},[2687],{"type":419,"value":2658},{"type":413,"tag":1270,"props":2689,"children":2690},{"style":1506},[2691],{"type":419,"value":2692}," =",{"type":413,"tag":1270,"props":2694,"children":2696},{"style":2695},"--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC",[2697],{"type":419,"value":2698}," true",{"type":413,"tag":1270,"props":2700,"children":2701},{"style":1506},[2702],{"type":419,"value":2703},")]",{"type":413,"tag":1270,"props":2705,"children":2706},{"style":1495},[2707],{"type":419,"value":1557},{"type":413,"tag":1270,"props":2709,"children":2710},{"class":1272,"line":742},[2711,2717,2722,2726],{"type":413,"tag":1270,"props":2712,"children":2714},{"style":2713},"--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA",[2715],{"type":419,"value":2716},"readonly",{"type":413,"tag":1270,"props":2718,"children":2719},{"style":1277},[2720],{"type":419,"value":2721}," Solution",{"type":413,"tag":1270,"props":2723,"children":2724},{"style":1277},[2725],{"type":419,"value":2721},{"type":413,"tag":1270,"props":2727,"children":2728},{"style":1506},[2729],{"type":419,"value":1874},{"type":413,"tag":2731,"props":2732,"children":2734},"calout",{"icon":2733},"i-fluent-emoji-magic-wand",[2735],{"type":413,"tag":422,"props":2736,"children":2737},{},[2738,2740,2747],{"type":419,"value":2739},"It looks like magic but it's not! Nuke uses a ",{"type":413,"tag":452,"props":2741,"children":2744},{"href":2742,"rel":2743},"https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview",[456],[2745],{"type":419,"value":2746},"source generator",{"type":419,"value":2748},"  to do that behind the scenes.",{"type":413,"tag":414,"props":2750,"children":2752},{"id":2751},"provisioning-the-app-service-with-pulumi",[2753],{"type":419,"value":2754},"Provisioning the App Service with Pulumi",{"type":413,"tag":831,"props":2756,"children":2758},{"id":2757},"the-pulumi-project",[2759],{"type":419,"value":2760},"The Pulumi project",{"type":413,"tag":422,"props":2762,"children":2763},{},[2764,2766,2772,2774,2780],{"type":419,"value":2765},"By default, the infrastructure code is contained in the ",{"type":413,"tag":552,"props":2767,"children":2769},{"className":2768},[],[2770],{"type":419,"value":2771},"Program.cs",{"type":419,"value":2773}," file of our project. The resources to provision are declared in the lambda in parameter of the ",{"type":413,"tag":552,"props":2775,"children":2777},{"className":2776},[],[2778],{"type":419,"value":2779},"Deployment.RunAsync",{"type":419,"value":2781}," method.",{"type":413,"tag":422,"props":2783,"children":2784},{},[2785],{"type":413,"tag":522,"props":2786,"children":2790},{"alt":2787,"className":2788,"src":2789},"Pulumi program file opened in the IDE.",[526,527],"/posts/images/pulumi_met_nuke_10.png",[],{"type":413,"tag":509,"props":2792,"children":2793},{"icon":511},[2794],{"type":413,"tag":422,"props":2795,"children":2796},{},[2797],{"type":419,"value":2798},"The project uses the top-level statement feature of C#.",{"type":413,"tag":422,"props":2800,"children":2801},{},[2802,2804,2809],{"type":419,"value":2803},"As we don't have many resources to declare for our scenario we will keep all the code in the ",{"type":413,"tag":552,"props":2805,"children":2807},{"className":2806},[],[2808],{"type":419,"value":2771},{"type":419,"value":2810}," file but that is not what you would do in a more complex project.",{"type":413,"tag":831,"props":2812,"children":2814},{"id":2813},"azure-resources-to-provision",[2815],{"type":419,"value":2816},"Azure resources to provision",{"type":413,"tag":422,"props":2818,"children":2819},{},[2820],{"type":419,"value":2821},"There are 3 Azure resources we need to create in our stack (instance of a Pulumi program):",{"type":413,"tag":479,"props":2823,"children":2824},{},[2825,2830,2842],{"type":413,"tag":483,"props":2826,"children":2827},{},[2828],{"type":419,"value":2829},"a resource group to contain the different Azure resources",{"type":413,"tag":483,"props":2831,"children":2832},{},[2833,2835],{"type":419,"value":2834},"an App Service Plan which ",{"type":413,"tag":452,"props":2836,"children":2839},{"href":2837,"rel":2838},"https://learn.microsoft.com/en-us/azure/app-service/overview-hosting-plans",[456],[2840],{"type":419,"value":2841},"defines the set of compute resources for a web app to run",{"type":413,"tag":483,"props":2843,"children":2844},{},[2845],{"type":419,"value":2846},"a Web App / App Service which is where the API will be deployed",{"type":413,"tag":1260,"props":2848,"children":2850},{"className":1484,"code":2849,"language":326,"meta":401,"style":401},"var resourceGroup = new ResourceGroup($\"rg-{Deployment.Instance.ProjectName}-{Deployment.Instance.StackName}\");  \n  \nvar appServicePlan = new AppServicePlan($\"plan-{Deployment.Instance.ProjectName}-{Deployment.Instance.StackName}\", new AppServicePlanArgs  \n{  \n    ResourceGroupName = resourceGroup.Name,  \n    Kind = \"App\",  \n    Sku = new SkuDescriptionArgs  \n    {  \n        Tier = \"Basic\",  \n        Name = \"B1\",  \n    },  \n});  \n  \nvar appService = new WebApp($\"app-{Deployment.Instance.ProjectName}-{Deployment.Instance.StackName}\", new WebAppArgs   \n{   \n    ResourceGroupName = resourceGroup.Name,  \n    ServerFarmId = appServicePlan.Id  \n});\n",[2851],{"type":413,"tag":552,"props":2852,"children":2853},{"__ignoreMap":401},[2854,2970,2977,3092,3103,3137,3170,3195,3206,3239,3272,3284,3296,3304,3421,3433,3465,3491],{"type":413,"tag":1270,"props":2855,"children":2856},{"class":1272,"line":1273},[2857,2862,2867,2871,2875,2880,2884,2889,2894,2899,2904,2908,2913,2917,2922,2927,2932,2936,2940,2944,2948,2952,2957,2962,2966],{"type":413,"tag":1270,"props":2858,"children":2859},{"style":1277},[2860],{"type":419,"value":2861},"var",{"type":413,"tag":1270,"props":2863,"children":2864},{"style":1277},[2865],{"type":419,"value":2866}," resourceGroup",{"type":413,"tag":1270,"props":2868,"children":2869},{"style":1506},[2870],{"type":419,"value":2692},{"type":413,"tag":1270,"props":2872,"children":2873},{"style":1506},[2874],{"type":419,"value":1285},{"type":413,"tag":1270,"props":2876,"children":2877},{"style":1277},[2878],{"type":419,"value":2879}," ResourceGroup",{"type":413,"tag":1270,"props":2881,"children":2882},{"style":1506},[2883],{"type":419,"value":1542},{"type":413,"tag":1270,"props":2885,"children":2886},{"style":1506},[2887],{"type":419,"value":2888},"$\"",{"type":413,"tag":1270,"props":2890,"children":2891},{"style":1282},[2892],{"type":419,"value":2893},"rg-",{"type":413,"tag":1270,"props":2895,"children":2896},{"style":1506},[2897],{"type":419,"value":2898},"{",{"type":413,"tag":1270,"props":2900,"children":2901},{"style":1495},[2902],{"type":419,"value":2903},"Deployment",{"type":413,"tag":1270,"props":2905,"children":2906},{"style":1506},[2907],{"type":419,"value":461},{"type":413,"tag":1270,"props":2909,"children":2910},{"style":1495},[2911],{"type":419,"value":2912},"Instance",{"type":413,"tag":1270,"props":2914,"children":2915},{"style":1506},[2916],{"type":419,"value":461},{"type":413,"tag":1270,"props":2918,"children":2919},{"style":1495},[2920],{"type":419,"value":2921},"ProjectName",{"type":413,"tag":1270,"props":2923,"children":2924},{"style":1506},[2925],{"type":419,"value":2926},"}",{"type":413,"tag":1270,"props":2928,"children":2929},{"style":1282},[2930],{"type":419,"value":2931},"-",{"type":413,"tag":1270,"props":2933,"children":2934},{"style":1506},[2935],{"type":419,"value":2898},{"type":413,"tag":1270,"props":2937,"children":2938},{"style":1495},[2939],{"type":419,"value":2903},{"type":413,"tag":1270,"props":2941,"children":2942},{"style":1506},[2943],{"type":419,"value":461},{"type":413,"tag":1270,"props":2945,"children":2946},{"style":1495},[2947],{"type":419,"value":2912},{"type":413,"tag":1270,"props":2949,"children":2950},{"style":1506},[2951],{"type":419,"value":461},{"type":413,"tag":1270,"props":2953,"children":2954},{"style":1495},[2955],{"type":419,"value":2956},"StackName",{"type":413,"tag":1270,"props":2958,"children":2959},{"style":1506},[2960],{"type":419,"value":2961},"}\"",{"type":413,"tag":1270,"props":2963,"children":2964},{"style":1506},[2965],{"type":419,"value":1674},{"type":413,"tag":1270,"props":2967,"children":2968},{"style":1495},[2969],{"type":419,"value":1557},{"type":413,"tag":1270,"props":2971,"children":2972},{"class":1272,"line":742},[2973],{"type":413,"tag":1270,"props":2974,"children":2975},{"style":1495},[2976],{"type":419,"value":1557},{"type":413,"tag":1270,"props":2978,"children":2979},{"class":1272,"line":995},[2980,2984,2989,2993,2997,3002,3006,3010,3015,3019,3023,3027,3031,3035,3039,3043,3047,3051,3055,3059,3063,3067,3071,3075,3079,3083,3088],{"type":413,"tag":1270,"props":2981,"children":2982},{"style":1277},[2983],{"type":419,"value":2861},{"type":413,"tag":1270,"props":2985,"children":2986},{"style":1277},[2987],{"type":419,"value":2988}," appServicePlan",{"type":413,"tag":1270,"props":2990,"children":2991},{"style":1506},[2992],{"type":419,"value":2692},{"type":413,"tag":1270,"props":2994,"children":2995},{"style":1506},[2996],{"type":419,"value":1285},{"type":413,"tag":1270,"props":2998,"children":2999},{"style":1277},[3000],{"type":419,"value":3001}," AppServicePlan",{"type":413,"tag":1270,"props":3003,"children":3004},{"style":1506},[3005],{"type":419,"value":1542},{"type":413,"tag":1270,"props":3007,"children":3008},{"style":1506},[3009],{"type":419,"value":2888},{"type":413,"tag":1270,"props":3011,"children":3012},{"style":1282},[3013],{"type":419,"value":3014},"plan-",{"type":413,"tag":1270,"props":3016,"children":3017},{"style":1506},[3018],{"type":419,"value":2898},{"type":413,"tag":1270,"props":3020,"children":3021},{"style":1495},[3022],{"type":419,"value":2903},{"type":413,"tag":1270,"props":3024,"children":3025},{"style":1506},[3026],{"type":419,"value":461},{"type":413,"tag":1270,"props":3028,"children":3029},{"style":1495},[3030],{"type":419,"value":2912},{"type":413,"tag":1270,"props":3032,"children":3033},{"style":1506},[3034],{"type":419,"value":461},{"type":413,"tag":1270,"props":3036,"children":3037},{"style":1495},[3038],{"type":419,"value":2921},{"type":413,"tag":1270,"props":3040,"children":3041},{"style":1506},[3042],{"type":419,"value":2926},{"type":413,"tag":1270,"props":3044,"children":3045},{"style":1282},[3046],{"type":419,"value":2931},{"type":413,"tag":1270,"props":3048,"children":3049},{"style":1506},[3050],{"type":419,"value":2898},{"type":413,"tag":1270,"props":3052,"children":3053},{"style":1495},[3054],{"type":419,"value":2903},{"type":413,"tag":1270,"props":3056,"children":3057},{"style":1506},[3058],{"type":419,"value":461},{"type":413,"tag":1270,"props":3060,"children":3061},{"style":1495},[3062],{"type":419,"value":2912},{"type":413,"tag":1270,"props":3064,"children":3065},{"style":1506},[3066],{"type":419,"value":461},{"type":413,"tag":1270,"props":3068,"children":3069},{"style":1495},[3070],{"type":419,"value":2956},{"type":413,"tag":1270,"props":3072,"children":3073},{"style":1506},[3074],{"type":419,"value":2961},{"type":413,"tag":1270,"props":3076,"children":3077},{"style":1506},[3078],{"type":419,"value":1636},{"type":413,"tag":1270,"props":3080,"children":3081},{"style":1506},[3082],{"type":419,"value":1285},{"type":413,"tag":1270,"props":3084,"children":3085},{"style":1277},[3086],{"type":419,"value":3087}," AppServicePlanArgs",{"type":413,"tag":1270,"props":3089,"children":3090},{"style":1495},[3091],{"type":419,"value":1557},{"type":413,"tag":1270,"props":3093,"children":3094},{"class":1272,"line":1585},[3095,3099],{"type":413,"tag":1270,"props":3096,"children":3097},{"style":1506},[3098],{"type":419,"value":2898},{"type":413,"tag":1270,"props":3100,"children":3101},{"style":1495},[3102],{"type":419,"value":1557},{"type":413,"tag":1270,"props":3104,"children":3105},{"class":1272,"line":1598},[3106,3111,3116,3120,3124,3129,3133],{"type":413,"tag":1270,"props":3107,"children":3108},{"style":1495},[3109],{"type":419,"value":3110},"    ResourceGroupName ",{"type":413,"tag":1270,"props":3112,"children":3113},{"style":1506},[3114],{"type":419,"value":3115},"=",{"type":413,"tag":1270,"props":3117,"children":3118},{"style":1495},[3119],{"type":419,"value":2866},{"type":413,"tag":1270,"props":3121,"children":3122},{"style":1506},[3123],{"type":419,"value":461},{"type":413,"tag":1270,"props":3125,"children":3126},{"style":1495},[3127],{"type":419,"value":3128},"Name",{"type":413,"tag":1270,"props":3130,"children":3131},{"style":1506},[3132],{"type":419,"value":1636},{"type":413,"tag":1270,"props":3134,"children":3135},{"style":1495},[3136],{"type":419,"value":1557},{"type":413,"tag":1270,"props":3138,"children":3139},{"class":1272,"line":1681},[3140,3145,3149,3153,3158,3162,3166],{"type":413,"tag":1270,"props":3141,"children":3142},{"style":1495},[3143],{"type":419,"value":3144},"    Kind ",{"type":413,"tag":1270,"props":3146,"children":3147},{"style":1506},[3148],{"type":419,"value":3115},{"type":413,"tag":1270,"props":3150,"children":3151},{"style":1506},[3152],{"type":419,"value":1641},{"type":413,"tag":1270,"props":3154,"children":3155},{"style":1282},[3156],{"type":419,"value":3157},"App",{"type":413,"tag":1270,"props":3159,"children":3160},{"style":1506},[3161],{"type":419,"value":1622},{"type":413,"tag":1270,"props":3163,"children":3164},{"style":1506},[3165],{"type":419,"value":1636},{"type":413,"tag":1270,"props":3167,"children":3168},{"style":1495},[3169],{"type":419,"value":1557},{"type":413,"tag":1270,"props":3171,"children":3172},{"class":1272,"line":1707},[3173,3178,3182,3186,3191],{"type":413,"tag":1270,"props":3174,"children":3175},{"style":1495},[3176],{"type":419,"value":3177},"    Sku ",{"type":413,"tag":1270,"props":3179,"children":3180},{"style":1506},[3181],{"type":419,"value":3115},{"type":413,"tag":1270,"props":3183,"children":3184},{"style":1506},[3185],{"type":419,"value":1285},{"type":413,"tag":1270,"props":3187,"children":3188},{"style":1277},[3189],{"type":419,"value":3190}," SkuDescriptionArgs",{"type":413,"tag":1270,"props":3192,"children":3193},{"style":1495},[3194],{"type":419,"value":1557},{"type":413,"tag":1270,"props":3196,"children":3197},{"class":1272,"line":2244},[3198,3202],{"type":413,"tag":1270,"props":3199,"children":3200},{"style":1506},[3201],{"type":419,"value":1591},{"type":413,"tag":1270,"props":3203,"children":3204},{"style":1495},[3205],{"type":419,"value":1557},{"type":413,"tag":1270,"props":3207,"children":3208},{"class":1272,"line":2265},[3209,3214,3218,3222,3227,3231,3235],{"type":413,"tag":1270,"props":3210,"children":3211},{"style":1495},[3212],{"type":419,"value":3213},"        Tier ",{"type":413,"tag":1270,"props":3215,"children":3216},{"style":1506},[3217],{"type":419,"value":3115},{"type":413,"tag":1270,"props":3219,"children":3220},{"style":1506},[3221],{"type":419,"value":1641},{"type":413,"tag":1270,"props":3223,"children":3224},{"style":1282},[3225],{"type":419,"value":3226},"Basic",{"type":413,"tag":1270,"props":3228,"children":3229},{"style":1506},[3230],{"type":419,"value":1622},{"type":413,"tag":1270,"props":3232,"children":3233},{"style":1506},[3234],{"type":419,"value":1636},{"type":413,"tag":1270,"props":3236,"children":3237},{"style":1495},[3238],{"type":419,"value":1557},{"type":413,"tag":1270,"props":3240,"children":3241},{"class":1272,"line":2542},[3242,3247,3251,3255,3260,3264,3268],{"type":413,"tag":1270,"props":3243,"children":3244},{"style":1495},[3245],{"type":419,"value":3246},"        Name ",{"type":413,"tag":1270,"props":3248,"children":3249},{"style":1506},[3250],{"type":419,"value":3115},{"type":413,"tag":1270,"props":3252,"children":3253},{"style":1506},[3254],{"type":419,"value":1641},{"type":413,"tag":1270,"props":3256,"children":3257},{"style":1282},[3258],{"type":419,"value":3259},"B1",{"type":413,"tag":1270,"props":3261,"children":3262},{"style":1506},[3263],{"type":419,"value":1622},{"type":413,"tag":1270,"props":3265,"children":3266},{"style":1506},[3267],{"type":419,"value":1636},{"type":413,"tag":1270,"props":3269,"children":3270},{"style":1495},[3271],{"type":419,"value":1557},{"type":413,"tag":1270,"props":3273,"children":3274},{"class":1272,"line":2550},[3275,3280],{"type":413,"tag":1270,"props":3276,"children":3277},{"style":1506},[3278],{"type":419,"value":3279},"    },",{"type":413,"tag":1270,"props":3281,"children":3282},{"style":1495},[3283],{"type":419,"value":1557},{"type":413,"tag":1270,"props":3285,"children":3286},{"class":1272,"line":2609},[3287,3292],{"type":413,"tag":1270,"props":3288,"children":3289},{"style":1506},[3290],{"type":419,"value":3291},"});",{"type":413,"tag":1270,"props":3293,"children":3294},{"style":1495},[3295],{"type":419,"value":1557},{"type":413,"tag":1270,"props":3297,"children":3299},{"class":1272,"line":3298},13,[3300],{"type":413,"tag":1270,"props":3301,"children":3302},{"style":1495},[3303],{"type":419,"value":1557},{"type":413,"tag":1270,"props":3305,"children":3307},{"class":1272,"line":3306},14,[3308,3312,3317,3321,3325,3330,3334,3338,3343,3347,3351,3355,3359,3363,3367,3371,3375,3379,3383,3387,3391,3395,3399,3403,3407,3411,3416],{"type":413,"tag":1270,"props":3309,"children":3310},{"style":1277},[3311],{"type":419,"value":2861},{"type":413,"tag":1270,"props":3313,"children":3314},{"style":1277},[3315],{"type":419,"value":3316}," appService",{"type":413,"tag":1270,"props":3318,"children":3319},{"style":1506},[3320],{"type":419,"value":2692},{"type":413,"tag":1270,"props":3322,"children":3323},{"style":1506},[3324],{"type":419,"value":1285},{"type":413,"tag":1270,"props":3326,"children":3327},{"style":1277},[3328],{"type":419,"value":3329}," WebApp",{"type":413,"tag":1270,"props":3331,"children":3332},{"style":1506},[3333],{"type":419,"value":1542},{"type":413,"tag":1270,"props":3335,"children":3336},{"style":1506},[3337],{"type":419,"value":2888},{"type":413,"tag":1270,"props":3339,"children":3340},{"style":1282},[3341],{"type":419,"value":3342},"app-",{"type":413,"tag":1270,"props":3344,"children":3345},{"style":1506},[3346],{"type":419,"value":2898},{"type":413,"tag":1270,"props":3348,"children":3349},{"style":1495},[3350],{"type":419,"value":2903},{"type":413,"tag":1270,"props":3352,"children":3353},{"style":1506},[3354],{"type":419,"value":461},{"type":413,"tag":1270,"props":3356,"children":3357},{"style":1495},[3358],{"type":419,"value":2912},{"type":413,"tag":1270,"props":3360,"children":3361},{"style":1506},[3362],{"type":419,"value":461},{"type":413,"tag":1270,"props":3364,"children":3365},{"style":1495},[3366],{"type":419,"value":2921},{"type":413,"tag":1270,"props":3368,"children":3369},{"style":1506},[3370],{"type":419,"value":2926},{"type":413,"tag":1270,"props":3372,"children":3373},{"style":1282},[3374],{"type":419,"value":2931},{"type":413,"tag":1270,"props":3376,"children":3377},{"style":1506},[3378],{"type":419,"value":2898},{"type":413,"tag":1270,"props":3380,"children":3381},{"style":1495},[3382],{"type":419,"value":2903},{"type":413,"tag":1270,"props":3384,"children":3385},{"style":1506},[3386],{"type":419,"value":461},{"type":413,"tag":1270,"props":3388,"children":3389},{"style":1495},[3390],{"type":419,"value":2912},{"type":413,"tag":1270,"props":3392,"children":3393},{"style":1506},[3394],{"type":419,"value":461},{"type":413,"tag":1270,"props":3396,"children":3397},{"style":1495},[3398],{"type":419,"value":2956},{"type":413,"tag":1270,"props":3400,"children":3401},{"style":1506},[3402],{"type":419,"value":2961},{"type":413,"tag":1270,"props":3404,"children":3405},{"style":1506},[3406],{"type":419,"value":1636},{"type":413,"tag":1270,"props":3408,"children":3409},{"style":1506},[3410],{"type":419,"value":1285},{"type":413,"tag":1270,"props":3412,"children":3413},{"style":1277},[3414],{"type":419,"value":3415}," WebAppArgs",{"type":413,"tag":1270,"props":3417,"children":3418},{"style":1495},[3419],{"type":419,"value":3420},"   \n",{"type":413,"tag":1270,"props":3422,"children":3424},{"class":1272,"line":3423},15,[3425,3429],{"type":413,"tag":1270,"props":3426,"children":3427},{"style":1506},[3428],{"type":419,"value":2898},{"type":413,"tag":1270,"props":3430,"children":3431},{"style":1495},[3432],{"type":419,"value":3420},{"type":413,"tag":1270,"props":3434,"children":3436},{"class":1272,"line":3435},16,[3437,3441,3445,3449,3453,3457,3461],{"type":413,"tag":1270,"props":3438,"children":3439},{"style":1495},[3440],{"type":419,"value":3110},{"type":413,"tag":1270,"props":3442,"children":3443},{"style":1506},[3444],{"type":419,"value":3115},{"type":413,"tag":1270,"props":3446,"children":3447},{"style":1495},[3448],{"type":419,"value":2866},{"type":413,"tag":1270,"props":3450,"children":3451},{"style":1506},[3452],{"type":419,"value":461},{"type":413,"tag":1270,"props":3454,"children":3455},{"style":1495},[3456],{"type":419,"value":3128},{"type":413,"tag":1270,"props":3458,"children":3459},{"style":1506},[3460],{"type":419,"value":1636},{"type":413,"tag":1270,"props":3462,"children":3463},{"style":1495},[3464],{"type":419,"value":1557},{"type":413,"tag":1270,"props":3466,"children":3468},{"class":1272,"line":3467},17,[3469,3474,3478,3482,3486],{"type":413,"tag":1270,"props":3470,"children":3471},{"style":1495},[3472],{"type":419,"value":3473},"    ServerFarmId ",{"type":413,"tag":1270,"props":3475,"children":3476},{"style":1506},[3477],{"type":419,"value":3115},{"type":413,"tag":1270,"props":3479,"children":3480},{"style":1495},[3481],{"type":419,"value":2988},{"type":413,"tag":1270,"props":3483,"children":3484},{"style":1506},[3485],{"type":419,"value":461},{"type":413,"tag":1270,"props":3487,"children":3488},{"style":1495},[3489],{"type":419,"value":3490},"Id  \n",{"type":413,"tag":1270,"props":3492,"children":3494},{"class":1272,"line":3493},18,[3495],{"type":413,"tag":1270,"props":3496,"children":3497},{"style":1506},[3498],{"type":419,"value":3499},"});\n",{"type":413,"tag":422,"props":3501,"children":3502},{},[3503],{"type":419,"value":3504},"The code is quite simple, and because we are writing C# in our IDE, we have autocompletion and everything we need to make writing the infrastructure code easier.",{"type":413,"tag":509,"props":3506,"children":3507},{"icon":1150},[3508],{"type":413,"tag":422,"props":3509,"children":3510},{},[3511],{"type":419,"value":3512},"If you are used to Azure Bicep or ARM templates, the names of the classes or properties will look familiar to you. It's because we are using Azure Native, which is a Pulumi native provider that is generated from Azure APIs.",{"type":413,"tag":831,"props":3514,"children":3516},{"id":3515},"stack-outputs",[3517],{"type":419,"value":3518},"Stack outputs",{"type":413,"tag":422,"props":3520,"children":3521},{},[3522],{"type":419,"value":3523},"Provisioning the cloud resources we need is great but we have to think about the next step which is to deploy our API on these resources. So what will we need for that?",{"type":413,"tag":422,"props":3525,"children":3526},{},[3527,3529,3535],{"type":419,"value":3528},"First, we will need to have the name of the provisioned App Service. That's easy it's the property Name of the ",{"type":413,"tag":552,"props":3530,"children":3532},{"className":3531},[],[3533],{"type":419,"value":3534},"appService",{"type":419,"value":3536}," variable.",{"type":413,"tag":422,"props":3538,"children":3539},{},[3540,3542,3549],{"type":419,"value":3541},"Second, because we are going to use the Kudu API to zip deploy our application to the App Service, we will need the ",{"type":413,"tag":452,"props":3543,"children":3546},{"href":3544,"rel":3545},"https://github.com/projectkudu/kudu/wiki/Deployment-credentials#site-credentials-aka-publish-profile-credentials",[456],[3547],{"type":419,"value":3548},"site credentials (aka the Publishing Profile Credentials)",{"type":419,"value":3550},". These can be retrieved in the Pulumi program using the following code:",{"type":413,"tag":1260,"props":3552,"children":3554},{"className":1484,"code":3553,"language":326,"meta":401,"style":401},"var publishingCredentials = ListWebAppPublishingCredentials.Invoke(new()  \n{  \n    ResourceGroupName = resourceGroup.Name,  \n    Name = appService.Name  \n});\n",[3555],{"type":413,"tag":552,"props":3556,"children":3557},{"__ignoreMap":401},[3558,3597,3608,3639,3664],{"type":413,"tag":1270,"props":3559,"children":3560},{"class":1272,"line":1273},[3561,3565,3570,3574,3579,3583,3588,3593],{"type":413,"tag":1270,"props":3562,"children":3563},{"style":1277},[3564],{"type":419,"value":2861},{"type":413,"tag":1270,"props":3566,"children":3567},{"style":1277},[3568],{"type":419,"value":3569}," publishingCredentials",{"type":413,"tag":1270,"props":3571,"children":3572},{"style":1506},[3573],{"type":419,"value":2692},{"type":413,"tag":1270,"props":3575,"children":3576},{"style":1495},[3577],{"type":419,"value":3578}," ListWebAppPublishingCredentials",{"type":413,"tag":1270,"props":3580,"children":3581},{"style":1506},[3582],{"type":419,"value":461},{"type":413,"tag":1270,"props":3584,"children":3585},{"style":1534},[3586],{"type":419,"value":3587},"Invoke",{"type":413,"tag":1270,"props":3589,"children":3590},{"style":1506},[3591],{"type":419,"value":3592},"(new()",{"type":413,"tag":1270,"props":3594,"children":3595},{"style":1495},[3596],{"type":419,"value":1557},{"type":413,"tag":1270,"props":3598,"children":3599},{"class":1272,"line":742},[3600,3604],{"type":413,"tag":1270,"props":3601,"children":3602},{"style":1506},[3603],{"type":419,"value":2898},{"type":413,"tag":1270,"props":3605,"children":3606},{"style":1495},[3607],{"type":419,"value":1557},{"type":413,"tag":1270,"props":3609,"children":3610},{"class":1272,"line":995},[3611,3615,3619,3623,3627,3631,3635],{"type":413,"tag":1270,"props":3612,"children":3613},{"style":1495},[3614],{"type":419,"value":3110},{"type":413,"tag":1270,"props":3616,"children":3617},{"style":1506},[3618],{"type":419,"value":3115},{"type":413,"tag":1270,"props":3620,"children":3621},{"style":1495},[3622],{"type":419,"value":2866},{"type":413,"tag":1270,"props":3624,"children":3625},{"style":1506},[3626],{"type":419,"value":461},{"type":413,"tag":1270,"props":3628,"children":3629},{"style":1495},[3630],{"type":419,"value":3128},{"type":413,"tag":1270,"props":3632,"children":3633},{"style":1506},[3634],{"type":419,"value":1636},{"type":413,"tag":1270,"props":3636,"children":3637},{"style":1495},[3638],{"type":419,"value":1557},{"type":413,"tag":1270,"props":3640,"children":3641},{"class":1272,"line":1585},[3642,3647,3651,3655,3659],{"type":413,"tag":1270,"props":3643,"children":3644},{"style":1495},[3645],{"type":419,"value":3646},"    Name ",{"type":413,"tag":1270,"props":3648,"children":3649},{"style":1506},[3650],{"type":419,"value":3115},{"type":413,"tag":1270,"props":3652,"children":3653},{"style":1495},[3654],{"type":419,"value":3316},{"type":413,"tag":1270,"props":3656,"children":3657},{"style":1506},[3658],{"type":419,"value":461},{"type":413,"tag":1270,"props":3660,"children":3661},{"style":1495},[3662],{"type":419,"value":3663},"Name  \n",{"type":413,"tag":1270,"props":3665,"children":3666},{"class":1272,"line":1598},[3667],{"type":413,"tag":1270,"props":3668,"children":3669},{"style":1506},[3670],{"type":419,"value":3499},{"type":413,"tag":509,"props":3672,"children":3673},{"icon":511},[3674],{"type":413,"tag":422,"props":3675,"children":3676},{},[3677,3679,3686],{"type":419,"value":3678},"Using the Kudu API is just one of the ",{"type":413,"tag":452,"props":3680,"children":3683},{"href":3681,"rel":3682},"https://learn.microsoft.com/en-us/azure/app-service/deploy-zip?tabs=cli#deploy-a-zip-package",[456],[3684],{"type":419,"value":3685},"many ways",{"type":419,"value":3687}," to deploy a zip package to an App Service. I could have chosen another way like using the Azure CLI, in that case retrieving the site credentials would not have been needed.",{"type":413,"tag":422,"props":3689,"children":3690},{},[3691,3693,3700],{"type":419,"value":3692},"Pulumi, like Terraform, has this concept of stack ",{"type":413,"tag":452,"props":3694,"children":3697},{"href":3695,"rel":3696},"https://www.pulumi.com/docs/intro/concepts/stack/#outputs",[456],[3698],{"type":419,"value":3699},"output",{"type":419,"value":3701},"  where outputs are information about your stack/infrastructure that you want to expose. That is exactly what we need to export our App Service name and our site credentials so that they can be retrieved later by the Nuke code that will take care of the application deployment. To export these values we can return them in a Dictionary like that:",{"type":413,"tag":1260,"props":3703,"children":3705},{"className":1484,"code":3704,"language":326,"meta":401,"style":401},"return new Dictionary\u003Cstring, object?>  \n{  \n    [\"publishingUsername\"] = Output.CreateSecret(publishingCredentials.Apply(c => c.PublishingUserName)),  \n    [\"publishingUserPassword\"] = Output.CreateSecret(publishingCredentials.Apply(c => c.PublishingPassword)),  \n    [\"appServiceName\"] = appService.Name  \n};\n",[3706],{"type":413,"tag":552,"props":3707,"children":3708},{"__ignoreMap":401},[3709,3750,3761,3859,3948,3988],{"type":413,"tag":1270,"props":3710,"children":3711},{"class":1272,"line":1273},[3712,3718,3722,3727,3732,3737,3741,3746],{"type":413,"tag":1270,"props":3713,"children":3715},{"style":3714},"--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF;--shiki-light-font-style:italic;--shiki-default-font-style:italic;--shiki-dark-font-style:italic",[3716],{"type":419,"value":3717},"return",{"type":413,"tag":1270,"props":3719,"children":3720},{"style":1506},[3721],{"type":419,"value":1285},{"type":413,"tag":1270,"props":3723,"children":3724},{"style":1277},[3725],{"type":419,"value":3726}," Dictionary",{"type":413,"tag":1270,"props":3728,"children":3729},{"style":1506},[3730],{"type":419,"value":3731},"\u003C",{"type":413,"tag":1270,"props":3733,"children":3734},{"style":1506},[3735],{"type":419,"value":3736},"string",{"type":413,"tag":1270,"props":3738,"children":3739},{"style":1506},[3740],{"type":419,"value":1636},{"type":413,"tag":1270,"props":3742,"children":3743},{"style":1506},[3744],{"type":419,"value":3745}," object?>",{"type":413,"tag":1270,"props":3747,"children":3748},{"style":1495},[3749],{"type":419,"value":1557},{"type":413,"tag":1270,"props":3751,"children":3752},{"class":1272,"line":742},[3753,3757],{"type":413,"tag":1270,"props":3754,"children":3755},{"style":1506},[3756],{"type":419,"value":2898},{"type":413,"tag":1270,"props":3758,"children":3759},{"style":1495},[3760],{"type":419,"value":1557},{"type":413,"tag":1270,"props":3762,"children":3763},{"class":1272,"line":995},[3764,3769,3773,3778,3782,3787,3791,3796,3800,3805,3809,3814,3818,3823,3827,3832,3836,3841,3845,3850,3855],{"type":413,"tag":1270,"props":3765,"children":3766},{"style":1506},[3767],{"type":419,"value":3768},"    [",{"type":413,"tag":1270,"props":3770,"children":3771},{"style":1506},[3772],{"type":419,"value":1622},{"type":413,"tag":1270,"props":3774,"children":3775},{"style":1282},[3776],{"type":419,"value":3777},"publishingUsername",{"type":413,"tag":1270,"props":3779,"children":3780},{"style":1506},[3781],{"type":419,"value":1622},{"type":413,"tag":1270,"props":3783,"children":3784},{"style":1506},[3785],{"type":419,"value":3786},"]",{"type":413,"tag":1270,"props":3788,"children":3789},{"style":1506},[3790],{"type":419,"value":2692},{"type":413,"tag":1270,"props":3792,"children":3793},{"style":1495},[3794],{"type":419,"value":3795}," Output",{"type":413,"tag":1270,"props":3797,"children":3798},{"style":1506},[3799],{"type":419,"value":461},{"type":413,"tag":1270,"props":3801,"children":3802},{"style":1534},[3803],{"type":419,"value":3804},"CreateSecret",{"type":413,"tag":1270,"props":3806,"children":3807},{"style":1506},[3808],{"type":419,"value":1542},{"type":413,"tag":1270,"props":3810,"children":3811},{"style":1495},[3812],{"type":419,"value":3813},"publishingCredentials",{"type":413,"tag":1270,"props":3815,"children":3816},{"style":1506},[3817],{"type":419,"value":461},{"type":413,"tag":1270,"props":3819,"children":3820},{"style":1534},[3821],{"type":419,"value":3822},"Apply",{"type":413,"tag":1270,"props":3824,"children":3825},{"style":1506},[3826],{"type":419,"value":1542},{"type":413,"tag":1270,"props":3828,"children":3829},{"style":1277},[3830],{"type":419,"value":3831},"c",{"type":413,"tag":1270,"props":3833,"children":3834},{"style":1506},[3835],{"type":419,"value":1509},{"type":413,"tag":1270,"props":3837,"children":3838},{"style":1495},[3839],{"type":419,"value":3840}," c",{"type":413,"tag":1270,"props":3842,"children":3843},{"style":1506},[3844],{"type":419,"value":461},{"type":413,"tag":1270,"props":3846,"children":3847},{"style":1495},[3848],{"type":419,"value":3849},"PublishingUserName",{"type":413,"tag":1270,"props":3851,"children":3852},{"style":1506},[3853],{"type":419,"value":3854},")),",{"type":413,"tag":1270,"props":3856,"children":3857},{"style":1495},[3858],{"type":419,"value":1557},{"type":413,"tag":1270,"props":3860,"children":3861},{"class":1272,"line":1585},[3862,3866,3870,3875,3879,3883,3887,3891,3895,3899,3903,3907,3911,3915,3919,3923,3927,3931,3935,3940,3944],{"type":413,"tag":1270,"props":3863,"children":3864},{"style":1506},[3865],{"type":419,"value":3768},{"type":413,"tag":1270,"props":3867,"children":3868},{"style":1506},[3869],{"type":419,"value":1622},{"type":413,"tag":1270,"props":3871,"children":3872},{"style":1282},[3873],{"type":419,"value":3874},"publishingUserPassword",{"type":413,"tag":1270,"props":3876,"children":3877},{"style":1506},[3878],{"type":419,"value":1622},{"type":413,"tag":1270,"props":3880,"children":3881},{"style":1506},[3882],{"type":419,"value":3786},{"type":413,"tag":1270,"props":3884,"children":3885},{"style":1506},[3886],{"type":419,"value":2692},{"type":413,"tag":1270,"props":3888,"children":3889},{"style":1495},[3890],{"type":419,"value":3795},{"type":413,"tag":1270,"props":3892,"children":3893},{"style":1506},[3894],{"type":419,"value":461},{"type":413,"tag":1270,"props":3896,"children":3897},{"style":1534},[3898],{"type":419,"value":3804},{"type":413,"tag":1270,"props":3900,"children":3901},{"style":1506},[3902],{"type":419,"value":1542},{"type":413,"tag":1270,"props":3904,"children":3905},{"style":1495},[3906],{"type":419,"value":3813},{"type":413,"tag":1270,"props":3908,"children":3909},{"style":1506},[3910],{"type":419,"value":461},{"type":413,"tag":1270,"props":3912,"children":3913},{"style":1534},[3914],{"type":419,"value":3822},{"type":413,"tag":1270,"props":3916,"children":3917},{"style":1506},[3918],{"type":419,"value":1542},{"type":413,"tag":1270,"props":3920,"children":3921},{"style":1277},[3922],{"type":419,"value":3831},{"type":413,"tag":1270,"props":3924,"children":3925},{"style":1506},[3926],{"type":419,"value":1509},{"type":413,"tag":1270,"props":3928,"children":3929},{"style":1495},[3930],{"type":419,"value":3840},{"type":413,"tag":1270,"props":3932,"children":3933},{"style":1506},[3934],{"type":419,"value":461},{"type":413,"tag":1270,"props":3936,"children":3937},{"style":1495},[3938],{"type":419,"value":3939},"PublishingPassword",{"type":413,"tag":1270,"props":3941,"children":3942},{"style":1506},[3943],{"type":419,"value":3854},{"type":413,"tag":1270,"props":3945,"children":3946},{"style":1495},[3947],{"type":419,"value":1557},{"type":413,"tag":1270,"props":3949,"children":3950},{"class":1272,"line":1598},[3951,3955,3959,3964,3968,3972,3976,3980,3984],{"type":413,"tag":1270,"props":3952,"children":3953},{"style":1506},[3954],{"type":419,"value":3768},{"type":413,"tag":1270,"props":3956,"children":3957},{"style":1506},[3958],{"type":419,"value":1622},{"type":413,"tag":1270,"props":3960,"children":3961},{"style":1282},[3962],{"type":419,"value":3963},"appServiceName",{"type":413,"tag":1270,"props":3965,"children":3966},{"style":1506},[3967],{"type":419,"value":1622},{"type":413,"tag":1270,"props":3969,"children":3970},{"style":1506},[3971],{"type":419,"value":3786},{"type":413,"tag":1270,"props":3973,"children":3974},{"style":1506},[3975],{"type":419,"value":2692},{"type":413,"tag":1270,"props":3977,"children":3978},{"style":1495},[3979],{"type":419,"value":3316},{"type":413,"tag":1270,"props":3981,"children":3982},{"style":1506},[3983],{"type":419,"value":461},{"type":413,"tag":1270,"props":3985,"children":3986},{"style":1495},[3987],{"type":419,"value":3663},{"type":413,"tag":1270,"props":3989,"children":3990},{"class":1272,"line":1681},[3991],{"type":413,"tag":1270,"props":3992,"children":3993},{"style":1506},[3994],{"type":419,"value":3995},"};\n",{"type":413,"tag":422,"props":3997,"children":3998},{},[3999,4001,4007],{"type":419,"value":4000},"You might notice that we use the ",{"type":413,"tag":552,"props":4002,"children":4004},{"className":4003},[],[4005],{"type":419,"value":4006},"Output.CreateSecret",{"type":419,"value":4008}," method to create outputs for our publishing credentials. The aim is to tell Pulumi to treat these values as secrets, and that's what it will do by encrypting them in the state file for extra protection (that is not something Terraform does by the way).",{"type":413,"tag":831,"props":4010,"children":4012},{"id":4011},"implementing-the-provision-infrastructure-step",[4013],{"type":419,"value":4014},"Implementing the Provision Infrastructure step",{"type":413,"tag":422,"props":4016,"children":4017},{},[4018,4020,4026],{"type":419,"value":4019},"To deploy the infrastructure, we can use the ",{"type":413,"tag":552,"props":4021,"children":4023},{"className":4022},[],[4024],{"type":419,"value":4025},"pulumi up",{"type":419,"value":4027}," command. We will write the code in a fluent way as we did with the dotnet CLI:",{"type":413,"tag":1260,"props":4029,"children":4031},{"className":1484,"code":4030,"language":326,"meta":401,"style":401},"AbsolutePath InfrastructureDirectory => RootDirectory / \"infra\";\n\nTarget ProvisionInfra => _ => _  \n    .Description(\"Provision the infrastructure on Azure\")  \n    .Executes(() =>  \n    {  \n        PulumiTasks.PulumiUp(_ => _  \n            .SetCwd(InfrastructureDirectory)  \n            .SetStack(\"dev\")  \n            .EnableSkipPreview());  \n    });\n",[4032],{"type":413,"tag":552,"props":4033,"children":4034},{"__ignoreMap":401},[4035,4074,4083,4111,4148,4171,4182,4215,4243,4280,4300],{"type":413,"tag":1270,"props":4036,"children":4037},{"class":1272,"line":1273},[4038,4042,4046,4050,4054,4058,4062,4066,4070],{"type":413,"tag":1270,"props":4039,"children":4040},{"style":1495},[4041],{"type":419,"value":1733},{"type":413,"tag":1270,"props":4043,"children":4044},{"style":1277},[4045],{"type":419,"value":1793},{"type":413,"tag":1270,"props":4047,"children":4048},{"style":1506},[4049],{"type":419,"value":1509},{"type":413,"tag":1270,"props":4051,"children":4052},{"style":1495},[4053],{"type":419,"value":1747},{"type":413,"tag":1270,"props":4055,"children":4056},{"style":1506},[4057],{"type":419,"value":1752},{"type":413,"tag":1270,"props":4059,"children":4060},{"style":1506},[4061],{"type":419,"value":1641},{"type":413,"tag":1270,"props":4063,"children":4064},{"style":1282},[4065],{"type":419,"value":1814},{"type":413,"tag":1270,"props":4067,"children":4068},{"style":1506},[4069],{"type":419,"value":1622},{"type":413,"tag":1270,"props":4071,"children":4072},{"style":1506},[4073],{"type":419,"value":1874},{"type":413,"tag":1270,"props":4075,"children":4076},{"class":1272,"line":742},[4077],{"type":413,"tag":1270,"props":4078,"children":4080},{"emptyLinePlaceholder":4079},true,[4081],{"type":419,"value":4082},"\n",{"type":413,"tag":1270,"props":4084,"children":4085},{"class":1272,"line":995},[4086,4090,4095,4099,4103,4107],{"type":413,"tag":1270,"props":4087,"children":4088},{"style":1495},[4089],{"type":419,"value":1498},{"type":413,"tag":1270,"props":4091,"children":4092},{"style":1277},[4093],{"type":419,"value":4094},"ProvisionInfra",{"type":413,"tag":1270,"props":4096,"children":4097},{"style":1506},[4098],{"type":419,"value":1509},{"type":413,"tag":1270,"props":4100,"children":4101},{"style":1277},[4102],{"type":419,"value":1514},{"type":413,"tag":1270,"props":4104,"children":4105},{"style":1506},[4106],{"type":419,"value":1509},{"type":413,"tag":1270,"props":4108,"children":4109},{"style":1495},[4110],{"type":419,"value":1523},{"type":413,"tag":1270,"props":4112,"children":4113},{"class":1272,"line":1585},[4114,4118,4123,4127,4131,4136,4140,4144],{"type":413,"tag":1270,"props":4115,"children":4116},{"style":1506},[4117],{"type":419,"value":1531},{"type":413,"tag":1270,"props":4119,"children":4120},{"style":1534},[4121],{"type":419,"value":4122},"Description",{"type":413,"tag":1270,"props":4124,"children":4125},{"style":1506},[4126],{"type":419,"value":1542},{"type":413,"tag":1270,"props":4128,"children":4129},{"style":1506},[4130],{"type":419,"value":1622},{"type":413,"tag":1270,"props":4132,"children":4133},{"style":1282},[4134],{"type":419,"value":4135},"Provision the infrastructure on Azure",{"type":413,"tag":1270,"props":4137,"children":4138},{"style":1506},[4139],{"type":419,"value":1622},{"type":413,"tag":1270,"props":4141,"children":4142},{"style":1506},[4143],{"type":419,"value":1552},{"type":413,"tag":1270,"props":4145,"children":4146},{"style":1495},[4147],{"type":419,"value":1557},{"type":413,"tag":1270,"props":4149,"children":4150},{"class":1272,"line":1598},[4151,4155,4159,4163,4167],{"type":413,"tag":1270,"props":4152,"children":4153},{"style":1506},[4154],{"type":419,"value":1531},{"type":413,"tag":1270,"props":4156,"children":4157},{"style":1534},[4158],{"type":419,"value":1569},{"type":413,"tag":1270,"props":4160,"children":4161},{"style":1506},[4162],{"type":419,"value":1574},{"type":413,"tag":1270,"props":4164,"children":4165},{"style":1506},[4166],{"type":419,"value":1509},{"type":413,"tag":1270,"props":4168,"children":4169},{"style":1495},[4170],{"type":419,"value":1557},{"type":413,"tag":1270,"props":4172,"children":4173},{"class":1272,"line":1681},[4174,4178],{"type":413,"tag":1270,"props":4175,"children":4176},{"style":1506},[4177],{"type":419,"value":1591},{"type":413,"tag":1270,"props":4179,"children":4180},{"style":1495},[4181],{"type":419,"value":1557},{"type":413,"tag":1270,"props":4183,"children":4184},{"class":1272,"line":1707},[4185,4190,4194,4199,4203,4207,4211],{"type":413,"tag":1270,"props":4186,"children":4187},{"style":1495},[4188],{"type":419,"value":4189},"        PulumiTasks",{"type":413,"tag":1270,"props":4191,"children":4192},{"style":1506},[4193],{"type":419,"value":461},{"type":413,"tag":1270,"props":4195,"children":4196},{"style":1534},[4197],{"type":419,"value":4198},"PulumiUp",{"type":413,"tag":1270,"props":4200,"children":4201},{"style":1506},[4202],{"type":419,"value":1542},{"type":413,"tag":1270,"props":4204,"children":4205},{"style":1277},[4206],{"type":419,"value":1987},{"type":413,"tag":1270,"props":4208,"children":4209},{"style":1506},[4210],{"type":419,"value":1509},{"type":413,"tag":1270,"props":4212,"children":4213},{"style":1495},[4214],{"type":419,"value":1523},{"type":413,"tag":1270,"props":4216,"children":4217},{"class":1272,"line":2244},[4218,4222,4227,4231,4235,4239],{"type":413,"tag":1270,"props":4219,"children":4220},{"style":1506},[4221],{"type":419,"value":2193},{"type":413,"tag":1270,"props":4223,"children":4224},{"style":1534},[4225],{"type":419,"value":4226},"SetCwd",{"type":413,"tag":1270,"props":4228,"children":4229},{"style":1506},[4230],{"type":419,"value":1542},{"type":413,"tag":1270,"props":4232,"children":4233},{"style":1495},[4234],{"type":419,"value":1793},{"type":413,"tag":1270,"props":4236,"children":4237},{"style":1506},[4238],{"type":419,"value":1552},{"type":413,"tag":1270,"props":4240,"children":4241},{"style":1495},[4242],{"type":419,"value":1557},{"type":413,"tag":1270,"props":4244,"children":4245},{"class":1272,"line":2265},[4246,4250,4255,4259,4263,4268,4272,4276],{"type":413,"tag":1270,"props":4247,"children":4248},{"style":1506},[4249],{"type":419,"value":2193},{"type":413,"tag":1270,"props":4251,"children":4252},{"style":1534},[4253],{"type":419,"value":4254},"SetStack",{"type":413,"tag":1270,"props":4256,"children":4257},{"style":1506},[4258],{"type":419,"value":1542},{"type":413,"tag":1270,"props":4260,"children":4261},{"style":1506},[4262],{"type":419,"value":1622},{"type":413,"tag":1270,"props":4264,"children":4265},{"style":1282},[4266],{"type":419,"value":4267},"dev",{"type":413,"tag":1270,"props":4269,"children":4270},{"style":1506},[4271],{"type":419,"value":1622},{"type":413,"tag":1270,"props":4273,"children":4274},{"style":1506},[4275],{"type":419,"value":1552},{"type":413,"tag":1270,"props":4277,"children":4278},{"style":1495},[4279],{"type":419,"value":1557},{"type":413,"tag":1270,"props":4281,"children":4282},{"class":1272,"line":2542},[4283,4287,4292,4296],{"type":413,"tag":1270,"props":4284,"children":4285},{"style":1506},[4286],{"type":419,"value":2193},{"type":413,"tag":1270,"props":4288,"children":4289},{"style":1534},[4290],{"type":419,"value":4291},"EnableSkipPreview",{"type":413,"tag":1270,"props":4293,"children":4294},{"style":1506},[4295],{"type":419,"value":2258},{"type":413,"tag":1270,"props":4297,"children":4298},{"style":1495},[4299],{"type":419,"value":1557},{"type":413,"tag":1270,"props":4301,"children":4302},{"class":1272,"line":2550},[4303],{"type":413,"tag":1270,"props":4304,"children":4305},{"style":1506},[4306],{"type":419,"value":1713},{"type":413,"tag":414,"props":4308,"children":4310},{"id":4309},"deploying-the-aspnet-core-api-to-azure-app-service",[4311],{"type":419,"value":4312},"Deploying the ASP.NET Core API to Azure App Service",{"type":413,"tag":422,"props":4314,"children":4315},{},[4316,4318,4324],{"type":419,"value":4317},"I previously said we were going to use the Kudu API to deploy our application. You can check the ",{"type":413,"tag":452,"props":4319,"children":4322},{"href":4320,"rel":4321},"https://github.com/projectkudu/kudu/wiki/Deploying-from-a-zip-file-or-url",[456],[4323],{"type":419,"value":1333},{"type":419,"value":4325}," about that but concretely we will do a POST request to the zipdeploy endpoint using Basic authentication.",{"type":413,"tag":422,"props":4327,"children":4328},{},[4329,4331,4337],{"type":419,"value":4330},"To retrieve a stack output, we can use the ",{"type":413,"tag":552,"props":4332,"children":4334},{"className":4333},[],[4335],{"type":419,"value":4336},"pulumi stack output",{"type":419,"value":4338}," command. To avoid duplicating the code I wrote a short method:",{"type":413,"tag":1260,"props":4340,"children":4342},{"className":1484,"code":4341,"language":326,"meta":401,"style":401},"string GetPulumiOutput(string outputName)  \n{  \n    return PulumiTasks.PulumiStackOutput(_ => _  \n            .SetCwd(InfrastructureDirectory)  \n            .SetPropertyName(outputName)  \n            .EnableShowSecrets()\n            .DisableProcessLogOutput())  \n        .StdToText();  \n}\n",[4343],{"type":413,"tag":552,"props":4344,"children":4345},{"__ignoreMap":401},[4346,4379,4390,4428,4455,4484,4501,4522,4544],{"type":413,"tag":1270,"props":4347,"children":4348},{"class":1272,"line":1273},[4349,4353,4358,4362,4366,4371,4375],{"type":413,"tag":1270,"props":4350,"children":4351},{"style":1506},[4352],{"type":419,"value":3736},{"type":413,"tag":1270,"props":4354,"children":4355},{"style":1534},[4356],{"type":419,"value":4357}," GetPulumiOutput",{"type":413,"tag":1270,"props":4359,"children":4360},{"style":1506},[4361],{"type":419,"value":1542},{"type":413,"tag":1270,"props":4363,"children":4364},{"style":1506},[4365],{"type":419,"value":3736},{"type":413,"tag":1270,"props":4367,"children":4368},{"style":1277},[4369],{"type":419,"value":4370}," outputName",{"type":413,"tag":1270,"props":4372,"children":4373},{"style":1506},[4374],{"type":419,"value":1552},{"type":413,"tag":1270,"props":4376,"children":4377},{"style":1495},[4378],{"type":419,"value":1557},{"type":413,"tag":1270,"props":4380,"children":4381},{"class":1272,"line":742},[4382,4386],{"type":413,"tag":1270,"props":4383,"children":4384},{"style":1506},[4385],{"type":419,"value":2898},{"type":413,"tag":1270,"props":4387,"children":4388},{"style":1495},[4389],{"type":419,"value":1557},{"type":413,"tag":1270,"props":4391,"children":4392},{"class":1272,"line":995},[4393,4398,4403,4407,4412,4416,4420,4424],{"type":413,"tag":1270,"props":4394,"children":4395},{"style":3714},[4396],{"type":419,"value":4397},"    return",{"type":413,"tag":1270,"props":4399,"children":4400},{"style":1495},[4401],{"type":419,"value":4402}," PulumiTasks",{"type":413,"tag":1270,"props":4404,"children":4405},{"style":1506},[4406],{"type":419,"value":461},{"type":413,"tag":1270,"props":4408,"children":4409},{"style":1534},[4410],{"type":419,"value":4411},"PulumiStackOutput",{"type":413,"tag":1270,"props":4413,"children":4414},{"style":1506},[4415],{"type":419,"value":1542},{"type":413,"tag":1270,"props":4417,"children":4418},{"style":1277},[4419],{"type":419,"value":1987},{"type":413,"tag":1270,"props":4421,"children":4422},{"style":1506},[4423],{"type":419,"value":1509},{"type":413,"tag":1270,"props":4425,"children":4426},{"style":1495},[4427],{"type":419,"value":1523},{"type":413,"tag":1270,"props":4429,"children":4430},{"class":1272,"line":1585},[4431,4435,4439,4443,4447,4451],{"type":413,"tag":1270,"props":4432,"children":4433},{"style":1506},[4434],{"type":419,"value":2193},{"type":413,"tag":1270,"props":4436,"children":4437},{"style":1534},[4438],{"type":419,"value":4226},{"type":413,"tag":1270,"props":4440,"children":4441},{"style":1506},[4442],{"type":419,"value":1542},{"type":413,"tag":1270,"props":4444,"children":4445},{"style":1495},[4446],{"type":419,"value":1793},{"type":413,"tag":1270,"props":4448,"children":4449},{"style":1506},[4450],{"type":419,"value":1552},{"type":413,"tag":1270,"props":4452,"children":4453},{"style":1495},[4454],{"type":419,"value":1557},{"type":413,"tag":1270,"props":4456,"children":4457},{"class":1272,"line":1598},[4458,4462,4467,4471,4476,4480],{"type":413,"tag":1270,"props":4459,"children":4460},{"style":1506},[4461],{"type":419,"value":2193},{"type":413,"tag":1270,"props":4463,"children":4464},{"style":1534},[4465],{"type":419,"value":4466},"SetPropertyName",{"type":413,"tag":1270,"props":4468,"children":4469},{"style":1506},[4470],{"type":419,"value":1542},{"type":413,"tag":1270,"props":4472,"children":4473},{"style":1495},[4474],{"type":419,"value":4475},"outputName",{"type":413,"tag":1270,"props":4477,"children":4478},{"style":1506},[4479],{"type":419,"value":1552},{"type":413,"tag":1270,"props":4481,"children":4482},{"style":1495},[4483],{"type":419,"value":1557},{"type":413,"tag":1270,"props":4485,"children":4486},{"class":1272,"line":1681},[4487,4491,4496],{"type":413,"tag":1270,"props":4488,"children":4489},{"style":1506},[4490],{"type":419,"value":2193},{"type":413,"tag":1270,"props":4492,"children":4493},{"style":1534},[4494],{"type":419,"value":4495},"EnableShowSecrets",{"type":413,"tag":1270,"props":4497,"children":4498},{"style":1506},[4499],{"type":419,"value":4500},"()\n",{"type":413,"tag":1270,"props":4502,"children":4503},{"class":1272,"line":1707},[4504,4508,4513,4518],{"type":413,"tag":1270,"props":4505,"children":4506},{"style":1506},[4507],{"type":419,"value":2193},{"type":413,"tag":1270,"props":4509,"children":4510},{"style":1534},[4511],{"type":419,"value":4512},"DisableProcessLogOutput",{"type":413,"tag":1270,"props":4514,"children":4515},{"style":1506},[4516],{"type":419,"value":4517},"())",{"type":413,"tag":1270,"props":4519,"children":4520},{"style":1495},[4521],{"type":419,"value":1557},{"type":413,"tag":1270,"props":4523,"children":4524},{"class":1272,"line":2244},[4525,4530,4535,4540],{"type":413,"tag":1270,"props":4526,"children":4527},{"style":1506},[4528],{"type":419,"value":4529},"        .",{"type":413,"tag":1270,"props":4531,"children":4532},{"style":1534},[4533],{"type":419,"value":4534},"StdToText",{"type":413,"tag":1270,"props":4536,"children":4537},{"style":1506},[4538],{"type":419,"value":4539},"();",{"type":413,"tag":1270,"props":4541,"children":4542},{"style":1495},[4543],{"type":419,"value":1557},{"type":413,"tag":1270,"props":4545,"children":4546},{"class":1272,"line":2265},[4547],{"type":413,"tag":1270,"props":4548,"children":4549},{"style":1506},[4550],{"type":419,"value":4551},"}\n",{"type":413,"tag":422,"props":4553,"children":4554},{},[4555],{"type":419,"value":4556},"The step itself is not very complicated, just standard C# code using an HttpClient to send a POST request (with our application package as the content) to the Kudu API.",{"type":413,"tag":1260,"props":4558,"children":4560},{"className":1484,"code":4559,"language":326,"meta":401,"style":401},"Target Deploy => _ => _  \n    .DependsOn(Publish)  \n    .After(ProvisionInfra)  \n    .Executes(async () =>  \n    {  \n        var publishingUsername = GetPulumiOutput(\"publishingUsername\");  \n        var publishingUserPassword = GetPulumiOutput(\"publishingUserPassword\");  \n        var base64Auth = Convert.ToBase64String(Encoding.Default.GetBytes($\"{publishingUsername}:{publishingUserPassword}\"));  \n  \n        await using var package = File.OpenRead(ArtifactsDirectory / \"api.zip\");  \n        using var httpClient = new HttpClient();  \n        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Basic\", base64Auth);  \n        await httpClient.PostAsync($\"https://{GetPulumiOutput(\"appServiceName\")}.scm.azurewebsites.net/api/zipdeploy\",  \n            new StreamContent(package));  \n    });\n",[4561],{"type":413,"tag":552,"props":4562,"children":4563},{"__ignoreMap":401},[4564,4592,4619,4647,4680,4691,4736,4780,4878,4885,4959,4997,5068,5141,5171],{"type":413,"tag":1270,"props":4565,"children":4566},{"class":1272,"line":1273},[4567,4571,4576,4580,4584,4588],{"type":413,"tag":1270,"props":4568,"children":4569},{"style":1495},[4570],{"type":419,"value":1498},{"type":413,"tag":1270,"props":4572,"children":4573},{"style":1277},[4574],{"type":419,"value":4575},"Deploy",{"type":413,"tag":1270,"props":4577,"children":4578},{"style":1506},[4579],{"type":419,"value":1509},{"type":413,"tag":1270,"props":4581,"children":4582},{"style":1277},[4583],{"type":419,"value":1514},{"type":413,"tag":1270,"props":4585,"children":4586},{"style":1506},[4587],{"type":419,"value":1509},{"type":413,"tag":1270,"props":4589,"children":4590},{"style":1495},[4591],{"type":419,"value":1523},{"type":413,"tag":1270,"props":4593,"children":4594},{"class":1272,"line":742},[4595,4599,4603,4607,4611,4615],{"type":413,"tag":1270,"props":4596,"children":4597},{"style":1506},[4598],{"type":419,"value":1531},{"type":413,"tag":1270,"props":4600,"children":4601},{"style":1534},[4602],{"type":419,"value":2111},{"type":413,"tag":1270,"props":4604,"children":4605},{"style":1506},[4606],{"type":419,"value":1542},{"type":413,"tag":1270,"props":4608,"children":4609},{"style":1495},[4610],{"type":419,"value":2315},{"type":413,"tag":1270,"props":4612,"children":4613},{"style":1506},[4614],{"type":419,"value":1552},{"type":413,"tag":1270,"props":4616,"children":4617},{"style":1495},[4618],{"type":419,"value":1557},{"type":413,"tag":1270,"props":4620,"children":4621},{"class":1272,"line":995},[4622,4626,4631,4635,4639,4643],{"type":413,"tag":1270,"props":4623,"children":4624},{"style":1506},[4625],{"type":419,"value":1531},{"type":413,"tag":1270,"props":4627,"children":4628},{"style":1534},[4629],{"type":419,"value":4630},"After",{"type":413,"tag":1270,"props":4632,"children":4633},{"style":1506},[4634],{"type":419,"value":1542},{"type":413,"tag":1270,"props":4636,"children":4637},{"style":1495},[4638],{"type":419,"value":4094},{"type":413,"tag":1270,"props":4640,"children":4641},{"style":1506},[4642],{"type":419,"value":1552},{"type":413,"tag":1270,"props":4644,"children":4645},{"style":1495},[4646],{"type":419,"value":1557},{"type":413,"tag":1270,"props":4648,"children":4649},{"class":1272,"line":1585},[4650,4654,4658,4662,4667,4672,4676],{"type":413,"tag":1270,"props":4651,"children":4652},{"style":1506},[4653],{"type":419,"value":1531},{"type":413,"tag":1270,"props":4655,"children":4656},{"style":1534},[4657],{"type":419,"value":1569},{"type":413,"tag":1270,"props":4659,"children":4660},{"style":1506},[4661],{"type":419,"value":1542},{"type":413,"tag":1270,"props":4663,"children":4664},{"style":2713},[4665],{"type":419,"value":4666},"async",{"type":413,"tag":1270,"props":4668,"children":4669},{"style":1506},[4670],{"type":419,"value":4671}," ()",{"type":413,"tag":1270,"props":4673,"children":4674},{"style":1506},[4675],{"type":419,"value":1509},{"type":413,"tag":1270,"props":4677,"children":4678},{"style":1495},[4679],{"type":419,"value":1557},{"type":413,"tag":1270,"props":4681,"children":4682},{"class":1272,"line":1598},[4683,4687],{"type":413,"tag":1270,"props":4684,"children":4685},{"style":1506},[4686],{"type":419,"value":1591},{"type":413,"tag":1270,"props":4688,"children":4689},{"style":1495},[4690],{"type":419,"value":1557},{"type":413,"tag":1270,"props":4692,"children":4693},{"class":1272,"line":1681},[4694,4699,4704,4708,4712,4716,4720,4724,4728,4732],{"type":413,"tag":1270,"props":4695,"children":4696},{"style":1277},[4697],{"type":419,"value":4698},"        var",{"type":413,"tag":1270,"props":4700,"children":4701},{"style":1277},[4702],{"type":419,"value":4703}," publishingUsername",{"type":413,"tag":1270,"props":4705,"children":4706},{"style":1506},[4707],{"type":419,"value":2692},{"type":413,"tag":1270,"props":4709,"children":4710},{"style":1534},[4711],{"type":419,"value":4357},{"type":413,"tag":1270,"props":4713,"children":4714},{"style":1506},[4715],{"type":419,"value":1542},{"type":413,"tag":1270,"props":4717,"children":4718},{"style":1506},[4719],{"type":419,"value":1622},{"type":413,"tag":1270,"props":4721,"children":4722},{"style":1282},[4723],{"type":419,"value":3777},{"type":413,"tag":1270,"props":4725,"children":4726},{"style":1506},[4727],{"type":419,"value":1622},{"type":413,"tag":1270,"props":4729,"children":4730},{"style":1506},[4731],{"type":419,"value":1674},{"type":413,"tag":1270,"props":4733,"children":4734},{"style":1495},[4735],{"type":419,"value":1557},{"type":413,"tag":1270,"props":4737,"children":4738},{"class":1272,"line":1707},[4739,4743,4748,4752,4756,4760,4764,4768,4772,4776],{"type":413,"tag":1270,"props":4740,"children":4741},{"style":1277},[4742],{"type":419,"value":4698},{"type":413,"tag":1270,"props":4744,"children":4745},{"style":1277},[4746],{"type":419,"value":4747}," publishingUserPassword",{"type":413,"tag":1270,"props":4749,"children":4750},{"style":1506},[4751],{"type":419,"value":2692},{"type":413,"tag":1270,"props":4753,"children":4754},{"style":1534},[4755],{"type":419,"value":4357},{"type":413,"tag":1270,"props":4757,"children":4758},{"style":1506},[4759],{"type":419,"value":1542},{"type":413,"tag":1270,"props":4761,"children":4762},{"style":1506},[4763],{"type":419,"value":1622},{"type":413,"tag":1270,"props":4765,"children":4766},{"style":1282},[4767],{"type":419,"value":3874},{"type":413,"tag":1270,"props":4769,"children":4770},{"style":1506},[4771],{"type":419,"value":1622},{"type":413,"tag":1270,"props":4773,"children":4774},{"style":1506},[4775],{"type":419,"value":1674},{"type":413,"tag":1270,"props":4777,"children":4778},{"style":1495},[4779],{"type":419,"value":1557},{"type":413,"tag":1270,"props":4781,"children":4782},{"class":1272,"line":2244},[4783,4787,4792,4796,4801,4805,4810,4814,4819,4823,4828,4832,4837,4841,4846,4850,4854,4858,4862,4866,4870,4874],{"type":413,"tag":1270,"props":4784,"children":4785},{"style":1277},[4786],{"type":419,"value":4698},{"type":413,"tag":1270,"props":4788,"children":4789},{"style":1277},[4790],{"type":419,"value":4791}," base64Auth",{"type":413,"tag":1270,"props":4793,"children":4794},{"style":1506},[4795],{"type":419,"value":2692},{"type":413,"tag":1270,"props":4797,"children":4798},{"style":1495},[4799],{"type":419,"value":4800}," Convert",{"type":413,"tag":1270,"props":4802,"children":4803},{"style":1506},[4804],{"type":419,"value":461},{"type":413,"tag":1270,"props":4806,"children":4807},{"style":1534},[4808],{"type":419,"value":4809},"ToBase64String",{"type":413,"tag":1270,"props":4811,"children":4812},{"style":1506},[4813],{"type":419,"value":1542},{"type":413,"tag":1270,"props":4815,"children":4816},{"style":1495},[4817],{"type":419,"value":4818},"Encoding",{"type":413,"tag":1270,"props":4820,"children":4821},{"style":1506},[4822],{"type":419,"value":461},{"type":413,"tag":1270,"props":4824,"children":4825},{"style":1495},[4826],{"type":419,"value":4827},"Default",{"type":413,"tag":1270,"props":4829,"children":4830},{"style":1506},[4831],{"type":419,"value":461},{"type":413,"tag":1270,"props":4833,"children":4834},{"style":1534},[4835],{"type":419,"value":4836},"GetBytes",{"type":413,"tag":1270,"props":4838,"children":4839},{"style":1506},[4840],{"type":419,"value":1542},{"type":413,"tag":1270,"props":4842,"children":4843},{"style":1506},[4844],{"type":419,"value":4845},"$\"{",{"type":413,"tag":1270,"props":4847,"children":4848},{"style":1495},[4849],{"type":419,"value":3777},{"type":413,"tag":1270,"props":4851,"children":4852},{"style":1506},[4853],{"type":419,"value":2926},{"type":413,"tag":1270,"props":4855,"children":4856},{"style":1282},[4857],{"type":419,"value":1335},{"type":413,"tag":1270,"props":4859,"children":4860},{"style":1506},[4861],{"type":419,"value":2898},{"type":413,"tag":1270,"props":4863,"children":4864},{"style":1495},[4865],{"type":419,"value":3874},{"type":413,"tag":1270,"props":4867,"children":4868},{"style":1506},[4869],{"type":419,"value":2961},{"type":413,"tag":1270,"props":4871,"children":4872},{"style":1506},[4873],{"type":419,"value":2018},{"type":413,"tag":1270,"props":4875,"children":4876},{"style":1495},[4877],{"type":419,"value":1557},{"type":413,"tag":1270,"props":4879,"children":4880},{"class":1272,"line":2265},[4881],{"type":413,"tag":1270,"props":4882,"children":4883},{"style":1495},[4884],{"type":419,"value":1557},{"type":413,"tag":1270,"props":4886,"children":4887},{"class":1272,"line":2542},[4888,4893,4898,4903,4908,4912,4917,4921,4926,4930,4935,4939,4943,4947,4951,4955],{"type":413,"tag":1270,"props":4889,"children":4890},{"style":1506},[4891],{"type":419,"value":4892},"        await",{"type":413,"tag":1270,"props":4894,"children":4895},{"style":3714},[4896],{"type":419,"value":4897}," using",{"type":413,"tag":1270,"props":4899,"children":4900},{"style":1277},[4901],{"type":419,"value":4902}," var",{"type":413,"tag":1270,"props":4904,"children":4905},{"style":1277},[4906],{"type":419,"value":4907}," package",{"type":413,"tag":1270,"props":4909,"children":4910},{"style":1506},[4911],{"type":419,"value":2692},{"type":413,"tag":1270,"props":4913,"children":4914},{"style":1495},[4915],{"type":419,"value":4916}," File",{"type":413,"tag":1270,"props":4918,"children":4919},{"style":1506},[4920],{"type":419,"value":461},{"type":413,"tag":1270,"props":4922,"children":4923},{"style":1534},[4924],{"type":419,"value":4925},"OpenRead",{"type":413,"tag":1270,"props":4927,"children":4928},{"style":1506},[4929],{"type":419,"value":1542},{"type":413,"tag":1270,"props":4931,"children":4932},{"style":1495},[4933],{"type":419,"value":4934},"ArtifactsDirectory ",{"type":413,"tag":1270,"props":4936,"children":4937},{"style":1506},[4938],{"type":419,"value":1752},{"type":413,"tag":1270,"props":4940,"children":4941},{"style":1506},[4942],{"type":419,"value":1641},{"type":413,"tag":1270,"props":4944,"children":4945},{"style":1282},[4946],{"type":419,"value":2294},{"type":413,"tag":1270,"props":4948,"children":4949},{"style":1506},[4950],{"type":419,"value":1622},{"type":413,"tag":1270,"props":4952,"children":4953},{"style":1506},[4954],{"type":419,"value":1674},{"type":413,"tag":1270,"props":4956,"children":4957},{"style":1495},[4958],{"type":419,"value":1557},{"type":413,"tag":1270,"props":4960,"children":4961},{"class":1272,"line":2550},[4962,4967,4971,4976,4980,4984,4989,4993],{"type":413,"tag":1270,"props":4963,"children":4964},{"style":3714},[4965],{"type":419,"value":4966},"        using",{"type":413,"tag":1270,"props":4968,"children":4969},{"style":1277},[4970],{"type":419,"value":4902},{"type":413,"tag":1270,"props":4972,"children":4973},{"style":1277},[4974],{"type":419,"value":4975}," httpClient",{"type":413,"tag":1270,"props":4977,"children":4978},{"style":1506},[4979],{"type":419,"value":2692},{"type":413,"tag":1270,"props":4981,"children":4982},{"style":1506},[4983],{"type":419,"value":1285},{"type":413,"tag":1270,"props":4985,"children":4986},{"style":1277},[4987],{"type":419,"value":4988}," HttpClient",{"type":413,"tag":1270,"props":4990,"children":4991},{"style":1506},[4992],{"type":419,"value":4539},{"type":413,"tag":1270,"props":4994,"children":4995},{"style":1495},[4996],{"type":419,"value":1557},{"type":413,"tag":1270,"props":4998,"children":4999},{"class":1272,"line":2609},[5000,5005,5009,5014,5018,5023,5027,5031,5036,5040,5044,5048,5052,5056,5060,5064],{"type":413,"tag":1270,"props":5001,"children":5002},{"style":1495},[5003],{"type":419,"value":5004},"        httpClient",{"type":413,"tag":1270,"props":5006,"children":5007},{"style":1506},[5008],{"type":419,"value":461},{"type":413,"tag":1270,"props":5010,"children":5011},{"style":1495},[5012],{"type":419,"value":5013},"DefaultRequestHeaders",{"type":413,"tag":1270,"props":5015,"children":5016},{"style":1506},[5017],{"type":419,"value":461},{"type":413,"tag":1270,"props":5019,"children":5020},{"style":1495},[5021],{"type":419,"value":5022},"Authorization ",{"type":413,"tag":1270,"props":5024,"children":5025},{"style":1506},[5026],{"type":419,"value":3115},{"type":413,"tag":1270,"props":5028,"children":5029},{"style":1506},[5030],{"type":419,"value":1285},{"type":413,"tag":1270,"props":5032,"children":5033},{"style":1277},[5034],{"type":419,"value":5035}," AuthenticationHeaderValue",{"type":413,"tag":1270,"props":5037,"children":5038},{"style":1506},[5039],{"type":419,"value":1542},{"type":413,"tag":1270,"props":5041,"children":5042},{"style":1506},[5043],{"type":419,"value":1622},{"type":413,"tag":1270,"props":5045,"children":5046},{"style":1282},[5047],{"type":419,"value":3226},{"type":413,"tag":1270,"props":5049,"children":5050},{"style":1506},[5051],{"type":419,"value":1622},{"type":413,"tag":1270,"props":5053,"children":5054},{"style":1506},[5055],{"type":419,"value":1636},{"type":413,"tag":1270,"props":5057,"children":5058},{"style":1495},[5059],{"type":419,"value":4791},{"type":413,"tag":1270,"props":5061,"children":5062},{"style":1506},[5063],{"type":419,"value":1674},{"type":413,"tag":1270,"props":5065,"children":5066},{"style":1495},[5067],{"type":419,"value":1557},{"type":413,"tag":1270,"props":5069,"children":5070},{"class":1272,"line":3298},[5071,5075,5079,5083,5088,5092,5096,5101,5105,5110,5115,5119,5124,5129,5133,5137],{"type":413,"tag":1270,"props":5072,"children":5073},{"style":1506},[5074],{"type":419,"value":4892},{"type":413,"tag":1270,"props":5076,"children":5077},{"style":1495},[5078],{"type":419,"value":4975},{"type":413,"tag":1270,"props":5080,"children":5081},{"style":1506},[5082],{"type":419,"value":461},{"type":413,"tag":1270,"props":5084,"children":5085},{"style":1534},[5086],{"type":419,"value":5087},"PostAsync",{"type":413,"tag":1270,"props":5089,"children":5090},{"style":1506},[5091],{"type":419,"value":1542},{"type":413,"tag":1270,"props":5093,"children":5094},{"style":1506},[5095],{"type":419,"value":2888},{"type":413,"tag":1270,"props":5097,"children":5098},{"style":1282},[5099],{"type":419,"value":5100},"https://",{"type":413,"tag":1270,"props":5102,"children":5103},{"style":1506},[5104],{"type":419,"value":2898},{"type":413,"tag":1270,"props":5106,"children":5107},{"style":1534},[5108],{"type":419,"value":5109},"GetPulumiOutput",{"type":413,"tag":1270,"props":5111,"children":5112},{"style":1506},[5113],{"type":419,"value":5114},"(\"",{"type":413,"tag":1270,"props":5116,"children":5117},{"style":1282},[5118],{"type":419,"value":3963},{"type":413,"tag":1270,"props":5120,"children":5121},{"style":1506},[5122],{"type":419,"value":5123},"\")}",{"type":413,"tag":1270,"props":5125,"children":5126},{"style":1282},[5127],{"type":419,"value":5128},".scm.azurewebsites.net/api/zipdeploy",{"type":413,"tag":1270,"props":5130,"children":5131},{"style":1506},[5132],{"type":419,"value":1622},{"type":413,"tag":1270,"props":5134,"children":5135},{"style":1506},[5136],{"type":419,"value":1636},{"type":413,"tag":1270,"props":5138,"children":5139},{"style":1495},[5140],{"type":419,"value":1557},{"type":413,"tag":1270,"props":5142,"children":5143},{"class":1272,"line":3306},[5144,5149,5154,5158,5163,5167],{"type":413,"tag":1270,"props":5145,"children":5146},{"style":1506},[5147],{"type":419,"value":5148},"            new",{"type":413,"tag":1270,"props":5150,"children":5151},{"style":1277},[5152],{"type":419,"value":5153}," StreamContent",{"type":413,"tag":1270,"props":5155,"children":5156},{"style":1506},[5157],{"type":419,"value":1542},{"type":413,"tag":1270,"props":5159,"children":5160},{"style":1495},[5161],{"type":419,"value":5162},"package",{"type":413,"tag":1270,"props":5164,"children":5165},{"style":1506},[5166],{"type":419,"value":2018},{"type":413,"tag":1270,"props":5168,"children":5169},{"style":1495},[5170],{"type":419,"value":1557},{"type":413,"tag":1270,"props":5172,"children":5173},{"class":1272,"line":3423},[5174],{"type":413,"tag":1270,"props":5175,"children":5176},{"style":1506},[5177],{"type":419,"value":1713},{"type":413,"tag":509,"props":5179,"children":5180},{"icon":1150},[5181],{"type":413,"tag":422,"props":5182,"children":5183},{},[5184,5186,5193],{"type":419,"value":5185},"George Dangl already wrote a nice ",{"type":413,"tag":452,"props":5187,"children":5190},{"href":5188,"rel":5189},"https://blog.dangl.me/archive/lets-use-nuke-to-quickly-deploy-an-app-to-azure-via-zip-deployment/",[456],[5191],{"type":419,"value":5192},"article",{"type":419,"value":5194}," about using Nuke to deploy an application to Azure App Service using the Kudu API, so you can have a look at it. The code in the article is similar to the one we have here except that the credentials don't come from Pulumi outputs but from an Azure Key Vault.",{"type":413,"tag":422,"props":5196,"children":5197},{},[5198],{"type":419,"value":5199},"What I like about this approach is that you know exactly what you are doing, and the deployment logic is not hidden from you in an obscure YAML task whose code you will never read (yes I am talking to you Azure Pipelines and GitHub Actions 😃).",{"type":413,"tag":422,"props":5201,"children":5202},{},[5203],{"type":419,"value":5204},"But the awesome part in Nuke is that you can put a breakpoint in the code and debug it locally. If you need to modify your pipeline, you don't need to write YAML code modifications without knowing if it would work or not 🤞, commit and push your modifications, wait for an agent to run the changed pipeline in the cloud, wait for it to fail, browse the logs to try to understand the problem, and try again until it works.",{"type":413,"tag":414,"props":5206,"children":5208},{"id":5207},"final-pipeline",[5209],{"type":419,"value":5210},"Final pipeline",{"type":413,"tag":422,"props":5212,"children":5213},{},[5214],{"type":419,"value":5215},"If I fold everything, the pipeline code we created looks like that:",{"type":413,"tag":422,"props":5217,"children":5218},{},[5219],{"type":413,"tag":522,"props":5220,"children":5224},{"alt":5221,"className":5222,"src":5223},"Complete Nuke pipeline in the Build.cs file.",[526,527],"/posts/images/pulumi_met_nuke_11.png",[],{"type":413,"tag":422,"props":5226,"children":5227},{},[5228,5230,5236],{"type":419,"value":5229},"I think it is quite clear with the different steps/targets defined with their dependencies/order. Yet if this is not clear enough for you, you can use the ",{"type":413,"tag":552,"props":5231,"children":5233},{"className":5232},[],[5234],{"type":419,"value":5235},"nuke --plan",{"type":419,"value":5237}," command to display a visual representation of the pipeline (how cool is that !?)",{"type":413,"tag":422,"props":5239,"children":5240},{},[5241],{"type":413,"tag":522,"props":5242,"children":5246},{"alt":5243,"className":5244,"src":5245},"Nuke execution plan displayed as a graph.",[526,527],"/posts/images/pulumi_met_nuke_12.png",[],{"type":413,"tag":509,"props":5248,"children":5249},{"icon":511},[5250],{"type":413,"tag":422,"props":5251,"children":5252},{},[5253],{"type":419,"value":5254},"You can see that the execution plan is almost identical to the pipelines steps we talked about in the beginning of the article. The only difference is that we added to the Publish step a dependence on Clean.",{"type":413,"tag":422,"props":5256,"children":5257},{},[5258],{"type":419,"value":5259},"Let's execute the complete pipeline:",{"type":413,"tag":422,"props":5261,"children":5262},{},[5263],{"type":413,"tag":522,"props":5264,"children":5268},{"alt":5265,"className":5266,"src":5267,"width":1092},"Pipeline output in terminal showing the different steps and their status.",[526,527],"/posts/images/pulumi_met_nuke_13.png",[],{"type":413,"tag":422,"props":5270,"children":5271},{},[5272],{"type":419,"value":5273},"If I go to my Azure portal I can see the new Azure resources, among them an App Service where my Weather API is deployed.",{"type":413,"tag":422,"props":5275,"children":5276},{},[5277],{"type":413,"tag":522,"props":5278,"children":5282},{"alt":5279,"className":5280,"src":5281,"width":632},"Weather API weather forecast endpoint response opened in a browser.",[526,527],"/posts/images/pulumi_met_nuke_14.png",[],{"type":413,"tag":414,"props":5284,"children":5286},{"id":5285},"conclusion",[5287],{"type":419,"value":5288},"Conclusion",{"type":413,"tag":831,"props":5290,"children":5292},{"id":5291},"improvements-to-the-example-pipeline",[5293],{"type":419,"value":5294},"Improvements to the example pipeline",{"type":413,"tag":422,"props":5296,"children":5297},{},[5298],{"type":419,"value":5299},"The pipeline I have shown in this article is just a simple sample. They are lots of things that could be done to improve it. Beyond obvious ones like adding a Test target or using GitVersion to version the package, I want to talk about some choices I made in the pipeline implementation that may not be the best ones.",{"type":413,"tag":422,"props":5301,"children":5302},{},[5303,5305,5312,5314,5321,5323,5330],{"type":419,"value":5304},"As I said there are many ways to deploy a package to an App Service. While using the Kudu API is fine and allowed me to show you how we can use Pulumi stack outputs to retrieve publishing credentials, it might be a bit limited in some cases and involves a bit of manual code to make the HTTP request. A good alternative would be to use the Azure CLI that has ",{"type":413,"tag":452,"props":5306,"children":5309},{"href":5307,"rel":5308},"https://learn.microsoft.com/en-us/cli/azure/webapp/deployment/source?view=azure-cli-latest#az-webapp-deployment-source-config-zip",[456],[5310],{"type":419,"value":5311},"a command",{"type":419,"value":5313}," for that. But my preferred option would be to use the ",{"type":413,"tag":452,"props":5315,"children":5318},{"href":5316,"rel":5317},"https://learn.microsoft.com/en-us/dotnet/api/overview/azure/resource-manager?view=azure-dotnet",[456],[5319],{"type":419,"value":5320},"Azure Resource Manager libraries for .NET",{"type":419,"value":5322},". Unfortunately this SDK is quite new and miss ",{"type":413,"tag":452,"props":5324,"children":5327},{"href":5325,"rel":5326},"https://github.com/Azure/azure-sdk-for-net/issues/30577",[456],[5328],{"type":419,"value":5329},"samples",{"type":419,"value":5331}," on how to do that.",{"type":413,"tag":422,"props":5333,"children":5334},{},[5335,5337,5344],{"type":419,"value":5336},"Speaking of SDK, Pulumi has an API called the ",{"type":413,"tag":452,"props":5338,"children":5341},{"href":5339,"rel":5340},"https://www.pulumi.com/automation/",[456],[5342],{"type":419,"value":5343},"Automation API",{"type":419,"value":5345}," to use the Pulumi engine as an SDK. I think it would be a better option than using the Pulumi CLI. Generally speaking, I think using SDK instead of CLIs to write the targets of a pipeline brings more power, more flexibility, and a better developer experience.",{"type":413,"tag":831,"props":5347,"children":5349},{"id":5348},"about-nuke-and-cicd",[5350],{"type":419,"value":5351},"About Nuke and CI/CD",{"type":413,"tag":422,"props":5353,"children":5354},{},[5355],{"type":419,"value":5356},"Nuke has many features I did not show in this small example. If we add some attributes to the pipeline code, Nuke can generate YAML workflow files to execute the Nuke pipeline. When executing the pipeline locally everything works fine because I am logged in to Pulumi CLI and Azure CLI in my terminal but I have to add secret parameters to my Nuke pipeline (a Pulumi token and an Azure Service Principal identifier/password) to make the authentication works when the pipeline is run from a CI/CD platform runner/agent.",{"type":413,"tag":422,"props":5358,"children":5359},{},[5360,5362,5368,5370,5377],{"type":419,"value":5361},"Moreover, there are many things I don't know yet about Nuke because I am just starting to use it. That is why I advise you to have a look at its ",{"type":413,"tag":452,"props":5363,"children":5366},{"href":5364,"rel":5365},"https://nuke.build/docs/introduction/",[456],[5367],{"type":419,"value":1333},{"type":419,"value":5369},", ",{"type":413,"tag":452,"props":5371,"children":5374},{"href":5372,"rel":5373},"https://nuke.build/resources/",[456],[5375],{"type":419,"value":5376},"at some resources",{"type":419,"value":5378}," and start playing with it by yourself.",{"type":413,"tag":422,"props":5380,"children":5381},{},[5382],{"type":419,"value":5383},"In the future, I see myself using Nuke for most of my CI pipelines, and not only for .NET projects (because I can run any CLI tools from Nuke, it also works for front projects where I would use the pnpm CLI for instance). I am not saying that because I am afraid of YAML or because I'm not familiar with ready-made tasks like Azure Pipelines tasks or GitHub Actions. I have been using Azure Pipelines for several years now and I have also played a bit with GitHub Actions. They are good platforms but lack local debugging and the great developer experience provided by a tool like Nuke. So I will continue using them but to run my Nuke pipelines 😉.",{"type":413,"tag":422,"props":5385,"children":5386},{},[5387],{"type":419,"value":5388},"Concerning the CD pipelines, I don't know yet if I can use Nuke for all my use cases. They are real benefits to using Nuke for deployments but I still have to investigate how some things can be done like deploying to multiple environments, and handling approvals between environments.",{"type":413,"tag":831,"props":5390,"children":5392},{"id":5391},"pulumi-nuke-the-perfect-combo",[5393],{"type":419,"value":5394},"Pulumi & Nuke, the perfect combo?",{"type":413,"tag":422,"props":5396,"children":5397},{},[5398],{"type":419,"value":5399},"I don't know if it's the perfect combo but it's definitively one I love. Having .NET everywhere, from infrastructure code to pipeline code without forgetting the application code is awesome.",{"type":413,"tag":5401,"props":5402,"children":5403},"style",{},[5404],{"type":419,"value":5405},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":401,"searchDepth":742,"depth":742,"links":5407},[5408,5409,5410,5415,5422,5428,5429,5430],{"id":1054,"depth":742,"text":1057},{"id":1075,"depth":742,"text":1078},{"id":1173,"depth":742,"text":1176,"children":5411},[5412,5413,5414],{"id":1179,"depth":995,"text":1182},{"id":1236,"depth":995,"text":1239},{"id":1366,"depth":995,"text":1369},{"id":1412,"depth":742,"text":1415,"children":5416},[5417,5418,5419,5420,5421],{"id":1418,"depth":995,"text":1421},{"id":1473,"depth":995,"text":1476},{"id":1877,"depth":995,"text":1880},{"id":2032,"depth":995,"text":2035},{"id":2273,"depth":995,"text":2276},{"id":2751,"depth":742,"text":2754,"children":5423},[5424,5425,5426,5427],{"id":2757,"depth":995,"text":2760},{"id":2813,"depth":995,"text":2816},{"id":3515,"depth":995,"text":3518},{"id":4011,"depth":995,"text":4014},{"id":4309,"depth":742,"text":4312},{"id":5207,"depth":742,"text":5210},{"id":5285,"depth":742,"text":5288,"children":5431},[5432,5433,5434],{"id":5291,"depth":995,"text":5294},{"id":5348,"depth":995,"text":5351},{"id":5391,"depth":995,"text":5394},"content:1.posts:45.when-pulumi-met-nuke.md","1.posts/45.when-pulumi-met-nuke.md",{"_path":115,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":114,"description":5438,"lead":5439,"date":5440,"image":5441,"badge":5443,"tags":5444,"body":5445,"_type":748,"_id":6717,"_source":750,"_file":6718,"_extension":752},"In today's world of cloud-first applications, multi-cloud/hybrid cloud companies, and complex infrastructures, using infrastructure as code is essential. In recent years, Terraform has become one of the most popular IaC solutions, but its challenger Pulumi is quickly gaining traction. In this article, I will tell you why I think Pulumi is better and why I will choose it over Terraform for my next project.","My take on choosing an Infrastructure as solution","2022-05-02T00:00:00.000Z",{"src":5442},"/images/clouds_1.jpg",{"label":266},[315,312,355,266],{"type":410,"children":5446,"toc":6686},[5447,5451,5456,5462,5467,5472,5545,5554,5559,5565,5570,5576,5599,5617,5626,5647,5663,5669,5674,5683,5696,5713,5726,5749,5758,5763,5781,5787,5817,5826,5848,5864,5878,5901,5910,5916,5921,5935,5940,5972,5992,6001,6007,6021,6027,6033,6038,6043,6052,6057,6063,6068,6077,6082,6098,6111,6124,6133,6139,6144,6149,6167,6179,6188,6193,6199,6213,6231,6239,6256,6261,6270,6275,6281,6295,6304,6309,6330,6336,6341,6346,6352,6357,6362,6371,6376,6382,6387,6392,6401,6406,6411,6419,6425,6430,6438,6443,6457,6469,6475,6480,6485,6491,6513,6519,6525,6530,6558,6567,6573,6578,6583,6588,6594,6599,6604,6609,6614,6620,6625,6630,6643,6648,6654,6659,6664,6669,6681],{"type":413,"tag":422,"props":5448,"children":5449},{},[5450],{"type":419,"value":5438},{"type":413,"tag":422,"props":5452,"children":5453},{},[5454],{"type":419,"value":5455},"But first, let's talk about what makes a good Infrastructure as Code solution.",{"type":413,"tag":414,"props":5457,"children":5459},{"id":5458},"what-makes-a-good-infrastructure-as-code-solution",[5460],{"type":419,"value":5461},"What makes a good Infrastructure as Code solution?",{"type":413,"tag":422,"props":5463,"children":5464},{},[5465],{"type":419,"value":5466},"There is no universal answer to this question, but I can give you the characteristics I am looking for in an IaC solution.",{"type":413,"tag":422,"props":5468,"children":5469},{},[5470],{"type":419,"value":5471},"In my opinion, an IaC solution should be:",{"type":413,"tag":479,"props":5473,"children":5474},{},[5475,5486,5495,5505,5515,5525,5535],{"type":413,"tag":483,"props":5476,"children":5477},{},[5478,5484],{"type":413,"tag":5479,"props":5480,"children":5481},"strong",{},[5482],{"type":419,"value":5483},"declarative",{"type":419,"value":5485},". I think it is important to focus on what infrastructure we want to provision rather than how to provision it",{"type":413,"tag":483,"props":5487,"children":5488},{},[5489,5493],{"type":413,"tag":5479,"props":5490,"children":5491},{},[5492],{"type":419,"value":334},{"type":419,"value":5494},". Beyond being a good thing, open source favors the adoption of technology and makes an ecosystem healthy with contributions from the community",{"type":413,"tag":483,"props":5496,"children":5497},{},[5498,5503],{"type":413,"tag":5479,"props":5499,"children":5500},{},[5501],{"type":419,"value":5502},"multi providers",{"type":419,"value":5504},". Even if I work mainly with Azure, companies tend to be multi clouds; projects often involve provisioning resources in different cloud providers and services. Moreover, beyond cloud providers, I want to be able to automate the provisioning of many things like my Azure DevOps or GitHub projects.",{"type":413,"tag":483,"props":5506,"children":5507},{},[5508,5513],{"type":413,"tag":5479,"props":5509,"children":5510},{},[5511],{"type":419,"value":5512},"easy to learn, easy to use, and easy to be productive with",{"type":419,"value":5514},". I love learning new things but even more when it's easy and I can be quickly productive with new technology.",{"type":413,"tag":483,"props":5516,"children":5517},{},[5518,5523],{"type":413,"tag":5479,"props":5519,"children":5520},{},[5521],{"type":419,"value":5522},"up to date with main cloud providers' resources and features",{"type":419,"value":5524},". IT (and specifically the cloud) is evolving very quickly. The infrastructure we provision needs to be able to benefit from the latest resources, innovations, security improvements... I don't want to have to wait for novelties to be available",{"type":413,"tag":483,"props":5526,"children":5527},{},[5528,5533],{"type":413,"tag":5479,"props":5529,"children":5530},{},[5531],{"type":419,"value":5532},"flexible and customizable",{"type":419,"value":5534},". Cloud infrastructures are more and more complex, and each project has its specificities so flexibility and the ability to easily write custom code to address these specificities are important",{"type":413,"tag":483,"props":5536,"children":5537},{},[5538,5543],{"type":413,"tag":5479,"props":5539,"children":5540},{},[5541],{"type":419,"value":5542},"secure",{"type":419,"value":5544},". Security in IT is of paramount importance (especially when dealing with infrastructure) and therefore should be built-in",{"type":413,"tag":422,"props":5546,"children":5547},{},[5548],{"type":413,"tag":522,"props":5549,"children":5553},{"alt":5550,"className":5551,"src":5552},"Sun shining through the clouds in the sky.",[526,527],"/posts/images/pulumivstf_cloud_1.jpg",[],{"type":413,"tag":422,"props":5555,"children":5556},{},[5557],{"type":419,"value":5558},"Terraform and Pulumi have many similarities and some of the characteristics (like being declarative, open source, or multi providers) I mentioned above are present in both solutions. However, that is not the case with all these characteristics. Furthermore, Terraform and Pulumi differ in many other aspects that we will talk about in this article.",{"type":413,"tag":414,"props":5560,"children":5562},{"id":5561},"pulumi-a-modern-iac-tool-with-many-built-in-features",[5563],{"type":419,"value":5564},"Pulumi, a modern IaC tool with many built-in features",{"type":413,"tag":422,"props":5566,"children":5567},{},[5568],{"type":419,"value":5569},"Usually, tools that have been there for quite some time have more features than new tools. New tools bring modernity and innovative features but need a bit of time to catch up with all the features. This is not at all the case with Pulumi, it is even the opposite. Although Terraform is older (created in 2014 vs. 2018 for Pulumi), Pulumi has more built-in features (including key features) than Terraform. Moreover, some of Terraform features are restricted to its paid version Terraform Cloud 💸.",{"type":413,"tag":831,"props":5571,"children":5573},{"id":5572},"state-backends-and-security",[5574],{"type":419,"value":5575},"State, backends, and security",{"type":413,"tag":422,"props":5577,"children":5578},{},[5579,5581,5588,5590,5597],{"type":419,"value":5580},"Both Terraform and Pulumi use code (HCL and programming languages respectively) to describe the desired state of an infrastructure that is compared to the current state of the infrastructure to know which operations (create, update, delete) to do on resources. The current state of the infrastructure is stored in a \"backend\" than can be for instance the local filesystem, AWS S3, Google Cloud Storage, Azure Blob Storage, or the SaaS offering of Terraform (Terraform Cloud)/Pulumi (Pulumi Service). ",{"type":413,"tag":452,"props":5582,"children":5585},{"href":5583,"rel":5584},"https://cloud.hashicorp.com/products/terraform",[456],[5586],{"type":419,"value":5587},"Terraform Cloud",{"type":419,"value":5589}," and ",{"type":413,"tag":452,"props":5591,"children":5594},{"href":5592,"rel":5593},"https://www.pulumi.com/docs/intro/pulumi-service/",[456],[5595],{"type":419,"value":5596},"Pulumi Service",{"type":419,"value":5598}," are self-managed backends that offer similar functionalities (deployment history, collaboration functionalities, RBAC for an organization...).",{"type":413,"tag":422,"props":5600,"children":5601},{},[5602,5604,5609,5611,5616],{"type":419,"value":5603},"The state stored in a backend contains sensitive data (secrets like connection strings 🔑) that you need to secure. Whatever the backend you choose, ",{"type":413,"tag":5479,"props":5605,"children":5606},{},[5607],{"type":419,"value":5608},"when using Pulumi the secrets in your state are always encrypted",{"type":419,"value":5610}," using an encryption provider. The default encryption provider depends on the backend but you can easily configure Pulumi to specify another encryption provider to use. The encryption provider can be a passphrase, AWS Key Management Service, Azure Key Vault, Google Cloud Key Management Service, HashiCorp Vault Transit Secrets Engine, or Pulumi Service. The ability to choose an encryption provider and to encrypt secrets in the state is not something Terraform supports. By default, Terraform will store the state in a local JSON file with the secrets in it 🙀. You probably won't run into security issues if you use Terraform Cloud, yet ",{"type":413,"tag":5479,"props":5612,"children":5613},{},[5614],{"type":419,"value":5615},"security should be built-in and not something you have to pay for",{"type":419,"value":461},{"type":413,"tag":422,"props":5618,"children":5619},{},[5620],{"type":413,"tag":522,"props":5621,"children":5625},{"alt":5622,"className":5623,"src":5624},"A padlock sitting on top of a computer keyboard.",[526,527],"/posts/images/pulumivstf_security_1.jpg",[],{"type":413,"tag":422,"props":5627,"children":5628},{},[5629,5631,5638,5640,5645],{"type":419,"value":5630},"You may not see this as a big concern as you are probably not storing your state locally but on a cloud storage (like an Azure Blob Storage) where access is restricted to only a few people. But let's imagine one of your storage access keys gets compromised. If you are using Terraform, someone could have access to all the secrets of your infrastructure. If you are using Pulumi he will not because all secrets in the state file are encrypted. And he will have trouble decrypting them because he would have to gain access to the encryption provider. Whether you use Azure Key Vault, AWS Key Management Service, or any other encryption provider, these are components whose purpose is to keep data safe 🔐, and that require proper permissions to have access to encryption/decryption keys. I am not saying you can't make your infrastructure safe with Terraform. (In my example above with an Azure Storage Account, you could always prevent the ",{"type":413,"tag":452,"props":5632,"children":5635},{"href":5633,"rel":5634},"https://docs.microsoft.com/en-us/azure/storage/common/shared-key-authorization-prevent?tabs=portal",[456],[5636],{"type":419,"value":5637},"shared key authorization",{"type":419,"value":5639}," and use only Azure AD authorization to reduce the security risk.) I am just saying that ",{"type":413,"tag":5479,"props":5641,"children":5642},{},[5643],{"type":419,"value":5644},"Pulumi is secure by default, Terraform is not",{"type":419,"value":5646}," and extra work is required.",{"type":413,"tag":509,"props":5648,"children":5649},{"icon":1150},[5650],{"type":413,"tag":422,"props":5651,"children":5652},{},[5653,5655,5661],{"type":419,"value":5654},"If you want to learn more about state, backend, security, and how Terraform handles state compared to Pulumi you can check this ",{"type":413,"tag":452,"props":5656,"children":5659},{"href":5657,"rel":5658},"https://www.techwatching.dev/posts/pulumi-azure-backend",[456],[5660],{"type":419,"value":5192},{"type":419,"value":5662}," where I talk about all. I also show how to use Azure Blob Storage as the backend and Azure Key Vault as the encryption provider for my infrastructure.",{"type":413,"tag":831,"props":5664,"children":5666},{"id":5665},"iac-brownfield-development",[5667],{"type":419,"value":5668},"IaC brownfield development",{"type":413,"tag":422,"props":5670,"children":5671},{},[5672],{"type":419,"value":5673},"Infrastructure as Code is not a new concept and before Terraform and Pulumi arrived, cloud providers' native solutions have been widely used to provision cloud infrastructure. Some infrastructures were also created manually. So today, there is a lot of existing infrastructure and most projects are not greenfield but brownfield projects. When choosing an IaC solution it is important to consider that and have the tools to integrate the existing infrastructure.",{"type":413,"tag":422,"props":5675,"children":5676},{},[5677],{"type":413,"tag":522,"props":5678,"children":5682},{"alt":5679,"className":5680,"src":5681},"A view of the ocean from the top of a hill with different types of soils.",[526,527],"/posts/images/pulumivstf_brownfield.jpg",[],{"type":413,"tag":422,"props":5684,"children":5685},{},[5686,5688,5694],{"type":419,"value":5687},"Both Terraform and Pulumi have an ",{"type":413,"tag":552,"props":5689,"children":5691},{"className":5690},[],[5692],{"type":419,"value":5693},"import",{"type":419,"value":5695},"  CLI command to import existing infrastructure. Currently, Terraform can only import one resource at a time and can only import it into the state without generating the corresponding configuration code. Pulumi supports bulk import operations (using a JSON file to specify the resources to import) and generates the corresponding infrastructure code to add. It may seem like anecdotal features but they become important when you have a lot of resources to import.",{"type":413,"tag":509,"props":5697,"children":5698},{"icon":511},[5699],{"type":413,"tag":422,"props":5700,"children":5701},{},[5702,5704,5711],{"type":419,"value":5703},"Besides, Microsoft has recently announced a new tool ",{"type":413,"tag":452,"props":5705,"children":5708},{"href":5706,"rel":5707},"https://github.com/Azure/aztfy",[456],[5709],{"type":419,"value":5710},"Azure Terrafy",{"type":419,"value":5712}," to \"quickly turn existing Azure infrastructure into Terraform HCL and import to Terraform state\". I guess they did not want to wait for Terraform to have this feature built-in. This won't help you if you are using Terraform with AWS or GCP though.",{"type":413,"tag":422,"props":5714,"children":5715},{},[5716,5718,5724],{"type":419,"value":5717},"Being able to import existing resources into Pulumi/Terraform is nice. However, for complex infrastructure, you will probably do it progressively or not at all if you want to keep some parts managed by other tools/teams. No matter the case, you will need your new infrastructure to coexist with the existing infrastructure not (yet) managed by Pulumi/Terraform. Both Terraform and Pulumi can reference existing infrastructure but Pulumi goes beyond that. First, it can reference other Pulumi stacks i.e Pulumi projects, which is especially useful when you are in a big organization or when your architecture is divided into microservices. Second, it can reference external states, i.e outputs from infrastructure created with other IaC tools than Pulumi. For instance, you could reference a Terraform state (whether it is a local ",{"type":413,"tag":552,"props":5719,"children":5721},{"className":5720},[],[5722],{"type":419,"value":5723},"tfstate",{"type":419,"value":5725}," or a remote state like a state in a Terraform Cloud workspace) or an AWS CloudFormation stack: you would get access to all the outputs of the corresponding provisioned infrastructure. That way, even if you have some existing infrastructure managed outside your Pulumi project, you can reference it and use it in your project without having to reference each resource with hard-coded names.",{"type":413,"tag":422,"props":5727,"children":5728},{},[5729,5731,5738,5740,5747],{"type":419,"value":5730},"If you have already spent time building a complex infrastructure with another IaC tool and want to migrate to Pulumi while preserving the organization of your code and without rewriting everything with Pulumi SDKs from scratch, Pulumi has some ",{"type":413,"tag":452,"props":5732,"children":5735},{"href":5733,"rel":5734},"https://www.pulumi.com/docs/converters/",[456],[5736],{"type":419,"value":5737},"conversion tools",{"type":419,"value":5739}," to help you with that. For instance, you can use the ",{"type":413,"tag":452,"props":5741,"children":5744},{"href":5742,"rel":5743},"https://www.pulumi.com/arm2pulumi/",[456],[5745],{"type":419,"value":5746},"arm2pulumi",{"type":419,"value":5748}," converter to convert your ARM templates to Pulumi code written in your preferred programming language. You can try it yourself on the website to see what it looks like.",{"type":413,"tag":422,"props":5750,"children":5751},{},[5752],{"type":413,"tag":522,"props":5753,"children":5757},{"alt":5754,"className":5755,"src":5756},"Demo of using ARM to Pulumi converter on Pulumi website.",[526,527],"/posts/images/pulumivstf_converter_1.png",[],{"type":413,"tag":422,"props":5759,"children":5760},{},[5761],{"type":419,"value":5762},"I like how Pulumi has been designed to work with brownfield projects: it offers all that is needed to coexist with existing infrastructure, adopting it, and converting it to Pulumi code if desired. It goes far beyond the built-in capabilities of Terraform on the topic.",{"type":413,"tag":509,"props":5764,"children":5766},{"icon":5765},"i-heroicons-document-text",[5767],{"type":413,"tag":422,"props":5768,"children":5769},{},[5770,5772,5779],{"type":419,"value":5771},"Pulumi has a very ",{"type":413,"tag":452,"props":5773,"children":5776},{"href":5774,"rel":5775},"https://www.pulumi.com/docs/guides/adopting/",[456],[5777],{"type":419,"value":5778},"well-written documentation",{"type":419,"value":5780}," about \"adopting Pulumi\" if you want to deep dive into working with existing infrastructure.",{"type":413,"tag":831,"props":5782,"children":5784},{"id":5783},"environments-and-configuration",[5785],{"type":419,"value":5786},"Environments and configuration",{"type":413,"tag":422,"props":5788,"children":5789},{},[5790,5792,5799,5801,5807,5809,5815],{"type":419,"value":5791},"Thanks to its concept of ",{"type":413,"tag":452,"props":5793,"children":5796},{"href":5794,"rel":5795},"https://www.pulumi.com/docs/intro/concepts/stack/",[456],[5797],{"type":419,"value":5798},"stack",{"type":419,"value":5800},", Pulumi has built-in support for deploying the same infrastructure in multiple environments. You can use configuration files to have different settings depending on the environment. For example, if you have a development environment and a production environment that need to have different sizes of VMs to avoid paying too much for the development environment, you will have 2 configuration files: ",{"type":413,"tag":552,"props":5802,"children":5804},{"className":5803},[],[5805],{"type":419,"value":5806},"Pulumi.development.yaml",{"type":419,"value":5808}," and a ",{"type":413,"tag":552,"props":5810,"children":5812},{"className":5811},[],[5813],{"type":419,"value":5814},"Pulumi.production.yaml",{"type":419,"value":5816},". These files will contain the same setting to specify the size of the VM with different values. This concept of having a configuration file by environment is quite similar to what you would use when developing an application. In addition to that, you can use the Pulumi CLI to set settings as secrets that will be encrypted in your configuration files (using the same encryption provider that encrypts your state). So Pulumi offers you everything you need to easily and safely provision infrastructure in different environments.",{"type":413,"tag":422,"props":5818,"children":5819},{},[5820],{"type":413,"tag":522,"props":5821,"children":5825},{"alt":5822,"className":5823,"src":5824},"Pulumi.dev.yaml file with encrypted settings.",[526,527],"/posts/images/pulumivstf_configuration_1.png",[],{"type":413,"tag":422,"props":5827,"children":5828},{},[5829,5831,5838,5839,5846],{"type":419,"value":5830},"Of course, Terraform can also provision infrastructure in different environments. Having different folders for the different environments used to be the way of handling different environments in Terraform which meant a lot of code duplication (unfortunately some companies still do that today😕). Because of that and other deficiencies of Terraform, different Terraform wrappers and frameworks (like ",{"type":413,"tag":452,"props":5832,"children":5835},{"href":5833,"rel":5834},"https://terragrunt.gruntwork.io/",[456],[5836],{"type":419,"value":5837},"Terragrunt",{"type":419,"value":666},{"type":413,"tag":452,"props":5840,"children":5843},{"href":5841,"rel":5842},"https://terraspace.cloud/",[456],[5844],{"type":419,"value":5845},"Terraspace",{"type":419,"value":5847},") were created by the community to keep the infrastructure code DRY and structured.",{"type":413,"tag":509,"props":5849,"children":5850},{"icon":5765},[5851],{"type":413,"tag":422,"props":5852,"children":5853},{},[5854,5856,5863],{"type":419,"value":5855},"If you want to know the differences between Terraform, Terragrunt, and Terraspace, BlogOps (the creator of Terraspace) has an interesting article on the ",{"type":413,"tag":452,"props":5857,"children":5860},{"href":5858,"rel":5859},"https://blog.boltops.com/2020/09/28/terraform-vs-terragrunt-vs-terraspace/",[456],[5861],{"type":419,"value":5862},"topic",{"type":419,"value":461},{"type":413,"tag":422,"props":5865,"children":5866},{},[5867,5869,5876],{"type":419,"value":5868},"In 2017, Terraform also introduced its built-in way of managing different environments with ",{"type":413,"tag":452,"props":5870,"children":5873},{"href":5871,"rel":5872},"https://www.terraform.io/language/state/workspaces",[456],[5874],{"type":419,"value":5875},"Terraform workspaces",{"type":419,"value":5877},". I think it's great to have different alternatives, but the drawback is to have the Terraform community divided between people that are using pure Terraform and people using other tools built on top of Terraform. Moreover, whatever the tool used I find managing environments and configuration a bit more complicated in Terraform than when you use Pulumi: a lot of documentation and blog posts to read before knowing how to manage environments the proper way.",{"type":413,"tag":422,"props":5879,"children":5880},{},[5881,5883,5890,5892,5899],{"type":419,"value":5882},"One important downside of Terraform concerning environments and configuration is that there is no built-in way of managing secrets. Unless you are a Terraform Cloud customer (in which case your secrets will be stored in workspaces as sensitive variables), you have to find a custom way to keep the secrets in your configuration safe. ",{"type":413,"tag":452,"props":5884,"children":5887},{"href":5885,"rel":5886},"https://gruntwork.io/",[456],[5888],{"type":419,"value":5889},"Gruntwork",{"type":419,"value":5891},", the company behind Terragrunt has a ",{"type":413,"tag":452,"props":5893,"children":5896},{"href":5894,"rel":5895},"https://blog.gruntwork.io/a-comprehensive-guide-to-managing-secrets-in-your-terraform-code-1d586955ace1",[456],[5897],{"type":419,"value":5898},"detailed article",{"type":419,"value":5900}," about managing secrets in Terraform that I suggest you read. It describes different techniques to keep secrets safe (so not hardcoded in plain text in Terraform code): using environment variables, encrypted files, or a secret store. As you can read in the conclusion of this article (and see in the screenshot of this article below), all these options have trade-offs and require extra work in comparison to Pulumi where secrets encryption is integrated. Moreover, even if you succeed in managing secrets properly in Terraform, they will end up in plain text in Terraform state as we previously mentioned.",{"type":413,"tag":422,"props":5902,"children":5903},{},[5904],{"type":413,"tag":522,"props":5905,"children":5909},{"alt":5906,"className":5907,"src":5908},"Table showing options for managing secrets in Terraform from Gruntwork website.",[526,527],"/posts/images/pulumivstf_security_2.png",[],{"type":413,"tag":831,"props":5911,"children":5913},{"id":5912},"embedded-iac-through-an-api",[5914],{"type":419,"value":5915},"Embedded IaC through an API",{"type":413,"tag":422,"props":5917,"children":5918},{},[5919],{"type":419,"value":5920},"To provision infrastructure using Terraform or Pulumi, you can use their respective CLI.",{"type":413,"tag":422,"props":5922,"children":5923},{},[5924,5926,5933],{"type":419,"value":5925},"If you are using Terraform Cloud, you can also perform Terraform runs (called ",{"type":413,"tag":452,"props":5927,"children":5930},{"href":5928,"rel":5929},"https://www.terraform.io/cloud-docs/run#terraform-runs-and-remote-operations",[456],[5931],{"type":419,"value":5932},"remote operations",{"type":419,"value":5934}," in the documentation which are concepts specific to Terraform Cloud) from there by using an API, UI controls or webhooks of your Version Control System (GitHub, GitLab, BitBucket, Azure DevOps, ...). These are useful capabilities but once again these are only available if you are using Terraform SaaS product.",{"type":413,"tag":422,"props":5936,"children":5937},{},[5938],{"type":419,"value":5939},"Now, there are use cases where you would want to provision infrastructure programmatically instead of using a CLI. It can be for instance to:",{"type":413,"tag":479,"props":5941,"children":5942},{},[5943,5957,5962,5967],{"type":413,"tag":483,"props":5944,"children":5945},{},[5946,5948,5955],{"type":419,"value":5947},"provide self-service infrastructure to development teams in your organization (through ",{"type":413,"tag":452,"props":5949,"children":5952},{"href":5950,"rel":5951},"https://www.pulumi.com/blog/organizational-patterns-developer-portal/",[456],[5953],{"type":419,"value":5954},"developer portals",{"type":419,"value":5956}," for example)",{"type":413,"tag":483,"props":5958,"children":5959},{},[5960],{"type":419,"value":5961},"integrate provisioning of infrastructure into your platforms and tools (whether it is your custom framework, CLI, or CI/CD workflow)",{"type":413,"tag":483,"props":5963,"children":5964},{},[5965],{"type":419,"value":5966},"automate infrastructure provisioning for your custom needs",{"type":413,"tag":483,"props":5968,"children":5969},{},[5970],{"type":419,"value":5971},"do complex deployments that mix infrastructure and application code such as database migrations",{"type":413,"tag":422,"props":5973,"children":5974},{},[5975,5977,5982,5984,5990],{"type":419,"value":5976},"And for that, Pulumi has an ",{"type":413,"tag":452,"props":5978,"children":5980},{"href":5339,"rel":5979},[456],[5981],{"type":419,"value":5343},{"type":419,"value":5983}," which allows you to build, deploy, and manage infrastructure dynamically from your code thanks to an SDK instead of a CLI. I think it's awesome and unfortunately Terraform does not offer something like that. There would be much to say about Automation API (and that's something that will probably continue to evolve), yet the best is that you check the ",{"type":413,"tag":452,"props":5985,"children":5988},{"href":5986,"rel":5987},"https://www.pulumi.com/docs/guides/automation-api/",[456],[5989],{"type":419,"value":1333},{"type":419,"value":5991}," to learn more about it.",{"type":413,"tag":422,"props":5993,"children":5994},{},[5995],{"type":413,"tag":522,"props":5996,"children":6000},{"alt":5997,"className":5998,"src":5999},"Diagram explaining Pulumi Automation API.",[526,527],"/posts/images/pulumivstf_automationapi.png",[],{"type":413,"tag":831,"props":6002,"children":6004},{"id":6003},"what-else",[6005],{"type":419,"value":6006},"What else?",{"type":413,"tag":422,"props":6008,"children":6009},{},[6010,6012,6019],{"type":419,"value":6011},"Pulumi has many more built-in features where it stands out compared to Terraform but I can't cover everything in this blog post. The ones I previously talked about were the main ones in my opinion but you can check the ",{"type":413,"tag":452,"props":6013,"children":6016},{"href":6014,"rel":6015},"https://www.pulumi.com/docs/intro/vs/terraform/",[456],[6017],{"type":419,"value":6018},"Pulumi vs. Terraform page",{"type":419,"value":6020}," in Pulumi's documentation to read about other differences between Pulumi and Terraform. Other interesting capabilities of Pulumi like dynamic providers or testing will be discussed later in this article.",{"type":413,"tag":414,"props":6022,"children":6024},{"id":6023},"providers",[6025],{"type":419,"value":6026},"Providers",{"type":413,"tag":831,"props":6028,"children":6030},{"id":6029},"why-providers",[6031],{"type":419,"value":6032},"Why providers?",{"type":413,"tag":422,"props":6034,"children":6035},{},[6036],{"type":419,"value":6037},"Providers are the reason why I think Terraform and Pulumi have a great future ahead. Instead of focusing on a specific cloud or platform (which is what Cloud Formation, Azure Bicep, or Google Cloud Deployment Manager do), Terraform and Pulumi allow you to provision resources in many different cloud providers and SaaS providers. That does not mean you will write the same infrastructure code to provision cloud resources on AWS and Azure. Of course not, each cloud provider has its specificities and the resources will be different on each platform so the code needs to be different as well. However, instead of having to use multiple IaC platforms (Cloud Formation and Azure Bicep for instance) if you need to provision resources from different clouds in your project, you will only use one (Terraform or Pulumi) with different providers.",{"type":413,"tag":422,"props":6039,"children":6040},{},[6041],{"type":419,"value":6042},"You can see how this works in Pulumi on this schema from the documentation:",{"type":413,"tag":422,"props":6044,"children":6045},{},[6046],{"type":413,"tag":522,"props":6047,"children":6051},{"alt":6048,"className":6049,"src":6050},"Diagram of how Pulumi works.",[526,527],"/posts/images/pulumivstf_architecture.png",[],{"type":413,"tag":422,"props":6053,"children":6054},{},[6055],{"type":419,"value":6056},"Today lots of companies are using multiple clouds to meet their needs and to avoid putting all their eggs in the same basket. And beyond cloud providers, a lot of other SaaS products are used in an organization and you will probably automate the provisioning of resources for these products too to make them available to your teams (whether it is a VCS or a monitoring platform). So being able to provision and manage all these resources from the same tools, using the same concepts and processes is a must-have, and that is what Pulumi and Terraform offer by supporting many providers 👍.",{"type":413,"tag":831,"props":6058,"children":6060},{"id":6059},"how-does-pulumi-benefit-from-terraform-ecosystem",[6061],{"type":419,"value":6062},"How does Pulumi benefit from Terraform ecosystem?",{"type":413,"tag":422,"props":6064,"children":6065},{},[6066],{"type":419,"value":6067},"When Pulumi came out in 2018, instead of reinventing the wheel they choose to take advantage of Terraform Providers' mature ecosystem to build most of their own providers. Indeed, Pulumi created tools to adapt/bridge any existing Terraform provider.",{"type":413,"tag":422,"props":6069,"children":6070},{},[6071],{"type":413,"tag":522,"props":6072,"children":6076},{"alt":6073,"className":6074,"src":6075,"width":632},"Pulumi Terraform Bridge repository on GitHub.",[526,527],"/posts/images/pulumivstf_bridge.png",[],{"type":413,"tag":422,"props":6078,"children":6079},{},[6080],{"type":419,"value":6081},"To understand what it means, we have to talk about what is exactly a provider. According to Terraform's documentation, \"providers are a logical abstraction of an upstream API. They are responsible for understanding API interactions and exposing resources\". That means a provider defines a schema describing the resources available on a cloud provider API, and all the mappings (parameters, models, responses ...) needed to interact with this API. Instead of doing the same job of mapping everything, a Pulumi provider that is created by \"bridging\" a Terraform provider simply reuses the same schema, that's it. But, Pulumi itself does not use Terraform, they have a completely different engine.",{"type":413,"tag":509,"props":6083,"children":6084},{"icon":5765},[6085],{"type":413,"tag":422,"props":6086,"children":6087},{},[6088,6090,6096],{"type":419,"value":6089},"If you are looking for a better explanation about how Pulumi \"bridges\" Terraform providers you can look at this ",{"type":413,"tag":452,"props":6091,"children":6094},{"href":6092,"rel":6093},"https://www.leebriggs.co.uk/blog/2021/11/06/pulumi-faqs.html#doesnt-pulumi-use-terraform-under-the-hood-",[456],[6095],{"type":419,"value":5192},{"type":419,"value":6097}," from Lee Briggs who work at Pulumi.",{"type":413,"tag":422,"props":6099,"children":6100},{},[6101,6103,6110],{"type":419,"value":6102},"Why do I talk about all this? It's because I think it's great for Pulumi to be able to benefit from Terraform Providers ecosystem. Thanks to this, like Terraform, Pulumi supports lots of cloud providers and modern cloud SaaS offerings. And in the event there were a provider available in Terraform and not in Pulumi, it would always be possible for anyone to create a Pulumi provider out of this Terraform provider thanks to the ",{"type":413,"tag":452,"props":6104,"children":6107},{"href":6105,"rel":6106},"https://github.com/pulumi/pulumi-terraform-bridge",[456],[6108],{"type":419,"value":6109},"Pulumi Terraform Bridge",{"type":419,"value":461},{"type":413,"tag":422,"props":6112,"children":6113},{},[6114,6116,6123],{"type":419,"value":6115},"By the way, the fact that some Pulumi providers are created by adapting Terraform providers is completely assumed by Pulumi as you can read in ",{"type":413,"tag":452,"props":6117,"children":6120},{"href":6118,"rel":6119},"https://www.pulumi.com/docs/intro/vs/terraform/#providers-terraform",[456],[6121],{"type":419,"value":6122},"Pulumi documentation",{"type":419,"value":461},{"type":413,"tag":422,"props":6125,"children":6126},{},[6127],{"type":413,"tag":522,"props":6128,"children":6132},{"alt":6129,"className":6130,"src":6131},"Pulumi documentation about using Terraform Providers.",[526,527],"/posts/images/pulumivstf_providers_1.png",[],{"type":413,"tag":831,"props":6134,"children":6136},{"id":6135},"the-problem-with-terraform-providers",[6137],{"type":419,"value":6138},"The problem with Terraform providers",{"type":413,"tag":422,"props":6140,"children":6141},{},[6142],{"type":419,"value":6143},"We have seen that Pulumi benefits from Terraform providers ecosystem. However, there is a major problem with Terraform providers (and so with corresponding Pulumi providers as well): they are implemented manually.",{"type":413,"tag":422,"props":6145,"children":6146},{},[6147],{"type":419,"value":6148},"It has the following inconveniences for a provider:",{"type":413,"tag":479,"props":6150,"children":6151},{},[6152,6157,6162],{"type":413,"tag":483,"props":6153,"children":6154},{},[6155],{"type":419,"value":6156},"it is more likely to contain bugs",{"type":413,"tag":483,"props":6158,"children":6159},{},[6160],{"type":419,"value":6161},"it doesn't have full coverage of its corresponding API",{"type":413,"tag":483,"props":6163,"children":6164},{},[6165],{"type":419,"value":6166},"it is always a little behind an API because it takes time for new resources or new features to be added",{"type":413,"tag":422,"props":6168,"children":6169},{},[6170,6172,6177],{"type":419,"value":6171},"At the beginning of the article I explain that I wanted my IaC solution should be ",{"type":413,"tag":6173,"props":6174,"children":6175},"em",{},[6176],{"type":419,"value":5522},{"type":419,"value":6178}," and that is not the case with Terraform providers even if the community (Pulumi included) is doing a great job at contributing to Terraform providers to make new resources available.",{"type":413,"tag":422,"props":6180,"children":6181},{},[6182],{"type":413,"tag":522,"props":6183,"children":6187},{"alt":6184,"className":6185,"src":6186},"A bunch of clocks that are sitting on a shelf.",[526,527],"/posts/images/pulumivstf_clocks.jpg",[],{"type":413,"tag":422,"props":6189,"children":6190},{},[6191],{"type":419,"value":6192},"Moreover, a version of a provider matches a version of the resources in the API. So you can't have 2 resources from different API versions coexist. For instance, let's say you need to use an old version of the Terraform provider for Azure because they are a lot of breaking changes in the latest version of the provider. You don't want to handle these changes yet as it involves some big changes in some of your resources. But in the same time, you want to use a resource that is only available in the latest version of the provider. Well in this case it's going to be complicated for you. The issue is that with Terraform providers you can't be very flexible with your resources and customize everything you want",{"type":413,"tag":831,"props":6194,"children":6196},{"id":6195},"what-are-pulumi-native-providers",[6197],{"type":419,"value":6198},"What are  Pulumi native providers?",{"type":413,"tag":422,"props":6200,"children":6201},{},[6202,6204,6211],{"type":419,"value":6203},"In order to solve the problem described in the previous section, Pulumi introduced the concept of ",{"type":413,"tag":452,"props":6205,"children":6208},{"href":6206,"rel":6207},"https://www.pulumi.com/blog/pulumiup-native-providers/",[456],[6209],{"type":419,"value":6210},"native providers",{"type":419,"value":6212}," for the providers Microsoft Azure, AWS, Google Cloud Platform, and Kubernetes. These providers are automatically built every day from the cloud providers' APIs 🎉. It has the following advantages:",{"type":413,"tag":479,"props":6214,"children":6215},{},[6216,6221,6226],{"type":413,"tag":483,"props":6217,"children":6218},{},[6219],{"type":419,"value":6220},"100% API coverage, so all resources available including the ones in preview",{"type":413,"tag":483,"props":6222,"children":6223},{},[6224],{"type":419,"value":6225},"Providers are always up-to-date with the APIs",{"type":413,"tag":483,"props":6227,"children":6228},{},[6229],{"type":419,"value":6230},"Access to all the versions of the APIs so that resources from different API versions can coexist in a project (as you would have with a cloud provider native solution like Azure Bicep)",{"type":413,"tag":422,"props":6232,"children":6233},{},[6234],{"type":413,"tag":5479,"props":6235,"children":6236},{},[6237],{"type":419,"value":6238},"Honestly, these are good enough reasons to choose Pulumi over Terraform. If you are developing cloud applications, you don't want to be limited to what you can do by your IaC solution, especially when it concerns the major cloud providers you are probably using",{"type":413,"tag":509,"props":6240,"children":6241},{"icon":511},[6242],{"type":413,"tag":422,"props":6243,"children":6244},{},[6245,6247,6254],{"type":419,"value":6246},"It's worth noting that people from Microsoft seem also concerned about these limitations of the Terraform Azure RM provider as they announced a new Azure provider ",{"type":413,"tag":452,"props":6248,"children":6251},{"href":6249,"rel":6250},"https://techcommunity.microsoft.com/t5/azure-tools-blog/announcing-azure-terrafy-and-azapi-terraform-provider-previews/ba-p/3270937",[456],[6252],{"type":419,"value":6253},"AzAPI",{"type":419,"value":6255},", built on top of the Azure ARM APIs that can be used to have access to Azure features and services in preview. However, it seems that for other resources, Microsoft expects people to continue using the existing Terraform provider with its limitations.",{"type":413,"tag":422,"props":6257,"children":6258},{},[6259],{"type":419,"value":6260},"I think generating the providers from the APIs is the right way of doing things. Unfortunately, it's only possible to create native providers for a cloud provider or a SaaS provider if its API exposes the necessary mapping. But if more APIs do that, more Pulumi native providers will probably be added in the future.",{"type":413,"tag":422,"props":6262,"children":6263},{},[6264],{"type":413,"tag":522,"props":6265,"children":6269},{"alt":6266,"className":6267,"src":6268},"List of Pulumi native Providers on Pulumi website.",[526,527],"/posts/images/pulumivstf_providers_2.png",[],{"type":413,"tag":422,"props":6271,"children":6272},{},[6273],{"type":419,"value":6274},"Another thing I appreciate a lot with these native providers:  you work with the same \"models\" as the ones of the corresponding cloud. On the contrary, because a Terraform provider is hand-coded you will have a more or less thin abstraction layer and potential differences between the models. That's not a big deal but can sometimes complicate things because you have to understand how the Terraform models map to the cloud API models.",{"type":413,"tag":831,"props":6276,"children":6278},{"id":6277},"more-about-providers",[6279],{"type":419,"value":6280},"More about providers",{"type":413,"tag":422,"props":6282,"children":6283},{},[6284,6286,6293],{"type":419,"value":6285},"If you have looked at ",{"type":413,"tag":452,"props":6287,"children":6290},{"href":6288,"rel":6289},"https://registry.terraform.io/browse/providers",[456],[6291],{"type":419,"value":6292},"Terraform registry",{"type":419,"value":6294},", you may have seen that there is a huge amount of community providers. Does it mean Terraform is better than Pulumi? I don't think so: quantity does not mean quality.  That's not me saying that there are many bad quality Terraform providers, not at all. That's me saying that there are probably lots of providers you don't care about. For instance, I assume you don't care that there is a Terraform provider to order a Domino's Pizza 🍕. Okay, that may be fun, but that's it. And concerning the more serious providers, a lot of them are just there to overcome the inherent limitations of HCL (not being a programming language) such as not being able to make an HTTP call without using a provider. You don't have these limitations with Pulumi where you use programming languages and their libraries.",{"type":413,"tag":422,"props":6296,"children":6297},{},[6298],{"type":413,"tag":522,"props":6299,"children":6303},{"alt":6300,"className":6301,"src":6302},"A collection of office supplies laid out on a table.",[526,527],"/posts/images/pulumivstf_tools_1.jpg",[],{"type":413,"tag":422,"props":6305,"children":6306},{},[6307],{"type":419,"value":6308},"We will talk more about that in the next section but being able to use a programming language and all its ecosystem is a true advantage of Pulumi over Terraform. There are some times when you need to provision some cloud resources and there is no provider to manage them (whether it be Pulumi or Terraform). If you are using Terraform you will have to wait for the community to implement this provider, if it ever really happens. If you are using Pulumi, you are using programming languages and therefore can implement anything you need (by using SDKs or making the API calls yourself). And I am not talking about implementing yourself a whole provider covering a complete API (which would require some time), but just implementing the resources you need.",{"type":413,"tag":422,"props":6310,"children":6311},{},[6312,6314,6321,6323,6329],{"type":419,"value":6313},"For that, Pulumi has a concept of ",{"type":413,"tag":452,"props":6315,"children":6318},{"href":6316,"rel":6317},"https://www.pulumi.com/docs/intro/concepts/resources/dynamic-providers/",[456],[6319],{"type":419,"value":6320},"Dynamic Providers",{"type":419,"value":6322}," with which you implement the different CRUD operations for a resource so that you are still doing declarative infrastructure as code but with custom logic. It's like implementing on the fly a custom provider specific to your needs directly in your project. Dynamic providers' usage is not limited to supporting a cloud provider that does not yet exist in Pulumi, it is also to do any infrastructure task that no existing provider can help deliver (see examples in this ",{"type":413,"tag":452,"props":6324,"children":6327},{"href":6325,"rel":6326},"https://www.pulumi.com/blog/dynamic-providers/#sample-use-cases",[456],[6328],{"type":419,"value":5192},{"type":419,"value":1655},{"type":413,"tag":414,"props":6331,"children":6333},{"id":6332},"using-programming-languages-the-best-way-to-do-iac",[6334],{"type":419,"value":6335},"Using programming languages: the best way to do IaC",{"type":413,"tag":422,"props":6337,"children":6338},{},[6339],{"type":419,"value":6340},"Pulumi's approach is to use programming languages to write your infrastructure code. On the opposite, Terraform's approach is to uses a DSL called HCL (that stands for Hashicorp Configuration Language). It's worth reminding that both tools are declarative, even when using an imperative language.",{"type":413,"tag":422,"props":6342,"children":6343},{},[6344],{"type":419,"value":6345},"When comparing IaC, the big debate ⚡ is often: show we use Domain Specific Languages (DSL) or programming languages? If you have read the previous sections of this article you know this is just one of many questions, there are other important aspects to consider. Nevertheless, it's an important question because there is more to it than simply a question of personal preference. In my opinion, using programming languages to write your infrastructure code is the right way of doing things and is the future of infrastructure as code. Let me explain to you why and how it is particularly great to use programming languages with Pulumi.",{"type":413,"tag":831,"props":6347,"children":6349},{"id":6348},"better-aligned-with-the-devops-culture",[6350],{"type":419,"value":6351},"Better aligned with the DevOps culture",{"type":413,"tag":422,"props":6353,"children":6354},{},[6355],{"type":419,"value":6356},"DevOps aims at continuously delivering value to end-users by removing the barrier between software development and IT operations. As one of the practices of DevOps, Infrastructure as Code should help bring closer people from Development teams and people from Operations teams.",{"type":413,"tag":422,"props":6358,"children":6359},{},[6360],{"type":419,"value":6361},"Therefore I don't think using different languages, tools, and practices for the application development and the infrastructure development is the right approach. And yet, this is what you do when you write your infrastructure code in HCL while the application code is written in TypeScript or C# for instance. Unfortunately, in some companies, I think using Terraform reinforces the Development and Operations silos more than anything else. It's difficult to deliver value to a customer if that means for a developer to create a ticket to the Operations team just to add a new setting in a web app because only them have access and knowledge of the Terraform code 😿.",{"type":413,"tag":422,"props":6363,"children":6364},{},[6365],{"type":413,"tag":522,"props":6366,"children":6370},{"alt":6367,"className":6368,"src":6369},"The words \"together is the way\" spelled with cubes.",[526,527],"/posts/images/pulumivstf_together.jpg",[],{"type":413,"tag":422,"props":6372,"children":6373},{},[6374],{"type":419,"value":6375},"I truly believe that the proper way of adopting DevOps practices is to have multidisciplinary self-organizing teams. And what a better way to make software developers, IT operations people, security engineers, ... collaborate in such teams than by making them \"speak\" the same language, use the same tools, and adopt the same engineering practices. That's what using programming languages for the infrastructure code is about and that's what Pulumi is about.",{"type":413,"tag":831,"props":6377,"children":6379},{"id":6378},"be-productive-faster-and-more",[6380],{"type":419,"value":6381},"Be productive faster and more",{"type":413,"tag":422,"props":6383,"children":6384},{},[6385],{"type":419,"value":6386},"Whatever the IaC solution you choose, you will have to learn new concepts that may not be obvious. Yet, writing the infrastructure code will be easy to learn if you are using a programming language you already know. On the contrary, if you use a DSL, it will add another thing for you to learn (especially HCL that I don't find very intuitive).",{"type":413,"tag":422,"props":6388,"children":6389},{},[6390],{"type":419,"value":6391},"Using Pulumi you will not only be able to use the programming language you know but also your favorite IDEs, as well as the libraries and the tools you are familiar with 🛠️. The programming languages supported by Pulumi (TypeScript/JavaScript, Python, Go, .NET languages, Java languages, and probably more to come) are used by lots of people for software development. Therefore IDE support (typing, static analysis, code completion....) is already great and tooling in general better than the tooling for an IaC DSL could ever be.",{"type":413,"tag":422,"props":6393,"children":6394},{},[6395],{"type":413,"tag":522,"props":6396,"children":6400},{"alt":6397,"className":6398,"src":6399},"A black and white banner that says everyone can code.",[526,527],"/posts/images/pulumivstf_eveyonecancode.jpg",[],{"type":413,"tag":422,"props":6402,"children":6403},{},[6404],{"type":419,"value":6405},"So in addition to learning faster IaC using programming languages you will be more productive too. That is also true for people not coming from development but from ops, they probably already have some scripting knowledge (Bash, PowerShell, Python...), that's why programming languages can be a good fit for them as well.",{"type":413,"tag":422,"props":6407,"children":6408},{},[6409],{"type":419,"value":6410},"Making people quickly productive on an IaC solution is important because as everywhere in IT, in IaC field it's hard to find skilled people to recruit so it's sometimes easier to train people already in your company. Speaking of that, I hear sometimes people saying that: as Terraform is currently more used than Pulumi (due to Pulumi being more recent), it will be easier to hire people who know Terraform than people who know Pulumi. Yes, that's true, but it will be much harder to find people who know HCL than people who know TypeScript, Python, .NET, or Go.",{"type":413,"tag":509,"props":6412,"children":6413},{"icon":511},[6414],{"type":413,"tag":422,"props":6415,"children":6416},{},[6417],{"type":419,"value":6418},"Independently of HCL, I find Terraform to be more complex to learn than Pulumi, with a lot of different concepts to grasp (variables, local values,  data sources, configuration, workspaces, modules) that are unique to Terraform. Pulumi felt much easier. Of course, learning is unique to each individual so you may have a different opinion but this has been my experience.",{"type":413,"tag":831,"props":6420,"children":6422},{"id":6421},"a-better-developer-experience",[6423],{"type":419,"value":6424},"A better developer experience",{"type":413,"tag":422,"props":6426,"children":6427},{},[6428],{"type":419,"value":6429},"The developer experience is so much better when using programming languages instead of a DSL.",{"type":413,"tag":509,"props":6431,"children":6432},{"icon":1150},[6433],{"type":413,"tag":422,"props":6434,"children":6435},{},[6436],{"type":419,"value":6437},"If you are not familiar with the term Developer Experience or DX, it's like User Experience but for software engineers using digital products (developer tools, IT solutions, platforms...).",{"type":413,"tag":422,"props":6439,"children":6440},{},[6441],{"type":419,"value":6442},"I already talked about the better code completion of Pulumi which is great to know which resource you can create, what properties can be set, which ones are required, and what are the possible values.",{"type":413,"tag":422,"props":6444,"children":6445},{},[6446,6448,6455],{"type":419,"value":6447},"There is also the fact that you can debug infrastructure code because it's just code in a program. With Automation API, Pulumi can be used from anywhere (a console application, a custom CLI, a web application, ...) and easily debugged. Good luck debugging Terraform code 😉. You even have something similar to hot reload for infrastructure with pulumi watch command (see ",{"type":413,"tag":452,"props":6449,"children":6452},{"href":6450,"rel":6451},"https://www.techwatching.dev/posts/pulumi-watch",[456],[6453],{"type":419,"value":6454},"my article",{"type":419,"value":6456}," on the topic).",{"type":413,"tag":422,"props":6458,"children":6459},{},[6460,6462,6468],{"type":419,"value":6461},"Another topic is testing. You can only do unit testing (understand the testing of components without deploying real infrastructure) with code written in a programming language. Pulumi allows you to write unit tests by mocking the external dependencies using your usual test and mock frameworks. Integration testing/end-to-end testing however can be done with both Pulumi and Terraform.  If you do a bit of search, you will find out that there are several frameworks to write end-to-end tests for Terraform and they require you to write your tests in Go or Ruby (so finally using a programming language after all 😀). You can read more about testing with Pulumi in the ",{"type":413,"tag":452,"props":6463,"children":6466},{"href":6464,"rel":6465},"https://www.pulumi.com/docs/guides/testing/",[456],[6467],{"type":419,"value":1333},{"type":419,"value":461},{"type":413,"tag":831,"props":6470,"children":6472},{"id":6471},"more-capabilities",[6473],{"type":419,"value":6474},"More capabilities",{"type":413,"tag":422,"props":6476,"children":6477},{},[6478],{"type":419,"value":6479},"Using a programming language you are not limited to what providers or modules can do. You have the power of a programming language to implement what you need (for example using the dynamic providers I talked about before). Some people are afraid of that, I don't think they should. Programming languages have static analysis mechanisms to ensure best practices are respected in the code. Pulumi also provides policy as code to let you write the rules you want your infrastructure to follow (it could be rules about security or cost for instance).",{"type":413,"tag":422,"props":6481,"children":6482},{},[6483],{"type":419,"value":6484},"Terraform has a great community that creates packages and tools for Terraform. As Pulumi is younger, the ecosystem is not as developed as Terraform's. However, when you use Pulumi you use programming languages that have a huge ecosystem from which you can benefit ❤️.",{"type":413,"tag":831,"props":6486,"children":6488},{"id":6487},"the-future",[6489],{"type":419,"value":6490},"The future",{"type":413,"tag":422,"props":6492,"children":6493},{},[6494,6496,6503,6505,6511],{"type":419,"value":6495},"I have given several reasons why I think programming languages is the best way to do IaC. Today, the majority of people are using DSL based on YAML or JSON to write infrastructure code but things are changing at a great speed. Following the example of Pulumi, there are more and more IaC tools that support programming languages to write infrastructure code, ",{"type":413,"tag":452,"props":6497,"children":6500},{"href":6498,"rel":6499},"https://aws.amazon.com/cdk/",[456],[6501],{"type":419,"value":6502},"AWS CDK",{"type":419,"value":6504}," for instance. Even Terraform itself is starting to see the limits of HCL and has launched its CDK in beta. Unfortunately at the time of writing it's still in preview since July 2020, has some limitations, and is not yet mature according to the ",{"type":413,"tag":452,"props":6506,"children":6509},{"href":6507,"rel":6508},"https://www.terraform.io/cdktf#project-maturity",[456],[6510],{"type":419,"value":1333},{"type":419,"value":6512},". Pulumi has a clear head start but it's very interesting to see the whole IaC ecosystem evolving in this direction.",{"type":413,"tag":414,"props":6514,"children":6516},{"id":6515},"final-thoughts",[6517],{"type":419,"value":6518},"Final thoughts",{"type":413,"tag":831,"props":6520,"children":6522},{"id":6521},"summary",[6523],{"type":419,"value":6524},"Summary",{"type":413,"tag":422,"props":6526,"children":6527},{},[6528],{"type":419,"value":6529},"To sum up, I will choose Pulumi over Terraform for my next project because:",{"type":413,"tag":479,"props":6531,"children":6532},{},[6533,6538,6543,6548,6553],{"type":413,"tag":483,"props":6534,"children":6535},{},[6536],{"type":419,"value":6537},"it has 100% resource coverage of my cloud provider and more flexibility",{"type":413,"tag":483,"props":6539,"children":6540},{},[6541],{"type":419,"value":6542},"it allows me to be more productive using languages, tools, and libraries I am already familiar with",{"type":413,"tag":483,"props":6544,"children":6545},{},[6546],{"type":419,"value":6547},"it has more built-in functionalities and is more modern",{"type":413,"tag":483,"props":6549,"children":6550},{},[6551],{"type":419,"value":6552},"it is easier to learn and use (something that my teammates will appreciate)",{"type":413,"tag":483,"props":6554,"children":6555},{},[6556],{"type":419,"value":6557},"it is secure by default",{"type":413,"tag":422,"props":6559,"children":6560},{},[6561],{"type":413,"tag":522,"props":6562,"children":6566},{"alt":6563,"className":6564,"src":6565},"A hand moving a white chess piece to make the back king fall on a chess board.",[526,527],"/posts/images/pulumivstf_chess.jpg",[],{"type":413,"tag":831,"props":6568,"children":6570},{"id":6569},"disclaimers",[6571],{"type":419,"value":6572},"Disclaimers",{"type":413,"tag":422,"props":6574,"children":6575},{},[6576],{"type":419,"value":6577},"Just like any other article on my blog, \"the opinions expressed herein are my own and do not represent those of my employer or any other third-party views in any way\".",{"type":413,"tag":422,"props":6579,"children":6580},{},[6581],{"type":419,"value":6582},"This article is not sponsored, I don't own any shares in Pulumi (unfortunately 😉), and I don't work for Pulumi. That means the article just reflects my point of view, the point of view of a cloud developer interested in infrastructure who has worked with both Terraform and Pulumi, and much prefers Pulumi.",{"type":413,"tag":422,"props":6584,"children":6585},{},[6586],{"type":419,"value":6587},"Even if I like Pulumi a lot as it is very nice working with it, I have tried to remain as objective as possible in this article. You will probably not agree with everything I said, but I hope you will understand what are my reasons for choosing Pulumi for my Infrastructure as Code and that it will help you with your choice, whatever it is.",{"type":413,"tag":831,"props":6589,"children":6591},{"id":6590},"is-terraform-bad",[6592],{"type":419,"value":6593},"Is Terraform bad?",{"type":413,"tag":422,"props":6595,"children":6596},{},[6597],{"type":419,"value":6598},"Not at all. I would have a hard time saying Terraform is bad and Pulumi is awesome when they rely on similar concepts like declarative infrastructure as code, state file to store the current state of the infrastructure, and providers to support multiple cloud providers and services. In fact, I think Terraform is a great Infrastructure as Code solution, just not the best one in my opinion.",{"type":413,"tag":422,"props":6600,"children":6601},{},[6602],{"type":419,"value":6603},"I think Terraform has truly revolutionized infrastructure as code when it came out in 2014. At that time, cloud providers native solutions were too verbose and too complex, and there was no true alternative to them. Terraform came to simplify that and has enabled people to use the same IaC solution to deploy resources of different cloud providers. Today, things have changed: cloud providers' native solutions have evolved, there are new modern alternatives to Terraform (and I am not talking only about Pulumi), and Terraform has shown some limitations.",{"type":413,"tag":422,"props":6605,"children":6606},{},[6607],{"type":419,"value":6608},"Because Terraform is used in many companies, benefits from a rich ecosystem, and has a great community, you will probably not make a big mistake if you go with Terraform instead of Pulumi for your next project. However, if you do so, you have to be aware of the limitations and weaknesses of Terraform that I talked about in this article. This is something very important because each project has its needs and constraints, so maybe Terraform's limitations are not a big deal for your use case, maybe it is. Just make sure which one it is.",{"type":413,"tag":422,"props":6610,"children":6611},{},[6612],{"type":419,"value":6613},"Despite all I said, there is one thing I like very much about Terraform, it's its community. Because many people have been using it, and for some years now, there are a lot of examples on the internet and many interesting tools or frameworks have been created around it.",{"type":413,"tag":831,"props":6615,"children":6617},{"id":6616},"is-pulumi-perfect",[6618],{"type":419,"value":6619},"Is Pulumi perfect?",{"type":413,"tag":422,"props":6621,"children":6622},{},[6623],{"type":419,"value":6624},"No Pulumi is not perfect, no IaC solution is, there are always things to improve.",{"type":413,"tag":422,"props":6626,"children":6627},{},[6628],{"type":419,"value":6629},"For instance, a few Pulumi features are not yet available in .NET like Dynamic providers or policy as code (not a real problem as you can write them in TypeScript or Python even if your stack is in .NET).",{"type":413,"tag":422,"props":6631,"children":6632},{},[6633,6635,6642],{"type":419,"value":6634},"I also hope more native providers will be created (even if I know it's not only up to Pulumi) because I think native providers are a game-changer. In particular, I would want to have a native provider for Microsoft Graph (you can vote for the issue ",{"type":413,"tag":452,"props":6636,"children":6639},{"href":6637,"rel":6638},"https://github.com/pulumi/pulumi/issues/8963",[456],[6640],{"type":419,"value":6641},"here",{"type":419,"value":1655},{"type":413,"tag":422,"props":6644,"children":6645},{},[6646],{"type":419,"value":6647},"More generally, I think Infrastructure as Code is evolving quickly and there are many things yet to come. But I am confident Pulumi will continue to bring more features and innovations to this space 🚀.",{"type":413,"tag":831,"props":6649,"children":6651},{"id":6650},"which-infrastructure-as-code-solution-should-you-choose",[6652],{"type":419,"value":6653},"Which Infrastructure as Code solution should you choose?",{"type":413,"tag":422,"props":6655,"children":6656},{},[6657],{"type":419,"value":6658},"To be honest, I can't think of any good reason why I would choose Terraform over Pulumi for a project. But that's just me.",{"type":413,"tag":422,"props":6660,"children":6661},{},[6662],{"type":419,"value":6663},"If you have already invested a lot of time learning HCL/Terraform and are happy with it, you should probably continue using Terraform.",{"type":413,"tag":422,"props":6665,"children":6666},{},[6667],{"type":419,"value":6668},"If you don't know either Terraform or Pulumi, I would suggest you use Pulumi even if you don't know any programming language. It's in my opinion the right way to do infrastructure as code. Furthermore, it is better to learn a programming language that could be useful somewhere else than HCL which you will only use in HashiCorp products.",{"type":413,"tag":422,"props":6670,"children":6671},{},[6672,6674,6679],{"type":419,"value":6673},"I would like to emphasize one thing: ",{"type":413,"tag":5479,"props":6675,"children":6676},{},[6677],{"type":419,"value":6678},"when choosing an IaC solution, make an informed decision.",{"type":419,"value":6680}," Do not choose Terraform because your favorite influencer promotes it. Do not choose Terraform because most people around you use it. The same is true for Pulumi. Do not choose Pulumi just because I say you should. Read articles from people with other points of view. Check what topics are important for you, and compare both solutions on these topics. Make up your own mind.",{"type":413,"tag":422,"props":6682,"children":6683},{},[6684],{"type":419,"value":6685},"In the end, you should choose the solution you feel is more appropriate to you and your project, and that you think will be easier for you to learn and use in the long term. If it's Terraform then go with it! If it's Pulumi, welcome to modern Infrastructure as Code!",{"title":401,"searchDepth":742,"depth":742,"links":6687},[6688,6689,6696,6703,6710],{"id":5458,"depth":742,"text":5461},{"id":5561,"depth":742,"text":5564,"children":6690},[6691,6692,6693,6694,6695],{"id":5572,"depth":995,"text":5575},{"id":5665,"depth":995,"text":5668},{"id":5783,"depth":995,"text":5786},{"id":5912,"depth":995,"text":5915},{"id":6003,"depth":995,"text":6006},{"id":6023,"depth":742,"text":6026,"children":6697},[6698,6699,6700,6701,6702],{"id":6029,"depth":995,"text":6032},{"id":6059,"depth":995,"text":6062},{"id":6135,"depth":995,"text":6138},{"id":6195,"depth":995,"text":6198},{"id":6277,"depth":995,"text":6280},{"id":6332,"depth":742,"text":6335,"children":6704},[6705,6706,6707,6708,6709],{"id":6348,"depth":995,"text":6351},{"id":6378,"depth":995,"text":6381},{"id":6421,"depth":995,"text":6424},{"id":6471,"depth":995,"text":6474},{"id":6487,"depth":995,"text":6490},{"id":6515,"depth":742,"text":6518,"children":6711},[6712,6713,6714,6715,6716],{"id":6521,"depth":995,"text":6524},{"id":6569,"depth":995,"text":6572},{"id":6590,"depth":995,"text":6593},{"id":6616,"depth":995,"text":6619},{"id":6650,"depth":995,"text":6653},"content:1.posts:36.pulumi-vs-terraform.md","1.posts/36.pulumi-vs-terraform.md",{"_path":43,"_dir":399,"_draft":400,"_partial":400,"_locale":401,"title":42,"description":6720,"lead":6721,"date":6722,"image":6723,"badge":6725,"tags":6727,"body":6728,"_type":748,"_id":7200,"_source":750,"_file":7201,"_extension":752},"DevOps has been the trendy term for quite a few years now. Lots of companies including the biggest companies in Silicon Valley have started adopting DevOps practices. However the DevOps journey is not an easy one, so you might wonder if DevOps is suited for you and your company. Do now worry anymore, you can forget about DevOps because your company is probably already using the best practices in IT, and these are not DevOps practices. Let's take a deep dive into some of these practices in this article.","Best practices in IT.","2021-02-11T00:00:00.000Z",{"src":6724},"/images/books_1.jpg",{"label":6726},"Essay",[266,269],{"type":410,"children":6729,"toc":7187},[6730,6734,6743,6749,6754,6759,6768,6773,6779,6784,6802,6807,6830,6839,6844,6849,6855,6860,6878,6887,6892,6897,6903,6908,6913,6919,6924,6929,6934,6952,6961,6966,6995,7000,7006,7011,7016,7022,7027,7032,7037,7046,7051,7056,7062,7067,7072,7081,7086,7095,7101,7106,7111,7120,7125,7137,7143,7148,7153,7158],{"type":413,"tag":422,"props":6731,"children":6732},{},[6733],{"type":419,"value":6720},{"type":413,"tag":509,"props":6735,"children":6737},{"icon":6736},"i-fluent-emoji-flat-pushpin",[6738],{"type":413,"tag":422,"props":6739,"children":6740},{},[6741],{"type":419,"value":6742},"The title as most of this article (apart from this paragraph and the conclusion) is highly ironic. I wanted to talk about the wrong ways of working in IT that I have seen or heard about, and I thought that if I talked about them as if they were best practices it would be more obvious why they are so wrong. Some of the \"best practices\" described in the article come from things I read on the web, some from things I witnessed in companies I worked for either as an employee or as a contractor, and some from discussions I had with IT people working in other companies. Yet, wherever they come from I deliberately exaggerated and transformed them to make them funny. The aim of this article is not to criticize everything but to make people realize that some decisions they take or ways of working they like can have a really bad impact on their projects, their team, other teams, and even their entire company.",{"type":413,"tag":414,"props":6744,"children":6746},{"id":6745},"no-spec-low-spec",[6747],{"type":419,"value":6748},"No-spec & Low-spec",{"type":413,"tag":422,"props":6750,"children":6751},{},[6752],{"type":419,"value":6753},"You may have heard of \"no-code and low-code\" platforms that aim at creating applications using GUI tools instead of writing code. With no-spec and low-spec, the concept is similar but applied to specifications: the goal is to create applications without providing specifications or the fewer specifications possible.",{"type":413,"tag":422,"props":6755,"children":6756},{},[6757],{"type":419,"value":6758},"The low spec practice is very useful when a Product Owner does not know what features need to be implemented in an application. By staying deliberately vague with \"high-level specifications\", unclear PowerPoint slides, or oral directives instead of a real backlog, a Product Owner can ensure that the lack of functional requirements will be the problem of the development team instead of his. Once the implementation is finished, the Product Owner can even ask the development team to write the specification retroactively based on the code they wrote. I am using the term \"Product Owner\" but when doing low spec the person responsible for the functional specifications is generally not part of the team developing the product and not very available to work with the developers which leads to creating funny roles like \"Proxy Product Owner\" in the team 🤔.",{"type":413,"tag":422,"props":6760,"children":6761},{},[6762],{"type":413,"tag":522,"props":6763,"children":6767},{"alt":6764,"className":6765,"src":6766},"Posts it.",[526,527],"/posts/images/devopsfuture_postit_1.jpg",[],{"type":413,"tag":422,"props":6769,"children":6770},{},[6771],{"type":419,"value":6772},"When there is no written specification at all, we can talk about \"no spec\". For instance, a manager asking the developer team to \"build an API\" on a business domain without providing other details. \"No spec\" practice does not mean there is nothing to do, there is generally a need sometimes urgent 🔥 but no backlog, no user stories, no document that describes what is expected. There is another name for this (that Serverless enthusiasts may like more 😉): Specless. In the same way, Serverless does not mean there is no server, Specless does not mean there are no specifications, it is just that they are in the head of someone but not written anywhere. Specless/no spec is interesting when you need to provide an occupation to a developer team, by giving them a Specless project to do you can keep them busy quite a bit of time because developers will have to guess what the business need, to understand what they need to implement and to find to whom ask their questions.",{"type":413,"tag":414,"props":6774,"children":6776},{"id":6775},"the-lead-reviewer-recipe",[6777],{"type":419,"value":6778},"The lead reviewer recipe",{"type":413,"tag":422,"props":6780,"children":6781},{},[6782],{"type":419,"value":6783},"For this recipe you will need:",{"type":413,"tag":479,"props":6785,"children":6786},{},[6787,6792,6797],{"type":413,"tag":483,"props":6788,"children":6789},{},[6790],{"type":419,"value":6791},"a new fresh project (take one that is quite hard with complex business rules to implement that require closeness between developers and the Product Owner)",{"type":413,"tag":483,"props":6793,"children":6794},{},[6795],{"type":419,"value":6796},"an experienced developer in your company",{"type":413,"tag":483,"props":6798,"children":6799},{},[6800],{"type":419,"value":6801},"an IT consulting company with offshore developers",{"type":413,"tag":422,"props":6803,"children":6804},{},[6805],{"type":419,"value":6806},"Follow the following steps :",{"type":413,"tag":479,"props":6808,"children":6809},{},[6810,6815,6820,6825],{"type":413,"tag":483,"props":6811,"children":6812},{},[6813],{"type":419,"value":6814},"Convince your boss that the new project will cost a lot less if it is done by offshore developers that will be supervised by one of the experienced developers of your company (pay attention to only take into account the short term costs, do not take into consideration the technical debt or other long term costs)",{"type":413,"tag":483,"props":6816,"children":6817},{},[6818],{"type":419,"value":6819},"Negotiate a fixed-price contract with an IT consulting company to do your project (make the price very low to ensure the consulting company will have to put only inexperienced developers on the project to be profitable)",{"type":413,"tag":483,"props":6821,"children":6822},{},[6823],{"type":419,"value":6824},"Contact an experienced developer of your company, tell him you value his skills, and offer him a position of \"technical leader\" on a new project (insist on the fact that it is a real opportunity for him to supervise a team of developers)",{"type":413,"tag":483,"props":6826,"children":6827},{},[6828],{"type":419,"value":6829},"Organize the project so that all go through the experienced developer of your company: the Product Owner should only talk to him, technical specifications sent to the offshore developers should be written by him and all the code should be reviewed by him",{"type":413,"tag":422,"props":6831,"children":6832},{},[6833],{"type":413,"tag":522,"props":6834,"children":6838},{"alt":6835,"className":6836,"src":6837},"A close up of an old fashioned typewriter.",[526,527],"/posts/images/devopsfuture_review_1.jpg",[],{"type":413,"tag":422,"props":6840,"children":6841},{},[6842],{"type":419,"value":6843},"And that is it, your former experienced developer should now have become a lead reviewer: he should only have time to write technical specifications, do code reviews and answer questions from the offshore developers.",{"type":413,"tag":422,"props":6845,"children":6846},{},[6847],{"type":419,"value":6848},"Unfortunately, lead reviewers do not last long. After some time without coding, they become less and less relevant and get bored of only reviewing code. But that should not stop you, once you know the recipe it is easy to make another lead reviewer 😉!",{"type":413,"tag":414,"props":6850,"children":6852},{"id":6851},"the-meeting-game",[6853],{"type":419,"value":6854},"The meeting game",{"type":413,"tag":422,"props":6856,"children":6857},{},[6858],{"type":419,"value":6859},"The meeting game is a game played side many companies. Generally, a manager asked someone to organize a meeting with people from different teams on a topic where some important decisions need to be taken. There are 3 phases in the game: before the meeting, during the meeting, and after the meeting:",{"type":413,"tag":479,"props":6861,"children":6862},{},[6863,6868,6873],{"type":413,"tag":483,"props":6864,"children":6865},{},[6866],{"type":419,"value":6867},"In the first phase, only the organizer of the meeting plays. His goal is to find a date and time for the meeting to take place, the sooner it is the more points he earns. It seems an easy task but it can be a real challenge to find a slot when all the attendees will be available. Indeed the meeting game has become so popular in companies that everyone has already several meeting game sessions in a day. Most active players can even have 8 in a single day 🙃! When sending the meeting invites, it is really important that the organizer provides the less information possible about the topic of the meeting in order not to compromise phase 2, you will understand why in a minute.",{"type":413,"tag":483,"props":6869,"children":6870},{},[6871],{"type":419,"value":6872},"In the second phase, all the attendees of the meeting play except for the organizer who acts as referee. The goal of this phase is to guess what the agenda of the meeting is. It seems a basic question but when playing the meeting game, the agenda is never sent in the invite so most attendees have no idea what the meeting is about. And with a manager who originally requested the meeting not necessarily present, a lot of participants in the meeting (the more players the better 😉) and many meetings in a day for everyone it can become quite hard to guess what the agenda is (sometimes even the organizer does not know exactly). Generally, for a 45 minutes meeting, the game begins 10 minutes after the start time of the meeting as there are always some people coming late from another meeting, the game lasts around 20 minutes the time for someone to find out what is the agenda, and there are 15 min left to discuss and take the decisions.",{"type":413,"tag":483,"props":6874,"children":6875},{},[6876],{"type":419,"value":6877},"In the third phase, attendees have to tell their respective teams what was said and apply the decisions made during the meeting. What makes this phase difficult is that in a meeting game it is recommended not to take notes and completely forbidden for anyone to write and send a meeting report (it is considered \"cheating\"). So participants have to act accordingly to what was decided during the meeting (and sometimes nothing has been settled) with nothing written and approved by everyone to help them. It is always quite funny to see some weeks later what everyone remembers of the meeting, and what actions have been taken.",{"type":413,"tag":422,"props":6879,"children":6880},{},[6881],{"type":413,"tag":522,"props":6882,"children":6886},{"alt":6883,"className":6884,"src":6885},"A group of dices flying through the air.",[526,527],"/posts/images/devopsfuture_dices_1.jpg",[],{"type":413,"tag":422,"props":6888,"children":6889},{},[6890],{"type":419,"value":6891},"There is a variant of the meeting game where the goal of some players is to highjack the meeting by making people talk about other things than the initial topic of the meeting.",{"type":413,"tag":422,"props":6893,"children":6894},{},[6895],{"type":419,"value":6896},"If you feel that some teams in your company are becoming too much productive at work, it is a good idea to impose mandatory meeting game sessions weekly or even daily.",{"type":413,"tag":414,"props":6898,"children":6900},{"id":6899},"everything-as-mail",[6901],{"type":419,"value":6902},"Everything as mail",{"type":413,"tag":422,"props":6904,"children":6905},{},[6906],{"type":419,"value":6907},"You are probably familiar with the practice of treating most things related to IT projects as code: builds and releases pipelines as code, configuration as code, and infrastructure as code just to name a few. There are many benefits to have \"everything as code\": everything is under source control and easy to reuse, everything becomes easily testable, everything can be automated thus predictable and consistent...",{"type":413,"tag":422,"props":6909,"children":6910},{},[6911],{"type":419,"value":6912},"It sounds great but code is a developer thing so forget this and instead think of something that everyone already knows and uses: emails! Emails can be used for all sorts of things and that is what will see.",{"type":413,"tag":831,"props":6914,"children":6916},{"id":6915},"specifications-as-mail",[6917],{"type":419,"value":6918},"Specifications as mail",{"type":413,"tag":422,"props":6920,"children":6921},{},[6922],{"type":419,"value":6923},"Who needs to describe User Stories in Azure Boards, Confluence, Trello, or JetBrains Space when you can write the specifications directly in an email?",{"type":413,"tag":422,"props":6925,"children":6926},{},[6927],{"type":419,"value":6928},"You will not have to struggle to fill all the mandatory fields of a work item, organize the work to do in epics, features, user stories, or whatever, with an email you can write the specifications any way you like. People can respond to your email with additional requests or questions, and by grouping your emails by conversation you will always be able to find what has been \"specified\" on a topic. Your mailbox is your source control where you will find the entire history of what has been said or decided in a project. Moreover using specifications as mail values people with organized mailboxes: the ability to quickly find the email conversation that contains the specification you are looking for becomes a necessary skill.",{"type":413,"tag":422,"props":6930,"children":6931},{},[6932],{"type":419,"value":6933},"Some people may say that it is quite difficult to track all the work there is to do just by using emails. But that is not true, be sure that when it is important work, even if it is buried deep in a conversation of your mailbox there will always be someone to unearth the topic or feature specified in this conversation. When you use specifications as mail, there is even a system to prioritize what is important. You can prioritize items in three different ways:",{"type":413,"tag":479,"props":6935,"children":6936},{},[6937,6942,6947],{"type":413,"tag":483,"props":6938,"children":6939},{},[6940],{"type":419,"value":6941},"Firstly, mark an email as important (not very effective as everyone does that)",{"type":413,"tag":483,"props":6943,"children":6944},{},[6945],{"type":419,"value":6946},"Secondly, write your email in all caps (especially useful to answer your own email to tell everyone that the work you have asked for has not been done and you are not happy about it)",{"type":413,"tag":483,"props":6948,"children":6949},{},[6950],{"type":419,"value":6951},"And last but not least, add lots of additional recipients in your email, especially VIPs in your company (always fun to make people nervous by including their line manager or their manager's manager in the conversation)",{"type":413,"tag":422,"props":6953,"children":6954},{},[6955],{"type":413,"tag":522,"props":6956,"children":6960},{"alt":6957,"className":6958,"src":6959},"A red envelope sitting on top of a white sheet of paper.",[526,527],"/posts/images/devopsfuture_mail_1.jpg",[],{"type":413,"tag":422,"props":6962,"children":6963},{},[6964],{"type":419,"value":6965},"If you are more a Word person to write your specifications, you can always attach your Word specifications in the emails you exchange. This way you are still doing specifications as mail while using your favorite text editor. In that scenario, what happens generally is the following:",{"type":413,"tag":479,"props":6967,"children":6968},{},[6969,6980,6985,6990],{"type":413,"tag":483,"props":6970,"children":6971},{},[6972,6974],{"type":419,"value":6973},"You send a document by email to your colleagues with a version number in its name for instance, ",{"type":413,"tag":552,"props":6975,"children":6977},{"className":6976},[],[6978],{"type":419,"value":6979},"Awesome specifications v1.0.docx",{"type":413,"tag":483,"props":6981,"children":6982},{},[6983],{"type":419,"value":6984},"One of your colleagues modifies it, adds a line at the beginning of the document to explain the changes he or she did, and increments the version number in the document's name before sending back the document to everyone",{"type":413,"tag":483,"props":6986,"children":6987},{},[6988],{"type":419,"value":6989},"The document continues to live like that for a while as you and your colleagues are modifying it",{"type":413,"tag":483,"props":6991,"children":6992},{},[6993],{"type":419,"value":6994},"You end up having many versions of the same document in your email box, and a specifications document that starts with 3 pages of history explaining the changes between versions",{"type":413,"tag":422,"props":6996,"children":6997},{},[6998],{"type":419,"value":6999},"Well, that is not a very environmentally friendly way of handling specifications versioning but it works fine as long as everyone takes the latest version of the specifications before modifying it and that 2 people do not do changes simultaneously.",{"type":413,"tag":831,"props":7001,"children":7003},{"id":7002},"bugs-as-mail",[7004],{"type":419,"value":7005},"Bugs as mail",{"type":413,"tag":422,"props":7007,"children":7008},{},[7009],{"type":419,"value":7010},"Bugs as mail are similar to specifications as mail. The concept is to use email instead of using any bug tracker like Jira, Azure Boards, or GitHub issues. Why would you bother to create a bug in a bug tracker and provide all the required information? If you spotted a bug, you do not want to wait for this bug to be planned on a sprint depending on its priority, to wait for it to be assigned to someone, and even less to have to regularly check the status of the bug to know if it has been corrected. It is much easier to directly send an email to the developer you think can help you and exchange by email until it gets solved.",{"type":413,"tag":422,"props":7012,"children":7013},{},[7014],{"type":419,"value":7015},"To prioritize bugs, you can use the same methods you use to prioritize specifications.",{"type":413,"tag":831,"props":7017,"children":7019},{"id":7018},"infrastructure-as-mail",[7020],{"type":419,"value":7021},"Infrastructure as mail",{"type":413,"tag":422,"props":7023,"children":7024},{},[7025],{"type":419,"value":7026},"Of the different \"as mail\" practices, this one is my favorite. For that one you need to have \"traditional horizontal teams\", understand interdependent teams divided according to the software architecture: a back-end team, a front-end team, an infrastructure team, a database team...",{"type":413,"tag":422,"props":7028,"children":7029},{},[7030],{"type":419,"value":7031},"Let's take an example for you to understand the infrastructure as mail practice. Imagine you are a member of the back-end team in charge of developing the Web API on the project. You are developing a new route that requires a specific configuration setting (or environment variable) that needs to be set on the platform that hosts your application (whether it is a virtual machine, an app service, or a docker container). As the developer of this new route, you are the most relevant person to set this new setting as you exactly know what is its purpose; however, it is not in your scope so you will have to contact the infrastructure team to do it. Therefore, you will send an email to the infrastructure team explaining what needs to be set so that they do it instead of you, and that's infrastructure as mail !📧",{"type":413,"tag":422,"props":7033,"children":7034},{},[7035],{"type":419,"value":7036},"Every change that you will need to do that is related to infrastructure will require you to send an email to the infrastructure team. If you store secrets in your application in a key vault and you need to add another secret in this key vault, you write an email to add this secret. If you need to set some configuration on a cloud resource your application is using, you write an email to have this resource configured for you. You do not care whether the change is tied to what you are developing or not, you are a developer-only here to write code, the infrastructure should not be your concern, you just have to send an email specifying what change you need in the infrastructure.",{"type":413,"tag":422,"props":7038,"children":7039},{},[7040],{"type":413,"tag":522,"props":7041,"children":7045},{"alt":7042,"className":7043,"src":7044},"A wall with many different colored envelopes on it.",[526,527],"/posts/images/devopsfuture_mail_2.jpg",[],{"type":413,"tag":422,"props":7047,"children":7048},{},[7049],{"type":419,"value":7050},"It is however really important to have a clear separation between the developer team and the infrastructure team. You certainly do not want to have some infrastructure people directly in your team because they could understand what features you are developing and what you are trying to achieve when you request a change in the infrastructure and because you could understand how the infrastructure works. If you have a team on a functional perimeter that can easily develop functionalities and operate them, you will be able to push quickly features to production, the customers will be happy but the business people will think development and infrastructure are easy things and you will have less consideration, time and budget.  That is not what you want! To avoid that, make sure that developers have no idea how the infrastructure is implemented and that the infrastructure team has no idea what the application code does. In doing so, you can be sure that infrastructure modifications discussed by email will be misinterpreted, that they will lead to errors, and that synchronizing application deployment and infrastructure deployment will be hard. Thus, business people will value you and your colleagues because the work you do will look very complex and as a matter of fact be very complex.",{"type":413,"tag":422,"props":7052,"children":7053},{},[7054],{"type":419,"value":7055},"What is funny with infrastructure as mail is that you can have the best engineers implementing the infrastructure, using the latest infrastructure as code tools (Pulumi, Terraform ...) in the end it always comes to exchanging emails between people.",{"type":413,"tag":414,"props":7057,"children":7059},{"id":7058},"go-big-or-go-home",[7060],{"type":419,"value":7061},"Go big or go home",{"type":413,"tag":422,"props":7063,"children":7064},{},[7065],{"type":419,"value":7066},"I do not know if this one is a best practice or just common sense. When you are building a product that is used by many customers, what you want is to have a big impact on them and bring to the product lots of new features to make the customer happy, thus what you want is to \"go big or go home\". Nowadays many people are talking about short development cycles, only delivering a few features at a time but more quickly. It may work for their small projects but you are working on big projects. You do not want the customer to only have one or two features every three weeks, you want your product to be completely transformed with big changes to impress the customer even if it takes 2 years to make them.",{"type":413,"tag":422,"props":7068,"children":7069},{},[7070],{"type":419,"value":7071},"Some people say deploying many changes at a time can be risky, especially if you are not used to deploying often, issues can occur and you will not be prepared to handle that. Those people are fools, if you have done a big testing phase and you have qualified people to deploy your software nothing can go wrong! And if ever an incident occurs, just find the person responsible and fire that person: you will learn nothing from the incident but people will have a fear of failure and will be more careful next time 😈. Concerning the fact that the needs of the customer might not correspond to what you specified or might have changed the time for your waterfall project to go to production, you should not worry too much about that if you have done a solid specifications phase. To sum up, \"Never fail\" should be your motto and not non-sense phrases like \"fail fast, recover quickly\" or \"fail fast, learn fast\".",{"type":413,"tag":422,"props":7073,"children":7074},{},[7075],{"type":413,"tag":522,"props":7076,"children":7080},{"alt":7077,"className":7078,"src":7079},"A small turtle walking across a sandy beach.",[526,527],"/posts/images/devopsfuture_tortoise_1.jpg",[],{"type":413,"tag":422,"props":7082,"children":7083},{},[7084],{"type":419,"value":7085},"If someone in your organization starts talking about continuous deployment, stop him right away. How your company should work is by doing waterfall projects with a big specification phase, a huge development phase, a long Quality Assurance phase, and the time necessary to deploy all that. Each major release of your software should follow this cycle to produce new big versions every 3 to 4 years for example. And if some bugs are discovered between these major releases, fix them but do not make the mistake to deploy the bug fixes right away. Doing a patch release just for a few bugs would be ridiculous and you would risk making the customer accustomed to having problems corrected quickly. No, make the fix, let it in a drawer and wait 6 months that there are enough other corrections to deploy to make another release. This is often called a \"maintenance release\" as it contains only bug fixes. I like to call such a release a cold fix, it is like a hotfix in production but you have waited so much time that you cannot call it 'hot' 🥶. If you want to please people that want to do continuous deployment, tell them you are doing a new thing called \"slow continuous deployment\". \"Go big or go home\" is probably the practice that has always been followed in your company, so there is no reason to change: it is not because many other companies like Google or Microsoft have changed their way of working to release their products more often that you should do the same. Yes, your competitors might out-innovate you by deploying new features faster than you do, but that is better than burdening yourself with trying to transform the organization of your company.",{"type":413,"tag":422,"props":7087,"children":7088},{},[7089],{"type":413,"tag":522,"props":7090,"children":7094},{"alt":7091,"className":7092,"src":7093},"A small waterfall in the middle of a forest.",[526,527],"/posts/images/devopsfuture_waterfall_1.jpg",[],{"type":413,"tag":414,"props":7096,"children":7098},{"id":7097},"divide-and-rule-among-your-company",[7099],{"type":419,"value":7100},"Divide and rule among your company",{"type":413,"tag":422,"props":7102,"children":7103},{},[7104],{"type":419,"value":7105},"Supporters of DevOps often talk about changing the organization of a company to work with vertical teams instead of horizontal teams. The aim is to have self-organizing end-to-end teams that are cross-functional (with people that have different expertise and are not just back-end people, data people, IT operations people, or UX people together) and that do not depend directly on other teams to do their work. This is a big mistake because if you have self-organizing teams in your company you lose some power and control over everyone. It is better to stay with the traditional horizontal teams, which is something that does not require any culture-shift nor transformation of the way your company is structured.",{"type":413,"tag":422,"props":7107,"children":7108},{},[7109],{"type":419,"value":7110},"If you have watched Game of Thrones, the practice \"divide and rule among your company\" is something you will find nice. Imagine each horizontal team or each department of your company as a House in Game of Thrones looking for more power that is more budget and responsibility. Even if everyone is supposed to share a common goal, each team has its own objectives, its processes, its methodology, and uses its own tools. Without saying that teams are working against each other, teams are for sure not working together. When an issue occurs, the goal for a team is not to contribute to solving it as quickly as possible but it is to make sure it can not be held responsible for the issue. With this practice, there is a toxic culture that encourages teams to blame each other: front-end team blames the back-end team, back-end team blames the QA team, QA team blames the architecture team, and everyone blames the infrastructure team... In this context, as a product/program/department manager supervising multiple horizontal teams, it is quite easy for you to maintain your power and control everything because teams are too busy fighting each other to contest the management decisions. Besides, because of dependency between teams, everyone is waiting for everyone, so projects fall behind schedule. And when a project falls behind schedule, the common understanding is that the project needs more people, thus more people are hired to work on your projects, of course, it solves nothing (quite the contrary in fact) but having more people means more budget so more responsibility for you. It is easier to play an influential role in your company when you supervise 6 teams of 10 people each than when you only have 2 teams of 4 people to manage.",{"type":413,"tag":422,"props":7112,"children":7113},{},[7114],{"type":413,"tag":522,"props":7115,"children":7119},{"alt":7116,"className":7117,"src":7118},"A close up of a chess board with pieces on it.",[526,527],"/posts/images/devopsfuture_chess_1.jpg",[],{"type":413,"tag":422,"props":7121,"children":7122},{},[7123],{"type":419,"value":7124},"Of course, playing \"divide and rule among your company\" with good old does horizontal teams does not look the most trendy thing to the outside world yet it is not that complicated to be among the cool kids anyway. There is probably in your company an Application Life Management team, or maybe a team in charge of automating build and release pipelines for all the other teams, or even a team dedicated to writing scripts to build your software infrastructure. You can take that team or merge these 3 teams in one if you have these 3 teams and then just rename it to \"DevOps team\". Of course, you are not applying DevOps practices by doing that, you just have a bunch of people in a team called \"DevOps\" that automate stuff for the rest of the company, but who cares? Most people do not know what DevOps is about so they will just think \"What an innovative company, they are doing DevOps, they even have a team dedicated to doing DevOps\". In addition to that, people in this new team will feel great because they will have a new and nice job title to put on their CV: \"DevOps Engineer\" (which is non-sense as DevOps is not a job title but whatever... recruiters are looking for this). If you prefer you can also use the name \"Site Reliability Engineering\" for the name of your new team, what is important is not what the name means but how it sounds and how it can make your company shine to the outside world.",{"type":413,"tag":7126,"props":7127,"children":7131},"div",{"className":7128},[7129,7130],"flex","justify-center",[7132],{"type":413,"tag":7133,"props":7134,"children":7136},"tweet",{"id":7135},"852879869998501889",[],{"type":413,"tag":414,"props":7138,"children":7140},{"id":7139},"final-words",[7141],{"type":419,"value":7142},"Final words",{"type":413,"tag":422,"props":7144,"children":7145},{},[7146],{"type":419,"value":7147},"I hope this article made you laugh or at least smile a little bit because it probably reminds you of similar \"best practices\" you have seen. And most importantly I hope it made you think about practices in your company that could be improved.",{"type":413,"tag":422,"props":7149,"children":7150},{},[7151],{"type":419,"value":7152},"Do not worry, despite all I said I am a big fan of DevOps principles 💖. I think DevOps can help to deliver more value to the end-users of the applications we are developing. Yet, DevOps practices are not an easy thing to apply as it often implies transforming the way the company is organized. Because DevOps is challenging, people prefer to keep the bad practices they gradually adopted over the years, practices like the ones I described in this article.",{"type":413,"tag":422,"props":7154,"children":7155},{},[7156],{"type":419,"value":7157},"Indeed despite our best efforts, we all make bad choices but instead of considering that it is too late to change things, we should learn from our mistakes, try to make things better, and keep improving over time. Adopting DevOps practices is a journey that never really ends: there is always something to improve.",{"type":413,"tag":422,"props":7159,"children":7160},{},[7161,7163,7169,7170,7176,7178,7185],{"type":419,"value":7162},"If you are interested in DevOps, I highly recommend reading ",{"type":413,"tag":552,"props":7164,"children":7166},{"className":7165},[],[7167],{"type":419,"value":7168},"The Phoenix Project",{"type":419,"value":5589},{"type":413,"tag":552,"props":7171,"children":7173},{"className":7172},[],[7174],{"type":419,"value":7175},"The Unicorn Project",{"type":419,"value":7177},", two captivating novels about DevOps that I enjoyed reading. Companies talking about how their DevOps journey is also a great way to learn more about DevOps, for instance, there is this ",{"type":413,"tag":452,"props":7179,"children":7182},{"href":7180,"rel":7181},"https://youtu.be/kgDdywJTBZ4",[456],[7183],{"type":419,"value":7184},"video",{"type":419,"value":7186}," from Microsoft which is great. And that is the end of this article, I wish you an amazing DevOps journey too.",{"title":401,"searchDepth":742,"depth":742,"links":7188},[7189,7190,7191,7192,7197,7198,7199],{"id":6745,"depth":742,"text":6748},{"id":6775,"depth":742,"text":6778},{"id":6851,"depth":742,"text":6854},{"id":6899,"depth":742,"text":6902,"children":7193},[7194,7195,7196],{"id":6915,"depth":995,"text":6918},{"id":7002,"depth":995,"text":7005},{"id":7018,"depth":995,"text":7021},{"id":7058,"depth":742,"text":7061},{"id":7097,"depth":742,"text":7100},{"id":7139,"depth":742,"text":7142},"content:1.posts:12.devops-future.md","1.posts/12.devops-future.md",1716749600640]