[{"data":1,"prerenderedAt":1895},["ShallowReactive",2],{"/blog/kubecon-na-2019-are-you-about-to-break-prod":3,"navigation-en-us":1110,"banner-en-us":1535,"footer-en-us":1545,"blog-post-authors-en-us-Erin Krengel, Pulumi":1790,"blog-related-posts-en-us-kubecon-na-2019-are-you-about-to-break-prod":1806,"blog-promotions-en-us":1831,"next-steps-en-us":1885},{"id":4,"title":5,"authors":6,"body":8,"category":1086,"date":1087,"description":1088,"extension":1089,"externalUrl":1090,"faq":1090,"featured":1091,"heroImage":1092,"meta":1093,"navigation":82,"path":1094,"seo":1095,"slug":1099,"stem":1100,"tags":1101,"template":1108,"updatedDate":1090,"__hash__":1109},"blogPosts/en-us/blog/kubecon-na-2019-are-you-about-to-break-prod.md","KubeCon NA: Are you about to break Prod?",[7],"Erin Krengel, Pulumi",{"type":9,"value":10,"toc":1077},"minimark",[11,28,31,34,85,88,93,96,100,103,106,109,113,132,384,389,392,399,408,411,607,611,627,630,643,789,800,804,807,819,826,1073],[12,13,14,15,21,22,27],"p",{},"A couple of months ago, my ",[16,17,20],"a",{"href":18,"rel":19},"https://www.pulumi.com/",[],"Pulumi"," colleague Sean Holung, staff sofware engineer, and I had the opportunity to present ",[16,23,26],{"href":24,"rel":25},"https://www.youtube.com/watch?v=jAQhDZiRzBQ",[],"\"Are you about to break prod? Acceptance Testing with Ephemeral Environments\""," at KubeCon NA 2019. In this talk, we covered what is an ephemeral environment, how to create one, and then we walked the audience through a concrete example. Given our limited time, we had to move quickly through a ton of information. This post will recap our presentation and add a few more details we weren't able to cover.",[12,29,30],{},"As software engineers, our job is to deliver business value. To do this, we need to be delivering software both quickly and reliably.",[12,32,33],{},"So the question we ask you is: are you about to break prod? Everyone will break production at some point because there are things we miss. As independent software lead Alexandra Johnson sums up so well in a tweet: \"Failures are part of the cost of building and shipping large systems.\" Building a robust pipeline allows us to move quickly in the case of failure and gain confidence around making changes to our infrastructure and applications.",[35,36,39,78,79],"div",{"className":37},[38],"center",[40,41,44,73,74],"blockquote",{"className":42},[43],"twitter-tweet",[12,45,48,49,53,54,58,59,63,64,58,68,72],{"lang":46,"dir":47},"en","ltr","Big takeaway from ",[16,50,52],{"href":51},"https://twitter.com/hashtag/KubeCon?src=hash&ref_src=twsrc%5Etfw","#KubeCon",": none of us want to break prod, but failures are part of the cost of building and shipping large systems. Using tools like ",[16,55,57],{"href":56},"https://twitter.com/hashtag/AcceptanceTesting?src=hash&ref_src=twsrc%5Etfw","#AcceptanceTesting"," (",[16,60,62],{"href":61},"https://twitter.com/eckrengel?ref_src=twsrc%5Etfw","@eckrengel",") and ",[16,65,67],{"href":66},"https://twitter.com/hashtag/ChaosEngineering?src=hash&ref_src=twsrc%5Etfw","#ChaosEngineering",[16,69,71],{"href":70},"https://twitter.com/Ana_M_Medina?ref_src=twsrc%5Etfw","@Ana_M_Medina",") can increase your confidence in your infrastructure changes!","— Alexandra Johnson (@alexandraj777) ",[16,75,77],{"href":76},"https://twitter.com/alexandraj777/status/1198373475049623552?ref_src=twsrc%5Etfw","November 23, 2019"," ",[80,81],"script",{"async":82,"src":83,"charSet":84},true,"https://platform.twitter.com/widgets.js","utf-8",[12,86,87],{},"With this in mind, we use Pulumi and GitLab to build a pipeline that validates both our application, infrastructure, and deployment process.",[89,90,92],"h2",{"id":91},"ephemeral-environments","Ephemeral environments",[12,94,95],{},"What is an ephemeral environment? It is a short-lived environment that mimics a production environment. To maintain agility, boundaries are defined in the environment to only encompass the first-level dependencies of the particular microservice that is being deployed. It means you don't have to spin up every single microservice or piece of infrastructure that's running in production. Yet you may need to spin up extra pieces of infrastructure to properly test the microservice. For example, you may need to create a subscription to pull from a PubSub topic your microservice writes to. This subscription would allow your acceptance tests to pull from a topic in order to validate an outbound message is published.",[89,97,99],{"id":98},"why-this-is-important","Why this is important",[12,101,102],{},"Infrastructure is a key part of an application's behavior. The architecture and requirements are continually evolving. How can you incorporate these into a testing suite to give us a high degree of confidence?",[12,104,105],{},"Ephemeral environments allow you to integrate infrastructure and deployment processes into a testing suite. They ensure your testing environment is always in-sync with production and therefore allow you to iterate quickly to meet new requirements.",[12,107,108],{},"Ephemeral environments also encourage you to lean on automated tests over manual tests. If you use ephemeral environments as a replacement for a testing environment, there is not enough time to go in and run a manual check. Shifting your mindset to automated tests can be challenging, yet it's imperative that we do so. Automated tests guarantee your application behaves as expected today as well as months from now when you're out on vacation.",[89,110,112],{"id":111},"our-demo-application","Our demo application",[12,114,115,116,120,121,126,127,131],{},"To demonstrate the effectiveness of integrating acceptance testing with ephemeral environments into your deployment process, we created a simple demo application. The service is written in Go and accepts a message on the ",[117,118,119],"code",{},"/message"," endpoint, then places it in a storage bucket and sends a notification about the new object on a PubSub topic. The code for this application lives in our ",[16,122,125],{"href":123,"rel":124},"https://gitlab.com/rocore/demo-app/blob/master/main.go",[],"main.go"," file. While you can walk through this code yourself, the most important thing to call out is that our application is ",[128,129,130],"em",{},"configurable",". This means we take configuration in at the very beginning of our main function and shut down the application if the values are not present.",[133,134,139],"pre",{"className":135,"code":136,"language":137,"meta":138,"style":138},"language-go shiki shiki-themes github-light","func main() {\n    ...\n    // Get configuration from environment variables. These are\n    // required configuration values, so we use an helper\n    // function get the values and exit if the value is not set.\n    project := getConfigurationValue(\"PROJECT\")\n    topicName := getConfigurationValue(\"TOPIC\")\n    bucketName := getConfigurationValue(\"BUCKET\")\n    ...\n}\n\nfunc getConfigurationValue(envVar string) string {\n    value := os.Getenv(envVar)\n    if value == \"\" {\n        log.Fatalf(\"%s not set\", envVar)\n    }\n    log.Printf(\"%s: %s\", envVar, value)\n    return value\n}\n","go","",[117,140,141,158,164,171,177,183,205,222,239,244,250,256,281,298,315,339,345,370,379],{"__ignoreMap":138},[142,143,146,150,154],"span",{"class":144,"line":145},"line",1,[142,147,149],{"class":148},"sD7c4","func",[142,151,153],{"class":152},"s7eDp"," main",[142,155,157],{"class":156},"sgsFI","() {\n",[142,159,161],{"class":144,"line":160},2,[142,162,163],{"class":148},"    ...\n",[142,165,167],{"class":144,"line":166},3,[142,168,170],{"class":169},"sAwPA","    // Get configuration from environment variables. These are\n",[142,172,174],{"class":144,"line":173},4,[142,175,176],{"class":169},"    // required configuration values, so we use an helper\n",[142,178,180],{"class":144,"line":179},5,[142,181,182],{"class":169},"    // function get the values and exit if the value is not set.\n",[142,184,186,189,192,195,198,202],{"class":144,"line":185},6,[142,187,188],{"class":156},"    project ",[142,190,191],{"class":148},":=",[142,193,194],{"class":152}," getConfigurationValue",[142,196,197],{"class":156},"(",[142,199,201],{"class":200},"sYBdl","\"PROJECT\"",[142,203,204],{"class":156},")\n",[142,206,208,211,213,215,217,220],{"class":144,"line":207},7,[142,209,210],{"class":156},"    topicName ",[142,212,191],{"class":148},[142,214,194],{"class":152},[142,216,197],{"class":156},[142,218,219],{"class":200},"\"TOPIC\"",[142,221,204],{"class":156},[142,223,225,228,230,232,234,237],{"class":144,"line":224},8,[142,226,227],{"class":156},"    bucketName ",[142,229,191],{"class":148},[142,231,194],{"class":152},[142,233,197],{"class":156},[142,235,236],{"class":200},"\"BUCKET\"",[142,238,204],{"class":156},[142,240,242],{"class":144,"line":241},9,[142,243,163],{"class":148},[142,245,247],{"class":144,"line":246},10,[142,248,249],{"class":156},"}\n",[142,251,253],{"class":144,"line":252},11,[142,254,255],{"emptyLinePlaceholder":82},"\n",[142,257,259,261,263,265,269,272,275,278],{"class":144,"line":258},12,[142,260,149],{"class":148},[142,262,194],{"class":152},[142,264,197],{"class":156},[142,266,268],{"class":267},"sqxcx","envVar",[142,270,271],{"class":148}," string",[142,273,274],{"class":156},") ",[142,276,277],{"class":148},"string",[142,279,280],{"class":156}," {\n",[142,282,284,287,289,292,295],{"class":144,"line":283},13,[142,285,286],{"class":156},"    value ",[142,288,191],{"class":148},[142,290,291],{"class":156}," os.",[142,293,294],{"class":152},"Getenv",[142,296,297],{"class":156},"(envVar)\n",[142,299,301,304,307,310,313],{"class":144,"line":300},14,[142,302,303],{"class":148},"    if",[142,305,306],{"class":156}," value ",[142,308,309],{"class":148},"==",[142,311,312],{"class":200}," \"\"",[142,314,280],{"class":156},[142,316,318,321,324,326,329,333,336],{"class":144,"line":317},15,[142,319,320],{"class":156},"        log.",[142,322,323],{"class":152},"Fatalf",[142,325,197],{"class":156},[142,327,328],{"class":200},"\"",[142,330,332],{"class":331},"sYu0t","%s",[142,334,335],{"class":200}," not set\"",[142,337,338],{"class":156},", envVar)\n",[142,340,342],{"class":144,"line":341},16,[142,343,344],{"class":156},"    }\n",[142,346,348,351,354,356,358,360,363,365,367],{"class":144,"line":347},17,[142,349,350],{"class":156},"    log.",[142,352,353],{"class":152},"Printf",[142,355,197],{"class":156},[142,357,328],{"class":200},[142,359,332],{"class":331},[142,361,362],{"class":200},": ",[142,364,332],{"class":331},[142,366,328],{"class":200},[142,368,369],{"class":156},", envVar, value)\n",[142,371,373,376],{"class":144,"line":372},18,[142,374,375],{"class":148},"    return",[142,377,378],{"class":156}," value\n",[142,380,382],{"class":144,"line":381},19,[142,383,249],{"class":156},[385,386,388],"h3",{"id":387},"infrastructure","Infrastructure",[12,390,391],{},"There are many pieces of infrastructure to spin up and we can use Pulumi to easily wire it all together. Our architecture looks like this:",[12,393,394],{},[395,396],"img",{"alt":397,"src":398},"Pulumi Architecture","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398883/blog/Content%20Images/pulumidemoarch.jpg",[12,400,401,402,407],{},"You can check out the Pulumi code that we use to reproduce both our ephemeral environments as well as production in the ",[16,403,406],{"href":404,"rel":405},"https://gitlab.com/rocore/demo-app/blob/master/infrastructure/index.ts",[],"infrastructure/index.ts"," file. The neat thing about using Pulumi is that we can create the Google Cloud Platform (GCP) resources we need and then directly reference them in our Kubernetes deployment. Using Pulumi ensures we're always configuring our application with the correct GCP resources for that environment.",[12,409,410],{},"For example, in our Kubernetes deployment, we set the environment variables by using the topic and bucket variables created just above.",[133,412,416],{"className":413,"code":414,"language":415,"meta":138,"style":138},"language-typescript shiki shiki-themes github-light","// Create a K8s Deployment for our application.\nconst appLabels = { appClass: name };\nconst deployment = new k8s.apps.v1.Deployment(name, {\n    metadata: { labels: appLabels },\n    spec: {\n        selector: { matchLabels: appLabels },\n        template: {\n            metadata: { labels: appLabels },\n            spec: {\n                containers: [{\n                    ...\n                    env: [\n                        { name: \"TOPIC\", value: topic.name }, // referencing topic just created\n                        { name: \"BUCKET\", value: bucket.name }, // referencing bucket just created\n                        { name: \"PROJECT\", value: project },\n                        {\n                            name: \"GOOGLE_APPLICATION_CREDENTIALS\",\n                            value: \"/var/secrets/google/key.json\"\n                        },\n                    ],\n                    ...\n                }]\n            }\n        }\n    },\n});\n","typescript",[117,417,418,423,437,458,463,468,473,478,483,488,493,498,503,516,528,537,542,553,561,566,572,577,583,589,595,601],{"__ignoreMap":138},[142,419,420],{"class":144,"line":145},[142,421,422],{"class":169},"// Create a K8s Deployment for our application.\n",[142,424,425,428,431,434],{"class":144,"line":160},[142,426,427],{"class":148},"const",[142,429,430],{"class":331}," appLabels",[142,432,433],{"class":148}," =",[142,435,436],{"class":156}," { appClass: name };\n",[142,438,439,441,444,446,449,452,455],{"class":144,"line":166},[142,440,427],{"class":148},[142,442,443],{"class":331}," deployment",[142,445,433],{"class":148},[142,447,448],{"class":148}," new",[142,450,451],{"class":156}," k8s.apps.v1.",[142,453,454],{"class":152},"Deployment",[142,456,457],{"class":156},"(name, {\n",[142,459,460],{"class":144,"line":173},[142,461,462],{"class":156},"    metadata: { labels: appLabels },\n",[142,464,465],{"class":144,"line":179},[142,466,467],{"class":156},"    spec: {\n",[142,469,470],{"class":144,"line":185},[142,471,472],{"class":156},"        selector: { matchLabels: appLabels },\n",[142,474,475],{"class":144,"line":207},[142,476,477],{"class":156},"        template: {\n",[142,479,480],{"class":144,"line":224},[142,481,482],{"class":156},"            metadata: { labels: appLabels },\n",[142,484,485],{"class":144,"line":241},[142,486,487],{"class":156},"            spec: {\n",[142,489,490],{"class":144,"line":246},[142,491,492],{"class":156},"                containers: [{\n",[142,494,495],{"class":144,"line":252},[142,496,497],{"class":148},"                    ...\n",[142,499,500],{"class":144,"line":258},[142,501,502],{"class":156},"                    env: [\n",[142,504,505,508,510,513],{"class":144,"line":283},[142,506,507],{"class":156},"                        { name: ",[142,509,219],{"class":200},[142,511,512],{"class":156},", value: topic.name }, ",[142,514,515],{"class":169},"// referencing topic just created\n",[142,517,518,520,522,525],{"class":144,"line":300},[142,519,507],{"class":156},[142,521,236],{"class":200},[142,523,524],{"class":156},", value: bucket.name }, ",[142,526,527],{"class":169},"// referencing bucket just created\n",[142,529,530,532,534],{"class":144,"line":317},[142,531,507],{"class":156},[142,533,201],{"class":200},[142,535,536],{"class":156},", value: project },\n",[142,538,539],{"class":144,"line":341},[142,540,541],{"class":156},"                        {\n",[142,543,544,547,550],{"class":144,"line":347},[142,545,546],{"class":156},"                            name: ",[142,548,549],{"class":200},"\"GOOGLE_APPLICATION_CREDENTIALS\"",[142,551,552],{"class":156},",\n",[142,554,555,558],{"class":144,"line":372},[142,556,557],{"class":156},"                            value: ",[142,559,560],{"class":200},"\"/var/secrets/google/key.json\"\n",[142,562,563],{"class":144,"line":381},[142,564,565],{"class":156},"                        },\n",[142,567,569],{"class":144,"line":568},20,[142,570,571],{"class":156},"                    ],\n",[142,573,575],{"class":144,"line":574},21,[142,576,497],{"class":148},[142,578,580],{"class":144,"line":579},22,[142,581,582],{"class":156},"                }]\n",[142,584,586],{"class":144,"line":585},23,[142,587,588],{"class":156},"            }\n",[142,590,592],{"class":144,"line":591},24,[142,593,594],{"class":156},"        }\n",[142,596,598],{"class":144,"line":597},25,[142,599,600],{"class":156},"    },\n",[142,602,604],{"class":144,"line":603},26,[142,605,606],{"class":156},"});\n",[385,608,610],{"id":609},"acceptance-tests","Acceptance tests",[12,612,613,614,78,617,622,623,626],{},"The acceptance tests validate that our service, when stood up, functions as expected. They are run against an ephemeral environment. The tests live in the ",[117,615,616],{},"acceptance/acceptance_test.go",[16,618,621],{"href":619,"rel":620},"https://gitlab.com/rocore/demo-app/blob/master/acceptance/acceptance_test.go",[],"file",". You'll notice we're once again using the helper function ",[117,624,625],{},"getConfigurationValue",". Our acceptance test must also be configured to ensure they're validating against the correct resources for that particular ephemeral environment.",[12,628,629],{},"Since the service is only accessible from within the Kubernetes cluster, we use a Kubernetes job to run our acceptance tests. Using a Kubernetes job is a good technique to use when your CI is running externally, such as from GitLab, and you do not want to expose your service publicly. Our ephemeral environment plus acceptance test looks like this:",[12,631,632,636,639,640,642],{},[395,633],{"alt":634,"src":635},"Acceptance Tests","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398881/blog/Content%20Images/pulumiacceptancetestarch.jpg",[637,638],"br",{},"\nWe spin up a Kubernetes Job and additional resources by using an if statement at the bottom of our ",[117,641,406],{}," file. The conditional depends on the environment's name as follows:",[133,644,646],{"className":413,"code":645,"language":415,"meta":138,"style":138},"// If it's a test environment, set up acceptance tests.\nlet job: k8s.batch.v1.Job | undefined;\nif (ENV.startsWith(\"test\")) {\n    job = acceptance.setupAcceptanceTests({\n        ...\n    });\n}\n\n// Export the acceptance job name, so we can get the logs from our\n// acceptance tests.\nexport const acceptanceJobName = job ? job.metadata.name : \"unapplicable\";\n",[117,647,648,653,692,715,732,737,742,746,750,755,760],{"__ignoreMap":138},[142,649,650],{"class":144,"line":145},[142,651,652],{"class":169},"// If it's a test environment, set up acceptance tests.\n",[142,654,655,658,661,664,667,670,673,675,678,680,683,686,689],{"class":144,"line":160},[142,656,657],{"class":148},"let",[142,659,660],{"class":156}," job",[142,662,663],{"class":148},":",[142,665,666],{"class":152}," k8s",[142,668,669],{"class":156},".",[142,671,672],{"class":152},"batch",[142,674,669],{"class":156},[142,676,677],{"class":152},"v1",[142,679,669],{"class":156},[142,681,682],{"class":152},"Job",[142,684,685],{"class":148}," |",[142,687,688],{"class":331}," undefined",[142,690,691],{"class":156},";\n",[142,693,694,697,699,702,704,707,709,712],{"class":144,"line":166},[142,695,696],{"class":148},"if",[142,698,58],{"class":156},[142,700,701],{"class":331},"ENV",[142,703,669],{"class":156},[142,705,706],{"class":152},"startsWith",[142,708,197],{"class":156},[142,710,711],{"class":200},"\"test\"",[142,713,714],{"class":156},")) {\n",[142,716,717,720,723,726,729],{"class":144,"line":173},[142,718,719],{"class":156},"    job ",[142,721,722],{"class":148},"=",[142,724,725],{"class":156}," acceptance.",[142,727,728],{"class":152},"setupAcceptanceTests",[142,730,731],{"class":156},"({\n",[142,733,734],{"class":144,"line":179},[142,735,736],{"class":148},"        ...\n",[142,738,739],{"class":144,"line":185},[142,740,741],{"class":156},"    });\n",[142,743,744],{"class":144,"line":207},[142,745,249],{"class":156},[142,747,748],{"class":144,"line":224},[142,749,255],{"emptyLinePlaceholder":82},[142,751,752],{"class":144,"line":241},[142,753,754],{"class":169},"// Export the acceptance job name, so we can get the logs from our\n",[142,756,757],{"class":144,"line":246},[142,758,759],{"class":169},"// acceptance tests.\n",[142,761,762,765,768,771,773,776,779,782,784,787],{"class":144,"line":252},[142,763,764],{"class":148},"export",[142,766,767],{"class":148}," const",[142,769,770],{"class":331}," acceptanceJobName",[142,772,433],{"class":148},[142,774,775],{"class":156}," job ",[142,777,778],{"class":148},"?",[142,780,781],{"class":156}," job.metadata.name ",[142,783,663],{"class":148},[142,785,786],{"class":200}," \"unapplicable\"",[142,788,691],{"class":156},[12,790,791,792,78,795,669],{},"That covers all the major aspects of our application and infrastructure, and if you'd like to view the code in detail, it is available in our ",[117,793,794],{},"demo-app",[16,796,799],{"href":797,"rel":798},"https://gitlab.com/rocore/demo-app",[],"GitLab repository",[89,801,803],{"id":802},"our-pipeline","Our pipeline",[12,805,806],{},"When developing a new service, we must establish a solid deployment strategy upfront. We want to make sure we're building in quality from day one. As we develop the service, we can add acceptance tests for every feature we add while the context and requirements are still fresh in our minds. This ensures we have thorough coverage of our app's functionality.",[12,808,809,810,815,816,818],{},"We used GitLab to set up our pipeline. We chose GitLab because it's straightforward to set up and allows us to run our pipeline on our Docker image of choice. We use a ",[16,811,814],{"href":812,"rel":813},"https://gitlab.com/rocore/global-infra/blob/master/base-image/Dockerfile",[],"base-image"," that has all our dependencies installed and then reference that Docker image and tag in our ",[117,817,794],{}," pipeline. The Docker image allows us to bundle and version the dependencies for building our application and infrastructure.",[12,820,821,825],{},[395,822],{"alt":823,"src":824},"GitLab Pipelines","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398882/blog/Content%20Images/pulumibloggitlabci.png","\n\\",[827,828,829,849],"ol",{},[830,831,832,836,837,842,843,848],"li",{},[833,834,835],"strong",{},"Test and Build"," - This runs our unit tests and builds both our application and acceptance test images. To build our images, we used ",[16,838,841],{"href":839,"rel":840},"https://github.com/GoogleContainerTools/kaniko",[],"Kaniko",", a tool for building images within a container or Kubernetes cluster. GitLab has excellent documentation on ",[16,844,847],{"href":845,"rel":846},"https://docs.gitlab.com/ci/docker/using_kaniko/",[],"how to incorporate Kaniko"," into your pipeline. The application image is an immutable image that is used for both running our acceptance tests and deploying to production.",[830,850,851,854,855,857,858,860,861,864,865,1063,1065,1066,1069,1070,1072],{},[833,852,853],{},"Acceptance Test"," - This is what spins up our ephemeral environments and runs our acceptance tests. This acts as a quality gate catching issues before production.",[637,856],{},"Our ephemeral environment and Kubernetes job are all spun up in the ",[117,859,80],{}," portion of the acceptance test job definition. We do a bit of setup for our new acceptance test stack and then run ",[117,862,863],{},"pulumi up",". Here is the print out from our acceptance tests.",[133,866,870],{"className":867,"code":868,"language":869,"meta":138,"style":138},"language-bash shiki shiki-themes github-light","...\n$ pulumi stack init rocore/$ENV-app\nLogging in using access token from PULUMI_ACCESS_TOKEN\nCreated stack 'rocore/test-96425413-app'\n$ pulumi config set DOCKER_TAG $DOCKER_TAG\n$ pulumi config set ENV $ENV\n$ pulumi config set gcp:project rocore-k8s\n$ pulumi config set gcp:zone us-west1-a\n$ pulumi up --skip-preview\nUpdating (rocore/test-96425413-app):\n...\nResources:\n    + 16 created\n\nDuration: 4m10s\n\nPermalink: https://app.pulumi.com/rocore/demo-app/test-96425413-app/updates/1\n","bash",[117,871,872,877,900,923,933,951,967,983,999,1011,1019,1023,1028,1039,1043,1051,1055],{"__ignoreMap":138},[142,873,874],{"class":144,"line":145},[142,875,876],{"class":331},"...\n",[142,878,879,882,885,888,891,894,897],{"class":144,"line":160},[142,880,881],{"class":152},"$",[142,883,884],{"class":200}," pulumi",[142,886,887],{"class":200}," stack",[142,889,890],{"class":200}," init",[142,892,893],{"class":200}," rocore/",[142,895,896],{"class":156},"$ENV",[142,898,899],{"class":200},"-app\n",[142,901,902,905,908,911,914,917,920],{"class":144,"line":166},[142,903,904],{"class":152},"Logging",[142,906,907],{"class":200}," in",[142,909,910],{"class":200}," using",[142,912,913],{"class":200}," access",[142,915,916],{"class":200}," token",[142,918,919],{"class":200}," from",[142,921,922],{"class":200}," PULUMI_ACCESS_TOKEN\n",[142,924,925,928,930],{"class":144,"line":173},[142,926,927],{"class":152},"Created",[142,929,887],{"class":200},[142,931,932],{"class":200}," 'rocore/test-96425413-app'\n",[142,934,935,937,939,942,945,948],{"class":144,"line":179},[142,936,881],{"class":152},[142,938,884],{"class":200},[142,940,941],{"class":200}," config",[142,943,944],{"class":200}," set",[142,946,947],{"class":200}," DOCKER_TAG",[142,949,950],{"class":156}," $DOCKER_TAG\n",[142,952,953,955,957,959,961,964],{"class":144,"line":185},[142,954,881],{"class":152},[142,956,884],{"class":200},[142,958,941],{"class":200},[142,960,944],{"class":200},[142,962,963],{"class":200}," ENV",[142,965,966],{"class":156}," $ENV\n",[142,968,969,971,973,975,977,980],{"class":144,"line":207},[142,970,881],{"class":152},[142,972,884],{"class":200},[142,974,941],{"class":200},[142,976,944],{"class":200},[142,978,979],{"class":200}," gcp:project",[142,981,982],{"class":200}," rocore-k8s\n",[142,984,985,987,989,991,993,996],{"class":144,"line":224},[142,986,881],{"class":152},[142,988,884],{"class":200},[142,990,941],{"class":200},[142,992,944],{"class":200},[142,994,995],{"class":200}," gcp:zone",[142,997,998],{"class":200}," us-west1-a\n",[142,1000,1001,1003,1005,1008],{"class":144,"line":241},[142,1002,881],{"class":152},[142,1004,884],{"class":200},[142,1006,1007],{"class":200}," up",[142,1009,1010],{"class":331}," --skip-preview\n",[142,1012,1013,1016],{"class":144,"line":246},[142,1014,1015],{"class":152},"Updating",[142,1017,1018],{"class":156}," (rocore/test-96425413-app):\n",[142,1020,1021],{"class":144,"line":252},[142,1022,876],{"class":331},[142,1024,1025],{"class":144,"line":258},[142,1026,1027],{"class":152},"Resources:\n",[142,1029,1030,1033,1036],{"class":144,"line":283},[142,1031,1032],{"class":152},"    +",[142,1034,1035],{"class":331}," 16",[142,1037,1038],{"class":200}," created\n",[142,1040,1041],{"class":144,"line":300},[142,1042,255],{"emptyLinePlaceholder":82},[142,1044,1045,1048],{"class":144,"line":317},[142,1046,1047],{"class":152},"Duration:",[142,1049,1050],{"class":200}," 4m10s\n",[142,1052,1053],{"class":144,"line":341},[142,1054,255],{"emptyLinePlaceholder":82},[142,1056,1057,1060],{"class":144,"line":347},[142,1058,1059],{"class":152},"Permalink:",[142,1061,1062],{"class":200}," https://app.pulumi.com/rocore/demo-app/test-96425413-app/updates/1\n",[637,1064],{},"The ",[117,1067,1068],{},"after_script"," destroys our stack as well as prints the logs of both our Kubernetes job and deployment, which help with debugging if our tests were to fail. We use the ",[117,1071,1068],{}," to make sure that we always clean up and print logs even when our acceptance tests fail.\n```bash\n...\n$ pulumi stack select rocore/$ENV-app\n$ kubectl logs -n rocore --selector=appClass=$ENV-demo-app-acc-test --tail=200\n=== RUN   TestSimpleHappyPath\n=== RUN   TestSimpleHappyPath/message_is_sent_to_PubSub_topic\n=== RUN   TestSimpleHappyPath/message_is_stored_in_bucket",[1074,1075,1076],"style",{},"html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html pre.shiki code .sqxcx, html code.shiki .sqxcx{--shiki-default:#E36209}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}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);}",{"title":138,"searchDepth":160,"depth":160,"links":1078},[1079,1080,1081,1085],{"id":91,"depth":160,"text":92},{"id":98,"depth":160,"text":99},{"id":111,"depth":160,"text":112,"children":1082},[1083,1084],{"id":387,"depth":166,"text":388},{"id":609,"depth":166,"text":610},{"id":802,"depth":160,"text":803},"open-source","2020-01-27","Use Pulumi and GitLab to build a pipeline that validates your application, infrastructure, and deployment process.","md",null,false,"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749666262/Blog/Hero%20Images/default-blog-image.png",{},"/en-us/blog/kubecon-na-2019-are-you-about-to-break-prod",{"title":5,"description":1088,"ogTitle":5,"ogDescription":1088,"noIndex":1091,"ogImage":1092,"ogUrl":1096,"ogSiteName":1097,"ogType":1098,"canonicalUrls":1096},"https://about.gitlab.com/blog/kubecon-na-2019-are-you-about-to-break-prod","https://about.gitlab.com","article","kubecon-na-2019-are-you-about-to-break-prod","en-us/blog/kubecon-na-2019-are-you-about-to-break-prod",[1102,1103,1104,1105,1106,1107],"testing","kubernetes","open source","CI/CD","integrations","events","BlogPost","nMKIGu4Q2EkHoBwcFfTpY6TwnEJBPBgOlWN0F0W71BI",{"logo":1111,"freeTrial":1116,"sales":1121,"login":1126,"items":1131,"search":1455,"minimal":1486,"duo":1505,"switchNav":1514,"pricingDeployment":1525},{"config":1112},{"href":1113,"dataGaName":1114,"dataGaLocation":1115},"/","gitlab logo","header",{"text":1117,"config":1118},"Get free trial",{"href":1119,"dataGaName":1120,"dataGaLocation":1115},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":1122,"config":1123},"Request a demo",{"href":1124,"dataGaName":1125,"dataGaLocation":1115},"/sales/?contact-topic=request-demo","sales",{"text":1127,"config":1128},"Sign in",{"href":1129,"dataGaName":1130,"dataGaLocation":1115},"https://gitlab.com/users/sign_in/","sign in",[1132,1161,1260,1265,1377,1433],{"text":1133,"config":1134,"menu":1136},"Platform",{"dataNavLevelOne":1135},"platform",{"type":1137,"columns":1138},"cards",[1139,1145,1153],{"title":1133,"description":1140,"link":1141},"The intelligent orchestration platform for DevSecOps",{"text":1142,"config":1143},"Explore our Platform",{"href":1144,"dataGaName":1135,"dataGaLocation":1115},"/platform/",{"title":1146,"description":1147,"link":1148},"GitLab Duo Agent Platform","Agentic AI for the entire software lifecycle",{"text":1149,"config":1150},"Meet GitLab Duo",{"href":1151,"dataGaName":1152,"dataGaLocation":1115},"/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":1154,"description":1155,"link":1156},"Why GitLab","See the top reasons enterprises choose GitLab",{"text":1157,"config":1158},"Learn more",{"href":1159,"dataGaName":1160,"dataGaLocation":1115},"/why-gitlab/","why gitlab",{"text":1162,"left":82,"config":1163,"menu":1165},"Product",{"dataNavLevelOne":1164},"solutions",{"type":1166,"link":1167,"columns":1171,"feature":1239},"lists",{"text":1168,"config":1169},"View all Solutions",{"href":1170,"dataGaName":1164,"dataGaLocation":1115},"/solutions/",[1172,1195,1218],{"title":1173,"description":1174,"link":1175,"items":1180},"Automation","CI/CD and automation to accelerate deployment",{"config":1176},{"icon":1177,"href":1178,"dataGaName":1179,"dataGaLocation":1115},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[1181,1184,1187,1191],{"text":1105,"config":1182},{"href":1183,"dataGaLocation":1115,"dataGaName":1105},"/solutions/continuous-integration/",{"text":1146,"config":1185},{"href":1151,"dataGaLocation":1115,"dataGaName":1186},"gitlab duo agent platform - product menu",{"text":1188,"config":1189},"Source Code Management",{"href":1190,"dataGaLocation":1115,"dataGaName":1188},"/solutions/source-code-management/",{"text":1192,"config":1193},"Automated Software Delivery",{"href":1178,"dataGaLocation":1115,"dataGaName":1194},"Automated software delivery",{"title":1196,"description":1197,"link":1198,"items":1203},"Security","Deliver code faster without compromising security",{"config":1199},{"href":1200,"dataGaName":1201,"dataGaLocation":1115,"icon":1202},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[1204,1208,1213],{"text":1205,"config":1206},"Application Security Testing",{"href":1200,"dataGaName":1207,"dataGaLocation":1115},"Application security testing",{"text":1209,"config":1210},"Software Supply Chain Security",{"href":1211,"dataGaLocation":1115,"dataGaName":1212},"/solutions/supply-chain/","Software supply chain security",{"text":1214,"config":1215},"Software Compliance",{"href":1216,"dataGaName":1217,"dataGaLocation":1115},"/solutions/software-compliance/","software compliance",{"title":1219,"link":1220,"items":1225},"Measurement",{"config":1221},{"icon":1222,"href":1223,"dataGaName":1224,"dataGaLocation":1115},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[1226,1230,1234],{"text":1227,"config":1228},"Visibility & Measurement",{"href":1223,"dataGaLocation":1115,"dataGaName":1229},"Visibility and Measurement",{"text":1231,"config":1232},"Value Stream Management",{"href":1233,"dataGaLocation":1115,"dataGaName":1231},"/solutions/value-stream-management/",{"text":1235,"config":1236},"Analytics & Insights",{"href":1237,"dataGaLocation":1115,"dataGaName":1238},"/solutions/analytics-and-insights/","Analytics and insights",{"title":1240,"type":1166,"items":1241},"GitLab for",[1242,1248,1254],{"text":1243,"config":1244},"Enterprise",{"icon":1245,"href":1246,"dataGaLocation":1115,"dataGaName":1247},"Building","/enterprise/","enterprise",{"text":1249,"config":1250},"Small Business",{"icon":1251,"href":1252,"dataGaLocation":1115,"dataGaName":1253},"Work","/small-business/","small business",{"text":1255,"config":1256},"Public Sector",{"icon":1257,"href":1258,"dataGaLocation":1115,"dataGaName":1259},"Organization","/solutions/public-sector/","public sector",{"text":1261,"config":1262},"Pricing",{"href":1263,"dataGaName":1264,"dataGaLocation":1115,"dataNavLevelOne":1264},"/pricing/","pricing",{"text":1266,"config":1267,"menu":1269},"Resources",{"dataNavLevelOne":1268},"resources",{"type":1166,"link":1270,"columns":1274,"feature":1366},{"text":1271,"config":1272},"View all resources",{"href":1273,"dataGaName":1268,"dataGaLocation":1115},"/resources/",[1275,1307,1334],{"title":1276,"items":1277},"Getting started",[1278,1283,1288,1293,1298,1303],{"text":1279,"config":1280},"Install",{"href":1281,"dataGaName":1282,"dataGaLocation":1115},"/install/","install",{"text":1284,"config":1285},"Quick start guides",{"href":1286,"dataGaName":1287,"dataGaLocation":1115},"/get-started/","quick setup checklists",{"text":1289,"config":1290},"Learn",{"href":1291,"dataGaLocation":1115,"dataGaName":1292},"https://university.gitlab.com/","learn",{"text":1294,"config":1295},"Product documentation",{"href":1296,"dataGaName":1297,"dataGaLocation":1115},"https://docs.gitlab.com/","product documentation",{"text":1299,"config":1300},"Best practice videos",{"href":1301,"dataGaName":1302,"dataGaLocation":1115},"/getting-started-videos/","best practice videos",{"text":1304,"config":1305},"Integrations",{"href":1306,"dataGaName":1106,"dataGaLocation":1115},"/integrations/",{"title":1308,"items":1309},"Discover",[1310,1315,1320,1325,1329],{"text":1311,"config":1312},"Customer success stories",{"href":1313,"dataGaName":1314,"dataGaLocation":1115},"/customers/","customer success stories",{"text":1316,"config":1317},"Blog",{"href":1318,"dataGaName":1319,"dataGaLocation":1115},"/blog/","blog",{"text":1321,"config":1322},"Demo Hub",{"href":1323,"dataGaName":1324,"dataGaLocation":1115},"/demo-hub/","demo hub",{"text":1326,"config":1327},"The Source",{"href":1328,"dataGaName":1319,"dataGaLocation":1115},"/the-source/",{"text":1330,"config":1331},"Remote",{"href":1332,"dataGaName":1333,"dataGaLocation":1115},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":1335,"items":1336},"Connect",[1337,1342,1347,1352,1357,1361],{"text":1338,"config":1339},"GitLab Services",{"href":1340,"dataGaName":1341,"dataGaLocation":1115},"/services/","services",{"text":1343,"config":1344},"Contribute",{"href":1345,"dataGaName":1346,"dataGaLocation":1115},"https://contributors.gitlab.com","contribute",{"text":1348,"config":1349},"Community",{"href":1350,"dataGaName":1351,"dataGaLocation":1115},"/community/","community",{"text":1353,"config":1354},"Forum",{"href":1355,"dataGaName":1356,"dataGaLocation":1115},"https://forum.gitlab.com/","forum",{"text":1358,"config":1359},"Events",{"href":1360,"dataGaName":1107,"dataGaLocation":1115},"/events/",{"text":1362,"config":1363},"Partners",{"href":1364,"dataGaName":1365,"dataGaLocation":1115},"/partners/","partners",{"config":1367,"title":1370,"text":1371,"link":1372},{"background":1368,"textColor":1369},"url('https://res.cloudinary.com/about-gitlab-com/image/upload/v1777322348/qpq8yrgn8knii57omj0c.png')","#000","What’s new in GitLab","Stay updated with our latest features and improvements.",{"text":1373,"config":1374},"Read the latest",{"href":1375,"dataGaName":1376,"dataGaLocation":1115},"/whats-new/","whats new",{"text":1378,"config":1379,"menu":1381},"Company",{"dataNavLevelOne":1380},"company",{"type":1166,"columns":1382},[1383],{"items":1384},[1385,1390,1396,1398,1403,1408,1413,1418,1423,1428],{"text":1386,"config":1387},"About",{"href":1388,"dataGaName":1389,"dataGaLocation":1115},"/company/","about",{"text":1391,"config":1392,"footerGa":1395},"Jobs",{"href":1393,"dataGaName":1394,"dataGaLocation":1115},"/jobs/","jobs",{"dataGaName":1394},{"text":1358,"config":1397},{"href":1360,"dataGaName":1107,"dataGaLocation":1115},{"text":1399,"config":1400},"Leadership",{"href":1401,"dataGaName":1402,"dataGaLocation":1115},"/company/team/e-group/","leadership",{"text":1404,"config":1405},"Handbook",{"href":1406,"dataGaName":1407,"dataGaLocation":1115},"https://handbook.gitlab.com/","handbook",{"text":1409,"config":1410},"Investor relations",{"href":1411,"dataGaName":1412,"dataGaLocation":1115},"https://ir.gitlab.com/overview/default.aspx","investor relations",{"text":1414,"config":1415},"Trust Center",{"href":1416,"dataGaName":1417,"dataGaLocation":1115},"/security/","trust center",{"text":1419,"config":1420},"AI Transparency Center",{"href":1421,"dataGaName":1422,"dataGaLocation":1115},"/ai-transparency-center/","ai transparency center",{"text":1424,"config":1425},"Newsletter",{"href":1426,"dataGaName":1427,"dataGaLocation":1115},"/company/contact/#contact-forms","newsletter",{"text":1429,"config":1430},"Press",{"href":1431,"dataGaName":1432,"dataGaLocation":1115},"/press/","press",{"text":1434,"config":1435,"menu":1436},"Contact us",{"dataNavLevelOne":1380},{"type":1166,"columns":1437},[1438],{"items":1439},[1440,1445,1450],{"text":1441,"config":1442},"Talk to sales",{"href":1443,"dataGaName":1444,"dataGaLocation":1115},"/sales/","talk to sales",{"text":1446,"config":1447},"Support portal",{"href":1448,"dataGaName":1449,"dataGaLocation":1115},"https://support.gitlab.com/hc/en-us","support portal",{"text":1451,"config":1452},"Customer portal",{"href":1453,"dataGaName":1454,"dataGaLocation":1115},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":1456,"login":1457,"suggestions":1464},"Close",{"text":1458,"link":1459},"To search repositories and projects, login to",{"text":1460,"config":1461},"gitlab.com",{"href":1129,"dataGaName":1462,"dataGaLocation":1463},"search login","search",{"text":1465,"default":1466},"Suggestions",[1467,1469,1473,1475,1479,1483],{"text":1146,"config":1468},{"href":1151,"dataGaName":1146,"dataGaLocation":1463},{"text":1470,"config":1471},"Code Suggestions (AI)",{"href":1472,"dataGaName":1470,"dataGaLocation":1463},"/solutions/code-suggestions/",{"text":1105,"config":1474},{"href":1183,"dataGaName":1105,"dataGaLocation":1463},{"text":1476,"config":1477},"GitLab on AWS",{"href":1478,"dataGaName":1476,"dataGaLocation":1463},"/partners/technology-partners/aws/",{"text":1480,"config":1481},"GitLab on Google Cloud",{"href":1482,"dataGaName":1480,"dataGaLocation":1463},"/partners/technology-partners/google-cloud-platform/",{"text":1484,"config":1485},"Why GitLab?",{"href":1159,"dataGaName":1484,"dataGaLocation":1463},{"freeTrial":1487,"mobileIcon":1492,"desktopIcon":1497,"secondaryButton":1500},{"text":1488,"config":1489},"Start free trial",{"href":1490,"dataGaName":1120,"dataGaLocation":1491},"https://gitlab.com/-/trials/new/","nav",{"altText":1493,"config":1494},"Gitlab Icon",{"src":1495,"dataGaName":1496,"dataGaLocation":1491},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":1493,"config":1498},{"src":1499,"dataGaName":1496,"dataGaLocation":1491},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":1501,"config":1502},"Get Started",{"href":1503,"dataGaName":1504,"dataGaLocation":1491},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/get-started/","get started",{"freeTrial":1506,"mobileIcon":1510,"desktopIcon":1512},{"text":1507,"config":1508},"Learn more about GitLab Duo",{"href":1151,"dataGaName":1509,"dataGaLocation":1491},"gitlab duo",{"altText":1493,"config":1511},{"src":1495,"dataGaName":1496,"dataGaLocation":1491},{"altText":1493,"config":1513},{"src":1499,"dataGaName":1496,"dataGaLocation":1491},{"button":1515,"mobileIcon":1520,"desktopIcon":1522},{"text":1516,"config":1517},"/switch",{"href":1518,"dataGaName":1519,"dataGaLocation":1491},"#contact","switch",{"altText":1493,"config":1521},{"src":1495,"dataGaName":1496,"dataGaLocation":1491},{"altText":1493,"config":1523},{"src":1524,"dataGaName":1496,"dataGaLocation":1491},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":1526,"mobileIcon":1531,"desktopIcon":1533},{"text":1527,"config":1528},"Back to pricing",{"href":1263,"dataGaName":1529,"dataGaLocation":1491,"icon":1530},"back to pricing","GoBack",{"altText":1493,"config":1532},{"src":1495,"dataGaName":1496,"dataGaLocation":1491},{"altText":1493,"config":1534},{"src":1499,"dataGaName":1496,"dataGaLocation":1491},{"title":1536,"titleMobile":1537,"button":1538,"config":1543},"Duo Agent Platform delivers 400% ROI, per new Forrester Consulting study.","400% ROI: Forrester TEI for GitLab Duo",{"text":1157,"config":1539},{"href":1540,"dataGaName":1541,"dataGaLocation":1542},"https://about.gitlab.com/blog/gitlab-duo-agent-platform-delivers-400-percent-roi/","forrester-tei-dap-banner","global-banner",{"layout":1544,"disabled":1091},"release",{"data":1546},{"text":1547,"source":1548,"edit":1554,"contribute":1559,"config":1564,"items":1569,"minimal":1779},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":1549,"config":1550},"View page source",{"href":1551,"dataGaName":1552,"dataGaLocation":1553},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":1555,"config":1556},"Edit this page",{"href":1557,"dataGaName":1558,"dataGaLocation":1553},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":1560,"config":1561},"Please contribute",{"href":1562,"dataGaName":1563,"dataGaLocation":1553},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":1565,"facebook":1566,"youtube":1567,"linkedin":1568},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[1570,1617,1671,1715,1747],{"title":1261,"links":1571,"subMenu":1586},[1572,1576,1581],{"text":1573,"config":1574},"View plans",{"href":1263,"dataGaName":1575,"dataGaLocation":1553},"view plans",{"text":1577,"config":1578},"Why Premium?",{"href":1579,"dataGaName":1580,"dataGaLocation":1553},"/pricing/premium/","why premium",{"text":1582,"config":1583},"Why Ultimate?",{"href":1584,"dataGaName":1585,"dataGaLocation":1553},"/pricing/ultimate/","why ultimate",[1587],{"title":1588,"links":1589},"Contact Us",[1590,1593,1595,1597,1602,1607,1612],{"text":1591,"config":1592},"Contact sales",{"href":1443,"dataGaName":1125,"dataGaLocation":1553},{"text":1446,"config":1594},{"href":1448,"dataGaName":1449,"dataGaLocation":1553},{"text":1451,"config":1596},{"href":1453,"dataGaName":1454,"dataGaLocation":1553},{"text":1598,"config":1599},"Status",{"href":1600,"dataGaName":1601,"dataGaLocation":1553},"https://status.gitlab.com/","status",{"text":1603,"config":1604},"Terms of use",{"href":1605,"dataGaName":1606,"dataGaLocation":1553},"/terms/","terms of use",{"text":1608,"config":1609},"Privacy statement",{"href":1610,"dataGaName":1611,"dataGaLocation":1553},"/privacy/","privacy statement",{"text":1613,"config":1614},"Cookie preferences",{"dataGaName":1615,"dataGaLocation":1553,"id":1616,"isOneTrustButton":82},"cookie preferences","ot-sdk-btn",{"title":1162,"links":1618,"subMenu":1627},[1619,1623],{"text":1620,"config":1621},"DevSecOps platform",{"href":1144,"dataGaName":1622,"dataGaLocation":1553},"devsecops platform",{"text":1624,"config":1625},"AI-Assisted Development",{"href":1151,"dataGaName":1626,"dataGaLocation":1553},"ai-assisted development",[1628],{"title":1629,"links":1630},"Topics",[1631,1636,1641,1646,1651,1656,1661,1666],{"text":1632,"config":1633},"CICD",{"href":1634,"dataGaName":1635,"dataGaLocation":1553},"/topics/ci-cd/","cicd",{"text":1637,"config":1638},"GitOps",{"href":1639,"dataGaName":1640,"dataGaLocation":1553},"/topics/gitops/","gitops",{"text":1642,"config":1643},"DevOps",{"href":1644,"dataGaName":1645,"dataGaLocation":1553},"/topics/devops/","devops",{"text":1647,"config":1648},"Version Control",{"href":1649,"dataGaName":1650,"dataGaLocation":1553},"/topics/version-control/","version control",{"text":1652,"config":1653},"DevSecOps",{"href":1654,"dataGaName":1655,"dataGaLocation":1553},"/topics/devsecops/","devsecops",{"text":1657,"config":1658},"Cloud Native",{"href":1659,"dataGaName":1660,"dataGaLocation":1553},"/topics/cloud-native/","cloud native",{"text":1662,"config":1663},"AI for Coding",{"href":1664,"dataGaName":1665,"dataGaLocation":1553},"/topics/devops/ai-for-coding/","ai for coding",{"text":1667,"config":1668},"Agentic AI",{"href":1669,"dataGaName":1670,"dataGaLocation":1553},"/topics/agentic-ai/","agentic ai",{"title":1672,"links":1673},"Solutions",[1674,1676,1678,1683,1687,1690,1694,1697,1699,1702,1705,1710],{"text":1205,"config":1675},{"href":1200,"dataGaName":1205,"dataGaLocation":1553},{"text":1194,"config":1677},{"href":1178,"dataGaName":1179,"dataGaLocation":1553},{"text":1679,"config":1680},"Agile development",{"href":1681,"dataGaName":1682,"dataGaLocation":1553},"/solutions/agile-delivery/","agile delivery",{"text":1684,"config":1685},"SCM",{"href":1190,"dataGaName":1686,"dataGaLocation":1553},"source code management",{"text":1632,"config":1688},{"href":1183,"dataGaName":1689,"dataGaLocation":1553},"continuous integration & delivery",{"text":1691,"config":1692},"Value stream management",{"href":1233,"dataGaName":1693,"dataGaLocation":1553},"value stream management",{"text":1637,"config":1695},{"href":1696,"dataGaName":1640,"dataGaLocation":1553},"/solutions/gitops/",{"text":1243,"config":1698},{"href":1246,"dataGaName":1247,"dataGaLocation":1553},{"text":1700,"config":1701},"Small business",{"href":1252,"dataGaName":1253,"dataGaLocation":1553},{"text":1703,"config":1704},"Public sector",{"href":1258,"dataGaName":1259,"dataGaLocation":1553},{"text":1706,"config":1707},"Education",{"href":1708,"dataGaName":1709,"dataGaLocation":1553},"/solutions/education/","education",{"text":1711,"config":1712},"Financial services",{"href":1713,"dataGaName":1714,"dataGaLocation":1553},"/solutions/finance/","financial services",{"title":1266,"links":1716},[1717,1719,1721,1723,1726,1728,1731,1733,1735,1737,1739,1741,1743,1745],{"text":1279,"config":1718},{"href":1281,"dataGaName":1282,"dataGaLocation":1553},{"text":1284,"config":1720},{"href":1286,"dataGaName":1287,"dataGaLocation":1553},{"text":1289,"config":1722},{"href":1291,"dataGaName":1292,"dataGaLocation":1553},{"text":1294,"config":1724},{"href":1296,"dataGaName":1725,"dataGaLocation":1553},"docs",{"text":1316,"config":1727},{"href":1318,"dataGaName":1319,"dataGaLocation":1553},{"text":1729,"config":1730},"What's new",{"href":1375,"dataGaName":1376,"dataGaLocation":1553},{"text":1311,"config":1732},{"href":1313,"dataGaName":1314,"dataGaLocation":1553},{"text":1330,"config":1734},{"href":1332,"dataGaName":1333,"dataGaLocation":1553},{"text":1338,"config":1736},{"href":1340,"dataGaName":1341,"dataGaLocation":1553},{"text":1343,"config":1738},{"href":1345,"dataGaName":1346,"dataGaLocation":1553},{"text":1348,"config":1740},{"href":1350,"dataGaName":1351,"dataGaLocation":1553},{"text":1353,"config":1742},{"href":1355,"dataGaName":1356,"dataGaLocation":1553},{"text":1358,"config":1744},{"href":1360,"dataGaName":1107,"dataGaLocation":1553},{"text":1362,"config":1746},{"href":1364,"dataGaName":1365,"dataGaLocation":1553},{"title":1378,"links":1748},[1749,1751,1753,1755,1757,1759,1763,1768,1770,1772,1774],{"text":1386,"config":1750},{"href":1388,"dataGaName":1380,"dataGaLocation":1553},{"text":1391,"config":1752},{"href":1393,"dataGaName":1394,"dataGaLocation":1553},{"text":1399,"config":1754},{"href":1401,"dataGaName":1402,"dataGaLocation":1553},{"text":1404,"config":1756},{"href":1406,"dataGaName":1407,"dataGaLocation":1553},{"text":1409,"config":1758},{"href":1411,"dataGaName":1412,"dataGaLocation":1553},{"text":1760,"config":1761},"Sustainability",{"href":1762,"dataGaName":1760,"dataGaLocation":1553},"/sustainability/",{"text":1764,"config":1765},"Diversity, inclusion and belonging (DIB)",{"href":1766,"dataGaName":1767,"dataGaLocation":1553},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":1414,"config":1769},{"href":1416,"dataGaName":1417,"dataGaLocation":1553},{"text":1424,"config":1771},{"href":1426,"dataGaName":1427,"dataGaLocation":1553},{"text":1429,"config":1773},{"href":1431,"dataGaName":1432,"dataGaLocation":1553},{"text":1775,"config":1776},"Modern Slavery Transparency Statement",{"href":1777,"dataGaName":1778,"dataGaLocation":1553},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":1780},[1781,1784,1787],{"text":1782,"config":1783},"Terms",{"href":1605,"dataGaName":1606,"dataGaLocation":1553},{"text":1785,"config":1786},"Cookies",{"dataGaName":1615,"dataGaLocation":1553,"id":1616,"isOneTrustButton":82},{"text":1788,"config":1789},"Privacy",{"href":1610,"dataGaName":1611,"dataGaLocation":1553},[1791],{"id":1792,"title":1793,"body":1090,"config":1794,"content":1796,"description":1090,"extension":1800,"meta":1801,"navigation":82,"path":1802,"seo":1803,"stem":1804,"__hash__":1805},"blogAuthors/en-us/blog/authors/erin-krengel-pulumi.yml","Erin Krengel Pulumi",{"template":1795},"BlogAuthor",{"name":7,"config":1797},{"headshot":1798,"ctfId":1799},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659488/Blog/Author%20Headshots/gitlab-logo-extra-whitespace.png","Erin-Krengel-Pulumi","yml",{},"/en-us/blog/authors/erin-krengel-pulumi",{},"en-us/blog/authors/erin-krengel-pulumi","6S2TuLhIvuUir6HGTlvY7zRe9aR-XvJxv0sx2L7doWQ",[1807,1815,1823],{"title":1808,"description":1809,"heroImage":1810,"category":1086,"date":1811,"authors":1812,"slug":1814,"externalUrl":1090},"What's new in Git 2.55.0?","Learn about the new features and changes in Git 2.55, including a new git-history(1) fixup command, an fsmonitor daemon for Linux, pushing to remote groups, and more.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782732487/ztwgrvlolpifo01vj8qc.png","2026-06-29",[1813],"Toon Claes","whats-new-in-git-2-55-0",{"title":1816,"description":1817,"heroImage":1818,"category":1086,"date":1819,"authors":1820,"slug":1822,"externalUrl":1090},"GitLab AI Hackathon 2026: Meet the winners","Nearly 7,000 developers built 600+ AI agents and flows on GitLab Duo Agent Platform. Find out who won and what they created.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1776457632/llddiylsgwuze0u1rjks.png","2026-04-22",[1821],"Nick Veenhof","gitlab-ai-hackathon-2026-meet-the-winners",{"title":1824,"description":1825,"heroImage":1826,"category":1086,"date":1827,"authors":1828,"slug":1830,"externalUrl":1090},"What’s new in Git 2.54.0?","Learn about release contributions, including new repository maintenance, a new command to edit commit history, a replacement for git-sizer(1), and more.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782734716/ztwgrvlolpifo01vj8qc.png","2026-04-20",[1829],"Patrick Steinhardt","whats-new-in-git-2-54-0",{"promotions":1832},[1833,1847,1859,1871],{"id":1834,"categories":1835,"header":1837,"text":1838,"button":1839,"image":1844},"ai-modernization",[1836],"ai","Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":1840,"config":1841},"Get your AI maturity score",{"href":1842,"dataGaName":1843,"dataGaLocation":1319},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":1845},{"src":1846},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":1848,"categories":1849,"header":1851,"text":1838,"button":1852,"image":1856},"devops-modernization",[1850,1655],"product","Are you just managing tools or shipping innovation?",{"text":1853,"config":1854},"Get your DevOps maturity score",{"href":1855,"dataGaName":1843,"dataGaLocation":1319},"/assessments/devops-modernization-assessment/",{"config":1857},{"src":1858},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":1860,"categories":1861,"header":1863,"text":1838,"button":1864,"image":1868},"security-modernization",[1862],"security","Are you trading speed for security?",{"text":1865,"config":1866},"Get your security maturity score",{"href":1867,"dataGaName":1843,"dataGaLocation":1319},"/assessments/security-modernization-assessment/",{"config":1869},{"src":1870},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":1872,"paths":1873,"header":1876,"text":1877,"button":1878,"image":1883},"github-azure-migration",[1874,1875],"migration-from-azure-devops-to-gitlab","integrating-azure-devops-scm-and-gitlab","Is your team ready for GitHub's Azure move?","GitHub is already rebuilding around Azure. Find out what it means for you.",{"text":1879,"config":1880},"See how GitLab compares to GitHub",{"href":1881,"dataGaName":1882,"dataGaLocation":1319},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":1884},{"src":1858},{"header":1886,"blurb":1887,"button":1888,"secondaryButton":1893},"Start building faster today","See what your team can do with the intelligent orchestration platform for DevSecOps.\n",{"text":1889,"config":1890},"Get your free trial",{"href":1891,"dataGaName":1120,"dataGaLocation":1892},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":1591,"config":1894},{"href":1443,"dataGaName":1125,"dataGaLocation":1892},1786803772641]