1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-14 11:03:09 +02:00
sap-jenkins-library/pkg/tms/tmsUtils.go
gerstneralex f5c33d51bb
Tms export (#4160)
* Change parameter type of nodeExtDescriptorMapping

(cherry picked from commit ca7ce0485a)

* Remove usage of the depricated ioutil package

(cherry picked from commit 9821915b33)

* Fix cmd failure if neither git/commitId nor customDescription are
provided

(cherry picked from commit c362681e45)

* Fix unit test

(cherry picked from commit 53a90aabb5)

* Step metadata, step code generation

* change type of nodeExtDescriptorMapping for export

* Refactoring and export implementation

* integration test

* Add export step

* Integration test

* format

* discard piper.go

* Review related changes

* restore piper.go

* remove unused method

* Extend documentation

* Add parameter useGoStep to tmsUpload.groovy

* Regenerate steps

* Rename function

* refactor constants

* Add error path tests

* Move some code to tms package

* Move more code to tms

* Combine tmsUpload, tmsUtils

* Add groovy wrapper

* add parameters to groovy step

* add import

* jenkinsUtils instance

* comment namedUser logic in groovy

* namedUser param

* remove logic for namedUser param

* Remove TMS integration tests

* discard changes in tmsUpload.groovy

* Remove parameters

* Restore parameters

* Change type of NodeExtDescriptorMapping to map[string]interface{}

* tmsUpload: Change type of NodeExtDescriptorMapping to map

* Resolve ioutil deprecation

* Review related changes

* Formatting

* Review related improvements

* Add tmsUtils test

* Formatting tmsUtils_test

* Remove parameters from groovy wrapper

* Remove tmsUtils_test

* Add TMS steps to fieldRelatedWhitelist

* Add integration test

* Add test to github_actions_integration_test_list.yml

* Move test helper method

* Step documentation placeholder

* Remove parameter StashContent

* Restore cmd/integrationArtifactTransport.go

---------

Co-authored-by: Oliver Feldmann <oliver.feldmann@sap.com>
2023-03-27 16:55:29 +02:00

359 lines
12 KiB
Go

package tms
import (
"encoding/json"
"fmt"
"net/url"
"sort"
"strconv"
"github.com/SAP/jenkins-library/pkg/command"
piperHttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/piperutils"
"github.com/ghodss/yaml"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
type TmsUtils interface {
command.ExecRunner
FileExists(filename string) (bool, error)
FileRead(path string) ([]byte, error)
}
type uaa struct {
Url string `json:"url"`
ClientId string `json:"clientid"`
ClientSecret string `json:"clientsecret"`
}
type serviceKey struct {
Uaa uaa `json:"uaa"`
Uri string `json:"uri"`
}
type CommunicationInstance struct {
tmsUrl string
uaaUrl string
clientId string
clientSecret string
httpClient piperHttp.Uploader
logger *logrus.Entry
isVerbose bool
}
type Node struct {
Id int64 `json:"id"`
Name string `json:"name"`
}
type nodes struct {
Nodes []Node `json:"nodes"`
}
type MtaExtDescriptor struct {
Id int64 `json:"id"`
Description string `json:"description"`
MtaId string `json:"mtaId"`
MtaExtId string `json:"mtaExtId"`
MtaVersion string `json:"mtaVersion"`
LastChangedAt string `json:"lastChangedAt"`
}
type mtaExtDescriptors struct {
MtaExtDescriptors []MtaExtDescriptor `json:"mtaExtDescriptors"`
}
type FileInfo struct {
Id int64 `json:"fileId"`
Name string `json:"fileName"`
}
type NodeUploadResponseEntity struct {
TransportRequestId int64 `json:"transportRequestId"`
TransportRequestDescription string `json:"transportRequestDescription"`
QueueEntries []QueueEntry `json:"queueEntries"`
}
type QueueEntry struct {
Id int64 `json:"queueId"`
NodeId int64 `json:"nodeId"`
NodeName string `json:"nodeName"`
}
type NodeUploadRequestEntity struct {
ContentType string `json:"contentType"`
StorageType string `json:"storageType"`
NodeName string `json:"nodeName"`
Description string `json:"description"`
NamedUser string `json:"namedUser"`
Entries []Entry `json:"entries"`
}
type Entry struct {
Uri string `json:"uri"`
}
type CommunicationInterface interface {
GetNodes() ([]Node, error)
GetMtaExtDescriptor(nodeId int64, mtaId, mtaVersion string) (MtaExtDescriptor, error)
UpdateMtaExtDescriptor(nodeId, idOfMtaExtDescriptor int64, file, mtaVersion, description, namedUser string) (MtaExtDescriptor, error)
UploadMtaExtDescriptorToNode(nodeId int64, file, mtaVersion, description, namedUser string) (MtaExtDescriptor, error)
UploadFile(file, namedUser string) (FileInfo, error)
UploadFileToNode(nodeName, fileId, description, namedUser string) (NodeUploadResponseEntity, error)
ExportFileToNode(nodeName, fileId, description, namedUser string) (NodeUploadResponseEntity, error)
}
type Options struct {
TmsServiceKey string `json:"tmsServiceKey,omitempty"`
CustomDescription string `json:"customDescription,omitempty"`
NamedUser string `json:"namedUser,omitempty"`
NodeName string `json:"nodeName,omitempty"`
MtaPath string `json:"mtaPath,omitempty"`
MtaVersion string `json:"mtaVersion,omitempty"`
NodeExtDescriptorMapping map[string]interface{} `json:"nodeExtDescriptorMapping,omitempty"`
Proxy string `json:"proxy,omitempty"`
StashContent []string `json:"stashContent,omitempty"`
Verbose bool
}
type tmsUtilsBundle struct {
*command.Command
*piperutils.Files
}
const DEFAULT_TR_DESCRIPTION = "Created by Piper"
func NewTmsUtils() TmsUtils {
utils := tmsUtilsBundle{
Command: &command.Command{},
Files: &piperutils.Files{},
}
// Reroute command output to logging framework
utils.Stdout(log.Writer())
utils.Stderr(log.Writer())
return &utils
}
func unmarshalServiceKey(serviceKeyJson string) (serviceKey serviceKey, err error) {
err = json.Unmarshal([]byte(serviceKeyJson), &serviceKey)
if err != nil {
return
}
return
}
func FormNodeIdExtDescriptorMappingWithValidation(utils TmsUtils, nodeNameExtDescriptorMapping map[string]interface{}, nodes []Node, mtaYamlMap map[string]interface{}, mtaVersion string) (map[int64]string, error) {
var wrongMtaIdExtDescriptors []string
var wrongExtDescriptorPaths []string
var wrongNodeNames []string
var errorMessage string
nodeIdExtDescriptorMapping := make(map[int64]string)
for nodeName, mappedValue := range nodeNameExtDescriptorMapping {
mappedValueString := fmt.Sprintf("%v", mappedValue)
exists, _ := utils.FileExists(mappedValueString)
if exists {
extDescriptorMap, errGetYamlAsMap := GetYamlAsMap(utils, mappedValueString)
if errGetYamlAsMap == nil {
if fmt.Sprintf("%v", mtaYamlMap["ID"]) != fmt.Sprintf("%v", extDescriptorMap["extends"]) {
wrongMtaIdExtDescriptors = append(wrongMtaIdExtDescriptors, mappedValueString)
}
} else {
wrappedErr := errors.Wrapf(errGetYamlAsMap, "tried to parse %v as yaml, but got an error", mappedValueString)
errorMessage += fmt.Sprintf("%v\n", wrappedErr)
}
} else {
wrongExtDescriptorPaths = append(wrongExtDescriptorPaths, mappedValueString)
}
isNodeFound := false
for _, node := range nodes {
if node.Name == nodeName {
nodeIdExtDescriptorMapping[node.Id] = mappedValueString
isNodeFound = true
break
}
}
if !isNodeFound {
wrongNodeNames = append(wrongNodeNames, nodeName)
}
}
if mtaVersion != "*" && mtaVersion != mtaYamlMap["version"] {
errorMessage += "parameter 'mtaVersion' does not match the MTA version in mta.yaml\n"
}
if len(wrongMtaIdExtDescriptors) > 0 || len(wrongExtDescriptorPaths) > 0 || len(wrongNodeNames) > 0 {
if len(wrongMtaIdExtDescriptors) > 0 {
sort.Strings(wrongMtaIdExtDescriptors)
errorMessage += fmt.Sprintf("parameter 'extends' in MTA extension descriptor files %v is not the same as MTA ID or is missing at all\n", wrongMtaIdExtDescriptors)
}
if len(wrongExtDescriptorPaths) > 0 {
sort.Strings(wrongExtDescriptorPaths)
errorMessage += fmt.Sprintf("MTA extension descriptor files %v do not exist\n", wrongExtDescriptorPaths)
}
if len(wrongNodeNames) > 0 {
sort.Strings(wrongNodeNames)
errorMessage += fmt.Sprintf("nodes %v do not exist. Please check node names provided in 'nodeExtDescriptorMapping' parameter or create these nodes\n", wrongNodeNames)
}
}
if errorMessage == "" {
return nodeIdExtDescriptorMapping, nil
} else {
return nil, errors.New(errorMessage)
}
}
func GetYamlAsMap(utils TmsUtils, yamlPath string) (map[string]interface{}, error) {
var result map[string]interface{}
bytes, err := utils.FileRead(yamlPath)
if err != nil {
return result, err
}
err = yaml.Unmarshal(bytes, &result)
if err != nil {
return result, err
}
return result, nil
}
func SetupCommunication(config Options) (communicationInstance CommunicationInterface) {
client := &piperHttp.Client{}
proxy := config.Proxy
options := piperHttp.ClientOptions{}
if proxy != "" {
transportProxy, err := url.Parse(proxy)
if err != nil {
log.Entry().WithError(err).Fatalf("Failed to parse proxy string %v into a URL structure", proxy)
}
options = piperHttp.ClientOptions{TransportProxy: transportProxy}
client.SetOptions(options)
if config.Verbose {
log.Entry().Infof("HTTP client instructed to use %v proxy", proxy)
}
}
serviceKey, err := unmarshalServiceKey(config.TmsServiceKey)
if err != nil {
log.Entry().WithError(err).Fatal("Failed to unmarshal TMS service key")
}
log.RegisterSecret(serviceKey.Uaa.ClientSecret)
if config.Verbose {
log.Entry().Info("Will be used for communication:")
log.Entry().Infof("- client id: %v", serviceKey.Uaa.ClientId)
log.Entry().Infof("- TMS URL: %v", serviceKey.Uri)
log.Entry().Infof("- UAA URL: %v", serviceKey.Uaa.Url)
}
commuInstance, err := NewCommunicationInstance(client, serviceKey.Uri, serviceKey.Uaa.Url, serviceKey.Uaa.ClientId, serviceKey.Uaa.ClientSecret, config.Verbose, options)
if err != nil {
log.Entry().WithError(err).Fatal("Failed to prepare client for talking with TMS")
}
return commuInstance
}
func UploadDescriptors(config Options, communicationInstance CommunicationInterface, utils TmsUtils) error {
description := config.CustomDescription
namedUser := config.NamedUser
nodeName := config.NodeName
mtaVersion := config.MtaVersion
nodeNameExtDescriptorMapping := config.NodeExtDescriptorMapping
mtaPath := config.MtaPath
if config.Verbose {
log.Entry().Info("The step will use the following values:")
log.Entry().Infof("- description: %v", config.CustomDescription)
if len(nodeNameExtDescriptorMapping) > 0 {
log.Entry().Infof("- mapping between node names and MTA extension descriptor file paths: %v", nodeNameExtDescriptorMapping)
}
log.Entry().Infof("- MTA path: %v", mtaPath)
log.Entry().Infof("- MTA version: %v", mtaVersion)
if namedUser != "" {
log.Entry().Infof("- named user: %v", namedUser)
}
log.Entry().Infof("- node name: %v", nodeName)
}
if len(nodeNameExtDescriptorMapping) > 0 {
nodes, errGetNodes := communicationInstance.GetNodes()
if errGetNodes != nil {
log.SetErrorCategory(log.ErrorService)
return fmt.Errorf("failed to get nodes: %w", errGetNodes)
}
mtaYamlMap, errGetMtaYamlAsMap := GetYamlAsMap(utils, "mta.yaml")
if errGetMtaYamlAsMap != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return fmt.Errorf("failed to get mta.yaml as map: %w", errGetMtaYamlAsMap)
}
_, isIdParameterInMap := mtaYamlMap["ID"]
_, isVersionParameterInMap := mtaYamlMap["version"]
if !isIdParameterInMap || !isVersionParameterInMap {
var errorMessage string
if !isIdParameterInMap {
errorMessage += "parameter 'ID' is not found in mta.yaml\n"
}
if !isVersionParameterInMap {
errorMessage += "parameter 'version' is not found in mta.yaml\n"
}
log.SetErrorCategory(log.ErrorConfiguration)
return errors.New(errorMessage)
}
// validate the whole mapping and then throw errors together, so that user can get them after a single pipeline run
nodeIdExtDescriptorMapping, errGetNodeIdExtDescriptorMapping := FormNodeIdExtDescriptorMappingWithValidation(utils, nodeNameExtDescriptorMapping, nodes, mtaYamlMap, mtaVersion)
if errGetNodeIdExtDescriptorMapping != nil {
log.SetErrorCategory(log.ErrorConfiguration)
return errGetNodeIdExtDescriptorMapping
}
for nodeId, mtaExtDescriptorPath := range nodeIdExtDescriptorMapping {
obtainedMtaExtDescriptor, errGetMtaExtDescriptor := communicationInstance.GetMtaExtDescriptor(nodeId, fmt.Sprintf("%v", mtaYamlMap["ID"]), mtaVersion)
if errGetMtaExtDescriptor != nil {
log.SetErrorCategory(log.ErrorService)
return fmt.Errorf("failed to get MTA extension descriptor: %w", errGetMtaExtDescriptor)
}
if obtainedMtaExtDescriptor != (MtaExtDescriptor{}) {
_, errUpdateMtaExtDescriptor := communicationInstance.UpdateMtaExtDescriptor(nodeId, obtainedMtaExtDescriptor.Id, mtaExtDescriptorPath, mtaVersion, description, namedUser)
if errUpdateMtaExtDescriptor != nil {
log.SetErrorCategory(log.ErrorService)
return fmt.Errorf("failed to update MTA extension descriptor: %w", errUpdateMtaExtDescriptor)
}
} else {
_, errUploadMtaExtDescriptor := communicationInstance.UploadMtaExtDescriptorToNode(nodeId, mtaExtDescriptorPath, mtaVersion, description, namedUser)
if errUploadMtaExtDescriptor != nil {
log.SetErrorCategory(log.ErrorService)
return fmt.Errorf("failed to upload MTA extension descriptor to node: %w", errUploadMtaExtDescriptor)
}
}
}
}
return nil
}
func UploadFile(config Options, communicationInstance CommunicationInterface, utils TmsUtils) (string, error) {
mtaPath := config.MtaPath
exists, _ := utils.FileExists(mtaPath)
if !exists {
log.SetErrorCategory(log.ErrorConfiguration)
return "", fmt.Errorf("mta file %s not found", mtaPath)
}
fileInfo, errUploadFile := communicationInstance.UploadFile(mtaPath, config.NamedUser)
if errUploadFile != nil {
log.SetErrorCategory(log.ErrorService)
return "", fmt.Errorf("failed to upload file: %w", errUploadFile)
}
fileId := strconv.FormatInt(fileInfo.Id, 10)
return fileId, nil
}