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

Write out image digest to <target>/jib-image.digest #1155

Merged
merged 16 commits into from
Oct 29, 2018
Merged

Write out image digest to <target>/jib-image.digest #1155

merged 16 commits into from
Oct 29, 2018

Conversation

briandealwis
Copy link
Member

Fixed #933

@briandealwis
Copy link
Member Author

I'm not enamoured by this solution which has a lot of repetition. I'll look into instead leveraging the eventing system.

Copy link
Contributor

@coollog coollog left a comment

Choose a reason for hiding this comment

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

I think we can just refactor the writing of the digest out to BuildStepsRunner or somewhere else in jib-plugins-common?

}

/**
* Returns the output directory for the image digest. By default, it is {@code
Copy link
Contributor

Choose a reason for hiding this comment

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

output file

@@ -117,6 +132,8 @@ private static String buildAndRunAdditionalTag(
verifier.executeGoals(Arrays.asList("clean", "compile", "jib:build"));
verifier.verifyErrorFreeLog();

assertImageDigest(projectRoot);
Copy link
Contributor

Choose a reason for hiding this comment

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

For the test, we can actually pull and build using the digest that was generated to assert that the image was actually pushed with that digest.

@briandealwis
Copy link
Member Author

So I tried various other approaches and creating an ImageCreatedEvent seemed the cleanest approach IMHO.

@coollog
Copy link
Contributor

coollog commented Oct 18, 2018

Hmm, what was the reason for this extra ImageCreatedEvent when the output of the build is already the image digest?

@briandealwis
Copy link
Member Author

The additional information is what I'd want from the library. From my experience, the image digest is only useful for the build to registry case: I can't use it to access the created image In the build to docker case (nor build to tar). I can't look up a container by the image digest if I haven't pushed it to a registry. I can't use the image digest to run the image.

An alternative is to change the return results from a DescriptorDigest to a JibContainer and add more information to JibContainer.

Why an event? It allowed me to separate my action (write the digest to a file) from when that information was available. I could have modified BuildStepRunner's forBuild* methods to pass along the path to write to, or I could have added yet another argument to BuildStepsRunner.build(). But that doesn't scale — what if we want to write out the Image ID too? I could have passed along a file name additional methods on BuildStepsRunner.

BuildStepsRunner.forBuildImage(targetImageReference, jibExtension.getTo().getTags())
        .setImageDigestPath(imageDigestPath)
        .build(
            jibContainerBuilder,
            containerizer,
            eventDispatcher,
            gradleProjectProperties.getJavaLayerConfigurations().getLayerConfigurations(),
            helpfulSuggestions);

Copy link
Contributor

@coollog coollog left a comment

Choose a reason for hiding this comment

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

Yes, that's a good point that the image digest is only able to reference the image in the build to registry case. However, I'm not sure about the necessity of an event handler to write the file. I think adding the event might be redundant (compared to just adding more information to JibContainer) since then both JibContainer and the ImageCreatedEvent would essentially have the same information. Also, event handling is a bit limited in what can be actually done since, for one, it doesn't currently have a good way of throwing an exception since it is executed asynchronously, whereas if we used JibContainer to get the information after the build executes, we could write out the digest file synchronously and throw an exception if it did not succeed. The current way the eventing system is designed is more suited for tracking what is actually being done by the asynchronous steps rather than performing any costly operations or file I/O, but we could potentially change that (such as allowing checked exceptions to be thrown by event handlers).

}

/** @return the created image */
public Image<? extends Layer> getImage() {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can remove this since Image is more of an internal representation rather than something we want to expose to a user.

}

/**
* Return the <em>image digest</em>, the digest of the registry image manifest.
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah right, maybe we should use <strong> and <em> in our other javadocs as well.

String digest = new String(Files.readAllBytes(digestPath), StandardCharsets.UTF_8);
DescriptorDigest.fromDigest(digest);
} catch (IOException | DigestException ex) {
throw new AssertionError("Invalid jib-image.digest", ex);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think since this is a test we can just have this method throw the exceptions directly?

new TimerEventHandler(message -> logEventHandler.accept(LogEvent.debug(message))));

Path buildOutputPath = project.getBuildDir().toPath();
Path digestOutputPath = buildOutputPath.resolve("jib-image.digest");
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should make this configurable like jib-image.tar is?

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh interesting, I thought we had that as configurable - nevermind then.

Copy link
Member Author

@briandealwis briandealwis left a comment

Choose a reason for hiding this comment

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

Reverted back and modified BuildStepsRunner and friends to return a JibContainer. Augmented the JibContainer with both image digest and ID. Added support for writing out the imageDigest to BuildStepsRunner.

new TimerEventHandler(message -> logEventHandler.accept(LogEvent.debug(message))));

Path buildOutputPath = project.getBuildDir().toPath();
Path digestOutputPath = buildOutputPath.resolve("jib-image.digest");
Copy link
Member Author

Choose a reason for hiding this comment

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

* @param imageId digest of the container configuration
* @return the new container
*/
public static JibContainer create(DescriptorDigest imageDigest, DescriptorDigest imageId) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, minor API thing, but since JibContainer is the user-facing API for representing the build results, it might be better to not include a public factory method to create a JibContainer. I think we should still keep the package-private constructor (or a package-private factory method) to only allow JibContainerBuilder#containerize to instantiate it.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't disagree, but it's created from the build steps in .build.steps, not .api.

I've moved the method on JibContainerBuilder and added a note that it is for internal consumption. I could mark it with Guava's com.google.common.annotations.Beta annotation.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should keep the package-hierarchy free of cyclical dependencies such that .api depends on .build.steps and there isn't a reference in the other direction - so an alternative here would be to have the build steps return the image digest and id (via some class like BuildSteps.Result or something) and have JibContainerBuilder create the JibContainer using those return values.

@@ -130,15 +130,11 @@ static void assertCreationTimeEpoch(String imageReference)
new Command("docker", "inspect", "-f", "{{.Created}}", imageReference).run().trim());
}

static void assertImageDigest(Path projectRoot) {
static String assertImageDigest(Path projectRoot) throws IOException, DigestException {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the String return used?

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm no. We're testing the result in the maven tests, so I'll drop it from here.

BuildStepsRunner.forBuildImage(targetImageReference, getTargetImageAdditionalTags())
.imageDigestOutputPath(imageDigestOutputPath)
Copy link
Contributor

@coollog coollog Oct 22, 2018

Choose a reason for hiding this comment

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

Hmm, rather than a nullable with a setter, could this just be writeImageDigest(path) method on either a separate class (like ImageDigestWriter?) or chained onto BuildStepsRunner#build?

Like

BuildStepsRunner...
    .build(...)
    .writeImageDigest(imageDigestOutputPath);

Copy link
Contributor

@TadCordle TadCordle left a comment

Choose a reason for hiding this comment

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

Aside from Q's comments/a few javadoc nits, seems good to me.

}

/**
* Gets the digest of the container image built by Jib.
* Gets the image digest, the digest of the registry image manifest built by Jib. This digest can
Copy link
Contributor

Choose a reason for hiding this comment

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

First sentence seems a bit redundant, maybe just "Gets the digest of the registry image manifest built by Jib."

*
* @return the image digest
*/
public DescriptorDigest getDigest() {
return imageDigest;
}

/**
* Gets the image ID, the digest of the container configuration built by Jib.
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here, "Gets the digest of the container configuration built by Jib."

@@ -35,13 +35,13 @@
private interface ImageBuildRunnable {

/**
* Builds an image and returns its digest.
* Build an image.
Copy link
Contributor

Choose a reason for hiding this comment

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

This should still be "Builds" with an 's', shouldn't it?

@coollog
Copy link
Contributor

coollog commented Oct 26, 2018

Let's add a CHANGLOG entry as well.


@Override
public boolean equals(Object obj) {
if (this == obj) return true;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I think we generally still use brackets for single-line if-statements.

Copy link
Contributor

@coollog coollog left a comment

Choose a reason for hiding this comment

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

LGTM, just some merge conflicts to resolve.

public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we generally use instanceof even though we don't use inheritance, but it does save the extra null check.

Copy link
Member Author

Choose a reason for hiding this comment

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

I meant to look up Bloch previously and he discusses at length in Effective Java how .equals() is required to be symmetric and thus instanceof should never be used.

Copy link
Member Author

Choose a reason for hiding this comment

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

Filed #1202

@briandealwis briandealwis merged commit 8f46e19 into master Oct 29, 2018
@briandealwis briandealwis deleted the i933 branch November 23, 2018 15:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants