com.jetbrains.plugin.structure.teamcity.action.TeamCityActionPluginManager.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of structure-teamcity-actions Show documentation
Show all versions of structure-teamcity-actions Show documentation
Library for parsing JetBrains TeamCity actions. Can be used to verify that plugin complies with JetBrains Marketplace requirements.
package com.jetbrains.plugin.structure.teamcity.action
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import com.jetbrains.plugin.structure.base.decompress.DecompressorSizeLimitExceededException
import com.jetbrains.plugin.structure.base.plugin.*
import com.jetbrains.plugin.structure.base.plugin.Settings.EXTRACT_DIRECTORY
import com.jetbrains.plugin.structure.base.problems.*
import com.jetbrains.plugin.structure.base.utils.*
import com.vdurmont.semver4j.Semver
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.nio.file.Files
import java.nio.file.Files.createTempDirectory
import java.nio.file.Path
import java.nio.file.Paths
class TeamCityActionPluginManager
private constructor(private val extractDirectory: Path) : PluginManager {
private val objectMapper = ObjectMapper(YAMLFactory()).registerKotlinModule()
companion object {
private val LOG: Logger = LoggerFactory.getLogger(TeamCityActionPluginManager::class.java)
fun createManager(extractDirectory: Path = Paths.get(EXTRACT_DIRECTORY.get())) =
TeamCityActionPluginManager(extractDirectory.createDir())
}
override fun createPlugin(pluginFile: Path): PluginCreationResult {
require(pluginFile.exists()) { "TeamCity Action file ${pluginFile.toAbsolutePath()} does not exist" }
return when {
pluginFile.isZip() || pluginFile.isYaml() -> createPluginFrom(pluginFile)
else -> fileFormatError(pluginFile)
}
}
private fun createPluginFrom(actionPath: Path): PluginCreationResult {
val sizeLimit = Settings.TEAM_CITY_ACTION_SIZE_LIMIT.getAsLong()
if (Files.size(actionPath) > sizeLimit) {
return PluginCreationFail(FileTooBig(actionPath.simpleName, sizeLimit))
}
return when {
actionPath.isZip() -> extractActionFromZip(actionPath, sizeLimit)
actionPath.isYaml() -> parseYaml(actionPath)
else -> fileFormatError(actionPath)
}
}
private fun extractActionFromZip(zipPath: Path, sizeLimit: Long): PluginCreationResult {
val tempDirectory = createTempDirectory(extractDirectory, "teamcity_action_extracted_${zipPath.simpleName}_")
return try {
extractZip(zipPath, tempDirectory, sizeLimit)
val yaml = Files.walk(tempDirectory)
.filter { item -> item.isYaml() }
.findFirst()
if (yaml.isEmpty) {
return PluginCreationFail(MissedFile("action.yaml"))
}
parseYaml(yaml.get())
} catch (e: DecompressorSizeLimitExceededException) {
return PluginCreationFail(PluginFileSizeIsTooLarge(e.sizeLimit))
} finally {
tempDirectory.deleteLogged()
}
}
private fun parseYaml(yamlPath: Path): PluginCreationResult {
val yamlContent = yamlPath.readText()
val actionDescriptor: TeamCityActionDescriptor
try {
actionDescriptor = objectMapper.readValue(yamlContent, TeamCityActionDescriptor::class.java)
} catch (e: Exception) {
LOG.warn("Failed to parse TeamCity Action", e)
return PluginCreationFail(ParseYamlProblem)
}
try {
return parse(actionDescriptor)
} catch (e: Exception) {
e.rethrowIfInterrupted()
val errorMessage = "An unexpected error occurred while parsing the TeamCity Action descriptor"
LOG.warn(errorMessage, e)
return PluginCreationFail(UnableToReadDescriptor(yamlPath.toAbsolutePath().toString(), errorMessage))
}
}
private fun parse(descriptor: TeamCityActionDescriptor): PluginCreationResult {
val validationResult = validateTeamCityAction(descriptor)
if (validationResult.any { it.isError }) {
return PluginCreationFail(validationResult)
}
val plugin = with(descriptor) {
TeamCityActionPlugin(
// All the fields are expected to be non-null due to the validations above
pluginId = this.name!!, // TODO: It is a temporary way of the pluginID creation, must be changed
pluginName = this.name,
description = this.description!!,
pluginVersion = this.version!!,
specVersion = Semver(this.specVersion!!),
)
}
return PluginCreationSuccess(plugin, validationResult)
}
}
private fun fileFormatError(pluginFile: Path): PluginCreationResult =
PluginCreationFail(IncorrectPluginFile(pluginFile.simpleName, "ZIP archive or YAML file"))
private fun Path.isYaml(): Boolean = this.hasExtension("yaml") || this.hasExtension("yml")