Skip to content

Using actions

As a reminder, to be able to use an action, you have to know its owner, name and version, e.g. actions/checkout@v3. The library comes bundled with some popular actions, but you can consume any action you want. Read on to learn about your possibilities.

Built-in actions

Take a look here: Supported actions. These are actions ready to use, grouped by owners. For actions/checkout@v3, there's CheckoutV3 accepting all inputs defined in its metadata file, along with some basic typing. You may notice that for each major version, a separate class exists. It's because it's assumed Semantic Versioning is used to version the actions, as recommended by GitHub. Each new major version means a breaking change, and it usually means that the Kotlin binding for the action needs a breaking change as well.

Requirements for adding a new action

An action is eligible to be added to this library (i.e. have its Kotlin binding generated and maintained by the library) if the following conditions are fulfilled:

  • follows Semantic Versioning, with exceptions for pre-releases (like v0.2)
  • provides major version tags, as described here. An example valid tag is v2 that points to the newest release with major version number 2. Example invalid tags are v2.1.0, latest or main

Nice to have:

User-defined actions

If your action is not bundled with the library, you are in a hurry and contributing to the library now is not an option, you have two ways to proceed.

Typed binding

When to use this approach

It lets you create an action binding in a similar manner that is provided by the built-in action bindings in this library, i.e. a class that takes some constructor arguments with types of your choice, and maps them to strings inside toYamlArguments. Use it to have better type-safety when using the binding.

Repository based actions

In case of a repository based action which most GitHub actions are, inherit from RegularAction and in case of actions without explicit outputs, use the Actions.Outputs class as type argument:

class MyCoolActionV3(
    private val someArgument: String,
) : RegularAction<Action.Outputs>("acmecorp", "cool-action", "v3") {
    override fun toYamlArguments() =
        linkedMapOf(
            "some-argument" to someArgument,
        )

    override fun buildOutputObject(stepId: String) = Outputs(stepId)
}

or, in case of actions with explicit outputs, create a subclass of Action.Outputs for the type argument:

class MyCoolActionV3(
    private val someArgument: String,
) : RegularAction<MyCoolActionV3.Outputs>("acmecorp", "cool-action", "v3") {
    override fun toYamlArguments() =
        linkedMapOf(
            "some-argument" to someArgument,
        )

    override fun buildOutputObject(stepId: String) = Outputs(stepId)

    class Outputs(stepId: String) : Action.Outputs(stepId) {
        public val coolOutput: String = "steps.$stepId.outputs.coolOutput"
    }
}

Once you've got your action, it's now as simple as using it like this:

uses(
    name = "FooBar",
    action = MyCoolActionV3(someArgument = "foobar"),
)

Local actions

In case of a local action you have available in your repository or cloned from a private repository, inherit from LocalAction instead:

class MyCoolLocalActionV3(
    private val someArgument: String,
) : LocalAction<Action.Outputs>("./.github/actions/cool-action") {
    override fun toYamlArguments() =
        linkedMapOf(
            "some-argument" to someArgument,
        )

    override fun buildOutputObject(stepId: String) = Outputs(stepId)
}

Published Docker actions

In case of a published Docker action, inherit from DockerAction instead:

class MyCoolDockerActionV3(
    private val someArgument: String,
) : DockerAction<Action.Outputs>("alpine", "latest") {
    override fun toYamlArguments() =
        linkedMapOf(
            "some-argument" to someArgument,
        )

    override fun buildOutputObject(stepId: String) = Outputs(stepId)
}

Untyped binding

When to use this approach

It omits typing entirely, and both inputs and outputs are referenced using strings. Use it if you don't care about types because you're in the middle of experimenting. It's also more convenient to produce such code by a code generator.

Repository based actions

In case of a repository based action which most GitHub actions are, use a CustomAction:

val customAction =
    CustomAction(
        actionOwner = "xu-cheng",
        actionName = "latex-action",
        actionVersion = "v2",
        inputs =
            linkedMapOf(
                "root_file" to "report.tex",
                "compiler" to "latexmk",
            ),
    )

If your custom action has outputs, you can access them, albeit in a type-unsafe manner:

job(id = "test_job", runsOn = RunnerType.UbuntuLatest) {
    val customActionStep =
        uses(
            name = "Some step with output",
            action = customAction,
        )

    // use your outputs:
    println(expr(customActionStep.outputs["custom-output"]))
}

Local actions

In case of a local action you have available in your repository or cloned from a private repository, use a CustomLocalAction instead:

val customAction =
    CustomLocalAction(
        actionPath = "./.github/actions/setup-build-env",
    )

Published Docker actions

In case of a published Docker action, use a CustomDockerAction instead:

val customAction =
    CustomDockerAction(
        actionImage = "alpine",
        actionTag = "latest",
    )