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 docker run --dns-search flag to agent template #1105

Merged
merged 2 commits into from
Oct 12, 2024
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 @@ -39,6 +39,7 @@ def templateBaseParameters = [
// cpuShares: (Integer)null,
// devicesString: '',
// dnsString: '',
// dnsSearchString: '',
// dockerCommand: '',
// environmentsString: '',
// extraDockerLabelsString: '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public DockerSimpleTemplate(
String image,
String pullCredentialsId,
String dnsString,
String dnsSearchString,
String network,
String dockerCommand,
String mountsString,
Expand All @@ -35,6 +36,7 @@ public DockerSimpleTemplate(
image,
pullCredentialsId,
dnsString,
dnsSearchString,
network,
dockerCommand,
mountsString,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@
return dockerTemplateBase.getDnsString();
}

public String getDnsSearchString() {
return dockerTemplateBase.getDnsSearchString();

Check warning on line 165 in src/main/java/com/nirima/jenkins/plugins/docker/DockerTemplate.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 165 is not covered by tests
}

@CheckForNull
public String[] getMounts() {
return dockerTemplateBase.getMounts();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@

public @CheckForNull String[] dnsHosts;

public @CheckForNull String[] dnsSearch;

public @CheckForNull String network;

/**
Expand Down Expand Up @@ -158,6 +160,7 @@
* @param image See {@link #DockerTemplateBase(String)}
* @param pullCredentialsId See {@link #setPullCredentialsId(String)}
* @param dnsString See {@link #setDnsString(String)}
* @param dnsSearchString See {@link #setDnsSearchString(String)}
* @param network See {@link #setNetwork(String)}
* @param dockerCommand See {@link #setDockerCommand(String)}
* @param mountsString See {@link #setMountsString(String)}
Expand All @@ -184,6 +187,7 @@
String image,
String pullCredentialsId,
String dnsString,
String dnsSearchString,
String network,
String dockerCommand,
String mountsString,
Expand All @@ -207,6 +211,7 @@
this(image);
setPullCredentialsId(pullCredentialsId);
setDnsString(dnsString);
setDnsSearchString(dnsSearchString);
setNetwork(network);
setDockerCommand(dockerCommand);
setMountsString(mountsString);
Expand Down Expand Up @@ -335,6 +340,28 @@
setDnsHosts(splitAndFilterEmpty(dnsString, " "));
}

@CheckForNull
public String[] getDnsSearch() {
return fixEmpty(dnsSearch);
}

@NonNull
public String getDnsSearchString() {
if (dnsSearch == null) {

Check warning on line 350 in src/main/java/com/nirima/jenkins/plugins/docker/DockerTemplateBase.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 350 is only partially covered, one branch is missing
return "";
}
return Joiner.on(" ").join(dnsSearch);

Check warning on line 353 in src/main/java/com/nirima/jenkins/plugins/docker/DockerTemplateBase.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 353 is not covered by tests
}

public void setDnsSearch(String[] dnsSearch) {
this.dnsSearch = fixEmpty(dnsSearch);
}

@DataBoundSetter
public void setDnsSearchString(String dnsSearchString) {
setDnsSearch(splitAndFilterEmpty(dnsSearchString, " "));
}

@CheckForNull
public String getNetwork() {
return Util.fixEmpty(network);
Expand Down Expand Up @@ -838,309 +865,317 @@
hostConfig(containerConfig).withDns(dnsHostsOrNull);
}

final String[] dnsSearchOrNull = getDnsSearch();
if (dnsSearchOrNull != null && dnsSearchOrNull.length > 0) {

Check warning on line 869 in src/main/java/com/nirima/jenkins/plugins/docker/DockerTemplateBase.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 869 is only partially covered, 3 branches are missing
hostConfig(containerConfig).withDnsSearch(dnsSearchOrNull);
}

final String networkOrNull = getNetwork();
if (networkOrNull != null && networkOrNull.length() > 0) {
containerConfig.withNetworkDisabled(false);
hostConfig(containerConfig).withNetworkMode(networkOrNull);
}

// https://github.com/docker/docker/blob/ed257420025772acc38c51b0f018de3ee5564d0f/runconfig/parse.go#L182-L196
final String[] mountsOrNull = getMounts();
if (mountsOrNull != null && mountsOrNull.length > 0) {
ArrayList<Mount> mnts = new ArrayList<>();
parseMountsStrings(mountsOrNull, mnts);
hostConfig(containerConfig).withMounts(mnts);
}

final String[] volumesFrom2OrNull = getVolumesFrom2();
if (volumesFrom2OrNull != null && volumesFrom2OrNull.length > 0) {
ArrayList<VolumesFrom> volFrom = new ArrayList<>();
for (String volFromStr : volumesFrom2OrNull) {
volFrom.add(VolumesFrom.parse(volFromStr));
}
hostConfig(containerConfig).withVolumesFrom(volFrom.toArray(new VolumesFrom[0]));
}

final String[] devicesOrNull = getDevices();
if (devicesOrNull != null && devicesOrNull.length > 0) {
final List<Device> list = new ArrayList<>();
for (String deviceStr : devicesOrNull) {
list.add(Device.parse(deviceStr));
}
hostConfig(containerConfig).withDevices(list);
}

containerConfig.withTty(tty);

final String[] environmentOrNull = getEnvironment();
if (environmentOrNull != null && environmentOrNull.length > 0) {
containerConfig.withEnv(environmentOrNull);
}

final String macAddressOrNull = getMacAddress();
if (macAddressOrNull != null && !macAddressOrNull.isEmpty()) {
containerConfig.withMacAddress(macAddressOrNull);
}

final List<String> extraHostsOrNull = getExtraHosts();
if (CollectionUtils.isNotEmpty(extraHostsOrNull)) {
hostConfig(containerConfig).withExtraHosts(extraHostsOrNull.toArray(new String[0]));
}

final Integer shmSizeOrNull = getShmSize();
if (shmSizeOrNull != null && shmSizeOrNull.intValue() > 0) {
final long shmSizeInByte = shmSizeOrNull.longValue() * 1024L * 1024L;
hostConfig(containerConfig).withShmSize(shmSizeInByte);
}

final List<String> securityOptionsOrNull = getSecurityOpts();
if (CollectionUtils.isNotEmpty(securityOptionsOrNull)) {
hostConfig(containerConfig).withSecurityOpts(securityOptionsOrNull);
}

final List<String> capabilitiesToAddOrNull = getCapabilitiesToAdd();
if (CollectionUtils.isNotEmpty(capabilitiesToAddOrNull)) {
hostConfig(containerConfig).withCapAdd(toCapabilities(capabilitiesToAddOrNull));
}

final List<String> capabilitiesToDropOrNull = getCapabilitiesToDrop();
if (CollectionUtils.isNotEmpty(capabilitiesToDropOrNull)) {
hostConfig(containerConfig).withCapDrop(toCapabilities(capabilitiesToDropOrNull));
}

return containerConfig;
}

/**
* Parses a given mountsString value, appending any {@link Mount}s to the specified lists.
* @param mounts The strings to be parsed.
* @param mountListResult List to which any {@link Mount}s should be stored in.
* @throws IllegalArgumentException if anything is invalid.
*/
private static void parseMountsStrings(final String[] mounts, List<Mount> mountListResult) {
for (String mnt : mounts) {
parseMountsString(mnt, mountListResult);
}
}

private static void parseMountsString(String mnt, List<Mount> mountListResult) {
Mount mount = new Mount().withType(MountType.VOLUME);
BindOptions bindOptions = null;
TmpfsOptions tmpfsOptions = null;

final String[] tokens = mnt.split(",");
for (String token : tokens) {
final String[] parts = token.split("=");
if (!(parts.length == 2 || parts.length == 1 && ("ro".equals(parts[0]) || "readonly".equals(parts[0])))) {
throw new IllegalArgumentException(
"Invalid mount: expected key=value comma separated pairs, or 'ro' / 'readonly' keywords");
}

switch (parts[0]) {
case "type":
mount.withType(MountType.valueOf(parts[1].toUpperCase()));
break;
case "src":
case "source":
mount.withSource(parts[1]);
break;
case "target":
case "destination":
case "dst":
mount.withTarget(parts[1]);
break;
case "ro":
case "readonly":
String value = parts.length == 2 && parts[1] != null ? parts[1].trim() : "";
if (value.isEmpty() || "true".equalsIgnoreCase(value) || "1".equals(value)) {
mount.withReadOnly(true);
} else if ("false".equalsIgnoreCase(value) || "0".equals(value)) {
mount.withReadOnly(false);
}
break;
case "bind-propagation":
bindOptions = new BindOptions()
.withPropagation(BindPropagation.valueOf(
(parts[1].startsWith("r") ? "R_" + parts[1].substring(1) : parts[1])
.toUpperCase()));
break;
case "tmpfs-mode":
if (tmpfsOptions == null) {
tmpfsOptions = new TmpfsOptions();
}
tmpfsOptions.withMode(Integer.parseInt(parts[1], 8));
break;
case "tmpfs-size":
if (tmpfsOptions == null) {
tmpfsOptions = new TmpfsOptions();
}
tmpfsOptions.withSizeBytes(Long.parseLong(parts[1]));
break;
default:
throw new IllegalArgumentException("Unsupported keyword: " + parts[0]);
}
}

String target = mount.getTarget();
if (target == null || target.isEmpty()) {
throw new IllegalArgumentException("Invalid mount: target/destination must be set");
}

if (bindOptions != null) {
mount.withBindOptions(bindOptions);
}
if (tmpfsOptions != null) {
mount.withTmpfsOptions(tmpfsOptions);
}
mountListResult.add(mount);
}

private static String[] convertVolumes(String[] vols) {
List<String> mnts = new ArrayList<>();
for (String vol : vols) {
if (!vol.contains(":")) {
mnts.add("type=volume,destination=" + vol);
} else {
StringBuilder builder = new StringBuilder();
if (vol.startsWith("/")) {
Bind bind = Bind.parse(vol);
builder.append("type=bind,source=");
builder.append(bind.getPath());
builder.append(",destination=");
builder.append(bind.getVolume().getPath());
if (bind.getAccessMode() == AccessMode.ro) {
builder.append(",readonly");
}
if (bind.getPropagationMode() != PropagationMode.DEFAULT) {
builder.append(",bind-propagation=");
builder.append(bind.getPropagationMode().toString());
}
} else {
String[] parts = vol.split(":");
builder.append("type=volume,source=");
builder.append(parts[0]);
builder.append(",destination=");
builder.append(parts[1]);
if (parts.length == 3
&& ("readonly".equalsIgnoreCase(parts[2]) || "ro".equalsIgnoreCase(parts[2]))) {
builder.append(",readonly");
}
}
mnts.add(builder.toString());
}
}
return mnts.toArray(new String[0]);
}

@NonNull
private static HostConfig hostConfig(CreateContainerCmd containerConfig) {
final HostConfig hc = containerConfig.getHostConfig();
if (hc == null) {
throw new IllegalStateException("Can't find " + HostConfig.class.getCanonicalName() + " within "
+ CreateContainerCmd.class.getCanonicalName() + " " + containerConfig);
}
return hc;
}

private static Capability[] toCapabilities(List<String> capabilitiesString) {
final ArrayList<Capability> res = new ArrayList<>();
for (String capability : capabilitiesString) {
try {
res.add(Capability.valueOf(capability));
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid capability name : " + capability, e);
}
}
return res.toArray(new Capability[0]);
}

/**
* Calculates the value we use for the Docker label called
* {@link DockerContainerLabelKeys#JENKINS_URL} that we put into every
* container we make, so that we can recognize our own containers later.
*/
@NonNull
static String getJenkinsUrlForContainerLabel() {
final Jenkins jenkins = Jenkins.getInstanceOrNull();
// Note: Jenkins.getInstanceOrNull() can return null during unit-tests.
final String rootUrl = jenkins == null ? null : jenkins.getRootUrl();
return Util.fixNull(rootUrl);
}

/**
* Calculates the value we use for the Docker label called
* {@link DockerContainerLabelKeys#JENKINS_INSTANCE_ID} that we put into every
* container we make, so that we can recognize our own containers later.
*/
@NonNull
static String getJenkinsInstanceIdForContainerLabel() {
return JenkinsUtils.getInstanceId();
}

@Override
public Descriptor<DockerTemplateBase> getDescriptor() {
return Jenkins.get().getDescriptor(DockerTemplateBase.class);
}

public String getFullImageId() {
NameParser.ReposTag repostag = NameParser.parseRepositoryTag(image);
// if image was specified without tag, then treat as latest
if (repostag.tag.isEmpty()) {
if (repostag.repos.contains("@sha256:")) {
// image has no tag but instead use a digest, do not append anything!
return repostag.repos;
}
// else no tag provided, append latest as tag
return repostag.repos + ":" + "latest";
}
// else use declared tag:
return repostag.repos + ":" + repostag.tag;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DockerTemplateBase that = (DockerTemplateBase) o;
// Maintenance note: This should include all non-transient fields.
// Fields that are "usually unique" should go first.
// Primitive fields should be tested before objects.
// Computationally-expensive fields get tested last.
// Note: If modifying this code, remember to update hashCode() and toString()
if (bindAllPorts != that.bindAllPorts) {
return false;
}
if (privileged != that.privileged) {
return false;
}
if (tty != that.tty) {
return false;
}
if (!image.equals(that.image)) {
return false;
}
if (!Objects.equals(pullCredentialsId, that.pullCredentialsId)) {
return false;
}
if (!Objects.equals(dockerCommand, that.dockerCommand)) {
return false;
}
if (!Objects.equals(hostname, that.hostname)) {
return false;
}
if (!Objects.equals(user, that.user)) {
return false;
}
if (!Objects.equals(extraGroups, that.extraGroups)) {
return false;
}
if (!Arrays.equals(dnsHosts, that.dnsHosts)) {
return false;
}
if (!Arrays.equals(dnsSearch, that.dnsSearch)) {
return false;

Check warning on line 1177 in src/main/java/com/nirima/jenkins/plugins/docker/DockerTemplateBase.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 870-1177 are not covered by tests
}
if (!Objects.equals(network, that.network)) {
return false;
}
Expand Down Expand Up @@ -1216,6 +1251,7 @@
result = 31 * result + (user != null ? user.hashCode() : 0);
result = 31 * result + (extraGroups != null ? extraGroups.hashCode() : 0);
result = 31 * result + Arrays.hashCode(dnsHosts);
result = 31 * result + Arrays.hashCode(dnsSearch);
result = 31 * result + (network != null ? network.hashCode() : 0);
result = 31 * result + Arrays.hashCode(mounts);
result = 31 * result + Arrays.hashCode(volumesFrom2);
Expand Down Expand Up @@ -1255,6 +1291,7 @@
bldToString(sb, "user", user);
bldToString(sb, "extraGroups", extraGroups);
bldToString(sb, "dnsHosts", dnsHosts);
bldToString(sb, "dnsSearch", dnsSearch);

Check warning on line 1294 in src/main/java/com/nirima/jenkins/plugins/docker/DockerTemplateBase.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 1294 is not covered by tests
bldToString(sb, "network'", network);
bldToString(sb, "mounts", mounts);
bldToString(sb, "volumesFrom2", volumesFrom2);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
private String pullCredentialsId;
private transient DockerRegistryEndpoint registry;
public final String dnsString;
public final String dnsSearchString;
public final String network;
public final String dockerCommand;
public final String mountsString;
Expand Down Expand Up @@ -66,6 +67,7 @@
String image,
String pullCredentialsId,
String dnsString,
String dnsSearchString,
String network,
String dockerCommand,
String mountsString,
Expand All @@ -91,6 +93,7 @@
this.image = image;
this.pullCredentialsId = pullCredentialsId;
this.dnsString = dnsString;
this.dnsSearchString = dnsSearchString;

Check warning on line 96 in src/main/java/com/nirima/jenkins/plugins/docker/builder/DockerBuilderControlOptionRun.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 96 is not covered by tests
this.network = network;
this.dockerCommand = dockerCommand;
this.mountsString = mountsString;
Expand Down Expand Up @@ -188,6 +191,7 @@
xImage,
pullCredentialsId,
dnsString,
dnsSearchString,
network,
xCommand,
mountsString,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
<f:textbox />
</f:entry>

<f:entry title="${%DNS Search}" field="dnsSearchString">
<f:textbox />
</f:entry>

<f:entry title="${%Network}" field="network">
<f:textbox />
</f:entry>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
Set the DNS search domains to use within your images, if not set Docker will use DNS settings of the host
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ public void globalConfigRoundtrip() throws Exception {
"image",
"pullCredentialsId",
"dnsString",
"dnsSearchString",
"network",
"dockerCommand",
"mountsString",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class DockerTemplateTest {
String suffixStartAgentCmd = " suffixStartAgentCmd";
String instanceCapStr = "";
String network = "";
String dnsSearchString = "docker.com";

String dockerCommand = "dockerCommand";
String mountsString = "mounts";
Expand All @@ -50,6 +51,7 @@ private DockerTemplate getDockerTemplateInstanceWithDNSHost(String dnsString) {
image,
null,
dnsString,
dnsSearchString,
network,
dockerCommand,
mountsString,
Expand Down Expand Up @@ -98,6 +100,16 @@ public void testDnsHosts() {
assertArrayEquals(expected, instance.getDockerTemplateBase().dnsHosts);
}

@Test
public void testDnsSearch() {
DockerTemplate instance;
String[] expected;

instance = getDockerTemplateInstanceWithDNSHost("");
expected = new String[] {"docker.com"};
assertArrayEquals(expected, instance.getDockerTemplateBase().dnsSearch);
}

@Test
public void testLimits() {
DockerTemplate instance;
Expand Down
Loading