Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to attach a saved image archive as a project artifact #1210

Merged
merged 3 commits into from
Apr 21, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions doc/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- Restore ANSI color to Maven logging if disabled during plugin execution and enable color for Windows with Maven 3.5.0 or later. Color logging is enabled by default, but disabled if the Maven CLI disables color (e.g. in batch mode) ([#1108](https://github.com/fabric8io/docker-maven-plugin/issues/1108))
- Fix NPE if docker:save is called with -Dfile=file-name-only.tar ([#1203](https://github.com/fabric8io/docker-maven-plugin/issues/1203))
- Improve GZIP compression performance for docker:save ([#1205](https://github.com/fabric8io/docker-maven-plugin/issues/1205))
- Allow docker:save to attach image archive as a project artifact ([#1210](https://github.com/fabric8io/docker-maven-plugin/pull/1210))

* **0.29.0** (2019-04-08)
- Avoid failing docker:save when no images with build configuration are present ([#1185](https://github.com/fabric8io/docker-maven-plugin/issues/1185))
Expand Down
8 changes: 8 additions & 0 deletions src/main/asciidoc/inc/_docker-save.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ Please note that the exported image contains all image layers and can be quite l
| The filename to save.
| `docker.save.file` or `docker.file` or `file`

| *saveAttach*
| Whether to attach the saved archive to the project (`false` by default).
| `docker.save.attach`

| *saveClassifier*
| Sets the artifact classifier when attaching the saved archive to the project. If `saveAlias` is set, defaults to `archive-<saveAlias>`, otherwise defaults to `archive`. Note that using overriding the default to use `docker` or `docker-<saveAlias>` may lead to a conflict if a source archive is also attached with <<{plugin}:source>>.
| `docker.save.classifier`

| *skipSave*
| A boolean flag whether to skip execution of the goal.
| `docker.skip.save`
Expand Down
22 changes: 21 additions & 1 deletion src/main/java/io/fabric8/maven/docker/SaveMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ public class SaveMojo extends AbstractDockerMojo {
@Parameter(property = "docker.skip.save", defaultValue = "false")
private boolean skipSave;

@Parameter(property = "docker.save.attach", defaultValue = "false")
private boolean saveAttach;

@Parameter(property = "docker.save.classifier")
private String saveClassifier;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I wonder whether we could combine both properties (as I'm always trying to keep the configuration surface small). What's about having a single 'saveAttach' string property, which can be 'true/false' (then a default classifier is select for 'true') or any other value in which case it enables the featue and uses that classifier ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can, although I always worry about making something that looks boolean, but isn't, because of previous mockery.

I could make specifying a non-empty saveClassifier mean "attach with this classifier", and not provide the default logic, since by the time you write <saveAttach>true</saveAttach>, you probably could have written <saveClassifier>archive</saveClassifier>, and we could use %a for the alias (as in <saveClassifier>archive-%a</saveClassifier> or -Ddocker.save.classifier=archive-%a)?

Copy link
Collaborator

Choose a reason for hiding this comment

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

We can, although I always worry about making something that looks boolean, but isn't, because of previous mockery.

I could make specifying a non-empty saveClassifier mean "attach with this classifier", and not provide the default logic, since by the time you write <saveAttach>true</saveAttach>, you probably could have written <saveClassifier>archive</saveClassifier>, and we could use %a for the alias (as in <saveClassifier>archive-%a</saveClassifier> or -Ddocker.save.classifier=archive-%a)?

Fair enough. I think having no default name is fine as adding one is trivial (also saves the user a documentation lookup for finding out what the default would be).


@Override
protected void executeInternal(ServiceHub serviceHub) throws DockerAccessException, MojoExecutionException {

Expand All @@ -56,8 +62,13 @@ protected void executeInternal(ServiceHub serviceHub) throws DockerAccessExcepti
}

long time = System.currentTimeMillis();
serviceHub.getDockerAccess().saveImage(imageName, fileName, ArchiveCompression.fromFileName(fileName));
ArchiveCompression compression = ArchiveCompression.fromFileName(fileName);
serviceHub.getDockerAccess().saveImage(imageName, fileName, compression);
log.info("%s: Saved image to %s in %s", imageName, fileName, EnvUtil.formatDurationTill(time));

if(saveAttach) {
projectHelper.attachArtifact(project, compression.getFileSuffix(), getClassifier(), new File(fileName));
}
}

private boolean skipSaveFor(List<ImageConfiguration> images) {
Expand Down Expand Up @@ -149,6 +160,15 @@ private List<ImageConfiguration> getImagesWithBuildConfig(List<ImageConfiguratio
return ret;
}

private String getClassifier() {
if(saveClassifier == null) {
return saveAlias == null ? "archive" : "archive-" + saveAlias;
} else {
return saveClassifier;
}
}


private boolean equalAlias(ImageConfiguration ic) {
return saveAlias != null && saveAlias.equals(ic.getAlias());
}
Expand Down
251 changes: 251 additions & 0 deletions src/test/java/io/fabric8/maven/docker/SaveMojoTest.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
package io.fabric8.maven.docker;

import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Properties;

import org.apache.maven.model.Build;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectHelper;
import org.junit.Test;

import io.fabric8.maven.docker.access.DockerAccess;
import io.fabric8.maven.docker.access.DockerAccessException;
import io.fabric8.maven.docker.config.ArchiveCompression;
import io.fabric8.maven.docker.config.BuildImageConfiguration;
import io.fabric8.maven.docker.config.ImageConfiguration;
import io.fabric8.maven.docker.service.QueryService;
import io.fabric8.maven.docker.service.ServiceHub;
import io.fabric8.maven.docker.util.Logger;
import mockit.Deencapsulation;
import mockit.Expectations;
import mockit.Injectable;
import mockit.Mocked;
import mockit.Tested;
Expand All @@ -28,6 +37,248 @@ public class SaveMojoTest {
@Mocked
ServiceHub serviceHub;

@Mocked
QueryService queryService;

@Mocked
DockerAccess dockerAccess;

@Mocked
MavenProject mavenProject;

@Mocked
Build mavenBuild;

@Mocked
MavenProjectHelper mavenProjectHelper;

@Test
public void saveWithoutNameAliasOrFile() throws DockerAccessException, MojoExecutionException {
ImageConfiguration image = new ImageConfiguration.Builder()
.name("example:latest")
.buildConfig(new BuildImageConfiguration.Builder()
.from("scratch")
.build())
.build();

new Expectations() {{
mavenProject.getProperties(); result = new Properties();
queryService.hasImage("example:latest"); result = true;
dockerAccess.saveImage("example:latest", anyString, ArchiveCompression.gzip);
serviceHub.getQueryService(); result = queryService;
serviceHub.getDockerAccess(); result = dockerAccess;
}};

Deencapsulation.setField(saveMojo, "images", Collections.singletonList(image));
Deencapsulation.setField(saveMojo, "resolvedImages", Collections.singletonList(image));
Deencapsulation.setField(saveMojo, "project", mavenProject);
Deencapsulation.setField(saveMojo, "projectHelper", mavenProjectHelper);

saveMojo.executeInternal(serviceHub);
}

@Test
public void saveAndAttachWithoutNameAliasOrFile() throws DockerAccessException, MojoExecutionException {
ImageConfiguration image = new ImageConfiguration.Builder()
.name("example:latest")
.buildConfig(new BuildImageConfiguration.Builder()
.from("scratch")
.build())
.build();

new Expectations() {{
mavenProject.getProperties(); result = new Properties();
mavenProject.getBuild(); result = mavenBuild;
mavenBuild.getDirectory(); result = "mock-target";
queryService.hasImage("example:latest"); result = true;
dockerAccess.saveImage("example:latest", anyString, ArchiveCompression.gzip);
serviceHub.getQueryService(); result = queryService;
serviceHub.getDockerAccess(); result = dockerAccess;
mavenProjectHelper.attachArtifact(mavenProject, "tar.gz", "archive", new File("mock-target/example-latest.tar.gz"));
}};

Deencapsulation.setField(saveMojo, "images", Collections.singletonList(image));
Deencapsulation.setField(saveMojo, "resolvedImages", Collections.singletonList(image));
Deencapsulation.setField(saveMojo, "saveAttach", true);
Deencapsulation.setField(saveMojo, "project", mavenProject);
Deencapsulation.setField(saveMojo, "projectHelper", mavenProjectHelper);

saveMojo.executeInternal(serviceHub);
}

@Test
public void saveWithFile() throws DockerAccessException, MojoExecutionException {
ImageConfiguration image = new ImageConfiguration.Builder()
.name("example:latest")
.buildConfig(new BuildImageConfiguration.Builder()
.from("scratch")
.build())
.build();

new Expectations() {{
mavenProject.getProperties(); result = new Properties();
queryService.hasImage("example:latest"); result = true;
dockerAccess.saveImage("example:latest", "destination/archive-name.tar.bz2", ArchiveCompression.bzip2);
serviceHub.getQueryService(); result = queryService;
serviceHub.getDockerAccess(); result = dockerAccess;
}};

Deencapsulation.setField(saveMojo, "images", Collections.singletonList(image));
Deencapsulation.setField(saveMojo, "resolvedImages", Collections.singletonList(image));
Deencapsulation.setField(saveMojo, "saveFile", "destination/archive-name.tar.bz2");
Deencapsulation.setField(saveMojo, "project", mavenProject);
Deencapsulation.setField(saveMojo, "projectHelper", mavenProjectHelper);

saveMojo.executeInternal(serviceHub);
}

@Test
public void saveAndAttachWithFile() throws DockerAccessException, MojoExecutionException {
ImageConfiguration image = new ImageConfiguration.Builder()
.name("example:latest")
.buildConfig(new BuildImageConfiguration.Builder()
.from("scratch")
.build())
.build();

new Expectations() {{
mavenProject.getProperties(); result = new Properties();
queryService.hasImage("example:latest"); result = true;
dockerAccess.saveImage("example:latest", "destination/archive-name.tar.bz2", ArchiveCompression.bzip2);
serviceHub.getQueryService(); result = queryService;
serviceHub.getDockerAccess(); result = dockerAccess;
mavenProjectHelper.attachArtifact(mavenProject, "tar.bz", "archive", new File("destination/archive-name.tar.bz2"));
}};

Deencapsulation.setField(saveMojo, "images", Collections.singletonList(image));
Deencapsulation.setField(saveMojo, "resolvedImages", Collections.singletonList(image));
Deencapsulation.setField(saveMojo, "saveFile", "destination/archive-name.tar.bz2");
Deencapsulation.setField(saveMojo, "saveAttach", true);
Deencapsulation.setField(saveMojo, "project", mavenProject);
Deencapsulation.setField(saveMojo, "projectHelper", mavenProjectHelper);

saveMojo.executeInternal(serviceHub);
}

@Test
public void saveWithAlias() throws DockerAccessException, MojoExecutionException {
ImageConfiguration image1 = new ImageConfiguration.Builder()
.name("example1:latest")
.alias("example1")
.buildConfig(new BuildImageConfiguration.Builder()
.from("scratch")
.build())
.build();

ImageConfiguration image2 = new ImageConfiguration.Builder()
.name("example2:latest")
.alias("example2")
.buildConfig(new BuildImageConfiguration.Builder()
.from("scratch")
.build())
.build();

new Expectations() {{
mavenProject.getProperties(); result = new Properties();
mavenProject.getBuild(); result = mavenBuild;
mavenProject.getVersion(); result = "1.2.3-SNAPSHOT";
mavenBuild.getDirectory(); result = "mock-target";
queryService.hasImage("example2:latest"); result = true;
dockerAccess.saveImage("example2:latest", "mock-target/example2-1.2.3-SNAPSHOT.tar.gz", ArchiveCompression.gzip);
serviceHub.getQueryService(); result = queryService;
serviceHub.getDockerAccess(); result = dockerAccess;
}};

Deencapsulation.setField(saveMojo, "images", Arrays.asList(image1, image2));
Deencapsulation.setField(saveMojo, "resolvedImages", Arrays.asList(image1, image2));
Deencapsulation.setField(saveMojo, "saveAlias", "example2");
Deencapsulation.setField(saveMojo, "project", mavenProject);
Deencapsulation.setField(saveMojo, "projectHelper", mavenProjectHelper);

saveMojo.executeInternal(serviceHub);
}

@Test
public void saveAndAttachWithAlias() throws DockerAccessException, MojoExecutionException {
ImageConfiguration image1 = new ImageConfiguration.Builder()
.name("example1:latest")
.alias("example1")
.buildConfig(new BuildImageConfiguration.Builder()
.from("scratch")
.build())
.build();

ImageConfiguration image2 = new ImageConfiguration.Builder()
.name("example2:latest")
.alias("example2")
.buildConfig(new BuildImageConfiguration.Builder()
.from("scratch")
.build())
.build();

new Expectations() {{
mavenProject.getProperties(); result = new Properties();
mavenProject.getBuild(); result = mavenBuild;
mavenProject.getVersion(); result = "1.2.3-SNAPSHOT";
mavenBuild.getDirectory(); result = "mock-target";
queryService.hasImage("example2:latest"); result = true;
dockerAccess.saveImage("example2:latest", "mock-target/example2-1.2.3-SNAPSHOT.tar.gz", ArchiveCompression.gzip);
serviceHub.getQueryService(); result = queryService;
serviceHub.getDockerAccess(); result = dockerAccess;
mavenProjectHelper.attachArtifact(mavenProject, "tar.gz", "archive-example2", new File("mock-target/example2-1.2.3-SNAPSHOT.tar.gz"));
}};

Deencapsulation.setField(saveMojo, "images", Arrays.asList(image1, image2));
Deencapsulation.setField(saveMojo, "resolvedImages", Arrays.asList(image1, image2));
Deencapsulation.setField(saveMojo, "saveAlias", "example2");
Deencapsulation.setField(saveMojo, "saveAttach", true);
Deencapsulation.setField(saveMojo, "project", mavenProject);
Deencapsulation.setField(saveMojo, "projectHelper", mavenProjectHelper);

saveMojo.executeInternal(serviceHub);
}

@Test
public void saveAndAttachWithAliasButAlsoClassifier() throws DockerAccessException, MojoExecutionException {
ImageConfiguration image1 = new ImageConfiguration.Builder()
.name("example1:latest")
.alias("example1")
.buildConfig(new BuildImageConfiguration.Builder()
.from("scratch")
.build())
.build();

ImageConfiguration image2 = new ImageConfiguration.Builder()
.name("example2:latest")
.alias("example2")
.buildConfig(new BuildImageConfiguration.Builder()
.from("scratch")
.build())
.build();

new Expectations() {{
mavenProject.getProperties(); result = new Properties();
mavenProject.getBuild(); result = mavenBuild;
mavenProject.getVersion(); result = "1.2.3-SNAPSHOT";
mavenBuild.getDirectory(); result = "mock-target";
queryService.hasImage("example2:latest"); result = true;
dockerAccess.saveImage("example2:latest", "mock-target/example2-1.2.3-SNAPSHOT.tar.gz", ArchiveCompression.gzip);
serviceHub.getQueryService(); result = queryService;
serviceHub.getDockerAccess(); result = dockerAccess;
mavenProjectHelper.attachArtifact(mavenProject, "tar.gz", "preferred", new File("mock-target/example2-1.2.3-SNAPSHOT.tar.gz"));
}};

Deencapsulation.setField(saveMojo, "images", Arrays.asList(image1, image2));
Deencapsulation.setField(saveMojo, "resolvedImages", Arrays.asList(image1, image2));
Deencapsulation.setField(saveMojo, "saveAlias", "example2");
Deencapsulation.setField(saveMojo, "saveClassifier", "preferred");
Deencapsulation.setField(saveMojo, "saveAttach", true);
Deencapsulation.setField(saveMojo, "project", mavenProject);
Deencapsulation.setField(saveMojo, "projectHelper", mavenProjectHelper);

saveMojo.executeInternal(serviceHub);
}

@Test
public void noFailureWithEmptyImageList() throws DockerAccessException, MojoExecutionException {
Deencapsulation.setField(saveMojo, "images", Collections.<ImageConfiguration>emptyList());
Expand Down