This repository has been archived by the owner on Jul 25, 2018. It is now read-only.
/
cmd_file.go
202 lines (163 loc) · 5.37 KB
/
cmd_file.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
191
192
193
194
195
196
197
198
199
200
201
202
package main
import (
"bytes"
"compress/gzip"
"crypto/sha1"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/dynport/urknall/utils"
)
// The "FileCommand" is used to write files to the host being provisioned. The go templating mechanism (see
// http://golang.org/pkg/text/template) is applied on the file's content using the package. Thereby it is possible to
// have dynamic content (based on the package's configuration) for the file content and at the same time store it in
// an asset (which is generated at compile time). Please note that the underlying actions will panic if either no path
// or content are given.
type FileCommand struct {
Path string // Path to the file to create.
Content string // Content of the file to create.
Owner string // Owner of the file to create (root per default).
Permissions os.FileMode // Permissions of the file created (only changed from system default if set).
}
func (cmd *FileCommand) Render(i interface{}) {
cmd.Path = utils.MustRenderTemplate(cmd.Path, i)
cmd.Content = utils.MustRenderTemplate(cmd.Content, i)
}
func (cmd *FileCommand) Validate() error {
if cmd.Path == "" {
return fmt.Errorf("no path given")
}
if cmd.Content == "" {
return fmt.Errorf("no content given for file %q", cmd.Path)
}
return nil
}
// Helper method to create a file at the given path with the given content, and with owner and permissions set
// accordingly. The "Owner" and "Permissions" options are optional in the sense that they are ignored if set to go's
// default value.
func WriteFile(path string, content string, owner string, permissions os.FileMode) *FileCommand {
return &FileCommand{Path: path, Content: content, Owner: owner, Permissions: permissions}
}
var b64 = base64.StdEncoding
func (fc *FileCommand) Shell() string {
buf := &bytes.Buffer{}
// Zip the content.
zipper := gzip.NewWriter(buf)
zipper.Write([]byte(fc.Content))
zipper.Flush()
zipper.Close()
// Encode the zipped content in Base64.
encoded := b64.EncodeToString(buf.Bytes())
// Compute sha256 hash of the encoded and zipped content.
hash := sha256.New()
hash.Write([]byte(fc.Content))
// Create temporary filename (hash as filename).
tmpPath := fmt.Sprintf("/tmp/wunderscale.%x", hash.Sum(nil))
// Get directory part of target file.
dir := filepath.Dir(fc.Path)
// Create command, that will decode and unzip the content and write to the temporary file.
cmd := ""
cmd += fmt.Sprintf("mkdir -p %s", dir)
cmd += fmt.Sprintf(" && echo %s | base64 -d | gunzip > %s", encoded, tmpPath)
if fc.Owner != "" { // If owner given, change accordingly.
cmd += fmt.Sprintf(" && chown %s %s", fc.Owner, tmpPath)
}
if fc.Permissions > 0 { // If mode given, change accordingly.
cmd += fmt.Sprintf(" && chmod %o %s", fc.Permissions, tmpPath)
}
// Move the temporary file to the requested location.
cmd += fmt.Sprintf(" && mv %s %s", tmpPath, fc.Path)
return cmd
}
func (fc *FileCommand) Logging() string {
sList := []string{"[FILE ]"}
if fc.Owner != "" && fc.Owner != "root" {
sList = append(sList, fmt.Sprintf("[CHOWN:%s]", fc.Owner))
}
if fc.Permissions != 0 {
sList = append(sList, fmt.Sprintf("[CHMOD:%.4o]", fc.Permissions))
}
sList = append(sList, " "+fc.Path)
cLen := len(fc.Content)
if cLen > 50 {
cLen = 50
}
//sList = append(sList, fmt.Sprintf(" << %s", strings.Replace(string(fc.Content[0:cLen]), "\n", "⁋", -1)))
return strings.Join(sList, "")
}
type FileSendCommand struct {
Source string
Target string
Owner string
Permissions os.FileMode
}
func SendFile(source, target, owner string, perm os.FileMode) *FileSendCommand {
return &FileSendCommand{
Source: source,
Target: target,
Owner: owner,
Permissions: perm,
}
}
func (fsc *FileSendCommand) Render(i interface{}) {
fsc.Source = utils.MustRenderTemplate(fsc.Source, i)
fsc.Target = utils.MustRenderTemplate(fsc.Target, i)
}
func (fsc *FileSendCommand) Validate() error {
if fsc.Source == "" {
return fmt.Errorf("no source path given")
}
if _, e := os.Stat(fsc.Source); e != nil {
return e
}
if fsc.Target == "" {
return fmt.Errorf("no target path given for file %q", fsc.Source)
}
return nil
}
func (fsc *FileSendCommand) sourceHash() string {
fh, e := os.Open(fsc.Source)
if e != nil {
panic(e)
}
defer fh.Close()
hash := sha1.New()
if _, e = io.Copy(hash, fh); e != nil {
panic(e)
}
return hex.EncodeToString(hash.Sum(nil))
}
func (fsc *FileSendCommand) Shell() string {
sList := []string{
fmt.Sprintf("echo %q", fsc.sourceHash()), // nope use content hash
fmt.Sprintf("cat - > %s", fsc.Target),
}
if fsc.Owner != "root" {
sList = append(sList, fmt.Sprintf("chown %s %s", fsc.Owner, fsc.Target))
}
sList = append(sList, fmt.Sprintf("chmod %s %s", fsc.Permissions, fsc.Target))
return strings.Join(sList, " && ")
}
func (fsc *FileSendCommand) Input() io.ReadCloser {
fh, e := os.Open(fsc.Source)
if e != nil {
panic(e)
}
return fh
}
func (fsc *FileSendCommand) Logging() string {
sList := []string{"[FILE ]"}
if fsc.Owner != "" && fsc.Owner != "root" {
sList = append(sList, fmt.Sprintf("[CHOWN:%s]", fsc.Owner))
}
if fsc.Permissions != 0 {
sList = append(sList, fmt.Sprintf("[CHMOD:%.4o]", fsc.Permissions))
}
sList = append(sList, fmt.Sprintf(" Writing local file %s to %s", fsc.Source, fsc.Target))
return strings.Join(sList, "")
}