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

com.netflix.spinnaker.keel.igor.artifact.ArtifactMetadataService.kt Maven / Gradle / Ivy

There is a newer version: 1.4.1
Show newest version
package com.netflix.spinnaker.keel.igor.artifact

import com.netflix.spinnaker.keel.igor.BuildService
import com.netflix.spinnaker.keel.api.artifacts.ArtifactMetadata
import com.netflix.spinnaker.keel.api.artifacts.BuildMetadata
import com.netflix.spinnaker.keel.api.artifacts.Commit
import com.netflix.spinnaker.keel.api.artifacts.GitMetadata
import com.netflix.spinnaker.keel.api.artifacts.Job
import com.netflix.spinnaker.keel.api.artifacts.PullRequest
import com.netflix.spinnaker.keel.api.artifacts.Repo
import com.netflix.spinnaker.keel.igor.model.Build
import com.netflix.spinnaker.keel.igor.model.CompletionStatus
import io.github.resilience4j.kotlin.retry.executeSuspendFunction
import io.github.resilience4j.retry.Retry
import io.github.resilience4j.retry.RetryConfig
import kotlinx.coroutines.TimeoutCancellationException
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component
import retrofit2.HttpException
import java.time.Duration

/**
 * Provides functionality to convert build metadata, which is coming from internal service, to artifact metadata (via igor).
 */
@Component
class ArtifactMetadataService(
  private val buildService: BuildService
) {
  companion object {
    private const val DEFAULT_MAX_ATTEMPTS = 20
  }

  /**
   * Returns additional metadata about the specified build and commit, if available. This call is configured
   * to auto-retry as it's not on a code path where any external retries would happen.
   */
  suspend fun getArtifactMetadata(
    buildNumber: String,
    commitId: String,
    maxAttempts: Int = DEFAULT_MAX_ATTEMPTS
  ): ArtifactMetadata? {

    val buildList = getArtifactMetadataWithRetries(commitId, buildNumber)

    if (buildList.isNullOrEmpty()) {
      log.debug("artifact metadata buildList is null or empty, for build $buildNumber and commit $commitId")
      return null
    }

    log.debug("received artifact metadata $buildList for build $buildNumber and commit $commitId")
    return buildList.first().toArtifactMetadata(commitId)
  }

  private fun Build.toArtifactMetadata(commitId: String) =
    ArtifactMetadata(
      BuildMetadata(
        id = number,
        uid = id,
        job = Job(
          link = url,
          name = name
        ),
        startedAt = properties?.get("startedAt") as String?,
        completedAt = properties?.get("completedAt") as String?,
        number = number.toString(),
        status = result.toString()
      ),
      GitMetadata(
        commit = let {
          if (commitId.length > 7)
            commitId.substring(0, 7)
          else {
            commitId
          }
        },
        commitInfo = Commit(
          sha = scm?.first()?.sha1,
          link = scm?.first()?.compareUrl,
          message = scm?.first()?.message,
        ),
        author = scm?.first()?.committer,
        pullRequest = PullRequest(
          number = properties?.get("pullRequestNumber") as String?,
          url = properties?.get("pullRequestUrl") as String?
        ),
        repo = Repo(
          name = properties?.get("repoSlug") as String?,
          //TODO[gyardeni]: add link (will come from Igor)
          link = ""
        ),
        branch = scm?.first()?.branch,
        project = properties?.get("projectKey") as String?,
      )
    )

  //This will not be used for now, until we will figure out what is wrong!
  private suspend fun getArtifactMetadataWithRetries(
    commitId: String,
    buildNumber: String,
    maxAttempts: Int = DEFAULT_MAX_ATTEMPTS
  ): List? {
    val retry = Retry.of(
      "get artifact metadata",
      RetryConfig.custom?>()
        // retry 20 times over a total of 90 seconds
        .maxAttempts(maxAttempts)
        .waitDuration(Duration.ofMillis(4500))
        .retryOnResult{ result ->
          if (result.isNullOrEmpty()) {
            log.debug("Retrying artifact metadata retrieval due to empty response (commit=$commitId, build=$buildNumber)")
          }
          result.isNullOrEmpty()
        }
        .retryOnException { t: Throwable ->
          // https://github.com/resilience4j/resilience4j/issues/688
          val retryFilter = when (t) {
            is TimeoutCancellationException -> true
            else -> t is HttpException
          }
          log.debug(if (retryFilter) "Retrying " else "Not retrying " +
            "artifact metadata retrieval (commit=$commitId, build=$buildNumber) on exception: ${t::class.java.name}")
          retryFilter
        }
        .build()
    )

    return retry.executeSuspendFunction {
      buildService.getArtifactMetadata(commitId = commitId.trim(), buildNumber = buildNumber.trim(), completionStatus = CompletionStatus.values().joinToString { it.name })
    }
  }


  private val log by lazy { LoggerFactory.getLogger(javaClass) }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy