github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/cmd/mtaBuild.go (about) 1 package cmd 2 3 import ( 4 "bytes" 5 "encoding/base64" 6 "encoding/json" 7 "fmt" 8 "net/http" 9 "os" 10 "path" 11 "path/filepath" 12 "strconv" 13 "strings" 14 "text/template" 15 "time" 16 17 "github.com/SAP/jenkins-library/pkg/buildsettings" 18 "github.com/SAP/jenkins-library/pkg/npm" 19 20 "github.com/SAP/jenkins-library/pkg/command" 21 piperhttp "github.com/SAP/jenkins-library/pkg/http" 22 "github.com/SAP/jenkins-library/pkg/log" 23 "github.com/SAP/jenkins-library/pkg/maven" 24 "github.com/SAP/jenkins-library/pkg/piperutils" 25 "github.com/SAP/jenkins-library/pkg/telemetry" 26 "github.com/ghodss/yaml" 27 "github.com/pkg/errors" 28 ) 29 30 const templateMtaYml = `_schema-version: "3.1" 31 ID: "{{.ID}}" 32 version: {{.Version}} 33 34 parameters: 35 hcp-deployer-version: "1.1.0" 36 37 modules: 38 - name: {{.ApplicationName}} 39 type: com.sap.hcp.html5 40 path: . 41 parameters: 42 version: {{.Version}}-${timestamp} 43 name: {{.ApplicationName}} 44 build-parameters: 45 builder: grunt 46 build-result: dist` 47 48 // MTABuildTarget ... 49 type MTABuildTarget int 50 51 const ( 52 // NEO ... 53 NEO MTABuildTarget = iota 54 // CF ... 55 CF MTABuildTarget = iota 56 //XSA ... 57 XSA MTABuildTarget = iota 58 ) 59 60 // ValueOfBuildTarget ... 61 func ValueOfBuildTarget(str string) (MTABuildTarget, error) { 62 switch str { 63 case "NEO": 64 return NEO, nil 65 case "CF": 66 return CF, nil 67 case "XSA": 68 return XSA, nil 69 default: 70 return -1, fmt.Errorf("Unknown Platform: '%s'", str) 71 } 72 } 73 74 // String ... 75 func (m MTABuildTarget) String() string { 76 return [...]string{ 77 "NEO", 78 "CF", 79 "XSA", 80 }[m] 81 } 82 83 type mtaBuildUtils interface { 84 maven.Utils 85 86 SetEnv(env []string) 87 AppendEnv(env []string) 88 89 Abs(path string) (string, error) 90 FileRead(path string) ([]byte, error) 91 FileWrite(path string, content []byte, perm os.FileMode) error 92 93 DownloadAndCopySettingsFiles(globalSettingsFile string, projectSettingsFile string) error 94 95 SetNpmRegistries(defaultNpmRegistry string) error 96 InstallAllDependencies(defaultNpmRegistry string) error 97 } 98 99 type mtaBuildUtilsBundle struct { 100 *command.Command 101 *piperutils.Files 102 *piperhttp.Client 103 } 104 105 func (bundle *mtaBuildUtilsBundle) SetNpmRegistries(defaultNpmRegistry string) error { 106 npmExecutorOptions := npm.ExecutorOptions{DefaultNpmRegistry: defaultNpmRegistry, ExecRunner: bundle} 107 npmExecutor := npm.NewExecutor(npmExecutorOptions) 108 return npmExecutor.SetNpmRegistries() 109 } 110 111 func (bundle *mtaBuildUtilsBundle) InstallAllDependencies(defaultNpmRegistry string) error { 112 npmExecutorOptions := npm.ExecutorOptions{DefaultNpmRegistry: defaultNpmRegistry, ExecRunner: bundle} 113 npmExecutor := npm.NewExecutor(npmExecutorOptions) 114 return npmExecutor.InstallAllDependencies(npmExecutor.FindPackageJSONFiles()) 115 } 116 117 func (bundle *mtaBuildUtilsBundle) DownloadAndCopySettingsFiles(globalSettingsFile string, projectSettingsFile string) error { 118 return maven.DownloadAndCopySettingsFiles(globalSettingsFile, projectSettingsFile, bundle) 119 } 120 121 func newMtaBuildUtilsBundle() mtaBuildUtils { 122 utils := mtaBuildUtilsBundle{ 123 Command: &command.Command{ 124 StepName: "mtaBuild", 125 }, 126 Files: &piperutils.Files{}, 127 Client: &piperhttp.Client{}, 128 } 129 utils.Stdout(log.Writer()) 130 utils.Stderr(log.Writer()) 131 return &utils 132 } 133 134 func mtaBuild(config mtaBuildOptions, 135 telemetryData *telemetry.CustomData, 136 commonPipelineEnvironment *mtaBuildCommonPipelineEnvironment) { 137 log.Entry().Debugf("Launching mta build") 138 utils := newMtaBuildUtilsBundle() 139 140 err := runMtaBuild(config, commonPipelineEnvironment, utils) 141 if err != nil { 142 log.Entry(). 143 WithError(err). 144 Fatal("failed to execute mta build") 145 } 146 } 147 148 func runMtaBuild(config mtaBuildOptions, 149 commonPipelineEnvironment *mtaBuildCommonPipelineEnvironment, 150 utils mtaBuildUtils) error { 151 152 var err error 153 154 err = handleSettingsFiles(config, utils) 155 if err != nil { 156 return err 157 } 158 159 err = handleActiveProfileUpdate(config, utils) 160 if err != nil { 161 return err 162 } 163 164 err = utils.SetNpmRegistries(config.DefaultNpmRegistry) 165 166 mtaYamlFile := filepath.Join(getSourcePath(config), "mta.yaml") 167 mtaYamlFileExists, err := utils.FileExists(mtaYamlFile) 168 169 if err != nil { 170 return err 171 } 172 173 if !mtaYamlFileExists { 174 175 if err = createMtaYamlFile(mtaYamlFile, config.ApplicationName, utils); err != nil { 176 return err 177 } 178 179 } else { 180 log.Entry().Infof("\"%s\" file found in project sources", mtaYamlFile) 181 } 182 183 if err = setTimeStamp(mtaYamlFile, utils); err != nil { 184 return err 185 } 186 187 mtarName, isMtarNativelySuffixed, err := getMtarName(config, mtaYamlFile, utils) 188 189 if err != nil { 190 return err 191 } 192 193 var call []string 194 195 platform, err := ValueOfBuildTarget(config.Platform) 196 if err != nil { 197 log.SetErrorCategory(log.ErrorConfiguration) 198 return err 199 } 200 201 call = append(call, "mbt", "build", "--mtar", mtarName, "--platform", platform.String()) 202 if len(config.Extensions) != 0 { 203 call = append(call, fmt.Sprintf("--extensions=%s", config.Extensions)) 204 } 205 206 call = append(call, "--source", getSourcePath(config)) 207 call = append(call, "--target", getAbsPath(getMtarFileRoot(config))) 208 209 if config.Jobs > 0 { 210 call = append(call, "--mode=verbose") 211 call = append(call, "--jobs="+strconv.Itoa(config.Jobs)) 212 } 213 214 if err = addNpmBinToPath(utils); err != nil { 215 return err 216 } 217 218 if len(config.M2Path) > 0 { 219 absolutePath, err := utils.Abs(config.M2Path) 220 if err != nil { 221 return err 222 } 223 utils.AppendEnv([]string{"MAVEN_OPTS=-Dmaven.repo.local=" + absolutePath}) 224 } 225 226 log.Entry().Infof("Executing mta build call: \"%s\"", strings.Join(call, " ")) 227 228 if err := utils.RunExecutable(call[0], call[1:]...); err != nil { 229 log.SetErrorCategory(log.ErrorBuild) 230 return err 231 } 232 233 log.Entry().Debugf("creating build settings information...") 234 stepName := "mtaBuild" 235 dockerImage, err := GetDockerImageValue(stepName) 236 if err != nil { 237 return err 238 } 239 240 mtaConfig := buildsettings.BuildOptions{ 241 Profiles: config.Profiles, 242 GlobalSettingsFile: config.GlobalSettingsFile, 243 Publish: config.Publish, 244 BuildSettingsInfo: config.BuildSettingsInfo, 245 DefaultNpmRegistry: config.DefaultNpmRegistry, 246 DockerImage: dockerImage, 247 } 248 buildSettingsInfo, err := buildsettings.CreateBuildSettingsInfo(&mtaConfig, stepName) 249 if err != nil { 250 log.Entry().Warnf("failed to create build settings info: %v", err) 251 } 252 commonPipelineEnvironment.custom.buildSettingsInfo = buildSettingsInfo 253 254 commonPipelineEnvironment.mtarFilePath = filepath.ToSlash(getMtarFilePath(config, mtarName)) 255 commonPipelineEnvironment.custom.mtaBuildToolDesc = filepath.ToSlash(mtaYamlFile) 256 257 if config.InstallArtifacts { 258 // install maven artifacts in local maven repo because `mbt build` executes `mvn package -B` 259 err = installMavenArtifacts(utils, config) 260 if err != nil { 261 return err 262 } 263 // mta-builder executes 'npm install --production', therefore we need 'npm ci/install' to install the dev-dependencies 264 err = utils.InstallAllDependencies(config.DefaultNpmRegistry) 265 if err != nil { 266 return err 267 } 268 } 269 270 if config.Publish { 271 log.Entry().Infof("publish detected") 272 if (len(config.MtaDeploymentRepositoryPassword) > 0) && (len(config.MtaDeploymentRepositoryUser) > 0) && 273 (len(config.MtaDeploymentRepositoryURL) > 0) { 274 if (len(config.MtarGroup) > 0) && (len(config.Version) > 0) { 275 httpClient := &piperhttp.Client{} 276 277 credentialsEncoded := "Basic " + base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", config.MtaDeploymentRepositoryUser, config.MtaDeploymentRepositoryPassword))) 278 headers := http.Header{} 279 headers.Add("Authorization", credentialsEncoded) 280 281 config.MtarGroup = strings.ReplaceAll(config.MtarGroup, ".", "/") 282 283 mtarArtifactName := mtarName 284 285 // only trim the .mtar suffix from the mtarName 286 if !isMtarNativelySuffixed { 287 mtarArtifactName = strings.TrimSuffix(mtarArtifactName, ".mtar") 288 } 289 290 config.MtaDeploymentRepositoryURL += config.MtarGroup + "/" + mtarArtifactName + "/" + config.Version + "/" + fmt.Sprintf("%v-%v.%v", mtarArtifactName, config.Version, "mtar") 291 292 commonPipelineEnvironment.custom.mtarPublishedURL = config.MtaDeploymentRepositoryURL 293 294 log.Entry().Infof("pushing mtar artifact to repository : %s", config.MtaDeploymentRepositoryURL) 295 296 data, err := os.Open(getMtarFilePath(config, mtarName)) 297 if err != nil { 298 return errors.Wrap(err, "failed to open mtar archive for upload") 299 } 300 _, httpErr := httpClient.SendRequest("PUT", config.MtaDeploymentRepositoryURL, data, headers, nil) 301 302 if httpErr != nil { 303 return errors.Wrap(err, "failed to upload mtar to repository") 304 } 305 } else { 306 return errors.New("mtarGroup, version not found and must be present") 307 308 } 309 310 } else { 311 return errors.New("mtaDeploymentRepositoryUser, mtaDeploymentRepositoryPassword and mtaDeploymentRepositoryURL not found , must be present") 312 } 313 } else { 314 log.Entry().Infof("no publish detected, skipping upload of mtar artifact") 315 } 316 return err 317 } 318 319 func handleActiveProfileUpdate(config mtaBuildOptions, utils mtaBuildUtils) error { 320 if len(config.Profiles) > 0 { 321 return maven.UpdateActiveProfileInSettingsXML(config.Profiles, utils) 322 } 323 return nil 324 } 325 326 func installMavenArtifacts(utils mtaBuildUtils, config mtaBuildOptions) error { 327 pomXMLExists, err := utils.FileExists("pom.xml") 328 if err != nil { 329 return err 330 } 331 if pomXMLExists { 332 err = maven.InstallMavenArtifacts(&maven.EvaluateOptions{M2Path: config.M2Path}, utils) 333 if err != nil { 334 return err 335 } 336 } 337 return nil 338 } 339 340 func addNpmBinToPath(utils mtaBuildUtils) error { 341 dir, _ := os.Getwd() 342 newPath := path.Join(dir, "node_modules", ".bin") 343 oldPath := os.Getenv("PATH") 344 if len(oldPath) > 0 { 345 newPath = newPath + ":" + oldPath 346 } 347 utils.SetEnv([]string{"PATH=" + newPath}) 348 return nil 349 } 350 351 func getMtarName(config mtaBuildOptions, mtaYamlFile string, utils mtaBuildUtils) (string, bool, error) { 352 353 mtarName := config.MtarName 354 isMtarNativelySuffixed := false 355 if len(mtarName) == 0 { 356 357 log.Entry().Debugf("mtar name not provided via config. Extracting from file \"%s\"", mtaYamlFile) 358 359 mtaID, err := getMtaID(mtaYamlFile, utils) 360 361 if err != nil { 362 log.SetErrorCategory(log.ErrorConfiguration) 363 return "", isMtarNativelySuffixed, err 364 } 365 366 if len(mtaID) == 0 { 367 log.SetErrorCategory(log.ErrorConfiguration) 368 return "", isMtarNativelySuffixed, fmt.Errorf("Invalid mtar ID. Was empty") 369 } 370 371 log.Entry().Debugf("mtar name extracted from file \"%s\": \"%s\"", mtaYamlFile, mtaID) 372 373 // there can be cases where the mtaId itself has the value com.myComapany.mtar , adding an extra .mtar causes .mtar.mtar 374 if !strings.HasSuffix(mtaID, ".mtar") { 375 mtarName = mtaID + ".mtar" 376 } else { 377 isMtarNativelySuffixed = true 378 mtarName = mtaID 379 } 380 381 } 382 383 return mtarName, isMtarNativelySuffixed, nil 384 385 } 386 387 func setTimeStamp(mtaYamlFile string, utils mtaBuildUtils) error { 388 389 mtaYaml, err := utils.FileRead(mtaYamlFile) 390 if err != nil { 391 return err 392 } 393 394 mtaYamlStr := string(mtaYaml) 395 396 timestampVar := "${timestamp}" 397 if strings.Contains(mtaYamlStr, timestampVar) { 398 399 if err := utils.FileWrite(mtaYamlFile, []byte(strings.ReplaceAll(mtaYamlStr, timestampVar, getTimestamp())), 0644); err != nil { 400 log.SetErrorCategory(log.ErrorConfiguration) 401 return err 402 } 403 log.Entry().Infof("Timestamp replaced in \"%s\"", mtaYamlFile) 404 } else { 405 log.Entry().Infof("No timestamp contained in \"%s\". File has not been modified.", mtaYamlFile) 406 } 407 408 return nil 409 } 410 411 func getTimestamp() string { 412 t := time.Now() 413 return fmt.Sprintf("%d%02d%02d%02d%02d%02d", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second()) 414 } 415 416 func createMtaYamlFile(mtaYamlFile, applicationName string, utils mtaBuildUtils) error { 417 418 log.Entry().Infof("\"%s\" file not found in project sources", mtaYamlFile) 419 420 if len(applicationName) == 0 { 421 return fmt.Errorf("'%[1]s' not found in project sources and 'applicationName' not provided as parameter - cannot generate '%[1]s' file", mtaYamlFile) 422 } 423 424 packageFileExists, err := utils.FileExists("package.json") 425 if !packageFileExists { 426 return fmt.Errorf("package.json file does not exist") 427 } 428 429 var result map[string]interface{} 430 pContent, err := utils.FileRead("package.json") 431 if err != nil { 432 return err 433 } 434 json.Unmarshal(pContent, &result) 435 436 version, ok := result["version"].(string) 437 if !ok { 438 return fmt.Errorf("Version not found in \"package.json\" (or wrong type)") 439 } 440 441 name, ok := result["name"].(string) 442 if !ok { 443 return fmt.Errorf("Name not found in \"package.json\" (or wrong type)") 444 } 445 446 mtaConfig, err := generateMta(name, applicationName, version) 447 if err != nil { 448 return err 449 } 450 451 if err := utils.FileWrite(mtaYamlFile, []byte(mtaConfig), 0644); err != nil { 452 return fmt.Errorf("failed to write %v: %w", mtaYamlFile, err) 453 } 454 log.Entry().Infof("\"%s\" created.", mtaYamlFile) 455 456 return nil 457 } 458 459 func handleSettingsFiles(config mtaBuildOptions, utils mtaBuildUtils) error { 460 return utils.DownloadAndCopySettingsFiles(config.GlobalSettingsFile, config.ProjectSettingsFile) 461 } 462 463 func generateMta(id, applicationName, version string) (string, error) { 464 465 if len(id) == 0 { 466 return "", fmt.Errorf("Generating mta file: ID not provided") 467 } 468 if len(applicationName) == 0 { 469 return "", fmt.Errorf("Generating mta file: ApplicationName not provided") 470 } 471 if len(version) == 0 { 472 return "", fmt.Errorf("Generating mta file: Version not provided") 473 } 474 475 tmpl, e := template.New("mta.yaml").Parse(templateMtaYml) 476 if e != nil { 477 return "", e 478 } 479 480 type properties struct { 481 ID string 482 ApplicationName string 483 Version string 484 } 485 486 props := properties{ID: id, ApplicationName: applicationName, Version: version} 487 488 var script bytes.Buffer 489 if err := tmpl.Execute(&script, props); err != nil { 490 log.Entry().Warningf("failed to execute template: %v", err) 491 } 492 return script.String(), nil 493 } 494 495 func getMtaID(mtaYamlFile string, utils mtaBuildUtils) (string, error) { 496 497 var result map[string]interface{} 498 p, err := utils.FileRead(mtaYamlFile) 499 if err != nil { 500 return "", err 501 } 502 err = yaml.Unmarshal(p, &result) 503 if err != nil { 504 return "", err 505 } 506 507 id, ok := result["ID"].(string) 508 if !ok || len(id) == 0 { 509 return "", fmt.Errorf("Id not found in mta yaml file (or wrong type)") 510 } 511 512 return id, nil 513 } 514 515 // the "source" path locates the project's root 516 func getSourcePath(config mtaBuildOptions) string { 517 path := config.Source 518 if path == "" { 519 path = "./" 520 } 521 return filepath.FromSlash(path) 522 } 523 524 // target defines a subfolder of the project's root 525 func getTargetPath(config mtaBuildOptions) string { 526 path := config.Target 527 if path == "" { 528 path = "./" 529 } 530 return filepath.FromSlash(path) 531 } 532 533 // the "mtar" path resides below the project's root 534 // path=<config.source>/<config.target>/<mtarname> 535 func getMtarFileRoot(config mtaBuildOptions) string { 536 sourcePath := getSourcePath(config) 537 targetPath := getTargetPath(config) 538 539 return filepath.FromSlash(filepath.Join(sourcePath, targetPath)) 540 } 541 542 func getMtarFilePath(config mtaBuildOptions, mtarName string) string { 543 root := getMtarFileRoot(config) 544 545 if root == "" || root == filepath.FromSlash("./") { 546 return mtarName 547 } 548 549 return filepath.FromSlash(filepath.Join(root, mtarName)) 550 } 551 552 func getAbsPath(path string) string { 553 abspath, err := filepath.Abs(path) 554 // ignore error, pass customers path value in case of trouble 555 if err != nil { 556 abspath = path 557 } 558 return filepath.FromSlash(abspath) 559 }