github.com/cloudfoundry/postgres-release/src/acceptance-tests@v0.0.0-20240511030151-872bdd2e0dba/testing/helpers/bosh_director.go (about) 1 package helpers 2 3 import ( 4 "crypto/rsa" 5 "crypto/x509" 6 "encoding/pem" 7 "errors" 8 "fmt" 9 "io/ioutil" 10 11 yaml "gopkg.in/yaml.v2" 12 13 boshdir "github.com/cloudfoundry/bosh-cli/director" 14 boshtempl "github.com/cloudfoundry/bosh-cli/director/template" 15 boshuaa "github.com/cloudfoundry/bosh-cli/uaa" 16 bosherr "github.com/cloudfoundry/bosh-utils/errors" 17 boshlog "github.com/cloudfoundry/bosh-utils/logger" 18 cfgtypes "github.com/cloudfoundry/config-server/types" 19 patch "github.com/cppforlife/go-patch/patch" 20 ) 21 22 type BOSHDirector struct { 23 Uaa boshuaa.UAA 24 Director boshdir.Director 25 DeploymentsInfo map[string]*DeploymentData 26 DirectorConfig BOSHConfig 27 CloudConfig BOSHCloudConfig 28 DefaultReleasesVersion map[string]string 29 } 30 type DeploymentData struct { 31 ManifestBytes []byte 32 ManifestData map[string]interface{} 33 Deployment boshdir.Deployment 34 Variables boshtempl.Variables 35 } 36 type BOSHConfig struct { 37 Target string `yaml:"target"` 38 Credentials BOSHCredentials `yaml:"credentials"` 39 UseUaa bool `yaml:"use_uaa"` 40 } 41 42 type BOSHCredentials struct { 43 Client string `yaml:"client"` 44 ClientSecret string `yaml:"client_secret"` 45 CACert string `yaml:"ca_cert"` 46 } 47 48 type BOSHCloudConfig struct { 49 AZs []string `yaml:"default_azs"` 50 Networks []BOSHJobNetwork `yaml:"default_networks"` 51 PersistentDiskType string `yaml:"default_persistent_disk_type"` 52 VmType string `yaml:"default_vm_type"` 53 StemcellOs string `yaml:"default_stemcell_os"` 54 StemcellVersion string `yaml:"default_stemcell_version"` 55 } 56 type BOSHJobNetwork struct { 57 Name string `yaml:"name"` 58 StaticIPs []string `yaml:"static_ips,omitempty"` 59 Default []string `yaml:"default,omitempty"` 60 } 61 62 var DefaultBOSHConfig = BOSHConfig{ 63 Target: "192.168.50.4", 64 } 65 var DefaultCloudConfig = BOSHCloudConfig{ 66 AZs: []string{"z1"}, 67 Networks: []BOSHJobNetwork{ 68 BOSHJobNetwork{ 69 Name: "default", 70 }, 71 }, 72 PersistentDiskType: "10GB", 73 VmType: "small", 74 StemcellOs: "ubuntu-jammy", 75 StemcellVersion: "latest", 76 } 77 78 type VarsCertLoader struct { 79 vars boshtempl.Variables 80 } 81 82 type EvaluateOptions boshtempl.EvaluateOpts 83 type OpDefinition patch.OpDefinition 84 85 const MissingDeploymentNameMsg = "Invalid manifest: deployment name not present" 86 const VMNotPresentMsg = "No VM exists with name %s" 87 const ProcessNotPresentInVmMsg = "Process %s does not exist in vm %s" 88 89 func GenerateEnvName(prefix string) string { 90 return fmt.Sprintf("pgats-%s-%s", prefix, GetUUID()) 91 } 92 93 func NewBOSHDirector(boshConfig BOSHConfig, cloudConfig BOSHCloudConfig, releasesVersions map[string]string) (BOSHDirector, error) { 94 var boshDirector BOSHDirector 95 var uaaURL, directorURL string 96 97 boshDirector.DirectorConfig = boshConfig 98 boshDirector.CloudConfig = cloudConfig 99 boshDirector.DefaultReleasesVersion = releasesVersions 100 101 directorURL = fmt.Sprintf("https://%s:25555", boshConfig.Target) 102 logger := boshlog.NewLogger(boshlog.LevelError) 103 factory := boshdir.NewFactory(logger) 104 directorConfig, err := boshdir.NewConfigFromURL(directorURL) 105 if err != nil { 106 return BOSHDirector{}, err 107 } 108 directorConfig.CACert = boshConfig.Credentials.CACert 109 110 if boshConfig.UseUaa { 111 uaaURL = fmt.Sprintf("https://%s:8443", boshConfig.Target) 112 uaaFactory := boshuaa.NewFactory(logger) 113 uaaConfig, err := boshuaa.NewConfigFromURL(uaaURL) 114 if err != nil { 115 return BOSHDirector{}, err 116 } 117 uaaConfig.Client = boshConfig.Credentials.Client 118 uaaConfig.ClientSecret = boshConfig.Credentials.ClientSecret 119 uaaConfig.CACert = boshConfig.Credentials.CACert 120 uaa, err := uaaFactory.New(uaaConfig) 121 if err != nil { 122 return BOSHDirector{}, err 123 } 124 boshDirector.Uaa = uaa 125 126 directorConfig.TokenFunc = boshuaa.NewClientTokenSession(uaa).TokenFunc 127 } else { 128 directorConfig.Client = boshConfig.Credentials.Client 129 directorConfig.ClientSecret = boshConfig.Credentials.ClientSecret 130 } 131 director, err := factory.New(directorConfig, boshdir.NewNoopTaskReporter(), boshdir.NewNoopFileReporter()) 132 if err != nil { 133 return BOSHDirector{}, err 134 } 135 136 boshDirector.Director = director 137 boshDirector.DeploymentsInfo = make(map[string]*DeploymentData) 138 139 return boshDirector, nil 140 } 141 142 func (bd BOSHDirector) GetEnv(envName string) *DeploymentData { 143 return bd.DeploymentsInfo[envName] 144 } 145 func (bd *BOSHDirector) SetDeploymentFromManifest(manifestFilePath string, releasesVersions map[string]string, deploymentName string) error { 146 var err error 147 var dd DeploymentData 148 149 dd.ManifestBytes, err = ioutil.ReadFile(manifestFilePath) 150 if err != nil { 151 return err 152 } 153 154 if err := yaml.Unmarshal(dd.ManifestBytes, &dd.ManifestData); err != nil { 155 return err 156 } 157 158 dd.ManifestData["name"] = deploymentName 159 160 if dd.ManifestData["releases"] != nil { 161 for _, elem := range dd.ManifestData["releases"].([]interface{}) { 162 relName := elem.(map[interface{}]interface{})["name"] 163 if version, ok := releasesVersions[relName.(string)]; ok { 164 elem.(map[interface{}]interface{})["version"] = version 165 } else if version, ok := bd.DefaultReleasesVersion[relName.(string)]; ok { 166 elem.(map[interface{}]interface{})["version"] = version 167 } 168 } 169 } 170 171 if dd.ManifestData["stemcells"] != nil { 172 for _, elem := range dd.ManifestData["stemcells"].([]interface{}) { 173 stemcellAlias := elem.(map[interface{}]interface{})["alias"] 174 if stemcellAlias.(string) == "linux" { 175 elem.(map[interface{}]interface{})["os"] = bd.CloudConfig.StemcellOs 176 elem.(map[interface{}]interface{})["version"] = bd.CloudConfig.StemcellVersion 177 } 178 } 179 } 180 181 if dd.ManifestData["instance_groups"] != nil { 182 183 netBytes, err := yaml.Marshal(&bd.CloudConfig.Networks) 184 if err != nil { 185 return err 186 } 187 var netData []map[string]interface{} 188 if err := yaml.Unmarshal(netBytes, &netData); err != nil { 189 return err 190 } 191 192 for _, elem := range dd.ManifestData["instance_groups"].([]interface{}) { 193 elem.(map[interface{}]interface{})["azs"] = bd.CloudConfig.AZs 194 elem.(map[interface{}]interface{})["networks"] = netData 195 elem.(map[interface{}]interface{})["persistent_disk_type"] = bd.CloudConfig.PersistentDiskType 196 elem.(map[interface{}]interface{})["vm_type"] = bd.CloudConfig.VmType 197 } 198 } 199 200 dd.ManifestBytes, err = yaml.Marshal(&dd.ManifestData) 201 if err != nil { 202 return err 203 } 204 205 if dd.ManifestData["name"] == nil || dd.ManifestData["name"] == "" { 206 return errors.New(MissingDeploymentNameMsg) 207 } 208 209 dd.Deployment, err = bd.Director.FindDeployment(dd.ManifestData["name"].(string)) 210 if err != nil { 211 return err 212 } 213 bd.DeploymentsInfo[deploymentName] = &dd 214 return nil 215 } 216 func (bd BOSHDirector) UploadPostgresReleaseFromURL(version int) error { 217 return bd.UploadReleaseFromURL("cloudfoundry", "postgres-release", version) 218 } 219 func (bd BOSHDirector) UploadReleaseFromURL(organization string, repo string, version int) error { 220 url := fmt.Sprintf("https://bosh.io/d/github.com/%s/%s?v=%d", organization, repo, version) 221 return bd.Director.UploadReleaseURL(url, "", false, false) 222 } 223 func (bd BOSHDirector) UploadLatestReleaseFromURL(organization string, repo string) error { 224 url := fmt.Sprintf("https://bosh.io/d/github.com/%s/%s", organization, repo) 225 return bd.Director.UploadReleaseURL(url, "", false, false) 226 } 227 228 func (dd DeploymentData) ContainsVariables() bool { 229 return dd.ManifestData != nil && dd.ManifestData["variables"] != nil && len(dd.ManifestData["variables"].([]interface{})) != 0 230 } 231 232 func (dd DeploymentData) GetVariable(key string) interface{} { 233 if dd.Variables != nil { 234 vardef := boshtempl.VariableDefinition{Name: key} 235 if value, ok, err := dd.Variables.Get(vardef); ok && err == nil { 236 return value 237 } 238 } 239 return nil 240 } 241 242 func (dd *DeploymentData) EvaluateTemplate(vars map[string]interface{}, opDefs []OpDefinition, opts EvaluateOptions) error { 243 template := boshtempl.NewTemplate(dd.ManifestBytes) 244 245 var ops patch.Ops 246 var opDefinitions []patch.OpDefinition 247 for _, def := range opDefs { 248 opDefinitions = append(opDefinitions, patch.OpDefinition(def)) 249 } 250 ops, err := patch.NewOpsFromDefinitions(opDefinitions) 251 if err != nil { 252 return err 253 } 254 255 var staticVariables boshtempl.StaticVariables 256 var structVariables boshtempl.StaticVariables 257 var mapVariables MapVariables 258 259 staticVariables = boshtempl.StaticVariables(vars) 260 structVariables = boshtempl.StaticVariables(make(map[string]interface{})) 261 result, err := template.Evaluate(boshtempl.StaticVariables(vars), ops, boshtempl.EvaluateOpts(opts)) 262 if err != nil { 263 return err 264 } 265 dd.ManifestBytes = result 266 if err := yaml.Unmarshal(dd.ManifestBytes, &dd.ManifestData); err != nil { 267 return err 268 } 269 multiVars := boshtempl.NewMultiVars([]boshtempl.Variables{staticVariables, structVariables}) 270 factory := cfgtypes.NewValueGeneratorConcrete(NewVarsCertLoader(multiVars)) 271 272 if dd.ManifestData["variables"] != nil { 273 for _, elem := range dd.ManifestData["variables"].([]interface{}) { 274 vdname := elem.(map[interface{}]interface{})["name"] 275 vdtype := elem.(map[interface{}]interface{})["type"] 276 vdoptions := elem.(map[interface{}]interface{})["options"] 277 278 generator, err := factory.GetGenerator(vdtype.(string)) 279 if err != nil { 280 return err 281 } 282 value, err := generator.Generate(vdoptions) 283 if err != nil { 284 return err 285 } 286 if vdtype == "ssh" || vdtype == "certificate" { 287 structVariables[vdname.(string)] = value 288 } else { 289 staticVariables[vdname.(string)] = value 290 } 291 } 292 } 293 for key, value := range structVariables { 294 mapVariables.Add(key, value) 295 } 296 multiVars = boshtempl.NewMultiVars([]boshtempl.Variables{staticVariables, mapVariables}) 297 result, err = template.Evaluate(multiVars, ops, boshtempl.EvaluateOpts(opts)) 298 if err != nil { 299 return err 300 } 301 dd.ManifestBytes = result 302 if err := yaml.Unmarshal(dd.ManifestBytes, &dd.ManifestData); err != nil { 303 return err 304 } 305 dd.ManifestData["variables"] = []boshtempl.Variables{} 306 dd.ManifestBytes, err = yaml.Marshal(&dd.ManifestData) 307 if err != nil { 308 return err 309 } 310 dd.Variables = multiVars 311 return nil 312 } 313 func (dd DeploymentData) CreateOrUpdateDeployment() error { 314 updateOpts := boshdir.UpdateOpts{} 315 return dd.Deployment.Update(dd.ManifestBytes, updateOpts) 316 } 317 318 func (dd DeploymentData) PrintDeploymentDiffs() error { 319 diff, err := dd.Deployment.Diff(dd.ManifestBytes, false) 320 fmt.Println("Deployment differences:") 321 for _, line := range diff.Diff { 322 lineMod, _ := line[1].(string) 323 324 if lineMod == "added" { 325 fmt.Printf("+ %s\n", line[0]) 326 } else if lineMod == "removed" { 327 fmt.Printf("- %s\n", line[0]) 328 } else { 329 fmt.Printf(" %s\n", line[0]) 330 } 331 } 332 return err 333 } 334 335 func (dd DeploymentData) DeleteDeployment() error { 336 return dd.Deployment.Delete(true) 337 } 338 339 func (dd DeploymentData) Restart(instanceGroupName string) error { 340 slug := boshdir.NewAllOrInstanceGroupOrInstanceSlug(instanceGroupName, "0") 341 restartOptions := boshdir.RestartOpts{} 342 return dd.Deployment.Restart(slug, restartOptions) 343 } 344 func (dd DeploymentData) Stop(instanceGroupName string) error { 345 slug := boshdir.NewAllOrInstanceGroupOrInstanceSlug(instanceGroupName, "0") 346 stopOptions := boshdir.StopOpts{} 347 return dd.Deployment.Stop(slug, stopOptions) 348 } 349 func (dd DeploymentData) Start(instanceGroupName string) error { 350 slug := boshdir.NewAllOrInstanceGroupOrInstanceSlug(instanceGroupName, "0") 351 startOptions := boshdir.StartOpts{} 352 return dd.Deployment.Start(slug, startOptions) 353 } 354 355 func (dd DeploymentData) IsVmProcessRunning(vmid string, processName string) (bool, error) { 356 vms, err := dd.Deployment.VMInfos() 357 if err != nil { 358 return false, err 359 } 360 for _, info := range vms { 361 if info.ID == vmid { 362 if processName == "" { 363 return info.IsRunning(), nil 364 } else if info.Processes == nil || len(info.Processes) == 0 { 365 return false, nil 366 } else { 367 for _, p := range info.Processes { 368 if p.Name == processName { 369 return p.IsRunning(), nil 370 } 371 } 372 return false, errors.New(fmt.Sprintf(ProcessNotPresentInVmMsg, processName, vmid)) 373 } 374 } 375 } 376 return false, errors.New(fmt.Sprintf(VMNotPresentMsg, vmid)) 377 } 378 func (dd DeploymentData) GetVmAddresses(vmname string) ([]string, error) { 379 var result []string 380 vms, err := dd.Deployment.VMInfos() 381 if err != nil { 382 return nil, err 383 } 384 for _, info := range vms { 385 if info.JobName == vmname { 386 result = append(result, info.IPs[0]) 387 } 388 } 389 if result == nil { 390 return nil, errors.New(fmt.Sprintf(VMNotPresentMsg, vmname)) 391 } 392 return result, nil 393 } 394 func (dd DeploymentData) GetVmDNS(vmname string) (string, error) { 395 var result string 396 vms, err := dd.Deployment.VMInfos() 397 if err != nil { 398 return "", err 399 } 400 for _, info := range vms { 401 if info.JobName == vmname && len(info.DNS) > 0 { 402 return info.DNS[0], nil 403 } 404 } 405 if result == "" { 406 return "", errors.New(fmt.Sprintf(VMNotPresentMsg, vmname)) 407 } 408 return result, nil 409 } 410 func (dd DeploymentData) GetVmAddress(vmname string) (string, error) { 411 addresses, err := dd.GetVmAddresses(vmname) 412 if err != nil { 413 return "", err 414 } 415 return addresses[0], nil 416 } 417 func (dd DeploymentData) GetVmIdByAddress(vmaddress string) (string, error) { 418 vms, err := dd.Deployment.VMInfos() 419 if err != nil { 420 return "", err 421 } 422 for _, info := range vms { 423 for _, ip := range info.IPs { 424 if ip == vmaddress { 425 return info.ID, nil 426 } 427 } 428 } 429 return "", errors.New(fmt.Sprintf(VMNotPresentMsg, vmaddress)) 430 } 431 func (dd DeploymentData) UpdateResurrection(enable bool) error { 432 vms, err := dd.Deployment.VMInfos() 433 if err != nil { 434 return err 435 } 436 for _, info := range vms { 437 err = dd.Deployment.EnableResurrection(boshdir.NewInstanceSlug(info.JobName, info.ID), enable) 438 if err != nil { 439 return err 440 } 441 } 442 return nil 443 } 444 func (dd DeploymentData) GetJobsProperties() (ManifestProperties, error) { 445 // since global properties and instance group properties are deprecated, we only considers those specified for the instance group jobs 446 var result ManifestProperties 447 if dd.ManifestData["instance_groups"] != nil { 448 for _, elem := range dd.ManifestData["instance_groups"].([]interface{}) { 449 if elem.(map[interface{}]interface{})["jobs"] != nil { 450 for _, job := range elem.(map[interface{}]interface{})["jobs"].([]interface{}) { 451 bytes, err := yaml.Marshal(job.(map[interface{}]interface{})["properties"]) 452 if err != nil { 453 return ManifestProperties{}, err 454 } 455 jobInstanceName := job.(map[interface{}]interface{})["name"] 456 err = result.LoadJobProperties(jobInstanceName.(string), bytes) 457 if err != nil { 458 return ManifestProperties{}, err 459 } 460 } 461 } 462 } 463 } 464 return result, nil 465 } 466 467 func NewVarsCertLoader(vars boshtempl.Variables) VarsCertLoader { 468 return VarsCertLoader{vars} 469 } 470 471 func (l VarsCertLoader) LoadCerts(name string) (*x509.Certificate, *rsa.PrivateKey, error) { 472 val, found, err := l.vars.Get(boshtempl.VariableDefinition{Name: name}) 473 if err != nil { 474 return nil, nil, err 475 } else if !found { 476 return nil, nil, fmt.Errorf("Expected to find variable '%s' with a certificate", name) 477 } 478 479 // Convert to YAML for easier struct parsing 480 valBytes, err := yaml.Marshal(val) 481 if err != nil { 482 return nil, nil, bosherr.WrapErrorf(err, "Expected variable '%s' to be serializable", name) 483 } 484 485 type CertVal struct { 486 Certificate string 487 PrivateKey string `yaml:"private_key"` 488 } 489 490 var certVal CertVal 491 492 err = yaml.Unmarshal(valBytes, &certVal) 493 if err != nil { 494 return nil, nil, bosherr.WrapErrorf(err, "Expected variable '%s' to be deserializable", name) 495 } 496 497 crt, err := l.parseCertificate(certVal.Certificate) 498 if err != nil { 499 return nil, nil, err 500 } 501 502 key, err := l.parsePrivateKey(certVal.PrivateKey) 503 if err != nil { 504 return nil, nil, err 505 } 506 507 return crt, key, nil 508 } 509 510 func (VarsCertLoader) parseCertificate(data string) (*x509.Certificate, error) { 511 cpb, _ := pem.Decode([]byte(data)) 512 if cpb == nil { 513 return nil, bosherr.Error("Certificate did not contain PEM formatted block") 514 } 515 516 crt, err := x509.ParseCertificate(cpb.Bytes) 517 if err != nil { 518 return nil, bosherr.WrapError(err, "Parsing certificate") 519 } 520 521 return crt, nil 522 } 523 524 func (VarsCertLoader) parsePrivateKey(data string) (*rsa.PrivateKey, error) { 525 kpb, _ := pem.Decode([]byte(data)) 526 if kpb == nil { 527 return nil, bosherr.Error("Private key did not contain PEM formatted block") 528 } 529 530 key, err := x509.ParsePKCS1PrivateKey(kpb.Bytes) 531 if err != nil { 532 return nil, bosherr.WrapError(err, "Parsing private key") 533 } 534 535 return key, nil 536 }