hardenedRecommendations) {
var tooltipBuilder = new StringBuilder("").append("").append(image).append("
");
@@ -211,15 +204,9 @@ static String generateTooltip(String image, AnalysisReport report, String recomm
}
}));
- if (recommendation != null) {
- tooltipBuilder.append("")
- .append("Replace your image with RedHat UBI: ")
- .append(recommendation)
- .append("
");
- }
if (hardenedRecommendations != null && !hardenedRecommendations.isEmpty()) {
tooltipBuilder.append("")
- .append("Red Hat Hardened Image available: ")
+ .append("
Recommended image: ")
.append(String.join(", ", hardenedRecommendations))
.append("
");
}
@@ -245,23 +232,6 @@ static boolean hasIssue(AnalysisReport report) {
.orElse(false);
}
- /** Returns the UBI image recommendation (from source-level dependencies), or null if none. */
- static String getRecommendation(AnalysisReport report, ImageRef imageRef) {
- var deps = Optional.ofNullable(report.getProviders())
- .stream()
- .flatMap(provider -> provider.values().stream())
- .filter(Objects::nonNull)
- .map(ProviderReport::getSources)
- .filter(Objects::nonNull)
- .map(Map::values)
- .flatMap(Collection::stream)
- .filter(Objects::nonNull)
- .map(Source::getDependencies)
- .filter(Objects::nonNull)
- .flatMap(Collection::stream);
- return findMatchingRecommendation(deps, imageRef, DependencyReport::getRef, DependencyReport::getRecommendation);
- }
-
/**
* Returns hardened image references from provider-level recommendations matching the given image.
* Each entry is a displayable image reference suitable for Dockerfile FROM line replacement.
@@ -296,26 +266,6 @@ static List getHardenedRecommendations(AnalysisReport report, ImageRef i
.collect(Collectors.toList());
}
- private static String findMatchingRecommendation(Stream items, ImageRef imageRef,
- Function refExtractor,
- Function recommendationExtractor) {
- return items
- .filter(r -> refExtractor.apply(r) != null)
- .filter(r -> {
- try {
- return imageRef.getPackageURL().equals(refExtractor.apply(r).purl());
- } catch (MalformedPackageURLException e) {
- LOG.warn("Skipping recommendation with malformed PURL", e);
- return false;
- }
- })
- .map(recommendationExtractor)
- .filter(Objects::nonNull)
- .findAny()
- .map(DockerfileAnnotator::toImageName)
- .orElse(null);
- }
-
private static String toImageName(io.github.guacsec.trustifyda.api.PackageRef ref) {
try {
var purl = ref.purl();
@@ -352,13 +302,12 @@ private static String fullyDecode(String value) {
}
@NotNull
- private static HighlightSeverity getHighlightSeverity(AnalysisReport report, String recommendation,
+ private static HighlightSeverity getHighlightSeverity(AnalysisReport report,
List hardenedRecommendations, boolean hasIssue,
@NotNull PsiElement context) {
// Recommendation-only (no vulnerabilities): use INFORMATION severity (blue)
if (!hasIssue) {
- boolean hasAnyRecommendation = recommendation != null
- || (hardenedRecommendations != null && !hardenedRecommendations.isEmpty());
+ boolean hasAnyRecommendation = hardenedRecommendations != null && !hardenedRecommendations.isEmpty();
if (hasAnyRecommendation) {
return HighlightSeverity.INFORMATION;
}
@@ -375,10 +324,7 @@ private static HighlightSeverity getHighlightSeverity(AnalysisReport report, Str
}
}
- // Fallback to original logic if inspection settings can't be determined
- return hasIssue || recommendation == null ?
- HighlightSeverity.ERROR :
- HighlightSeverity.WEAK_WARNING;
+ return HighlightSeverity.ERROR;
}
@Override
@@ -461,18 +407,16 @@ public void apply(@NotNull PsiFile file, Map annotationResult
if (isReportAvailable(report)) {
var hasIssue = hasIssue(report);
boolean recommendationsEnabled = ApiSettingsState.getInstance().recommendationsEnabled;
- var recommendation = recommendationsEnabled
- ? getRecommendation(report, value.getImageRef()) : null;
var hardenedRecommendations = recommendationsEnabled
? getHardenedRecommendations(report, value.getImageRef()) : List.of();
var message = generateMessage(key.getImageName(), report,
- recommendation, hardenedRecommendations);
+ hardenedRecommendations);
var tooltip = generateTooltip(key.getImageName(), report,
- recommendation, hardenedRecommendations);
+ hardenedRecommendations);
elements.forEach(e -> {
- var severity = getHighlightSeverity(report, recommendation,
+ var severity = getHighlightSeverity(report,
hardenedRecommendations, hasIssue, e);
if (e != null) {
var builder = holder
@@ -486,7 +430,6 @@ public void apply(@NotNull PsiFile file, Map annotationResult
builder = builder.enforcedTextAttributes(attrs);
}
builder = builder.withFix(new ImageReportIntentionAction());
- builder = builder.withFix(new UBIIntentionAction());
for (String hardenedImage : hardenedRecommendations) {
builder = builder.withFix(
new HardenedImageIntentionAction(hardenedImage));
diff --git a/src/main/java/org/jboss/tools/intellij/image/HardenedImageIntentionAction.java b/src/main/java/org/jboss/tools/intellij/image/HardenedImageIntentionAction.java
index ba8b06e..463ee69 100644
--- a/src/main/java/org/jboss/tools/intellij/image/HardenedImageIntentionAction.java
+++ b/src/main/java/org/jboss/tools/intellij/image/HardenedImageIntentionAction.java
@@ -29,7 +29,7 @@
/**
* Intention action that replaces a Dockerfile FROM line's image reference
- * with a recommended Red Hat Hardened Image.
+ * with a recommended image.
*/
public class HardenedImageIntentionAction implements IntentionAction {
@@ -41,7 +41,7 @@ public HardenedImageIntentionAction(String imageReference) {
@Override
public @IntentionName @NotNull String getText() {
- return "Replace with Red Hat Hardened Image: " + imageReference;
+ return "Switch to " + imageReference + " for enhanced security";
}
@Override
diff --git a/src/main/java/org/jboss/tools/intellij/image/UBIIntentionAction.java b/src/main/java/org/jboss/tools/intellij/image/UBIIntentionAction.java
deleted file mode 100644
index 17038c3..0000000
--- a/src/main/java/org/jboss/tools/intellij/image/UBIIntentionAction.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2024 Red Hat, Inc.
- * Distributed under license by Red Hat, Inc. All rights reserved.
- * This program is made available under the terms of the
- * Eclipse Public License v2.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v20.html
- *
- * Contributors:
- * Red Hat, Inc. - initial API and implementation
- ******************************************************************************/
-
-package org.jboss.tools.intellij.image;
-
-import com.intellij.codeInsight.intention.IntentionAction;
-import com.intellij.codeInspection.util.IntentionFamilyName;
-import com.intellij.codeInspection.util.IntentionName;
-import org.jboss.tools.intellij.image.build.filetype.DockerfileFileType;
-import com.intellij.ide.BrowserUtil;
-import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.project.Project;
-import com.intellij.psi.PsiFile;
-import com.intellij.util.IncorrectOperationException;
-import org.jetbrains.annotations.NotNull;
-
-import java.net.URI;
-
-public class UBIIntentionAction implements IntentionAction {
-
- public static final String UBI9_BASE_IMAGE_LINK = "https://catalog.redhat.com/software/containers/ubi9/ubi/615bcf606feffc5384e8452e?architecture=amd64&image=66993ed7cb27bf20e0cf2d27";
-
- @Override
- public @IntentionName @NotNull String getText() {
- return "Switch to UBI 9 for enhanced security and enterprise-grade stability";
- }
-
- @Override
- public @NotNull @IntentionFamilyName String getFamilyName() {
- return "RHDA";
- }
-
- @Override
- public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile psiFile) {
- return DockerfileFileType.isDockerfile(psiFile);
- }
-
- @Override
- public void invoke(@NotNull Project project, Editor editor, PsiFile psiFile) throws IncorrectOperationException {
- BrowserUtil.browse(URI.create(UBI9_BASE_IMAGE_LINK));
- }
-
- @Override
- public boolean startInWriteAction() {
- return false;
- }
-}
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index 8df7069..221b12c 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -215,7 +215,6 @@
After the analysis finishes, you can view any recommendations and remediation by clicking the More
actions... menu from the highlighted image name.
Any recommendations for an alternative image does not replace the current image.
-
By clicking Switch to..., you go to Red Hat's Ecosystem Catalog for the recommended image.
You must have the syft and hardenedRecs = DockerfileAnnotator.getHardenedRecommendations(report, imageRef);
-
- assertNotNull("UBI recommendation should be present", ubiRec);
- assertTrue("Should contain ubi path", ubiRec.contains("ubi9/ubi"));
- assertFalse("Hardened recommendations should not be empty", hardenedRecs.isEmpty());
- assertTrue("Should contain hardened path", hardenedRecs.get(0).contains("hardened"));
- }
-
- @Test
- public void testGetRecommendationsReturnEmptyForEmptyReport() throws MalformedPackageURLException {
+ public void testGetHardenedRecommendationsReturnEmptyForEmptyReport() throws MalformedPackageURLException {
PackageURL imagePurl = buildOciPurl("nginx", IMAGE_DIGEST, "docker.io/library/nginx");
ImageRef imageRef = new ImageRef(imagePurl);
AnalysisReport report = new AnalysisReport();
- String ubiRec = DockerfileAnnotator.getRecommendation(report, imageRef);
List hardenedRecs = DockerfileAnnotator.getHardenedRecommendations(report, imageRef);
- assertNull("UBI recommendation should be null for empty report", ubiRec);
assertTrue("Hardened recommendations should be empty for empty report", hardenedRecs.isEmpty());
}
diff --git a/src/test/java/org/jboss/tools/intellij/image/HardenedImageIntentionActionTest.java b/src/test/java/org/jboss/tools/intellij/image/HardenedImageIntentionActionTest.java
index b0d7961..f00ebd0 100644
--- a/src/test/java/org/jboss/tools/intellij/image/HardenedImageIntentionActionTest.java
+++ b/src/test/java/org/jboss/tools/intellij/image/HardenedImageIntentionActionTest.java
@@ -49,7 +49,7 @@ public class HardenedImageIntentionActionTest {
@Test
public void testGetText_containsImageReference() {
var action = new HardenedImageIntentionAction("quay.io/hardened/nginx");
- assertEquals("Replace with Red Hat Hardened Image: quay.io/hardened/nginx", action.getText());
+ assertEquals("Switch to quay.io/hardened/nginx for enhanced security", action.getText());
}
/** Verifies that the family name matches the RHDA convention. */
@@ -263,23 +263,10 @@ public void testIsReportAvailable_trueForBothVulnsAndRecommendations() throws Ma
public void testGenerateMessage_withHardenedRecommendation() {
AnalysisReport report = new AnalysisReport();
String message = DockerfileAnnotator.generateMessage("nginx:latest", report,
- null, List.of("quay.io/hardened/nginx"));
+ List.of("quay.io/hardened/nginx"));
- assertTrue("Should contain hardened recommendation",
- message.contains("Red Hat Hardened Image available: quay.io/hardened/nginx"));
- }
-
- /** Verifies message includes both UBI and hardened recommendation text. */
- @Test
- public void testGenerateMessage_withBothRecommendations() {
- AnalysisReport report = new AnalysisReport();
- String message = DockerfileAnnotator.generateMessage("nginx:latest", report,
- "ubi9/ubi", List.of("quay.io/hardened/nginx"));
-
- assertTrue("Should contain UBI recommendation",
- message.contains("Replace your image with RedHat UBI: ubi9/ubi"));
- assertTrue("Should contain hardened recommendation",
- message.contains("Red Hat Hardened Image available: quay.io/hardened/nginx"));
+ assertTrue("Should contain recommendation",
+ message.contains("Recommended image: quay.io/hardened/nginx"));
}
/** Verifies message with multiple hardened recommendations lists them comma-separated. */
@@ -287,7 +274,7 @@ public void testGenerateMessage_withBothRecommendations() {
public void testGenerateMessage_withMultipleHardenedRecommendations() {
AnalysisReport report = new AnalysisReport();
String message = DockerfileAnnotator.generateMessage("nginx:latest", report,
- null, List.of("quay.io/hardened/nginx", "quay.io/hardened/nginx-alt"));
+ List.of("quay.io/hardened/nginx", "quay.io/hardened/nginx-alt"));
assertTrue("Should contain both hardened recommendations",
message.contains("quay.io/hardened/nginx, quay.io/hardened/nginx-alt"));
@@ -298,7 +285,7 @@ public void testGenerateMessage_withMultipleHardenedRecommendations() {
public void testGenerateMessage_withNoRecommendations() {
AnalysisReport report = new AnalysisReport();
String message = DockerfileAnnotator.generateMessage("nginx:latest", report,
- null, List.of());
+ List.of());
assertEquals("Should only contain image name", "nginx:latest", message);
}
@@ -310,10 +297,10 @@ public void testGenerateMessage_withNoRecommendations() {
public void testGenerateTooltip_withHardenedRecommendation() {
AnalysisReport report = new AnalysisReport();
String tooltip = DockerfileAnnotator.generateTooltip("nginx:latest", report,
- null, List.of("quay.io/hardened/nginx"));
+ List.of("quay.io/hardened/nginx"));
- assertTrue("Should contain hardened recommendation",
- tooltip.contains("Red Hat Hardened Image available: quay.io/hardened/nginx"));
+ assertTrue("Should contain recommendation",
+ tooltip.contains("Recommended image: quay.io/hardened/nginx"));
}
// ── Helper methods ──────────────────────────────────────────────────────