diff --git a/src/main/java/org/openrewrite/kubernetes/KubernetesParser.java b/src/main/java/org/openrewrite/kubernetes/KubernetesParser.java index 1269428..2dfdbb6 100644 --- a/src/main/java/org/openrewrite/kubernetes/KubernetesParser.java +++ b/src/main/java/org/openrewrite/kubernetes/KubernetesParser.java @@ -25,11 +25,12 @@ import java.util.regex.Pattern; import java.util.stream.Stream; +/** + * @deprecated Unused; likely better served by {@link org.openrewrite.kubernetes.trait.Traits}. + */ +@Deprecated public final class KubernetesParser extends YamlParser { - private static final Pattern METADATA_LABEL = Pattern.compile("/metadata/labels/(.+)"); - private static final Pattern METADATA_ANNOTATION = Pattern.compile("/metadata/annotations/(.+)"); - public static Builder builder() { return new Builder(); } diff --git a/src/main/java/org/openrewrite/kubernetes/KubernetesVisitor.java b/src/main/java/org/openrewrite/kubernetes/KubernetesVisitor.java index af10a9e..4274b3e 100644 --- a/src/main/java/org/openrewrite/kubernetes/KubernetesVisitor.java +++ b/src/main/java/org/openrewrite/kubernetes/KubernetesVisitor.java @@ -21,6 +21,10 @@ import org.openrewrite.yaml.YamlVisitor; import org.openrewrite.yaml.tree.Yaml; +/** + * @deprecated Likely better served by {@link org.openrewrite.kubernetes.trait.Traits}. + */ +@Deprecated public class KubernetesVisitor

extends YamlVisitor

{ @Override diff --git a/src/main/java/org/openrewrite/kubernetes/UpdateKubernetesModel.java b/src/main/java/org/openrewrite/kubernetes/UpdateKubernetesModel.java index e9e71db..aa94825 100644 --- a/src/main/java/org/openrewrite/kubernetes/UpdateKubernetesModel.java +++ b/src/main/java/org/openrewrite/kubernetes/UpdateKubernetesModel.java @@ -33,27 +33,26 @@ public class UpdateKubernetesModel

extends YamlIsoVisitor

{ public Yaml.Document visitDocument(Yaml.Document document, P p) { Yaml.Document d = super.visitDocument(document, p); - KubernetesModel resource = new KubernetesModel( - randomId(), - getCursor().getMessage("apiVersion"), - getCursor().getMessage("kind"), - new KubernetesModel.Metadata( - getCursor().getMessage("namespace"), - getCursor().getMessage("name"), - getCursor().getMessage("annotations"), - getCursor().getMessage("labels") - ) - ); - if (resource.getApiVersion() != null && resource.getKind() != null) { - return d.withMarkers(document.getMarkers().addIfAbsent(resource)); - } else { - return d; + if (getCursor().getMessage("apiVersion") != null && + getCursor().getMessage("kind") != null) { + KubernetesModel kubernetesModel = new KubernetesModel( + randomId(), + getCursor().getMessage("apiVersion"), + getCursor().getMessage("kind"), + new KubernetesModel.Metadata( + getCursor().getMessage("namespace"), + getCursor().getMessage("name"), + getCursor().getMessage("annotations"), + getCursor().getMessage("labels") + )); + return d.withMarkers(document.getMarkers().addIfAbsent(kubernetesModel)); } + + return d; } @Override public Yaml.Mapping.Entry visitMappingEntry(Yaml.Mapping.Entry entry, P p) { - String path = getPath(); if (entry.getValue() instanceof Yaml.Scalar) { diff --git a/src/main/java/org/openrewrite/kubernetes/search/FindResourceMissingConfiguration.java b/src/main/java/org/openrewrite/kubernetes/search/FindResourceMissingConfiguration.java index 82649d6..8be9ab9 100644 --- a/src/main/java/org/openrewrite/kubernetes/search/FindResourceMissingConfiguration.java +++ b/src/main/java/org/openrewrite/kubernetes/search/FindResourceMissingConfiguration.java @@ -19,11 +19,17 @@ import lombok.Value; import org.openrewrite.*; import org.openrewrite.internal.lang.Nullable; -import org.openrewrite.kubernetes.tree.K8S; +import org.openrewrite.kubernetes.trait.KubernetesResource; +import org.openrewrite.kubernetes.trait.Traits; import org.openrewrite.marker.SearchResult; +import org.openrewrite.yaml.JsonPathMatcher; import org.openrewrite.yaml.YamlIsoVisitor; import org.openrewrite.yaml.tree.Yaml; +import java.util.concurrent.atomic.AtomicBoolean; + +import static java.util.Objects.requireNonNull; + @Value @EqualsAndHashCode(callSuper = false) public class FindResourceMissingConfiguration extends Recipe { @@ -57,29 +63,26 @@ public String getDescription() { return "Find Kubernetes resources with missing configuration."; } - @Override public TreeVisitor getVisitor() { - YamlIsoVisitor visitor = new YamlIsoVisitor() { - @Override - public Yaml.Document visitDocument(Yaml.Document document, ExecutionContext ctx) { - Yaml.Block b = (Yaml.Block) visit(document.getBlock(), ctx, getCursor()); - boolean inKind = resourceKind == null || K8S.inKind(resourceKind, getCursor()); - if (inKind && !"true".equals(getCursor().getMessage(FindResourceMissingConfiguration.class.getSimpleName()))) { - return SearchResult.found(document.withBlock(b)); - } - return document; - } + TreeVisitor kubernetesResourceVisitor = Traits.kubernetesResource(resourceKind) + .asVisitor((KubernetesResource resource, ExecutionContext ctx) -> { + AtomicBoolean pathFound = new AtomicBoolean(false); + new YamlIsoVisitor() { + @Override + public Yaml.Mapping.Entry visitMappingEntry(Yaml.Mapping.Entry entry, AtomicBoolean bool) { + if (new JsonPathMatcher(configurationPath).matches(getCursor())) { + bool.set(true); + } + return bool.get() ? entry : super.visitMappingEntry(entry, bool); + } + }.visitNonNull(resource.getTree(), pathFound, requireNonNull(resource.getCursor().getParent())); + return pathFound.get() ? resource.getTree() : SearchResult.found(resource.getTree()); + }); - @Override - public Yaml.Mapping.Entry visitMappingEntry(Yaml.Mapping.Entry entry, ExecutionContext ctx) { - if (K8S.firstEnclosingEntryMatching(configurationPath, getCursor()).isPresent()) { - getCursor().putMessageOnFirstEnclosing(Yaml.Document.class, - FindResourceMissingConfiguration.class.getSimpleName(), "true"); - } - return super.visitMappingEntry(entry, ctx); - } - }; - return fileMatcher != null ? Preconditions.check(new FindSourceFiles(fileMatcher), visitor) : visitor; + if (fileMatcher != null) { + return Preconditions.check(new FindSourceFiles(fileMatcher), kubernetesResourceVisitor); + } + return kubernetesResourceVisitor; } } diff --git a/src/main/java/org/openrewrite/kubernetes/trait/KubernetesResource.java b/src/main/java/org/openrewrite/kubernetes/trait/KubernetesResource.java new file mode 100644 index 0000000..7d9ab8d --- /dev/null +++ b/src/main/java/org/openrewrite/kubernetes/trait/KubernetesResource.java @@ -0,0 +1,58 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.kubernetes.trait; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.Cursor; +import org.openrewrite.ExecutionContext; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.internal.lang.Nullable; +import org.openrewrite.kubernetes.UpdateKubernetesModel; +import org.openrewrite.kubernetes.tree.KubernetesModel; +import org.openrewrite.trait.SimpleTraitMatcher; +import org.openrewrite.trait.Trait; +import org.openrewrite.yaml.tree.Yaml; + +@Value +public class KubernetesResource implements Trait { + + Cursor cursor; + KubernetesModel model; + + @Value + @EqualsAndHashCode(callSuper = false) + public static class Matcher extends SimpleTraitMatcher { + + @Nullable + String kind; + + @Override + protected @Nullable KubernetesResource test(Cursor cursor) { + Object value = cursor.getValue(); + if (value instanceof Yaml.Document) { + return new UpdateKubernetesModel() + .visitNonNull((Yaml.Document) value, new InMemoryExecutionContext(), cursor.getParent()) + .getMarkers() + .findFirst(KubernetesModel.class) + .filter(model -> kind == null || kind.equals(model.getKind())) + .map(kubernetesModel -> new KubernetesResource(cursor, kubernetesModel)) + .orElse(null); + } + return null; + } + } +} diff --git a/src/main/java/org/openrewrite/kubernetes/trait/Traits.java b/src/main/java/org/openrewrite/kubernetes/trait/Traits.java new file mode 100644 index 0000000..f31705b --- /dev/null +++ b/src/main/java/org/openrewrite/kubernetes/trait/Traits.java @@ -0,0 +1,28 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.kubernetes.trait; + +import org.openrewrite.internal.lang.Nullable; + +public class Traits { + + private Traits() { + } + + public static KubernetesResource.Matcher kubernetesResource(@Nullable String kind) { + return new KubernetesResource.Matcher(kind); + } +} diff --git a/src/main/java/org/openrewrite/kubernetes/tree/K8S.java b/src/main/java/org/openrewrite/kubernetes/tree/K8S.java index dd3370d..0001b30 100644 --- a/src/main/java/org/openrewrite/kubernetes/tree/K8S.java +++ b/src/main/java/org/openrewrite/kubernetes/tree/K8S.java @@ -22,10 +22,8 @@ import lombok.With; import lombok.experimental.FieldDefaults; import org.openrewrite.Cursor; -import org.openrewrite.Tree; import org.openrewrite.internal.lang.Nullable; import org.openrewrite.kubernetes.resource.ResourceLimit; -import org.openrewrite.marker.Marker; import org.openrewrite.yaml.JsonPathMatcher; import org.openrewrite.yaml.tree.Yaml; @@ -35,8 +33,11 @@ import static java.util.Collections.emptySet; import static org.openrewrite.Tree.randomId; -public interface K8S extends Marker { - +/** + * @deprecated Likely better served by {@link org.openrewrite.kubernetes.trait.Traits}. + */ +@Deprecated +public interface K8S { static boolean inKind(String kind, Cursor cursor) { Yaml.Document doc = cursor.firstEnclosing(Yaml.Document.class); @@ -210,7 +211,7 @@ static Optional firstEnclosingEntryMatching(JsonPathMatcher jsonPath, @N } @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) - @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = false) + @EqualsAndHashCode(callSuper = false) @Data class Resource implements K8S { @EqualsAndHashCode.Include @@ -222,7 +223,7 @@ class Resource implements K8S { } @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) - @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = false) + @EqualsAndHashCode(callSuper = false) @Data class Metadata implements K8S { @EqualsAndHashCode.Include @@ -248,7 +249,7 @@ public static boolean isMetadata(Cursor cursor) { } @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) - @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = false) + @EqualsAndHashCode(callSuper = false) @Data class Annotations implements K8S { @EqualsAndHashCode.Include @@ -282,7 +283,7 @@ public boolean valueMatches(String name, Pattern regex, Cursor cursor) { } @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) - @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = false) + @EqualsAndHashCode(callSuper = false) @Data class Labels implements K8S { @EqualsAndHashCode.Include @@ -312,7 +313,7 @@ public boolean valueMatches(String name, Pattern regex, Cursor cursor) { } @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) - @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = false) + @EqualsAndHashCode(callSuper = false) @Data class Pod implements K8S { @EqualsAndHashCode.Include @@ -326,7 +327,7 @@ public static boolean inSpec(Cursor cursor) { } @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) - @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = false) + @EqualsAndHashCode(callSuper = false) @Data class Containers implements K8S { @EqualsAndHashCode.Include @@ -343,7 +344,7 @@ public static boolean isImageName(Cursor cursor) { } @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) - @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = false) + @EqualsAndHashCode(callSuper = false) @Data class InitContainers implements K8S { @EqualsAndHashCode.Include @@ -356,7 +357,7 @@ public static boolean inInitContainerSpec(Cursor cursor) { } @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) - @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = false) + @EqualsAndHashCode(callSuper = false) @Data class ResourceLimits implements K8S { @EqualsAndHashCode.Include @@ -380,7 +381,7 @@ public static boolean inRequests(String type, Cursor cursor) { } @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) - @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = false) + @EqualsAndHashCode(callSuper = false) @Data class Service implements K8S { @EqualsAndHashCode.Include @@ -406,7 +407,7 @@ public static boolean inExternalIPs(Cursor cursor) { } @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) - @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = false) + @EqualsAndHashCode(callSuper = false) @Data class Ingress implements K8S { @EqualsAndHashCode.Include @@ -434,7 +435,7 @@ public static boolean isDisallowHttpConfigured(Cursor cursor) { } @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) - @EqualsAndHashCode(callSuper = false, onlyExplicitlyIncluded = false) + @EqualsAndHashCode(callSuper = false) @Data class RBAC implements K8S { @EqualsAndHashCode.Include diff --git a/src/test/java/org/openrewrite/kubernetes/search/FindResourceMissingConfigurationTest.java b/src/test/java/org/openrewrite/kubernetes/search/FindResourceMissingConfigurationTest.java index d44a1c1..65d9061 100644 --- a/src/test/java/org/openrewrite/kubernetes/search/FindResourceMissingConfigurationTest.java +++ b/src/test/java/org/openrewrite/kubernetes/search/FindResourceMissingConfigurationTest.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; +import org.openrewrite.Issue; import org.openrewrite.config.Environment; import org.openrewrite.kubernetes.KubernetesRecipeTest; @@ -33,6 +34,7 @@ void podLivenessProbe() { "$.spec.containers[*].livenessProbe", null )), + //language=yaml yaml( """ apiVersion: v1 @@ -54,6 +56,31 @@ void podLivenessProbe() { ); } + @Test + void noChangeIfPresent() { + rewriteRun( + spec -> spec.recipe(new FindResourceMissingConfiguration( + "Pod", + "$.spec.containers[*].livenessProbe", + null + )), + //language=yaml + yaml( + """ + apiVersion: v1 + kind: Pod + spec: + containers: + - name: + image: + livenessProbe: + httpGet: + path: /healthz + """ + ) + ); + } + @Test void correctlyConfiguredPodLivenessProbe() { rewriteRun( @@ -62,6 +89,7 @@ void correctlyConfiguredPodLivenessProbe() { "$.spec.containers[*].livenessProbe", null )), + //language=yaml yaml( """ apiVersion: v1 @@ -91,6 +119,7 @@ void onlyMatchOnConfiguredResources() { "..spec.containers[*].livenessProbe", null )), + //language=yaml yaml( """ apiVersion: apps/v1 @@ -117,10 +146,8 @@ void onlyMatchOnConfiguredResources() { @Test void missingPodLivenessProbe() { rewriteRun( - spec -> spec.recipe(Environment.builder() - .scanRuntimeClasspath() - .build() - .activateRecipes("org.openrewrite.kubernetes.MissingPodLivenessProbe")), + spec -> spec.recipeFromResources("org.openrewrite.kubernetes.MissingPodLivenessProbe"), + //language=yaml yaml( """ apiVersion: apps/v1 @@ -147,12 +174,10 @@ void missingPodLivenessProbe() { } @Test - void missingCpuLimits() { + void cpuLimitsMissing() { rewriteRun( - spec -> spec.recipe(Environment.builder() - .scanRuntimeClasspath() - .build() - .activateRecipes("org.openrewrite.kubernetes.MissingCpuLimits")), + spec -> spec.recipeFromResources("org.openrewrite.kubernetes.MissingCpuLimits"), + //language=yaml yaml( """ apiVersion: apps/v1 @@ -181,4 +206,45 @@ void missingCpuLimits() { ) ); } + + @Test + void cpuLimitsPresent() { + rewriteRun( + spec -> spec.recipeFromResources("org.openrewrite.kubernetes.MissingCpuLimits"), + //language=yaml + yaml( + """ + apiVersion: apps/v1 + kind: Deployment + metadata: + labels: + app: application + spec: + template: + spec: + containers: + - image: nginx:latest + resources: + limits: + cpu: "64Mi" + """ + ) + ); + } + + @Test + @Issue("https://github.com/openrewrite/rewrite-kubernetes/issues/51") + void springApplicationProperties() { + rewriteRun( + spec -> spec.recipeFromResources("org.openrewrite.kubernetes.MissingCpuLimits"), + //language=yaml + yaml( + """ + spring: + application: + foo: hello + """ + ) + ); + } }