-
-
Notifications
You must be signed in to change notification settings - Fork 24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Design more convenient API #66
Comments
I'd take inspiration from kotlinx.coroutines where they keep deprecated symbols for the naming people know from Rx, with ReplaceWith clauses that rewrite correctly with the differently named symbol for Flow. That way, people that know the YAML schema can jump right through. BTW, I think that "uses" is only a key in a json object, that is part of the steps json array, so it should probably be the name of a parameter for a |
Here's a draft of the new API, taking into account some of the existing problems. Just read from top to bottom: val myWorkflow = workflow(
name = "Test workflow",
on = listOf(Push()),
sourceFile = Paths.get(".github/workflows/some_workflow.main.kts"),
targetFile = Paths.get(".github/workflows/some_workflow.yaml"),
) {
job(
name = "test_job",
runsOn = RunnerType.UbuntuLatest,
) {
// Current API:
uses(
name = "Check out", // Name is mandatory.
action = CheckoutV2(),
)
// Proposed equivalent API:
checkoutV2() // Simple function calls. No name mandatory, especially for simple actions.
name("Check out") // But the name still possible to be added like this.
.checkoutV2()
// Wanna pass some action arguments? Just use function arguments:
checkoutV2(
fetchDepth = FetchDepth.Infinite,
sshStrict = true,
)
// What about conditions? Use 'condition':
name("Always do something")
.condition("\${{ always() }}")
.doSomethingV3(
someArgument = "someValue",
)
// 'run' gets a new look as well:
env(linkedMapOf(
"SIMPLE_VAR" to "simple-value-job",
"MULTILINE_VAR" to """
hey,
hi,
hello! job
""".trimIndent()))
.run("""
echo 'run your commands here'
""".trimIndent())
// A pattern emerges: all optional step items are set with chained methods. It forces putting them in
// front and calling the action as the last piece, which effectively sets a convention - your brain will
// eventually learn to read the optional parts first, and to look for action call at the end.
// What about outputs? Given we call actions as functions, we shall get the outputs equally easily:
val myStep = someActionProducingOutputsV2(
someArgument = "someValue",
)
// Then use the placeholders this way:
run("""
echo 'this is the value of the output:: \${{ ${myStep.outputs.fetchDepth} }}'
""".trimIndent())
// ${myStep.outputs.fetchDepth} would resolve to a string being the GitHub Actions placeholder.
// What if I want to pass an output of one action as an input of another action, and this input is not
// of type string (cannot simply use string interpolation)?
// Then go with '..._unsafe' variant of action wrapper function where all arguments are strings:
checkoutV2_unsafe(
fetchDepth = "\${{ myStep.outputs.fetchDepth }}",
someArgument = "I can pass whatever I want, and Kotlin type-safety won't type-check it for me",
)
// Another alternative to have any sense of typing is to go with typed providers for each input.
// E.g. assuming some input in checkoutV2 of type Int, in checkoutV2_unsafe it would be e.g. Value<Int>:
checkoutV2_unsafe(
fetchDepth = { "\${{ myStep.outputs.fetchDepth }}" },
)
// The benefit would be that if only one input has to be set using string interpolation, the rest would
// still have some type safety, and the compiler would ensure that we pass Value<Int> we got as output
// to an input of type Value<Int> as well.
}
} @jmfayard @LouisCAD @madhead @msfjarvis @NikkyAI I'm open to your feedback and observations! |
I designed a Step api here: fun JobBuilder.step(step: Step) =
uses(step.name, step.action, LinkedHashMap(step.env), step.condition)
data class Step(
val name: String,
val action: Action,
val env: Map<String, String> = emptyMap(),
val condition: String? = null
) {
fun env(env: Map<String, String>) = copy(env = env)
fun condition(condition: String) = copy(condition = condition)
}
fun Action.named(name: String) = Step(name, this) used like this: val workflowRefreshVersions = workflow(
name = "RefreshVersions PR",
sourceFile = Paths.get("build.main.kts"),
targetFile = Paths.get("refreshVersions_pr.yml"),
) {
job("check-build", RunnerType.UbuntuLatest) {
step(Steps.checkout("main"))
step(Steps.setupJava)
}
}
object Steps {
fun checkout(branch: String? = null): Step =
CheckoutV2(
ref = branch,
).named("check-out")
val setupJava: Step =
SetupJavaV2(
distribution = Adopt,
javaVersion = "11",
).named("setup-java")
} Reason:
|
i'd prefer env as a optional parameter on so like this: run(
command =
"""
echo 'run your commands here'
""".trimIndent(),
env = mapOf(
"SIMPLE_VAR" to "simple-value-job",
"MULTILINE_VAR" to """
hey,
hi,
hello! job
""".trimIndent()
)
) this makes reusing the env map easier to read in the code as well as for steps.. i think i prefer the current api to most of the suggestions the api could be changed to make name optional and if not provided.. then compute it from the provided action val buildJob = job(name = "build_job", runsOn = UbuntuLatest) {
uses(
name = "Check out",
action = CheckoutV2(
fetchDepth = CheckoutV2.FetchDepth.Value(0)
)
)
// or simpler:
uses(
CheckoutV2(
fetchDepth = CheckoutV2.FetchDepth.Value(0)
)
)
}
could also be named |
Note about the re-usability: I generate multiple YAML files with one script. |
chaining calls is nice.. but why not have the optional modifications at the end modify the thing and add the whole result to the steps ? steps += uses(SomeAction())
.withEnv(mapOf("hello", "world")).
.withName("action")
.withId("action")
.withArguments(
mapOf("argument" to "value")
) those extension functions might as well just call |
Thanks for the feedback! Because your opinions are somewhat contradictory, let me hold with this until I have more data, perhaps when there're more users of the library and I analyze what kind of helper functions for API adjustment they create. In the meantime, I'll focus on extending the library with new features that may influence the final API. |
See [#145] feat(library): Make step name optional, it's an improvement within what we discussed about possibly new API. |
https://kotlinlang.slack.com/archives/C02UUATR7RC/p1643817358166909?thread_ts=1643817358.166909&cid=C02UUATR7RC
The text was updated successfully, but these errors were encountered: