github.com/xgoffin/jenkins-library@v1.154.0/cmd/abapEnvironmentPushATCSystemConfig.go (about)

     1  package cmd
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"net/http/cookiejar"
    10  	"path/filepath"
    11  	"reflect"
    12  	"strconv"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/SAP/jenkins-library/pkg/abaputils"
    17  	"github.com/SAP/jenkins-library/pkg/command"
    18  	piperhttp "github.com/SAP/jenkins-library/pkg/http"
    19  	"github.com/SAP/jenkins-library/pkg/log"
    20  	"github.com/SAP/jenkins-library/pkg/telemetry"
    21  	"github.com/pkg/errors"
    22  )
    23  
    24  func abapEnvironmentPushATCSystemConfig(config abapEnvironmentPushATCSystemConfigOptions, telemetryData *telemetry.CustomData) {
    25  	// for command execution use Command
    26  	c := command.Command{}
    27  	// reroute command output to logging framework
    28  	c.Stdout(log.Writer())
    29  	c.Stderr(log.Writer())
    30  
    31  	var autils = abaputils.AbapUtils{
    32  		Exec: &c,
    33  	}
    34  
    35  	client := piperhttp.Client{}
    36  
    37  	err := runAbapEnvironmentPushATCSystemConfig(&config, telemetryData, &autils, &client)
    38  	if err != nil {
    39  		log.Entry().WithError(err).Fatal("step execution failed")
    40  	}
    41  }
    42  
    43  func runAbapEnvironmentPushATCSystemConfig(config *abapEnvironmentPushATCSystemConfigOptions, telemetryData *telemetry.CustomData, autils abaputils.Communication, client piperhttp.Sender) error {
    44  
    45  	subOptions := convertATCSysOptions(config)
    46  
    47  	// Determine the host, user and password, either via the input parameters or via a cloud foundry service key.
    48  	connectionDetails, err := autils.GetAbapCommunicationArrangementInfo(subOptions, "/sap/opu/odata4/sap/satc_ci_cf_api/srvd_a2x/sap/satc_ci_cf_sv_api/0001")
    49  	if err != nil {
    50  		return errors.Wrap(err, "Parameters for the ABAP Connection not available")
    51  	}
    52  
    53  	cookieJar, err := cookiejar.New(nil)
    54  	if err != nil {
    55  		return errors.Wrap(err, "could not create a Cookie Jar")
    56  	}
    57  	clientOptions := piperhttp.ClientOptions{
    58  		MaxRequestDuration: 180 * time.Second,
    59  		CookieJar:          cookieJar,
    60  		Username:           connectionDetails.User,
    61  		Password:           connectionDetails.Password,
    62  	}
    63  	client.SetOptions(clientOptions)
    64  
    65  	return pushATCSystemConfig(config, connectionDetails, client)
    66  
    67  }
    68  
    69  func pushATCSystemConfig(config *abapEnvironmentPushATCSystemConfigOptions, connectionDetails abaputils.ConnectionDetailsHTTP, client piperhttp.Sender) error {
    70  
    71  	//check, if given ATC System configuration File
    72  	parsedConfigurationJson, atcSystemConfiguartionJsonFile, err := checkATCSystemConfigurationFile(config)
    73  	if err != nil {
    74  		return err
    75  	}
    76  	//check, if ATC configuration with given name already exists in Backend
    77  	configDoesExist, configName, configUUID, configLastChangedBackend, err := checkConfigExistsInBackend(config, atcSystemConfiguartionJsonFile, connectionDetails, client)
    78  	if err != nil {
    79  		return err
    80  	}
    81  	if !configDoesExist {
    82  		//regular push of configuration
    83  		configUUID = ""
    84  		return handlePushConfiguration(config, configUUID, configDoesExist, atcSystemConfiguartionJsonFile, connectionDetails, client)
    85  	}
    86  	if !parsedConfigurationJson.LastChangedAt.IsZero() {
    87  		if configLastChangedBackend.Before(parsedConfigurationJson.LastChangedAt) && !config.PatchIfExisting {
    88  			//config exists, is not recent but must NOT be patched
    89  			log.Entry().Info("pushing ATC System Configuration skipped. Reason: ATC System Configuration with name " + configName + " exists and is outdated (Backend: " + configLastChangedBackend.Local().String() + " vs. File: " + parsedConfigurationJson.LastChangedAt.Local().String() + ") but should not be overwritten (check step configuration parameter).")
    90  			return nil
    91  		}
    92  		if configLastChangedBackend.After(parsedConfigurationJson.LastChangedAt) || configLastChangedBackend == parsedConfigurationJson.LastChangedAt {
    93  			//configuration exists and is most recent
    94  			log.Entry().Info("pushing ATC System Configuration skipped. Reason: ATC System Configuration with name " + configName + " exists and is most recent (Backend: " + configLastChangedBackend.Local().String() + " vs. File: " + parsedConfigurationJson.LastChangedAt.Local().String() + "). Therefore no update needed.")
    95  			return nil
    96  		}
    97  	}
    98  	if configLastChangedBackend.Before(parsedConfigurationJson.LastChangedAt) || parsedConfigurationJson.LastChangedAt.IsZero() {
    99  		if config.PatchIfExisting {
   100  			//configuration exists and is older than current config (or does not provide information about lastChanged) and should be patched
   101  			return handlePushConfiguration(config, configUUID, configDoesExist, atcSystemConfiguartionJsonFile, connectionDetails, client)
   102  		} else {
   103  			//config exists, is not recent but must NOT be patched
   104  			log.Entry().Info("pushing ATC System Configuration skipped. Reason: ATC System Configuration with name " + configName + " exists but should not be overwritten (check step configuration parameter).")
   105  			return nil
   106  		}
   107  	}
   108  
   109  	return nil
   110  }
   111  
   112  func checkATCSystemConfigurationFile(config *abapEnvironmentPushATCSystemConfigOptions) (parsedConfigJsonWithExpand, []byte, error) {
   113  	var parsedConfigurationJson parsedConfigJsonWithExpand
   114  	var emptyConfigurationJson parsedConfigJsonWithExpand
   115  	var atcSystemConfiguartionJsonFile []byte
   116  
   117  	parsedConfigurationJson, atcSystemConfiguartionJsonFile, err := readATCSystemConfigurationFile(config)
   118  	if err != nil {
   119  		return parsedConfigurationJson, atcSystemConfiguartionJsonFile, err
   120  	}
   121  
   122  	//check if parsedConfigurationJson is not initial or Configuration Name not supplied
   123  	if reflect.DeepEqual(parsedConfigurationJson, emptyConfigurationJson) ||
   124  		parsedConfigurationJson.ConfName == "" {
   125  		return parsedConfigurationJson, atcSystemConfiguartionJsonFile, fmt.Errorf("pushing ATC System Configuration failed. Reason: Configured File does not contain required ATC System Configuration attributes (File: " + config.AtcSystemConfigFilePath + ")")
   126  	}
   127  
   128  	return parsedConfigurationJson, atcSystemConfiguartionJsonFile, nil
   129  }
   130  
   131  func readATCSystemConfigurationFile(config *abapEnvironmentPushATCSystemConfigOptions) (parsedConfigJsonWithExpand, []byte, error) {
   132  	var parsedConfigurationJson parsedConfigJsonWithExpand
   133  	var emptyConfigurationJson parsedConfigJsonWithExpand
   134  	var atcSystemConfiguartionJsonFile []byte
   135  	var filename string
   136  
   137  	filelocation, err := filepath.Glob(config.AtcSystemConfigFilePath)
   138  	if err != nil {
   139  		return parsedConfigurationJson, atcSystemConfiguartionJsonFile, err
   140  	}
   141  
   142  	if len(filelocation) == 0 {
   143  		return parsedConfigurationJson, atcSystemConfiguartionJsonFile, fmt.Errorf("pushing ATC System Configuration failed. Reason: Configured Filelocation is empty (File: " + config.AtcSystemConfigFilePath + ")")
   144  	}
   145  
   146  	filename, err = filepath.Abs(filelocation[0])
   147  	if err != nil {
   148  		return parsedConfigurationJson, atcSystemConfiguartionJsonFile, err
   149  	}
   150  	atcSystemConfiguartionJsonFile, err = ioutil.ReadFile(filename)
   151  	if err != nil {
   152  		return parsedConfigurationJson, atcSystemConfiguartionJsonFile, err
   153  	}
   154  	if len(atcSystemConfiguartionJsonFile) == 0 {
   155  		return parsedConfigurationJson, atcSystemConfiguartionJsonFile, fmt.Errorf("pushing ATC System Configuration failed. Reason: Configured File is empty (File: " + config.AtcSystemConfigFilePath + ")")
   156  	}
   157  
   158  	err = json.Unmarshal(atcSystemConfiguartionJsonFile, &parsedConfigurationJson)
   159  	if err != nil {
   160  		return emptyConfigurationJson, atcSystemConfiguartionJsonFile, err
   161  	}
   162  
   163  	return parsedConfigurationJson, atcSystemConfiguartionJsonFile, err
   164  }
   165  
   166  func handlePushConfiguration(config *abapEnvironmentPushATCSystemConfigOptions, confUUID string, configDoesExist bool, atcSystemConfiguartionJsonFile []byte, connectionDetails abaputils.ConnectionDetailsHTTP, client piperhttp.Sender) error {
   167  	var err error
   168  	connectionDetails.XCsrfToken, err = fetchXcsrfTokenFromHead(connectionDetails, client)
   169  	if err != nil {
   170  		return err
   171  	}
   172  	if configDoesExist {
   173  		err = doPatchATCSystemConfig(config, confUUID, atcSystemConfiguartionJsonFile, connectionDetails, client)
   174  		if err != nil {
   175  			return err
   176  		}
   177  		log.Entry().Info("ATC System Configuration successfully pushed from file " + config.AtcSystemConfigFilePath + " and patched in system")
   178  	}
   179  	if !configDoesExist {
   180  		err = doPushATCSystemConfig(config, atcSystemConfiguartionJsonFile, connectionDetails, client)
   181  		if err != nil {
   182  			return err
   183  		}
   184  		log.Entry().Info("ATC System Configuration successfully pushed from file " + config.AtcSystemConfigFilePath + " and created in system")
   185  	}
   186  
   187  	return nil
   188  
   189  }
   190  
   191  func fetchXcsrfTokenFromHead(connectionDetails abaputils.ConnectionDetailsHTTP, client piperhttp.Sender) (string, error) {
   192  
   193  	log.Entry().WithField("ABAP Endpoint: ", connectionDetails.URL).Debug("Fetching Xcrsf-Token")
   194  	uriConnectionDetails := connectionDetails
   195  	uriConnectionDetails.URL = ""
   196  	connectionDetails.XCsrfToken = "fetch"
   197  
   198  	// Loging into the ABAP System - getting the x-csrf-token and cookies
   199  	resp, err := abaputils.GetHTTPResponse("HEAD", connectionDetails, nil, client)
   200  	if err != nil {
   201  		err = abaputils.HandleHTTPError(resp, err, "authentication on the ABAP system failed", connectionDetails)
   202  		return connectionDetails.XCsrfToken, err
   203  	}
   204  	defer resp.Body.Close()
   205  
   206  	log.Entry().WithField("StatusCode", resp.Status).WithField("ABAP Endpoint", connectionDetails.URL).Debug("Authentication on the ABAP system successful")
   207  	uriConnectionDetails.XCsrfToken = resp.Header.Get("X-Csrf-Token")
   208  	connectionDetails.XCsrfToken = uriConnectionDetails.XCsrfToken
   209  
   210  	return connectionDetails.XCsrfToken, err
   211  }
   212  
   213  func doPatchATCSystemConfig(config *abapEnvironmentPushATCSystemConfigOptions, confUUID string, atcSystemConfiguartionJsonFile []byte, connectionDetails abaputils.ConnectionDetailsHTTP, client piperhttp.Sender) error {
   214  
   215  	batchATCSystemConfigFile, err := buildATCSystemConfigBatchRequest(confUUID, atcSystemConfiguartionJsonFile)
   216  	if err != nil {
   217  		return err
   218  	}
   219  	return doBatchATCSystemConfig(config, batchATCSystemConfigFile, connectionDetails, client)
   220  
   221  }
   222  
   223  func buildATCSystemConfigBatchRequest(confUUID string, atcSystemConfiguartionJsonFile []byte) (string, error) {
   224  
   225  	var batchRequestString string
   226  
   227  	//splitting json into configuration base and configuration properties & build a batch request for oData - patch config & patch priorities
   228  	var configBaseJson parsedConfigJsonBase
   229  	err := json.Unmarshal(atcSystemConfiguartionJsonFile, &configBaseJson)
   230  	if err != nil {
   231  		return batchRequestString, err
   232  	}
   233  	var parsedConfigPriorities parsedConfigPriorities
   234  	err = json.Unmarshal(atcSystemConfiguartionJsonFile, &parsedConfigPriorities)
   235  	if err != nil {
   236  		return batchRequestString, err
   237  	}
   238  
   239  	configBaseJson.ConfUUID = confUUID
   240  
   241  	//build the Batch request string
   242  	contentID := 1
   243  
   244  	batchRequestString = addBeginOfBatch(batchRequestString)
   245  	//now adding opening Changeset as at least config base is to be patched
   246  	batchRequestString = addChangesetBegin(batchRequestString, contentID)
   247  
   248  	configBaseJsonBody, err := json.Marshal(&configBaseJson)
   249  	if err != nil {
   250  		return batchRequestString, err
   251  	}
   252  	batchRequestString = addPatchConfigBaseChangeset(batchRequestString, confUUID, configBaseJsonBody)
   253  
   254  	if len(parsedConfigPriorities.Priorities) > 0 {
   255  		// in case Priorities need patches too
   256  		var priority priorityJson
   257  		for i, priorityLine := range parsedConfigPriorities.Priorities {
   258  
   259  			//for each line, add content id
   260  			contentID += 1
   261  			priority.Priority = priorityLine.Priority
   262  			priorityJsonBody, err := json.Marshal(&priority)
   263  			if err != nil {
   264  				log.Entry().Errorf("problem with marshall of single priority in line "+strconv.Itoa(i), err)
   265  				continue
   266  			}
   267  			batchRequestString = addChangesetBegin(batchRequestString, contentID)
   268  
   269  			//now PATCH command for priority
   270  			batchRequestString = addPatchSinglePriorityChangeset(batchRequestString, confUUID, priorityLine.Test, priorityLine.MessageId, priorityJsonBody)
   271  
   272  		}
   273  	}
   274  
   275  	//at the end, add closing inner and outer boundary tags
   276  	batchRequestString = addEndOfBatch(batchRequestString)
   277  
   278  	log.Entry().Info("Batch Request String: " + batchRequestString)
   279  
   280  	return batchRequestString, nil
   281  
   282  }
   283  
   284  func addPatchConfigBaseChangeset(inputString string, confUUID string, configBaseJsonBody []byte) string {
   285  
   286  	entityIdString := `(root_id='1',conf_id=` + confUUID + `)`
   287  	newString := addCommandEntityChangeset("PATCH", "configuration", entityIdString, inputString, configBaseJsonBody)
   288  
   289  	return newString
   290  }
   291  
   292  func addPatchSinglePriorityChangeset(inputString string, confUUID string, test string, messageId string, priorityJsonBody []byte) string {
   293  
   294  	entityIdString := `(root_id='1',conf_id=` + confUUID + `,test='` + test + `',message_id='` + messageId + `')`
   295  	newString := addCommandEntityChangeset("PATCH", "priority", entityIdString, inputString, priorityJsonBody)
   296  
   297  	return newString
   298  }
   299  
   300  func addChangesetBegin(inputString string, contentID int) string {
   301  
   302  	newString := inputString + `
   303  --changeset
   304  Content-Type: application/http
   305  Content-Transfer-Encoding: binary
   306  Content-ID: ` + strconv.Itoa(contentID) + `
   307  `
   308  	return newString
   309  }
   310  
   311  func addBeginOfBatch(inputString string) string {
   312  	//Starting always with outer boundary - followed by mandatory Contenttype and boundary for changeset
   313  	newString := inputString + `
   314  --request-separator
   315  Content-Type: multipart/mixed;boundary=changeset
   316  `
   317  	return newString
   318  }
   319  
   320  func addEndOfBatch(inputString string) string {
   321  	//Starting always with outer boundary - followed by mandatory Contenttype and boundary for changeset
   322  	newString := inputString + `
   323  --changeset--
   324  
   325  --request-separator--`
   326  
   327  	return newString
   328  }
   329  
   330  func addCommandEntityChangeset(command string, entity string, entityIdString string, inputString string, jsonBody []byte) string {
   331  
   332  	newString := inputString + `
   333  ` + command + ` ` + entity + entityIdString + ` HTTP/1.1
   334  Content-Type: application/json
   335  
   336  `
   337  	if len(jsonBody) > 0 {
   338  		newString += string(jsonBody) + `
   339  `
   340  	}
   341  
   342  	return newString
   343  
   344  }
   345  
   346  func doPushATCSystemConfig(config *abapEnvironmentPushATCSystemConfigOptions, atcSystemConfiguartionJsonFile []byte, connectionDetails abaputils.ConnectionDetailsHTTP, client piperhttp.Sender) error {
   347  	abapEndpoint := connectionDetails.URL
   348  	connectionDetails.URL = abapEndpoint + "/configuration"
   349  
   350  	resp, err := abaputils.GetHTTPResponse("POST", connectionDetails, atcSystemConfiguartionJsonFile, client)
   351  	return HandleHttpResponse(resp, err, "Post Request for Creating ATC System Configuration", connectionDetails)
   352  }
   353  
   354  func doBatchATCSystemConfig(config *abapEnvironmentPushATCSystemConfigOptions, batchRequestBodyFile string, connectionDetails abaputils.ConnectionDetailsHTTP, client piperhttp.Sender) error {
   355  	abapEndpoint := connectionDetails.URL
   356  	connectionDetails.URL = abapEndpoint + "/$batch"
   357  
   358  	header := make(map[string][]string)
   359  	header["X-Csrf-Token"] = []string{connectionDetails.XCsrfToken}
   360  	header["Content-Type"] = []string{"multipart/mixed;boundary=request-separator"}
   361  
   362  	batchRequestBodyFileByte := []byte(batchRequestBodyFile)
   363  	resp, err := client.SendRequest("POST", connectionDetails.URL, bytes.NewBuffer(batchRequestBodyFileByte), header, nil)
   364  	return HandleHttpResponse(resp, err, "Batch Request for Patching ATC System Configuration", connectionDetails)
   365  }
   366  
   367  func checkConfigExistsInBackend(config *abapEnvironmentPushATCSystemConfigOptions, atcSystemConfiguartionJsonFile []byte, connectionDetails abaputils.ConnectionDetailsHTTP, client piperhttp.Sender) (bool, string, string, time.Time, error) {
   368  	var configName string
   369  	var configUUID string
   370  	var configLastChangedAt time.Time
   371  
   372  	//extract Configuration Name from atcSystemConfiguartionJsonFile
   373  	var parsedConfigurationJson parsedConfigJsonWithExpand
   374  	err := json.Unmarshal(atcSystemConfiguartionJsonFile, &parsedConfigurationJson)
   375  	if err != nil {
   376  		return false, configName, configUUID, configLastChangedAt, err
   377  	}
   378  
   379  	//call a get on config with filter on given name
   380  	configName = parsedConfigurationJson.ConfName
   381  	abapEndpoint := connectionDetails.URL
   382  	connectionDetails.URL = abapEndpoint + "/configuration" + "?$filter=conf_name%20eq%20" + "'" + configName + "'"
   383  	if err != nil {
   384  		return false, configName, configUUID, configLastChangedAt, err
   385  	}
   386  	resp, err := abaputils.GetHTTPResponse("GET", connectionDetails, nil, client)
   387  	if err != nil {
   388  		return false, configName, configUUID, configLastChangedAt, err
   389  	}
   390  	var body []byte
   391  	body, err = ioutil.ReadAll(resp.Body)
   392  	if err != nil {
   393  		return false, configName, configUUID, configLastChangedAt, err
   394  	}
   395  
   396  	var parsedoDataResponse parsedOdataResp
   397  	err = json.Unmarshal(body, &parsedoDataResponse)
   398  	if err != nil {
   399  		return false, configName, configUUID, configLastChangedAt, err
   400  	}
   401  	if len(parsedoDataResponse.Value) > 0 {
   402  		configUUID = parsedoDataResponse.Value[0].ConfUUID
   403  		configLastChangedAt = parsedoDataResponse.Value[0].LastChangedAt
   404  		log.Entry().Info("ATC System Configuration " + configName + " does exist and last changed at " + configLastChangedAt.Local().String())
   405  		return true, configName, configUUID, configLastChangedAt, nil
   406  	} else {
   407  		//response value is empty, so NOT found entity with this name!
   408  		log.Entry().Info("ATC System Configuration " + configName + " does not exist!")
   409  		return false, configName, "", configLastChangedAt, nil
   410  	}
   411  }
   412  
   413  func HandleHttpResponse(resp *http.Response, err error, message string, connectionDetails abaputils.ConnectionDetailsHTTP) error {
   414  
   415  	var bodyText []byte
   416  	var readError error
   417  
   418  	if resp == nil {
   419  		// Response is nil in case of a timeout
   420  		log.Entry().WithError(err).WithField("ABAP Endpoint", connectionDetails.URL).Error("Request failed")
   421  	} else {
   422  		log.Entry().WithField("StatusCode", resp.Status).Info(message)
   423  		bodyText, readError = ioutil.ReadAll(resp.Body)
   424  		if readError != nil {
   425  			defer resp.Body.Close()
   426  			return readError
   427  		}
   428  		log.Entry().Infof("Response body: %s", bodyText)
   429  
   430  		errorDetails, parsingError := getErrorDetailsFromBody(resp, bodyText)
   431  		if parsingError == nil &&
   432  			errorDetails != "" {
   433  			err = errors.New(errorDetails)
   434  		}
   435  	}
   436  	defer resp.Body.Close()
   437  	return err
   438  
   439  }
   440  
   441  func getErrorDetailsFromBody(resp *http.Response, bodyText []byte) (errorString string, err error) {
   442  
   443  	// Include the error message of the ABAP Environment system, if available
   444  	var abapErrorResponse AbapError
   445  	var abapResp map[string]*json.RawMessage
   446  
   447  	//errors could also be reported inside an e.g. BATCH request wich returned with status code 200!!!
   448  	contentType := resp.Header.Get("Content-type")
   449  	if len(bodyText) != 0 &&
   450  		strings.Contains(contentType, "multipart/mixed") {
   451  		//scan for inner errors! (by now count as error only RespCode starting with 4 or 5)
   452  		if strings.Contains(string(bodyText), "HTTP/1.1 4") ||
   453  			strings.Contains(string(bodyText), "HTTP/1.1 5") {
   454  			errorString = fmt.Sprintf("Outer Response Code: %v - but at least one Inner response returned StatusCode 4* or 5*. Please check Log for details.", resp.StatusCode)
   455  		} else {
   456  			log.Entry().Info("no Inner Response Errors")
   457  		}
   458  		if errorString != "" {
   459  			return errorString, nil
   460  		}
   461  	}
   462  	if len(bodyText) != 0 &&
   463  		strings.Contains(contentType, "application/json") {
   464  		errUnmarshal := json.Unmarshal(bodyText, &abapResp)
   465  		if errUnmarshal != nil {
   466  			return errorString, errUnmarshal
   467  		}
   468  		if _, ok := abapResp["error"]; ok {
   469  			json.Unmarshal(*abapResp["error"], &abapErrorResponse)
   470  			if (AbapError{}) != abapErrorResponse {
   471  				log.Entry().WithField("ErrorCode", abapErrorResponse.Code).Error(abapErrorResponse.Message.Value)
   472  				errorString = fmt.Sprintf("%s - %s", abapErrorResponse.Code, abapErrorResponse.Message.Value)
   473  				return errorString, nil
   474  			}
   475  		}
   476  	}
   477  
   478  	return errorString, errors.New("Could not parse the JSON error response")
   479  
   480  }
   481  
   482  func convertATCSysOptions(options *abapEnvironmentPushATCSystemConfigOptions) abaputils.AbapEnvironmentOptions {
   483  	subOptions := abaputils.AbapEnvironmentOptions{}
   484  
   485  	subOptions.CfAPIEndpoint = options.CfAPIEndpoint
   486  	subOptions.CfServiceInstance = options.CfServiceInstance
   487  	subOptions.CfServiceKeyName = options.CfServiceKeyName
   488  	subOptions.CfOrg = options.CfOrg
   489  	subOptions.CfSpace = options.CfSpace
   490  	subOptions.Host = options.Host
   491  	subOptions.Password = options.Password
   492  	subOptions.Username = options.Username
   493  
   494  	return subOptions
   495  }
   496  
   497  type parsedOdataResp struct {
   498  	Value []parsedConfigJsonWithExpand `json:"value"`
   499  }
   500  
   501  type parsedConfigJsonWithExpand struct {
   502  	ConfName      string                 `json:"conf_name"`
   503  	ConfUUID      string                 `json:"conf_id"`
   504  	LastChangedAt time.Time              `json:"last_changed_at"`
   505  	Priorities    []parsedConfigPriority `json:"_priorities"`
   506  }
   507  
   508  type parsedConfigJsonBase struct {
   509  	ConfName            string `json:"conf_name"`
   510  	ConfUUID            string `json:"conf_id"`
   511  	Checkvariant        string `json:"checkvariant"`
   512  	PseudoCommentPolicy string `json:"pseudo_comment_policy"`
   513  	BlockFindings       string `json:"block_findings"`
   514  	InformFindings      string `json:"inform_findings"`
   515  	IsDefault           bool   `json:"is_default"`
   516  	IsProxyVariant      bool   `json:"is_proxy_variant"`
   517  }
   518  
   519  type parsedConfigPriorities struct {
   520  	Priorities []parsedConfigPriority `json:"_priorities"`
   521  }
   522  
   523  type parsedConfigPriority struct {
   524  	Test      string      `json:"test"`
   525  	MessageId string      `json:"message_id"`
   526  	Priority  json.Number `json:"priority"`
   527  }
   528  
   529  type priorityJson struct {
   530  	Priority json.Number `json:"priority"`
   531  }
   532  
   533  // AbapError contains the error code and the error message for ABAP errors
   534  type AbapError struct {
   535  	Code    string           `json:"code"`
   536  	Message AbapErrorMessage `json:"message"`
   537  }
   538  
   539  // AbapErrorMessage contains the lanuage and value fields for ABAP errors
   540  type AbapErrorMessage struct {
   541  	Lang  string `json:"lang"`
   542  	Value string `json:"value"`
   543  }