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