From 371876a66195bc18bfe6908ddff4209e1fbf68e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kautler?= Date: Fri, 16 Aug 2024 12:07:43 +0200 Subject: [PATCH] feat(server): make it possible to POST custom typings for testing during typing creation --- .../api/action-binding-generator.api | 15 +- .../domain/ActionCoords.kt | 3 +- .../domain/TypingActualSource.kt | 1 + .../generation/Generation.kt | 10 +- .../typing/TypesProviding.kt | 12 +- .../typing/TypesProvidingTest.kt | 154 ++++++++++++++++++ docs/user-guide/using-actions.md | 12 ++ jit-binding-server/build.gradle.kts | 1 + .../jitbindingserver/ActionCoords.kt | 18 +- .../jitbindingserver/ArtifactRoutes.kt | 46 +++++- .../jitbindingserver/InternalRoutes.kt | 4 +- .../workflows/jitbindingserver/Main.kt | 2 +- .../jitbindingserver/MetadataRoutes.kt | 2 +- .../mavenbinding/ActionCoordsUtils.kt | 2 + .../workflows/mavenbinding/JarBuilding.kt | 4 +- .../mavenbinding/MavenMetadataBuilding.kt | 2 +- .../workflows/mavenbinding/ModuleBuilding.kt | 2 +- .../workflows/mavenbinding/PomBuilding.kt | 2 +- .../mavenbinding/VersionArtifactsBuilding.kt | 4 +- 19 files changed, 261 insertions(+), 35 deletions(-) diff --git a/action-binding-generator/api/action-binding-generator.api b/action-binding-generator/api/action-binding-generator.api index d587f20c3e..d9c96a4176 100644 --- a/action-binding-generator/api/action-binding-generator.api +++ b/action-binding-generator/api/action-binding-generator.api @@ -1,18 +1,20 @@ public final class io/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/SignificantVersion;Ljava/lang/String;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/SignificantVersion;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/SignificantVersion;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/SignificantVersion;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/SignificantVersion; public final fun component5 ()Ljava/lang/String; - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/SignificantVersion;Ljava/lang/String;)Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords; - public static synthetic fun copy$default (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/SignificantVersion;Ljava/lang/String;ILjava/lang/Object;)Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords; + public final fun component6 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/SignificantVersion;Ljava/lang/String;Ljava/lang/String;)Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords; + public static synthetic fun copy$default (Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/SignificantVersion;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords; public fun equals (Ljava/lang/Object;)Z public final fun getName ()Ljava/lang/String; public final fun getOwner ()Ljava/lang/String; public final fun getPath ()Ljava/lang/String; public final fun getSignificantVersion ()Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/SignificantVersion; + public final fun getTypesUuid ()Ljava/lang/String; public final fun getVersion ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; @@ -58,6 +60,7 @@ public final class io/github/typesafegithub/workflows/actionbindinggenerator/dom public final class io/github/typesafegithub/workflows/actionbindinggenerator/domain/TypingActualSource : java/lang/Enum { public static final field ACTION Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/TypingActualSource; + public static final field CUSTOM Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/TypingActualSource; public static final field TYPING_CATALOG Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/TypingActualSource; public static fun getEntries ()Lkotlin/enums/EnumEntries; public static fun valueOf (Ljava/lang/String;)Lio/github/typesafegithub/workflows/actionbindinggenerator/domain/TypingActualSource; @@ -84,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/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;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/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 final class io/github/typesafegithub/workflows/actionbindinggenerator/metadata/Input { diff --git a/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords.kt b/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords.kt index ad776d2c58..4d42bd51e3 100644 --- a/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords.kt +++ b/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/domain/ActionCoords.kt @@ -15,6 +15,7 @@ public data class ActionCoords( */ val significantVersion: SignificantVersion = FULL, val path: String? = null, + val typesUuid: String? = null, ) /** @@ -25,7 +26,7 @@ public val ActionCoords.isTopLevel: Boolean get() = path == null public val ActionCoords.prettyPrint: String get() = "$owner/$fullName${ significantVersion.takeUnless { it == FULL }?.let { " with $it version" } ?: "" -}@$version" +}@$version${typesUuid?.let { " (types: $it)" } ?: ""}" /** * For most actions, it's empty. diff --git a/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/domain/TypingActualSource.kt b/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/domain/TypingActualSource.kt index 094f3251b4..4a6fca4f9e 100644 --- a/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/domain/TypingActualSource.kt +++ b/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/domain/TypingActualSource.kt @@ -3,4 +3,5 @@ package io.github.typesafegithub.workflows.actionbindinggenerator.domain public enum class TypingActualSource { ACTION, TYPING_CATALOG, + CUSTOM, } diff --git a/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/Generation.kt b/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/Generation.kt index 6af3e5c84d..f48580eda2 100644 --- a/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/Generation.kt +++ b/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/generation/Generation.kt @@ -64,11 +64,13 @@ public fun ActionCoords.generateBinding( metadataRevision: MetadataRevision, metadata: Metadata? = null, inputTypings: Pair, TypingActualSource?>? = null, + types: String? = null, ): List { val metadataResolved = metadata ?: this.fetchMetadata(metadataRevision) ?: return emptyList() val metadataProcessed = metadataResolved.removeDeprecatedInputsIfNameClash() - val inputTypingsResolved = inputTypings ?: this.provideTypes(metadataRevision) + val (inputTypingsResolved, typingActualSource) = + inputTypings ?: this.provideTypes(metadataRevision, types = types) val packageName = owner.toKotlinPackageName() val className = this.buildActionClassName() @@ -81,7 +83,7 @@ public fun ActionCoords.generateBinding( emptyMap(), classNameUntyped, untypedClass = true, - replaceWith = inputTypingsResolved.second?.let { CodeBlock.of("ReplaceWith(%S)", className) }, + replaceWith = typingActualSource?.let { CodeBlock.of("ReplaceWith(%S)", className) }, ) return listOfNotNull( @@ -92,12 +94,12 @@ public fun ActionCoords.generateBinding( packageName = packageName, typingActualSource = null, ), - inputTypingsResolved.second?.let { + typingActualSource?.let { val actionBindingSourceCode = generateActionBindingSourceCode( metadata = metadataProcessed, coords = this, - inputTypings = inputTypingsResolved.first, + inputTypings = inputTypingsResolved, className = className, ) ActionBinding( diff --git a/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/typing/TypesProviding.kt b/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/typing/TypesProviding.kt index 46e6eb6344..63477a3860 100644 --- a/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/typing/TypesProviding.kt +++ b/action-binding-generator/src/main/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/typing/TypesProviding.kt @@ -10,6 +10,7 @@ import io.github.typesafegithub.workflows.actionbindinggenerator.domain.Metadata import io.github.typesafegithub.workflows.actionbindinggenerator.domain.NewestForVersion import io.github.typesafegithub.workflows.actionbindinggenerator.domain.TypingActualSource import io.github.typesafegithub.workflows.actionbindinggenerator.domain.TypingActualSource.ACTION +import io.github.typesafegithub.workflows.actionbindinggenerator.domain.TypingActualSource.CUSTOM import io.github.typesafegithub.workflows.actionbindinggenerator.domain.TypingActualSource.TYPING_CATALOG import io.github.typesafegithub.workflows.actionbindinggenerator.domain.subName import io.github.typesafegithub.workflows.actionbindinggenerator.metadata.fetchUri @@ -24,10 +25,12 @@ private val logger = logger { } internal fun ActionCoords.provideTypes( metadataRevision: MetadataRevision, fetchUri: (URI) -> String = ::fetchUri, + types: String? = null, ): Pair, TypingActualSource?> = ( - this.fetchTypingMetadata(metadataRevision, fetchUri) - ?: this.toMajorVersion().fetchFromTypingsFromCatalog(fetchUri) + customTypingMetadata(types) + ?: this.fetchTypingMetadata(metadataRevision, fetchUri) + ?: this.toMajorVersion().fetchTypingsFromCatalog(fetchUri) )?.let { Pair(it.first.toTypesMap(), it.second) } ?: Pair(emptyMap(), null) @@ -45,6 +48,9 @@ private fun ActionCoords.catalogMetadata() = private fun ActionCoords.actionTypesYamlUrl(gitRef: String) = "https://raw.githubusercontent.com/$owner/$name/$gitRef$subName/action-types.yaml" +private fun customTypingMetadata(types: String? = null) = + types?.let { Pair(yaml.decodeFromStringOrDefaultIfEmpty(it, ActionTypes()), CUSTOM) } + private fun ActionCoords.fetchTypingMetadata( metadataRevision: MetadataRevision, fetchUri: (URI) -> String = ::fetchUri, @@ -68,7 +74,7 @@ private fun ActionCoords.fetchTypingMetadata( return Pair(yaml.decodeFromStringOrDefaultIfEmpty(typesMetadataYaml, ActionTypes()), ACTION) } -private fun ActionCoords.fetchFromTypingsFromCatalog(fetchUri: (URI) -> String = ::fetchUri): Pair? = +private fun ActionCoords.fetchTypingsFromCatalog(fetchUri: (URI) -> String = ::fetchUri): Pair? = ( fetchTypingsFromUrl(url = actionTypesFromCatalog(), fetchUri = fetchUri) ?: fetchTypingsForOlderVersionFromCatalog(fetchUri = fetchUri) diff --git a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/typing/TypesProvidingTest.kt b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/typing/TypesProvidingTest.kt index 4d56781e1a..8218bcd8e3 100644 --- a/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/typing/TypesProvidingTest.kt +++ b/action-binding-generator/src/test/kotlin/io/github/typesafegithub/workflows/actionbindinggenerator/typing/TypesProvidingTest.kt @@ -152,6 +152,12 @@ class TypesProvidingTest : stored-in-typing-catalog: type: string """.trimIndent() + val custom = + """ + inputs: + custom: + type: string + """.trimIndent() val metadata = """ "versionsWithTypings": @@ -376,6 +382,134 @@ class TypesProvidingTest : types shouldBe Pair(mapOf("hosted-by-action-yml" to StringTyping), TypingActualSource.ACTION) } + test("only custom") { + // Given + val fetchUri: (URI) -> String = { throw IOException() } + val actionCoord = ActionCoords("some-owner", "some-name", "v3") + + // When + val types = actionCoord.provideTypes(metadataRevision = CommitHash("some-hash"), fetchUri = fetchUri, types = custom) + + // Then + types shouldBe Pair(mapOf("custom" to StringTyping), TypingActualSource.CUSTOM) + } + + test("only custom for subaction") { + // Given + val fetchUri: (URI) -> String = { throw IOException() } + val actionCoord = ActionCoords("some-owner", "some-name", "v3", "some-sub") + + // When + val types = actionCoord.provideTypes(metadataRevision = CommitHash("some-hash"), fetchUri = fetchUri, types = custom) + + // Then + types shouldBe Pair(mapOf("custom" to StringTyping), TypingActualSource.CUSTOM) + } + + test("hosted by action, stored in typing catalog, and custom") { + // Given + val fetchUri: (URI) -> String = { + when (it) { + URI( + "https://raw.githubusercontent.com/some-owner/some-name/" + + "some-hash/action-types.yml", + ), + -> hostedByActionYml + URI( + "https://raw.githubusercontent.com/typesafegithub/github-actions-typing-catalog/" + + "main/typings/some-owner/some-name/v3/action-types.yml", + ), + -> storedInTypingCatalog + else -> throw IOException() + } + } + val actionCoord = ActionCoords("some-owner", "some-name", "v3") + + // When + val types = actionCoord.provideTypes(metadataRevision = CommitHash("some-hash"), fetchUri = fetchUri, types = custom) + + // Then + types shouldBe Pair(mapOf("custom" to StringTyping), TypingActualSource.CUSTOM) + } + + test("hosted by subaction, stored in typing catalog, and custom") { + // Given + val fetchUri: (URI) -> String = { + when (it) { + URI( + "https://raw.githubusercontent.com/some-owner/some-name/" + + "some-hash/some-sub/action-types.yml", + ), + -> hostedByActionYml + URI( + "https://raw.githubusercontent.com/typesafegithub/github-actions-typing-catalog/" + + "main/typings/some-owner/some-name/v3/some-sub/action-types.yml", + ), + -> storedInTypingCatalog + else -> throw IOException() + } + } + val actionCoord = ActionCoords("some-owner", "some-name", "v3", "some-sub") + + // When + val types = actionCoord.provideTypes(metadataRevision = CommitHash("some-hash"), fetchUri = fetchUri, types = custom) + + // Then + types shouldBe Pair(mapOf("custom" to StringTyping), TypingActualSource.CUSTOM) + } + + test("hosted by action, stored in typing catalog, and empty custom") { + // Given + val fetchUri: (URI) -> String = { + when (it) { + URI( + "https://raw.githubusercontent.com/some-owner/some-name/" + + "some-hash/action-types.yml", + ), + -> hostedByActionYml + URI( + "https://raw.githubusercontent.com/typesafegithub/github-actions-typing-catalog/" + + "main/typings/some-owner/some-name/v3/action-types.yml", + ), + -> storedInTypingCatalog + else -> throw IOException() + } + } + val actionCoord = ActionCoords("some-owner", "some-name", "v3") + + // When + val types = actionCoord.provideTypes(metadataRevision = CommitHash("some-hash"), fetchUri = fetchUri, types = "") + + // Then + types shouldBe Pair(emptyMap(), TypingActualSource.CUSTOM) + } + + test("hosted by subaction, stored in typing catalog, and empty custom") { + // Given + val fetchUri: (URI) -> String = { + when (it) { + URI( + "https://raw.githubusercontent.com/some-owner/some-name/" + + "some-hash/some-sub/action-types.yml", + ), + -> hostedByActionYml + URI( + "https://raw.githubusercontent.com/typesafegithub/github-actions-typing-catalog/" + + "main/typings/some-owner/some-name/v3/some-sub/action-types.yml", + ), + -> storedInTypingCatalog + else -> throw IOException() + } + } + val actionCoord = ActionCoords("some-owner", "some-name", "v3", "some-sub") + + // When + val types = actionCoord.provideTypes(metadataRevision = CommitHash("some-hash"), fetchUri = fetchUri, types = "") + + // Then + types shouldBe Pair(emptyMap(), TypingActualSource.CUSTOM) + } + test("only stored in typing catalog for older version") { // Given val fetchUri: (URI) -> String = { @@ -576,6 +710,26 @@ class TypesProvidingTest : ) } + test("only custom") { + // Given + val fetchUri: (URI) -> String = { throw IOException() } + val actionCoord = ActionCoords("some-owner", "some-name", "v3") + + // When + val types = actionCoord.provideTypes(metadataRevision = CommitHash("some-hash"), fetchUri = fetchUri, types = typingYml) + + // Then + types shouldBe + Pair( + mapOf( + "granted-scopes" to ListOfTypings(",", EnumTyping("GrantedScopes", listOf("read", "write"))), + "granted-scopes2" to ListOfTypings(",", EnumTyping("GrantedScopes", listOf("read", "write"))), + "granted-scopes3" to ListOfTypings("""\n""", EnumTyping("GrantedScopes", listOf("read", "write"))), + ), + TypingActualSource.CUSTOM, + ) + } + test("billion laughs attack is prevented") { // Given val billionLaughsAttack = diff --git a/docs/user-guide/using-actions.md b/docs/user-guide/using-actions.md index f88ea7208b..3f8a59dfa3 100644 --- a/docs/user-guide/using-actions.md +++ b/docs/user-guide/using-actions.md @@ -74,6 +74,18 @@ There are two ways of configuring typings: a community-maintained place to host the typings. You can contribute or fix typings for your favorite action by sending a PR. +While developing a typing manifest it might be a good idea to test the result without needing to +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 +``` +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. + 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 diff --git a/jit-binding-server/build.gradle.kts b/jit-binding-server/build.gradle.kts index 40ec12e4c2..f6d0477764 100644 --- a/jit-binding-server/build.gradle.kts +++ b/jit-binding-server/build.gradle.kts @@ -16,6 +16,7 @@ dependencies { implementation("io.ktor:ktor-serialization-kotlinx-json") implementation("io.ktor:ktor-server-call-logging") implementation("io.ktor:ktor-server-call-id") + implementation("it.krzeminski:snakeyaml-engine-kmp:3.0.2") implementation("io.ktor:ktor-server-metrics-micrometer") implementation("io.micrometer:micrometer-registry-prometheus:1.14.4") diff --git a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ActionCoords.kt b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ActionCoords.kt index 125f567649..066fe95950 100644 --- a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ActionCoords.kt +++ b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ActionCoords.kt @@ -5,8 +5,10 @@ import io.github.typesafegithub.workflows.actionbindinggenerator.domain.Signific import io.github.typesafegithub.workflows.actionbindinggenerator.domain.SignificantVersion.FULL import io.ktor.http.Parameters -fun Parameters.extractActionCoords(extractVersion: Boolean): ActionCoords { - val owner = this["owner"]!! +fun Parameters.extractActionCoords( + extractVersion: Boolean, + owner: String = this["owner"]!!, +): ActionCoords { val nameAndPathAndSignificantVersionParts = this["name"]!!.split("___", limit = 2) val nameAndPath = nameAndPathAndSignificantVersionParts.first() val significantVersion = @@ -27,6 +29,16 @@ fun Parameters.extractActionCoords(extractVersion: Boolean): ActionCoords { .joinToString("/") .takeUnless { it.isBlank() } val version = if (extractVersion) this["version"]!! else "irrelevant" + // we cannot give the types UUID separately from the post handler + // only in the post handler we generate the UUID, but for the other + // handlers the UUID part is already coming through the request as part of the owner + val ownerAndTypesUuid = owner.split("__types__", limit = 2) + val ownerPlain = ownerAndTypesUuid.first() + val typesUuid = + ownerAndTypesUuid + .drop(1) + .takeIf { it.isNotEmpty() } + ?.single() - return ActionCoords(owner, name, version, significantVersion, path) + return ActionCoords(ownerPlain, name, version, significantVersion, path, typesUuid) } diff --git a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ArtifactRoutes.kt b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ArtifactRoutes.kt index 9660352f48..38e338bb5a 100644 --- a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ArtifactRoutes.kt +++ b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/ArtifactRoutes.kt @@ -12,18 +12,23 @@ 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.parameters import io.ktor.server.application.ApplicationCall import io.ktor.server.request.httpMethod +import io.ktor.server.request.receiveText import io.ktor.server.response.respondBytes import io.ktor.server.response.respondText import io.ktor.server.routing.Route import io.ktor.server.routing.Routing 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.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 java.util.UUID.randomUUID import kotlin.time.Duration.Companion.hours private val logger = logger { } @@ -55,6 +60,7 @@ private fun Route.artifact( ) { headArtifact(prometheusRegistry, refresh) getArtifact(prometheusRegistry, refresh) + postArtifact(prometheusRegistry) } private fun Route.headArtifact( @@ -67,7 +73,7 @@ private fun Route.headArtifact( val file = call.parameters["file"] ?: return@head call.respondNotFound() if (file in bindingArtifacts) { - call.respondText("Exists", status = HttpStatusCode.OK) + call.respondText(text = "Exists", status = HttpStatusCode.OK) } else { call.respondNotFound() } @@ -83,14 +89,14 @@ private fun Route.getArtifact( get { val bindingArtifacts = call.toBindingArtifacts(refresh) ?: return@get call.respondNotFound() - if (refresh && !deliverOnRefreshRoute) return@get call.respondText("OK") + if (refresh && !deliverOnRefreshRoute) return@get call.respondText(text = "OK") val file = call.parameters["file"] ?: return@get call.respondNotFound() val artifact = bindingArtifacts[file] ?: return@get call.respondNotFound() when (artifact) { - is TextArtifact -> call.respondText(artifact.data()) + is TextArtifact -> call.respondText(text = artifact.data()) is JarArtifact -> call.respondBytes(artifact.data(), ContentType.parse("application/java-archive")) } @@ -98,16 +104,42 @@ private fun Route.getArtifact( } } -private suspend fun ApplicationCall.toBindingArtifacts(refresh: Boolean): Map? { - val actionCoords = parameters.extractActionCoords(extractVersion = true) +private fun Route.postArtifact(prometheusRegistry: PrometheusMeterRegistry) { + post { + val owner = "${call.parameters["owner"]}__types__${randomUUID()}" + val name = call.parameters["name"]!! + val version = call.parameters["version"]!! + val types = call.receiveText() + runCatching { + Load().loadOne(types) + }.onFailure { + call.respondText( + text = "Exception while parsing supplied typings:\n${it.stackTraceToString()}", + status = HttpStatusCode.UnprocessableEntity, + ) + return@post + } + call.toBindingArtifacts(refresh = true, owner = owner, types = types) + call.respondText(text = "$owner:$name:$version") + + incrementArtifactCounter(prometheusRegistry, call) + } +} + +private suspend fun ApplicationCall.toBindingArtifacts( + refresh: Boolean, + owner: String = parameters["owner"]!!, + types: String? = null, +): Map? { + val actionCoords = parameters.extractActionCoords(extractVersion = true, owner = owner) logger.info { "➡️ Requesting ${actionCoords.prettyPrint}" } return if (refresh) { - actionCoords.buildVersionArtifacts().also { + actionCoords.buildVersionArtifacts(types ?: actionCoords.typesUuid?.let { "" }).also { bindingsCache.put(actionCoords, runCatching { it!! }) } } else { - bindingsCache.get(actionCoords) { runCatching { actionCoords.buildVersionArtifacts()!! } }.getOrNull() + bindingsCache.get(actionCoords) { runCatching { actionCoords.buildVersionArtifacts(types ?: actionCoords.typesUuid?.let { "" })!! } }.getOrNull() } } diff --git a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/InternalRoutes.kt b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/InternalRoutes.kt index 36147ad5c7..e868db0833 100644 --- a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/InternalRoutes.kt +++ b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/InternalRoutes.kt @@ -7,10 +7,10 @@ import io.micrometer.prometheusmetrics.PrometheusMeterRegistry fun Routing.internalRoutes(prometheusRegistry: PrometheusMeterRegistry) { get("/metrics") { - call.respondText(prometheusRegistry.scrape()) + call.respondText(text = prometheusRegistry.scrape()) } get("/status") { - call.respondText("OK") + call.respondText(text = "OK") } } diff --git a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/Main.kt b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/Main.kt index 99370249b3..5c5b7c2d58 100644 --- a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/Main.kt +++ b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/Main.kt @@ -51,4 +51,4 @@ fun main() { val deliverOnRefreshRoute = System.getenv("GWKT_DELIVER_ON_REFRESH").toBoolean() -suspend fun ApplicationCall.respondNotFound() = respondText("Not found", status = HttpStatusCode.NotFound) +suspend fun ApplicationCall.respondNotFound() = respondText(text = "Not found", status = HttpStatusCode.NotFound) diff --git a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/MetadataRoutes.kt b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/MetadataRoutes.kt index 040c36a172..631ee5f7f9 100644 --- a/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/MetadataRoutes.kt +++ b/jit-binding-server/src/main/kotlin/io/github/typesafegithub/workflows/jitbindingserver/MetadataRoutes.kt @@ -29,7 +29,7 @@ private fun Route.metadata(refresh: Boolean = false) { val bindingArtifacts = actionCoords.buildPackageArtifacts(githubToken = getGithubToken()) if (file in bindingArtifacts) { when (val artifact = bindingArtifacts[file]) { - is String -> call.respondText(artifact) + is String -> call.respondText(text = artifact) else -> call.respondText(text = "Not found", status = HttpStatusCode.NotFound) } } else { diff --git a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/ActionCoordsUtils.kt b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/ActionCoordsUtils.kt index 33dcb41f46..431d31b03c 100644 --- a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/ActionCoordsUtils.kt +++ b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/ActionCoordsUtils.kt @@ -4,6 +4,8 @@ import io.github.typesafegithub.workflows.actionbindinggenerator.domain.ActionCo import io.github.typesafegithub.workflows.actionbindinggenerator.domain.SignificantVersion.FULL import io.github.typesafegithub.workflows.actionbindinggenerator.domain.subName +internal val ActionCoords.mavenGroup: String get() = "$owner${typesUuid?.let { "__types__$it" } ?: ""}" + internal val ActionCoords.mavenName: String get() = "$name${subName.replace("/", "__")}${ significantVersion.takeUnless { it == FULL }?.let { "___$it" } ?: "" }" diff --git a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/JarBuilding.kt b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/JarBuilding.kt index 0c7a3fe618..2a1e525dbe 100644 --- a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/JarBuilding.kt +++ b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/JarBuilding.kt @@ -26,9 +26,9 @@ internal data class Jars( val sourcesJar: () -> ByteArray, ) -internal fun ActionCoords.buildJars(): Jars? { +internal fun ActionCoords.buildJars(types: String?): Jars? { val binding = - generateBinding(metadataRevision = NewestForVersion).also { + generateBinding(metadataRevision = NewestForVersion, types = types).also { if (it.isEmpty()) return null } diff --git a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/MavenMetadataBuilding.kt b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/MavenMetadataBuilding.kt index d94d3d4d2e..1320d34653 100644 --- a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/MavenMetadataBuilding.kt +++ b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/MavenMetadataBuilding.kt @@ -21,7 +21,7 @@ internal suspend fun ActionCoords.buildMavenMetadataFile( return """ - $owner + $mavenGroup $mavenName $newest diff --git a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/ModuleBuilding.kt b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/ModuleBuilding.kt index 76dbbda419..3507c8549a 100644 --- a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/ModuleBuilding.kt +++ b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/ModuleBuilding.kt @@ -7,7 +7,7 @@ internal fun ActionCoords.buildModuleFile() = { "formatVersion": "1.1", "component": { - "group": "$owner", + "group": "$mavenGroup", "module": "$mavenName", "version": "$version", "attributes": { diff --git a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/PomBuilding.kt b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/PomBuilding.kt index cf7f0e4205..ddfeddaaac 100644 --- a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/PomBuilding.kt +++ b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/PomBuilding.kt @@ -11,7 +11,7 @@ internal fun ActionCoords.buildPomFile() = 4.0.0 - $owner + $mavenGroup $mavenName $version $fullName diff --git a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/VersionArtifactsBuilding.kt b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/VersionArtifactsBuilding.kt index aef17b35be..d0c3f80e30 100644 --- a/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/VersionArtifactsBuilding.kt +++ b/maven-binding-builder/src/main/kotlin/io/github/typesafegithub/workflows/mavenbinding/VersionArtifactsBuilding.kt @@ -12,8 +12,8 @@ data class JarArtifact( val data: () -> ByteArray, ) : Artifact -fun ActionCoords.buildVersionArtifacts(): Map? { - val jars = buildJars() ?: return null +fun ActionCoords.buildVersionArtifacts(types: String? = null): Map? { + val jars = buildJars(types = types) ?: return null val pom = buildPomFile() val module = buildModuleFile() return mapOf(