Loading video player...
Hello.
In this short tutorial, we are going to build some GitHub action workflows.
The CUE way.
If you've ever copied and pasted YAML between repositories only to
get bitten by a typo after pushing.
Or struggled to keep your monorepo pipelines consistent.
CUE can fix that.
No platform changes.
You still run on GitHub actions.
You just author your workflows in CUE instead of YAML.
The key idea is that CUE 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, steps as CUE 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 CUE basics.
Use the CUE Central Registry and set up your pipelines for success.
By the end, you'll know how to replace brittle YAML with
well-defined CUE, 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
get help for yourself on your CUE journey.
The first place you'll want to go to is cue.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.cue 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 kind of just looks
like YAML with a package statement.
The package statement.
Of course from the heritage of Go, which is what the CUE tooling is all written in,
we then define a name, David and Copilots jumping ahead and trying to define my age.
But before we take a look at anything else, we'll just
come here and run cue export.
By default cue exports in JSON we can see here that we have a property called
name that has a string value of David.
If we take copilot suggestion and just say that I'm 30 years old, uh uh, we could go
back and export again and we'll see here.
The age is a property with a integer value of 30.
The essence of CUE is that we can have our schema and our data all live 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 conflict in 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 CUE.
We can see that we only allow ages greater than or equal to 18
and less than or equal to 120.
Now, this is still correct until we go to 16 where we have an out of
bound error because again, we set the constraints that the lower bound was 18.
The upper value of one 120 is fine, but 121 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?
This resembles a person, so why not just have a data type or definition in
CUE with data and schema that defines what a person is and 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 set this to string, and now we have a definition, a
schema for what a person is.
Notes.
We could then say that we want to collect a list of people.
With the constraints of persons.
We are 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 cue export, and we see that we get
the people list back with Alice.
Something else that we can do with CUE is use the vet command.
cue vet just validates that what we have.
Is correct for the constraints that are defined.
You can use cue 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 and I won't use mine.
This is Alice at Rawkode Academy.
We can run over the cue vet; cue 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 cue export,
we get an incomplete value.
And the same for qve.
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,
run cue vet -c ; cue 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 of string, and C, which is a list of ints.
We are 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 qve C and everything is okay.
There is no error.
If we remove B, 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.
CUE 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 scaler values, but we then have the required exclamation mark to
change the behavior for lists.
Now if we run this B and C are in complete B, we can set to Hello
and C. We can set to a list of integers, getting us back to
our complete and happy value.
It is now time to talk about the CUE Central Registry.
This is the relatively new effort from the CUE team to make it easier
to adopt CUE 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 or Workflow.
YAML fails.
Or new and improved CUE ones that we're going to write as part of this tutorial.
We copy the identifier here
and head back to our terminal.
Now.
All we have is the main.cue, so we do need to run cue mod init.
This creates a cue.mod directory, meaning we can now do a cue mod 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 githubactions from cue.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 cue vet. It tells us we have incomplete values, which
is expected because they're not defining any jobs or any triggers for the workflow.
And what's important here is that we said workflow is a workflow.
That's it, right?
It's just an association between the definitions that we get from the CUE
Central Registry to this workflow property where we then set the name.
But again, we're not setting 'em 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.
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 cue vet.
So let's head over to our terminal where we can run cue vet. You can see I've
already already typed this command.
We're looking for a complete value with dash C dash D is shorthand for schema.
We're saying we're going to use the workflow definition, and this comes
from a CUE package on the CUE Central registry called GitHub Actions, and we
pass the fail that we want to validate.
This tells me that my GitHub action workflow 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 defined, so you can take your existing workflows
and validate them before you push.
Not only that, we can import this YAML to give us CUE
versions of our GitHub actions.
So let's try
workflows deploy, and write this to workflows.
Deploy queue, not forgetting the cue at the start.
We can then find deploy.cue, and now we have a CUE representation.
Okay, so while this is a valid Q workflow for GitHub actions, we're
not actually using any of the definitions and enforcing this with QV.
So what we're gonna do is first set up a package main so that we
can use this file with main dot Q, which is now currently empty.
We are going to import Q 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 stick the closing brace at the bottom and run Q format
and job done.
Now if we run qve dash C, everything is still valid, so now it's time to take some
niceties of Q 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, the push on main expand as type
whoever paths,
we can format that up.
And now we have this.
So if we run Cube Fit.
It fails drastically.
And that's just because we need push paths, 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 could be reused across other workflows.
Now, you may notice here that all of the paths on our pushy and our
pill 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 cube there, things look pretty good.
Now, if we export
as YAML
and write this to a fail.
We have a forced 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 we
only want to write the deploy property, in this case, our workflow to the file.
And if we open this, this look much cleaner already.
If we try the diff.
You will see it is superficial editors with quoting of 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 iterate on this.
So let's do one more together.
So let's head back to deploy docu, and let's talk about security and salsa
compliance, or at least skim over the boring stuff and do the fun qubits.
Down here we have a step
for performing a checkout.
Pretty much every GitHub actions workflow has the step and drift between versions.
Even if you are using versions V three, four, whatever.
But best practice is to pin this to Aisha 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 shas on each workflow, depending
on how many workflows you have, and well depend, bot will do its best to
try and keep these up to date for you.
Maybe using a Q module via the registry or just and a config or main Q like
we're using today is a better approach.
So let's take our action and say actions check out which is equal to this.
Now I have gone ahead and grabbed a share in advance so we can just save this file.
We can run Q format, pop back, and this looks pretty good.
We can go back to our deploy queue.
And now here we delete.
This, and we could just say, actions not checkout,
like so, and I'm not sure how Q is gonna format that, so why not?
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 Shah address.
And as I said.
This could live in a Q central registry where your whole team can publish their
salsa compliance approved versions.
Now outside of our main deploy dot queue, we've actually lost any validation or
verification that this is a correct type.
But we can come to the central registry for GitHub actions, step for use,
because we know that is a known property.
And if we scroll up, we'll see that we have GitHub actions.
Step.
So we can use this type here by saying GitHub actions step and just
importing Q Dev GitHub actions V zero.
Now if we run Q Vet dash D, 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 do Q. 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 runs on tu.
Well, not saving us any lines of code, just a slightly nicer experience
than having to walk with the runs on.
Been to strings,
we then have an action to in 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 and the dagger
shell that we want to run.
And then lastly here, we have conditions dot Push on Main.
This is gating or protecting our deployment Dagger.
Shell command to deploy the production website unless it is a push on main.
If we pop open the main Q, we have our runs on tu, 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 EV condition to check the branch,
the reference or workflow dispatch.
And again, we're just scratching the surface of the
things you can do with cure.
And that's how you can improve your GitHub actions with Q, you 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 rate your GitHub actions in queue, making them
ergonomic and dry because who doesn't want to reduce boiler plate?
It'll make you hate GitHub actions, and it'll make you hate YAML that
little bit less on every deploy.
All you have to do is remember to use Q Export and save your GitHub actions.
But of course you can automate that too, making sure your CI fails
when your cue and your yam will 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.
Are you tired of copy-pasting YAML between repositories only to be bitten by typos after pushing? In this tutorial, we explore how to replace brittle GitHub Actions YAML with CUE - a powerful configuration language that brings type safety, validation, and reusability to your pipelines. We walk through the process of adopting CUE incrementally, allowing you to keep the parts of GitHub Actions you love while removing the pain of managing massive YAML files. You'll learn how to treat your configuration as code, enforce policies, and generate your final YAML artifacts automatically. In this video, we cover: - CUE Basics: Understanding how CUE combines schema and data to validate inputs (for example, our example will show how to ensure age constraints). - Validation: Using cue vet to catch mistakes and validate workflows against schemas before you push. - The CUE Central Registry: Importing existing GitHub Actions definitions to get instant validation for your pipelines. - Importing & Exporting: How to convert existing YAML files into CUE (cue import) and generate valid - YAML back out (cue export). - DRY Workflows: Creating reusable triggers and modules to avoid code duplication across your repositories. - Security: How to use CUE to pin action versions to specific SHAs for safer, compliant builds. Resources: - CUE Central Registry Documentation: https://cue.dev/docs - CUE Central Registry: https://registry.cue.works/ - More about the CUE project: https://cuelang.org