Skip to content
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
4 changes: 1 addition & 3 deletions src/main/java/org/openrewrite/github/ChangeAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,7 @@ public class ChangeAction extends Recipe {
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(
Preconditions.or(
new IsGitHubActionsWorkflow().getVisitor(),
new IsGitHubActionDefinition().getVisitor()),
new IsGitHubActionsFile(),
new ChangeUsesVisitor(
"$..[?(@.uses =~ '" + oldAction + "(?:@.+)?')].uses",
oldSha,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@ public class ChangeActionVersion extends Recipe {
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(
Preconditions.or(
new IsGitHubActionsWorkflow().getVisitor(),
new IsGitHubActionDefinition().getVisitor()),
new IsGitHubActionsFile(),
new ChangeUsesVisitor(
"$..[?(@.uses =~ '" + action + "(?:@.+)?')].uses",
oldSha,
Expand Down
42 changes: 42 additions & 0 deletions src/main/java/org/openrewrite/github/IsGitHubActionsFile.java

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense to combine, and a static call saves some char, but its the first time I've seen this pattern in our (java-based) recipes. I wonder if we should introduce this or stick with the ususal patterns..

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2026 the original author or authors.
* <p>
* Licensed under the Moderne Source Available License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://docs.moderne.io/licensing/moderne-source-available-license
* <p>
* 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.github;

import lombok.Getter;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;

public class IsGitHubActionsFile extends Recipe {

@Getter
final String displayName = "Is GitHub Actions workflow or action definition";

@Getter
final String description = "Checks if the file is either a GitHub Actions workflow file, or a GitHub Action " +
"definition (`action.yml`). Steps, and the `uses:` references within them, appear in both, so prefer " +
"this over `IsGitHubActionsWorkflow` as a precondition for any recipe that operates on steps. Recipes " +
"that read workflow-only keys such as `on:`, `permissions:`, `runs-on:` or `needs:` should keep the " +
"narrower `IsGitHubActionsWorkflow`.";

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.or(
new IsGitHubActionsWorkflow().getVisitor(),
new IsGitHubActionDefinition().getVisitor());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public class SetupJavaAdoptOpenJDKToTemurin extends Recipe {

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(new IsGitHubActionsWorkflow(), new SetupJavaDistributionReplacerVisitor(Arrays.asList("adopt", "adopt-hotspot"), "temurin"));
return Preconditions.check(new IsGitHubActionsFile(), new SetupJavaDistributionReplacerVisitor(Arrays.asList("adopt", "adopt-hotspot"), "temurin"));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public class SetupJavaAdoptOpenj9ToSemeru extends Recipe {

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(new IsGitHubActionsWorkflow(),
return Preconditions.check(new IsGitHubActionsFile(),
new SetupJavaDistributionReplacerVisitor(singletonList("adopt-openj9"), "semeru"));
}

Expand Down
12 changes: 6 additions & 6 deletions src/main/java/org/openrewrite/github/SetupJavaCaching.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,26 @@ public class SetupJavaCaching extends Recipe {

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(new IsGitHubActionsWorkflow(), new YamlVisitor<ExecutionContext>() {
return Preconditions.check(new IsGitHubActionsFile(), new YamlVisitor<ExecutionContext>() {
@Override
public Yaml visitDocuments(Yaml.Documents documents, ExecutionContext ctx) {
Yaml.Documents d = documents;
if (!FindKey.find(documents, "$.jobs..steps[?(@.run =~ '.*gradle.*')]").isEmpty()) {
d = (Yaml.Documents) new MergeYaml("$.jobs..steps[?(@.uses =~ 'actions/setup-java(?:@v.+)?')]",
if (!FindKey.find(documents, "$..steps[?(@.run =~ '.*gradle.*')]").isEmpty()) {
d = (Yaml.Documents) new MergeYaml("$..steps[?(@.uses =~ 'actions/setup-java(?:@v.+)?')]",
"" +
"with:\n" +
" cache: 'gradle'", true, null, null, null, null, null)
.getVisitor().visitNonNull(d, ctx);
}
if (!FindKey.find(documents, "$.jobs..steps[?(@.run =~ '.*mvn.*')]").isEmpty()) {
d = (Yaml.Documents) new MergeYaml("$.jobs..steps[?(@.uses =~ 'actions/setup-java(?:@v.+)?')]",
if (!FindKey.find(documents, "$..steps[?(@.run =~ '.*mvn.*')]").isEmpty()) {
d = (Yaml.Documents) new MergeYaml("$..steps[?(@.uses =~ 'actions/setup-java(?:@v.+)?')]",
"" +
"with:\n" +
" cache: 'maven'", true, null, null, null, null, null)
.getVisitor().visitNonNull(d, ctx);
}
if (d != documents) {
d = (Yaml.Documents) new DeleteKey("$.jobs..steps[?(@.uses =~ 'actions/cache(?:@v.+)?')]", null)
d = (Yaml.Documents) new DeleteKey("$..steps[?(@.uses =~ 'actions/cache(?:@v.+)?')]", null)
.getVisitor().visitNonNull(d, ctx);
}
return d;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public class SetupJavaUpgradeJavaVersion extends Recipe {

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(new IsGitHubActionsWorkflow(), new UpgradeJavaVersionVisitor(
return Preconditions.check(new IsGitHubActionsFile(), new UpgradeJavaVersionVisitor(
minimumJavaMajorVersion == null ? 21 : minimumJavaMajorVersion
));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public class SetupNodeUpgradeNodeVersion extends Recipe {

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(new IsGitHubActionsWorkflow(), new UpgradeNodeVersionVisitor(
return Preconditions.check(new IsGitHubActionsFile(), new UpgradeNodeVersionVisitor(
minimumNodeMajorVersion == null ? 24 : minimumNodeMajorVersion
));
}
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/openrewrite/github/SetupPythonToUv.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public class SetupPythonToUv extends Recipe {
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(
new IsGitHubActionsWorkflow(),
new IsGitHubActionsFile(),
new SetupPythonToUvVisitor(
uvVersion != null ? uvVersion : "v6",
mapSyncStrategy(syncStrategy != null ? syncStrategy : "basic"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public Validated<Object> validate() {

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(new IsGitHubActionsWorkflow(), new YamlVisitor<ExecutionContext>() {
return Preconditions.check(new IsGitHubActionsFile(), new YamlVisitor<ExecutionContext>() {
@Override
public Yaml visitMappingEntry(Yaml.Mapping.Entry entry, ExecutionContext ctx) {
if (!"python-version".equals(entry.getKey().getValue()) ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public Accumulator getInitialValue(ExecutionContext ctx) {

@Override
public TreeVisitor<?, ExecutionContext> getScanner(Accumulator acc) {
return Preconditions.check(workflowOrActionDefinition(), new YamlIsoVisitor<ExecutionContext>() {
return Preconditions.check(new IsGitHubActionsFile(), new YamlIsoVisitor<ExecutionContext>() {
@Override
public Yaml.Mapping.Entry visitMappingEntry(Yaml.Mapping.Entry entry, ExecutionContext ctx) {
if (entry.getKey() instanceof Yaml.Scalar &&
Expand Down Expand Up @@ -85,7 +85,7 @@ public TreeVisitor<?, ExecutionContext> getVisitor(Accumulator acc) {
replacements.put(target.getAction() + '@' + target.getCurrentRef(),
target.getAction() + '@' + target.getTarget());
}
return Preconditions.check(workflowOrActionDefinition(), new YamlIsoVisitor<ExecutionContext>() {
return Preconditions.check(new IsGitHubActionsFile(), new YamlIsoVisitor<ExecutionContext>() {
@Override
public Yaml.Mapping.Entry visitMappingEntry(Yaml.Mapping.Entry entry, ExecutionContext ctx) {
Yaml.Mapping.Entry e = super.visitMappingEntry(entry, ctx);
Expand All @@ -103,16 +103,6 @@ public Yaml.Mapping.Entry visitMappingEntry(Yaml.Mapping.Entry entry, ExecutionC
});
}

/**
* Official action references appear both in workflow files ({@code $.jobs..steps[].uses}) and in
* the {@code runs.steps[].uses} of composite action definitions, so both file types are matched.
*/
private static TreeVisitor<?, ExecutionContext> workflowOrActionDefinition() {
return Preconditions.or(
new IsGitHubActionsWorkflow().getVisitor(),
new IsGitHubActionDefinition().getVisitor());
}

private static Map<String, String> loadKnownShas() {
try (InputStream is = UpgradeOfficialGitHubActions.class
.getResourceAsStream("/META-INF/rewrite/known-action-shas.properties")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public class UpgradeSlackNotificationVersion2 extends Recipe {

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(new IsGitHubActionsWorkflow(), new UpgradeSlackNotificationActionVisitor());
return Preconditions.check(new IsGitHubActionsFile(), new UpgradeSlackNotificationActionVisitor());
}

@AllArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import lombok.Value;
import org.jspecify.annotations.Nullable;
import org.openrewrite.*;
import org.openrewrite.github.IsGitHubActionsWorkflow;
import org.openrewrite.github.IsGitHubActionsFile;
import org.openrewrite.marker.SearchResult;
import org.openrewrite.yaml.JsonPathMatcher;
import org.openrewrite.yaml.YamlIsoVisitor;
Expand Down Expand Up @@ -68,7 +68,7 @@ public class ArtifactSecurity extends Recipe {
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(
new IsGitHubActionsWorkflow(),
new IsGitHubActionsFile(),
new ArtifactSecurityVisitor()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import lombok.Value;
import org.jspecify.annotations.Nullable;
import org.openrewrite.*;
import org.openrewrite.github.IsGitHubActionsWorkflow;
import org.openrewrite.github.IsGitHubActionsFile;
import org.openrewrite.marker.SearchResult;
import org.openrewrite.yaml.YamlIsoVisitor;
import org.openrewrite.yaml.tree.Yaml;
Expand Down Expand Up @@ -109,7 +109,7 @@ public ForbiddenUses(
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(
new IsGitHubActionsWorkflow(),
new IsGitHubActionsFile(),
new ForbiddenUsesVisitor(allDangerousActions, allSuspiciousPatterns)
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.github.IsGitHubActionsWorkflow;
import org.openrewrite.github.IsGitHubActionsFile;
import org.openrewrite.marker.SearchResult;
import org.openrewrite.yaml.YamlIsoVisitor;
import org.openrewrite.yaml.tree.Yaml;
Expand All @@ -47,7 +47,7 @@ public class Obfuscation extends Recipe {
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(
new IsGitHubActionsWorkflow(),
new IsGitHubActionsFile(),
new ObfuscationVisitor()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import lombok.Value;
import org.jspecify.annotations.Nullable;
import org.openrewrite.*;
import org.openrewrite.github.IsGitHubActionsWorkflow;
import org.openrewrite.github.IsGitHubActionsFile;
import org.openrewrite.ipc.http.HttpSender;
import org.openrewrite.yaml.YamlIsoVisitor;
import org.openrewrite.yaml.tree.Yaml;
Expand Down Expand Up @@ -128,7 +128,7 @@ public TreeVisitor<?, ExecutionContext> getVisitor(Map<String, String> knownShas
String apiToken = githubApiToken;
List<String> allowList = includedActions == null ? emptyList() : includedActions;
return Preconditions.check(
new IsGitHubActionsWorkflow(),
new IsGitHubActionsFile(),
new YamlIsoVisitor<ExecutionContext>() {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.openrewrite.*;
import org.openrewrite.github.IsGitHubActionsWorkflow;
import org.openrewrite.github.IsGitHubActionsFile;
import org.openrewrite.marker.SearchResult;
import org.openrewrite.yaml.YamlIsoVisitor;
import org.openrewrite.yaml.tree.Yaml;
Expand Down Expand Up @@ -49,7 +49,7 @@ public class RefVersionMismatch extends Recipe {
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(
new IsGitHubActionsWorkflow(),
new IsGitHubActionsFile(),
new RefVersionMismatchVisitor()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.github.IsGitHubActionsWorkflow;
import org.openrewrite.github.IsGitHubActionsFile;
import org.openrewrite.marker.SearchResult;
import org.openrewrite.yaml.JsonPathMatcher;
import org.openrewrite.yaml.YamlIsoVisitor;
Expand Down Expand Up @@ -86,7 +86,7 @@ public class TemplateInjection extends Recipe {
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(
new IsGitHubActionsWorkflow(),
new IsGitHubActionsFile(),
new TemplateInjectionVisitor()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.openrewrite.*;
import org.openrewrite.github.IsGitHubActionsWorkflow;
import org.openrewrite.github.IsGitHubActionsFile;
import org.openrewrite.marker.SearchResult;
import org.openrewrite.yaml.YamlIsoVisitor;
import org.openrewrite.yaml.tree.Yaml;
Expand Down Expand Up @@ -68,7 +68,7 @@ public class TrustedPublishing extends Recipe {
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(
new IsGitHubActionsWorkflow(),
new IsGitHubActionsFile(),
new TrustedPublishingVisitor()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.github.IsGitHubActionsWorkflow;
import org.openrewrite.github.IsGitHubActionsFile;
import org.openrewrite.marker.SearchResult;
import org.openrewrite.yaml.YamlIsoVisitor;
import org.openrewrite.yaml.tree.Yaml;
Expand All @@ -48,7 +48,7 @@ public class UnpinnedActions extends Recipe {
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(
new IsGitHubActionsWorkflow(),
new IsGitHubActionsFile(),
new UnpinnedActionsVisitor()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.github.IsGitHubActionsWorkflow;
import org.openrewrite.github.IsGitHubActionsFile;
import org.openrewrite.marker.SearchResult;
import org.openrewrite.yaml.YamlIsoVisitor;
import org.openrewrite.yaml.tree.Yaml;
Expand All @@ -48,7 +48,7 @@ public class UnpinnedDockerImages extends Recipe {
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(
new IsGitHubActionsWorkflow(),
new IsGitHubActionsFile(),
new UnpinnedDockerImagesVisitor()
);
}
Expand All @@ -71,6 +71,12 @@ public Yaml.Mapping.Entry visitMappingEntry(Yaml.Mapping.Entry entry, ExecutionC
return mappingEntry;
}

private boolean isLocalDockerfile(String imageValue) {
return !imageValue.startsWith("docker://") &&
(imageValue.startsWith("./") || imageValue.startsWith("../") ||
"Dockerfile".equals(imageValue) || imageValue.endsWith("/Dockerfile"));
}

private boolean isImageEntry(Yaml.Mapping.Entry entry) {
return "image".equals(entry.getKey().getValue());
}
Expand All @@ -80,6 +86,11 @@ private String getImageValue(Yaml.Mapping.Entry entry) {
}

private boolean isUnpinnedDockerImage(String imageValue) {
// A Docker container action may build from a local Dockerfile, which has no digest to pin
if (isLocalDockerfile(imageValue)) {
return false;
}

// Handle docker:// prefix
String cleanImage = imageValue;
if (cleanImage.startsWith("docker://")) {
Expand Down
Loading
Loading