Skip to content

Commit 6e64a04

Browse files
authored
Merge pull request #1107 from sarveshkaushal/cp-test-fix
Supporting copy operation when tar is not present
2 parents 794fd97 + cac60fe commit 6e64a04

File tree

3 files changed

+270
-0
lines changed

3 files changed

+270
-0
lines changed

util/src/main/java/io/kubernetes/client/Copy.java

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,17 @@
1919
import io.kubernetes.client.openapi.models.V1Pod;
2020
import io.kubernetes.client.util.exception.CopyNotSupportedException;
2121
import java.io.BufferedInputStream;
22+
import java.io.BufferedReader;
2223
import java.io.ByteArrayInputStream;
2324
import java.io.File;
2425
import java.io.FileInputStream;
2526
import java.io.FileOutputStream;
2627
import java.io.IOException;
2728
import java.io.InputStream;
29+
import java.io.InputStreamReader;
2830
import java.io.OutputStream;
2931
import java.nio.file.Path;
32+
import java.nio.file.Paths;
3033
import org.apache.commons.codec.binary.Base64InputStream;
3134
import org.apache.commons.codec.binary.Base64OutputStream;
3235
import org.apache.commons.compress.archivers.ArchiveEntry;
@@ -121,6 +124,35 @@ public void copyDirectoryFromPod(
121124
if (!isTarPresentInContainer(namespace, pod, container)) {
122125
throw new CopyNotSupportedException("Tar is not present in the target container");
123126
}
127+
copyDirectoryFromPod(namespace, pod, container, srcPath, destination, true);
128+
}
129+
130+
/**
131+
* Copy directory from a pod to local.
132+
*
133+
* @param namespace
134+
* @param pod
135+
* @param container
136+
* @param srcPath
137+
* @param destination
138+
* @param enableTarCompressing: false if tar is not present in target container
139+
* @throws IOException
140+
* @throws ApiException
141+
*/
142+
public void copyDirectoryFromPod(
143+
String namespace,
144+
String pod,
145+
String container,
146+
String srcPath,
147+
Path destination,
148+
boolean enableTarCompressing)
149+
throws IOException, ApiException {
150+
if (!enableTarCompressing) {
151+
TreeNode tree = new TreeNode(true, srcPath, true);
152+
createDirectoryTree(tree, namespace, pod, container, srcPath);
153+
createDirectoryStructureFromTree(tree, namespace, pod, container, srcPath, destination);
154+
return;
155+
}
124156
final Process proc =
125157
this.exec(
126158
namespace,
@@ -165,6 +197,125 @@ public void copyDirectoryFromPod(
165197
}
166198
}
167199

200+
// This creates directories and files using tree of files and directories under container
201+
private void createDirectoryStructureFromTree(
202+
TreeNode tree,
203+
String namespace,
204+
String pod,
205+
String container,
206+
String srcPath,
207+
Path destination)
208+
throws IOException, ApiException {
209+
// Code for creating invidual directory and files
210+
createDirectory(tree, destination);
211+
createFiles(tree, namespace, pod, container, srcPath, destination);
212+
}
213+
214+
// Method to create files from directories/files tree in destination
215+
private void createFiles(
216+
TreeNode node,
217+
String namespace,
218+
String pod,
219+
String container,
220+
String srcPath,
221+
Path destination)
222+
throws IOException, ApiException {
223+
if (node == null) {
224+
return;
225+
}
226+
for (TreeNode childNode : node.getChildren()) {
227+
if (!childNode.isDir) {
228+
// Create file which is under 'node'
229+
String filePath = genericPathBuilder(destination.toString(), childNode.name);
230+
File f = new File(filePath);
231+
if (!f.createNewFile()) {
232+
throw new IOException("Failed to create file: " + f);
233+
}
234+
String modifiedSrcPath = genericPathBuilder(srcPath, childNode.name);
235+
try (InputStream is = copyFileFromPod(namespace, pod, modifiedSrcPath);
236+
OutputStream fs = new FileOutputStream(f)) {
237+
ByteStreams.copy(is, fs);
238+
fs.flush();
239+
}
240+
} else {
241+
String newSrcPath = genericPathBuilder(srcPath, childNode.name);
242+
// TODO: Change this once the method genericPathBuilder is changed to varargs
243+
Path newDestination = Paths.get(destination.toString(), childNode.name);
244+
createFiles(childNode, namespace, pod, container, newSrcPath, newDestination);
245+
}
246+
}
247+
}
248+
249+
// Method to create directories in destination path
250+
private void createDirectory(TreeNode node, Path destination) throws IOException {
251+
if (node == null) {
252+
return;
253+
}
254+
if (!node.isRoot) {
255+
// String directoryPath = genericPathBuilder(destination.toString(), node.name);
256+
File f = new File(destination.toString());
257+
// File f = new File(directoryPath);
258+
if (!f.mkdirs()) {
259+
throw new IOException("Failed to create directory: " + f);
260+
}
261+
}
262+
for (TreeNode childNode : node.getChildren()) {
263+
if (childNode.isDir) {
264+
String path = genericPathBuilder(destination.toString(), childNode.name);
265+
Path newPath = Paths.get(path);
266+
createDirectory(childNode, newPath);
267+
}
268+
}
269+
}
270+
271+
// Method to create a tree of files and directory of location inside container
272+
private void createDirectoryTree(
273+
TreeNode node, String namespace, String pod, String container, String srcPath)
274+
throws IOException, ApiException {
275+
if (node.isDir) {
276+
final Process proc =
277+
this.exec(
278+
namespace,
279+
pod,
280+
new String[] {"sh", "-c", "ls -F " + srcPath},
281+
container,
282+
false,
283+
false);
284+
285+
try (InputStream is = proc.getInputStream();
286+
BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
287+
String line = "";
288+
while ((line = reader.readLine()) != null) {
289+
int len = line.length();
290+
// line = stripFileSeparators(line);
291+
if (line.charAt(line.length() - 1) == '/') {
292+
TreeNode subDirTree =
293+
new TreeNode(
294+
true,
295+
line.substring(0, len - 1),
296+
false); // Stripping off '/' in the end of directory
297+
String path = genericPathBuilder(srcPath, subDirTree.name);
298+
createDirectoryTree(subDirTree, namespace, pod, container, path);
299+
node.getChildren().add(subDirTree);
300+
} else {
301+
node.getChildren().add(new TreeNode(false, line, false));
302+
}
303+
}
304+
}
305+
}
306+
}
307+
308+
/*
309+
Generic method to create path.
310+
TODO: Change this to varargs
311+
*/
312+
private String genericPathBuilder(String first, String second) {
313+
StringBuilder pathBuilder = new StringBuilder(first);
314+
pathBuilder.append(File.separator);
315+
pathBuilder.append(second);
316+
return pathBuilder.toString();
317+
}
318+
168319
public static void copyFileFromPod(String namespace, String pod, String srcPath, Path dest)
169320
throws ApiException, IOException {
170321
Copy c = new Copy();
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
Copyright 2020 The Kubernetes Authors.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
package io.kubernetes.client;
14+
15+
import com.google.gson.Gson;
16+
import java.util.ArrayList;
17+
import java.util.List;
18+
19+
/** Helper class to represent directory tree */
20+
public class TreeNode {
21+
boolean isDir;
22+
String name;
23+
boolean isRoot;
24+
List<TreeNode> children;
25+
26+
public TreeNode(boolean isDir, String name, boolean isRoot) {
27+
this.isDir = isDir;
28+
this.name = name;
29+
this.isRoot = isRoot;
30+
this.children = new ArrayList<>();
31+
}
32+
33+
public boolean isDir() {
34+
return isDir;
35+
}
36+
37+
public void setDir(boolean dir) {
38+
isDir = dir;
39+
}
40+
41+
public String getName() {
42+
return name;
43+
}
44+
45+
public void setName(String name) {
46+
this.name = name;
47+
}
48+
49+
public boolean isRoot() {
50+
return isRoot;
51+
}
52+
53+
public void setRoot(boolean root) {
54+
isRoot = root;
55+
}
56+
57+
public List<TreeNode> getChildren() {
58+
return children;
59+
}
60+
61+
public void setChildren(List<TreeNode> children) {
62+
this.children = children;
63+
}
64+
65+
@Override
66+
public String toString() {
67+
return new Gson().toJson(this).toString();
68+
}
69+
}

util/src/test/java/io/kubernetes/client/CopyTest.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@
2626
import io.kubernetes.client.openapi.models.V1ObjectMeta;
2727
import io.kubernetes.client.openapi.models.V1Pod;
2828
import io.kubernetes.client.util.ClientBuilder;
29+
import io.kubernetes.client.util.exception.CopyNotSupportedException;
2930
import java.io.File;
3031
import java.io.IOException;
3132
import java.io.InputStream;
3233
import java.nio.file.Paths;
3334
import org.junit.Before;
3435
import org.junit.Rule;
3536
import org.junit.Test;
37+
import org.junit.rules.TemporaryFolder;
3638

3739
/** Tests for the Copy helper class */
3840
public class CopyTest {
@@ -44,6 +46,7 @@ public class CopyTest {
4446

4547
private static final int PORT = 8089;
4648
@Rule public WireMockRule wireMockRule = new WireMockRule(PORT);
49+
@Rule public TemporaryFolder folder = new TemporaryFolder();
4750

4851
@Before
4952
public void setup() throws IOException {
@@ -185,4 +188,51 @@ public void run() {
185188
.withQueryParam("command", equalTo("-c"))
186189
.withQueryParam("command", equalTo("base64 -d | tar -xmf - -C /")));
187190
}
191+
192+
public void testCopyDirectoryFromPod() throws IOException, ApiException, InterruptedException {
193+
194+
// Create a temp directory
195+
File tempFolder = folder.newFolder("destinationFolder");
196+
197+
Copy copy = new Copy(client);
198+
199+
wireMockRule.stubFor(
200+
get(urlPathEqualTo("/api/v1/namespaces/" + namespace + "/pods/" + podName + "/exec"))
201+
.willReturn(
202+
aResponse()
203+
.withStatus(404)
204+
.withHeader("Content-Type", "application/json")
205+
.withBody("{}")));
206+
207+
Thread t =
208+
new Thread(
209+
new Runnable() {
210+
public void run() {
211+
try {
212+
copy.copyDirectoryFromPod(
213+
namespace,
214+
podName,
215+
"",
216+
tempFolder.toPath().toString(),
217+
Paths.get("/copied-testDir"));
218+
} catch (IOException | ApiException | CopyNotSupportedException ex) {
219+
ex.printStackTrace();
220+
}
221+
}
222+
});
223+
t.start();
224+
Thread.sleep(2000);
225+
t.interrupt();
226+
227+
verify(
228+
getRequestedFor(
229+
urlPathEqualTo("/api/v1/namespaces/" + namespace + "/pods/" + podName + "/exec"))
230+
.withQueryParam("stdin", equalTo("false"))
231+
.withQueryParam("stdout", equalTo("true"))
232+
.withQueryParam("stderr", equalTo("true"))
233+
.withQueryParam("tty", equalTo("false"))
234+
.withQueryParam("command", equalTo("sh"))
235+
.withQueryParam("command", equalTo("-c"))
236+
.withQueryParam("command", equalTo("tar --version")));
237+
}
188238
}

0 commit comments

Comments
 (0)