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  }