Skip to content

Commit 6ad9103

Browse files
Copy collector enhanced (#237)
Copy collector enhanced
1 parent 016d6e4 commit 6ad9103

File tree

3 files changed

+181
-11
lines changed

3 files changed

+181
-11
lines changed

cmd/troubleshoot/cli/run.go

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
package cli
22

33
import (
4+
"archive/tar"
45
"bytes"
56
"context"
67
"crypto/tls"
78
"encoding/json"
89
"fmt"
10+
"io"
911
"io/ioutil"
1012
"net/http"
1113
"net/url"
1214
"os"
1315
"path/filepath"
16+
"sort"
1417
"strings"
1518
"time"
1619

@@ -395,7 +398,7 @@ func runCollectors(v *viper.Viper, collectors []*troubleshootv1beta1.Collect, ad
395398
}
396399

397400
if result != nil {
398-
err = saveCollectorOutput(result, bundlePath)
401+
err = saveCollectorOutput(result, bundlePath, collector)
399402
if err != nil {
400403
progressChan <- fmt.Errorf("failed to parse collector spec %q: %v", collector.GetDisplayName(), err)
401404
continue
@@ -415,8 +418,15 @@ func runCollectors(v *viper.Viper, collectors []*troubleshootv1beta1.Collect, ad
415418
return filename, nil
416419
}
417420

418-
func saveCollectorOutput(output map[string][]byte, bundlePath string) error {
421+
func saveCollectorOutput(output map[string][]byte, bundlePath string, c *collect.Collector) error {
419422
for filename, maybeContents := range output {
423+
if c.Collect.Copy != nil {
424+
err := untarAndSave(maybeContents, filepath.Join(bundlePath, filepath.Dir(filename)))
425+
if err != nil {
426+
return errors.Wrap(err, "extract copied files")
427+
}
428+
continue
429+
}
420430
fileDir, fileName := filepath.Split(filename)
421431
outPath := filepath.Join(bundlePath, fileDir)
422432

@@ -431,7 +441,59 @@ func saveCollectorOutput(output map[string][]byte, bundlePath string) error {
431441

432442
return nil
433443
}
434-
444+
func untarAndSave(tarFile []byte, bundlePath string) error {
445+
keys := make([]string, 0)
446+
dirs := make(map[string]*tar.Header)
447+
files := make(map[string][]byte)
448+
fileHeaders := make(map[string]*tar.Header)
449+
tarReader := tar.NewReader(bytes.NewBuffer(tarFile))
450+
//Extract and separate tar contentes in file and folders, keeping header info from each one.
451+
for {
452+
header, err := tarReader.Next()
453+
if err != nil {
454+
if err != io.EOF {
455+
return err
456+
}
457+
break
458+
}
459+
switch header.Typeflag {
460+
case tar.TypeDir:
461+
dirs[header.Name] = header
462+
case tar.TypeReg:
463+
file := new(bytes.Buffer)
464+
_, err = io.Copy(file, tarReader)
465+
if err != nil {
466+
return err
467+
}
468+
files[header.Name] = file.Bytes()
469+
fileHeaders[header.Name] = header
470+
default:
471+
return fmt.Errorf("Tar file entry %s contained unsupported file type %v", header.Name, header.FileInfo().Mode())
472+
}
473+
}
474+
//Create directories from base path: <namespace>/<pod name>/containerPath
475+
if err := os.MkdirAll(filepath.Join(bundlePath), 0777); err != nil {
476+
return errors.Wrap(err, "create output file")
477+
}
478+
//Order folders stored in variable keys to start always by parent folder. That way folder info is preserved.
479+
for k := range dirs {
480+
keys = append(keys, k)
481+
}
482+
sort.Strings(keys)
483+
//Orderly create folders.
484+
for _, k := range keys {
485+
if err := os.Mkdir(filepath.Join(bundlePath, k), dirs[k].FileInfo().Mode().Perm()); err != nil {
486+
return errors.Wrap(err, "create output file")
487+
}
488+
}
489+
//Populate folders with respective files and its permissions stored in the header.
490+
for k, v := range files {
491+
if err := ioutil.WriteFile(filepath.Join(bundlePath, k), v, fileHeaders[k].FileInfo().Mode().Perm()); err != nil {
492+
return err
493+
}
494+
}
495+
return nil
496+
}
435497
func uploadSupportBundle(r *troubleshootv1beta1.ResultRequest, archivePath string) error {
436498
contentType := getExpectedContentType(r.URI)
437499
if contentType != "" && contentType != "application/tar+gzip" {

pkg/collect/copy.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"k8s.io/client-go/tools/remotecommand"
1414
)
1515

16+
//Copy function gets a file or folder from a container specified in the specs.
1617
func Copy(c *Collector, copyCollector *troubleshootv1beta1.Copy) (map[string][]byte, error) {
1718
client, err := kubernetes.NewForConfig(c.ClientConfig)
1819
if err != nil {
@@ -47,7 +48,7 @@ func Copy(c *Collector, copyCollector *troubleshootv1beta1.Copy) (map[string][]b
4748
}
4849

4950
for k, v := range files {
50-
copyOutput[filepath.Join(bundlePath, k)] = v
51+
copyOutput[filepath.Join(bundlePath, filepath.Dir(copyCollector.ContainerPath), k)] = v
5152
}
5253
}
5354
}
@@ -60,9 +61,7 @@ func copyFiles(c *Collector, client *kubernetes.Clientset, pod corev1.Pod, copyC
6061
if copyCollector.ContainerName != "" {
6162
container = copyCollector.ContainerName
6263
}
63-
64-
command := []string{"cat", copyCollector.ContainerPath}
65-
64+
command := []string{"tar", "-C", filepath.Dir(copyCollector.ContainerPath), "-cf", "-", filepath.Base(copyCollector.ContainerPath)}
6665
req := client.CoreV1().RESTClient().Post().Resource("pods").Name(pod.Name).Namespace(pod.Namespace).SubResource("exec")
6766
scheme := runtime.NewScheme()
6867
if err := corev1.AddToScheme(scheme); err != nil {
@@ -110,7 +109,7 @@ func copyFiles(c *Collector, client *kubernetes.Clientset, pod corev1.Pod, copyC
110109
}
111110

112111
return map[string][]byte{
113-
copyCollector.ContainerPath: output.Bytes(),
112+
filepath.Base(copyCollector.ContainerPath) + ".tar": output.Bytes(),
114113
}, nil
115114
}
116115

pkg/collect/redact.go

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,129 @@
11
package collect
22

33
import (
4+
"archive/tar"
5+
"bytes"
6+
"compress/gzip"
7+
"encoding/binary"
8+
"io"
9+
"path/filepath"
10+
"strings"
11+
412
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
513
"github.com/replicatedhq/troubleshoot/pkg/redact"
614
)
715

816
func redactMap(input map[string][]byte, additionalRedactors []*troubleshootv1beta1.Redact) (map[string][]byte, error) {
917
result := make(map[string][]byte)
1018
for k, v := range input {
11-
if v != nil {
12-
redacted, err := redact.Redact(v, k, additionalRedactors)
19+
if v == nil {
20+
continue
21+
}
22+
//If the file is .tar, .tgz or .tar.gz, it must not be redacted. Instead it is decompressed and each file inside the
23+
//tar is decompressed, redacted and compressed back into the tar.
24+
if filepath.Ext(k) == ".tar" || filepath.Ext(k) == ".tgz" || strings.HasSuffix(k, ".tar.gz") {
25+
tarFile := bytes.NewBuffer(v)
26+
unRedacted, tarHeaders, err := decompressFile(tarFile, k)
27+
if err != nil {
28+
return nil, err
29+
}
30+
redacted, err := redactMap(unRedacted, additionalRedactors)
31+
if err != nil {
32+
return nil, err
33+
}
34+
result[k], err = compressFiles(redacted, tarHeaders, k)
1335
if err != nil {
1436
return nil, err
1537
}
16-
result[k] = redacted
38+
//Content of the tar file was redacted. Continue to next file.
39+
continue
1740
}
41+
redacted, err := redact.Redact(v, k, additionalRedactors)
42+
if err != nil {
43+
return nil, err
44+
}
45+
result[k] = redacted
1846
}
1947
return result, nil
2048
}
49+
50+
func compressFiles(tarContent map[string][]byte, tarHeaders map[string]*tar.Header, filename string) ([]byte, error) {
51+
buff := new(bytes.Buffer)
52+
var tw *tar.Writer
53+
var zw *gzip.Writer
54+
if filepath.Ext(filename) != ".tar" {
55+
zw = gzip.NewWriter(buff)
56+
tw = tar.NewWriter(zw)
57+
defer zw.Close()
58+
} else {
59+
tw = tar.NewWriter(buff)
60+
}
61+
defer tw.Close()
62+
for p, f := range tarContent {
63+
if tarHeaders[p].FileInfo().IsDir() {
64+
err := tw.WriteHeader(tarHeaders[p])
65+
if err != nil {
66+
return nil, err
67+
}
68+
continue
69+
}
70+
//File size must be recalculated in case the redactor added some bytes while redacting.
71+
tarHeaders[p].Size = int64(binary.Size(f))
72+
err := tw.WriteHeader(tarHeaders[p])
73+
if err != nil {
74+
return nil, err
75+
}
76+
_, err = tw.Write(f)
77+
if err != nil {
78+
return nil, err
79+
}
80+
}
81+
err := tw.Close()
82+
if err != nil {
83+
return nil, err
84+
}
85+
if filepath.Ext(filename) != ".tar" {
86+
err = zw.Close()
87+
if err != nil {
88+
return nil, err
89+
}
90+
}
91+
return buff.Bytes(), nil
92+
93+
}
94+
95+
func decompressFile(tarFile *bytes.Buffer, filename string) (map[string][]byte, map[string]*tar.Header, error) {
96+
var tarReader *tar.Reader
97+
var zr *gzip.Reader
98+
var err error
99+
if filepath.Ext(filename) != ".tar" {
100+
zr, err = gzip.NewReader(tarFile)
101+
if err != nil {
102+
return nil, nil, err
103+
}
104+
defer zr.Close()
105+
tarReader = tar.NewReader(zr)
106+
} else {
107+
tarReader = tar.NewReader(tarFile)
108+
}
109+
tarHeaders := make(map[string]*tar.Header)
110+
tarContent := make(map[string][]byte)
111+
for {
112+
header, err := tarReader.Next()
113+
if err != nil {
114+
if err != io.EOF {
115+
return nil, nil, err
116+
}
117+
break
118+
}
119+
file := new(bytes.Buffer)
120+
_, err = io.Copy(file, tarReader)
121+
if err != nil {
122+
return nil, nil, err
123+
}
124+
tarContent[header.Name] = file.Bytes()
125+
tarHeaders[header.Name] = header
126+
127+
}
128+
return tarContent, tarHeaders, nil
129+
}

0 commit comments

Comments
 (0)