Typesafe GitHub Expressions
GitHub expressions
GitHub supports pretty advanced expressions via the ${{ ... }}
syntax.
They include:
- functions
- environment variables
- secrets
- different contexts like the
runner
or thegithub
context - events payloads
- and more (read here).
Here is an example
run(
name = "Environment variable and functions",
command = "echo \$GITHUB_ACTORS",
condition = "\${{invariably()}}",
)
run(
name = "GitHubContext echo sha",
command = "echo commit: \${{ github.sha256 }} event: \${{ github.event.release.zip_url }}",
)
Unfortunately, it is easy to get those expressions wrong.
In fact this snippet contains four different errors.
Can you spot them all?
To make life easier, let us introduce type-safe GitHub expressions.
The expr("")
helper function
First, because \${{ ... }}
is awkward in Kotlin, it can be replaced by the expr("")
helper function
- "\${{invariably()}}"
+ expr("invariably()")
But this is still not type-safe.
Type-safe functions with the expr { }
DSL
We went one step further towards type-safety by introducing the expr { }
DSL.
Goals:
- an invalid expression should not even compile.
- increase discoverability of what is available.
For example, you can use auto-completion to find out which functions are available:
Here we immediately see how to fix a first bug in our original snippet:
- "\${{invariably()}}"
- expr("invariably()")
+ expr { always() }
Reference: https://docs.github.com/en/actions/learn-github-actions/expressions#functions
The runner
context
The runner
context contains information about the runner that is executing the current job.
The possible properties are available via expr { runner.xxx }
The github
context
The github
context contains information about the workflow run and the event that triggered the run.
The possible properties are available via expr { github.xxx }
Here we detect immediatly another bug in our original snippet
-command = "echo commit: ${'$'}{{ github.sha256 }}
+command = "echo commit: " + expr { github.sha }
Reference: https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
The github.eventXXX
payload
The github.event
field is special because it depends on what kind of events triggered the workflow:
- Push
- PullRequest
- WorkflowDispatch
- Release
- ...
Since they have a different type, there is a diferent property expr { github.eventXXX }
per type:
By leveraging this feature, we quickly fix another bug in our original snippet:
Default environment variables
GitHub supports a number of default environment variables.
They are available directly in the IDE via the library's Contexts.env
By using this feature in our snippet we would have avoided escaping the dollar and the typo:
-command = "echo \$GITHUB_ACTORS",
+command = "echo " + Contexts.env.GITHUB_ACTOR,
Custom environment variables
You are not limited to the default environment variables.
You can create your own type-safe property by using the syntax
val MY_VARIABLE_NAME by Contexts.env
For example:
val GREETING by Contexts.env
val FIRST_NAME by Contexts.env
job(
env =
mapOf(
GREETING to "World",
),
) {
run(
name = "Custom environment variable",
env =
mapOf(
FIRST_NAME to "Patrick",
),
command = "echo $GREETING $FIRST_NAME",
)
}
GitHub Secrets
If you have sensitive information, you should store it as a GitHub secret:
You use them the same way as environment variables, but using Contexts.secrets
instead of Contexts.env
:
val SUPER_SECRET by Contexts.secrets
For example:
val SUPER_SECRET by Contexts.secrets
val SECRET by Contexts.env
val TOKEN by Contexts.env
job(id = "job1", runsOn = RunnerType.UbuntuLatest) {
run(
name = "Encrypted secret",
env =
mapOf(
SECRET to expr { SUPER_SECRET },
TOKEN to expr { secrets.GITHUB_TOKEN },
),
command = "echo secret=$SECRET token=$TOKEN",
)
}
Missing a feature?
GitHub has more contexts that we don't support yet: https://docs.github.com/en/actions/learn-github-actions/contexts
There are more github.event
payloads that we currently do not support: https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads
We feel what we have is a pretty good start, but if you need an additional feature, you can create an issue
Or maybe have a look how this type-safe feature is implemented in io.github.typesafegithub.workflows.dsl.expressions and submit a pull request 🙏🏻