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 --label to Build/Publish Docker Image-Step #769

Closed
wants to merge 1 commit into from

Conversation

glance-
Copy link
Contributor

@glance- glance- commented Jan 20, 2020

This adds the ability to add labels to a docker image build.

This is usefull for example encoding build number, git hash and so on into the docker image.

Copy link
Member

@pjdarton pjdarton left a comment

Choose a reason for hiding this comment

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

I've added numerous review comments where there's stuff that's changed which (I think) needs modifying.
gitHub doesn't let me comment on places where there's no changes but needs changes so I'll mention those here...

If we're adding data to DockerBuildImageAction then we should also display that data (otherwise there's little point in storing it).
There's two jelly files, src/main/resources/.../DockerBuildImageAction/index.jelly and summary.jelly which are nigh-on identical and show the containerHost, containerId and tags fields from the DockerBuildImageAction instance. If we're going to store the tags then we should handle those in the jelly so that they get displayed too.
FYI there's an example of good jelly syntax for this in the openstack-cloud plugin's JCloudsComputer/main.jelly file where it lists the liveOpenstackServerDetails and openstackSlaveData maps.
Note: any such code MUST handle null label maps; if a user uses this enhanced code to look at a build built prior to these changes, they won't have a labels field persisted so it'll be de-serialised as null. The jelly code must not NPE under such circumstances.

DockerBuilderPublisher.DescriptorImpl needs to have a doCheck... method added for the new field that the UI is now handling (currently labels but I think it should be labelsString).
This needs to return a FormValidation indicating what (if anything) is wrong with what the user specified. See the doCheckTagsString method for an example.

@@ -32,6 +33,7 @@
public final boolean pushOnSuccess;
public /* almost final */ boolean noCache;
public /* almost final */ boolean pull;
public Map<String, String> labels;
Copy link
Member

Choose a reason for hiding this comment

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

This should include the /* almost final */ comment - it's only non-final because it can be set by setters; like the other "almost final" fields, it shouldn't change after it's been set.

this(containerHost, containerId, tags, cleanupWithJenkinsJobDelete, pushOnSuccess, noCache, pull);
setLabels(labels);
}

Copy link
Member

Choose a reason for hiding this comment

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

We don't need both these internal-use-only constructors - we should only have one of them.
Combine them so we've only got one "populates all fields" constructor that for internal-use-only ... and ensure that anywhere (in this plugin) that calls the old one is amended to provide the extra parameter.

@@ -133,6 +135,7 @@
public final String cloud;
public /* almost final */ boolean noCache;
public /* almost final */ boolean pull;
public Map<String, String> labels;
Copy link
Member

Choose a reason for hiding this comment

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

This should have the @CheckForNull annotation because it can be null.
...and everywhere that uses it should be null-proof.

Copy link
Member

Choose a reason for hiding this comment

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

See comment below - I think it'd be better to store labelString rather than the parsed result of the string the user specified.
If we only store the parsed result, we can't store an invalid result, meaning that anything the user types in that we can't parse gets destroyed rather than letting the user go back and re-edit it.

Copy link
Member

Choose a reason for hiding this comment

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

..and, also, it should be /* almost final */ too, because it's only set by the setter at construction time - it's not really something that's mutated while the code is running.

@@ -207,6 +210,34 @@ public void setPull(boolean pull) {
this.pull = pull;
}

public String getLabels() {
Copy link
Member

Choose a reason for hiding this comment

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

The established convention within this plugin (e.g. the "tags" field) is that where we've got a collection that's also available in string form (for the UI), we have getX and @DataBoundSetter setX methods that return/take the raw type and getXString and setXString methods that return/take Strings.

So a method called getLabels should return Map<String, String> and the method defined here should be called getLabelsString.
Similarly, just below, the setLabels method should be the one annotated with @DataBoundSetter.

...and both the .jelly code and the doCheck... methods in the Descriptior (for the UI) will then need to refer to the labelsString field.

Copy link
Member

Choose a reason for hiding this comment

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

I was thinking about this this morning... I have a better idea.

We should store the labelsString field. There should be a getter and setter for this field.

We should also have a private static Map<String, String> parseLabels(String labelsString) method which can throw IllegalArgumentException if it's given an invalid string (and returns null if there's no labels), and a public Map<String, String> getLabels() method which calls it with the labelsString field value.

This way, the code will keep and persist whatever the user enters into the WebUI, we can use the parseLabels method in the doCheckLabelsString method in the Descriptor class to provide users will fast feedback about the validity of what they've typed in, and we'll get the same (consistent) exception messages in the error logs if the user enters in borked data and then saves that.

@DataBoundSetter
public void setLabels(String labelsString) {
setLabels(isEmpty(labelsString) ?
Collections.EMPTY_MAP :
Copy link
Member

Choose a reason for hiding this comment

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

It'd be better to store "no labels" as a null Map instead of an empty one.
This way, the code knows that the collection is either null or it's got some content, and it also means that the serialized form of this class has no labels element when there's no labels, which is more efficient and exactly the same as we'd get if we had builds defined before this feature was added.

This decision also affects code later where we (currently) call .withLabels(labels) unconditionally; we are (unintentionally) relying on the docker engine to treat "labels not specified in request" exactly the same as "labels were specified to be empty".

@@ -47,4 +47,8 @@
<f:checkbox default="false"/>
</f:entry>

<f:entry title="${%Label list}" field="labels">
Copy link
Member

Choose a reason for hiding this comment

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

If we follow the getter/setter convention as used by the tags field, this will have to change to field="labelsString".
See earlier comments for full explanation.

@@ -0,0 +1,4 @@
<div>
If set, builds the image with <code>--labels</code> to add labels to the image built
Copy link
Member

Choose a reason for hiding this comment

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

Need a full stop at the end of this line.

@@ -0,0 +1,4 @@
<div>
If set, builds the image with <code>--labels</code> to add labels to the image built
See the docker <a href="https://docs.docker.com/engine/reference/commandline/build/">build command</a> for more information.
Copy link
Member

Choose a reason for hiding this comment

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

Best to ensure that this text starts on a newline when rendered.
e.g. put a line with just <br/> before this line.

@@ -0,0 +1,4 @@
<div>
Copy link
Member

Choose a reason for hiding this comment

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

If we follow the getter/setter convention as used by the tags field, this file will have to be renamed to help-labelsString.html.
See earlier comments for full explanation.

<div>
If set, builds the image with <code>--labels</code> to add labels to the image built
See the docker <a href="https://docs.docker.com/engine/reference/commandline/build/">build command</a> for more information.
</div>
Copy link
Member

Choose a reason for hiding this comment

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

General note about this:
I've just checked the documentation regarding labels on the docker website and I'm sorry to say that it's woefully incomplete - there's no mention of the syntax at all 😞
So we can't rely on that site to tell users about this - we're going to have to explain to the user what the syntax is.
...and if we do that, it'd probably be best to have 3 sections to this content - the initial summary line, then a <p> ... </p> paragraph explaining the syntax required by this plugin (i.e. LabelName=LabelValue, one per line), and then the final "See ... for more information" line (which then wouldn't need a <br/> before it if it had a </p> before it).

We don't need a massive essay, but we do need to tell folks it's multiple entries (one per line) and what each line's syntax is. Take a look at the tagsString.html file as an example.

@pjdarton
Copy link
Member

Thanks for the PR - it's a very welcome enhancement.
I've reviewed the code - some modifications needed, but nothing too horrible (I hope).

@glance-
Copy link
Contributor Author

glance- commented Jan 31, 2020

I'm sorry to inform that I'm going offline for a while transitioning to a new job. This work was done as a side-task for my previous job, and I'm not sure if I'm going to get around to picking this up and finishing it. Maybe one of my old colleagues will pick this up and finish it.

That said, anyone is welcome to finish, steal, rework or just get inspired by this and do what they feel for with it.

@pjdarton
Copy link
Member

pjdarton commented Feb 3, 2020

OK, thanks for letting me know; I'll know not to keep poking you for progress 😉
I'll leave this "open" for a while (to help folks find it) but will (eventually) close it if there's no further progress.

To anyone else finding this issue in the future: If you fancy continuing where this one left off, that'd be appreciated.

@pjdarton pjdarton added the WIP PR that is *not* ready for merging (but hopefully being worked on by the author). label Feb 3, 2020
@pjdarton pjdarton added the enhancement A PR providing an enhancement to existing functionality. label Mar 11, 2020
@pjdarton
Copy link
Member

pjdarton commented Jun 9, 2022

As there's been no movement on this for over a year, I'm going to close it.
If anyone still wants this, feel free to re-open this (or a new PR), resolve the conflicts and requested changes and I can take another look at merging it.

@pjdarton pjdarton closed this Jun 9, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement A PR providing an enhancement to existing functionality. WIP PR that is *not* ready for merging (but hopefully being worked on by the author).
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants