-
Notifications
You must be signed in to change notification settings - Fork 13
/
main.go
190 lines (162 loc) · 5.59 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// The RHMAP System Dump Tool.
package main
import (
"bufio"
"bytes"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"time"
)
const (
// dumpDir is a path to the base directory where the output of the tool
// goes.
dumpDir = "rhmap-dumps"
// dumpTimestampFormat is a layout for use with Time.Format. Used to
// create directories with a timestamp. Based on time.RFC3339.
dumpTimestampFormat = "2006-01-02T15-04-05Z0700"
// defaultMaxLogLines is the default limit of number of log lines to
// fetch.
defaultMaxLogLines = 1000
)
var (
concurrentTasks = flag.Int("p", runtime.NumCPU(), "number of tasks to run concurrently")
maxLogLines = flag.Int("max-log-lines", defaultMaxLogLines, "max number of log lines fetched with oc logs")
printVersion = flag.Bool("version", false, "print version and exit")
)
// showAllErrors enables printing of ignorable errors, suitable for debugging.
// This is intentionally not exposed as a flag, and shall stay intentionally
// undocumented, used for development only.
var _, showAllErrors = os.LookupEnv("FH_SYSTEM_DUMP_TOOL_DEBUG")
// GetProjects returns a list of project names visible by the current logged in
// user.
func GetProjects(runner Runner) ([]string, error) {
cmd := exec.Command("oc", "get", "projects", "-o=jsonpath={.items[*].metadata.name}")
var b bytes.Buffer
cmd.Stdout = &b
if err := runner.Run(cmd, filepath.Join("project-names")); err != nil {
return nil, err
}
return readSpaceSeparated(&b)
}
// GetResourceNames returns a list of resource names of type rtype, visible by
// the current logged in user, scoped by project.
func GetResourceNames(runner Runner, project, rtype string) ([]string, error) {
cmd := exec.Command("oc", "-n", project, "get", rtype, "-o=jsonpath={.items[*].metadata.name}")
var b bytes.Buffer
cmd.Stdout = &b
if err := runner.Run(cmd, filepath.Join("projects", project, "names", rtype)); err != nil {
return nil, err
}
return readSpaceSeparated(&b)
}
// readSpaceSeparated reads from r and returns a list of space-separated words.
func readSpaceSeparated(r io.Reader) ([]string, error) {
var words []string
scanner := bufio.NewScanner(r)
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
words = append(words, scanner.Text())
}
if err := scanner.Err(); err != nil {
return nil, err
}
return words, nil
}
// archive archives the target path to a tar.gz file.
func archive(path string) error {
var stdout, stderr bytes.Buffer
cmd := exec.Command("tar", "-czf", path+".tar.gz", path)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
return cmd.Run()
}
func checkPrerequisites() error {
if _, err := exec.LookPath("oc"); err != nil {
return errors.New("oc command not found, please install the OpenShift CLI before using this tool")
}
// This serves as a sort of "ping" to the server, to make sure we are
// logged in and can access the server before we issue more calls.
if err := exec.Command("oc", "whoami").Run(); err != nil {
return errors.New("could not access OpenShift, please run 'oc login' and make sure that a user is logged in and the server is accessible")
}
return nil
}
func main() {
flag.Parse()
if *printVersion {
PrintVersion(os.Stdout)
os.Exit(0)
}
if !(*concurrentTasks > 0) {
fmt.Fprintln(os.Stderr, "Error: argument to -p flag must be greater than 0")
os.Exit(1)
}
if err := checkPrerequisites(); err != nil {
log.Fatalln("Error:", err)
}
start := time.Now().UTC()
startTimestamp := start.Format(dumpTimestampFormat)
basePath := filepath.Join(dumpDir, startTimestamp)
if err := os.MkdirAll(basePath, 0770); err != nil {
log.Fatalln("Error:", err)
}
var b bytes.Buffer
PrintVersion(&b)
if err := ioutil.WriteFile(filepath.Join(basePath, "version"), b.Bytes(), 0660); err != nil {
log.Fatalln("Error:", err)
}
logfile, err := os.Create(filepath.Join(basePath, "dump.log"))
if err != nil {
log.Fatalln("Error:", err)
}
defer logfile.Close()
log.SetOutput(io.MultiWriter(os.Stderr, logfile))
fileOnlyLogger := log.New(logfile, "", log.LstdFlags)
// defer creating a tar.gz file from the dumped output files
defer func() {
// Write this only to logfile, before we archive it and remove
// basePath. After that, logs will go only to stderr.
fileOnlyLogger.Printf("Dumped system information to: %s", basePath)
if err := archive(basePath); err != nil {
fileOnlyLogger.Printf("Could not create data archive: %v", err)
log.Printf("Could not archive dump data, unarchived data in: %s", basePath)
return
}
// The archive was created successfully, remove basePath. The
// error from os.RemoveAll is intentionally ignored, since there
// is no useful action we can do, and we don't need to confuse
// the user with an error message.
os.RemoveAll(basePath)
log.Printf("Dumped system information to: %s", basePath+".tar.gz")
}()
log.Print("Starting RHMAP System Dump Tool...")
runner := NewDumpRunner(basePath)
log.Print("Collecting system information...")
errs := RunAllDumpTasks(runner, basePath, *concurrentTasks, os.Stderr)
for _, err := range errs {
if ierr, ok := err.(IgnorableError); !showAllErrors && ok && ierr.Ignore() {
fileOnlyLogger.Printf("Task error: %v", err)
continue
}
// TODO: there should be a way to identify which task
// had an error.
log.Printf("Task error: %v", err)
}
log.Print("Analyzing data...")
analysisResults := RunAllAnalysisTasks(runner, basePath, *concurrentTasks)
delta := time.Since(start)
// Remove sub-second precision.
delta -= delta % time.Second
if delta > time.Second {
log.Printf("Finished in %v", delta)
}
PrintAnalysisReport(analysisResults, os.Stdout)
}