All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.jetbrains.plugin.structure.teamcity.action.TeamCityActionPluginManager.kt Maven / Gradle / Ivy

Go to download

Library for parsing JetBrains TeamCity actions. Can be used to verify that plugin complies with JetBrains Marketplace requirements.

There is a newer version: 3.289
Show newest version
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")




© 2015 - 2024 Weber Informatics LLC | Privacy Policy