Skip to content

Commit dee579b

Browse files
authored
Revert "Upgrade ARG default values used in Docker FROM instructions (#1213)" (#1215)
This reverts commit d33eca3. The recipe reached for rewrite-docker API that no released version of rewrite carries yet. `Docker.Argument.getText()`, `getTextWithVariables()` and `hasEnvironmentVariables()` arrived in openrewrite/rewrite#8576, and `org.openrewrite.docker.trait.ImageName` in openrewrite/rewrite#8599, both landed 2026-08-21, one day after v8.90.3. The Moderne CLI loads the LST classes in its own classloader, so a recipe runs against the rewrite-docker the CLI bundles rather than the one this artifact resolves. CLI 4.6.3 bundles rewrite-docker 8.90.3, where `Docker.Argument` exposes only `getContents()` and `ImageName` does not exist. `visitFile` reads a global `ARG` through `getText()` and `visitFrom` opens with `hasEnvironmentVariables()`, so the first `FROM` of every Dockerfile would raise `NoSuchMethodError`, surfacing as error markup on every Dockerfile of every `UpgradeToJava*` run. Nothing is lost by waiting: v3.42.1 predates this commit, so the breakage has not shipped. The state restored here is the #1212 fix, which reads an image reference through `DockerFrom` alone and so links against 8.90.3. Reapplied in a follow-up PR, to merge once a rewrite release carries #8576, #8590 and #8599 and the CLI picks it up.
1 parent d33eca3 commit dee579b

3 files changed

Lines changed: 53 additions & 577 deletions

File tree

src/main/java/org/openrewrite/java/migrate/UpgradeDockerImageVersion.java

Lines changed: 24 additions & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -17,28 +17,18 @@
1717

1818
import lombok.EqualsAndHashCode;
1919
import lombok.Value;
20-
import org.jspecify.annotations.Nullable;
2120
import org.openrewrite.ExecutionContext;
2221
import org.openrewrite.Option;
2322
import org.openrewrite.Recipe;
2423
import org.openrewrite.TreeVisitor;
25-
import org.openrewrite.docker.DockerIsoVisitor;
2624
import org.openrewrite.docker.trait.DockerFrom;
27-
import org.openrewrite.docker.trait.ImageName;
28-
import org.openrewrite.docker.tree.Docker;
29-
import org.openrewrite.internal.ListUtils;
3025

31-
import java.util.HashMap;
3226
import java.util.HashSet;
33-
import java.util.List;
34-
import java.util.Map;
3527
import java.util.Set;
3628
import java.util.regex.Matcher;
3729
import java.util.regex.Pattern;
3830

3931
import static java.util.Arrays.asList;
40-
import static java.util.Collections.emptyMap;
41-
import static java.util.Objects.requireNonNull;
4232

4333
@EqualsAndHashCode(callSuper = false)
4434
@Value
@@ -60,208 +50,48 @@ public class UpgradeDockerImageVersion extends Recipe {
6050
private static final int OLDEST_VERSION = 8;
6151
private static final Pattern VERSIONED_TAG = Pattern.compile("(\\d{1,3})(\\D.*)?");
6252

63-
private static final String ARG_DEFAULTS = "argDefaults";
64-
private static final String ARG_UPGRADES = "argUpgrades";
65-
6653
String displayName = "Upgrade Docker image Java version";
6754
String description = "Upgrade Docker image tags to use the specified Java version. " +
6855
"Updates common Java Docker images including eclipse-temurin, amazoncorretto, azul/zulu-openjdk, " +
6956
"and others. Also migrates deprecated images (openjdk, adoptopenjdk) to eclipse-temurin, " +
70-
"preserving any tag suffix such as `-jre-alpine`. When a `FROM` is built from a build argument, the " +
71-
"default value of the corresponding global `ARG` is upgraded instead, such that `ARG java_version=17` " +
72-
"used as `FROM eclipse-temurin:${java_version}` becomes `ARG java_version=25`. Image references built " +
73-
"from arguments without a default value are left untouched, as their value can not be determined " +
74-
"statically. A digest pin is dropped when the tag is upgraded, as the stale digest would otherwise " +
75-
"keep resolving to the old image.";
57+
"preserving any tag suffix such as `-jre-alpine`. Image references built from build arguments or " +
58+
"environment variables are left untouched, as their value can not be determined statically. A digest " +
59+
"pin is dropped when the tag is upgraded, as the stale digest would otherwise keep resolving to the " +
60+
"old image.";
7661

7762
@Override
7863
public TreeVisitor<?, ExecutionContext> getVisitor() {
7964
if (version == null) {
8065
return TreeVisitor.noop();
8166
}
82-
return new DockerIsoVisitor<ExecutionContext>() {
83-
84-
@Override
85-
public Docker.File visitFile(Docker.File file, ExecutionContext ctx) {
86-
Map<String, String> defaults = new HashMap<>();
87-
for (Docker.Arg arg : file.getGlobalArgs()) {
88-
String value = arg.getValue() == null ? null : arg.getValue().getText();
89-
if (value != null) {
90-
defaults.put(arg.getName().getText(), value);
91-
}
92-
}
93-
94-
Map<String, String> upgrades = new HashMap<>();
95-
getCursor().putMessage(ARG_DEFAULTS, defaults);
96-
getCursor().putMessage(ARG_UPGRADES, upgrades);
97-
Docker.File f = super.visitFile(file, ctx);
98-
if (upgrades.isEmpty()) {
99-
return f;
100-
}
101-
return f.withGlobalArgs(ListUtils.map(f.getGlobalArgs(), arg -> {
102-
String name = arg.getName().getText();
103-
String upgraded = upgrades.get(name);
104-
// A name may be declared more than once; only the declaration the default was read from moves
105-
if (upgraded == null || arg.getValue() == null || !defaults.get(name).equals(arg.getValue().getText())) {
106-
return arg;
107-
}
108-
return arg.withValue(withText(arg.getValue(), upgraded));
109-
}));
67+
return new DockerFrom.Matcher().asVisitor((image, ctx) -> {
68+
String imageName = image.getImageName().orElse("");
69+
String tag = image.getTag().orElse("");
70+
if (containsVariable(imageName) || containsVariable(tag)) {
71+
return image.getTree();
11072
}
11173

112-
@Override
113-
public Docker.From visitFrom(Docker.From from, ExecutionContext ctx) {
114-
if (containsVariable(from.getImageName()) || containsVariable(from.getTag())) {
115-
return upgradeThroughArgs(from,
116-
getCursor().getNearestMessage(ARG_DEFAULTS, emptyMap()),
117-
getCursor().getNearestMessage(ARG_UPGRADES, new HashMap<>()));
118-
}
119-
120-
DockerFrom image = new DockerFrom(getCursor());
121-
String newTag = upgradedTag(image.getTag().orElse(""));
122-
if (newTag == null) {
123-
return from;
124-
}
125-
String imageName = image.getImageName().orElse("");
126-
String newImageName = upgradedImageName(imageName);
127-
if (newImageName == null) {
128-
return from;
129-
}
130-
if (!newImageName.equals(imageName)) {
131-
return image.withImageReference(newImageName + ":" + newTag);
132-
}
133-
return image.withTag(newTag).withDigest(null);
74+
Matcher matcher = VERSIONED_TAG.matcher(tag);
75+
if (!matcher.matches()) {
76+
return image.getTree();
13477
}
135-
};
136-
}
137-
138-
private Docker.From upgradeThroughArgs(Docker.From from, Map<String, String> defaults, Map<String, String> upgrades) {
139-
String imageVariable = soleVariable(from.getImageName());
140-
String tagVariable = from.getTag() == null ? null : leadingVariable(from.getTag());
141-
String imageName = imageVariable == null ?
142-
from.getImageName().getTextWithVariables() :
143-
defaults.get(imageVariable);
144-
if (imageName == null) {
145-
return from;
146-
}
147-
148-
String tag;
149-
boolean wholeReference = from.getTag() == null;
150-
if (wholeReference) {
151-
// A single argument holding the whole reference, as in `FROM ${BASE_IMAGE}`
152-
String[] reference = imageVariable == null ? null : splitReference(imageName);
153-
if (reference == null) {
154-
return from;
78+
int currentVersion = Integer.parseInt(matcher.group(1));
79+
if (currentVersion < OLDEST_VERSION || version <= currentVersion) {
80+
return image.getTree();
15581
}
156-
imageName = reference[0];
157-
tag = reference[1];
158-
} else {
159-
tag = tagVariable == null ? from.getTag().getText() : defaults.get(tagVariable);
160-
}
161-
if (tag == null) {
162-
return from;
163-
}
164-
165-
String newImageName = upgradedImageName(imageName);
166-
String newTag = upgradedTag(tag);
167-
if (newImageName == null || newTag == null) {
168-
return from;
169-
}
17082

171-
if (wholeReference) {
172-
upgrades.put(requireNonNull(imageVariable), newImageName + ":" + newTag);
173-
return from.withDigest(null);
174-
}
175-
if (tagVariable == null) {
176-
from = from.withTag(withText(requireNonNull(from.getTag()), newTag));
177-
} else {
178-
upgrades.put(tagVariable, newTag);
179-
}
180-
if (!newImageName.equals(imageName)) {
181-
if (imageVariable == null) {
182-
from = from.withImageName(withRepository(from.getImageName(), imageName, newImageName));
183-
} else {
184-
upgrades.put(imageVariable, newImageName);
83+
String newTag = version + (matcher.group(2) == null ? "" : matcher.group(2));
84+
if (DEPRECATED_IMAGES.contains(imageName)) {
85+
return image.withImageReference(NEW_IMAGE + ":" + newTag);
18586
}
186-
}
187-
return from.withDigest(null);
188-
}
189-
190-
private @Nullable String upgradedImageName(String imageName) {
191-
ImageName parsed = ImageName.parse(imageName);
192-
String path = parsed.getPath();
193-
if (DEPRECATED_IMAGES.contains(path)) {
194-
String registry = parsed.getRegistry();
195-
return registry == null ? NEW_IMAGE : registry + '/' + NEW_IMAGE;
196-
}
197-
return CURRENT_IMAGES.contains(path) ? imageName : null;
198-
}
199-
200-
private @Nullable String upgradedTag(String tag) {
201-
Matcher matcher = VERSIONED_TAG.matcher(tag);
202-
if (!matcher.matches()) {
203-
return null;
204-
}
205-
int currentVersion = Integer.parseInt(matcher.group(1));
206-
if (currentVersion < OLDEST_VERSION || version <= currentVersion) {
207-
return null;
208-
}
209-
return version + (matcher.group(2) == null ? "" : matcher.group(2));
210-
}
211-
212-
private static boolean containsVariable(Docker.@Nullable Argument argument) {
213-
return argument != null && argument.hasEnvironmentVariables();
214-
}
215-
216-
private static @Nullable String soleVariable(Docker.Argument argument) {
217-
List<Docker.ArgumentContent> contents = argument.getContents();
218-
return contents.size() == 1 && contents.get(0) instanceof Docker.EnvironmentVariable ?
219-
((Docker.EnvironmentVariable) contents.get(0)).getName() : null;
220-
}
221-
222-
private static @Nullable String leadingVariable(Docker.Argument argument) {
223-
List<Docker.ArgumentContent> contents = argument.getContents();
224-
if (contents.isEmpty() || !(contents.get(0) instanceof Docker.EnvironmentVariable)) {
225-
return null;
226-
}
227-
for (int i = 1; i < contents.size(); i++) {
228-
if (!(contents.get(i) instanceof Docker.Literal)) {
229-
return null;
230-
}
231-
}
232-
return ((Docker.EnvironmentVariable) contents.get(0)).getName();
233-
}
234-
235-
private static String @Nullable [] splitReference(String reference) {
236-
int at = reference.indexOf('@');
237-
String withoutDigest = at == -1 ? reference : reference.substring(0, at);
238-
int colon = withoutDigest.indexOf(':', withoutDigest.lastIndexOf('/') + 1);
239-
if (colon == -1) {
240-
return null;
241-
}
242-
return new String[]{withoutDigest.substring(0, colon), withoutDigest.substring(colon + 1)};
243-
}
244-
245-
/// The registry an image is pulled from is left as written, which may be a variable, so only the trailing
246-
/// repository is rewritten.
247-
private static Docker.Argument withRepository(Docker.Argument imageName, String from, String to) {
248-
String oldPath = ImageName.parse(from).getPath();
249-
String newPath = ImageName.parse(to).getPath();
250-
return imageName.withContents(ListUtils.mapLast(imageName.getContents(), content -> {
251-
if (!(content instanceof Docker.Literal)) {
252-
return content;
87+
if (CURRENT_IMAGES.contains(imageName)) {
88+
return image.withTag(newTag).withDigest(null);
25389
}
254-
Docker.Literal literal = (Docker.Literal) content;
255-
String text = literal.getText();
256-
return text.endsWith(oldPath) ?
257-
literal.withText(text.substring(0, text.length() - oldPath.length()) + newPath) :
258-
literal;
259-
}));
90+
return image.getTree();
91+
});
26092
}
26193

262-
private static Docker.Argument withText(Docker.Argument argument, String text) {
263-
return argument.withContents(ListUtils.mapLast(argument.getContents(),
264-
content -> content instanceof Docker.Literal ? ((Docker.Literal) content).withText(text) : content));
94+
private static boolean containsVariable(String imageReferencePart) {
95+
return imageReferencePart.indexOf('$') != -1;
26596
}
266-
26797
}

0 commit comments

Comments
 (0)