Skip to content

Commit 3a2dcc4

Browse files
KostyaShamarcuslinke
authored andcommitted
Implemented Device parser (docker-java#593)
* Implement Device parser Signed-off-by: Kanstantsin Shautsou <kanstantsin.sha@gmail.com> * Add more tests, fix.
1 parent 6058156 commit 3a2dcc4

File tree

2 files changed

+171
-0
lines changed

2 files changed

+171
-0
lines changed

src/main/java/com/github/dockerjava/api/model/Device.java

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.github.dockerjava.api.model;
22

33
import static com.google.common.base.Preconditions.checkNotNull;
4+
import static org.apache.commons.lang.BooleanUtils.isNotTrue;
5+
import static org.apache.commons.lang.StringUtils.isEmpty;
46

57
import org.apache.commons.lang.builder.EqualsBuilder;
68
import org.apache.commons.lang.builder.HashCodeBuilder;
@@ -9,6 +11,11 @@
911
import com.fasterxml.jackson.annotation.JsonInclude.Include;
1012
import com.fasterxml.jackson.annotation.JsonProperty;
1113

14+
import javax.annotation.Nonnull;
15+
import java.util.HashMap;
16+
import java.util.Map;
17+
import java.util.StringTokenizer;
18+
1219
@JsonInclude(Include.NON_NULL)
1320
public class Device {
1421

@@ -45,6 +52,75 @@ public String getPathOnHost() {
4552
return pathOnHost;
4653
}
4754

55+
/**
56+
* @link https://github.com/docker/docker/blob/6b4a46f28266031ce1a1315f17fb69113a06efe1/runconfig/opts/parse_test.go#L468
57+
*/
58+
@Nonnull
59+
public static Device parse(@Nonnull String deviceStr) {
60+
String src = "";
61+
String dst = "";
62+
String permissions = "rwm";
63+
final String[] arr = deviceStr.trim().split(":");
64+
// java String.split() returns wrong length, use tokenizer instead
65+
switch (new StringTokenizer(deviceStr, ":").countTokens()) {
66+
case 3: {
67+
// Mismatches docker code logic. While there is no validations after parsing, checking heregit
68+
if (validDeviceMode(arr[2])) {
69+
permissions = arr[2];
70+
} else {
71+
throw new IllegalArgumentException("Invalid device specification: " + deviceStr);
72+
}
73+
}
74+
case 2: {
75+
if (validDeviceMode(arr[1])) {
76+
permissions = arr[1];
77+
} else {
78+
dst = arr[1];
79+
}
80+
}
81+
case 1: {
82+
src = arr[0];
83+
break;
84+
}
85+
default: {
86+
throw new IllegalArgumentException("Invalid device specification: " + deviceStr);
87+
}
88+
}
89+
90+
if (isEmpty(dst)) {
91+
dst = src;
92+
}
93+
94+
return new Device(permissions, dst, src);
95+
}
96+
97+
/**
98+
* ValidDeviceMode checks if the mode for device is valid or not.
99+
* Valid mode is a composition of r (read), w (write), and m (mknod).
100+
*
101+
* @link https://github.com/docker/docker/blob/6b4a46f28266031ce1a1315f17fb69113a06efe1/runconfig/opts/parse.go#L796
102+
*/
103+
private static boolean validDeviceMode(String deviceMode) {
104+
Map<String, Boolean> validModes = new HashMap<>(3);
105+
validModes.put("r", true);
106+
validModes.put("w", true);
107+
validModes.put("m", true);
108+
109+
if (isEmpty(deviceMode)) {
110+
return false;
111+
}
112+
113+
for (char ch : deviceMode.toCharArray()) {
114+
final String mode = String.valueOf(ch);
115+
if (isNotTrue(validModes.get(mode))) {
116+
return false; // wrong mode
117+
}
118+
validModes.put(mode, false);
119+
}
120+
121+
return true;
122+
}
123+
48124
@Override
49125
public boolean equals(Object obj) {
50126
if (obj instanceof Device) {
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package com.github.dockerjava.api.model;
2+
3+
import org.testng.annotations.Test;
4+
5+
import java.util.ArrayList;
6+
import java.util.Arrays;
7+
import java.util.Collections;
8+
import java.util.HashMap;
9+
import java.util.LinkedHashMap;
10+
import java.util.List;
11+
import java.util.Map;
12+
13+
import static junit.framework.Assert.fail;
14+
import static org.hamcrest.MatcherAssert.assertThat;
15+
import static org.hamcrest.Matchers.contains;
16+
import static org.hamcrest.Matchers.containsString;
17+
import static org.hamcrest.Matchers.equalTo;
18+
import static org.hamcrest.Matchers.is;
19+
20+
/**
21+
* @author Kanstantsin Shautsou
22+
*/
23+
public class DeviceTest {
24+
25+
public static List<String> validPaths = Arrays.asList(
26+
"/home",
27+
"/home:/home",
28+
"/home:/something/else",
29+
"/with space",
30+
"/home:/with space",
31+
"relative:/absolute-path",
32+
"hostPath:/containerPath:r",
33+
"/hostPath:/containerPath:rw",
34+
"/hostPath:/containerPath:mrw"
35+
);
36+
37+
public static HashMap<String, String> badPaths = new LinkedHashMap<String, String>() {{
38+
put("", "bad format for path: ");
39+
// TODO implement ValidatePath
40+
// put("./", "./ is not an absolute path");
41+
// put("../", "../ is not an absolute path");
42+
// put("/:../", "../ is not an absolute path");
43+
// put("/:path", "path is not an absolute path");
44+
// put(":", "bad format for path: :");
45+
// put("/tmp:", " is not an absolute path");
46+
// put(":test", "bad format for path: :test");
47+
// put(":/test", "bad format for path: :/test");
48+
// put("tmp:", " is not an absolute path");
49+
// put(":test:", "bad format for path: :test:");
50+
// put("::", "bad format for path: ::");
51+
// put(":::", "bad format for path: :::");
52+
// put("/tmp:::", "bad format for path: /tmp:::");
53+
// put(":/tmp::", "bad format for path: :/tmp::");
54+
// put("path:ro", "ro is not an absolute path");
55+
// put("path:rr", "rr is not an absolute path");
56+
put("a:/b:ro", "bad mode specified: ro");
57+
put("a:/b:rr", "bad mode specified: rr");
58+
}};
59+
60+
@Test
61+
public void testParse() throws Exception {
62+
assertThat(Device.parse("/dev/sda:/dev/xvdc:r"),
63+
equalTo(new Device("r", "/dev/xvdc", "/dev/sda")));
64+
65+
assertThat(Device.parse("/dev/snd:rw"),
66+
equalTo(new Device("rw", "/dev/snd", "/dev/snd")));
67+
68+
assertThat(Device.parse("/dev/snd:/something"),
69+
equalTo(new Device("rwm", "/something", "/dev/snd")));
70+
71+
assertThat(Device.parse("/dev/snd:/something:rw"),
72+
equalTo(new Device("rw", "/something", "/dev/snd")));
73+
74+
}
75+
76+
@Test
77+
public void testParseBadPaths() {
78+
for (Map.Entry<String, String> entry : badPaths.entrySet()) {
79+
final String deviceStr = entry.getKey();
80+
try {
81+
Device.parse(deviceStr);
82+
fail("Should fail because: " + entry.getValue() + " '" + deviceStr + "'");
83+
} catch (IllegalArgumentException ex) {
84+
assertThat(ex.getMessage(), containsString("Invalid device specification:"));
85+
}
86+
}
87+
}
88+
89+
@Test
90+
public void testParseValidPaths() {
91+
for (String path : validPaths) {
92+
Device.parse(path);
93+
}
94+
}
95+
}

0 commit comments

Comments
 (0)