-
Notifications
You must be signed in to change notification settings - Fork 1
/
profile.go
287 lines (234 loc) · 7.42 KB
/
profile.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
package lanes
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/go-multierror"
"gopkg.in/yaml.v2"
"github.com/codekoala/go-aws-lanes/ssh"
)
type Profile struct {
AWSProfile string `yaml:"aws_profile"`
AWSAccessKeyID string `yaml:"aws_access_key_id"`
AWSSecretAccessKey string `yaml:"aws_secret_access_key"`
Region string `yaml:"region,omitempty"`
SSH ssh.Config `yaml:"ssh"`
global *Config
overwrite bool
}
// GetSampleProfile returns a sample profile that is easy to use as an example.
func GetSampleProfile() *Profile {
return &Profile{
SSH: ssh.Config{
Default: &ssh.DefaultProfile,
Mods: map[string]*ssh.Profile{
"dev": {
Identity: "~/.ssh/id_rsa_dev",
Tunnels: []string{
"8080:127.0.0.1:80",
"3306:127.0.0.1:3306",
},
},
"stage": {
Identity: "~/.ssh/id_rsa_stage",
Tunnel: "8080:127.0.0.1:80",
},
"prod": {
Identity: "~/.ssh/id_rsa_prod",
},
},
},
}
}
// GetAvailableProfiles returns a list of all Lanes profiles found in the configuration directory.
func GetAvailableProfiles() (found []string) {
matches, _ := filepath.Glob(filepath.Join(ConfigDir, "*.yml"))
for _, match := range matches {
name := filepath.Base(strings.TrimSuffix(match, filepath.Ext(match)))
if name == "lanes" {
// this is not a profile
continue
}
found = append(found, name)
}
return
}
// GetProfilePath uses the specified name to return a path to the file that is expected to hold the configuration for
// the named profile.
func GetProfilePath(name string, checkPerms bool) string {
path := filepath.Join(ConfigDir, name+".yml")
if checkPerms {
CheckProfilePermissions(path)
}
return path
}
// CheckProfilePermissions looks for any concerns with permissions that are too permissible for Lanes profiles.
func CheckProfilePermissions(path string) {
var result error
// check the directory first
dFatal, dErr := CheckPermissions(filepath.Dir(path))
if dErr != nil {
result = multierror.Append(dErr)
}
// check the actual profile
pFatal, pErr := CheckPermissions(path)
if pErr != nil {
result = multierror.Append(pErr)
}
prefix := "WARNING"
fatal := dFatal || pFatal
if fatal {
prefix = "ERROR"
}
if result != nil {
fmt.Printf("%s: checking profile permissions, %s\n\n", prefix, result)
}
if fatal {
os.Exit(1)
}
}
// CheckPermissions looks for possible concerns with directory and file permissions.
func CheckPermissions(path string) (fatal bool, result error) {
pStats, err := os.Stat(path)
if err != nil {
fatal = true
result = multierror.Append(result, err)
} else {
mode := pStats.Mode()
// check user permissions
if (mode&0700)>>6 <= 3 {
fatal = true
result = multierror.Append(result, fmt.Errorf("%s is not user-accessible", path))
}
// check group permissions
if (mode&0070)>>3 != 0 {
result = multierror.Append(result, fmt.Errorf("%s is group-accessible", path))
}
// check world permissions
if mode&0007 != 0 {
result = multierror.Append(result, fmt.Errorf("%s is world-accessible", path))
}
}
return
}
// LoadProfile attempts to read the specified profile from the filesystem.
func LoadProfile(name string) (prof *Profile, err error) {
var in []byte
if in, err = ioutil.ReadFile(GetProfilePath(name, true)); err != nil {
err = fmt.Errorf("unable to read profile: %s", err)
return
}
return LoadProfileBytes(in)
}
// LoadProfileBytes loads the currently configured lane profile from the specified YAML bytes.
func LoadProfileBytes(in []byte) (prof *Profile, err error) {
prof = new(Profile)
if err = yaml.Unmarshal(in, prof); err != nil {
err = fmt.Errorf("unable to parse lane profile: %s", err)
return
}
// allow the profile to access global configuration values
prof.global = config
if err = prof.Validate(); err != nil {
err = fmt.Errorf("invalid profile: %s", err)
return
}
return prof, nil
}
// SetOverwrite allows other packages to mark this profile as one that can safely be overwritten.
func (this *Profile) SetOverwrite(value bool) {
this.overwrite = value
}
// Validate checks that the profile includes the necessary information to interact with AWS.
func (this *Profile) Validate() (err error) {
if this.AWSAccessKeyID == "" {
err = multierror.Append(err, ErrMissingAccessKey)
}
if this.AWSSecretAccessKey == "" {
err = multierror.Append(err, ErrMissingSecretKey)
}
if err != nil {
if this.AWSProfile == "" {
err = ErrMissingAWSProfile
} else {
err = nil
}
}
if this.global != nil {
if this.Region == "" {
this.Region = this.global.Region
}
} else {
this.Region = os.Getenv("LANES_REGION")
}
return err
}
// Activate sets some environment variables to access AWS using a given profile.
func (this *Profile) Activate() {
if this.AWSAccessKeyID != "" && this.AWSSecretAccessKey != "" {
os.Setenv("AWS_ACCESS_KEY_ID", this.AWSAccessKeyID)
os.Setenv("AWS_SECRET_ACCESS_KEY", this.AWSSecretAccessKey)
os.Unsetenv("AWS_PROFILE")
} else {
os.Setenv("AWS_PROFILE", this.AWSProfile)
os.Unsetenv("AWS_ACCESS_KEY_ID")
os.Unsetenv("AWS_SECRET_ACCESS_KEY")
}
}
// Deactivate unsets environment variables to no longer access AWS with this profile.
func (this *Profile) Deactivate() {
os.Unsetenv("AWS_PROFILE")
os.Unsetenv("AWS_ACCESS_KEY_ID")
os.Unsetenv("AWS_SECRET_ACCESS_KEY")
}
// FetchServers retrieves all EC2 instances for the current profile.
func (this *Profile) FetchServers(svc *ec2.EC2) ([]*Server, error) {
return this.FetchServersBy(svc, nil, "")
}
// FetchServersInLane retrieves all EC2 instances in the specified lane for the current profile.
func (this *Profile) FetchServersInLane(svc *ec2.EC2, lane string) ([]*Server, error) {
return this.FetchServersInLaneByKeyword(svc, lane, "")
}
// FetchServersInLane retrieves all EC2 instances in the specified lane for the current profile.
func (this *Profile) FetchServersInLaneByKeyword(svc *ec2.EC2, lane, keyword string) ([]*Server, error) {
return this.FetchServersBy(svc, CreateLaneFilter(lane), keyword)
}
// FetchServersBy retrieves all EC2 instances for the current profile using any specified filters. Each instance is
// automatically tagged with the appropriate SSH profile to access it.
func (this *Profile) FetchServersBy(svc *ec2.EC2, input *ec2.DescribeInstancesInput, keyword string) (servers []*Server, err error) {
if servers, err = FetchServersBy(svc, input, keyword); err != nil {
return
}
return servers, nil
}
// Write saves the current settings to disk using the specified profile name.
func (this *Profile) Write(name string) (err error) {
return this.WriteFile(name, GetProfilePath(name, false))
}
// WriteFile saves the current profile settings to the specified file.
func (this *Profile) WriteFile(name, dest string) (err error) {
var out []byte
// don't overwrite existing profiles without a flag being set to allow it
if _, err = os.Stat(dest); err == nil && !this.overwrite {
return fmt.Errorf("profile %q already exists", name)
}
if out, err = this.WriteBytes(); err != nil {
return
}
// make sure the destination directory exists
if err = os.MkdirAll(filepath.Dir(dest), 0700); err != nil {
return
}
if err = ioutil.WriteFile(dest, out, 0600); err != nil {
return
}
fmt.Printf("Profile %q written to %s\n", name, dest)
return nil
}
// WriteBytes marshals the current settings to YAML.
func (this *Profile) WriteBytes() ([]byte, error) {
return yaml.Marshal(this)
}