diff --git a/client/src/main/java/dev/snowdrop/buildpack/BuilderImage.java b/client/src/main/java/dev/snowdrop/buildpack/BuilderImage.java index 4ddf60a..98b082e 100644 --- a/client/src/main/java/dev/snowdrop/buildpack/BuilderImage.java +++ b/client/src/main/java/dev/snowdrop/buildpack/BuilderImage.java @@ -187,14 +187,19 @@ private ImageReference getRunImageFromMetadataJSON() throws BuildpackException { // if caller did not set runImage, and metadata is absent, error. if(ri==null){ - throw new Exception("No runImage specified, and builderImage is missing metadata declaration"); + throw new Exception("No runImage specified, and builderImage is missing metadata declaration :: "+this.metadataJson); } - // remap docker.io references back to docker.io - // if (ri.startsWith("index.docker.io/")) { - // ri = ri.substring("index.docker.io/".length()); - // ri = "docker.io/" + ri; - // } + //first observed with ubi9 builder, stack/runImage/image is empty string + //image is present in the images array, so fallback to that. + if(ri.trim().isEmpty()){ + //fallback to looking for runImage in the images array.. + String fbImg = JsonUtils.getValue(root, "images/0/image"); + if(fbImg==null || fbImg.trim().isEmpty()){ + throw new Exception("No runImage specified, and builderImage is missing metadata declaration :: (fbImage:"+fbImg+") "+this.metadataJson); + } + ri = fbImg; + } return new ImageReference(ri); } catch (Exception e) { diff --git a/client/src/main/java/dev/snowdrop/buildpack/utils/JsonUtils.java b/client/src/main/java/dev/snowdrop/buildpack/utils/JsonUtils.java index 3eae5b9..d16fcc1 100644 --- a/client/src/main/java/dev/snowdrop/buildpack/utils/JsonUtils.java +++ b/client/src/main/java/dev/snowdrop/buildpack/utils/JsonUtils.java @@ -12,14 +12,23 @@ public static String getValue(JsonNode root, String path) { path = path.substring(1); } String[] parts = path.split("/"); - JsonNode next = root.get(parts[0]); + JsonNode next = null; + if(!root.isArray()){ + next = root.get(parts[0]); + }else{ + try{ + next = root.get(Integer.parseInt(parts[0])); + }catch(NumberFormatException nfe){ + throw new IllegalArgumentException("Invalid path, expected array index but got: "+parts[0]+" for path: "+path); + } + } if (next != null && parts.length > 1) { return getValue(next, path.substring(path.indexOf("/") + 1)); } if (next == null) { return null; - } - return next.asText(); + } + return next.asText(); } public static List getArray(JsonNode root, String path) { if (path.startsWith("/")) { diff --git a/client/src/test/java/dev/snowdrop/buildpack/utils/JsonUtilsTest.java b/client/src/test/java/dev/snowdrop/buildpack/utils/JsonUtilsTest.java index eea681c..6db86da 100644 --- a/client/src/test/java/dev/snowdrop/buildpack/utils/JsonUtilsTest.java +++ b/client/src/test/java/dev/snowdrop/buildpack/utils/JsonUtilsTest.java @@ -2,6 +2,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.fail; import java.util.List; @@ -60,4 +62,54 @@ void testJsonUtils() { fail(e); } } + + @Test + void testGetValueArrayIndexTraversal() throws Exception { + // JSON with nested arrays: /name/0/thing/2/name style paths + String json = "{\"items\":[{\"name\":\"first\",\"tags\":[\"a\",\"b\",\"c\"]},{\"name\":\"second\",\"tags\":[\"x\",\"y\",\"z\"]}]}"; + + ObjectMapper om = new ObjectMapper(); + om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + JsonNode root = om.readTree(json); + + // Access object field then array index then field + String name = JsonUtils.getValue(root, "items/0/name"); + assertEquals("first", name); + + String secondName = JsonUtils.getValue(root, "/items/1/name"); + assertEquals("second", secondName); + + // Access object field then array index then array index (nested arrays) + String tag = JsonUtils.getValue(root, "items/0/tags/2"); + assertEquals("c", tag); + + String lastTag = JsonUtils.getValue(root, "/items/1/tags/0"); + assertEquals("x", lastTag); + } + + @Test + void testGetValueArrayIndexOutOfBounds() throws Exception { + String json = "{\"items\":[{\"name\":\"only\"}]}"; + + ObjectMapper om = new ObjectMapper(); + om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + JsonNode root = om.readTree(json); + + // Out-of-bounds index returns null + String val = JsonUtils.getValue(root, "items/5/name"); + assertNull(val); + } + + @Test + void testGetValueArrayNonNumericIndexThrows() throws Exception { + String json = "{\"items\":[{\"name\":\"only\"}]}"; + + ObjectMapper om = new ObjectMapper(); + om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + JsonNode root = om.readTree(json); + + // Non-numeric path segment against an array should throw + assertThrows(IllegalArgumentException.class, () -> + JsonUtils.getValue(root, "items/notANumber/name")); + } }