github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/cmd/abapEnvironmentBuild.go (about) 1 package cmd 2 3 import ( 4 "encoding/json" 5 "net/url" 6 "reflect" 7 "strings" 8 "time" 9 10 abapbuild "github.com/SAP/jenkins-library/pkg/abap/build" 11 "github.com/SAP/jenkins-library/pkg/abaputils" 12 "github.com/SAP/jenkins-library/pkg/command" 13 piperhttp "github.com/SAP/jenkins-library/pkg/http" 14 "github.com/SAP/jenkins-library/pkg/log" 15 "github.com/SAP/jenkins-library/pkg/piperutils" 16 "github.com/SAP/jenkins-library/pkg/telemetry" 17 "github.com/pkg/errors" 18 ) 19 20 type abapEnvironmentBuildUtils interface { 21 command.ExecRunner 22 abaputils.Communication 23 abapbuild.HTTPSendLoader 24 piperutils.FileUtils 25 getMaxRuntime() time.Duration 26 getPollingInterval() time.Duration 27 publish() 28 } 29 30 type abapEnvironmentBuildUtilsBundle struct { 31 *command.Command 32 *piperhttp.Client 33 *abaputils.AbapUtils 34 *piperutils.Files 35 maxRuntime time.Duration 36 pollingInterval time.Duration 37 storePublish publish 38 } 39 40 type publish struct { 41 stepName string 42 workspace string 43 reports []piperutils.Path 44 links []piperutils.Path 45 } 46 47 func (p *publish) publish(utils piperutils.FileUtils) { 48 if p.stepName != "" { 49 piperutils.PersistReportsAndLinks(p.stepName, p.workspace, utils, p.reports, p.links) 50 } 51 } 52 53 func (aEBUB *abapEnvironmentBuildUtilsBundle) publish() { 54 aEBUB.storePublish.publish(aEBUB) 55 } 56 57 func (aEBUB *abapEnvironmentBuildUtilsBundle) getMaxRuntime() time.Duration { 58 return aEBUB.maxRuntime 59 } 60 61 func (aEBUB *abapEnvironmentBuildUtilsBundle) getPollingInterval() time.Duration { 62 return aEBUB.pollingInterval 63 } 64 65 func (aEBUB *abapEnvironmentBuildUtilsBundle) PersistReportsAndLinks(stepName, workspace string, reports, links []piperutils.Path) { 66 // abapbuild.PersistReportsAndLinks(stepName, workspace, reports, links) 67 if aEBUB.storePublish.stepName == "" { 68 aEBUB.storePublish.stepName = stepName 69 aEBUB.storePublish.workspace = workspace 70 aEBUB.storePublish.reports = reports 71 aEBUB.storePublish.links = links 72 } else { 73 aEBUB.storePublish.reports = append(aEBUB.storePublish.reports, reports...) 74 aEBUB.storePublish.links = append(aEBUB.storePublish.reports, links...) 75 } 76 } 77 78 func newAbapEnvironmentBuildUtils(maxRuntime time.Duration, pollingInterval time.Duration) abapEnvironmentBuildUtils { 79 utils := abapEnvironmentBuildUtilsBundle{ 80 Command: &command.Command{}, 81 Client: &piperhttp.Client{}, 82 AbapUtils: &abaputils.AbapUtils{ 83 Exec: &command.Command{}, 84 }, 85 maxRuntime: maxRuntime * time.Minute, 86 pollingInterval: pollingInterval * time.Second, 87 storePublish: publish{}, 88 } 89 // Reroute command output to logging framework 90 utils.Stdout(log.Writer()) 91 utils.Stderr(log.Writer()) 92 return &utils 93 } 94 95 func abapEnvironmentBuild(config abapEnvironmentBuildOptions, telemetryData *telemetry.CustomData, cpe *abapEnvironmentBuildCommonPipelineEnvironment) { 96 utils := newAbapEnvironmentBuildUtils(time.Duration(config.MaxRuntimeInMinutes), time.Duration(config.PollingIntervalInSeconds)) 97 if err := runAbapEnvironmentBuild(&config, telemetryData, utils, cpe); err != nil { 98 log.Entry().WithError(err).Fatal("step execution failed") 99 } 100 } 101 102 func runAbapEnvironmentBuild(config *abapEnvironmentBuildOptions, telemetryData *telemetry.CustomData, utils abapEnvironmentBuildUtils, cpe *abapEnvironmentBuildCommonPipelineEnvironment) error { 103 104 log.Entry().Info("╔════════════════════════════════╗") 105 log.Entry().Info("║ abapEnvironmentBuild ║") 106 log.Entry().Info("╠════════════════════════════════╣") 107 log.Entry().Infof("║ %-30v ║", config.Phase) 108 log.Entry().Info("╙────────────────────────────────╜") 109 110 conn := new(abapbuild.Connector) 111 if err := initConnection(conn, config, utils); err != nil { 112 return errors.Wrap(err, "Connector initialization for communication with the ABAP system failed") 113 } 114 115 valuesList, err := evaluateAddonDescriptor(config) 116 if err != nil { 117 return errors.Wrap(err, "Error during the evaluation of the AddonDescriptor") 118 } 119 120 finalValues, err := runBuilds(conn, config, utils, valuesList) 121 // files should be published, even if an error occured 122 utils.publish() 123 if err != nil { 124 return err 125 } 126 127 cpe.abap.buildValues, err = convertValuesForCPE(finalValues) 128 if err != nil { 129 return errors.Wrap(err, "Error during the conversion of the values for the commonPipelineenvironment") 130 } 131 return nil 132 } 133 134 func runBuilds(conn *abapbuild.Connector, config *abapEnvironmentBuildOptions, utils abapEnvironmentBuildUtils, valuesList [][]abapbuild.Value) ([]abapbuild.Value, error) { 135 var finalValues []abapbuild.Value 136 // No addonDescriptor involved 137 if len(valuesList) == 0 { 138 values, err := generateValuesOnlyFromConfig(config) 139 if err != nil { 140 return finalValues, errors.Wrap(err, "Generating the values from config failed") 141 } 142 finalValues, err = runBuild(conn, config, utils, values) 143 if err != nil { 144 return finalValues, errors.Wrap(err, "Error during execution of build framework") 145 } 146 } else { 147 // Run several times for each repository in the addonDescriptor 148 var errstrings []string 149 vE := valuesEvaluator{} 150 vE.m = make(map[string]string) 151 for _, values := range valuesList { 152 cummulatedValues, err := generateValuesWithAddonDescriptor(config, values) 153 if err != nil { 154 return finalValues, errors.Wrap(err, "Error generating input values") 155 } 156 finalValuesForOneBuild, err := runBuild(conn, config, utils, cummulatedValues) 157 if err != nil { 158 err = errors.Wrapf(err, "Build with input values %s failed", values2string(values)) 159 if config.StopOnFirstError { 160 return finalValues, err 161 } 162 errstrings = append(errstrings, err.Error()) 163 } 164 finalValuesForOneBuild = removeAddonDescriptorValues(finalValuesForOneBuild, values) 165 // This means: probably values are duplicated, but the first one wins -> perhaps change this in the future if needed 166 if err := vE.appendValuesIfNotPresent(finalValuesForOneBuild, false); err != nil { 167 errstrings = append(errstrings, err.Error()) 168 } 169 } 170 finalValues = vE.generateValueSlice() 171 if len(errstrings) > 0 { 172 finalError := errors.Errorf("%d out %d build runs failed:\n%s", len(errstrings), len(valuesList), (strings.Join(errstrings, "\n"))) 173 return finalValues, finalError 174 } 175 } 176 return finalValues, nil 177 } 178 179 func initConnection(conn *abapbuild.Connector, config *abapEnvironmentBuildOptions, utils abapEnvironmentBuildUtils) error { 180 var connConfig abapbuild.ConnectorConfiguration 181 connConfig.CfAPIEndpoint = config.CfAPIEndpoint 182 connConfig.CfOrg = config.CfOrg 183 connConfig.CfSpace = config.CfSpace 184 connConfig.CfServiceInstance = config.CfServiceInstance 185 connConfig.CfServiceKeyName = config.CfServiceKeyName 186 connConfig.Host = config.Host 187 connConfig.Username = config.Username 188 connConfig.Password = config.Password 189 connConfig.MaxRuntimeInMinutes = config.MaxRuntimeInMinutes 190 connConfig.CertificateNames = config.CertificateNames 191 connConfig.Parameters = url.Values{} 192 if len(config.AbapSourceClient) != 0 { 193 connConfig.Parameters.Add("sap-client", config.AbapSourceClient) 194 } 195 196 if err := conn.InitBuildFramework(connConfig, utils, utils); err != nil { 197 return err 198 } 199 200 conn.MaxRuntime = utils.getMaxRuntime() 201 conn.PollingInterval = utils.getPollingInterval() 202 return nil 203 } 204 205 // ***********************************Run Build*************************************************************** 206 func runBuild(conn *abapbuild.Connector, config *abapEnvironmentBuildOptions, utils abapEnvironmentBuildUtils, values []abapbuild.Value) ([]abapbuild.Value, error) { 207 var finalValues []abapbuild.Value 208 var inputValues abapbuild.Values 209 inputValues.Values = values 210 211 build := myBuild{ 212 Build: abapbuild.Build{ 213 Connector: *conn, 214 }, 215 abapEnvironmentBuildOptions: config, 216 } 217 if err := build.Start(inputValues); err != nil { 218 return finalValues, err 219 } 220 221 if err := build.Poll(); err != nil { 222 return finalValues, errors.Wrap(err, "Error during the polling for the final state of the build run") 223 } 224 225 if err := build.PrintLogs(); err != nil { 226 return finalValues, errors.Wrap(err, "Error printing the logs") 227 } 228 229 errBuildRun := build.EvaluteIfBuildSuccessful() 230 231 if err := build.Download(); err != nil { 232 if errBuildRun != nil { 233 errWraped := errors.Errorf("Download failed after execution of build failed: %v. Build error: %v", err, errBuildRun) 234 return finalValues, errWraped 235 } 236 return finalValues, err 237 } 238 if err := build.Publish(utils); err != nil { 239 return finalValues, err 240 } 241 242 finalValues, err := build.GetFinalValues() 243 if err != nil { 244 return finalValues, err 245 } 246 return finalValues, errBuildRun 247 } 248 249 type myBuild struct { 250 abapbuild.Build 251 *abapEnvironmentBuildOptions 252 } 253 254 func (b *myBuild) Start(values abapbuild.Values) error { 255 if err := b.Build.Start(b.abapEnvironmentBuildOptions.Phase, values); err != nil { 256 return errors.Wrap(err, "Error starting the build framework") 257 } 258 return nil 259 } 260 261 func (b *myBuild) EvaluteIfBuildSuccessful() error { 262 if err := b.Build.EvaluteIfBuildSuccessful(b.TreatWarningsAsError); err != nil { 263 return errors.Wrap(err, "Build ended without success") 264 } 265 return nil 266 } 267 268 func (b *myBuild) Download() error { 269 if b.DownloadAllResultFiles { 270 if err := b.DownloadAllResults(b.SubDirectoryForDownload, b.FilenamePrefixForDownload); err != nil { 271 return errors.Wrap(err, "Error during the download of the result files") 272 } 273 } else { 274 if err := b.DownloadResults(b.DownloadResultFilenames, b.SubDirectoryForDownload, b.FilenamePrefixForDownload); err != nil { 275 return errors.Wrapf(err, "Error during the download of the result files %s", b.DownloadResultFilenames) 276 } 277 } 278 return nil 279 } 280 281 func (b *myBuild) Publish(utils abapEnvironmentBuildUtils) error { 282 if b.PublishAllDownloadedResultFiles { 283 b.PublishAllDownloadedResults("abapEnvironmentBuild", utils) 284 } else { 285 if err := b.PublishDownloadedResults("abapEnvironmentBuild", b.PublishResultFilenames, utils); err != nil { 286 return errors.Wrapf(err, "Error during the publish of the result files %s", b.PublishResultFilenames) 287 } 288 } 289 return nil 290 } 291 292 func (b *myBuild) GetFinalValues() ([]abapbuild.Value, error) { 293 var values []abapbuild.Value 294 if err := b.GetValues(); err != nil { 295 return values, errors.Wrapf(err, "Error getting the values from build framework") 296 } 297 return b.Build.Values, nil 298 } 299 300 // **********************************Values Handling************************************************************** 301 func convertValuesForCPE(values []abapbuild.Value) (string, error) { 302 type cpeValue struct { 303 ValueID string `json:"value_id"` 304 Value string `json:"value"` 305 } 306 var cpeValues []cpeValue 307 byt, err := json.Marshal(&values) 308 if err != nil { 309 return "", errors.Wrapf(err, "Error converting the values from the build framework") 310 } 311 if err := json.Unmarshal(byt, &cpeValues); err != nil { 312 return "", errors.Wrapf(err, "Error converting the values from the build framework into the structure for the commonPipelineEnvironment") 313 } 314 jsonBytes, err := json.Marshal(cpeValues) 315 if err != nil { 316 return "", errors.Wrapf(err, "Error converting the converted values") 317 } 318 return string(jsonBytes), nil 319 } 320 321 func removeAddonDescriptorValues(finalValuesFromBuild []abapbuild.Value, valuesFromAddonDescriptor []abapbuild.Value) []abapbuild.Value { 322 var finalValues []abapbuild.Value 323 mapForAddonDescriptorValues := make(map[string]string) 324 for _, value := range valuesFromAddonDescriptor { 325 mapForAddonDescriptorValues[value.ValueID] = value.Value 326 } 327 for _, value := range finalValuesFromBuild { 328 _, present := mapForAddonDescriptorValues[value.ValueID] 329 if !present { 330 finalValues = append(finalValues, value) 331 } 332 } 333 return finalValues 334 } 335 336 func generateValuesWithAddonDescriptor(config *abapEnvironmentBuildOptions, repoValues []abapbuild.Value) ([]abapbuild.Value, error) { 337 var values []abapbuild.Value 338 vE := valuesEvaluator{} 339 // values from config 340 if err := vE.initialize(config.Values); err != nil { 341 return values, err 342 } 343 // values from addondescriptor 344 if err := vE.appendValuesIfNotPresent(repoValues, true); err != nil { 345 return values, err 346 } 347 // values from commonepipelineEnvironment 348 if err := vE.appendStringValuesIfNotPresent(config.CpeValues, false); err != nil { 349 return values, err 350 } 351 values = vE.generateValueSlice() 352 return values, nil 353 } 354 355 func generateValuesOnlyFromConfig(config *abapEnvironmentBuildOptions) ([]abapbuild.Value, error) { 356 return generateValuesWithAddonDescriptor(config, []abapbuild.Value{}) 357 } 358 359 func generateValuesFromString(stringValues string) ([]abapbuild.Value, error) { 360 var values []abapbuild.Value 361 if len(stringValues) > 0 { 362 if err := json.Unmarshal([]byte(stringValues), &values); err != nil { 363 log.SetErrorCategory(log.ErrorConfiguration) 364 return values, errors.Wrapf(err, "Could not convert the values %s", stringValues) 365 } 366 } 367 return values, nil 368 } 369 370 type valuesEvaluator struct { 371 m map[string]string 372 } 373 374 func (vE *valuesEvaluator) initialize(stringValues string) error { 375 values, err := generateValuesFromString(stringValues) 376 if err != nil { 377 return errors.Wrapf(err, "Error converting the vales from the config") 378 } 379 vE.m = make(map[string]string) 380 for _, value := range values { 381 if (len(value.ValueID) == 0) || (len(value.Value) == 0) { 382 log.SetErrorCategory(log.ErrorConfiguration) 383 return errors.Errorf("Values %s from config have not the right format", stringValues) 384 } 385 _, present := vE.m[value.ValueID] 386 if present { 387 log.SetErrorCategory(log.ErrorConfiguration) 388 return errors.Errorf("Value_id %s is not unique in the config", value.ValueID) 389 } 390 vE.m[value.ValueID] = value.Value 391 } 392 return nil 393 } 394 395 func (vE *valuesEvaluator) appendStringValuesIfNotPresent(stringValues string, throwErrorIfPresent bool) error { 396 var values []abapbuild.Value 397 values, err := generateValuesFromString(stringValues) 398 if err != nil { 399 return errors.Wrapf(err, "Error converting the vales from the commonPipelineEnvironment") 400 } 401 if err := vE.appendValuesIfNotPresent(values, throwErrorIfPresent); err != nil { 402 return err 403 } 404 return nil 405 } 406 407 func (vE *valuesEvaluator) appendValuesIfNotPresent(values []abapbuild.Value, throwErrorIfPresent bool) error { 408 for _, value := range values { 409 if value.ValueID == "PHASE" || value.ValueID == "BUILD_FRAMEWORK_MODE" { 410 continue 411 } 412 _, present := vE.m[value.ValueID] 413 if present { 414 if throwErrorIfPresent { 415 return errors.Errorf("Value_id %s already existed in the config", value.ValueID) 416 } 417 log.Entry().Infof("Value '%s':'%s' already existed -> discard this value", value.ValueID, value.Value) 418 } else { 419 vE.m[value.ValueID] = value.Value 420 } 421 } 422 return nil 423 } 424 425 func (vE *valuesEvaluator) generateValueSlice() []abapbuild.Value { 426 var values []abapbuild.Value 427 var value abapbuild.Value 428 for k, v := range vE.m { 429 value.ValueID = k 430 value.Value = v 431 values = append(values, value) 432 } 433 return values 434 } 435 436 // **********************************Evaluate AddonDescriptor************************************************************** 437 type myRepo struct { 438 abaputils.Repository 439 } 440 441 type condition struct { 442 Field string `json:"field"` 443 Operator string `json:"operator"` 444 Value string `json:"value"` 445 } 446 447 type useField struct { 448 Use string `json:"use"` 449 Rename string `json:"renameTo"` 450 } 451 452 func evaluateAddonDescriptor(config *abapEnvironmentBuildOptions) ([][]abapbuild.Value, error) { 453 var listOfValuesList [][]abapbuild.Value 454 if len(config.AddonDescriptor) == 0 && len(config.UseFieldsOfAddonDescriptor) > 0 { 455 return listOfValuesList, errors.New("Config contains UseFieldsOfAddonDescriptor but no addonDescriptor is provided in the commonPipelineEnvironment") 456 } 457 if len(config.AddonDescriptor) > 0 { 458 addonDescriptor := new(abaputils.AddonDescriptor) 459 if err := addonDescriptor.InitFromJSONstring(config.AddonDescriptor); err != nil { 460 log.SetErrorCategory(log.ErrorConfiguration) 461 return listOfValuesList, errors.Wrap(err, "Error during the conversion of the AddonDescriptor") 462 } 463 for _, repo := range addonDescriptor.Repositories { 464 myRepo := myRepo{ 465 Repository: repo, 466 } 467 use, err := myRepo.checkCondition(config) 468 if err != nil { 469 return listOfValuesList, errors.Wrapf(err, "Checking of ConditionOnAddonDescriptor failed") 470 } 471 if use { 472 values, err := myRepo.generateValues(config) 473 if err != nil { 474 return listOfValuesList, errors.Wrap(err, "Error generating values from AddonDescriptor") 475 } 476 if len(values) > 0 { 477 listOfValuesList = append(listOfValuesList, values) 478 } 479 } 480 } 481 } 482 return listOfValuesList, nil 483 } 484 485 func (mR *myRepo) checkCondition(config *abapEnvironmentBuildOptions) (bool, error) { 486 var conditions []condition 487 if len(config.ConditionOnAddonDescriptor) > 0 { 488 if err := json.Unmarshal([]byte(config.ConditionOnAddonDescriptor), &conditions); err != nil { 489 log.SetErrorCategory(log.ErrorConfiguration) 490 return false, errors.Wrapf(err, "Conversion of ConditionOnAddonDescriptor in the config failed") 491 } 492 for _, cond := range conditions { 493 if cond.Field == "" || cond.Operator == "" || cond.Value == "" { 494 log.SetErrorCategory(log.ErrorConfiguration) 495 return false, errors.Errorf("Invalid condition for field %s with operator %s and value %s", cond.Field, cond.Operator, cond.Value) 496 } 497 use, err := mR.amI(cond.Field, cond.Operator, cond.Value) 498 if err != nil { 499 return false, errors.Wrapf(err, "Checking the field %s failed", cond.Field) 500 } 501 if !use { 502 log.Entry().Infof("addonDescriptor with the name %s does not fulfil the requierement %s%s%s from the ConditionOnAddonDescriptor, therefore it is not used", mR.Name, cond.Field, cond.Operator, cond.Value) 503 return false, nil 504 } 505 log.Entry().Infof("addonDescriptor with the name %s does fulfil the requierement %s%s%s in the ConditionOnAddonDescriptor", mR.Name, cond.Field, cond.Operator, cond.Value) 506 } 507 } 508 return true, nil 509 } 510 511 func (mR *myRepo) generateValues(config *abapEnvironmentBuildOptions) ([]abapbuild.Value, error) { 512 var values []abapbuild.Value 513 var useFields []useField 514 if len(config.UseFieldsOfAddonDescriptor) == 0 { 515 log.Entry().Infof("UseFieldsOfAddonDescriptor is empty, nothing is used from the addonDescriptor") 516 } else { 517 if err := json.Unmarshal([]byte(config.UseFieldsOfAddonDescriptor), &useFields); err != nil { 518 log.SetErrorCategory(log.ErrorConfiguration) 519 return values, errors.Wrapf(err, "Conversion of UseFieldsOfAddonDescriptor in the config failed") 520 } 521 m := make(map[string]string) 522 for _, uF := range useFields { 523 if uF.Use == "" || uF.Rename == "" { 524 log.SetErrorCategory(log.ErrorConfiguration) 525 return values, errors.Errorf("Invalid UseFieldsOfAddonDescriptor for use %s and renameTo %s", uF.Use, uF.Rename) 526 } 527 m[uF.Use] = uF.Rename 528 } 529 530 fields := reflect.ValueOf(mR.Repository) 531 typeOfS := fields.Type() 532 for i := 0; i < fields.NumField(); i++ { 533 var value abapbuild.Value 534 ValueID := typeOfS.Field(i).Name 535 rename, present := m[ValueID] 536 if present { 537 log.Entry().Infof("Use field %s from addonDescriptor and rename it to %s, the value is %s", ValueID, rename, fields.Field(i).String()) 538 value.ValueID = rename 539 value.Value = fields.Field(i).String() 540 values = append(values, value) 541 } 542 } 543 if len(values) != len(useFields) { 544 log.SetErrorCategory(log.ErrorConfiguration) 545 return values, errors.Errorf("Not all fields in UseFieldsOfAddonDescriptor have been found. Probably a 'use' was used which does not exist") 546 } 547 } 548 return values, nil 549 } 550 551 func (mR *myRepo) getField(field string) string { 552 r := reflect.ValueOf(mR) 553 f := reflect.Indirect(r).FieldByName(field) 554 return string(f.String()) 555 } 556 557 func (mR *myRepo) amI(field string, operator string, comp string) (bool, error) { 558 operators := OperatorCallback{ 559 "==": Equal, 560 "!=": Unequal, 561 } 562 name := mR.getField(field) 563 if fn, ok := operators[operator]; ok { 564 return fn(name, comp), nil 565 } 566 log.SetErrorCategory(log.ErrorConfiguration) 567 return false, errors.Errorf("Invalid operator %s", operator) 568 } 569 570 type OperatorCallback map[string]func(string, string) bool 571 572 func Equal(a, b string) bool { 573 return a == b 574 } 575 576 func Unequal(a, b string) bool { 577 return a != b 578 } 579 580 func values2string(values []abapbuild.Value) string { 581 var result string 582 for index, value := range values { 583 if index > 0 { 584 result = result + "; " 585 } 586 result = result + value.ValueID + " = " + value.Value 587 } 588 return result 589 }