From dc36d1729891f401a153866875ac5a08df7af42e Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Fri, 24 Jun 2022 15:31:20 -0700 Subject: [PATCH] Fixes #724 Record information in eclipse metadata for docker images build by IDE. Just enough info is recored for a IDE built-image so we can avoid parsing image metadata from the image itself (the image metadata format is unstable and we do not have a usable api to get at it easily, so trying to rely on it is fraught with problems). --- .../boot/dash/docker/runtarget/DockerApp.java | 39 +++++++++++++------ .../docker/runtarget/DockerContainer.java | 39 +++++-------------- .../dash/docker/runtarget/DockerImage.java | 11 ++++-- .../boot/dash/test/BootDashDockerTests.java | 17 ++++++-- .../ide/eclipse/boot/dash/api/AppContext.java | 3 +- .../model/remote/GenericRemoteAppElement.java | 14 +++++-- 6 files changed, 69 insertions(+), 54 deletions(-) diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.docker/src/org/springframework/ide/eclipse/boot/dash/docker/runtarget/DockerApp.java b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.docker/src/org/springframework/ide/eclipse/boot/dash/docker/runtarget/DockerApp.java index 59a929b2d..8d9764dd3 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.docker/src/org/springframework/ide/eclipse/boot/dash/docker/runtarget/DockerApp.java +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.docker/src/org/springframework/ide/eclipse/boot/dash/docker/runtarget/DockerApp.java @@ -53,6 +53,7 @@ import org.springframework.ide.eclipse.boot.dash.docker.exceptions.MissingBuildS import org.springframework.ide.eclipse.boot.dash.docker.exceptions.MissingBuildTagException; import org.springframework.ide.eclipse.boot.dash.docker.jmx.JmxSupport; import org.springframework.ide.eclipse.boot.dash.docker.runtarget.BuildScriptLocator.BuildKind; +import org.springframework.ide.eclipse.boot.dash.docker.runtarget.DockerApp.BuildCommand; import org.springframework.ide.eclipse.boot.dash.labels.BootDashLabels; import org.springframework.ide.eclipse.boot.dash.model.RunState; import org.springframework.ide.eclipse.boot.dash.model.remote.ChildBearing; @@ -61,6 +62,7 @@ import org.springframework.ide.eclipse.boot.dash.util.LineBasedStreamGobler; import org.springframework.ide.eclipse.boot.launch.util.PortFinder; import org.springframework.ide.eclipse.boot.util.JavaProjectUtil; import org.springsource.ide.eclipse.commons.core.pstore.PropertyStoreApi; +import org.springsource.ide.eclipse.commons.core.pstore.PropertyStores; import org.springsource.ide.eclipse.commons.frameworks.core.util.JobUtil; import org.springsource.ide.eclipse.commons.frameworks.core.util.StringUtils; import org.springsource.ide.eclipse.commons.livexp.core.AbstractDisposable; @@ -87,7 +89,19 @@ import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -public class DockerApp extends AbstractDisposable implements App, ChildBearing, Deletable, ProjectRelatable, DesiredInstanceCount, SystemPropertySupport, LogSource, DevtoolsConnectable { +public class DockerApp extends AbstractDisposable implements App, ChildBearing, Deletable, ProjectRelatable, DesiredInstanceCount, + SystemPropertySupport, LogSource, DevtoolsConnectable { + + public class BuildCommand { + String[] command; + boolean builtWithDevToolsArgs; + public BuildCommand(String[] command, boolean builtWithDevToolsArgs) { + super(); + this.command = command; + this.builtWithDevToolsArgs = builtWithDevToolsArgs; + } + + } private static final String DOCKER_IO_LIBRARY = "docker.io/library/"; private static final String[] NO_STRINGS = new String[0]; @@ -389,14 +403,14 @@ Successfully tagged fui:latest private String build(AppConsole console) throws Exception { String[] imageIds = new String[BUILT_IMAGE_MESSAGE_PATS.length]; File directory = new File(project.getLocation().toString()); - String[] command = getBuildCommand(directory); + BuildCommand command = getBuildCommand(directory); - ProcessBuilder builder = new ProcessBuilder(command).directory(directory); + ProcessBuilder builder = new ProcessBuilder(command.command).directory(directory); String jhome = getJavaHome(); builder.environment().put("JAVA_HOME", jhome); console.write("build.env.JAVA_HOME="+jhome, LogType.STDOUT); console.write("build.directory="+directory, LogType.STDOUT); - console.logCommand(CommandUtil.escape(command)); + console.logCommand(CommandUtil.escape(command.command)); Process process = builder.start(); LineBasedStreamGobler outputGobler = new LineBasedStreamGobler(process.getInputStream(), (line) -> { System.out.println(line); @@ -447,10 +461,10 @@ Successfully tagged fui:latest if (images.isEmpty()) { // maybe the 'imageTag' is not actually a tag but an id/hash. InspectImageResponse inspect = client.inspectImageCmd(imageTag).exec(); - addPersistedImage(inspect.getId()); + addPersistedImage(inspect.getId(), command); } else { for (Image img : images) { - addPersistedImage(img.getId()); + addPersistedImage(img.getId(), command); } } return imageTag; @@ -461,19 +475,22 @@ Successfully tagged fui:latest return jvm.getInstallLocation().toString(); } - private String[] getBuildCommand(File directory) throws MissingBuildScriptException { + private BuildCommand getBuildCommand(File directory) throws MissingBuildScriptException { BuildScriptLocator buildScriptLocator = new BuildScriptLocator(directory); BuildKind buildKind = buildScriptLocator.getBuildKind(); if (buildKind==null) { throw new MissingBuildScriptException(buildScriptLocator.checkedLocations); } boolean wantsDevtools = deployment().getSystemProperties().getOrDefault(DevtoolsUtil.REMOTE_SECRET_PROP, null)!=null; + boolean withDevtoolsArgs = false; List command = buildScriptLocator.command; if (wantsDevtools) { if (buildKind==BuildKind.MAVEN) { + withDevtoolsArgs = true; command.add("-Dspring-boot.repackage.excludeDevtools=false"); } else if (buildKind==BuildKind.GRADLE) { try { + withDevtoolsArgs = true; command.addAll(gradle_initScript( "allprojects {\n" + " afterEvaluate {\n" + @@ -488,7 +505,7 @@ Successfully tagged fui:latest } } } - return command.toArray(new String[command.size()]); + return new BuildCommand(command.toArray(new String[command.size()]), withDevtoolsArgs); } private synchronized static List gradle_initScript(String script) throws IOException { @@ -503,7 +520,7 @@ Successfully tagged fui:latest ); } - synchronized private void addPersistedImage(String imageId) { + synchronized private void addPersistedImage(String imageId, BuildCommand command) { String key = imagesKey(); try { ImmutableSet.Builder builder = ImmutableSet.builder(); @@ -511,12 +528,12 @@ Successfully tagged fui:latest builder.addAll(Arrays.asList(props.get(key, NO_STRINGS))); builder.add(imageId); props.put(key, builder.build().toArray(NO_STRINGS)); - + props.put(DockerImage.hasDevtoolsKey(imageId), context.projectHasDevtoolsDependency() && command.builtWithDevToolsArgs); } catch (Exception e) { Log.log(e); } } - + private void setPersistedImages(Set existingImages) { try { getTarget().getPersistentProperties().put(imagesKey(), existingImages.toArray(NO_STRINGS)); diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.docker/src/org/springframework/ide/eclipse/boot/dash/docker/runtarget/DockerContainer.java b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.docker/src/org/springframework/ide/eclipse/boot/dash/docker/runtarget/DockerContainer.java index 56166e3c6..8481abaca 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.docker/src/org/springframework/ide/eclipse/boot/dash/docker/runtarget/DockerContainer.java +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.docker/src/org/springframework/ide/eclipse/boot/dash/docker/runtarget/DockerContainer.java @@ -25,7 +25,6 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ResourcesPlugin; @@ -54,8 +53,6 @@ import org.springframework.ide.eclipse.boot.dash.docker.util.Ownable; import org.springframework.ide.eclipse.boot.dash.model.RunState; import org.springframework.ide.eclipse.boot.dash.model.remote.RefreshStateTracker; import org.springframework.ide.eclipse.boot.util.RetryUtil; -import org.springframework.ide.eclipse.editor.support.yaml.path.JavaObjectNav; -import org.springframework.ide.eclipse.editor.support.yaml.path.YamlNavigable; import org.springframework.ide.eclipse.editor.support.yaml.path.YamlPath; import org.springframework.ide.eclipse.editor.support.yaml.path.YamlTraversal; import org.springsource.ide.eclipse.commons.core.util.StringUtil; @@ -72,8 +69,6 @@ import com.github.dockerjava.api.exception.NotFoundException; import com.github.dockerjava.api.model.Container; import com.github.dockerjava.api.model.Frame; import com.github.dockerjava.api.model.StreamType; -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -89,33 +84,12 @@ public class DockerContainer implements App, RunStateProvider, JmxConnectable, S private static Map RUNSTATE_ICONS = null; private DockerApp app; + private AppContext context; public DockerContainer(DockerRunTarget target, DockerApp app, Container container) { this.target = target; this.app = app; this.container = container; - this.hasDevtoolsDep = hasDevtoolsDependency(container::getLabels); - } - - public static Supplier hasDevtoolsDependency(Supplier> labelsSupplier) { - return Suppliers.memoize(() ->{ - Map labels = labelsSupplier.get(); - try { - if (labels!=null) { - String buildpackMetadata = labels.get("io.buildpacks.build.metadata"); - if (buildpackMetadata!=null) { - Map bpmd = new ObjectMapper().readValue(buildpackMetadata, Map.class); - Set deps = dependencyNamePath - .traverseAmbiguously(YamlNavigable.javaObject(bpmd)) - .flatMap(JavaObjectNav::asStringMaybe).collect(Collectors.toSet()); - return deps.contains("spring-boot-devtools"); - } - } - } catch (Exception e) { - Log.log(e); - } - return false; - }); } @Override @@ -285,6 +259,7 @@ public class DockerContainer implements App, RunStateProvider, JmxConnectable, S @Override public void setContext(AppContext context) { this.refreshTracker.complete(context.getRefreshTracker()); + this.context = context; } @Override @@ -362,12 +337,16 @@ public class DockerContainer implements App, RunStateProvider, JmxConnectable, S .thenValAt("name"); private static final boolean USE_DEDICATED_CLIENT = false; - - private Supplier hasDevtoolsDep; + + @Override public boolean hasDevtoolsDependency() { - return this.hasDevtoolsDep.get(); + if (context!=null) { + DockerImage image = context.getParent(DockerImage.class); + return image.hasDevtoolsDependency(); + } + return false; } // for debugging... keep in comments for now diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.docker/src/org/springframework/ide/eclipse/boot/dash/docker/runtarget/DockerImage.java b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.docker/src/org/springframework/ide/eclipse/boot/dash/docker/runtarget/DockerImage.java index 15dd68d98..b9dab0a38 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.docker/src/org/springframework/ide/eclipse/boot/dash/docker/runtarget/DockerImage.java +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.docker/src/org/springframework/ide/eclipse/boot/dash/docker/runtarget/DockerImage.java @@ -38,6 +38,7 @@ import org.springframework.ide.eclipse.boot.dash.model.RunState; import org.springframework.ide.eclipse.boot.dash.model.remote.ChildBearing; import org.springframework.ide.eclipse.boot.dash.model.remote.RefreshStateTracker; import org.springframework.ide.eclipse.boot.util.RetryUtil; +import org.springsource.ide.eclipse.commons.core.pstore.PropertyStoreApi; import org.springsource.ide.eclipse.commons.core.util.StringUtil; import org.springsource.ide.eclipse.commons.frameworks.core.util.JobUtil; import org.springsource.ide.eclipse.commons.livexp.ui.Stylers; @@ -47,7 +48,6 @@ import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.exception.NotFoundException; import com.github.dockerjava.api.model.Container; import com.github.dockerjava.api.model.Image; -import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ImmutableMap; @@ -59,14 +59,12 @@ public class DockerImage implements App, ChildBearing, Styleable, ProjectRelatab private final DockerApp app; private final Image image; public final CompletableFuture refreshTracker = new CompletableFuture<>(); - private final Supplier hasDevtoolsDependency; private static Map RUNSTATE_ICONS = null; public DockerImage(DockerApp app, Image image) { this.app = app; this.image = image; - this.hasDevtoolsDependency = DockerContainer.hasDevtoolsDependency(image::getLabels); } @Override @@ -249,11 +247,16 @@ public class DockerImage implements App, ChildBearing, Styleable, ProjectRelatab @Override public boolean hasDevtoolsDependency() { - return this.hasDevtoolsDependency.get(); + PropertyStoreApi props = getTarget().getPersistentProperties(); + return props.get(hasDevtoolsKey(image.getId()), false); } @Override public TemporalBoolean isDevtoolsConnectable() { return TemporalBoolean.NEVER; } + + public static String hasDevtoolsKey(String imageId) { + return imageId +".hasDevtoolsDependency"; + } } diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.test/src/org/springframework/ide/eclipse/boot/dash/test/BootDashDockerTests.java b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.test/src/org/springframework/ide/eclipse/boot/dash/test/BootDashDockerTests.java index 402abfbee..4b002e0a9 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.test/src/org/springframework/ide/eclipse/boot/dash/test/BootDashDockerTests.java +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash.test/src/org/springframework/ide/eclipse/boot/dash/test/BootDashDockerTests.java @@ -22,7 +22,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.springframework.ide.eclipse.boot.test.BootProjectTestHarness.bootVersionAtLeast; +import static org.springframework.ide.eclipse.boot.test.BootProjectTestHarness.*; import static org.springframework.ide.eclipse.boot.test.BootProjectTestHarness.withImportStrategy; import static org.springframework.ide.eclipse.boot.test.BootProjectTestHarness.withStarters; import static org.springsource.ide.eclipse.commons.tests.util.StsTestCase.assertContains; @@ -128,7 +128,10 @@ public class BootDashDockerTests { @Test public void projectWithdDockerFile() throws Exception { GenericRemoteBootDashModel model = createDockerTarget(); - IProject project = projects.createBootWebProject("webby", bootVersionAtLeast("2.3.0")); + IProject project = projects.createBootWebProject("webby", + bootVersionAtLeast("2.3.0"), + withJavaVersion("11") + ); createExeFile(project, "sts-docker-build.sh", "#!/bin/bash\n" + "docker build -t webby ." @@ -163,7 +166,10 @@ public class BootDashDockerTests { @Test public void missingBuildTagException() throws Exception { GenericRemoteBootDashModel model = createDockerTarget(); - IProject project = projects.createBootWebProject("webby", bootVersionAtLeast("2.3.0")); + IProject project = projects.createBootWebProject("webby", + bootVersionAtLeast("2.3.0"), + withJavaVersion("11") + ); createExeFile(project, "sts-docker-build.sh", "#!/bin/bash\n" + "# do nothing" @@ -180,7 +186,10 @@ public class BootDashDockerTests { @Test public void projectWithdDockerFileNoTag() throws Exception { GenericRemoteBootDashModel model = createDockerTarget(); - IProject project = projects.createBootWebProject("webby", bootVersionAtLeast("2.3.0")); + IProject project = projects.createBootWebProject("webby", + bootVersionAtLeast("2.3.0"), + withJavaVersion("11") + ); createExeFile(project, "sts-docker-build.sh", "#!/bin/bash\n" + "docker build ." diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/api/AppContext.java b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/api/AppContext.java index 185702ab2..fc597387e 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/api/AppContext.java +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/api/AppContext.java @@ -26,7 +26,6 @@ public interface AppContext { * somewhere in the boot dash ui. */ RefreshStateTracker getRefreshTracker(); - boolean projectHasDevtoolsDependency(); - + T getParent(Class expectedType); } diff --git a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/model/remote/GenericRemoteAppElement.java b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/model/remote/GenericRemoteAppElement.java index 8349448fd..26d6fe07f 100644 --- a/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/model/remote/GenericRemoteAppElement.java +++ b/eclipse-extensions/org.springframework.ide.eclipse.boot.dash/src/org/springframework/ide/eclipse/boot/dash/model/remote/GenericRemoteAppElement.java @@ -15,7 +15,6 @@ import java.util.EnumSet; import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.core.resources.IProject; @@ -90,8 +89,6 @@ public class GenericRemoteAppElement extends WrappingBootDashElement imp private static final boolean DEBUG = false; - private static AtomicInteger instances = new AtomicInteger(); - private LiveVariable app = new LiveVariable<>(); private final Object parent; @@ -125,6 +122,17 @@ public class GenericRemoteAppElement extends WrappingBootDashElement imp return super.getCustomRunStateIcon(); } + @Override + public T getParent(Class expectedType) { + if (parent instanceof GenericRemoteAppElement) { + App parentApp = ((GenericRemoteAppElement) parent).getAppData(); + if (parentApp!=null) { + return expectedType.cast(parentApp); + } + } + return null; + } + private ObservableSet children = ObservableSet.builder().refresh(AsyncMode.ASYNC).compute(() -> { App appVal = app.getValue();