Skip to content

Commit

Permalink
Fixes #724
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
kdvolder committed Jun 24, 2022
1 parent 078d1b1 commit dc36d17
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
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;
Expand All @@ -61,6 +62,7 @@
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;
Expand All @@ -87,7 +89,19 @@
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];
Expand Down Expand Up @@ -389,14 +403,14 @@ private void run(AppConsole console, String image, DockerDeployment deployment)
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);
Expand Down Expand Up @@ -447,10 +461,10 @@ private String build(AppConsole console) throws Exception {
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;
Expand All @@ -461,19 +475,22 @@ private String getJavaHome() throws CoreException {
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<String> 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" +
Expand All @@ -488,7 +505,7 @@ private String[] getBuildCommand(File directory) throws MissingBuildScriptExcept
}
}
}
return command.toArray(new String[command.size()]);
return new BuildCommand(command.toArray(new String[command.size()]), withDevtoolsArgs);
}

private synchronized static List<String> gradle_initScript(String script) throws IOException {
Expand All @@ -503,20 +520,20 @@ private synchronized static List<String> gradle_initScript(String script) throws
);
}

synchronized private void addPersistedImage(String imageId) {
synchronized private void addPersistedImage(String imageId, BuildCommand command) {
String key = imagesKey();
try {
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
PropertyStoreApi props = getTarget().getPersistentProperties();
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<String> existingImages) {
try {
getTarget().getPersistentProperties().put(imagesKey(), existingImages.toArray(NO_STRINGS));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
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;
Expand Down Expand Up @@ -54,8 +53,6 @@
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;
Expand All @@ -72,8 +69,6 @@
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;

Expand All @@ -89,33 +84,12 @@ public class DockerContainer implements App, RunStateProvider, JmxConnectable, S

private static Map<RunState, ImageDescriptor> 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<Boolean> hasDevtoolsDependency(Supplier<Map<String,String>> labelsSupplier) {
return Suppliers.memoize(() ->{
Map<String, String> 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<String> 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
Expand Down Expand Up @@ -285,6 +259,7 @@ public void restart(RunState runingOrDebugging) {
@Override
public void setContext(AppContext context) {
this.refreshTracker.complete(context.getRefreshTracker());
this.context = context;
}

@Override
Expand Down Expand Up @@ -362,12 +337,16 @@ public String getDevtoolsSecret() {
.thenValAt("name");

private static final boolean USE_DEDICATED_CLIENT = false;

private Supplier<Boolean> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
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;
Expand All @@ -47,7 +48,6 @@
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;
Expand All @@ -59,14 +59,12 @@ public class DockerImage implements App, ChildBearing, Styleable, ProjectRelatab
private final DockerApp app;
private final Image image;
public final CompletableFuture<RefreshStateTracker> refreshTracker = new CompletableFuture<>();
private final Supplier<Boolean> hasDevtoolsDependency;

private static Map<RunState, ImageDescriptor> RUNSTATE_ICONS = null;

public DockerImage(DockerApp app, Image image) {
this.app = app;
this.image = image;
this.hasDevtoolsDependency = DockerContainer.hasDevtoolsDependency(image::getLabels);
}

@Override
Expand Down Expand Up @@ -249,11 +247,16 @@ public String getDevtoolsSecret() {

@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";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
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;
Expand Down Expand Up @@ -128,7 +128,10 @@ public void testCreateDockerTarget() throws Exception {
@Test
public void projectWithdDockerFile() throws Exception {
GenericRemoteBootDashModel<DockerClient, DockerTargetParams> 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 ."
Expand Down Expand Up @@ -163,7 +166,10 @@ public void projectWithdDockerFile() throws Exception {
@Test
public void missingBuildTagException() throws Exception {
GenericRemoteBootDashModel<DockerClient, DockerTargetParams> 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"
Expand All @@ -180,7 +186,10 @@ public void missingBuildTagException() throws Exception {
@Test
public void projectWithdDockerFileNoTag() throws Exception {
GenericRemoteBootDashModel<DockerClient, DockerTargetParams> 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 ."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ public interface AppContext {
* somewhere in the boot dash ui.
*/
RefreshStateTracker getRefreshTracker();

boolean projectHasDevtoolsDependency();

<T extends App> T getParent(Class<T> expectedType);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
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;
Expand Down Expand Up @@ -90,8 +89,6 @@ public class GenericRemoteAppElement extends WrappingBootDashElement<String> imp

private static final boolean DEBUG = false;

private static AtomicInteger instances = new AtomicInteger();

private LiveVariable<App> app = new LiveVariable<>();

private final Object parent;
Expand Down Expand Up @@ -125,6 +122,17 @@ public ImageDescriptor getCustomRunStateIcon() {
return super.getCustomRunStateIcon();
}

@Override
public <T extends App> T getParent(Class<T> expectedType) {
if (parent instanceof GenericRemoteAppElement) {
App parentApp = ((GenericRemoteAppElement) parent).getAppData();
if (parentApp!=null) {
return expectedType.cast(parentApp);
}
}
return null;
}

private ObservableSet<BootDashElement> children = ObservableSet.<BootDashElement>builder().refresh(AsyncMode.ASYNC).compute(() -> {

App appVal = app.getValue();
Expand Down

0 comments on commit dc36d17

Please sign in to comment.