-
Notifications
You must be signed in to change notification settings - Fork 1
/
resource_exec.go
174 lines (147 loc) · 3.9 KB
/
resource_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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
//"strings"
"crypto/sha1"
"encoding/hex"
"time"
"github.com/hashicorp/terraform/helper/schema"
)
// Default timeout for the command is 60s
const defaultTimeout = 60
// ExecCmd holds data necessary for a command to run
type ExecCmd struct {
Cmd string
Timeout int
}
// Terraform schema for the 'exec' resource that is
// used in the provider configuration
func resourceExec() *schema.Resource {
return &schema.Resource{
Create: resourceExecCreate,
Read: resourceExecRead,
Update: resourceExecUpdate,
Delete: resourceExecDelete,
Schema: map[string]*schema.Schema{
"command": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"only_if": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"timeout": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
},
"output": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}
func resourceExecCreate(d *schema.ResourceData, m interface{}) error {
return ExecResourceCmd(d, m)
}
func resourceExecUpdate(d *schema.ResourceData, m interface{}) error {
if d.HasChange("command") {
// Set the id of the resource to destroy the resource
d.SetId("")
}
return ExecResourceCmd(d, m)
}
func resourceExecRead(d *schema.ResourceData, m interface{}) error {
return nil
}
func resourceExecDelete(d *schema.ResourceData, m interface{}) error {
return nil
}
func ExecResourceCmd(d *schema.ResourceData, m interface{}) error {
timeout := d.Get("timeout").(int)
cmd := &ExecCmd{
Cmd: d.Get("command").(string),
Timeout: timeout,
}
onlyIf := &ExecCmd{
Cmd: d.Get("only_if").(string),
Timeout: timeout,
}
// run the only_if command and continue only on success
if onlyIf.Cmd != "" {
_, err := ExecuteCmd(onlyIf)
if err != nil {
log.Printf("[DEBUG] Skipped execution (%s): `%s` exited with a failed state", cmd.Cmd, onlyIf.Cmd)
// stop executing the command by returning nil
return nil
}
}
// run the actual command
out, err := ExecuteCmd(cmd)
if err != nil {
d.Set("output", "")
return nil
}
log.Printf("[DEBUG] Command Output (%s): %s", cmd.Cmd, out)
d.Set("output", out)
// Set the id of the resource
d.SetId(GenerateSHA1(cmd.Cmd))
return nil
}
func ExecuteCmd(command *ExecCmd) (output string, err error) {
// Wrap the command in a temp file
var cmdWrapper *os.File
cmdWrapper, err = ioutil.TempFile("", "exec")
if err != nil {
log.Fatal(fmt.Sprintf("Error while creating temp file: %s", err))
return "", err
}
defer cmdWrapper.Close()
if err = os.Chmod(cmdWrapper.Name(), 0755); err != nil {
log.Fatal(fmt.Sprintf("Error while making the file executable: %s", err))
}
// Run the command in the current working directory
var path string
path, err = os.Getwd()
if err != nil {
log.Fatal(fmt.Sprintf("Error getting pwd: %s", err))
return "", err
}
code := fmt.Sprintf("#!/usr/bin/env /bin/sh\ncd %s\n%s", path, command.Cmd)
if err = ioutil.WriteFile(cmdWrapper.Name(), []byte(code), 0755); err != nil {
log.Fatal(fmt.Sprintf("Error while writing to temp file: %s", err))
return "", err
}
if command.Timeout == 0 {
command.Timeout = defaultTimeout
}
// Run the command in a channel using select statement
// with time.After for timingout calls that run too long
var out []byte
timeout := make(chan error)
go func() {
out, err = exec.Command(cmdWrapper.Name()).Output()
timeout <- err
}()
select {
case err := <-timeout:
if err != nil {
return "", err
}
case <-time.After(time.Duration(command.Timeout) * time.Second):
log.Printf("[DEBUG] Execution (%s) timedout in %ds", command.Cmd, command.Timeout)
return "", nil
}
return string(out[:]), nil
}
// GenerateSHA1 generates a SHA1 hex digest for the given string
func GenerateSHA1(str string) string {
h := sha1.New()
h.Write([]byte(str))
return hex.EncodeToString(h.Sum(nil))
}