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 history as part of layer metadata #877

Merged
merged 19 commits into from
Aug 28, 2018
Merged

Write history as part of layer metadata #877

merged 19 commits into from
Aug 28, 2018

Conversation

TadCordle
Copy link
Contributor

Part of #875. Base image history is propagated, and history elements for each of the new layers are added on top. "created_by" is always just "jib" for now; I'll submit a followup later where we can pass in different strings.

Image<Layer> baseImage = NonBlockingSteps.get(pullBaseImageStep).getBaseImage();
ContainerConfiguration containerConfiguration =
buildConfiguration.getContainerConfiguration();
String buildTime =
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: creationTime may be less ambiguous - buildTime can be construed as time it took to build

imageBuilder.addEnvironment(baseImage.getEnvironment());
imageBuilder.addLabels(baseImage.getLabels());

ContainerConfiguration containerConfiguration =
buildConfiguration.getContainerConfiguration();
// Add history elements if base layers/history sizes don't match
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be used to add the application layer history objects too - just add the buildAndCacheApplicationLayerSteps.size(). This way, there's only one place that calls new HistoryObjectTemplate(buildTime, "Jib", "jib")

* {
* "author": "Jib",
* "created": "1970-01-01T00:00:00Z",
* "created_by": "jib build ..."
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: "created_by": "jib"

Copy link
Member

Choose a reason for hiding this comment

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

ah this is my bad, the bug description is off, I'll go update this.

@@ -139,5 +143,9 @@ public void test_propagateBaseImageConfiguration()
Assert.assertEquals(
ImmutableMap.of("base.label", "base.label.value", "my.label", "my.label.value"),
image.getLabels());
Assert.assertTrue(
"History doesn't contain expected base image layer",
image.getHistory().contains(expectedHistory));
Copy link
Contributor

Choose a reason for hiding this comment

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

Assert the first history is the expectedHistory?

@Nullable private String created_by;

/** Whether or not the layer is empty */
@Nullable private Boolean empty_layer;
Copy link
Contributor

Choose a reason for hiding this comment

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

boolean? I believe we can default to false.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wanted to make this field optional, so I made it a @Nullable Boolean instead of boolean (if it's boolean then the field will always show up in the generated config).

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, that makes sense 👍

}

@Override
public boolean equals(Object other) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: can short circuit with if (this == other) return true;

// Add history elements if base layers/history sizes don't match
if (baseImage.getHistory().size() < baseImageLayers.size()) {
int sizeDifference = baseImageLayers.size() - baseImage.getHistory().size();
for (int count = 0; count < sizeDifference; count++) {
Copy link
Contributor

Choose a reason for hiding this comment

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

History with empty_layer=true should not be taken into account when calculating the history objects to add (they don't correspond to a layer). This should also get a test to make sure this is accounted for correctly.

import javax.annotation.Nullable;

@JsonIgnoreProperties(ignoreUnknown = true)
public class HistoryObjectTemplate implements JsonTemplate {
Copy link
Contributor

Choose a reason for hiding this comment

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

javadoc

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, was there a reason for having this outside of ContainerConfigurationTemplate (besides just a long file)? If we keep it outside, might want to name it like ContainerConfigurationHistoryObjectTemplate for something.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wasn't sure if it was a good idea to use nested classes in this case since HistoryObjectTemplate is used in multiple places outside of ContainerConfigurationTemplate. But I could nest it if you think it's a better idea.

Copy link
Contributor

Choose a reason for hiding this comment

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

It's mostly that even though it is used outside of ContainerConfigurationTemplate, it is just a type of object inside the config JSON, so some sort of association there that ties them closely would probably make more sense for the class relationship.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I will move it to be inside ContainerConfigurationTemplate.

baseImageLayers.size() + buildAndCacheApplicationLayerSteps.size() - nonEmptyLayerCount;
for (int count = 0; count < sizeDifference; count++) {
imageBuilder.addHistory(
new HistoryObjectTemplate(
Copy link
Contributor

Choose a reason for hiding this comment

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

Oh right, I just realized that this was passing a JSON template object into the Image builder. Image is intended to be a canonicalized Java object model of an image independent of the format (hence why we have ImageToJsonTranslator/JsonToImageTranslator). Making this distinction would allow for this image-building code not have to touch JSON templates at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there a way to address this without making two nearly identical classes?

Copy link
Contributor Author

@TadCordle TadCordle Aug 24, 2018

Choose a reason for hiding this comment

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

Hmm actually I guess they might not be that identical. It still doesn't feel good to be making two classes that hold exactly the same values, the only difference being that one is for serialization.

return empty_layer == null ? false : empty_layer;
}

@VisibleForTesting
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this used in testing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, not anymore.

@swilibc
Copy link

swilibc commented Aug 28, 2018

fwiw, this issue is causing problems for me with a private repo. The owner has confirmed that your fix addresses that issue.

// Add history elements for non-empty layers that don't have one yet
int sizeDifference =
baseImageLayers.size() + buildAndCacheApplicationLayerSteps.size() - nonEmptyLayerCount;
for (int count = 0; count < sizeDifference; count++) {
Copy link
Member

Choose a reason for hiding this comment

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

My only concern here is that we're claiming (and setting ourselves up for blame for) layers that were not generated by us. Perhaps we could only add missing-history items here, and then add jib layers in the next for() where you're adding the Jib-generated layers (lines 136–139)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I originally had it this way and changed it so we only needed to use new HistoryItem() once, but on second thought I agree it's clearer the original way (easier to tell which history entries correspond to which layers).


/** Represents an item in the container configuration's {@code history} list. */
@JsonIgnoreProperties(ignoreUnknown = true)
public class HistoryItem implements JsonTemplate {
Copy link
Member

Choose a reason for hiding this comment

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

Or HistoryEntry? (Just throwing this out as a suggestion.)

@Nullable private String created_by;

/** Whether or not the layer is empty ({@code @Nullable Boolean} to make field optional). */
@Nullable private Boolean empty_layer;
Copy link
Member

Choose a reason for hiding this comment

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

Question: should we follow Google naming style and use @JsonProperty for the external?

? Instant.EPOCH.toString()
: containerConfiguration.getCreationTime().toString();
for (int count = 0; count < baseImageLayers.size() - nonEmptyLayerCount; count++) {
imageBuilder.addHistory(new HistoryEntry(layerCreationTime, "Jib", "jib", null));
Copy link
Member

Choose a reason for hiding this comment

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

But I don't think this should be "Jib"/"jib" — maybe author = "(unknown)" and createdBy= "(unavailable)"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can change them, but right now createdBy is just a placeholder. After this I think I'm going to add a way to pass in something like "jib-gradle-plugin" or "jib-maven-plugin". Maybe I should do the same for author?

@coollog WDYT

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 Brian means for the base image layers that don't have history to have author and createdBy be (unknown), though I think leaving them empty would be fine too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh ok, makes sense.

public class HistoryEntry implements JsonTemplate {

/** The timestamp at which the image was created. */
private String created;
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to be making the created field non-nullable (in the config spec, it is optional)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was referencing this and it didn't seem optional (no "omitempty").

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, interesting - okay, let's match that behavior then since we don't want to break compatibility with Docker.

Copy link
Member

Choose a reason for hiding this comment

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

But on the flip side we may error out reading an OCI-conformant image that has no created field. Right?

public class HistoryEntry implements JsonTemplate {

/** The timestamp at which the image was created. */
private String created;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can name this in code style and use JsonProperty here too. Something like creationTimestamp is more descriptive.

@JsonIgnoreProperties(ignoreUnknown = true)
public class HistoryEntry implements JsonTemplate {

/** The timestamp at which the image was created. */
Copy link
Contributor

Choose a reason for hiding this comment

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

A comment about this needing to be formatted according to RFC 3339, section 5.6

@Nullable
private String createdBy;

/** Whether or not the layer is empty ({@code @Nullable Boolean} to make field optional). */
Copy link
Contributor

Choose a reason for hiding this comment

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

This field is actually about whether or not this history entry corresponds to an actual layer in the container.

/** The name of the author specified when committing the image. */
@Nullable private String author;

/** The command used while building the image. */
Copy link
Contributor

Choose a reason for hiding this comment

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

s/while building/to build/?

import java.util.Objects;
import javax.annotation.Nullable;

/** Represents an item in the container configuration's {@code history} list. */
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's add a link to https://github.com/opencontainers/image-spec/blob/master/config.md#properties and pointing specifically to the history field.


/** Represents an item in the container configuration's {@code history} list. */
@JsonIgnoreProperties(ignoreUnknown = true)
public class HistoryEntry implements JsonTemplate {
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 this might better belong under the image.json package, next to ContainerConfigurationTemplate.

I'm thinking it might make more sense to call this ContainerConfigurationHistory and have it manage the actual JSON templates. Then, Image will have a ContainerConfigurationHistory and not have to directly manage HistoryEntry JSON templates.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll move it to the package, but I'm just going to make an issue for the second point for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

/** Whether or not the layer is empty ({@code @Nullable Boolean} to make field optional). */
@JsonProperty("empty_layer")
@Nullable
private Boolean emptyLayer;
Copy link
Contributor

Choose a reason for hiding this comment

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

Could be named like isLayer?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that naming is too different from the field it corresponds to and could get confusing.

@briandealwis
Copy link
Member

Might be worth including the comment field on HistoryEntry — we can fill that in for our auto-generated missing layer history entries.

@@ -124,14 +124,15 @@
? Instant.EPOCH.toString()
: containerConfiguration.getCreationTime().toString();
for (int count = 0; count < baseImageLayers.size() - nonEmptyLayerCount; count++) {
imageBuilder.addHistory(new HistoryEntry(layerCreationTime, null, null, null));
imageBuilder.addHistory(
new HistoryEntry(layerCreationTime, null, null, "auto-generated by Jib", null));
Copy link
Contributor

Choose a reason for hiding this comment

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

We probably want to go with a builder pattern here since it's unclear which field is which.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, "auto-generated by Jib" might not be the text we want for created_by, since created_by refers to what created the layer the history entry refers to (and that layer was not "auto-generated by Jib") - I think "unknown" would be more suitable here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's comment, not created_by.

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, then my first comment applies haha.

@JsonIgnoreProperties(ignoreUnknown = true)
public class HistoryEntry implements JsonTemplate {

/** The RFC 3339 formatted timestamp at which the image was created. */
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: specifically section 5.6 of RFC 3339

@Nullable
private String author;

/** The command used to build the image. */
Copy link
Contributor

Choose a reason for hiding this comment

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

s/image/layer/

private String comment;

/**
* Whether or not the entry corresponds to an empty layer ({@code @Nullable Boolean} to make field
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment should be updated too

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To what? I already updated this from earlier.

Copy link
Contributor

Choose a reason for hiding this comment

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

The same as the hasLayer method? "whether or not the history object corresponds to a layer in the container"

* @return {@code true} if the history object corresponds to a layer in the container
*/
@JsonIgnore
public boolean hasLayer() {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: hasLayer seems to mean if this HistoryEntry has a layer - perhaps hasLayerInContainer or hasCorrespondingLayer or isForLayer?

@@ -137,10 +152,18 @@ public void addLayerDiffId(DescriptorDigest diffId) {
rootfs.diff_ids.add(diffId);
}

public void addHistory(HistoryEntry historyEntry) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: since history is a plural list of HistoryEntrys, perhaps this method name should be addToHistory instead

@@ -70,6 +82,9 @@
/** Execution parameters that should be used as a base when running the container. */
private final ConfigurationObjectTemplate config = new ConfigurationObjectTemplate();

/** Build commands used to create the image. */
Copy link
Contributor

Choose a reason for hiding this comment

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

"Describes the history of each layer"?

@Nullable private String comment;
@Nullable private Boolean emptyLayer;

public Builder setCreationTimestamp(String creationTimestamp) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it make sense to take an Instant here instead?

@@ -105,4 +154,17 @@ public boolean equals(Object other) {
public int hashCode() {
return Objects.hash(author, creationTimestamp, createdBy, comment, emptyLayer);
}

@Override
public String toString() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Whoops, added that to test something and forgot to get rid of it.

@@ -32,7 +32,56 @@
@JsonIgnoreProperties(ignoreUnknown = true)
public class HistoryEntry implements JsonTemplate {

/** The RFC 3339 formatted timestamp at which the image was created. */
public static class Builder {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: short javadoc

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I didn't see any other Builders in the code base with a javadoc (and I don't think it's necessary, this is pretty self-explanatory).

@TadCordle TadCordle merged commit 907827f into master Aug 28, 2018
@TadCordle TadCordle deleted the layer-history branch August 28, 2018 21:35
@coollog
Copy link
Contributor

coollog commented Aug 29, 2018

This should fix #534

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants