This repository has been archived by the owner on Jul 12, 2018. It is now read-only.
/
plugin.go
232 lines (204 loc) · 7.93 KB
/
plugin.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
package pscs
import (
"encoding/json"
"fmt"
cli "gopkg.in/urfave/cli.v2"
"github.com/enaml-ops/enaml"
"github.com/enaml-ops/pluginlib/cred"
"github.com/enaml-ops/pluginlib/pcli"
"github.com/enaml-ops/pluginlib/pluginutil"
"github.com/enaml-ops/pluginlib/productv1"
"github.com/xchapter7x/lo"
)
// Plugin is an omg product plugin for depoying p-spring-cloud-services.
type Plugin struct {
Version string
}
// generatePassword is the default for password flags that should be generated by
// the plugin if not specified by the user
const generatePassword = "[autogenerated]"
func (p *Plugin) GetFlags() []pcli.Flag {
return []pcli.Flag{
pcli.CreateStringFlag("deployment-name", "the name bosh will use for the deployment", "p-scs"),
pcli.CreateStringFlag("system-domain", "the system domain"),
pcli.CreateStringSliceFlag("app-domain", "Applications Domains"),
pcli.CreateStringSliceFlag("az", "the AZs to use"),
pcli.CreateStringFlag("network", "the name of the network to use"),
pcli.CreateStringFlag("stemcell-ver", "the version number of the stemcell you wish to use", StemcellVersion),
pcli.CreateStringFlag("vm-type", "VM type to use for SCS instance groups"),
pcli.CreateBoolFlag("skip-ssl-verify", "skip SSL verification"),
pcli.CreateStringFlag("broker-username", "the service broker username", generatePassword),
pcli.CreateStringFlag("broker-password", "the service broker password", generatePassword),
pcli.CreateStringFlag("worker-client-secret", "client secret for worker", generatePassword),
pcli.CreateStringFlag("worker-password", "worker password", generatePassword),
pcli.CreateStringFlag("instances-password", "instances password", generatePassword),
pcli.CreateStringFlag("broker-dashboard-secret", "broker dashboard secret", generatePassword),
pcli.CreateStringFlag("encryption-key", "encryption key", generatePassword),
pcli.CreateStringFlag("admin-password", "CF admin password"),
pcli.CreateStringFlag("uaa-admin-secret", "UAA client secret for admin account"),
pcli.CreateBoolFlag("infer-from-cloud", "attempt to pull defaults from your targetted bosh"),
pcli.CreateStringFlag("vault-domain", "the location of your vault server (ie. http://10.0.0.1:8200)"),
pcli.CreateStringFlag("vault-token", "the token to make connections to your vault"),
pcli.CreateStringFlag("vault-hash-secret", "the hash to read/write SCS secrets to"),
pcli.CreateStringSliceFlag("vault-hash", "a list of vault hashes to pull values from (read only)"),
}
}
func (p *Plugin) GetMeta() product.Meta {
return product.Meta{
Name: "p-spring-cloud-services",
Stemcell: enaml.Stemcell{
Name: StemcellName,
Alias: StemcellAlias,
Version: StemcellVersion,
},
Releases: []enaml.Release{
enaml.Release{
Name: SpringCloudBrokerReleaseName,
Version: SpringCloudBrokerReleaseVersion,
},
},
Properties: map[string]interface{}{
"version": p.Version,
"stemcell": StemcellVersion,
"pivotal-spring-cloud-services": fmt.Sprintf("%s / %s", "pivotal-spring-cloud-services", ProductVersion),
"spring-cloud-broker-release": fmt.Sprintf("%s / %s", SpringCloudBrokerReleaseName, SpringCloudBrokerReleaseVersion),
},
}
}
// flags that go in the secrets hash
var secrets = map[string]struct{}{
"broker-username": {},
"broker-password": {},
"worker-client-secret": {},
"worker-password": {},
"instances-password": {},
"broker-dashboard-secret": {},
"encryption-key": {},
}
func (p *Plugin) GetProduct(args []string, cloudConfig []byte, cs cred.Store) ([]byte, error) {
var err error
flags := p.GetFlags()
c := pluginutil.NewContext(args, pluginutil.ToCliFlagArray(flags))
if c.Bool("infer-from-cloud") {
inferFromCloud(cloudConfig, flags, c)
c = pluginutil.NewContext(args, pluginutil.ToCliFlagArray(flags))
}
writeToVault := false
var v *pluginutil.VaultUnmarshal
domain := c.String("vault-domain")
tok := c.String("vault-token")
hashes := c.StringSlice("vault-hash")
secretHash := c.String("vault-hash-secret")
if secretHash != "" {
hashes = append(hashes, secretHash)
}
if domain != "" && tok != "" && len(hashes) > 0 {
lo.G.Debug("connecting to vault at", domain)
v = pluginutil.NewVaultUnmarshal(domain, tok)
for _, hash := range hashes {
err = v.UnmarshalFlags(hash, flags)
if err != nil {
lo.G.Errorf("error reading vault hash %s: %s", hash, err.Error())
return nil, err
}
}
// if we aren't able to get values for all password flags, then we'll have to generate
// them...and if we generate them we should write them back to vault
writeToVault = shouldWriteBackToVault(flags)
c = pluginutil.NewContext(args, pluginutil.ToCliFlagArray(flags))
}
cfg, err := configFromContext(c)
if err != nil {
lo.G.Error(err)
return nil, err
}
// at thish point we'll populated flags with generated passwords
// and may need to write them back
if writeToVault && v != nil {
if err = writeSecretsToVault(v, secretHash, cfg); err != nil {
return nil, err
}
}
dm := new(enaml.DeploymentManifest)
dm.SetName(cfg.DeploymentName)
dm.AddRelease(enaml.Release{Name: SpringCloudBrokerReleaseName, Version: SpringCloudBrokerReleaseVersion})
dm.AddStemcell(enaml.Stemcell{OS: StemcellName, Version: cfg.StemcellVersion, Alias: StemcellAlias})
dm.AddInstanceGroup(NewDeployServiceBroker(cfg))
dm.AddInstanceGroup(NewRegisterBroker(cfg))
dm.AddInstanceGroup(NewDestroyServiceBroker(cfg))
dm.Update = enaml.Update{
Canaries: 1,
CanaryWatchTime: "30000-300000",
UpdateWatchTime: "30000-300000",
MaxInFlight: 1,
Serial: true,
}
return dm.Bytes(), err
}
func inferFromCloud(cloudConfig []byte, flags []pcli.Flag, c *cli.Context) {
inferer := pluginutil.NewCloudConfigInferFromBytes(cloudConfig)
vm := inferer.InferDefaultVMType()
network := inferer.InferDefaultNetwork()
az := inferer.InferDefaultAZ()
for i := range flags {
name := flags[i].Name
if !c.IsSet(name) {
switch name {
case "network":
lo.G.Debugf("got network '%s' from cloud config", network)
flags[i].Value = network
case "az":
lo.G.Debugf("got azs %v from cloud config", az)
flags[i].Value = az
case "vm-type":
lo.G.Debugf("got vm type %s from cloud config", vm)
flags[i].Value = vm
}
}
}
}
// storeSecrets writes generated secrets back to vault
func writeSecretsToVault(v *pluginutil.VaultUnmarshal, hash string, cfg *Config) error {
// this is a bit of a hack as the current VaultUnmarshal type
// doesn't expose direct access to the vault values...
// this will change when credential stores are broken out for v1
type secretHash struct {
BrokerUsername string `json:"broker-username"`
BrokerPassword string `json:"broker-password"`
WorkerClientSecret string `json:"worker-client-secret"`
WorkerPassword string `json:"worker-password"`
InstancesPassword string `json:"instances-password"`
BrokerDashboardSecret string `json:"broker-dashboard-secret"`
EncryptionKey string `json:"encryption-key"`
}
sh := secretHash{
BrokerUsername: cfg.BrokerUsername,
BrokerPassword: cfg.BrokerPassword,
WorkerClientSecret: cfg.WorkerClientSecret,
WorkerPassword: cfg.WorkerPassword,
InstancesPassword: cfg.InstancesPassword,
BrokerDashboardSecret: cfg.BrokerDashboardSecret,
EncryptionKey: cfg.EncryptionKey,
}
b, err := json.Marshal(sh)
if err != nil {
return err
}
lo.G.Debug("writing secrets back to vault")
lo.G.Debugf("%#v\n", sh)
// note: RotateSecrets simply stores the provided []byte to the specified hash
return v.RotateSecrets(hash, b)
}
func shouldWriteBackToVault(flags []pcli.Flag) bool {
// for each flag in the secrets hash
for i := range flags {
// if the flag is not populated and needs to be generated
// then we have to generate and write back to vault
if _, ok := secrets[flags[i].Name]; ok {
if flags[i].Value == generatePassword {
return true
}
}
}
return false
}