Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package com.netflix.spinnaker.keel.sql
import com.fasterxml.jackson.databind.ObjectMapper
import com.netflix.spinnaker.keel.api.ActionStateUpdateContext
import com.netflix.spinnaker.keel.api.DeliveryConfig
import com.netflix.spinnaker.keel.api.Environment
import com.netflix.spinnaker.keel.api.constraints.ConstraintStatus
import com.netflix.spinnaker.keel.api.plugins.ArtifactSupplier
import com.netflix.spinnaker.keel.api.ArtifactInEnvironmentContext
import com.netflix.spinnaker.keel.api.action.Action
import com.netflix.spinnaker.keel.api.action.ActionRepository
import com.netflix.spinnaker.keel.api.action.ActionState
import com.netflix.spinnaker.keel.api.action.ActionStateFull
import com.netflix.spinnaker.keel.api.action.ActionType
import com.netflix.spinnaker.keel.api.action.ActionType.POST_DEPLOY
import com.netflix.spinnaker.keel.api.action.ActionType.VERIFICATION
import com.netflix.spinnaker.keel.pause.PauseScope.*
import com.netflix.spinnaker.keel.persistence.metamodel.Tables
import com.netflix.spinnaker.keel.persistence.metamodel.Tables.ACTION_STATE
import com.netflix.spinnaker.keel.persistence.metamodel.Tables.DELIVERY_ARTIFACT
import com.netflix.spinnaker.keel.persistence.metamodel.Tables.DELIVERY_CONFIG
import com.netflix.spinnaker.keel.persistence.metamodel.Tables.ENVIRONMENT_LAST_VERIFIED
import com.netflix.spinnaker.keel.persistence.metamodel.Tables.ACTIVE_ENVIRONMENT
import com.netflix.spinnaker.keel.resources.ResourceSpecIdentifier
import com.netflix.spinnaker.keel.resources.SpecMigrator
import com.netflix.spinnaker.keel.sql.RetryCategory.WRITE
import com.netflix.spinnaker.keel.sql.deliveryconfigs.deliveryConfigByName
import org.jooq.*
import org.jooq.impl.DSL.field
import org.jooq.impl.DSL.function
import org.jooq.impl.DSL.inline
import org.jooq.impl.DSL.name
import org.jooq.impl.DSL.select
import org.jooq.impl.DSL.value
import org.slf4j.LoggerFactory
import org.springframework.core.env.Environment as SpringEnvironment
import java.time.Clock
import java.time.Duration
class SqlActionRepository(
jooq: DSLContext,
clock: Clock,
resourceSpecIdentifier: ResourceSpecIdentifier,
objectMapper: ObjectMapper,
sqlRetry: SqlRetry,
artifactSuppliers: List> = emptyList(),
specMigrators: List> = emptyList(),
private val environment: SpringEnvironment
) : SqlStorageContext(
jooq,
clock,
sqlRetry,
objectMapper,
resourceSpecIdentifier,
artifactSuppliers,
specMigrators
), ActionRepository {
private val log by lazy { LoggerFactory.getLogger(javaClass) }
private val useLockingRead : Boolean
get() = environment.getProperty("keel.verifications.db.lock.reads.enabled", Boolean::class.java, true)
override fun nextEnvironmentsForVerification(
minTimeSinceLastCheck: Duration,
limit: Int
): Collection {
val now = clock.instant()
val cutoff = now.minus(minTimeSinceLastCheck)
return sqlRetry.withRetry(WRITE) {
// TODO: only consider environments that have verifications
jooq.inTransaction {
nextEnvironmentQuery(cutoff, limit)
.lockInShareMode(useLockingRead)
.fetch()
.onEach { (_, _, environmentUid, _, artifactUid, _, artifactVersion) ->
insertInto(ENVIRONMENT_LAST_VERIFIED)
.set(ENVIRONMENT_LAST_VERIFIED.ENVIRONMENT_UID, environmentUid)
.set(ENVIRONMENT_LAST_VERIFIED.ARTIFACT_UID, artifactUid)
.set(ENVIRONMENT_LAST_VERIFIED.ARTIFACT_VERSION, artifactVersion)
.set(ENVIRONMENT_LAST_VERIFIED.AT, now)
.onDuplicateKeyUpdate()
.set(ENVIRONMENT_LAST_VERIFIED.ARTIFACT_VERSION, artifactVersion)
.set(ENVIRONMENT_LAST_VERIFIED.AT, now)
.execute()
}
}
}
.map { (_, deliveryConfigName, _, environmentName, _, artifactReference, artifactVersion) ->
ArtifactInEnvironmentContext(
deliveryConfigByName(deliveryConfigName),
environmentName,
artifactReference,
artifactVersion
)
}
}
private fun getState(
context: ArtifactInEnvironmentContext,
id: String,
type: ActionType
): ActionState? =
with(context) {
jooq
.select(
ACTION_STATE.STATUS,
ACTION_STATE.STARTED_AT,
ACTION_STATE.ENDED_AT,
ACTION_STATE.METADATA,
ACTION_STATE.LINK
)
.from(ACTION_STATE)
.where(ACTION_STATE.ENVIRONMENT_UID.eq(environmentUid))
.and(ACTION_STATE.ARTIFACT_UID.eq(artifactUid))
.and(ACTION_STATE.ARTIFACT_VERSION.eq(version))
.and(ACTION_STATE.ACTION_ID.eq(id))
.and(ACTION_STATE.TYPE.eq(type))
.fetchOneInto()
}
override fun getState(context: ArtifactInEnvironmentContext, action: Action): ActionState? {
return getState(context, action.id, action.actionType)
}
override fun getStates(context: ArtifactInEnvironmentContext, type: ActionType): Map =
with(context) {
when {
type == VERIFICATION && verifications.isEmpty() -> emptyMap() // Optimization: don't hit the db if we know there are no entries
type == POST_DEPLOY && postDeployActions.isEmpty() -> emptyMap() // Optimization: don't hit the db if we know there are no entries
else -> jooq.select(
ACTION_STATE.ACTION_ID,
ACTION_STATE.STATUS,
ACTION_STATE.STARTED_AT,
ACTION_STATE.ENDED_AT,
ACTION_STATE.METADATA,
ACTION_STATE.LINK
)
.from(ACTION_STATE)
.where(ACTION_STATE.ENVIRONMENT_UID.eq(environmentUid))
.and(ACTION_STATE.ARTIFACT_UID.eq(artifactUid))
.and(ACTION_STATE.ARTIFACT_VERSION.eq(version))
.and(ACTION_STATE.TYPE.eq(type))
.fetch()
.associate { (id, status, started_at, ended_at, metadata, link) ->
id to ActionState(status, started_at, ended_at, metadata, link)
}
}
}
override fun allPassed(context: ArtifactInEnvironmentContext, type: ActionType): Boolean {
val actions = if (type == VERIFICATION) {
context.verifications
} else {
context.postDeployActions
}
return when {
actions.isEmpty() -> true
else -> {
val states = getStates(context, type)
// must have an entry for all defined actions, and all those entries must be passing
actions
.map { it.id }
.all { id ->
when (states[id]?.status) {
ConstraintStatus.PASS, ConstraintStatus.OVERRIDE_PASS -> true.also {
log.info("${type.name} ($id) passed against version ${context.version} for app ${context.deliveryConfig.application}")
}
ConstraintStatus.FAIL, ConstraintStatus.OVERRIDE_FAIL -> false.also {
log.info("${type.name} ($id) failed against version ${context.version} for app ${context.deliveryConfig.application}")
}
ConstraintStatus.NOT_EVALUATED, ConstraintStatus.PENDING -> false.also {
log.info("${type.name} ($id) still running against version ${context.version} for app ${context.deliveryConfig.application}")
}
null -> false.also {
log.info("no database entry for ${type.name} ($id) against version ${context.version} for app ${context.deliveryConfig.application}")
}
}
}
}
}
}
override fun allStarted(context: ArtifactInEnvironmentContext, type: ActionType): Boolean {
val actions = if (type == VERIFICATION) {
context.verifications
} else {
context.postDeployActions
}
return when {
actions.isEmpty() -> true
else -> {
val states = getStates(context, type)
// must have an entry for all defined actions, since that indicates they've started.
log.info("${type.name} found entries for actions ${states.keys} against version ${context.version} for app ${context.deliveryConfig.application}")
return actions.map { it.id }.containsAll(states.keys)
}
}
}
override fun getVerificationContextsWithStatus(deliveryConfig: DeliveryConfig, environment: Environment, status: ConstraintStatus): Collection =
jooq.select(
DELIVERY_ARTIFACT.REFERENCE,
ACTION_STATE.ARTIFACT_VERSION
)
.from(ACTION_STATE)
.join(DELIVERY_ARTIFACT)
.on(DELIVERY_ARTIFACT.UID.eq(ACTION_STATE.ARTIFACT_UID))
.join(ACTIVE_ENVIRONMENT)
.on(ACTIVE_ENVIRONMENT.UID.eq(ACTION_STATE.ENVIRONMENT_UID))
.join(DELIVERY_CONFIG)
.on(DELIVERY_CONFIG.UID.eq(ACTIVE_ENVIRONMENT.DELIVERY_CONFIG_UID))
.where(DELIVERY_CONFIG.NAME.eq(deliveryConfig.name))
.and(ACTIVE_ENVIRONMENT.NAME.eq(environment.name))
.and(ACTION_STATE.STATUS.eq(status))
.fetch()
.map { (artifactReference, version) ->
ArtifactInEnvironmentContext(
deliveryConfig = deliveryConfig,
environmentName = environment.name,
artifactReference = artifactReference,
version = version)
}
.toList()
/**
* Query the repository for the states of multiple contexts.
*
* This call is semantically equivalent to
* contexts.map { context -> this.getStates(context) }
*
* However, it's implemented as a single query for efficiency.
*
* @param contexts a list of verification contexts to query for state
* @return a list of maps of verification ids to states, in the same order as the contexts. If there are no
* verification states associated with a context, the resulting map will be empty.
*/
//todo: remove once we switch to graphql api, because the new api loads verifications and actions in one call
override fun getStatesBatch(
contexts: List,
type: ActionType
): List