 I'd like to thank everybody who is joining us today. Welcome to today's CNCF webinar, Brigade Scripting Container Workflows on Kubernetes. You're in for a real treat today. My name is Locky Evenson. I'm a principal program manager at Microsoft and cloud native ambassador. I'll be moderating today's webinar and we would like to welcome our presenter today, Radu Matthai. He's a software engineer at Microsoft and maintainer of the Brigade project. Brigade is actually in the CNCF sandbox. A few housekeeping items before we get into it. During the webinar, you're not able to talk as an entity. There is a Q&A box at the bottom of your screen. Feel free to click that and drop your questions in there and then we will get to the questions as we see fit. So again, please don't ask questions in the chat specifically go down to the Q&A box and we will see them and we will get them answered throughout the webinar. Thank you very much. This is an official webinar of the CNCF and as such is subjected to the CNCF Code of Conduct. Please do not add anything to the chat or any questions that would be in violation of that Code of Conduct. Basically, please be respectful to all your fellow participants and presenters. And with that, I will hand it over to Radu to kick off today's presentation. Take it away, Radu. Hello everyone, good morning, evening, afternoon. Hopefully the daylight saving times in Europe didn't mess up too many schedules this week. Today we're going to talk about Brigade and scripting container workflows on Kubernetes. My name is Radu. I'm an open source maintainer for Brigade and the cloud native application bundles project. You can hear me talk about developer tools, OCI, security protocols, really, really into music and bicycles and I'm a software engineer at Microsoft. Today in this webinar, we're going to talk about Brigade and you could go ahead and check out the documentation. Throughout this presentation, they'll build links to multiple pages in the documentation. You can also check the GitHub project on github.com, Brigade Core. Before talking about Brigade, let's take a step back and think about what an operating system is, what its job is and essentially the program that after initially being loaded it manages all of the other programs, right? And if we think about it, an operating system manages processes on a single machine and Kubernetes manages containers in a cluster. So without stretching the analogy too much, we can think about Kubernetes as a cloud operating system. Going forward, we've been using operating system, shell scripting, bash, PowerShell for forever now, right? And essentially shell scripting in an operating system world is essentially flow control that wraps the execution of processes, but it's not opinionated about what's running inside a process or what the process would do. And so when bash we're able to iterate through a bunch of files, start a process, LS for example, take the output of it and then use it as input for another process, echo for example, and seamlessly pass data between multiple processes. If we continue the analogy, then how would we think about cluster shell scripting? I would like to have something that controls the flow of executing containers, but it's not opinionated about what the containers do or how they run. So I would like to be able to do something very similar to how I would do bash, but instead of starting processes on a single machine, I'd like to be able to start containers in a Kubernetes cluster. So how are we doing this right now? Well, first of all, we're using good old bash scripts and to some extent abusing YAML. And we're gonna see in a minute about that. We think that we have a solution for this problem with Brigade. Brigade is a cloud native, is a CNCF sandbox project and essentially the framework is an in cluster runtime for event-driven scripting on Kubernetes. It's really really lightweight and we're gonna see just how lightweight in a minute. And essentially it is used to chain together containers to create multiple workflows. And we were talking earlier about how would some cluster shell scripting look like? Well, I would like to start multiple containers in my cluster, iterate to the result, pass data between them, attach Kubernetes volumes, wait for them to finish, start them in groups and all that stuff, which so far we've been doing with either bash scripts, really, really complicated and unmaintainable or untestable bash scripts or some sort of trying to use YAML to do that for us. And thinking about the shell scripting, well, there were a bunch of questions that Brigade had to answer. First of all, is what programming language should we use? How do we share data between multiple containers, multiple jobs? And when are we supposed to execute scripts? And let's take each of those and talk about them. First one is we decided to choose a language and not write one. And if in 2017, 18, 19 or 2020, we're thinking about using a programming language to reach as many people as we can, well, honestly, there's only one language that fits that and that's JavaScript regardless of what we think about it. It's been the number one programming language over the last couple of years in all metrics. So, well, we chose to go with JavaScript mainly because it's extremely popular, has a very rich ecosystem of tools and it's extremely flexible. More than that, it has more than 1 billion weekly NPM package pools. Some of those are CI, but some of those are actual real humans using JavaScript. Second question was, how do we share data between containers? Think about it this way. I wanna start a job, wait for it to finish and get the results or an output or something from that job and pass it as input to the next job. If we, if you're thinking about how to do right now with Kubernetes as well, we're thinking about Kube control exec or Kube control run, paste the output in the console, grab through it, take some info and then pass it forward. JQ and Jason and Yan will help with that a bit, but it's still really, really painful. And we're interested in how to pass information to containers and how to get information from containers that have executed. And more importantly, because we chose JavaScript, we wanted to be JavaScript native to feel like you're actually writing JavaScript. And as important, we wanted to preserve existing paradigms for passing data to and from containers in a Kubernetes world. And at the end of the day, it's just JavaScript. It's a really, really simple syntax for writing scripts on top of Kubernetes, right? So I instantiate the new job, I add a few tasks, I add them in environmental variables and then I execute the run function. The third question was when do we execute scripts? And the most obvious was reacting to triggers or events, right? And event handlers become the entry point for brigade scripts. We live in a world where every system is able to trigger some sort of events, be it by webhooks or by a message queue or something like that. And essentially it made sense for us to execute to outside events. Where do these events come from? Well, they could be any HTTP webhook, either internal from your systems, it can be a third party like GitHub, Container Registries, Trello, or Kubernetes itself. Yet, I'm mentioning Kubernetes, in the end any event source can be configured for brigade in a component that we're gonna take a look at in a minute called gateway. And this is a complete script for brigade. If you add this to a brigade project, essentially you're able to execute it every time there's an event called sumEvent. And essentially what we're doing is we're importing the brigade package in JavaScript and then we react to an event called sumEvent with this function. We start a job, we add some tasks to be executed and then we run it. This is the most simple task that we can execute a brigade we're gonna see in a couple of minutes more complicated tasks and actual control flow. So what do people use brigade for? We've seen it being used as a foundation for opinionated CI-CD systems. We'll take a look at those in a minute. Overnight application security scannings, aggregating and analyzing data from multiple systems, building reports, creating preview environments on top of Kubernetes full request. So if you have a project that's in Git, Bitbucket, GitLab, GitHub, whenever there's a full request, we've seen projects that created a new namespace, essentially a preview environment of the entire application, allowed you to investigate the environment and then either delete it or promote it or to other GitOps-y things with it. Processing orders connecting to external services, databases, actual CI-CD. We've actually been using brigade to build and deploy brigade forever now we have an integration with the GitHub checks API that we're gonna see essentially allowing us to push back build logs back into the GitHub UI. So essentially we're using brigade with the existing checks API. And in the end, any potential container workflow that you wanna run on top of Kubernetes that would actually benefit from language features. So if your only use case is just starting three jobs one after the other or at the same time and you're not interested in actual control flow or I think gonna wait for some of those jobs passing easily passing data back and forth. And you could probably achieve that with either Kubernetes primitives or some other declarative system. But if you find yourself restrained by YAML then having a full programming language and environment could be of use. So how do we get started with Brigade? Well, Brigade, we can configure it with Helm. Helm install and then Brig check. I really encourage you to go check the docs brigade.sh website. You can find all of the getting started right on the website. Now there's a question of whether we can replace Docker files or manifest with JavaScript. Essentially, you can configure Brigade to work with a GitHub repo. And if you put that, if you put a manifest in your GitHub repo, you could. You could also choose to build container images before using them, but you would need a Docker runtime in your cluster to do that. We have an example of how to do it on the documentation. So how do we actually get started with Brigade? Is my screen still sharing? Okay. Yes. I did a Helm install a couple of minutes ago. Did you bump your font a little bit, Radu? Can you bump your size? I think so. Yeah. There you go. One, maybe one more. There you go. Okay. I was telling you that Brigade is fairly lightweight. Essentially, the default installation of Brigade is three pods. We have a completed pod that's just a cron job that's running, that's not running right now. So there are three main components for Brigade. There's a Kubernetes controller, which is looking for Brigade events. And then there's an API, which we can use it to essentially use it to interact with Brigade. And then we have a web dashboard that's obviously optional. So after we do a Helm install, we can do a Brig check to make sure that everything is running healthy in our Brigade installation. And if everything is running okay, then we can start the web dashboard to see if that's running. And that's gonna start your default browser. I'm gonna create a tunnel to Kubernetes and access the Brigade dashboard. And right now, we don't have any projects. We haven't configured anything. I just deployed Brigade a little bit earlier so that I don't rely on pulling Helm charts on the internet right during the call. So let's just start it with actually configuring a project and we'll see how that looks like. Big project create. And essentially it's a wizard that helps us configure a project and we're gonna choose the following. So we don't want to use a Git repo so far. I'm gonna give it a name, hello. We don't want to have any secrets and essentially we have a couple of scripts that we're gonna use. You can find them, I'll place the GitHub repo for this. Most of this thing you're gonna be able to find in docs.rigade.sh, the entire demo that I'm gonna show you right now is from the documentation. So please be sure to check that out. This is the first script that you're gonna use and you're gonna pass it at the default script to be used in case we don't use anything else. And we don't want any advanced options. So at this point, we have our Brigade project called hello and if we do a Brig run, essentially I was telling you that Brigade reacts to triggers and external events. Right now we're using the Brig CLI to create an event for us. And I wanna execute a new event and I wanna pass for the project, hello. And at this point, what's gonna happen is we also have a second terminal dashboard for Brigade. It's a CLI one in case you don't have access to a web interface. It's called Brig turn, Brigade turns someone from the community contributed this. We're super happy with the community and the things that they've been contributing so far. Essentially, let's walk through what happened. So Brig created an event. It was a simple run, exec event. This was called by the Brigade controller. We scheduled the Brigade worker and essentially our job was called do nothing based on an alpine container. And the only thing that was supposed to execute were echo hello and then echo world and then run and exit. So let's see how that looks like. We have the Brig run creates an event called exec that we're handling to that we're responding to. And then if we go any further, we can see that the job was called do nothing. And we can see the two tasks that were supposed to be executed echo hello and then echo world and then an exit. So Kubernetes get pods. And right now we're able to see the Brigade worker that was whose job was to execute this JavaScript file. And then our do nothing job which was a Kubernetes pod with a single container called based on alpine. So this is the simplest Brigade script that you could execute. There was a question of people not being familiar with JavaScript and how hard was it to learn? Essentially, this is the simplest way you can start with Brigade. We tried to have the syntax as simple as possible first of all for people who are not familiar with JavaScript, but at the same time if you are familiar with JavaScript and if you have written JavaScript before we're trying to make use of as many useful JavaScript patterns as we can. So we're gonna see throughout today's demo useful JavaScript features and things that are obvious to people who've used JavaScript but as well as easy enough to get started for other people. So let's have a look at a second script. Essentially, this one starts two jobs. So it's gonna start them at the same time one called hello, which will do echo hello and echo world and then one goodbye, both of them based on alpine. So we're gonna do the same thing, Brig run or Brig exec, that's the same command. And then instead we're gonna pass script number two. So the same thing is gonna happen, Brig is gonna create an event, the Brigade controller will catch that event and will schedule a Brigade worker and the worker pod will start executing the Brigade.js file that we see right here. Essentially the Brigade worker is using the Kubernetes JavaScript API to interpret this and start jobs. And then if we go back to our CLI dashboard, we can see that we have two jobs, one hello, one goodbye. Now we can see that they were both started at the same time. It doesn't really matter the order that you pass right here. And then here we can see that they were started at the same time, they both last as pretty much the same time. And then we can go into the logs of each job and then see them. We started them, essentially we first tried to start hello and then try to start goodbye, but we don't see the order here. We first see goodbye and then hello. How are we supposed to actually control the order, the execution and things like that? So we have something called groups, first of all. Essentially think of them as collection of useful methods in Brigade that allow us to control the execution. So we can run each which will first start hello and then start goodbye sequentially or run all which was started in parallel. So let's see how this is gonna execute. We're doing the same thing, passing this file. And what we're expecting to see is Brigade first starting the hello job and then starting the goodbye job. And we are essentially here started, we saw them getting started at the same time. We expect them to be started separately. So we first see hello, we started the execution and then finished. And then after hello finished, we see the second job being started. So we see hello and then goodbye. Let's see that through the web dashboard as well. The command was Brig dashboard, which will start the tunnels of Kubernetes and then open your default browser. We see the project called hello and then we see a bunch of events that were executed five minutes ago, two minutes ago and a few seconds ago, which correspond to our Brig run commands. And this is how it looks for a single event. You see the two jobs and then the jobs, right? These are the logs from the Brigade worker, the same ones that we see right here when we execute Brigade and then we can go further into the logs of each individual job. So this is very useful when you don't have access to the Brig CLI, essentially the Brig CLI that we've been using so far requires access to a KubeConfig. It uses KubeConfig to essentially talk to the Brigade API and Kubernetes API for that. What I didn't mention so far is that everything in Brigade is Kubernetes native. So it doesn't have any external dependencies. Everything is done as Kubernetes native objects. And then the web dashboard essentially is a read-only version that uses the API. It cannot be used to modify the state of the cluster or of Brigade. It can be only used to view logs and view jobs and things like that. We actually have a public version of our dashboard that be used for our projects. Let me see if I can find it. Yeah, so this is, if you're interested, you can see the logs from multiple projects of ours. For example, these are the logs from Brigade itself. And we can see that the last build actually failed. So this is interesting. If you're interested in this, you can deep dive into what's happening there, but essentially because it's a read-only version of what happens in Brigade, you cannot affect the state. You can only see the logs. There's a question in chat whether we're running Kubernetes jobs. Almost running Kubernetes pods, not exactly jobs. Jobs in Kubernetes are an opinion over pods. So far we've been using the underlying pods definition from Kubernetes. We can enter, go into the details of why we chose pods over jobs. I don't think it's worth going to discussion right now. I'll be happy to chat about that if you're interested. So we've seen how to use the group subtraction in Brigade to start jobs one at a time. Now let's see how we could use more advanced JavaScript features to get the same results. So essentially what we're doing is we're starting two jobs just like before, but this time we're using the I think a weight functionality from JavaScript to essentially wait for a job one to finish and then start job two and then do something else, right? Imagine job one configuring something in your cluster and then job two using that, right? So let's see how that... Essentially we're not gonna see anything different than we saw when we were using groups. This is just a different language feature that we're able to use with Brigade. Yes, the job function supports promises. That's how we're using the sync away functionality. There was a question of whether the job supports promises. There's another question of how is this similar or different to Tecdon CD to create pipeline jobs or pops? The way we're thinking about it is essentially, first of all, we're not using any declarative features. We're not using YAML to configure our jobs. So that's one difference. The second one is essentially Tecdon CD is particularly suited for CI CD purposes. We've seen Brigade being used for a bunch of other use cases as well. We are using Brigade for CI and CD and there are a bunch of opinionated frameworks on top of Brigade that have declarative features for CD. But if you're only gonna use CI CD, you could have a look at Tecdon, for example, other things on top of Kubernetes. Let's see the last event. It was 50 seconds ago and lasted 11 seconds and exactly what we were expecting, right? Nothing different job one started and then finished and then gave you good job two. Now, let's dive a bit into things that are not so easy to do with declarative approach, right? So in this scenario, what we're trying to do is share a particular storage between three jobs. We have a job one, which is gonna write into a file. We have job two, which is gonna write into the same file and then job three is gonna echo whatever was in that file. So think about it in this way. You are trying to share the same persistent volume in Kubernetes across three different spots. You could potentially achieve that using a very complicated bash script, create the persistent volume attach it to the configure of the first spot as a persistent volume claim, then wait for the first bot to finish and then attach the persistent volume claim to the second bot and then attach it to the third. Essentially, this is what Brigade does for you automatically by just doing storage enabled equals true. So if you do that, it's gonna attach the persistent volume to your bot. The Brigade storage is a persistent volume that is one for each build, essentially, that is shared across all of the jobs in this build. So let's do that. Brig Run, just to iterate one more time on this, I'm starting executing events manually and passing Brigade.js files. You don't have to do that. You can use a Git provider for that to store your Brigade.js files and have an inversion control. This is the easiest way to test your script without pushing the source control, right? You can also store them in Kubernetes config maps. If you don't have a GitHub repo and we've actually seen more and more people using Brigade without GitHub repos, so it doesn't have to be a Git enabled scenario. So we do a Brig Run for this file and essentially what we're gonna see. And if we do a Kube control, we see a persistent volume claim being created. And yeah, this is essentially what's happening right now. We created the worker pod and then we created the persistent volume that we're attaching to that. And then we're waiting for that to finish. And then we're gonna start each individual Kubernetes pod, attach it, use the persistent volume and then, and then detach the PVC from the pod and then add it to the other. There's a question in chat whether it is better to use a weight versus the run each construct. Essentially the run each just uses a weight. So it's a matter of what language feature you're most comfortable with. You see that job one is starting. We're gonna start job two and then job three. Waiting for Kubernetes to pull images and start containers. Let's try that again. It's a new cluster, so I don't really know what's happening here. So it scheduled the first job. Move on to the next one and get back to this. There might be an issue with my cluster. Two minutes is way too much to attach a persistent volume. I'll switch to a different cluster and we can see that. Let's see. Let's run the same Brigade script for a different cluster. Not sure exactly what's going on with this one. There's a couple of questions, Rado, if you wanna take a moment. Yeah. Is JavaScript the only or preferred language for event-driven scripting for Kubernetes? Brigade by default uses JavaScript to do this. That being said, we have seen at least three implementations of a declarative approach on top of Brigade. So you're not necessarily only restricted to JavaScript. You can have a YAML file that configures Brigade for your needs. But specifically by default, Brigade only uses JavaScript. And then the second question was, could you pass configuration or parameters to pod deployments like having a template and deploy it to Kubernetes? Yes, we actually see that doing, we've seen that done pretty regularly. Essentially you're keeping a bunch of Kubernetes config files or Helm charts into a repo and then as part of executing of a trigger, you're able to take that config file and then essentially deploy to Kubernetes or even better start Kubernetes cluster inside of Brigade using kind Kubernetes and Docker. I'm gonna point you to a resource for that. That's my job number five doesn't really work. So let's see, let's check on number six. Unfortunately, does Brigade need to run in the same cluster to interact with others? No, if you have a KubeConfig file for a different cluster stored as a secret, you can pass it to Brigade. It doesn't have to run. By default in the easiest way would be for Brigade to, essentially because Brigade runs in your Kubernetes cluster, it's able to start pods in the same namespace as it's configured into. But yeah, you can point to a different KubeConfig as well. So my cluster is acting up really weird. In a working scenario, essentially this would create a persistent volume and share it across all the jobs that are in the current build, right? In the current event. This is one persistent volume created for each event and then attached to each individual job. The second type of storage that we are able to use in Brigade is called a cache. And essentially, if it has the same job name and you have the cache enabled, this will attach the same persistent volume to your job across multiple event invocations. So think about it, if you've ever needed to use build caches for Java, for Maven, or for things like that, this is what's used for, right? Or just use the same storage across multiple invocations. Now, this is really interesting. I really don't know why the demo gods are not with us for the job storage, but we'll get back to that. Another thing that we're able to easily do in Brigade is essentially pass information, not using persistent volumes, but just return values from our containers. So think about it this way, we have two jobs. And the first one just prints something on the console, it can be a formatted JSON or plain text. And then we're starting job one, and then we're taking the output, the standard output of the results, which is the result of the execution of job one, and then using it as inputs to job two, right? So it's waiting to schedule the pod. We see job one, that outputs world. And then essentially what we're hoping to see is job two using the output of world, and then just using it in some way. So we can see it outputting hello world, right? So think about it in a more complex way. Think about it, job one, executing something, passing an idea of a job or an idea of something in your database, and then the second job, picking it up and using it. If you're only passing very simple information, like something that would be able to be pushed to standard output, feel free to use the job return values. If you have something more complex, more bigger object that you're trying to pass between jobs, you're probably gonna wanna use storage or caches or other storage in Kubernetes. And then finally let's have a look at how we would use more complex JavaScript features like think and await and then try catch, right? Think about it and you're trying to execute a job, and then you're preparing that something could fail in the job, right? In a normal Kubernetes scenario, if a pod fails, you can either choose to restart it or you can choose to back off and stop restarting it, right? We could give you another choice, which is catch exceptions, catch failures in your script, and then do something else, like do some cleanup. What the script is gonna do is gonna start job one, which will execute successfully, and then job two will exit with code one, which is a failure, right? And then because we are trying and expecting and accepting an error in either of these two jobs, we're able to catch the exception and do some cleanup, right? Think about it and if you ever need to stop some service from running or restart something or simply retry, right? You can do it right here. So we've caught the exception, right? And then we're able to do additional things, right? The caught exception error is coming from your actual code. So if you go back here, you can see that in this case, the job, the Vendix self-succeeded, but there's one job that failed. The Vendix self-succeeded because we chose not to fail it explicitly. Here you can just error out if you don't want to handle your exception or if you just leave it unhandled. But because we've been able to recover from that exception, even if job two failed, we can choose whether the build failed or succeeded. It's up to you, essentially. There is one question if any feature use case overlap with Kubernetes operator patterns and regate. This is a really interesting question, which is because we've been discussing that in the last couple of weeks and I can point you to a project that does just that, which is the Brigade Universal Kubernetes Controller, simple buck. I'll paste the building for that. Can you synchronize in a completion of a group? You can, essentially, you can wait, yes. Groups can be used if you put multiple jobs in a single group. You can wait for the entire group to finish before starting new jobs or before doing something else. Is the cache only runtime or does it persist across Brigade restarts? The cache is a Kubernetes persistent volume. So if you don't delete your persistent volume, then it's gonna remain. And by default, we don't delete the persistent volume. You have to do that manually. I told you there are a bunch of components in Brigade. There's a Kubernetes controller. There are worker pods which execute a script and schedule the jobs. And then there is the optional API that is used to interact with Brigade from web or CLI. And then gateways. Essentially, they map events from outside the cluster or outside of Brigade inside Brigade. Out of the box, Brigade comes where we can figure it with a cloud events gateway or one that supports arbitrary JSON, gateways for container registries, GitHub. They can be web hooks or a simple back script that create the Kubernetes secrets. Events in Brigade are simply Kubernetes secrets. And we have examples on how to build gateways for Go, Node.js, Bash, Python. We have community integrations with cloud events and CNTF integration with a GitHub checks API. And I can actually show you that. So this is the Brigade repo itself. And then for each pull request in the checks tab, we have jobs that are running inside of our Brigade cluster in the beginning installation and then logs pushed back to GitHub. So this is GitHub, this is just a GitHub app. And then these are two jobs in our Brigade JS script that we use to test the actual Brigade repo. So we can see them and interact with them. If you have access to the repo, you can rerun them, essentially interact them in the same way you would do with a GitHub checks API. We have integrations with Prometheus and Virtual Cubelets, essentially scale in with any provider at Virtual Cubelets supports. And if you wanna see a tutorial on how to do that on Azure container instances, I've written a couple of months ago a blog post on the Microsoft blog, essentially about how to schedule, how to burst out to Brigade jobs to Virtual Cubelets. So if you don't have enough capacity or don't want to schedule on your usual nodes, essentially you can set your host to be Virtual Cubelets and that's it, it's gonna, if you configure it, your jobs will start executing onto Virtual Cubelets and then you can use the Brig term to see them being started right there. And then soon we're gonna have documentation on how to integrate with Linkertly, the standard service mesh. And then for a full ecosystem, we can see the GitHub page for that. We really, really like contributors. If you wanna get started, there's a good first issue label in GitHub and I think most of our projects, you are more than welcome to start contributing. We're happy to help you get started. That's not what I was supposed to do. If you wanna get involved, you can find us in the Brigade channel in the Kubernetes Slack. We have a bi-weekly community meeting on Tuesday, which is at 5 p.m. Pacific. GitHub.com Brigade Core and then join us at KubeCon. We have an introduction to Brigade and a deep dive. The too long didn't read for Brigade is a, Brigade is an in cluster scripting environment that allows you to chain multiple containers. It is really lightweight and works on any Kubernetes cluster. You write scripts in basic JavaScript and then it's essentially the Kubernetes application. It's made of a couple of pods. You can manage it and monitor it in the same way as you would any other Kubernetes application. It's a CNCF sandbox project, stable version 1.2. We're getting prepared to release a 1.3. And if you wanna learn more about advanced language features to sync with more examples on Trycatch, we have advanced scripting guide on our documentation. I can learn more about gateways. If you wanna learn about how to secure Brigade, I really encourage you to check the security topic. If you wanna package up dependencies and use other NPM modules in your Brigade.js or write your own, we have an article on that. If you wanna test Brigade scripts, we have a topic for testing as well. Any other questions? If not, I can show you a couple of other examples such as, I was telling you about a couple of opinionated CICD frameworks built on top of Brigade. Here's one called Anya, which also integrates with the GitHub checks API and allows you to have trigger gates for different pull requests. So essentially you check out the checks tab and then it gives you a button on whether to promote the specific pull requests to production or rejected and perform deployments and things like that. So this is built on top of Brigade, for example, and there are a bunch of other projects built on top of Brigade as well. Another one that I'm really, really excited about is, we've kept seeing Brigade being used for end-to-end testing. So essentially you have your application and then you wanna deploy it on Kubernetes. You're using Kubernetes. There are two options. You can create a preview environment or a different namespace or you can start a Kubernetes cluster inside of Kubernetes itself using kind, which is Kubernetes in Docker, which is a Kubernetes 6 project. Essentially it uses Docker containers to start Kubernetes nodes. I really encourage you for a local environment is really, really easy to get started with Kubernetes. But here we're configuring a kind cluster inside of Kubernetes using Brigade and essentially this is all that we have to do. We have a kind job in a library and then basically we're adding our tasks and executing it. And what's gonna happen and when that Brigade is gonna get executed, we see the cluster getting started and we can interact in the same way as we would interact with any other cluster. Without interfering with our production workloads, right? So this is an interesting real big shout out to the kind team for this project. I really encourage you to get started with it. And if you wanna use JavaScript dependencies other than what Brigade comes with, essentially you can either have dependencies in your GitHub repo, in a local file and then import them or you can put them in a Brigade JSON file or similar to a Package JSON where you could just list your dependencies and use them from there. So you would just require it just like you would in a normal JavaScript project. Any other questions? I think that's it, Rado. Huh. Thank you very much for the great presentation, Rado. The webinar recording and slides will be online later today and we look forward to seeing you at a future CNCF webinar. Have a great day.