github.com/jaylevin/jenkins-library@v1.230.4/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: 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 Files: &piperutils.Files{}, 125 Client: &piperhttp.Client{}, 126 } 127 utils.Stdout(log.Writer()) 128 utils.Stderr(log.Writer()) 129 return &utils 130 } 131 132 func mtaBuild(config mtaBuildOptions, 133 telemetryData *telemetry.CustomData, 134 commonPipelineEnvironment *mtaBuildCommonPipelineEnvironment) { 135 log.Entry().Debugf("Launching mta build") 136 utils := newMtaBuildUtilsBundle() 137 138 err := runMtaBuild(config, commonPipelineEnvironment, utils) 139 if err != nil { 140 log.Entry(). 141 WithError(err). 142 Fatal("failed to execute mta build") 143 } 144 } 145 146 func runMtaBuild(config mtaBuildOptions, 147 commonPipelineEnvironment *mtaBuildCommonPipelineEnvironment, 148 utils mtaBuildUtils) error { 149 150 var err error 151 152 err = handleSettingsFiles(config, utils) 153 if err != nil { 154 return err 155 } 156 157 err = handleActiveProfileUpdate(config, utils) 158 if err != nil { 159 return err 160 } 161 162 err = utils.SetNpmRegistries(config.DefaultNpmRegistry) 163 164 mtaYamlFile := filepath.Join(getSourcePath(config), "mta.yaml") 165 mtaYamlFileExists, err := utils.FileExists(mtaYamlFile) 166 167 if err != nil { 168 return err 169 } 170 171 if !mtaYamlFileExists { 172 173 if err = createMtaYamlFile(mtaYamlFile, config.ApplicationName, utils); err != nil { 174 return err 175 } 176 177 } else { 178 log.Entry().Infof("\"%s\" file found in project sources", mtaYamlFile) 179 } 180 181 if err = setTimeStamp(mtaYamlFile, utils); err != nil { 182 return err 183 } 184 185 mtarName, err := getMtarName(config, mtaYamlFile, utils) 186 187 if err != nil { 188 return err 189 } 190 191 var call []string 192 193 platform, err := ValueOfBuildTarget(config.Platform) 194 if err != nil { 195 log.SetErrorCategory(log.ErrorConfiguration) 196 return err 197 } 198 199 call = append(call, "mbt", "build", "--mtar", mtarName, "--platform", platform.String()) 200 if len(config.Extensions) != 0 { 201 call = append(call, fmt.Sprintf("--extensions=%s", config.Extensions)) 202 } 203 204 call = append(call, "--source", getSourcePath(config)) 205 call = append(call, "--target", getAbsPath(getMtarFileRoot(config))) 206 207 if config.Jobs > 0 { 208 call = append(call, "--mode=verbose") 209 call = append(call, "--jobs="+strconv.Itoa(config.Jobs)) 210 } 211 212 if err = addNpmBinToPath(utils); err != nil { 213 return err 214 } 215 216 if len(config.M2Path) > 0 { 217 absolutePath, err := utils.Abs(config.M2Path) 218 if err != nil { 219 return err 220 } 221 utils.AppendEnv([]string{"MAVEN_OPTS=-Dmaven.repo.local=" + absolutePath}) 222 } 223 224 log.Entry().Infof("Executing mta build call: \"%s\"", strings.Join(call, " ")) 225 226 if err := utils.RunExecutable(call[0], call[1:]...); err != nil { 227 log.SetErrorCategory(log.ErrorBuild) 228 return err 229 } 230 231 log.Entry().Debugf("creating build settings information...") 232 stepName := "mtaBuild" 233 dockerImage, err := GetDockerImageValue(stepName) 234 if err != nil { 235 return err 236 } 237 238 mtaConfig := buildsettings.BuildOptions{ 239 Profiles: config.Profiles, 240 GlobalSettingsFile: config.GlobalSettingsFile, 241 Publish: config.Publish, 242 BuildSettingsInfo: config.BuildSettingsInfo, 243 DefaultNpmRegistry: config.DefaultNpmRegistry, 244 DockerImage: dockerImage, 245 } 246 buildSettingsInfo, err := buildsettings.CreateBuildSettingsInfo(&mtaConfig, stepName) 247 if err != nil { 248 log.Entry().Warnf("failed to create build settings info: %v", err) 249 } 250 commonPipelineEnvironment.custom.buildSettingsInfo = buildSettingsInfo 251 252 commonPipelineEnvironment.mtarFilePath = filepath.ToSlash(getMtarFilePath(config, mtarName)) 253 commonPipelineEnvironment.custom.mtaBuildToolDesc = filepath.ToSlash(mtaYamlFile) 254 255 if config.InstallArtifacts { 256 // install maven artifacts in local maven repo because `mbt build` executes `mvn package -B` 257 err = installMavenArtifacts(utils, config) 258 if err != nil { 259 return err 260 } 261 // mta-builder executes 'npm install --production', therefore we need 'npm ci/install' to install the dev-dependencies 262 err = utils.InstallAllDependencies(config.DefaultNpmRegistry) 263 if err != nil { 264 return err 265 } 266 } 267 268 if config.Publish { 269 log.Entry().Infof("publish detected") 270 if (len(config.MtaDeploymentRepositoryPassword) > 0) && (len(config.MtaDeploymentRepositoryUser) > 0) && 271 (len(config.MtaDeploymentRepositoryURL) > 0) { 272 if (len(config.MtarGroup) > 0) && (len(config.Version) > 0) { 273 httpClient := &piperhttp.Client{} 274 275 credentialsEncoded := "Basic " + base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", config.MtaDeploymentRepositoryUser, config.MtaDeploymentRepositoryPassword))) 276 headers := http.Header{} 277 headers.Add("Authorization", credentialsEncoded) 278 279 config.MtarGroup = strings.ReplaceAll(config.MtarGroup, ".", "/") 280 281 mtarArtifactName := mtarName 282 283 mtarArtifactName = strings.ReplaceAll(mtarArtifactName, ".mtar", "") 284 285 config.MtaDeploymentRepositoryURL += config.MtarGroup + "/" + mtarArtifactName + "/" + config.Version + "/" + fmt.Sprintf("%v-%v.%v", mtarArtifactName, config.Version, "mtar") 286 287 commonPipelineEnvironment.custom.mtarPublishedURL = config.MtaDeploymentRepositoryURL 288 289 log.Entry().Infof("pushing mtar artifact to repository : %s", config.MtaDeploymentRepositoryURL) 290 291 data, err := os.Open(getMtarFilePath(config, mtarName)) 292 if err != nil { 293 return errors.Wrap(err, "failed to open mtar archive for upload") 294 } 295 _, httpErr := httpClient.SendRequest("PUT", config.MtaDeploymentRepositoryURL, data, headers, nil) 296 297 if httpErr != nil { 298 return errors.Wrap(err, "failed to upload mtar to repository") 299 } 300 } else { 301 return errors.New("mtarGroup, version not found and must be present") 302 303 } 304 305 } else { 306 return errors.New("mtaDeploymentRepositoryUser, mtaDeploymentRepositoryPassword and mtaDeploymentRepositoryURL not found , must be present") 307 } 308 } else { 309 log.Entry().Infof("no publish detected, skipping upload of mtar artifact") 310 } 311 return err 312 } 313 314 func handleActiveProfileUpdate(config mtaBuildOptions, utils mtaBuildUtils) error { 315 if len(config.Profiles) > 0 { 316 return maven.UpdateActiveProfileInSettingsXML(config.Profiles, utils) 317 } 318 return nil 319 } 320 321 func installMavenArtifacts(utils mtaBuildUtils, config mtaBuildOptions) error { 322 pomXMLExists, err := utils.FileExists("pom.xml") 323 if err != nil { 324 return err 325 } 326 if pomXMLExists { 327 err = maven.InstallMavenArtifacts(&maven.EvaluateOptions{M2Path: config.M2Path}, utils) 328 if err != nil { 329 return err 330 } 331 } 332 return nil 333 } 334 335 func addNpmBinToPath(utils mtaBuildUtils) error { 336 dir, _ := os.Getwd() 337 newPath := path.Join(dir, "node_modules", ".bin") 338 oldPath := os.Getenv("PATH") 339 if len(oldPath) > 0 { 340 newPath = newPath + ":" + oldPath 341 } 342 utils.SetEnv([]string{"PATH=" + newPath}) 343 return nil 344 } 345 346 func getMtarName(config mtaBuildOptions, mtaYamlFile string, utils mtaBuildUtils) (string, error) { 347 348 mtarName := config.MtarName 349 if len(mtarName) == 0 { 350 351 log.Entry().Debugf("mtar name not provided via config. Extracting from file \"%s\"", mtaYamlFile) 352 353 mtaID, err := getMtaID(mtaYamlFile, utils) 354 355 if err != nil { 356 log.SetErrorCategory(log.ErrorConfiguration) 357 return "", err 358 } 359 360 if len(mtaID) == 0 { 361 log.SetErrorCategory(log.ErrorConfiguration) 362 return "", fmt.Errorf("Invalid mtar ID. Was empty") 363 } 364 365 log.Entry().Debugf("mtar name extracted from file \"%s\": \"%s\"", mtaYamlFile, mtaID) 366 367 mtarName = mtaID + ".mtar" 368 } 369 370 return mtarName, nil 371 372 } 373 374 func setTimeStamp(mtaYamlFile string, utils mtaBuildUtils) error { 375 376 mtaYaml, err := utils.FileRead(mtaYamlFile) 377 if err != nil { 378 return err 379 } 380 381 mtaYamlStr := string(mtaYaml) 382 383 timestampVar := "${timestamp}" 384 if strings.Contains(mtaYamlStr, timestampVar) { 385 386 if err := utils.FileWrite(mtaYamlFile, []byte(strings.ReplaceAll(mtaYamlStr, timestampVar, getTimestamp())), 0644); err != nil { 387 log.SetErrorCategory(log.ErrorConfiguration) 388 return err 389 } 390 log.Entry().Infof("Timestamp replaced in \"%s\"", mtaYamlFile) 391 } else { 392 log.Entry().Infof("No timestamp contained in \"%s\". File has not been modified.", mtaYamlFile) 393 } 394 395 return nil 396 } 397 398 func getTimestamp() string { 399 t := time.Now() 400 return fmt.Sprintf("%d%02d%02d%02d%02d%02d", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second()) 401 } 402 403 func createMtaYamlFile(mtaYamlFile, applicationName string, utils mtaBuildUtils) error { 404 405 log.Entry().Debugf("mta yaml file not found in project sources.") 406 407 if len(applicationName) == 0 { 408 return fmt.Errorf("'%[1]s' not found in project sources and 'applicationName' not provided as parameter - cannot generate '%[1]s' file", mtaYamlFile) 409 } 410 411 packageFileExists, err := utils.FileExists("package.json") 412 if !packageFileExists { 413 return fmt.Errorf("package.json file does not exist") 414 } 415 416 var result map[string]interface{} 417 pContent, err := utils.FileRead("package.json") 418 if err != nil { 419 return err 420 } 421 json.Unmarshal(pContent, &result) 422 423 version, ok := result["version"].(string) 424 if !ok { 425 return fmt.Errorf("Version not found in \"package.json\" (or wrong type)") 426 } 427 428 name, ok := result["name"].(string) 429 if !ok { 430 return fmt.Errorf("Name not found in \"package.json\" (or wrong type)") 431 } 432 433 mtaConfig, err := generateMta(name, applicationName, version) 434 if err != nil { 435 return err 436 } 437 438 utils.FileWrite(mtaYamlFile, []byte(mtaConfig), 0644) 439 log.Entry().Infof("\"%s\" created.", mtaYamlFile) 440 441 return nil 442 } 443 444 func handleSettingsFiles(config mtaBuildOptions, utils mtaBuildUtils) error { 445 return utils.DownloadAndCopySettingsFiles(config.GlobalSettingsFile, config.ProjectSettingsFile) 446 } 447 448 func generateMta(id, applicationName, version string) (string, error) { 449 450 if len(id) == 0 { 451 return "", fmt.Errorf("Generating mta file: ID not provided") 452 } 453 if len(applicationName) == 0 { 454 return "", fmt.Errorf("Generating mta file: ApplicationName not provided") 455 } 456 if len(version) == 0 { 457 return "", fmt.Errorf("Generating mta file: Version not provided") 458 } 459 460 tmpl, e := template.New("mta.yaml").Parse(templateMtaYml) 461 if e != nil { 462 return "", e 463 } 464 465 type properties struct { 466 ID string 467 ApplicationName string 468 Version string 469 } 470 471 props := properties{ID: id, ApplicationName: applicationName, Version: version} 472 473 var script bytes.Buffer 474 tmpl.Execute(&script, props) 475 return script.String(), nil 476 } 477 478 func getMtaID(mtaYamlFile string, utils mtaBuildUtils) (string, error) { 479 480 var result map[string]interface{} 481 p, err := utils.FileRead(mtaYamlFile) 482 if err != nil { 483 return "", err 484 } 485 err = yaml.Unmarshal(p, &result) 486 if err != nil { 487 return "", err 488 } 489 490 id, ok := result["ID"].(string) 491 if !ok || len(id) == 0 { 492 return "", fmt.Errorf("Id not found in mta yaml file (or wrong type)") 493 } 494 495 return id, nil 496 } 497 498 // the "source" path locates the project's root 499 func getSourcePath(config mtaBuildOptions) string { 500 path := config.Source 501 if path == "" { 502 path = "./" 503 } 504 return filepath.FromSlash(path) 505 } 506 507 // target defines a subfolder of the project's root 508 func getTargetPath(config mtaBuildOptions) string { 509 path := config.Target 510 if path == "" { 511 path = "./" 512 } 513 return filepath.FromSlash(path) 514 } 515 516 // the "mtar" path resides below the project's root 517 // path=<config.source>/<config.target>/<mtarname> 518 func getMtarFileRoot(config mtaBuildOptions) string { 519 sourcePath := getSourcePath(config) 520 targetPath := getTargetPath(config) 521 522 return filepath.FromSlash(filepath.Join(sourcePath, targetPath)) 523 } 524 525 func getMtarFilePath(config mtaBuildOptions, mtarName string) string { 526 root := getMtarFileRoot(config) 527 528 if root == "" || root == filepath.FromSlash("./") { 529 return mtarName 530 } 531 532 return filepath.FromSlash(filepath.Join(root, mtarName)) 533 } 534 535 func getAbsPath(path string) string { 536 abspath, err := filepath.Abs(path) 537 // ignore error, pass customers path value in case of trouble 538 if err != nil { 539 abspath = path 540 } 541 return filepath.FromSlash(abspath) 542 }