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