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

[JENKINS-71959] Rename blank cloud names to be non-blank #1010

Merged
merged 4 commits into from
Sep 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,16 @@
super(name);
this.dockerApi = dockerApi;
this.templates = templates;
if (name == null || name.isBlank()) {
LOGGER.warn("Docker cloud requires a non-blank name after Jenkins 2.402");
}
}

/* Constructor to create a new DockerCloud based on an existing DockerCloud */
public DockerCloud(@NonNull String name, @NonNull DockerCloud source) {
super(name);
this.dockerApi = source.dockerApi;
this.templates = source.templates;
}

@Deprecated
Expand Down Expand Up @@ -727,6 +737,12 @@
dockerApi = new DockerAPI(dockerHost, connectTimeout, readTimeout, version, dockerHostname);
}

if (name == null || name.isBlank()) {

Check warning on line 740 in src/main/java/com/nirima/jenkins/plugins/docker/DockerCloud.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 740 is only partially covered, 4 branches are missing
String newName = "docker-cloud-" + Integer.toHexString(hashCode());

Check warning on line 741 in src/main/java/com/nirima/jenkins/plugins/docker/DockerCloud.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 741 is not covered by tests
Copy link
Member

Choose a reason for hiding this comment

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

hashCode() is not collision-free so could cause duplicate names.

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 confirmed that collisions happen when the hashCode values of two clouds are the same and the cloud name of both clouds is blank. Since the hashCode includes the DockerAPI object and the list of templates, I think it will be very rare that a Jenkins controller has more than one docker cloud definition that have the same values for DockerAPI and template list and use blank names for the cloud name.

I think it is better to risk a collision and prefer a stable transformation from blank name to generated name. If I remove the risk of collision, it seems like I am accepting that the transformation from blank name to generated name will choose a unique name each time it is run.

Does that seem like an acceptable risk to you or should I accept that the transformation will be different each time it is applied to a docker cloud that has a blank name?

Copy link
Member

Choose a reason for hiding this comment

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

Worse thing that will happen is you will see the same entry twice in the cloud list, causing a weird UX. Clicking on one of them will show you the first entry appearing in the underlying persisted list.
I think it can still be renamed to a different name by the user to clear the ambiguity.
However until the rename happens, the second cloud with the same name won't be editable.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When I did the rename in my test of editing the first in the list, it showed the new name for both in the web page. However, I didn't spend a lot of time exploring beyond that.

I think this is ready to merge. Interactive testing is complete. The pull request description includes details of that testing. Anything else that you feel should be changed before it is merged and released?

LOGGER.warn("Docker cloud requires a non-blank name after Jenkins 2.402, using '" + newName + "'");

Check warning on line 742 in src/main/java/com/nirima/jenkins/plugins/docker/DockerCloud.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 742 is not covered by tests
return new DockerCloud(newName, this);

Check warning on line 743 in src/main/java/com/nirima/jenkins/plugins/docker/DockerCloud.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 743 is not covered by tests
}

return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,17 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import org.hamcrest.MatcherAssert;
import org.hamcrest.core.IsIterableContaining;
import org.jenkinsci.plugins.docker.commons.credentials.DockerServerCredentials;
import org.jenkinsci.plugins.docker.commons.credentials.DockerServerEndpoint;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.LoggerRule;

/**
* @author Kanstantsin Shautsou
Expand All @@ -33,6 +38,9 @@ public class DockerCloudTest {
@Rule
public JenkinsRule jenkins = new JenkinsRule();

@Rule
public LoggerRule lr = new LoggerRule();

@SuppressWarnings("unused")
@Test
public void testConstructor_0_10_2() {
Expand All @@ -48,6 +56,43 @@ public void testConstructor_0_10_2() {
null); // dockerHostname
}

private static final String LOG_MESSAGE = "Docker cloud requires a non-blank name after Jenkins 2.402";

@Issue("JENKINS-70729") // Warn if cloud name is empty
@Test
public void testConstructorWithEmptyName() {
lr.record(DockerCloud.class.getName(), Level.ALL).capture(16);
DockerCloud cloud =
new DockerCloud("", new DockerAPI(new DockerServerEndpoint("uri", "credentialsId")), List.of());
Assert.assertEquals(cloud.getDisplayName(), "");
MatcherAssert.assertThat(lr.getMessages(), IsIterableContaining.hasItem(LOG_MESSAGE));
}

@Issue("JENKINS-70729") // Warn if cloud name is null
@Test
public void testConstructorWithNullName() {
lr.record(DockerCloud.class.getName(), Level.ALL).capture(16);
DockerCloud cloud =
new DockerCloud(null, new DockerAPI(new DockerServerEndpoint("uri", "credentialsId")), List.of());
Assert.assertEquals(cloud.getDisplayName(), null);
MatcherAssert.assertThat(lr.getMessages(), IsIterableContaining.hasItem(LOG_MESSAGE));
}

@Issue("JENKINS-70729") // Handle null or empty cloud name
@Test
public void testCopyConstructor() {
lr.record(DockerCloud.class.getName(), Level.ALL).capture(16);
DockerCloud cloud =
new DockerCloud(null, new DockerAPI(new DockerServerEndpoint("uri", "credentialsId")), List.of());
Assert.assertEquals(cloud.getDisplayName(), null);
MatcherAssert.assertThat(lr.getMessages(), IsIterableContaining.hasItem(LOG_MESSAGE));
String newName = "docker-cloud-" + Integer.toHexString(cloud.hashCode());
DockerCloud copy = new DockerCloud(newName, cloud);
Assert.assertEquals(cloud.getDockerApi(), copy.getDockerApi());
Assert.assertEquals(cloud.getTemplates().hashCode(), copy.getTemplates().hashCode());
Assert.assertEquals(newName, copy.getDisplayName());
}

@Test
public void globalConfigRoundtrip() throws Exception {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,11 @@ public class JenkinsUtilsTest {
public void getCloudByNameOrThrowGivenNameThenReturnsCloud() throws Exception {
// Given
final DockerAPI dockerApi = new DockerAPI(new DockerServerEndpoint("uri", "credentialsId"));
final DockerCloud cloudEmpty = new DockerCloud("", dockerApi, Collections.emptyList());
final String expectedCloudName = "expectedCloudName";
final DockerCloud cloudExpected = new DockerCloud(expectedCloudName, dockerApi, Collections.emptyList());
final DockerCloud cloudOther = new DockerCloud("otherCloudName", dockerApi, Collections.emptyList());
final OtherTypeOfCloud cloudForeign = new OtherTypeOfCloud("foreign");
final List<Cloud> clouds = List.of(cloudEmpty, cloudOther, cloudExpected, cloudForeign);
final List<Cloud> clouds = List.of(cloudOther, cloudExpected, cloudForeign);
jenkins.getInstance().clouds.replaceBy(clouds);

// When
Expand All @@ -47,14 +46,13 @@ public void getCloudByNameOrThrowGivenNameThenReturnsCloud() throws Exception {
public void getCloudByNameOrThrowGivenUnknownNameThenThrows() throws Exception {
// Given
final DockerAPI dockerApi = new DockerAPI(new DockerServerEndpoint("uri", "credentialsId"));
final DockerCloud cloudEmpty = new DockerCloud("", dockerApi, Collections.emptyList());
final String requestedCloudName = "anUnknownCloudName";
final String cloudName1 = "expectedCloudName";
final DockerCloud cloud1 = new DockerCloud(cloudName1, dockerApi, Collections.emptyList());
final String cloudName2 = "otherCloudName";
final DockerCloud cloud2 = new DockerCloud(cloudName2, dockerApi, Collections.emptyList());
final OtherTypeOfCloud cloudForeign = new OtherTypeOfCloud("foreign");
final List<Cloud> clouds = List.of(cloudEmpty, cloud2, cloud1, cloudForeign);
final List<Cloud> clouds = List.of(cloud2, cloud1, cloudForeign);
jenkins.getInstance().clouds.replaceBy(clouds);

try {
Expand All @@ -75,14 +73,13 @@ public void getCloudByNameOrThrowGivenUnknownNameThenThrows() throws Exception {
public void getCloudByNameOrThrowGivenForeignCloudNameThenThrows() throws Exception {
// Given
final DockerAPI dockerApi = new DockerAPI(new DockerServerEndpoint("uri", "credentialsId"));
final DockerCloud cloudEmpty = new DockerCloud("", dockerApi, Collections.emptyList());
final String requestedCloudName = "foreign";
final String cloudName1 = "DockerCloud1Name";
final DockerCloud cloud1 = new DockerCloud(cloudName1, dockerApi, Collections.emptyList());
final String cloudName2 = "DockerCloud2Name";
final DockerCloud cloud2 = new DockerCloud(cloudName2, dockerApi, Collections.emptyList());
final OtherTypeOfCloud cloudForeign = new OtherTypeOfCloud(requestedCloudName);
final List<Cloud> clouds = List.of(cloudEmpty, cloud2, cloud1, cloudForeign);
final List<Cloud> clouds = List.of(cloud2, cloud1, cloudForeign);
jenkins.getInstance().clouds.replaceBy(clouds);

try {
Expand Down
Loading