migrate docker-compose convention plugin out of buildSrc
This commit is contained in:
parent
105555bf6c
commit
f804c39a3b
32 changed files with 564 additions and 239 deletions
|
|
@ -1,140 +0,0 @@
|
|||
package dev.adamko.kafkatorio.factoriomod.portal
|
||||
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.cio.CIO
|
||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||
import io.ktor.client.plugins.defaultRequest
|
||||
import io.ktor.client.plugins.logging.LogLevel
|
||||
import io.ktor.client.request.forms.MultiPartFormDataContent
|
||||
import io.ktor.client.request.forms.formData
|
||||
import io.ktor.client.request.forms.submitForm
|
||||
import io.ktor.client.request.header
|
||||
import io.ktor.client.request.post
|
||||
import io.ktor.client.request.setBody
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.Headers
|
||||
import io.ktor.http.HttpHeaders
|
||||
import io.ktor.http.Parameters
|
||||
import io.ktor.http.append
|
||||
import io.ktor.http.isSuccess
|
||||
import io.ktor.serialization.kotlinx.json.json
|
||||
import java.io.File
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.gradle.api.internal.tasks.userinput.UserInputHandler
|
||||
import org.gradle.kotlin.dsl.support.useToRun
|
||||
import io.ktor.client.plugins.logging.Logger as KtorLogger
|
||||
import io.ktor.client.plugins.logging.Logging as KtorLogging
|
||||
import org.gradle.api.logging.Logger as GradleLogger
|
||||
import org.gradle.api.logging.Logging as GradleLogging
|
||||
|
||||
|
||||
class FactorioModPortalPublishClient(
|
||||
private val userInputHandler: UserInputHandler,
|
||||
private val distributionZip: File,
|
||||
private val modName: String,
|
||||
private val modVersion: String,
|
||||
private val portalApiKey: String,
|
||||
private val portalUploadEndpoint: String,
|
||||
private val modPortalBaseURl: String,
|
||||
) {
|
||||
|
||||
private val logger: GradleLogger = GradleLogging.getLogger(this::class.java)
|
||||
|
||||
private fun client() = HttpClient(CIO) {
|
||||
install(KtorLogging) {
|
||||
logger = object : KtorLogger {
|
||||
override fun log(message: String) {
|
||||
this@FactorioModPortalPublishClient.logger.debug(message)
|
||||
}
|
||||
}
|
||||
level = LogLevel.ALL
|
||||
}
|
||||
install(ContentNegotiation) {
|
||||
json(Json {
|
||||
prettyPrint = false
|
||||
isLenient = true
|
||||
})
|
||||
}
|
||||
defaultRequest {
|
||||
header(HttpHeaders.Authorization, "Bearer $portalApiKey")
|
||||
port = 443
|
||||
}
|
||||
followRedirects = false
|
||||
expectSuccess = false
|
||||
}
|
||||
|
||||
fun uploadMod(): Unit = runBlocking {
|
||||
client().useToRun {
|
||||
val initUploadResponse = initUpload()
|
||||
|
||||
val enteredVersion = userInputHandler
|
||||
.askQuestion(
|
||||
"""
|
||||
|Are you sure you want to publish $modName:$modVersion?
|
||||
|Enter the version number to confirm:
|
||||
""".trimMargin(),
|
||||
"",
|
||||
)
|
||||
|
||||
val confirmed = enteredVersion.trim().equals(modVersion.trim(), ignoreCase = true)
|
||||
|
||||
if (confirmed) {
|
||||
upload(initUploadResponse)
|
||||
} else {
|
||||
logger.lifecycle("aborting mod '$modName' upload")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun HttpClient.initUpload(): InitUploadResponse.Success {
|
||||
|
||||
val response = submitForm(
|
||||
url = portalUploadEndpoint,
|
||||
formParameters = Parameters.build {
|
||||
append("mod", modName)
|
||||
}
|
||||
)
|
||||
|
||||
// println(response)
|
||||
val initUploadResponse: InitUploadResponse =
|
||||
Json.decodeFromString(InitUploadResponse.serializer(), response.bodyAsText())
|
||||
// println(initUploadResponse)
|
||||
|
||||
require(response.status.isSuccess()) { "init upload request failed" }
|
||||
|
||||
return when (initUploadResponse) {
|
||||
is Failure -> error(initUploadResponse)
|
||||
is InitUploadResponse.Success -> initUploadResponse
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun HttpClient.upload(initUploadResponse: InitUploadResponse.Success) {
|
||||
|
||||
val requestForm = formData {
|
||||
append(
|
||||
"file",
|
||||
distributionZip.readBytes(),
|
||||
Headers.build {
|
||||
append(HttpHeaders.ContentType, ContentType.Application.Zip)
|
||||
append(HttpHeaders.ContentDisposition, "filename=${distributionZip.name}")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val response = post(initUploadResponse.uploadUrl) {
|
||||
setBody(MultiPartFormDataContent(requestForm))
|
||||
}
|
||||
|
||||
val submitUploadResponse =
|
||||
Json.decodeFromString(SubmitUploadResponse.serializer(), response.bodyAsText())
|
||||
|
||||
// logger(response)
|
||||
// println(submitUploadResponse)
|
||||
require(response.status.isSuccess() && submitUploadResponse is SubmitUploadResponse.Success) {
|
||||
"upload request failed"
|
||||
}
|
||||
logger.lifecycle("Mod '$modName' uploaded successfully! ${modPortalBaseURl.removeSuffix("/")}/$modName")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
package dev.adamko.kafkatorio.factoriomod.portal
|
||||
|
||||
import dev.adamko.kafkatorio.factoriomod.FactorioMod
|
||||
import io.ktor.http.URLBuilder
|
||||
import io.ktor.http.takeFrom
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.internal.tasks.userinput.UserInputHandler
|
||||
import org.gradle.api.model.ObjectFactory
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.provider.ProviderFactory
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import org.gradle.kotlin.dsl.property
|
||||
import org.gradle.kotlin.dsl.support.get
|
||||
|
||||
|
||||
abstract class FactorioModPublishTask @Inject constructor(
|
||||
objects: ObjectFactory,
|
||||
providers: ProviderFactory,
|
||||
) : DefaultTask() {
|
||||
|
||||
@get:InputFile
|
||||
abstract val distributionZip: RegularFileProperty
|
||||
|
||||
@get:Input
|
||||
abstract val modName: Property<String>
|
||||
|
||||
@get:Input
|
||||
abstract val modVersion: Property<String>
|
||||
|
||||
// @get:Input
|
||||
// abstract val factorioVersion: Property<String>
|
||||
|
||||
/** Bearer API key */
|
||||
@get:Input
|
||||
val portalApiKey: Property<String> = objects.property<String>().convention(
|
||||
providers.gradleProperty("FACTORIO_MOD_PORTAL_API_KEY")
|
||||
)
|
||||
|
||||
@get:Input
|
||||
val portalApiUrl: Property<String> = objects.property<String>()
|
||||
.convention("https://mods.factorio.com/api/v2/")
|
||||
|
||||
/**
|
||||
* Base URL for linking to a mod page in the portal
|
||||
*
|
||||
* Example `https://mods.factorio.com/mod/my-mod-name`
|
||||
*/
|
||||
@get:Input
|
||||
val modPortalBaseURl: Property<String> = objects.property<String>()
|
||||
.convention("https://mods.factorio.com/mod/")
|
||||
|
||||
@get:Input
|
||||
val portalUploadEndpoint: Property<String> = objects.property<String>()
|
||||
.convention("mods/releases/init_upload")
|
||||
|
||||
|
||||
init {
|
||||
group = FactorioMod.TASK_GROUP
|
||||
}
|
||||
|
||||
|
||||
@TaskAction
|
||||
fun deploy(): Unit = runBlocking {
|
||||
|
||||
val userInputHandler = services.get<UserInputHandler>()
|
||||
|
||||
val portalUploadEndpoint = URLBuilder(portalApiUrl.get())
|
||||
.takeFrom(portalUploadEndpoint.get())
|
||||
.buildString()
|
||||
|
||||
val client = FactorioModPortalPublishClient(
|
||||
userInputHandler = userInputHandler,
|
||||
distributionZip = distributionZip.get().asFile,
|
||||
modName = modName.get(),
|
||||
modVersion = modVersion.get(),
|
||||
portalApiKey = portalApiKey.get(),
|
||||
portalUploadEndpoint = portalUploadEndpoint,
|
||||
modPortalBaseURl = modPortalBaseURl.get(),
|
||||
)
|
||||
|
||||
client.uploadMod()
|
||||
}
|
||||
|
||||
// private fun validateInfoJson() {
|
||||
// val modVersion = modVersion.get()
|
||||
// val factorioVersion = factorioVersion.get()
|
||||
//
|
||||
// val distributionZip = distributionZip.get().asFile.toOkioPath()
|
||||
//
|
||||
// val zipContents = FileSystem.SYSTEM.openZip(distributionZip)
|
||||
//
|
||||
// val infoJson = zipContents.read("info.json".toPath()) {
|
||||
// generateSequence { readUtf8Line() }
|
||||
// }.joinToString("\n")
|
||||
//
|
||||
// val info: FactorioModInfo = Json.decodeFromString(FactorioModInfo.serializer(), infoJson)
|
||||
//
|
||||
// require(info.version == modVersion) {
|
||||
// "mod version mismatch, ${info.version} vs $modVersion "
|
||||
// }
|
||||
//
|
||||
// require(info.factorioVersion == factorioVersion) {
|
||||
// "Factorio version mismatch, ${info.factorioVersion} vs $factorioVersion "
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
package dev.adamko.kafkatorio.factoriomod.portal
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
|
||||
|
||||
@Serializable(with = InitUploadResponse.Serializer::class)
|
||||
sealed interface InitUploadResponse {
|
||||
|
||||
/**
|
||||
* @param[uploadUrl] URL the mod zip file should be uploaded to
|
||||
*/
|
||||
@Serializable
|
||||
data class Success(
|
||||
@SerialName("upload_url")
|
||||
val uploadUrl: String,
|
||||
) : InitUploadResponse
|
||||
|
||||
object Serializer :
|
||||
JsonContentPolymorphicSerializer<InitUploadResponse>(InitUploadResponse::class) {
|
||||
override fun selectDeserializer(element: JsonElement) = when {
|
||||
"upload_url" in element.jsonObject -> Success.serializer()
|
||||
else -> Failure.serializer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Serializable(with = SubmitUploadResponse.Serializer::class)
|
||||
sealed interface SubmitUploadResponse {
|
||||
|
||||
@Serializable
|
||||
data class Success(
|
||||
val success: Boolean
|
||||
) : SubmitUploadResponse
|
||||
|
||||
object Serializer : JsonContentPolymorphicSerializer<SubmitUploadResponse>(
|
||||
SubmitUploadResponse::class
|
||||
) {
|
||||
override fun selectDeserializer(element: JsonElement) = when {
|
||||
"success" in element.jsonObject -> Success.serializer()
|
||||
else -> Failure.serializer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
data class Failure(
|
||||
val error: String? = null,
|
||||
val message: String? = null,
|
||||
) : InitUploadResponse, SubmitUploadResponse {
|
||||
|
||||
private val reason: String?
|
||||
get() = reasons[error]
|
||||
|
||||
override fun toString() = "Failure(error=$error, message=$message, reason=$reason)"
|
||||
|
||||
companion object {
|
||||
private val reasons: Map<String?, String> = mapOf(
|
||||
//@formatter:off
|
||||
"InvalidApiKey" to "Missing or invalid API key for the current endpoint",
|
||||
"InvalidRequest" to "Invalid request",
|
||||
"InternalError" to "Internal error, please try again later.",
|
||||
"Forbidden" to "Insufficient permission for current endpoint",
|
||||
"Unknown" to "Unknown error, please try again later",
|
||||
"InvalidModRelease" to "Invalid release data in info.json",
|
||||
"InvalidModUpload" to "Invalid mod data in zipfile",
|
||||
"UnknownMod" to "Mod does not exist in mod portal",
|
||||
//@formatter:on
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
data class FactorioModInfo(
|
||||
val name: String,
|
||||
val version: String,
|
||||
val title: String,
|
||||
val description: String,
|
||||
val author: String,
|
||||
@SerialName("factorio_version") val factorioVersion: String,
|
||||
val dependencies: List<String>,
|
||||
)
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package dev.adamko.kafkatorio.factoriomod
|
||||
|
||||
object FactorioMod {
|
||||
|
||||
const val TASK_GROUP = "factorioMod"
|
||||
|
||||
/** Lifecycle task for publishing the mod locally */
|
||||
const val PUBLISH_MOD_LOCAL_TASK_NAME = "publishModLocal"
|
||||
|
||||
const val PUBLISH_MOD_TASK_NAME = "publishMod"
|
||||
|
||||
}
|
||||
25
buildSrc/src/main/kotlin/extensions.kt
Normal file
25
buildSrc/src/main/kotlin/extensions.kt
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import org.gradle.api.artifacts.Configuration
|
||||
import org.gradle.api.attributes.Usage
|
||||
import org.gradle.api.model.ObjectFactory
|
||||
import org.gradle.kotlin.dsl.named
|
||||
|
||||
// to be moved into gradle-plugins/builder-plugins...
|
||||
|
||||
|
||||
fun Configuration.asProvider() {
|
||||
isVisible = false
|
||||
isCanBeResolved = false
|
||||
isCanBeConsumed = true
|
||||
}
|
||||
|
||||
|
||||
fun Configuration.asConsumer() {
|
||||
isVisible = false
|
||||
isCanBeResolved = true
|
||||
isCanBeConsumed = false
|
||||
}
|
||||
|
||||
fun Configuration.typescriptAttributes(objects: ObjectFactory): Configuration =
|
||||
attributes {
|
||||
attribute(Usage.USAGE_ATTRIBUTE, objects.named("dev.adamko.gradle.factorio.typescriptFiles"))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue