Skip to content

fix: have service selectors match templates, too (#882) #899

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ private const val KIND_PERSISTENT_VOLUME = "PersistentVolume"
private const val KIND_PERSISTENT_VOLUME_CLAIM = "PersistentVolumeClaim"
private const val KIND_POD = "Pod"
private const val KIND_POD_DISRUPTION_BUDGET = "PodDisruptionBudget"
private const val KIND_REPLICATION_CONTROLLER = "ReplicationController"
private const val KIND_REPLICA_SET = "ReplicaSet"
private const val KIND_SERVICE = "Service"
private const val KIND_STATEFUL_SET = "StatefulSet"
Expand Down Expand Up @@ -61,6 +62,10 @@ fun KubernetesTypeInfo.isPodDisruptionBudget(): Boolean {
return this.kind == KIND_POD_DISRUPTION_BUDGET
}

fun KubernetesTypeInfo.isReplicationController(): Boolean {
return this.kind == KIND_REPLICATION_CONTROLLER
}

fun KubernetesTypeInfo.isReplicaSet(): Boolean {
return this.kind == KIND_REPLICA_SET
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ private const val KEY_LABELS = "labels"
private const val KEY_SPEC = "spec"
private const val KEY_SELECTOR = "selector"
private const val KEY_TEMPLATE = "template"
private const val KEY_JOB_TEMPLATE = "jobTemplate"
private const val KEY_BINARY_DATA = "binaryData"
private const val KEY_DATA = "data"

Expand Down Expand Up @@ -210,6 +211,7 @@ fun JsonObject.getTemplate(): JsonObject? {
return (this.findProperty(KEY_SPEC)?.value as? JsonObject?)
?.findProperty(KEY_TEMPLATE)?.value as? JsonObject?
}

fun PsiElement.hasTemplateLabels(): Boolean {
return this.getTemplateLabels() != null
}
Expand All @@ -223,12 +225,33 @@ fun PsiElement.getTemplateLabels(): PsiElement? {
}
}

fun PsiElement.getJobTemplate(): PsiElement? {
return when(this) {
is YAMLMapping -> this.getJobTemplate()
is JsonObject -> this.getJobTemplate()
else ->
null
}
}

fun YAMLMapping.getJobTemplate(): YAMLMapping? {
return (this.getKeyValueByKey(KEY_SPEC)?.value as? YAMLMapping)
?.getKeyValueByKey(KEY_JOB_TEMPLATE)?.value as? YAMLMapping
}

fun JsonObject.getJobTemplate(): JsonObject? {
return (this.findProperty(KEY_SPEC)?.value as? JsonObject?)
?.findProperty(KEY_JOB_TEMPLATE)?.value as? JsonObject?
}

fun YAMLMapping.getTemplateLabels(): YAMLMapping? {
return this.getTemplate()?.getLabels()
?: getJobTemplate()?.getTemplate()?.getLabels()
}

fun JsonObject.getTemplateLabels(): JsonObject? {
return this.getTemplate()?.getLabels()
?: getJobTemplate()?.getTemplate()?.getLabels()
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ import com.intellij.psi.PsiElement
import com.redhat.devtools.intellij.common.validation.KubernetesTypeInfo
import com.redhat.devtools.intellij.kubernetes.editor.util.areMatchingMatchExpressions
import com.redhat.devtools.intellij.kubernetes.editor.util.areMatchingMatchLabels
import com.redhat.devtools.intellij.kubernetes.editor.util.getJobTemplate
import com.redhat.devtools.intellij.kubernetes.editor.util.getKubernetesTypeInfo
import com.redhat.devtools.intellij.kubernetes.editor.util.getLabels
import com.redhat.devtools.intellij.kubernetes.editor.util.getResource
import com.redhat.devtools.intellij.kubernetes.editor.util.getTemplateLabels
import com.redhat.devtools.intellij.kubernetes.editor.util.hasMatchExpressions
import com.redhat.devtools.intellij.kubernetes.editor.util.hasMatchLabels
import com.redhat.devtools.intellij.kubernetes.editor.util.hasSelector
import com.redhat.devtools.intellij.kubernetes.editor.util.hasTemplateLabels
import com.redhat.devtools.intellij.kubernetes.editor.util.isCronJob
import com.redhat.devtools.intellij.kubernetes.editor.util.isDaemonSet
import com.redhat.devtools.intellij.kubernetes.editor.util.isDeployment
Expand All @@ -31,6 +33,7 @@ import com.redhat.devtools.intellij.kubernetes.editor.util.isPersistentVolumeCla
import com.redhat.devtools.intellij.kubernetes.editor.util.isPod
import com.redhat.devtools.intellij.kubernetes.editor.util.isPodDisruptionBudget
import com.redhat.devtools.intellij.kubernetes.editor.util.isReplicaSet
import com.redhat.devtools.intellij.kubernetes.editor.util.isReplicationController
import com.redhat.devtools.intellij.kubernetes.editor.util.isService
import com.redhat.devtools.intellij.kubernetes.editor.util.isStatefulSet

Expand Down Expand Up @@ -90,64 +93,53 @@ class LabelsFilter(selector: PsiElement): PsiElementMappingsFilter {
private fun canSelect(type: KubernetesTypeInfo): Boolean {
val selectorType = selectorResourceType ?: return false
return when {
selectorType.isDeployment() ->
type.isPod()
|| type.isDeployment() // can select deployment template

selectorType.isCronJob() ->
type.isPod()
|| type.isCronJob() // template

selectorType.isDaemonSet() ->
type.isPod()
|| type.isDaemonSet() // template

selectorType.isJob() ->
type.isPod()
|| type.isJob() // template

selectorType.isReplicaSet() ->
type.isPod()
|| type.isReplicaSet() // template

selectorType.isStatefulSet() ->
type.isPod()
|| type.isStatefulSet() // template

selectorType.isNetworkPolicy()
selectorType.isDaemonSet()
|| selectorType.isDeployment()
|| selectorType.isJob()
|| selectorType.isNetworkPolicy()
|| selectorType.isPodDisruptionBudget()
|| selectorType.isService() ->
|| selectorType.isReplicaSet()
|| selectorType.isReplicationController()
|| selectorType.isService()
|| selectorType.isStatefulSet() ->
type.isPod()
|| hasPodTemplate(type)

selectorType.isPersistentVolumeClaim() ->
type.isPersistentVolume()
|| type.isPersistentVolumeClaim()

else ->
false
}
}

private fun hasPodTemplate(type: KubernetesTypeInfo): Boolean {
return type.isCronJob()
|| type.isDaemonSet()
|| type.isDeployment()
|| type.isJob()
|| type.isReplicaSet()
|| type.isStatefulSet()
}

override fun getMatchingElement(element: PsiElement): PsiElement? {
val labeledType = element.getKubernetesTypeInfo() ?: return null
return getLabels(labeledType, element, selectorResourceType)
}

private fun getLabels(
labeledType: KubernetesTypeInfo,
labeledResourceType: KubernetesTypeInfo,
labeledResource: PsiElement,
selectorResourceType: KubernetesTypeInfo?
): PsiElement? {
return when {
selectorResourceType == null ->
null

(selectorResourceType.isCronJob() && labeledType.isCronJob())
|| (selectorResourceType.isDaemonSet() && labeledType.isDaemonSet())
|| (selectorResourceType.isDeployment() && labeledType.isDeployment())
|| (selectorResourceType.isJob() && labeledType.isJob())
|| (selectorResourceType.isReplicaSet() && labeledType.isReplicaSet())
|| (selectorResourceType.isStatefulSet() && labeledType.isStatefulSet()) ->
labeledResourceType.isCronJob() ->
labeledResource.getJobTemplate()?.getTemplateLabels()

labeledResource.hasTemplateLabels() ->
labeledResource.getTemplateLabels()

else ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ private const val KEY_METADATA = "metadata"
private const val KEY_SELECTOR = "selector"
private const val KEY_SPEC = "spec"
private const val KEY_TEMPLATE = "template"
private const val KEY_JOB_TEMPLATE = "jobTemplate"

fun YAMLMapping.createLabels(labelsChildren: YAMLMapping): YAMLKeyValue {
val metadataChildren = mock<YAMLMapping>()
Expand All @@ -54,23 +55,25 @@ fun JsonObject.createLabels(labelsChildren: JsonObject): JsonProperty {
}

fun YAMLMapping.createTemplate(templateChildren: YAMLMapping): YAMLKeyValue {
var spec = getKeyValueByKey(KEY_SPEC)
if (spec == null) {
val specMapping = mock<YAMLMapping>()
spec = createYAMLKeyValue(KEY_SPEC, specMapping, this)
}
val spec = getOrCreateSpec()
return createYAMLKeyValue(KEY_TEMPLATE, templateChildren, spec.value as YAMLMapping)
}

fun JsonObject.createTemplate(templateChildren: JsonObject): JsonProperty {
var spec = findProperty(KEY_SPEC)
if (spec == null) {
val specMapping = mock<JsonObject>()
spec = createJsonProperty(KEY_SPEC, specMapping, this)
}
val spec = getOrCreateSpec()
return createJsonProperty(KEY_TEMPLATE, templateChildren, spec.value as JsonObject)
}

fun YAMLMapping.createJobTemplate(templateChildren: YAMLMapping): YAMLKeyValue {
val spec = getOrCreateSpec()
return createYAMLKeyValue(KEY_JOB_TEMPLATE, templateChildren, spec.value as YAMLMapping)
}

fun JsonObject.createJobTemplate(templateChildren: JsonObject): JsonProperty {
val spec = getOrCreateSpec()
return createJsonProperty(KEY_JOB_TEMPLATE, templateChildren, spec.value as JsonObject)
}

fun YAMLMapping.createMatchLabels(matchLabels: YAMLMapping): YAMLKeyValue {
var selectorChildren = getSelector()
if (selectorChildren == null) {
Expand Down Expand Up @@ -119,21 +122,32 @@ fun createYAMLSequenceItem(key: String, operator: String, values: List<String>):
}

fun YAMLMapping.createSelector(selectorChildren: YAMLMapping = mock()): YAMLKeyValue {
val spec = getOrCreateSpec()
return createYAMLKeyValue(KEY_SELECTOR, selectorChildren, spec.value as YAMLMapping)
}

private fun YAMLMapping.getOrCreateSpec(): YAMLKeyValue {
var spec = getKeyValueByKey(KEY_SPEC)
if (spec == null) {
val specChildren = mock<YAMLMapping>()
spec = createYAMLKeyValue(KEY_SPEC, specChildren, this)
}
return createYAMLKeyValue(KEY_SELECTOR, selectorChildren, spec.value as YAMLMapping)
return spec
}


fun JsonObject.createSelector(selectorChildren: JsonObject = mock()): JsonProperty {
val spec = getOrCreateSpec()
return createJsonProperty(KEY_SELECTOR, selectorChildren, spec.value as JsonObject)
}

private fun JsonObject.getOrCreateSpec(): JsonProperty {
var spec = findProperty(KEY_SPEC)
if (spec == null) {
val specChildren = mock<JsonObject>()
spec = createJsonProperty(KEY_SPEC, specChildren, this)
val specChild = mock<JsonObject>()
spec = createJsonProperty(KEY_SPEC, specChild, this)
}
return createJsonProperty(KEY_SELECTOR, selectorChildren, spec.value as JsonObject)
return spec
}

fun YAMLMapping.createMetadata(): YAMLMapping {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.never
import com.nhaarman.mockitokotlin2.whenever
import com.redhat.devtools.intellij.common.validation.KubernetesTypeInfo
import com.redhat.devtools.intellij.kubernetes.editor.mocks.createJobTemplate
import com.redhat.devtools.intellij.kubernetes.editor.mocks.createJsonObject
import com.redhat.devtools.intellij.kubernetes.editor.mocks.createJsonProperty
import com.redhat.devtools.intellij.kubernetes.editor.mocks.createLabels
Expand All @@ -35,6 +36,7 @@ import org.jetbrains.yaml.psi.YAMLMapping
import org.junit.Before
import org.junit.Test
import org.mockito.MockedStatic
import kotlin.collections.listOf

class ResourcePsiElementUtilsTest {

Expand Down Expand Up @@ -294,6 +296,19 @@ class ResourcePsiElementUtilsTest {
assertThat(result).isSameAs(templateMapping)
}

@Test
fun `#getJobTemplate for YAML returns jobTemplate mapping`() {
// given
val specMapping = mock<YAMLMapping>()
createYAMLKeyValue("spec", specMapping, yamlElement)
val templateMapping = mock<YAMLMapping>()
createYAMLKeyValue("jobTemplate", templateMapping, specMapping)
// when
val result = yamlElement.getJobTemplate()
// then
assertThat(result).isSameAs(templateMapping)
}

@Test
fun `#getTemplateLabel for YAML returns template labels`() {
// given
Expand All @@ -319,10 +334,43 @@ class ResourcePsiElementUtilsTest {
assertThat(found).isSameAs(templateLabels)
}

@Test
fun `#getJobTemplateLabel for YAML returns template labels`() {
// given
val jobTemplateLabels = createYAMLMapping(listOf(
createYAMLKeyValue("jedi", "skywalker")
))
yamlElement.createJobTemplate(
createYAMLMapping(listOf(
createYAMLKeyValue("spec",
createYAMLMapping(listOf(
createYAMLKeyValue("template",
createYAMLMapping(listOf(
createYAMLKeyValue(
"metadata",
createYAMLMapping(listOf(
createYAMLKeyValue(
"labels",
jobTemplateLabels
)
))
)
))
)
))
)
))
)
// when
val found = yamlElement.getJobTemplate()?.getTemplateLabels()
// then
assertThat(found).isSameAs(jobTemplateLabels)
}

@Test
fun `#getTemplateLabel for Json returns template labels`() {
// given
val templateLabels = createJsonObject(listOf(
val jobTemplateLabels = createJsonObject(listOf(
createJsonProperty("jedi", "skywalker")
))
jsonElement.createTemplate(
Expand All @@ -332,7 +380,7 @@ class ResourcePsiElementUtilsTest {
createJsonObject(listOf(
createJsonProperty(
"labels",
templateLabels
jobTemplateLabels
)
))
)
Expand All @@ -341,6 +389,39 @@ class ResourcePsiElementUtilsTest {
// when
val found = jsonElement.getTemplateLabels()
// then
assertThat(found).isSameAs(jobTemplateLabels)
}

@Test
fun `#getJobTemplateLabel for Json returns template labels`() {
// given
val templateLabels = createJsonObject(listOf(
createJsonProperty("jedi", "skywalker")
))
jsonElement.createJobTemplate(
createJsonObject(listOf(
createJsonProperty("spec",
createJsonObject(listOf(
createJsonProperty("template",
createJsonObject(listOf(
createJsonProperty(
"metadata",
createJsonObject(listOf(
createJsonProperty(
"labels",
templateLabels
)
))
)
))
)
))
)
))
)
// when
val found = jsonElement.getJobTemplate()?.getTemplateLabels()
// then
assertThat(found).isSameAs(templateLabels)
}

Expand Down
Loading
Loading