About this video
What You'll Learn
- Author GitHub Actions workflows in CUE instead of handwritten YAML.
- Import registry definitions to type-check workflows and reuse shared steps.
- Pin actions to SHAs, add Dagger setup, and gate deploys on main.
Author GitHub Actions workflows in CUE instead of YAML. Walk through cue vet, import, and export, pull workflow definitions from the CUE Central Registry, pin actions to SHAs, and wire in a Dagger install step.
Full transcript
Generated from the English captions. Timestamps jump the player to that moment.
Read the full transcript
0:00 Hello. In this short tutorial, we're going to build some GitHub action workflows. The QUE. If you've ever copied and pasted YAML between repositories only to get bitten by a typo after pushing or struggle to keep your mono repo pipelines consistent. Queue can fix that. No platform changes who still run on GitHub Actions, you just author your workflows in Queue instead of YAML. The key idea is that Queue becomes your source of truth. Our YAML is now nothing more than a generated artifact. This means type safety, validation, and catching mistakes before you push. It means composition and reuse. You can share triggers, jobs,
0:43 steps as queue modules. It means safe defaults, policy enforcement and dry workflows that stay maintainable over time. You can adopt it incrementally and gain all of these benefits without giving up the parts of GitHub Actions that you already love. In this video, we'll cover the queue basics, use the Queue Central Registry, and set up your pipelines for success. By the end, you'll know how to replace brittle YAML with well defined queue, keeping the good parts and removing some pain. Let's get started. So before we start covering the basics of cue, it's important to know how you can
1:30 get help for yourself on your Queue journey. First place you'll wanna go to is queue.dev/docs. There's also a link on screen now that takes you directly to the documentation for working with GitHub Actions. Now let's get that terminal open. So in our IDE, we have a main dot q file that is currently empty. We're gonna start by just putting package main, where we'll export the name David. If you squint at this, it kinda just looks like YAML with a package statement. The package statement, of course, from the heritage of Go, which is what the queue tooling
2:09 is all written in. We then define the name David and Copilot's jumping ahead and trying to defend my age. Before we take a look at anything else, we'll just come here and run q export. By default, q exports in JSON. We can see here that we have a property called name, which has a string value of David. If we take Copilot suggestion and just say that I am 30 years old, we could go back and export again, and we'll see here the age is a property with a integer value of 30. Now the essence of Q is that we
2:40 can have our schema and our data all living side by side to the point where we can see that this is an integer. If we set this to be the string value of 30, we get a conflicting value mismatch types between int and string. So we can set it back to 30, but we can do so much more with the power of q. We can say that we only allow ages greater than or equal to 18 and less than or equal to a 120. Now, this is still correct until we go to 16, where we have an out of bound error,
3:21 because, again, we set the constraints that the lower bound was 18. The upper value of one twenty is fine, but one twenty one is not. Let's keep it back to 30 and move on. Now, as we can see here, we have some sort of shape to our data, right, that resembles a person. So why not just have a data type or definition in queue with data and schema that defines what a person is. We can say person equals, and we can grab this and put this here. Now, of course, we wouldn't want to define the name and the person itself, so we
4:02 set this to string. And now we have a definition, a schema for what a person is. We could then say that we want to collect a list of people with the constraints of persons. We're looking for a list of persons. We could now pop open a list, and we'll let Copilot succeed in some regards here. We'll give it something. Where, say, we have Alice with an age, like so. We can come over and we could run queue export, and we see that we get the people list back with Alice. Something else that we can do with q
4:43 is use the vet command. Q vet just validates that what we have is correct for the constraints that are defined. You can use q help vet to get more information on how to use this command, but we will use a few common variants as part of this tutorial. Let's head back over to our data. Let's assume that we also need email addresses. We can say that this is of type string with a constraint and given that the four second eyeball test, I'm gonna say Copilot is not too far off. We're then going to add an email address,
5:20 and I won't use mine. This is alice at rawkode academy. We can run over, do Queue Vet, Queue Export, everything seems to be good. But what if we want to make the email address an optional field? Currently, if we remove the email address, we run q export, we get an incomplete value and the same for qvet. If we add dash c as instructed, it just gives us the same warning saying that we're trying to get concrete values and it's failing because this is an incomplete value. So we can come to the email address, add a question mark,
6:02 run qvet dash c, q export, and we're back to a working configuration. So let's dig into that a little bit more. Let's assume we have property a of int, b, a string, and c, which is a list of ints. We're going to say that a is an optional field and b is a regular field, and we'll leave c as a regular field too. Now when we put together our Alice Person, we'll leave 'a' out and just add 'b' with 'hello'. We can then run 'cuvette -c' when everything is okay. There is no error. If we remove 'b',
6:48 we now have an incomplete value. But you may be looking at 'b' and 'c' and saying that they're the same. And this is just down to the default value for a list being an empty list. Queue has a way to work around this semantic with required fields. So optional, regular, which is the same or can be described as required on scalar values, but we then have the required exclamation mark to change the behaviour for lists. Now if we run this, b and c are incomplete, b we can set to hello, and c we can set to a list of integers,
7:33 getting us back to our complete and happy value. It's now time to talk about the queue central registry. This is the relatively new effort from the Q team to make it easier to adopt Q for common workflows. If we search for GitHub actions and click on the package, you'll see that we now have a bunch of definitions that we can import from the central registry to get instant validation against existing GitHub workflow YAML files, or new and improved Q1s that we're going to write as part of this tutorial. We copy the identifier here and head back
8:11 to our terminal. Now all we have is the main. Queue, so we do need to run qmod in it. This creates a q. Mod directory, meaning we can now do qmod get and paste in what we copied from the registry. We can now come to our code where we could delete all of our boilerplate thus far and say import GitHub actions from q. Dev. Now, what I could do is start to put together a workflow called deploy. I could indent it with workflow and say that workflow is of type github actions workflow. We could then come over, run QVet,
8:51 it tells us we have incomplete values, which is expected, because they're not defining any jobs or any triggers for the workflow. What's important here is that we said workflow as a workflow. That's it, right? It's just an association between the definitions that we get from the QCentral registry to this workflow property, where we then set the name, but again, we're not setting them up. We're not setting enough. Now, rather than typing all of this out line by line and working through the errors, we're going to take an alternative approach. That is, in this workflows directory,
9:23 I have pulled in the real production Rawkode Academy website workflow. Now there's two interesting things that we can do here. One is import, but the other is QUEVATE. So, let's head over to our terminal where we can run QUEVATE, and we can see I've already typed this command. We're looking for a complete value with -c. -d is shorthand for schema, we're saying we're going to use the workflow definition. And this comes from a queue package on the queue central registry called github actions, and we pass the fail that we want to validate. This tells me that my github action workflow
10:02 is fine. Of course, we can break this by removing a whole bunch of stuff, and now we're being told that we need on to find. So, you can take your existing workflows and validate them before you push. Not only that, we can import this YAML to give us queue versions of our GitHub actions. So, let's try workflows deploy and write this to workflows deploy dot queue, not forgetting the queue at the start. We can then find deploy dot queue, and now we have a queue representation. Okay. So while this is a valid queue workflow for GitHub actions,
10:48 we're not actually using any of the definitions and enforcing this with QVet. So what we're gonna do is first set up a package dot main so that we can use this file with main dot queue, which is now currently empty. We are going to import q dot dev GitHub actions just like we did previously. Now we need to be able to see that all of this is an instance of a definition. Why don't we just call this deploy, which is actually GitHub actions workflow? Like so. We can set the closing brace at the bottom and run queue format,
11:30 and job done. Now if we run queue vet dash c, everything is still valid. So now it's time to take some niceties of queue and clean up our config. Now pushing on main is quite a common thing to do. So much so, I think we should have something called triggers called push on main, which configures push branches main, like so. This means that we can come here, delete everything but the paths and then grab them. We can then say on triggers dot push on main, expand as type, move our paths. We can format that up
12:23 and now we have this. So if we run kubect, it fails drastically. And that's just because we need push pass, not just pass. And we're back to valid. So right away, we've found a way to set up a commonality, a trigger, that can be reused across other workflows. Now you may notice here that all of the paths on our Pushes and our Pull Requests are also the same. So we can also say that Source Paths are our two paths and set them like so. Again, if we run kubect, things look pretty good. Now if we export
13:17 as YAML and write this to a file, we're forced to overwrite an existing file. We can then say deploy. Yaml and workflows diff are two different files. Now, as we can see, there are an awful lot of differences here, and if we pop open this one, you'll see that our source paths have snuck in, and our entire workflow is nested under Deploy, which is not what our original workflow had. So, we need to modify our export to support this use case. If we pull up our export command again, we can pass an expression to say that
14:04 we only want to write the deploy property, in this case our workflow, to the file. And if we open this, this looked much cleaner already. If we try the diff, you'll see it is superficial errors with quoting our strings and the comments which are not imported by q import. So currently, our workflow in Q and in YAML is correct. But, of course, we can continue to SRA on this, so let's do one more together. So let's head back to deploy dot Q, and let's talk about security and Salsa compliance, or at least skim over the boring stuff
14:42 and do the fun cubits. Down here, we have a step for performing a checkout. Pretty much every GitHub actions workflow has this step and drift between versions, even if you are using versions v three, four, whatever. But best practice is to pin this to a SHA from the repository. And it's hard to do across repositories, but it's still even hard to do end repositories because it requires updating all of the SHAZ on each workflow, depending on how many workflows you have. And, well, Dependabot will do its best to try and keep these up to date for you. Maybe using a
15:22 queue module via the registry or just an a config or main dot queue like we're using today is a better approach. So let's take our action and say actions checkout, which is equal to this. Now, I have gone ahead and grabbed the SHA in advance, so we can just save this file. We can run queue format, pop back, and this looks pretty good. We can go back to our deploy dot queue. And now, here, we delete this. And we could just say actions. Checkout, like so. And I'm not sure how q is going to format that, so why not?
16:05 Let's not guess. Okay. But does this vet and is it still valid? We can run vet dash c. It looks good. We can continue with our export of deploy and we can run our diff. And what we'll see here is that we have went from actions v four, our actions checkout v four, to our new SHA address. And as I said, this could live in the queue central registry where your whole team can publish their Salsa compliance approved versions. Now outside of our main deploy. Queue, we've actually lost any validation or verification that this is a correct type. But we can come
16:49 to the central registry for Github actions, step for users because we know that is a known property. And if we scroll up, we'll see that we have a Github actions step. So we can use this type here by saying github actions dot step and just importing q. Dev github actions v zero. Now if we run q v dash c, everything is still valid. You could do the same for your triggers and anything else that you want to make reusable as well. Now I have, behind the scenes, made a few more changes to deploy dot q.
17:32 So let's go take a look at other improvements you can make to your GitHub Actions workflows using Q. So, if we scroll down Deploy. Q, we now have RunsOn Ubuntu. Well, not saving us any lines of code, just a slightly nicer experience than having to work with the runs on Ubuntu strings. We then have an action to install Dagger, so we no longer have to remember the command for that, and simplifying how to run a Dagger shell, because all of these Dagger shells run-in the same directory for this project. Really all we need is a name
18:13 and the Dagger shell that we want to run. And then lastly, here we have conditions. Pushonmain. This is gating or protecting our deployment Dagger shell command to deploy the production website unless it is a pushonmain. If we pop open the main. Q, we have our runs on Ubuntu. We have our Dagger install, which is just the GitHub action step. We have our Dagger shell, which just sets the working directory, and my AA keyboard is getting very frustrating. And we have conditions push on main, which sets the f condition to check the branch, the reference, or workflow dispatch.
18:52 And, again, we're just scratching the surface of the things you can do with Q. And that's how you can improve your GitHub actions with Q. Can use the Q registry to pull in the GitHub actions types. Not only that, you can publish your own helpers to the registry too. Make them available for everyone within your team, your organization, or even share them with the world. Then you can write your GitHub actions in queue, making them ergonomic and dry. Because who doesn't want to reduce boilerplate? It'll make you hate GitHub actions and it'll make you hate YAML that little bit less
19:25 on every deploy. All you have to do is remember to use qexport and save your GitHub actions. But of course, you can automate that too, making sure your CI fails when your q and your yaml diverge. I hope this session piqued your interest. I hope you're excited to learn Q and explore Q for GitHub Actions. Have a great day.
Technologies featured
Stay ahead in cloud native
Tutorials, deep dives, and curated events. No fluff.
Comments