Skip to content

Commit 58bcc12

Browse files
authored
Add MigrateToKafkaNative recipe for Testcontainers (#568) (#967)
* Add MigrateToKafkaNative recipe for Testcontainers (#568) * Address review feedback on ReplaceContainerImageName - Use @requiredargsconstructor with @Getter final fields for displayName/description - Make MethodMatcher a static field - Use UsesMethod precondition instead of UsesType - Also handle new DockerImageName(String) constructor calls * Regenerate recipes.csv for new recipes * Use @value instead of @requiredargsconstructor in ReplaceContainerImageName * Replace gvenzl/oracle-xe image in MigrateToOracleFree recipe * Update apache/kafka-native image to 4.0.2 * Add image replacements for ClickHouse, Toxiproxy, and Vault Replace deprecated Docker image names with their current equivalents: - yandex/clickhouse-server -> clickhouse/clickhouse-server - shopify/toxiproxy -> ghcr.io/shopify/toxiproxy - vault -> hashicorp/vault When newImage has no tag, the original tag is preserved.
1 parent 9e37a27 commit 58bcc12

7 files changed

Lines changed: 468 additions & 6 deletions

File tree

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.testing.testcontainers;
17+
18+
import lombok.EqualsAndHashCode;
19+
import lombok.Value;
20+
import org.openrewrite.*;
21+
import org.openrewrite.java.JavaIsoVisitor;
22+
import org.openrewrite.java.MethodMatcher;
23+
import org.openrewrite.java.search.UsesMethod;
24+
import org.openrewrite.java.tree.J;
25+
import org.openrewrite.java.tree.JavaType;
26+
27+
@Value
28+
@EqualsAndHashCode(callSuper = false)
29+
public class ReplaceContainerImageName extends Recipe {
30+
31+
private static final MethodMatcher DOCKER_IMAGE_NAME_PARSE = new MethodMatcher(
32+
"org.testcontainers.utility.DockerImageName parse(String)");
33+
private static final MethodMatcher DOCKER_IMAGE_NAME_CONSTRUCTOR = new MethodMatcher(
34+
"org.testcontainers.utility.DockerImageName <constructor>(String)");
35+
36+
@Option(displayName = "Container class",
37+
description = "The fully qualified name of the container class to match.",
38+
example = "org.testcontainers.containers.KafkaContainer")
39+
String containerClass;
40+
41+
@Option(displayName = "Image prefix to match",
42+
description = "The Docker image prefix to match (e.g. `confluentinc/cp-kafka`).",
43+
example = "confluentinc/cp-kafka")
44+
String imagePrefix;
45+
46+
@Option(displayName = "New image",
47+
description = "The new Docker image to use. When a tag is included (e.g. `apache/kafka-native:4.0.2`), " +
48+
"the entire image string is replaced. When no tag is included (e.g. `clickhouse/clickhouse-server`), " +
49+
"only the image name prefix is replaced and the original tag is preserved.",
50+
example = "apache/kafka-native:4.0.2")
51+
String newImage;
52+
53+
@Override
54+
public String getDisplayName() {
55+
return "Replace container image name";
56+
}
57+
58+
@Override
59+
public String getDescription() {
60+
return "Replace a Docker image name in `DockerImageName.parse(image)` or " +
61+
"`new DockerImageName(image)` constructor arguments for a specific container class.";
62+
}
63+
64+
@Override
65+
public TreeVisitor<?, ExecutionContext> getVisitor() {
66+
MethodMatcher containerConstructor = new MethodMatcher(containerClass + " <constructor>(..)");
67+
return Preconditions.check(new UsesMethod<>(containerConstructor), new JavaIsoVisitor<ExecutionContext>() {
68+
@Override
69+
public J.Literal visitLiteral(J.Literal literal, ExecutionContext ctx) {
70+
J.Literal l = super.visitLiteral(literal, ctx);
71+
if (l.getType() != JavaType.Primitive.String || l.getValue() == null) {
72+
return l;
73+
}
74+
String value = (String) l.getValue();
75+
if (!value.startsWith(imagePrefix)) {
76+
return l;
77+
}
78+
// Verify this literal is inside DockerImageName.parse(...) or new DockerImageName(...)
79+
Cursor parent = getCursor().getParentTreeCursor();
80+
if (parent.getValue() instanceof J.MethodInvocation) {
81+
J.MethodInvocation mi = parent.getValue();
82+
if (!DOCKER_IMAGE_NAME_PARSE.matches(mi)) {
83+
return l;
84+
}
85+
} else if (parent.getValue() instanceof J.NewClass) {
86+
J.NewClass nc = parent.getValue();
87+
if (!DOCKER_IMAGE_NAME_CONSTRUCTOR.matches(nc)) {
88+
return l;
89+
}
90+
} else {
91+
return l;
92+
}
93+
// When newImage has no tag, preserve the original tag
94+
String replacement = newImage.contains(":") ?
95+
newImage : newImage + value.substring(imagePrefix.length());
96+
return l.withValue(replacement)
97+
.withValueSource("\"" + replacement + "\"");
98+
}
99+
});
100+
}
101+
}

0 commit comments

Comments
 (0)