Skip to content

Commit 363dc3d

Browse files
committed
fix: allow to edit secrets/configmaps in multi-resource yaml documents (#852)
1 parent 1493d29 commit 363dc3d

File tree

9 files changed

+92
-178
lines changed

9 files changed

+92
-178
lines changed

gradle/libs.versions.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[versions]
22
# libraries
33
kubernetes-client = "7.1.0"
4-
devtools-common = "1.9.8"
4+
devtools-common = "1.9.9-SNAPSHOT"
55
jackson-core = "2.17.0"
66
commons-lang3 = "3.12.0"
77
assertj-core = "3.22.0"

src/main/kotlin/com/redhat/devtools/intellij/kubernetes/editor/inlay/Base64ValueInlayHintsProvider.kt renamed to src/main/kotlin/com/redhat/devtools/intellij/kubernetes/editor/inlay/ResourceEditorInlayHintsProvider.kt

+33-12
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,18 @@ import com.intellij.codeInsight.hints.InlayHintsProvider
2020
import com.intellij.codeInsight.hints.InlayHintsSink
2121
import com.intellij.codeInsight.hints.NoSettings
2222
import com.intellij.codeInsight.hints.SettingsKey
23+
import com.intellij.json.psi.JsonFile
2324
import com.intellij.openapi.editor.Editor
2425
import com.intellij.psi.PsiElement
2526
import com.intellij.psi.PsiFile
2627
import com.intellij.ui.dsl.builder.panel
27-
import com.redhat.devtools.intellij.common.validation.KubernetesResourceInfo
28-
import com.redhat.devtools.intellij.kubernetes.editor.util.getContent
28+
import com.redhat.devtools.intellij.common.validation.KubernetesTypeInfo
29+
import com.redhat.devtools.intellij.kubernetes.editor.inlay.base64.Base64Presentations
30+
import org.jetbrains.yaml.psi.YAMLFile
2931
import javax.swing.JComponent
3032

3133

32-
internal class Base64ValueInlayHintsProvider : InlayHintsProvider<NoSettings> {
34+
internal class ResourceEditorInlayHintsProvider : InlayHintsProvider<NoSettings> {
3335

3436
override val key: SettingsKey<NoSettings> = SettingsKey("KubernetesResource.hints")
3537
override val name: String = "Kubernetes"
@@ -50,21 +52,40 @@ internal class Base64ValueInlayHintsProvider : InlayHintsProvider<NoSettings> {
5052
}
5153

5254
override fun getCollectorFor(file: PsiFile, editor: Editor, settings: NoSettings, sink: InlayHintsSink): InlayHintsCollector? {
53-
val info = KubernetesResourceInfo.extractMeta(file) ?: return null
54-
return Collector(editor, info)
55+
return Collector(editor)
5556
}
5657

57-
private class Collector(editor: Editor, private val info: KubernetesResourceInfo) : FactoryInlayHintsCollector(editor) {
58+
private class Collector(editor: Editor) : FactoryInlayHintsCollector(editor) {
5859

5960
override fun collect(element: PsiElement, editor: Editor, sink: InlayHintsSink): Boolean {
60-
if (element !is PsiFile
61-
|| !element.isValid) {
61+
if (!element.isValid) {
6262
return true
6363
}
64-
val content = getContent(element) ?: return true
65-
val factory = Base64Presentations.create(content, info, sink, editor) ?: return true
66-
factory.create()
67-
return false
64+
return when(element) {
65+
is YAMLFile -> {
66+
create(element, sink, editor)
67+
false
68+
}
69+
is JsonFile -> {
70+
create(element, sink, editor)
71+
false
72+
}
73+
else -> true
74+
}
75+
}
76+
77+
private fun create(file: YAMLFile, sink: InlayHintsSink, editor: Editor) {
78+
file.documents.forEach { document ->
79+
val info = KubernetesTypeInfo.extractMeta(document) ?: return
80+
val element = document.topLevelValue ?: return
81+
Base64Presentations.create(element, info, sink, editor)?.create()
82+
}
6883
}
84+
85+
private fun create(file: JsonFile, sink: InlayHintsSink, editor: Editor) {
86+
val info = KubernetesTypeInfo.extractMeta(file) ?: return
87+
Base64Presentations.create(file, info, sink, editor)?.create()
88+
}
89+
6990
}
7091
}

src/main/kotlin/com/redhat/devtools/intellij/kubernetes/editor/inlay/Base64Presentations.kt renamed to src/main/kotlin/com/redhat/devtools/intellij/kubernetes/editor/inlay/base64/Base64Presentations.kt

+7-6
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
* Red Hat, Inc. - initial API and implementation
1010
******************************************************************************/
1111
@file:Suppress("UnstableApiUsage")
12-
package com.redhat.devtools.intellij.kubernetes.editor.inlay
12+
package com.redhat.devtools.intellij.kubernetes.editor.inlay.base64
1313

1414
import com.intellij.codeInsight.hints.InlayHintsSink
1515
import com.intellij.codeInsight.hints.presentation.InlayPresentation
@@ -19,9 +19,10 @@ import com.intellij.openapi.editor.Editor
1919
import com.intellij.openapi.project.Project
2020
import com.intellij.psi.PsiElement
2121
import com.redhat.devtools.intellij.common.validation.KubernetesResourceInfo
22+
import com.redhat.devtools.intellij.common.validation.KubernetesTypeInfo
2223
import com.redhat.devtools.intellij.kubernetes.balloon.StringInputBalloon
23-
import com.redhat.devtools.intellij.kubernetes.editor.inlay.Base64Presentations.InlayPresentationsFactory
24-
import com.redhat.devtools.intellij.kubernetes.editor.inlay.Base64Presentations.create
24+
import com.redhat.devtools.intellij.kubernetes.editor.inlay.base64.Base64Presentations.InlayPresentationsFactory
25+
import com.redhat.devtools.intellij.kubernetes.editor.inlay.base64.Base64Presentations.create
2526
import com.redhat.devtools.intellij.kubernetes.editor.util.getBinaryData
2627
import com.redhat.devtools.intellij.kubernetes.editor.util.getData
2728
import com.redhat.devtools.intellij.kubernetes.editor.util.isKubernetesResource
@@ -40,15 +41,15 @@ object Base64Presentations {
4041
private const val SECRET_RESOURCE_KIND = "Secret"
4142
private const val CONFIGMAP_RESOURCE_KIND = "ConfigMap"
4243

43-
fun create(content: PsiElement, info: KubernetesResourceInfo, sink: InlayHintsSink, editor: Editor): InlayPresentationsFactory? {
44+
fun create(element: PsiElement, info: KubernetesTypeInfo, sink: InlayHintsSink, editor: Editor): InlayPresentationsFactory? {
4445
return when {
4546
isKubernetesResource(SECRET_RESOURCE_KIND, info) -> {
46-
val data = getData(content) ?: return null
47+
val data = getData(element) ?: return null
4748
StringPresentationsFactory(data, sink, editor)
4849
}
4950

5051
isKubernetesResource(CONFIGMAP_RESOURCE_KIND, info) -> {
51-
val binaryData = getBinaryData(content) ?: return null
52+
val binaryData = getBinaryData(element) ?: return null
5253
BinaryPresentationsFactory(binaryData, sink, editor)
5354
}
5455

src/main/kotlin/com/redhat/devtools/intellij/kubernetes/editor/inlay/Base64ValueAdapter.kt renamed to src/main/kotlin/com/redhat/devtools/intellij/kubernetes/editor/inlay/base64/Base64ValueAdapter.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* Contributors:
99
* Red Hat, Inc. - initial API and implementation
1010
******************************************************************************/
11-
package com.redhat.devtools.intellij.kubernetes.editor.inlay
11+
package com.redhat.devtools.intellij.kubernetes.editor.inlay.base64
1212

1313
import com.intellij.psi.PsiElement
1414
import com.redhat.devtools.intellij.kubernetes.editor.util.decodeBase64

src/main/kotlin/com/redhat/devtools/intellij/kubernetes/editor/util/ResourceEditorUtils.kt

+40-106
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,25 @@ import com.intellij.json.psi.JsonFile
1616
import com.intellij.json.psi.JsonProperty
1717
import com.intellij.json.psi.JsonValue
1818
import com.intellij.openapi.application.ReadAction
19-
import com.intellij.openapi.editor.Document
2019
import com.intellij.openapi.fileEditor.FileEditor
2120
import com.intellij.openapi.project.Project
2221
import com.intellij.openapi.util.text.Strings
2322
import com.intellij.openapi.vfs.VirtualFile
24-
import com.intellij.psi.PsiDocumentManager
2523
import com.intellij.psi.PsiElement
26-
import com.intellij.psi.PsiFile
2724
import com.intellij.psi.PsiManager
25+
import com.intellij.psi.PsiNamedElement
2826
import com.redhat.devtools.intellij.common.validation.KubernetesResourceInfo
27+
import com.redhat.devtools.intellij.common.validation.KubernetesTypeInfo
2928
import com.redhat.devtools.intellij.kubernetes.editor.ResourceEditor
3029
import org.jetbrains.yaml.YAMLElementGenerator
31-
import org.jetbrains.yaml.YAMLUtil
32-
import org.jetbrains.yaml.psi.YAMLFile
30+
import org.jetbrains.yaml.psi.YAMLDocument
3331
import org.jetbrains.yaml.psi.YAMLKeyValue
32+
import org.jetbrains.yaml.psi.YAMLMapping
3433
import org.jetbrains.yaml.psi.YAMLPsiElement
34+
import org.jetbrains.yaml.psi.YAMLSequence
3535
import org.jetbrains.yaml.psi.YAMLValue
36-
import java.util.Base64
36+
import java.util.*
37+
3738

3839
private const val KEY_METADATA = "metadata"
3940
private const val KEY_DATA = "data"
@@ -67,9 +68,9 @@ fun isKubernetesResource(resourceInfo: KubernetesResourceInfo?): Boolean {
6768
&& resourceInfo?.typeInfo?.kind?.isNotBlank() ?: false
6869
}
6970

70-
fun isKubernetesResource(kind: String, resourceInfo: KubernetesResourceInfo?): Boolean {
71-
return resourceInfo?.typeInfo?.apiGroup?.isNotBlank() ?: false
72-
&& kind == resourceInfo?.typeInfo?.kind
71+
fun isKubernetesResource(kind: String, info: KubernetesTypeInfo?): Boolean {
72+
return info?.apiGroup?.isNotBlank() != null
73+
&& kind == info.kind
7374
}
7475

7576
/**
@@ -93,89 +94,21 @@ fun getKubernetesResourceInfo(file: VirtualFile?, project: Project): KubernetesR
9394
}
9495
}
9596

96-
/**
97-
* Sets or creates the given [resourceVersion] in the given document for the given [PsiDocumentManager] and [Project].
98-
* The document is **not** committed to allow further modifications before a commit to happen.
99-
* Does nothing if the resource version or the document is `null`.
100-
*
101-
* @param resourceVersion the resource version to set/create
102-
* @param document the document to set the resource version to
103-
* @param manager the [PsiDocumentManager] to use for the operation
104-
* @param project the [Project] to use
105-
*/
106-
fun setResourceVersion(resourceVersion: String?, document: Document?, manager: PsiDocumentManager, project: Project) {
107-
if (resourceVersion == null
108-
|| document == null) {
109-
return
110-
}
111-
val metadata = getMetadata(document, manager) ?: return
112-
createOrUpdateResourceVersion(resourceVersion, metadata, project)
113-
}
114-
115-
private fun createOrUpdateResourceVersion(resourceVersion: String, metadata: PsiElement, project: Project) {
116-
when (metadata) {
117-
is YAMLKeyValue -> createOrUpdateResourceVersion(resourceVersion, metadata, project)
118-
is JsonProperty -> createOrUpdateResourceVersion(resourceVersion, metadata, project)
119-
}
120-
}
121-
122-
private fun createOrUpdateResourceVersion(resourceVersion: String, metadata: JsonProperty, project: Project) {
123-
val metadataObject = metadata.value ?: return
124-
val generator = JsonElementGenerator(project)
125-
val version = generator.createProperty(KEY_RESOURCE_VERSION, "\"$resourceVersion\"")
126-
val existingVersion = getResourceVersion(metadata)
127-
if (existingVersion != null) {
128-
metadataObject.addAfter(version, existingVersion)
129-
existingVersion.delete()
130-
} else {
131-
metadataObject.addBefore(generator.createComma(), metadataObject.lastChild)
132-
metadataObject.addBefore(version, metadataObject.lastChild)
133-
}
134-
}
135-
136-
private fun createOrUpdateResourceVersion(resourceVersion: String, metadata: YAMLKeyValue, project: Project) {
137-
val metadataObject = metadata.value ?: return
138-
val existingVersion = getResourceVersion(metadata)
139-
val generator = YAMLElementGenerator.getInstance(project)
140-
val version = generator.createYamlKeyValue(KEY_RESOURCE_VERSION, "\"$resourceVersion\"")
141-
if (existingVersion != null) {
142-
existingVersion.setValue(version.value!!)
143-
} else {
144-
metadataObject.add(generator.createEol())
145-
metadataObject.add(generator.createIndent(YAMLUtil.getIndentToThisElement(metadataObject)))
146-
metadataObject.add(version)
147-
}
148-
}
149-
150-
fun getContent(element: PsiElement): PsiElement? {
151-
if (element !is PsiFile) {
152-
return null
153-
}
154-
return getContent(element)
155-
}
97+
fun getChildren(document: YAMLDocument): List<YAMLPsiElement> {
98+
val topLevelValue: YAMLValue = document.topLevelValue ?: return emptyList()
15699

157-
private fun getContent(file: PsiFile): PsiElement? {
158-
return when (file) {
159-
is YAMLFile -> {
160-
if (file.documents == null
161-
|| file.documents.isEmpty()) {
162-
return null
163-
}
164-
file.documents[0].topLevelValue
165-
}
166-
is JsonFile ->
167-
file.topLevelValue
168-
else -> null
100+
return when (topLevelValue) {
101+
is YAMLMapping ->
102+
topLevelValue.keyValues.toList()
103+
is YAMLSequence ->
104+
topLevelValue.items
105+
else ->
106+
emptyList()
169107
}
170108
}
171109

172-
fun getMetadata(document: Document?, psi: PsiDocumentManager): PsiElement? {
173-
if (document == null) {
174-
return null
175-
}
176-
val file = psi.getPsiFile(document) ?: return null
177-
val content = getContent(file) ?: return null
178-
return getMetadata(content)
110+
fun getChildren(file: JsonFile): List<JsonElement> {
111+
return file.allTopLevelValues
179112
}
180113

181114
/**
@@ -185,16 +118,20 @@ fun getMetadata(document: Document?, psi: PsiDocumentManager): PsiElement? {
185118
* @param element the PsiElement whose "metadata" child should be found.
186119
* @return the PsiElement named "metadata"
187120
*/
188-
private fun getMetadata(content: PsiElement): PsiElement? {
189-
return when (content) {
121+
private fun getMetadata(parent: PsiElement): PsiElement? {
122+
return getElement(KEY_METADATA, parent)
123+
}
124+
125+
private fun getElement(key: String, parent: PsiElement): PsiElement? {
126+
return when (parent) {
190127
is YAMLValue ->
191-
content.children
192-
.filterIsInstance<YAMLKeyValue>()
193-
.find { it.name == KEY_METADATA }
128+
parent.children
129+
.filterIsInstance<YAMLKeyValue>()
130+
.find { it.name == key }
194131
is JsonValue ->
195-
content.children.toList()
132+
parent.children.toList()
196133
.filterIsInstance<JsonProperty>()
197-
.find { it.name == KEY_METADATA }
134+
.find { it.name == key }
198135
else ->
199136
null
200137
}
@@ -208,17 +145,14 @@ private fun getMetadata(content: PsiElement): PsiElement? {
208145
* @return the PsiElement named "data"
209146
*/
210147
fun getData(element: PsiElement): PsiElement? {
211-
return when (element) {
212-
is YAMLPsiElement ->
213-
element.children
214-
.filterIsInstance<YAMLKeyValue>()
215-
.find { it.name == KEY_DATA }
216-
?.value
217-
is JsonElement ->
218-
element.children.toList()
219-
.filterIsInstance<JsonProperty>()
220-
.find { it.name == KEY_DATA }
221-
?.value
148+
val dataElement = element.children
149+
.filterIsInstance<PsiNamedElement>()
150+
.find { it.name == KEY_DATA }
151+
return when (dataElement) {
152+
is YAMLKeyValue ->
153+
dataElement.value
154+
is JsonProperty ->
155+
dataElement.value
222156
else ->
223157
null
224158
}

src/main/resources/META-INF/plugin.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@
233233
<postStartupActivity implementation="com.redhat.devtools.intellij.kubernetes.KubernetesPluginInitializer" />
234234
<editorTabTitleProvider implementation="com.redhat.devtools.intellij.kubernetes.editor.KubernetesEditorsTabTitleProvider"/>
235235
<codeInsight.inlayProvider language="yaml"
236-
implementationClass="com.redhat.devtools.intellij.kubernetes.editor.inlay.Base64ValueInlayHintsProvider"
236+
implementationClass="com.redhat.devtools.intellij.kubernetes.editor.inlay.ResourceEditorInlayHintsProvider"
237237
id="MarkdownTableInlayProvider"/>
238238
<projectConfigurable id="tools.settings.redhat.kubernetes"
239239
parentId="tools"

src/test/kotlin/com/redhat/devtools/intellij/kubernetes/editor/inlay/Base64PresentationsTest.kt

+4-9
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
package com.redhat.devtools.intellij.kubernetes.editor.inlay
1212

1313
import com.nhaarman.mockitokotlin2.mock
14+
import com.redhat.devtools.intellij.kubernetes.editor.inlay.base64.Base64Presentations
1415
import com.redhat.devtools.intellij.kubernetes.editor.mocks.createYAMLKeyValue
1516
import com.redhat.devtools.intellij.kubernetes.editor.mocks.createYAMLValue
1617
import com.redhat.devtools.intellij.kubernetes.model.mocks.Mocks.kubernetesResourceInfo
@@ -21,15 +22,9 @@ import org.junit.Test
2122

2223
class Base64PresentationsTest {
2324

24-
private val secret = kubernetesResourceInfo(
25-
"yoda", "light side", kubernetesTypeInfo("Secret", "v1")
26-
)
27-
private val configMap = kubernetesResourceInfo(
28-
"skywalker", "light side", kubernetesTypeInfo("ConfigMap", "v1")
29-
)
30-
private val pod = kubernetesResourceInfo(
31-
"anakin", "light side", kubernetesTypeInfo("Pod", "v1")
32-
)
25+
private val secret = kubernetesTypeInfo("Secret", "v1")
26+
private val configMap = kubernetesTypeInfo("ConfigMap", "v1")
27+
private val pod = kubernetesTypeInfo("Pod", "v1")
3328

3429
private val dataElement = createYAMLKeyValue("data")
3530
private val binaryDataElement = createYAMLKeyValue("binaryData")

src/test/kotlin/com/redhat/devtools/intellij/kubernetes/editor/inlay/Base64ValueAdapterTest.kt

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import com.nhaarman.mockitokotlin2.any
1616
import com.nhaarman.mockitokotlin2.eq
1717
import com.nhaarman.mockitokotlin2.mock
1818
import com.nhaarman.mockitokotlin2.verify
19+
import com.redhat.devtools.intellij.kubernetes.editor.inlay.base64.Base64ValueAdapter
1920
import com.redhat.devtools.intellij.kubernetes.editor.mocks.createJsonProperty
2021
import com.redhat.devtools.intellij.kubernetes.editor.mocks.createJsonPsiFileFactory
2122
import com.redhat.devtools.intellij.kubernetes.editor.mocks.createProjectWithServices

0 commit comments

Comments
 (0)