Skip to content

Commit

Permalink
Also make it possible to supply the action manifest
Browse files Browse the repository at this point in the history
  • Loading branch information
Vampire committed Feb 25, 2025
1 parent 371876a commit aef49b1
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 17 deletions.
8 changes: 4 additions & 4 deletions action-binding-generator/api/action-binding-generator.api
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ public final class io/github/typesafegithub/workflows/actionbindinggenerator/gen
}

public final class io/github/typesafegithub/workflows/actionbindinggenerator/generation/GenerationKt {
public static final fun generateBinding (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/MetadataRevision;Lio/github/typesafegithub/workflows/actionbindinggenerator/metadata/Metadata;Lkotlin/Pair;Ljava/lang/String;)Ljava/util/List;
public static synthetic fun generateBinding$default (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/MetadataRevision;Lio/github/typesafegithub/workflows/actionbindinggenerator/metadata/Metadata;Lkotlin/Pair;Ljava/lang/String;ILjava/lang/Object;)Ljava/util/List;
public static final fun generateBinding (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/MetadataRevision;Lio/github/typesafegithub/workflows/actionbindinggenerator/metadata/Metadata;Lkotlin/Pair;Ljava/lang/String;Ljava/lang/String;)Ljava/util/List;
public static synthetic fun generateBinding$default (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/MetadataRevision;Lio/github/typesafegithub/workflows/actionbindinggenerator/metadata/Metadata;Lkotlin/Pair;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ljava/util/List;
}

public final class io/github/typesafegithub/workflows/actionbindinggenerator/metadata/Input {
Expand Down Expand Up @@ -161,8 +161,8 @@ public final class io/github/typesafegithub/workflows/actionbindinggenerator/met
}

public final class io/github/typesafegithub/workflows/actionbindinggenerator/metadata/MetadataReadingKt {
public static final fun fetchMetadata (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/MetadataRevision;Lkotlin/jvm/functions/Function1;)Lio/github/typesafegithub/workflows/actionbindinggenerator/metadata/Metadata;
public static synthetic fun fetchMetadata$default (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/MetadataRevision;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lio/github/typesafegithub/workflows/actionbindinggenerator/metadata/Metadata;
public static final fun fetchMetadata (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/MetadataRevision;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lio/github/typesafegithub/workflows/actionbindinggenerator/metadata/Metadata;
public static synthetic fun fetchMetadata$default (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/MetadataRevision;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lio/github/typesafegithub/workflows/actionbindinggenerator/metadata/Metadata;
}

public final class io/github/typesafegithub/workflows/actionbindinggenerator/metadata/Output {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ public fun ActionCoords.generateBinding(
metadata: Metadata? = null,
inputTypings: Pair<Map<String, Typing>, TypingActualSource?>? = null,
types: String? = null,
explicitMetadata: String? = null,
): List<ActionBinding> {
val metadataResolved = metadata ?: this.fetchMetadata(metadataRevision) ?: return emptyList()
val metadataResolved = metadata ?: this.fetchMetadata(metadataRevision, explicitMetadata) ?: return emptyList()
val metadataProcessed = metadataResolved.removeDeprecatedInputsIfNameClash()

val (inputTypingsResolved, typingActualSource) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,13 @@ private fun ActionCoords.actionYamlUrl(gitRef: String) = "https://raw.githubuser

public fun ActionCoords.fetchMetadata(
metadataRevision: MetadataRevision,
explicitMetadata: String? = null,
fetchUri: (URI) -> String = ::fetchUri,
): Metadata? {
if (explicitMetadata != null) {
return yaml.decodeFromString(explicitMetadata)
}

val gitRef =
when (metadataRevision) {
is CommitHash -> metadataRevision.value
Expand Down
8 changes: 7 additions & 1 deletion docs/user-guide/using-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,20 @@ While developing a typing manifest it might be a good idea to test the result wi
release the action in question or merge a PR in the catalog. For this you can `POST` the typing manifest you have
on disk to the binding server using any valid URL for the action in question, for example using
```bash
curl --data-binary @action-types.yml https://bindings.krzeminski.it/pbrisbin/setup-tool-action/v2/setup-tool-action-v2.pom
curl -F types=@action-types.yml https://bindings.krzeminski.it/pbrisbin/setup-tool-action/v2/setup-tool-action-v2.pom
```
The binding server generates a binding with only the given type manifest and answer with some unique coordinates
that you can use in a test workflow script. The binding will be available the normal cache time on the binding
server and locally as long as you do not delete it from your local Maven repository where it is cached. After
the cache period on the server ended requesting the same coordinates will return a binding as if no typing
information is available at all.

When writing typings for a new action that is not published yet or a new version with changed inputs / outputs,
you should also provide the new action manifest, that the generation works with that state using
```bash
curl -F [email protected] -F [email protected] https://bindings.krzeminski.it/foo/bar/vX/bar-vX.pom
```

Once there are any typings in place for the action, the `_Untyped` suffixed class is marked `@Deprecated`, and a class
without that suffix is created additionally. In that class for each input that does not have type information available
there will still be only the property with `_Untyped` suffix and nullability according to required status. For each
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ import io.github.typesafegithub.workflows.mavenbinding.TextArtifact
import io.github.typesafegithub.workflows.mavenbinding.buildVersionArtifacts
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.http.content.PartData
import io.ktor.http.content.asFlow
import io.ktor.http.parameters
import io.ktor.server.application.ApplicationCall
import io.ktor.server.request.httpMethod
import io.ktor.server.request.receiveMultipart
import io.ktor.server.request.receiveText
import io.ktor.server.response.respondBytes
import io.ktor.server.response.respondText
Expand All @@ -24,13 +27,20 @@ import io.ktor.server.routing.get
import io.ktor.server.routing.head
import io.ktor.server.routing.post
import io.ktor.server.routing.route
import io.ktor.utils.io.readRemaining
import io.ktor.utils.io.readText
import io.micrometer.core.instrument.Tag
import io.micrometer.core.instrument.binder.cache.CaffeineCacheMetrics
import io.micrometer.prometheusmetrics.PrometheusMeterRegistry
import it.krzeminski.snakeyaml.engine.kmp.api.Load
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.toList
import java.util.UUID.randomUUID
import kotlin.time.Duration.Companion.hours

private const val METADATA_PARAMETER = "actionYaml"
private const val TYPES_PARAMETER = "types"

private val logger = logger { }

typealias ArtifactResult = Result<Map<String, Artifact>>
Expand Down Expand Up @@ -109,17 +119,90 @@ private fun Route.postArtifact(prometheusRegistry: PrometheusMeterRegistry) {
val owner = "${call.parameters["owner"]}__types__${randomUUID()}"
val name = call.parameters["name"]!!
val version = call.parameters["version"]!!
val types = call.receiveText()

val (metadata, types) =
runCatching {
val parts =
call
.receiveMultipart()
.asFlow()
.map {
it.name to
runCatching {
when (it) {
is PartData.FileItem -> it.provider().readRemaining().readText()
is PartData.FormItem -> it.value
else -> {
logger.error { "Unexpected part data ${it::class.simpleName}" }
error("Unexpected part data ${it::class.simpleName}")
}
}
}
}.toList()
.map { (name, result) ->
name to
when {
result.isSuccess -> result.getOrThrow()
else -> {
call.respondText(
text = HttpStatusCode.InternalServerError.description,
status = HttpStatusCode.InternalServerError,
)
return@post
}
}
}.associate { it }

if (parts.keys.any { (it != METADATA_PARAMETER) && (it != TYPES_PARAMETER) }) {
call.respondText(
text = "Only '$METADATA_PARAMETER' and '$TYPES_PARAMETER' are allowed as form data fields",
status = HttpStatusCode.BadRequest,
)
return@post
}
if (!parts.containsKey(TYPES_PARAMETER)) {
call.respondText(
text = "'$TYPES_PARAMETER' field is mandatory",
status = HttpStatusCode.BadRequest,
)
return@post
}
parts[METADATA_PARAMETER] to parts[TYPES_PARAMETER]!!
}.recover {
null to call.receiveText()
}.getOrThrow()

if (metadata != null) {
if (metadata.isEmpty()) {
call.respondText(
text = "Supplied $METADATA_PARAMETER is empty",
status = HttpStatusCode.UnprocessableEntity,
)
return@post
}

runCatching {
Load().loadOne(metadata)
}.onFailure {
call.respondText(
text = "Exception while parsing supplied $METADATA_PARAMETER:\n${it.stackTraceToString()}",
status = HttpStatusCode.UnprocessableEntity,
)
return@post
}
}

runCatching {
Load().loadOne(types)
}.onFailure {
call.respondText(
text = "Exception while parsing supplied typings:\n${it.stackTraceToString()}",
text = "Exception while parsing supplied $TYPES_PARAMETER:\n${it.stackTraceToString()}",
status = HttpStatusCode.UnprocessableEntity,
)
return@post
}
call.toBindingArtifacts(refresh = true, owner = owner, types = types)

call.toBindingArtifacts(refresh = true, owner = owner, types = types, metadata = metadata)
call.respondText(text = "$owner:$name:$version")

incrementArtifactCounter(prometheusRegistry, call)
Expand All @@ -130,16 +213,29 @@ private suspend fun ApplicationCall.toBindingArtifacts(
refresh: Boolean,
owner: String = parameters["owner"]!!,
types: String? = null,
metadata: String? = null,
): Map<String, Artifact>? {
val actionCoords = parameters.extractActionCoords(extractVersion = true, owner = owner)

logger.info { "➡️ Requesting ${actionCoords.prettyPrint}" }
return if (refresh) {
actionCoords.buildVersionArtifacts(types ?: actionCoords.typesUuid?.let { "" }).also {
bindingsCache.put(actionCoords, runCatching { it!! })
}
actionCoords
.buildVersionArtifacts(
types ?: actionCoords.typesUuid?.let { "" },
metadata,
).also {
bindingsCache.put(actionCoords, runCatching { it!! })
}
} else {
bindingsCache.get(actionCoords) { runCatching { actionCoords.buildVersionArtifacts(types ?: actionCoords.typesUuid?.let { "" })!! } }.getOrNull()
bindingsCache
.get(actionCoords) {
runCatching {
actionCoords.buildVersionArtifacts(
types ?: actionCoords.typesUuid?.let { "" },
metadata,
)!!
}
}.getOrNull()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ internal data class Jars(
val sourcesJar: () -> ByteArray,
)

internal fun ActionCoords.buildJars(types: String?): Jars? {
internal fun ActionCoords.buildJars(
types: String?,
metadata: String?,
): Jars? {
val binding =
generateBinding(metadataRevision = NewestForVersion, types = types).also {
generateBinding(metadataRevision = NewestForVersion, types = types, explicitMetadata = metadata).also {
if (it.isEmpty()) return null
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ data class JarArtifact(
val data: () -> ByteArray,
) : Artifact

fun ActionCoords.buildVersionArtifacts(types: String? = null): Map<String, Artifact>? {
val jars = buildJars(types = types) ?: return null
fun ActionCoords.buildVersionArtifacts(
types: String? = null,
metadata: String? = null,
): Map<String, Artifact>? {
val jars = buildJars(types = types, metadata = metadata) ?: return null
val pom = buildPomFile()
val module = buildModuleFile()
return mapOf(
Expand Down

0 comments on commit aef49b1

Please sign in to comment.