/
exec.go
153 lines (133 loc) · 4.48 KB
/
exec.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
// Copyright © 2016 Asteris, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package lowlevel
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
log "github.com/Sirupsen/logrus"
"github.com/pkg/errors"
)
// Exec is interface to `real system` also used for test injections
// NB: should be reviewed, and possible refactored as base for all
// processes/filesystem operrations, to make the easy mockable
// NB: possoble split to ExecInterface, FilesystemInterface (possible also SystemInterface for Getuid)
// Related issue: https://github.com/asteris-llc/converge/issues/455
type Exec interface {
Run(prog string, args []string) error
RunWithExitCode(prog string, args []string) (int, error) // for mountpoint querying
Read(prog string, args []string) (stdout string, err error)
ReadWithExitCode(prog string, args []string) (stdout string, rc int, err error) // for blkid querying
Lookup(prog string) error
Getuid() int
// unit read/write injection
ReadFile(fn string) ([]byte, error)
WriteFile(fn string, c []byte, p os.FileMode) error
MkdirAll(path string, perm os.FileMode) error
Exists(path string) (bool, error)
// Local Filesystem Functions
EvalSymlinks(string) (string, error)
}
type osExec struct {
}
// MakeOsExec create Exec backend
func MakeOsExec() Exec {
return &osExec{}
}
func (*osExec) EvalSymlinks(path string) (string, error) {
return filepath.EvalSymlinks(path)
}
func (*osExec) Run(prog string, args []string) error {
log.WithField("module", "lvm").Infof("Executing %s: %v", prog, args)
e := exec.Command(prog, args...).Run()
if e == nil {
log.WithField("module", "lvm").Debugf("%s: no error", prog)
} else {
log.WithField("module", "lvm").Debugf("%s: terminated with %s", prog, e.Error())
}
return e
}
func (e *osExec) RunWithExitCode(prog string, args []string) (int, error) {
err := e.Run(prog, args)
return exitStatus(err)
}
func (*osExec) ReadWithExitCode(prog string, args []string) (stdout string, rc int, err error) {
rc = 0
log.WithField("module", "lvm").Infof("Executing (read) %s: %v", prog, args)
out, err := exec.Command(prog, args...).Output()
strOut := strings.Trim(string(out), "\n ")
if err != nil {
rc, err = exitStatus(err)
if err != nil {
log.WithField("module", "lvm").Debugf("%s: terminated with %s", prog, err.Error())
return "", 0, errors.Wrapf(err, "reading output of process %s: %s", prog, args)
}
}
return strOut, rc, err
}
func (e *osExec) Read(prog string, args []string) (stdout string, err error) {
out, rc, err := e.ReadWithExitCode(prog, args)
if err != nil {
return "", err
}
if rc != 0 {
return "", fmt.Errorf("process %s: %s terminated with status code %d", prog, args, rc)
}
return out, nil
}
func exitStatus(err error) (int, error) {
if exiterr, ok := err.(*exec.ExitError); ok {
// The program has exited with an exit code != 0
// This works on both Unix and Windows. Although package
// syscall is generally platform dependent, WaitStatus is
// defined for both Unix and Windows and in both cases has
// an ExitStatus() method with the same signature.
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
return status.ExitStatus(), nil
}
}
return 0, err
}
func (*osExec) Lookup(prog string) error {
_, err := exec.LookPath(prog)
return err
}
func (*osExec) ReadFile(fn string) ([]byte, error) {
log.WithField("module", "lvm").Debugf("Reading %s...", fn)
return ioutil.ReadFile(fn)
}
func (*osExec) WriteFile(fn string, content []byte, perm os.FileMode) error {
log.WithField("module", "lvm").Debugf("Writing %s...", fn)
return ioutil.WriteFile(fn, content, perm)
}
func (*osExec) MkdirAll(path string, perm os.FileMode) error {
log.WithField("module", "lvm").Debugf("Make path %s...", path)
return os.MkdirAll(path, perm)
}
func (*osExec) Exists(path string) (bool, error) {
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
return true, nil
}
func (*osExec) Getuid() int {
return os.Getuid()
}