Homelab, Linux, JS & ABAP (~˘▾˘)~
 

[ABAP] Create ToC for a given transport, release it and download it as ZIP

Related Reports:
https://nocin.eu/abap-download-transport-as-zip/
https://nocin.eu/abap-import-transport-from-zip/

*&---------------------------------------------------------------------*
*& Report Z_ZIP_TOC
*&---------------------------------------------------------------------*
*& For the givin transport, this report creates a transport of copies (ToC),
*& releases it, and then download the ToC as ZIP file to your filesystem.
*& The given request can be in sate unreleased!
*&---------------------------------------------------------------------*
REPORT Z_ZIP_TOC.

INITIALIZATION.

  SELECTION-SCREEN BEGIN OF BLOCK bl01 WITH FRAME TITLE TEXT-t01.
  PARAMETERS p_trkorr LIKE e070-trkorr OBLIGATORY.
  PARAMETERS p_ttext  TYPE as4text.
  SELECTION-SCREEN END OF BLOCK bl01.

  SELECTION-SCREEN BEGIN OF BLOCK bl02 WITH FRAME TITLE TEXT-t02.
  PARAMETERS p_sapdir TYPE string LOWER CASE OBLIGATORY DEFAULT '/usr/sap/trans/'.
  PARAMETERS p_lcldir TYPE string LOWER CASE OBLIGATORY DEFAULT 'C:\temp\'.
  SELECTION-SCREEN END OF BLOCK bl02.

AT SELECTION-SCREEN ON VALUE-REQUEST FOR p_trkorr.
  CALL FUNCTION 'TR_F4_REQUESTS'
    EXPORTING
      iv_trkorr_pattern   = p_trkorr
    IMPORTING
      ev_selected_request = p_trkorr.

START-OF-SELECTION.

  " Read description of provided transport
  DATA ls_request TYPE trwbo_request.
  CALL FUNCTION 'TR_READ_REQUEST'
    EXPORTING
      iv_read_e07t     = 'X'
      iv_trkorr        = p_trkorr
    CHANGING
      cs_request       = ls_request
    EXCEPTIONS
      error_occured    = 1
      no_authorization = 2
      OTHERS           = 3.
  IF sy-subrc <> 0.
    MESSAGE 'Could not read Transport description' TYPE 'E'.
  ENDIF.


  " Read all objects for provided transport
  DATA: lt_objects TYPE  tr_objects,
        lt_keys    TYPE  tr_keys.
  CALL FUNCTION 'TR_GET_OBJECTS_OF_REQ_AN_TASKS'
    EXPORTING
      is_request_header      = VALUE trwbo_request_header( trkorr = p_trkorr )
      iv_condense_objectlist = 'X'
    IMPORTING
      et_objects             = lt_objects
      et_keys                = lt_keys
    EXCEPTIONS
      invalid_input          = 1
      OTHERS                 = 2.
  IF sy-subrc <> 0.
    MESSAGE 'Could not read Transport objects' TYPE 'E'.
  ENDIF.


  " Create new ToC
  DATA ls_request_header TYPE trwbo_request_header.
  CALL FUNCTION 'TR_INSERT_REQUEST_WITH_TASKS'
    EXPORTING
      iv_type           = 'T'
      iv_text           = COND as4text( WHEN p_ttext IS INITIAL THEN ls_request-h-as4text
                                                                ELSE p_ttext )
      iv_owner          = sy-uname
      iv_target         = 'DUM'
    IMPORTING
      es_request_header = ls_request_header
    EXCEPTIONS
      insert_failed     = 1
      enqueue_failed    = 2
      OTHERS            = 3.
  IF sy-subrc <> 0.
    MESSAGE 'Transport creation failed' TYPE 'E'.
  ENDIF.
  DATA(lv_trkorr_toc) = ls_request_header-trkorr.


  " Add all object to the new ToC
  CALL FUNCTION 'TRINT_APPEND_COMM'
    EXPORTING
      wi_exclusive       = 'X'
      wi_sel_e071        = 'X'
      wi_sel_e071k       = 'X'
      wi_trkorr          = lv_trkorr_toc
    TABLES
      wt_e071            = lt_objects
      wt_e071k           = lt_keys
    EXCEPTIONS
      e071k_append_error = 1
      e071_append_error  = 2
      trkorr_empty       = 3
      OTHERS             = 4.
  IF sy-subrc <> 0.
    MESSAGE 'Could not append objects to ToC' TYPE 'E'.
  ENDIF.


  " Release ToC Transport
  DATA lt_messages TYPE ctsgerrmsgs.
  CALL FUNCTION 'TRINT_RELEASE_REQUEST'
    EXPORTING
      iv_trkorr                   = lv_trkorr_toc
*     IV_DIALOG                   = 'X'
*     IV_AS_BACKGROUND_JOB        = ' '
*     IV_SUCCESS_MESSAGE          = 'X'
*     IV_WITHOUT_OBJECTS_CHECK    = ' '
*     IV_CALLED_BY_ADT            = ' '
*     IV_CALLED_BY_PERFORCE       = ' '
*     IV_WITHOUT_DOCU             = ' '
      iv_without_locking          = 'X'
*     IV_DISPLAY_EXPORT_LOG       = 'X'
*     IV_IGNORE_WARNINGS          = ' '
*     IV_SIMULATION               = ' '
    IMPORTING
      et_messages                 = lt_messages
    EXCEPTIONS
      cts_initialization_failure  = 1
      enqueue_failed              = 2
      no_authorization            = 3
      invalid_request             = 4
      request_already_released    = 5
      repeat_too_early            = 6
      object_lock_error           = 7
      object_check_error          = 8
      docu_missing                = 9
      db_access_error             = 10
      action_aborted_by_user      = 11
      export_failed               = 12
      execute_objects_check       = 13
      release_in_bg_mode          = 14
      release_in_bg_mode_w_objchk = 15
      error_in_export_methods     = 16
      object_lang_error           = 17
      OTHERS                      = 18.
  IF sy-subrc <> 0.
    MESSAGE 'Could not release ToC' TYPE 'E'.
    cl_demo_output=>display( lt_messages ).
  ENDIF.


  " Download released ToC as ZIP
  DATA lv_xcontent_k TYPE xstring.
  DATA lv_xcontent_r TYPE xstring.

  DATA(lv_transdir_k) = |{ p_sapdir }cofiles/K{ lv_trkorr_toc+4 }.{ lv_trkorr_toc(3) }|.
  DATA(lv_transdir_r) = |{ p_sapdir }data/R{    lv_trkorr_toc+4 }.{ lv_trkorr_toc(3) }|.


  TRY.

      " K
      OPEN  DATASET lv_transdir_k FOR INPUT IN BINARY MODE.
      READ  DATASET lv_transdir_k INTO lv_xcontent_k.
      CLOSE DATASET lv_transdir_k.

      " R
      OPEN  DATASET lv_transdir_r FOR INPUT IN BINARY MODE.
      READ  DATASET lv_transdir_r INTO lv_xcontent_r.
      CLOSE DATASET lv_transdir_r.


      " Add to ZIP
      DATA(lo_zipper) = NEW cl_abap_zip( ).

      lo_zipper->add( name    = |K{ lv_trkorr_toc+4 }.{ lv_trkorr_toc(3) }|
                      content = lv_xcontent_k ).

      lo_zipper->add( name    = |R{ lv_trkorr_toc+4 }.{ lv_trkorr_toc(3) }|
                      content = lv_xcontent_r ).


      " Download ZIP
      DATA(lv_xzip) = lo_zipper->save( ).

      " Convert to raw data
      DATA(lt_data) = cl_bcs_convert=>xstring_to_solix( iv_xstring = lv_xzip ).

      " Set zip filename
      DATA(lv_zip_name) = COND #( WHEN p_ttext IS INITIAL THEN |{ ls_request-h-as4text }_{ lv_trkorr_toc }|
                                                          ELSE |{ p_ttext              }_{ lv_trkorr_toc }| ).

      " Replace every character that is not [a-zA-Z0-9_] with '_'.
      REPLACE ALL OCCURRENCES OF REGEX '[^\w]+' IN lv_zip_name WITH '_'.

      cl_gui_frontend_services=>gui_download( EXPORTING filename = p_lcldir && lv_zip_name && '.zip'
                                                        filetype = 'BIN'
                                              CHANGING  data_tab = lt_data ).

    CATCH cx_root INTO DATA(e_text).
      MESSAGE e_text->get_text( ) TYPE 'E'.
  ENDTRY.

  MESSAGE |{ lv_zip_name }.zip created and downloaded to { p_lcldir }| TYPE 'S'.

[CAP] Use Destinations from Subscriber Account

While working on this topic, there are currently two chapters in the CAP docs regarding this topic:

And some SCN posts: herehere and here

But all this didn’t help me to complete the task. Got the solution which finally worked for me from this SAP Sample Susaas (two snippets: here and here).

server.js

const cds = require("@sap/cds")

cds.on('served', async () => {
    const { 'cds.xt.SaasProvisioningService': provisioning } = cds.services
    // Add provisioning logic only if multitenancy is there..
    if (provisioning) {
        let tenantProvisioning = require('./provisioning')
        provisioning.prepend(tenantProvisioning)
    } else {
        console.log(">>> There is no service, therefore does not serve multitenancy!")
    }
})

module.exports = cds.server

provisioning.js

const xsenv = require('@sap/xsenv')
xsenv.loadEnv()

module.exports = (service) => {

    service.on('dependencies', async (req, next) => {
        let dependencies = await next()
        const services = xsenv.getServices({
            registry: { tag: 'SaaS' },
            destination: { tag: 'destination' }
        })
        dependencies.push({ xsappname: services.destination.xsappname }) //adds the subscriber destination as dependency
        console.log(">>> SaaS Dependencies:", JSON.stringify(dependencies))
        return dependencies
    })

}

It injects the destination dependency by manually reading it using xsenv package and returning it in a dependencies callback handler.

[BTP] Create sap-successfactors-extensibility service providing a technicalUser

Prerequisite, you have registered an SAP SuccessFactors system in your Global Account (see here). Creating the sap-successfactors-extensibility service can be done via command line:

#Created the service instance
#An HTTP destination on a subaccount level with the same name as the service instance name is automatically generated
cf create-service sap-successfactors-extensibility api-access myInstanceName -c '{"systemName": "SFCPART000000","technicalUser": "sfadmin"}'

#Bind the instance to an application
cf bind-service myApp-srv myInstanceName 

Find an explanation of the parameters here: https://help.sap.com/docs/btp/sap-business-technology-platform/authentication-type-json-file

This service instance will result in creating:

  • a separate OAuth2 client application on SFSF side (can find in SF in Manage OAuth2 Client Applications)
  • a separate destination definition on a BTP sub-account level

The technicalUser parameter can be specified only during creation. There is no possibility to provide it afterwards using cf update-service. It may be possible to manually update the technicalUser in the destination, which got automatically created. But I did not test this yet.

Of course, the same service creation can also be done via mta.yaml.

resources:
 #####################################################################################################################
  # SuccessFactors Extensibility Service
  #####################################################################################################################
  - name: myInstanceName 
    type: org.cloudfoundry.managed-service
    #type: org.cloudfoundry.existing-service
    parameters:
      service: sap-successfactors-extensibility
      service-plan: api-access
      config:
        systemName: SFCPART000000 # <-- Provide your system name
        technicalUser: sfadmin

For initial deployment, you need the line type: org.cloudfoundry.managed-service. For all further deployments, you have to comment that line out and comment in the next line type: org.cloudfoundry.existing-service. Else you will receive an error. Read more about that behavior here:https://github.com/SAP-samples/successfactors-extension-calculate-employee-seniority/issues/2

[ABAP] Download Transport as ZIP

Related Reports:
https://nocin.eu/abap-import-transport-from-zip/
https://nocin.eu/abap-create-toc-for-a-given-transport-release-it-and-download-it-as-zip/

This report can be handy, especially since S/4HANA 2023 seems to have restricted the “classic” import way by using TCode CG3Y and CG3Z. See note 1949906, where it is recommended to create a custom report.

*&---------------------------------------------------------------------*
*& Report ZIP_TRANSPORT
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
REPORT zip_transport.

SELECTION-SCREEN BEGIN OF BLOCK bl01 WITH FRAME TITLE TEXT-t01.
PARAMETERS p_trkorr LIKE e070-trkorr OBLIGATORY.
PARAMETERS p_ttext  TYPE as4text.
SELECTION-SCREEN END OF BLOCK bl01.

SELECTION-SCREEN BEGIN OF BLOCK bl02 WITH FRAME TITLE TEXT-t02.
PARAMETERS p_sapdir TYPE string LOWER CASE OBLIGATORY DEFAULT '/usr/sap/trans/'.
PARAMETERS p_lcldir TYPE string LOWER CASE OBLIGATORY DEFAULT 'C:\temp\'.
SELECTION-SCREEN END OF BLOCK bl02.


START-OF-SELECTION.

  " Check if Transport is released
  DATA ls_request TYPE trwbo_request.
  CALL FUNCTION 'TR_READ_REQUEST'
    EXPORTING
      iv_read_e070     = abap_true
      iv_read_e07t     = abap_true
      iv_trkorr        = p_trkorr
    CHANGING
      cs_request       = ls_request
    EXCEPTIONS
      error_occured    = 1
      no_authorization = 2
      OTHERS           = 3.
  IF ls_request-h-trstatus <> 'R'.
    MESSAGE 'Transport not yet released' TYPE 'E'.
  ENDIF.


  " Read released Transport
  DATA lv_xcontent_k TYPE xstring.
  DATA lv_xcontent_r TYPE xstring.

  DATA(lv_transdir_k) = |{ p_sapdir }cofiles/K{ p_trkorr+4 }.{ p_trkorr(3) }|.
  DATA(lv_transdir_r) = |{ p_sapdir }data/R{    p_trkorr+4 }.{ p_trkorr(3) }|.


  TRY.

      " K
      OPEN  DATASET lv_transdir_k FOR INPUT IN BINARY MODE.
      READ  DATASET lv_transdir_k INTO lv_xcontent_k.
      CLOSE DATASET lv_transdir_k.

      " R
      OPEN  DATASET lv_transdir_r FOR INPUT IN BINARY MODE.
      READ  DATASET lv_transdir_r INTO lv_xcontent_r.
      CLOSE DATASET lv_transdir_r.

      " Add to ZIP
      DATA(lo_zipper) = NEW cl_abap_zip( ).

      lo_zipper->add( name    = |K{ p_trkorr+4 }.{ p_trkorr(3) }|
                      content = lv_xcontent_k ).

      lo_zipper->add( name    = |R{ p_trkorr+4 }.{ p_trkorr(3) }|
                      content = lv_xcontent_r ).


      " Download ZIP
      DATA(lv_xzip) = lo_zipper->save( ).

      " Convert to raw data
      DATA(lt_data) = cl_bcs_convert=>xstring_to_solix( iv_xstring = lv_xzip ).

      " Set zip filename
      DATA(lv_zip_name) = COND #( WHEN p_ttext IS INITIAL THEN |{ ls_request-h-as4text }_{ p_trkorr }|
                                                          ELSE |{ p_ttext              }_{ p_trkorr }| ).

      " Replace every character that is not [a-zA-Z0-9_] with '_'.
      REPLACE ALL OCCURRENCES OF REGEX '[^\w]+' IN lv_zip_name WITH '_'.

      cl_gui_frontend_services=>gui_download( EXPORTING filename = p_lcldir && lv_zip_name && '.zip'
                                                        filetype = 'BIN'
                                              CHANGING  data_tab = lt_data ).

    CATCH cx_root INTO DATA(e_text).
      MESSAGE e_text->get_text( ) TYPE 'E'.
  ENDTRY.

  MESSAGE |{ lv_zip_name }.zip created and downloaded to { p_lcldir }| TYPE 'S'.

[CAP] Download MTA deployment logs after deployment

With this simple command, you can download the MTA deployment logs of your recent deployment. Instead of manually having to use cf dmol -i <operation id>, just save the command (replace appId with your Id) as a shell script, make it executable (chmod +x) and run the script after your deployment. It stores the recent logs into the folder logs.

dmol is short for download-mta-op-logs

cf download-mta-op-logs --mta <appId> --last 1 -d logs  

To just have a quick look at the recent logs, use

cf logs <appId>-srv --recent

[CAP] Check if User exists in SuccessFactors

To check if a record exists, you can simply use the HEAD query.

“HEAD is almost identical to GET, but without the response body.” (https://www.w3schools.com/tags/ref_httpmethods.asp)

Unfortunately, there is no shortcut in CAP for a HEAD query, so simply use send with method HEAD.

    sfsfSrv = await cds.connect.to('sfsf')

    try {
        await sfsfSrv.send('HEAD', `/User('${userId}')`)
        return true  // status 200
    } catch (err) {
        return false // status 404
    }