github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/tms/tmsUtils.go (about)

     1  package tms
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/url"
     7  	"sort"
     8  
     9  	"github.com/SAP/jenkins-library/pkg/command"
    10  	piperHttp "github.com/SAP/jenkins-library/pkg/http"
    11  	"github.com/SAP/jenkins-library/pkg/log"
    12  	"github.com/SAP/jenkins-library/pkg/piperutils"
    13  	"github.com/ghodss/yaml"
    14  	"github.com/pkg/errors"
    15  	"github.com/sirupsen/logrus"
    16  )
    17  
    18  type TmsUtils interface {
    19  	command.ExecRunner
    20  	FileExists(filename string) (bool, error)
    21  	FileRead(path string) ([]byte, error)
    22  }
    23  
    24  type uaa struct {
    25  	Url          string `json:"url"`
    26  	ClientId     string `json:"clientid"`
    27  	ClientSecret string `json:"clientsecret"`
    28  }
    29  
    30  type serviceKey struct {
    31  	Uaa uaa    `json:"uaa"`
    32  	Uri string `json:"uri"`
    33  }
    34  
    35  type CommunicationInstance struct {
    36  	tmsUrl       string
    37  	uaaUrl       string
    38  	clientId     string
    39  	clientSecret string
    40  	httpClient   piperHttp.Uploader
    41  	logger       *logrus.Entry
    42  	isVerbose    bool
    43  }
    44  
    45  type Node struct {
    46  	Id   int64  `json:"id"`
    47  	Name string `json:"name"`
    48  }
    49  
    50  type nodes struct {
    51  	Nodes []Node `json:"nodes"`
    52  }
    53  
    54  type MtaExtDescriptor struct {
    55  	Id            int64  `json:"id"`
    56  	Description   string `json:"description"`
    57  	MtaId         string `json:"mtaId"`
    58  	MtaExtId      string `json:"mtaExtId"`
    59  	MtaVersion    string `json:"mtaVersion"`
    60  	LastChangedAt string `json:"lastChangedAt"`
    61  }
    62  
    63  type mtaExtDescriptors struct {
    64  	MtaExtDescriptors []MtaExtDescriptor `json:"mtaExtDescriptors"`
    65  }
    66  
    67  type FileInfo struct {
    68  	Id   int64  `json:"fileId"`
    69  	Name string `json:"fileName"`
    70  }
    71  
    72  type NodeUploadResponseEntity struct {
    73  	TransportRequestId          int64        `json:"transportRequestId"`
    74  	TransportRequestDescription string       `json:"transportRequestDescription"`
    75  	QueueEntries                []QueueEntry `json:"queueEntries"`
    76  }
    77  
    78  type QueueEntry struct {
    79  	Id       int64  `json:"queueId"`
    80  	NodeId   int64  `json:"nodeId"`
    81  	NodeName string `json:"nodeName"`
    82  }
    83  
    84  type NodeUploadRequestEntity struct {
    85  	ContentType string  `json:"contentType"`
    86  	StorageType string  `json:"storageType"`
    87  	NodeName    string  `json:"nodeName"`
    88  	Description string  `json:"description"`
    89  	NamedUser   string  `json:"namedUser"`
    90  	Entries     []Entry `json:"entries"`
    91  }
    92  
    93  type Entry struct {
    94  	Uri string `json:"uri"`
    95  }
    96  
    97  type CommunicationInterface interface {
    98  	GetNodes() ([]Node, error)
    99  	GetMtaExtDescriptor(nodeId int64, mtaId, mtaVersion string) (MtaExtDescriptor, error)
   100  	UpdateMtaExtDescriptor(nodeId, idOfMtaExtDescriptor int64, file, mtaVersion, description, namedUser string) (MtaExtDescriptor, error)
   101  	UploadMtaExtDescriptorToNode(nodeId int64, file, mtaVersion, description, namedUser string) (MtaExtDescriptor, error)
   102  	UploadFile(file, namedUser string) (FileInfo, error)
   103  	UploadFileToNode(fileInfo FileInfo, nodeName, description, namedUser string) (NodeUploadResponseEntity, error)
   104  	ExportFileToNode(fileInfo FileInfo, nodeName, description, namedUser string) (NodeUploadResponseEntity, error)
   105  }
   106  
   107  type Options struct {
   108  	TmsServiceKey            string                 `json:"tmsServiceKey,omitempty"`
   109  	CustomDescription        string                 `json:"customDescription,omitempty"`
   110  	NamedUser                string                 `json:"namedUser,omitempty"`
   111  	NodeName                 string                 `json:"nodeName,omitempty"`
   112  	MtaPath                  string                 `json:"mtaPath,omitempty"`
   113  	MtaVersion               string                 `json:"mtaVersion,omitempty"`
   114  	NodeExtDescriptorMapping map[string]interface{} `json:"nodeExtDescriptorMapping,omitempty"`
   115  	Proxy                    string                 `json:"proxy,omitempty"`
   116  	StashContent             []string               `json:"stashContent,omitempty"`
   117  	Verbose                  bool
   118  }
   119  
   120  type tmsUtilsBundle struct {
   121  	*command.Command
   122  	*piperutils.Files
   123  }
   124  
   125  const DEFAULT_TR_DESCRIPTION = "Created by Piper"
   126  
   127  func NewTmsUtils() TmsUtils {
   128  	utils := tmsUtilsBundle{
   129  		Command: &command.Command{},
   130  		Files:   &piperutils.Files{},
   131  	}
   132  	// Reroute command output to logging framework
   133  	utils.Stdout(log.Writer())
   134  	utils.Stderr(log.Writer())
   135  	return &utils
   136  }
   137  
   138  func unmarshalServiceKey(serviceKeyJson string) (serviceKey serviceKey, err error) {
   139  	err = json.Unmarshal([]byte(serviceKeyJson), &serviceKey)
   140  	if err != nil {
   141  		return
   142  	}
   143  	return
   144  }
   145  
   146  func FormNodeIdExtDescriptorMappingWithValidation(utils TmsUtils, nodeNameExtDescriptorMapping map[string]interface{}, nodes []Node, mtaYamlMap map[string]interface{}, mtaVersion string) (map[int64]string, error) {
   147  	var wrongMtaIdExtDescriptors []string
   148  	var wrongExtDescriptorPaths []string
   149  	var wrongNodeNames []string
   150  	var errorMessage string
   151  
   152  	nodeIdExtDescriptorMapping := make(map[int64]string)
   153  	for nodeName, mappedValue := range nodeNameExtDescriptorMapping {
   154  		mappedValueString := fmt.Sprintf("%v", mappedValue)
   155  		exists, _ := utils.FileExists(mappedValueString)
   156  		if exists {
   157  			extDescriptorMap, errGetYamlAsMap := GetYamlAsMap(utils, mappedValueString)
   158  			if errGetYamlAsMap == nil {
   159  				if fmt.Sprintf("%v", mtaYamlMap["ID"]) != fmt.Sprintf("%v", extDescriptorMap["extends"]) {
   160  					wrongMtaIdExtDescriptors = append(wrongMtaIdExtDescriptors, mappedValueString)
   161  				}
   162  			} else {
   163  				wrappedErr := errors.Wrapf(errGetYamlAsMap, "tried to parse %v as yaml, but got an error", mappedValueString)
   164  				errorMessage += fmt.Sprintf("%v\n", wrappedErr)
   165  			}
   166  		} else {
   167  			wrongExtDescriptorPaths = append(wrongExtDescriptorPaths, mappedValueString)
   168  		}
   169  
   170  		isNodeFound := false
   171  		for _, node := range nodes {
   172  			if node.Name == nodeName {
   173  				nodeIdExtDescriptorMapping[node.Id] = mappedValueString
   174  				isNodeFound = true
   175  				break
   176  			}
   177  		}
   178  		if !isNodeFound {
   179  			wrongNodeNames = append(wrongNodeNames, nodeName)
   180  		}
   181  	}
   182  
   183  	if mtaVersion != "*" && mtaVersion != mtaYamlMap["version"] {
   184  		errorMessage += "parameter 'mtaVersion' does not match the MTA version in mta.yaml\n"
   185  	}
   186  
   187  	if len(wrongMtaIdExtDescriptors) > 0 || len(wrongExtDescriptorPaths) > 0 || len(wrongNodeNames) > 0 {
   188  		if len(wrongMtaIdExtDescriptors) > 0 {
   189  			sort.Strings(wrongMtaIdExtDescriptors)
   190  			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)
   191  		}
   192  		if len(wrongExtDescriptorPaths) > 0 {
   193  			sort.Strings(wrongExtDescriptorPaths)
   194  			errorMessage += fmt.Sprintf("MTA extension descriptor files %v do not exist\n", wrongExtDescriptorPaths)
   195  		}
   196  		if len(wrongNodeNames) > 0 {
   197  			sort.Strings(wrongNodeNames)
   198  			errorMessage += fmt.Sprintf("nodes %v do not exist. Please check node names provided in 'nodeExtDescriptorMapping' parameter or create these nodes\n", wrongNodeNames)
   199  		}
   200  	}
   201  
   202  	if errorMessage == "" {
   203  		return nodeIdExtDescriptorMapping, nil
   204  	} else {
   205  		return nil, errors.New(errorMessage)
   206  	}
   207  }
   208  
   209  func GetYamlAsMap(utils TmsUtils, yamlPath string) (map[string]interface{}, error) {
   210  	var result map[string]interface{}
   211  	bytes, err := utils.FileRead(yamlPath)
   212  	if err != nil {
   213  		return result, err
   214  	}
   215  	err = yaml.Unmarshal(bytes, &result)
   216  	if err != nil {
   217  		return result, err
   218  	}
   219  
   220  	return result, nil
   221  }
   222  
   223  func SetupCommunication(config Options) (communicationInstance CommunicationInterface) {
   224  	client := &piperHttp.Client{}
   225  	proxy := config.Proxy
   226  	options := piperHttp.ClientOptions{}
   227  	if proxy != "" {
   228  		transportProxy, err := url.Parse(proxy)
   229  		if err != nil {
   230  			log.Entry().WithError(err).Fatalf("Failed to parse proxy string %v into a URL structure", proxy)
   231  		}
   232  
   233  		options = piperHttp.ClientOptions{TransportProxy: transportProxy}
   234  		client.SetOptions(options)
   235  		if config.Verbose {
   236  			log.Entry().Infof("HTTP client instructed to use %v proxy", proxy)
   237  		}
   238  	}
   239  
   240  	serviceKey, err := unmarshalServiceKey(config.TmsServiceKey)
   241  	if err != nil {
   242  		log.Entry().WithError(err).Fatal("Failed to unmarshal TMS service key")
   243  	}
   244  	log.RegisterSecret(serviceKey.Uaa.ClientSecret)
   245  
   246  	if config.Verbose {
   247  		log.Entry().Info("Will be used for communication:")
   248  		log.Entry().Infof("- client id: %v", serviceKey.Uaa.ClientId)
   249  		log.Entry().Infof("- TMS URL: %v", serviceKey.Uri)
   250  		log.Entry().Infof("- UAA URL: %v", serviceKey.Uaa.Url)
   251  	}
   252  
   253  	commuInstance, err := NewCommunicationInstance(client, serviceKey.Uri, serviceKey.Uaa.Url, serviceKey.Uaa.ClientId, serviceKey.Uaa.ClientSecret, config.Verbose, options)
   254  	if err != nil {
   255  		log.Entry().WithError(err).Fatal("Failed to prepare client for talking with TMS")
   256  	}
   257  	return commuInstance
   258  }
   259  
   260  func UploadDescriptors(config Options, communicationInstance CommunicationInterface, utils TmsUtils) error {
   261  	description := config.CustomDescription
   262  	namedUser := config.NamedUser
   263  	nodeName := config.NodeName
   264  	mtaVersion := config.MtaVersion
   265  	nodeNameExtDescriptorMapping := config.NodeExtDescriptorMapping
   266  	mtaPath := config.MtaPath
   267  
   268  	if config.Verbose {
   269  		log.Entry().Info("The step will use the following values:")
   270  		log.Entry().Infof("- description: %v", config.CustomDescription)
   271  
   272  		if len(nodeNameExtDescriptorMapping) > 0 {
   273  			log.Entry().Infof("- mapping between node names and MTA extension descriptor file paths: %v", nodeNameExtDescriptorMapping)
   274  		}
   275  		log.Entry().Infof("- MTA path: %v", mtaPath)
   276  		log.Entry().Infof("- MTA version: %v", mtaVersion)
   277  		if namedUser != "" {
   278  			log.Entry().Infof("- named user: %v", namedUser)
   279  		}
   280  		log.Entry().Infof("- node name: %v", nodeName)
   281  	}
   282  
   283  	if len(nodeNameExtDescriptorMapping) > 0 {
   284  		nodes, errGetNodes := communicationInstance.GetNodes()
   285  		if errGetNodes != nil {
   286  			log.SetErrorCategory(log.ErrorService)
   287  			return fmt.Errorf("failed to get nodes: %w", errGetNodes)
   288  		}
   289  
   290  		mtaYamlMap, errGetMtaYamlAsMap := GetYamlAsMap(utils, "mta.yaml")
   291  		if errGetMtaYamlAsMap != nil {
   292  			log.SetErrorCategory(log.ErrorConfiguration)
   293  			return fmt.Errorf("failed to get mta.yaml as map: %w", errGetMtaYamlAsMap)
   294  		}
   295  		_, isIdParameterInMap := mtaYamlMap["ID"]
   296  		_, isVersionParameterInMap := mtaYamlMap["version"]
   297  		if !isIdParameterInMap || !isVersionParameterInMap {
   298  			var errorMessage string
   299  			if !isIdParameterInMap {
   300  				errorMessage += "parameter 'ID' is not found in mta.yaml\n"
   301  			}
   302  			if !isVersionParameterInMap {
   303  				errorMessage += "parameter 'version' is not found in mta.yaml\n"
   304  			}
   305  			log.SetErrorCategory(log.ErrorConfiguration)
   306  			return errors.New(errorMessage)
   307  		}
   308  
   309  		// validate the whole mapping and then throw errors together, so that user can get them after a single pipeline run
   310  		nodeIdExtDescriptorMapping, errGetNodeIdExtDescriptorMapping := FormNodeIdExtDescriptorMappingWithValidation(utils, nodeNameExtDescriptorMapping, nodes, mtaYamlMap, mtaVersion)
   311  		if errGetNodeIdExtDescriptorMapping != nil {
   312  			log.SetErrorCategory(log.ErrorConfiguration)
   313  			return errGetNodeIdExtDescriptorMapping
   314  		}
   315  
   316  		for nodeId, mtaExtDescriptorPath := range nodeIdExtDescriptorMapping {
   317  			obtainedMtaExtDescriptor, errGetMtaExtDescriptor := communicationInstance.GetMtaExtDescriptor(nodeId, fmt.Sprintf("%v", mtaYamlMap["ID"]), mtaVersion)
   318  			if errGetMtaExtDescriptor != nil {
   319  				log.SetErrorCategory(log.ErrorService)
   320  				return fmt.Errorf("failed to get MTA extension descriptor: %w", errGetMtaExtDescriptor)
   321  			}
   322  
   323  			if obtainedMtaExtDescriptor != (MtaExtDescriptor{}) {
   324  				_, errUpdateMtaExtDescriptor := communicationInstance.UpdateMtaExtDescriptor(nodeId, obtainedMtaExtDescriptor.Id, mtaExtDescriptorPath, mtaVersion, description, namedUser)
   325  				if errUpdateMtaExtDescriptor != nil {
   326  					log.SetErrorCategory(log.ErrorService)
   327  					return fmt.Errorf("failed to update MTA extension descriptor: %w", errUpdateMtaExtDescriptor)
   328  				}
   329  			} else {
   330  				_, errUploadMtaExtDescriptor := communicationInstance.UploadMtaExtDescriptorToNode(nodeId, mtaExtDescriptorPath, mtaVersion, description, namedUser)
   331  				if errUploadMtaExtDescriptor != nil {
   332  					log.SetErrorCategory(log.ErrorService)
   333  					return fmt.Errorf("failed to upload MTA extension descriptor to node: %w", errUploadMtaExtDescriptor)
   334  				}
   335  			}
   336  		}
   337  	}
   338  	return nil
   339  }
   340  
   341  func UploadFile(config Options, communicationInstance CommunicationInterface, utils TmsUtils) (FileInfo, error) {
   342  	var fileInfo FileInfo
   343  
   344  	mtaPath := config.MtaPath
   345  	exists, _ := utils.FileExists(mtaPath)
   346  	if !exists {
   347  		log.SetErrorCategory(log.ErrorConfiguration)
   348  		return fileInfo, fmt.Errorf("mta file %s not found", mtaPath)
   349  	}
   350  
   351  	fileInfo, errUploadFile := communicationInstance.UploadFile(mtaPath, config.NamedUser)
   352  	if errUploadFile != nil {
   353  		log.SetErrorCategory(log.ErrorService)
   354  		return fileInfo, fmt.Errorf("failed to upload file: %w", errUploadFile)
   355  	}
   356  
   357  	return fileInfo, nil
   358  }