-
Notifications
You must be signed in to change notification settings - Fork 15
/
prepare.go
300 lines (268 loc) · 9.13 KB
/
prepare.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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
/*
* Copyright 2018-2019, CS Systemes d'Information, http://www.c-s.fr
*
* 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 userdata
//go:generate rice embed-go
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"strings"
"sync/atomic"
"text/template"
"github.com/sirupsen/logrus"
rice "github.com/GeertJohan/go.rice"
"github.com/CS-SI/SafeScale/lib/server/iaas/resources"
"github.com/CS-SI/SafeScale/lib/server/iaas/stacks"
"github.com/CS-SI/SafeScale/lib/system"
"github.com/CS-SI/SafeScale/lib/utils"
)
// Content is the structure to apply to userdata.sh template
type Content struct {
// BashLibrary contains the bash library
BashLibrary string
// Header is the bash header for scripts
Header string
// User is the name of the default user (api.DefaultUser)
User string
// ExitOnError helper to quit script on error
ExitOnError string
// Password for the user safescale (for troubleshoot use, usable only in console)
Password string
// PublicKey is the public key used to create the Host
PublicKey string
// PrivateKey is the private key used to create the Host
PrivateKey string
// ConfIF, if set to true, configure all interfaces to DHCP
ConfIF bool
// IsGateway, if set to true, activate IP forwarding
IsGateway bool
// PublicIP contains a public IP binded to the host
PublicIP string
// AddGateway, if set to true, configure default gateway
AddGateway bool
// DNSServers contains the list of DNS servers to use
// Used only if IsGateway is true
DNSServers []string
// CIDR contains the cidr of the network
CIDR string
// DefaultRouteIP is the IP of the gateway or the VIP if gateway HA is enabled
DefaultRouteIP string
// PrimaryGatewayPrivateIP is the private IP of the primary gateway
PrimaryGatewayPrivateIP string
// PrimaryGatewayPublicIP is the public IP of the primary gateway
PrimaryGatewayPublicIP string
// SecondaryGatewayPrivateIP is the private IP of the secondary gateway
SecondaryGatewayPrivateIP string
// SecondaryGatewayPublicIP is the public IP of the secondary gateway
SecondaryGatewayPublicIP string
// EmulatedPublicNet is a private network which is used to emulate a public one
EmulatedPublicNet string
// HostName contains the name wanted as host name (default == name of the Cloud resource)
HostName string
// Tags contains tags and their content(s); a tag is named #<tag> in the template
Tags map[string]map[string][]string
// IsPrimaryGateway tells if the host is a primary gateway
IsPrimaryGateway bool
// PrivateVIP contains the private IP of the VIP instance if it exists
PublicVIP string // VPL: change to EndpointIP
// PrivateVIP contains the private IP of the VIP instance if it exists
PrivateVIP string // VPL: change to DefaultRouteIP
// Dashboard bool // Add kubernetes dashboard
}
var (
userdataPhase1Template atomic.Value //*template.Template
userdataPhase2Template atomic.Value //*template.Template
)
// NewContent ...
func NewContent() *Content {
return &Content{
Tags: map[string]map[string][]string{},
}
}
// Prepare prepares the initial configuration script executed by cloud compute resource
func (ud *Content) Prepare(
options stacks.ConfigurationOptions, request resources.HostRequest, cidr string, defaultNetworkCIDR string,
) error {
// Generate password for user safescale
var (
err error
// autoHostNetworkInterfaces bool
useLayer3Networking = true
dnsList []string
operatorUsername string
useNATService = false
)
if request.Password == "" {
password, err := utils.GeneratePassword(16)
if err != nil {
return fmt.Errorf("failed to generate password: %s", err.Error())
}
request.Password = password
}
// Determine default route IP
ip := ""
if request.DefaultRouteIP != "" {
ip = request.DefaultRouteIP
}
// autoHostNetworkInterfaces = options.AutoHostNetworkInterfaces
useLayer3Networking = options.UseLayer3Networking
useNATService = options.UseNATService
operatorUsername = options.OperatorUsername
dnsList = options.DNSList
if len(dnsList) == 0 {
dnsList = []string{"1.1.1.1"}
}
bashLibrary, err := system.GetBashLibrary()
if err != nil {
return err
}
exitOnErrorHeader := ""
scriptHeader := "set -u -o pipefail"
if suffixCandidate := os.Getenv("SAFESCALE_SCRIPTS_FAIL_FAST"); suffixCandidate != "" {
if strings.EqualFold("True", strings.TrimSpace(suffixCandidate)) ||
strings.EqualFold("1", strings.TrimSpace(suffixCandidate)) {
scriptHeader = "set -Eeuxo pipefail"
exitOnErrorHeader = "echo 'PROVISIONING_ERROR: 222'"
}
}
ud.BashLibrary = bashLibrary
ud.Header = scriptHeader
ud.User = operatorUsername
ud.ExitOnError = exitOnErrorHeader
ud.PublicKey = strings.Trim(request.KeyPair.PublicKey, "\n")
ud.PrivateKey = strings.Trim(request.KeyPair.PrivateKey, "\n")
// ud.ConfIF = !autoHostNetworkInterfaces
ud.IsGateway = request.DefaultRouteIP == "" && request.Networks[0].Name != resources.SingleHostNetworkName && !useLayer3Networking
ud.AddGateway = !request.PublicIP && !useLayer3Networking && ip != "" && !useNATService
ud.DNSServers = dnsList
ud.CIDR = cidr
ud.DefaultRouteIP = ip
ud.Password = request.Password
ud.EmulatedPublicNet = defaultNetworkCIDR
if request.HostName != "" {
ud.HostName = request.HostName
} else {
ud.HostName = request.ResourceName
}
return nil
}
// Generate generates the script file corresponding to the phase
func (ud *Content) Generate(phase string) ([]byte, error) {
var (
box *rice.Box
result []byte
err error
)
// DEV VAR
provider := ""
if suffixCandidate := os.Getenv("SAFESCALE_SCRIPT_FLAVOR"); suffixCandidate != "" {
if suffixCandidate != "" {
problems := false
box, err = rice.FindBox("../userdata/scripts")
if err != nil || box == nil {
problems = true
}
if !problems && box != nil {
_, err := box.String(fmt.Sprintf("userdata%s.phase1.sh", suffixCandidate))
problems = err != nil
_, err = box.String(fmt.Sprintf("userdata%s.phase2.sh", suffixCandidate))
problems = problems || (err != nil)
if !problems {
provider = fmt.Sprintf(".%s", suffixCandidate)
}
}
if problems {
logrus.Warnf("Ignoring script flavor [%s]", suffixCandidate)
}
}
}
switch phase {
case "phase1":
anon := userdataPhase1Template.Load()
if anon == nil {
box, err = rice.FindBox("../userdata/scripts")
if err != nil {
return nil, err
}
tmplString, err := box.String(fmt.Sprintf("userdata%s.phase1.sh", provider))
if err != nil {
return nil, fmt.Errorf("error loading script template for phase1 : %s", err.Error())
}
tmpl, err := template.New("userdata.phase1").Parse(tmplString)
if err != nil {
return nil, fmt.Errorf("error parsing script template for phase 1 : %s", err.Error())
}
userdataPhase1Template.Store(tmpl)
anon = userdataPhase1Template.Load()
}
tmpl := anon.(*template.Template)
buf := bytes.NewBufferString("")
err := tmpl.Execute(buf, ud)
if err != nil {
return nil, err
}
result = buf.Bytes()
case "phase2":
anon := userdataPhase2Template.Load()
if anon == nil {
box, err = rice.FindBox("../userdata/scripts")
if err != nil {
return nil, err
}
tmplString, err := box.String(fmt.Sprintf("userdata%s.phase2.sh", provider))
if err != nil {
return nil, fmt.Errorf("error loading script template: %s", err.Error())
}
tmpl, err := template.New("userdata.phase2").Parse(tmplString)
if err != nil {
return nil, fmt.Errorf("error parsing script template: %s", err.Error())
}
userdataPhase2Template.Store(tmpl)
anon = userdataPhase2Template.Load()
}
tmpl := anon.(*template.Template)
buf := bytes.NewBufferString("")
err = tmpl.Execute(buf, ud)
if err != nil {
return nil, err
}
result = buf.Bytes()
for tagname, tagcontent := range ud.Tags[phase] {
for _, str := range tagcontent {
bytes.Replace(result, []byte("#"+tagname), []byte(str+"\n\n#"+tagname), 1)
}
}
default:
return nil, fmt.Errorf("phase '%s' not managed", phase)
}
if forensics := os.Getenv("SAFESCALE_FORENSICS"); forensics != "" {
_ = os.MkdirAll(utils.AbsPathify(fmt.Sprintf("$HOME/.safescale/forensics/%s", ud.HostName)), 0777)
dumpName := utils.AbsPathify(fmt.Sprintf("$HOME/.safescale/forensics/%s/userdata-%s.sh", ud.HostName, phase))
err = ioutil.WriteFile(dumpName, result, 0644)
if err != nil {
logrus.Warnf("[TRACE] Failure writing step info into %s", dumpName)
}
}
return result, nil
}
// AddInTag adds some useful code on the end of userdata.phase2.sh just before the end (on the label #insert_tag)
func (ud Content) AddInTag(phase string, tagname string, content string) {
if _, ok := ud.Tags[phase]; !ok {
ud.Tags[tagname] = map[string][]string{}
}
ud.Tags[phase][tagname] = append(ud.Tags[phase][tagname], content)
}