Skip to content

Commit

Permalink
#1236 Environment display options for a build
Browse files Browse the repository at this point in the history
  • Loading branch information
dcoraboeuf committed Dec 15, 2024
1 parent 69ad6b8 commit 40f0fff
Show file tree
Hide file tree
Showing 28 changed files with 918 additions and 462 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package net.nemerosa.ontrack.extension.environments.promotions

import net.nemerosa.ontrack.model.annotations.APIDescription
import net.nemerosa.ontrack.model.structure.Build

@APIDescription("Number of environments where a build is deployed")
data class EnvironmentBuildCount(
@APIDescription("Associated build")
val build: Build,
@APIDescription("Number of environments where a build is deployed")
val count: Int,
)
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package net.nemerosa.ontrack.extension.environments.promotions

import net.nemerosa.ontrack.extension.api.BuildPromotionInfoExtension
import net.nemerosa.ontrack.extension.environments.*
import net.nemerosa.ontrack.extension.environments.rules.PromotionRelatedSlotAdmissionRule
import net.nemerosa.ontrack.extension.environments.rules.SlotAdmissionRuleRegistry
import net.nemerosa.ontrack.extension.environments.EnvironmentsExtensionFeature
import net.nemerosa.ontrack.extension.environments.SlotPipeline
import net.nemerosa.ontrack.extension.environments.service.SlotService
import net.nemerosa.ontrack.extension.environments.settings.EnvironmentsSettings
import net.nemerosa.ontrack.extension.environments.settings.EnvironmentsSettingsBuildDisplayOption
import net.nemerosa.ontrack.extension.support.AbstractExtension
import net.nemerosa.ontrack.model.promotions.BuildPromotionInfoItem
import net.nemerosa.ontrack.model.settings.CachedSettingsService
import net.nemerosa.ontrack.model.structure.Build
import net.nemerosa.ontrack.model.structure.PromotionLevel
import org.springframework.stereotype.Component
Expand All @@ -15,79 +17,68 @@ import kotlin.reflect.KClass
@Component
class EnvironmentsBuildPromotionInfoExtension(
extensionFeature: EnvironmentsExtensionFeature,
private val cachedSettingsService: CachedSettingsService,
private val slotService: SlotService,
private val slotAdmissionRuleRegistry: SlotAdmissionRuleRegistry,
) : AbstractExtension(extensionFeature), BuildPromotionInfoExtension {

override val types: Collection<KClass<*>> = setOf(
Slot::class,
SlotPipeline::class,
EnvironmentBuildCount::class,
)

override fun buildPromotionInfoItems(
items: MutableList<BuildPromotionInfoItem<*>>,
build: Build,
promotionLevels: List<PromotionLevel>
) {
val contributions = mutableListOf<BuildPromotionInfoItem<*>>()
// Pipelines for this build
val buildPipelines = slotService.findPipelineByBuild(build)
// Gets the eligible slots for this build
val eligibleSlots = slotService.getEligibleSlotsForBuild(build)
.filter { it.eligible }
.map { it.slot }
.sortedByDescending { it.environment.order }
eligibleSlots.forEach { slot ->
// Getting the promotion level for this slot
val promotionLevel = promotionLevels.firstOrNull { pl ->
isSlotForPromotionLevel(slot, pl)
}
// Adding the contribution for this slot
contributions += buildPromotionInfoItemForEligibleSlot(slot, promotionLevel)
// Getting the pipelines for this slot & build
val pipelines = buildPipelines.filter { it.slot.id == slot.id }
.sortedByDescending { it.number }
pipelines.forEach { pipeline ->
contributions += buildPromotionInfoItemForSlotPipeline(pipeline, promotionLevel)
}
val (buildDisplayOption) = cachedSettingsService.getCachedSettings(EnvironmentsSettings::class.java)
return when (buildDisplayOption) {
EnvironmentsSettingsBuildDisplayOption.ALL -> allEnvironments(items, build)
EnvironmentsSettingsBuildDisplayOption.HIGHEST -> highestEnvironment(items, build)
EnvironmentsSettingsBuildDisplayOption.COUNT -> countEnvironments(items, build)
}
// Add the contributions before all the other items
items.addAll(0, contributions)
}

private fun isSlotForPromotionLevel(
slot: Slot,
promotionLevel: PromotionLevel
): Boolean {
return slotService.getAdmissionRuleConfigs(slot)
.count { config ->
val rule = slotAdmissionRuleRegistry.getRule(config.ruleId)
isAdmissionRuleConfigForPromotionLevel(rule, config, promotionLevel)
} == 1
private fun countEnvironments(
items: MutableList<BuildPromotionInfoItem<*>>,
build: Build
) {
val count = slotService.findSlotPipelinesWhereBuildIsLastDeployed(build).size
items.add(0, buildPromotionInfoItemForDeployedSlotCount(build, count))
}

private fun <T, D> isAdmissionRuleConfigForPromotionLevel(
rule: SlotAdmissionRule<T, D>,
config: SlotAdmissionRuleConfig,
promotionLevel: PromotionLevel
): Boolean {
if (rule is PromotionRelatedSlotAdmissionRule) {
val ruleConfig = rule.parseConfig(config.ruleConfig)
return rule.isForPromotionLevel(ruleConfig, promotionLevel)
} else {
return false
private fun highestEnvironment(
items: MutableList<BuildPromotionInfoItem<*>>,
build: Build
) {
val slotPipeline = slotService.findSlotPipelinesWhereBuildIsLastDeployed(build).firstOrNull()
if (slotPipeline != null) {
items.add(0, buildPromotionInfoItemForDeployedSlotPipeline(slotPipeline))
}
}

private fun buildPromotionInfoItemForEligibleSlot(slot: Slot, promotionLevel: PromotionLevel?) =
private fun allEnvironments(
items: MutableList<BuildPromotionInfoItem<*>>,
build: Build
) {
val slotPipelines = slotService.findSlotPipelinesWhereBuildIsLastDeployed(build)
items.addAll(0, slotPipelines.map { slotPipeline ->
buildPromotionInfoItemForDeployedSlotPipeline(slotPipeline)
})
}

private fun buildPromotionInfoItemForDeployedSlotPipeline(
slotPipeline: SlotPipeline,
) =
BuildPromotionInfoItem(
promotionLevel = promotionLevel,
data = slot,
promotionLevel = null,
data = slotPipeline,
)

private fun buildPromotionInfoItemForSlotPipeline(slotPipeline: SlotPipeline, promotionLevel: PromotionLevel?) =
private fun buildPromotionInfoItemForDeployedSlotCount(build: Build, count: Int) =
BuildPromotionInfoItem(
promotionLevel = promotionLevel,
data = slotPipeline,
promotionLevel = null,
data = EnvironmentBuildCount(build, count),
)

}
Original file line number Diff line number Diff line change
Expand Up @@ -200,4 +200,9 @@ interface SlotService {
*/
fun findEligibleSlotsByBuild(build: Build): List<Slot>

/**
* Gets all the slot pipelines where the given [build] is the last being deployed.
*/
fun findSlotPipelinesWhereBuildIsLastDeployed(build: Build): List<SlotPipeline>

}
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,17 @@ class SlotServiceImpl(
}.sortedBy { it.environment.order }
}

override fun findSlotPipelinesWhereBuildIsLastDeployed(build: Build): List<SlotPipeline> {
val projectSlots = findSlotsByProject(build.project)
return projectSlots.mapNotNull { slot ->
getCurrentPipeline(slot)
}.filter { pipeline ->
pipeline.build.id == build.id
}.sortedByDescending { pipeline ->
pipeline.slot.environment.order
}
}

private fun <C, D> getRequiredInput(
pipeline: SlotPipeline,
config: SlotAdmissionRuleConfig,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package net.nemerosa.ontrack.extension.environments.settings

import net.nemerosa.ontrack.model.annotations.APIDescription

data class EnvironmentsSettings(
@APIDescription("How the environments a build is deployed into are displayed")
val buildDisplayOption: EnvironmentsSettingsBuildDisplayOption = EnvironmentsSettingsBuildDisplayOption.HIGHEST,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package net.nemerosa.ontrack.extension.environments.settings

enum class EnvironmentsSettingsBuildDisplayOption {

/**
* All environments must be displayed
*/
ALL,

/**
* Only the highest environment is displayed
*/
HIGHEST,

/**
* Only a count of the environments is displayed
*/
COUNT,

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package net.nemerosa.ontrack.extension.environments.settings

import net.nemerosa.ontrack.extension.casc.context.settings.AbstractSubSettingsContext
import net.nemerosa.ontrack.model.settings.CachedSettingsService
import net.nemerosa.ontrack.model.settings.SettingsManagerService
import org.springframework.stereotype.Component

@Component
class EnvironmentsSettingsCasc(
settingsManagerService: SettingsManagerService,
cachedSettingsService: CachedSettingsService
) : AbstractSubSettingsContext<EnvironmentsSettings>(
"environments",
EnvironmentsSettings::class,
settingsManagerService,
cachedSettingsService
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package net.nemerosa.ontrack.extension.environments.settings

import net.nemerosa.ontrack.model.form.Form
import net.nemerosa.ontrack.model.security.SecurityService
import net.nemerosa.ontrack.model.settings.AbstractSettingsManager
import net.nemerosa.ontrack.model.settings.CachedSettingsService
import net.nemerosa.ontrack.model.support.SettingsRepository
import net.nemerosa.ontrack.model.support.setEnum
import org.springframework.stereotype.Component

@Component
class EnvironmentsSettingsManager(
cachedSettingsService: CachedSettingsService,
securityService: SecurityService,
private val settingsRepository: SettingsRepository,
) : AbstractSettingsManager<EnvironmentsSettings>(
EnvironmentsSettings::class.java,
cachedSettingsService,
securityService
) {

override fun doSaveSettings(settings: EnvironmentsSettings) {
settingsRepository.setEnum<EnvironmentsSettings, EnvironmentsSettingsBuildDisplayOption>(settings::buildDisplayOption)
}

override fun getId(): String = "environments"

override fun getTitle(): String = "Environments"

@Deprecated("Deprecated in Java")
override fun getSettingsForm(settings: EnvironmentsSettings?): Form = Form.create()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package net.nemerosa.ontrack.extension.environments.settings

import net.nemerosa.ontrack.model.settings.SettingsProvider
import net.nemerosa.ontrack.model.support.SettingsRepository
import net.nemerosa.ontrack.model.support.getEnum
import org.springframework.stereotype.Component

@Component
class EnvironmentsSettingsProvider(
private val settingsRepository: SettingsRepository,
) : SettingsProvider<EnvironmentsSettings> {

override fun getSettings() = EnvironmentsSettings(
buildDisplayOption = settingsRepository.getEnum(
property = EnvironmentsSettings::buildDisplayOption,
defaultValue = EnvironmentsSettingsBuildDisplayOption.HIGHEST,
),
)

override fun getSettingsClass(): Class<EnvironmentsSettings> =
EnvironmentsSettings::class.java

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package net.nemerosa.ontrack.extension.environments.ui

import graphql.Scalars.GraphQLID
import graphql.schema.GraphQLObjectType
import net.nemerosa.ontrack.extension.environments.promotions.EnvironmentBuildCount
import net.nemerosa.ontrack.graphql.schema.GQLType
import net.nemerosa.ontrack.graphql.schema.GQLTypeCache
import net.nemerosa.ontrack.graphql.support.getTypeDescription
import net.nemerosa.ontrack.graphql.support.intField
import net.nemerosa.ontrack.graphql.support.toNotNull
import org.springframework.stereotype.Component

@Component
class GQLTypeEnvironmentBuildCount : GQLType {

override fun getTypeName(): String = EnvironmentBuildCount::class.java.simpleName

override fun createType(cache: GQLTypeCache): GraphQLObjectType =
GraphQLObjectType.newObject()
.name(typeName)
.description(getTypeDescription(EnvironmentBuildCount::class))
.field {
it.name("id")
.description("ID of the count (build ID)")
.type(GraphQLID.toNotNull())
.dataFetcher { env ->
val count = env.getSource<EnvironmentBuildCount>()
count.build.id()
}
}
.intField(EnvironmentBuildCount::count)
.build()

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import net.nemerosa.ontrack.model.structure.Project
import net.nemerosa.ontrack.test.TestUtils.uid
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import kotlin.test.assertTrue

@Component
class SlotTestSupport : AbstractDSLTestSupport() {
Expand Down Expand Up @@ -85,8 +86,10 @@ class SlotTestSupport : AbstractDSLTestSupport() {
}

fun startAndDeployPipeline(pipeline: SlotPipeline) {
slotService.startDeployment(pipeline, dryRun = false)
slotService.finishDeployment(pipeline)
val status = slotService.startDeployment(pipeline, dryRun = false)
assertTrue(status.status, "Pipeline deploying")
val result = slotService.finishDeployment(pipeline)
assertTrue(result.deployed, "Pipeline deployed")
}

fun withDeployedSlotPipeline(
Expand Down
Loading

0 comments on commit 40f0fff

Please sign in to comment.