Cuba-cli or other command to import exported report zip file from command line?

Hello:

Is there any easy way to import exported report zip files from the command line? I have multiple client databases each running their own version of my Cuba app. Whenever I create a new report, I have to create it in one of the client’s databases, export it, then manually log in to all the others and import it.

I would like to automate this process. I can loop through all the client databases easily. Is there a command that would connect to a database and install a report zip file? Is this even possible?

Hi,
There is no such tool to import report from the command line. Importing a report requires the application context to be fully started.

However you can try to import reports by executing SQL commands in the database.
Report structure is stored in two tables: REPORT_REPORT and REPORT_TEMPLATE.
So in order to insert new report to the database, you will need to insert two rows, to each of those tables, with exact same content as on the source server.

OK, thanks. I guess I can try to copy the Import code you already use. There wouldn’t happen to be a REST interface to it, would there? Maybe that would be a simple solution?

I am not aware about such plans in the platform.
You can implement such REST endpoint in your project. Don’t forget to protect it by password.

The code you need to call is one of importReports methods in the com.haulmont.reports.app.service.ReportService service.

I have configured a service like this

@Service(ImportReportService.NAME)
public class ImportReportServiceBean implements ImportReportService {

    @Inject
    private ReportService reportService;

    public void importReports(byte[] zipBytes) {
        reportService.importReports(zipBytes);
    }
}
<services xmlns="http://schemas.haulmont.com/cuba/rest-services-v2.xsd">
    <service name="rade_ImportReportService">
        <method name="importReports">
            <param name="zipBytes" type="byte[]"/>
        </method>
    </service>
</services>

I can authorize with the server and get an access token. I also have a zip file generated from your Report Export function.

Is it possible to use curl or some other command to upload the file to the service? The only example in your docs is using javascript and the file upload component.

I have tried this:

curl -X POST 'http://localhost:8080/rade/rest/v2/services/rade_ImportReportService' -H 'Authorization: Bearer <token>' --data-binary @OfferListing.zip

I get this:

Request method 'POST'; not supported
Description: The method received in the request-line is known by the origin server but not supported by the target resource.

I got a little farther. I discovered that my URL was missing the method to run in the service:

curl -X POST 'http://localhost:8080/rade/rest/v2/services/rade_ImportReportService/importReports' -H 'Authorization: Bearer <token>' --data-binary @OfferListing.zip

The “/importReports” was missing from my previous URL. Now I get the following reply:

{"error":"Server error","details":""}

Not very informative. :wink: However, the app.log contains:

2021-05-21 15:06:56.271 ERROR [http-nio-8080-exec-18/rade/admin] com.haulmont.addon.restapi.api.controllers.RestControllerExceptionHandler - Exception in REST controller
com.google.gson.JsonSyntaxException: com.google.gson.stream.MalformedJsonException: Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 646 path $

So, the problem seems to be that I need to pass some kind of JSON to the service. How do I know what that should be? And how do I put a ZIP file in it?

I think I finally got it! There are a bunch of moving parts. For this example, my service is named rade_importReportService with a method named importReport and a parameter named zipBytes.

  1. encode client:secret as listed in web-app.properties
  2. Use that to get a token
  3. create a JSON stream with the zip file I want to upload encoded with base64. Feed that to the method
  auth=$( echo -n "<CLIENT>:<SECRET> | base64 -w 0 )

  token=$(curl -X POST -H "Content-type: application/x-www-form-urlencoded" -H "Authorization: Basic $auth" -d "grant_type=password&username=<USERNAME>&password=<PASSWORD>" <app URL>/rest/v2/oauth/token | python3 -c 'import json,sys;  obj=json.load(sys.stdin); print(obj["access_token"])' )

  (echo -n '{"zipBytes":'; base64 -w0 <ZIPFILE>; echo -n '"}') | tr -d "\n" | curl -X POST <app URL>/rest/v2/services/rade_ImportReportService/importReports -H "Authorization: Bearer $token" -H "Content-type: application/json" -d @- 

Of course, the JSON, service name and method name are specific to my particular need. You need to fill in <app URL>, <USERNAME>, <PASSWORD>, <CLIENT>, <SECRET> as defined in your setup.

The last thing I needed to do was to go into the Security system, create a role with REST-API access along with access to the entities required and assign that to my <USERNAME> user.

This presumes you have python3 installed on your system. Feel free to use any method you like to parse the json for the access_token field.

If anybody has an easier way to do this, please let me know. Otherwise, I hope somebody else finds this useful someday.

2 Likes

Documentation: rest_api_v2_custom_controllers

  1. modules/web/src/com/company/app/controller/ReportsController.java
package com.company.app.controller;

import com.haulmont.reports.app.service.ReportService;
import com.haulmont.reports.entity.Report;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.inject.Inject;
import java.util.Collection;
import java.util.Map;

@RestController
@RequestMapping("/v1/cuba/reports")
public class ReportsController {
    @Inject
    private ReportService reportService;

    @PostMapping("/import")
    public ResponseEntity importReports(@RequestParam("file") MultipartFile file) {
        byte[] zipBytes;
        Collection<Report> reports;

        try {
            zipBytes = file.getBytes();
        } catch (Exception ex) {
            return ResponseEntity.badRequest().body(Map.of("error", HttpStatus.BAD_REQUEST.getReasonPhrase(), "detail", ex.getMessage()));
        }

        try {
            reports = reportService.importReports(zipBytes);
            if (reports.isEmpty()) {
                return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of("error", HttpStatus.NOT_FOUND.getReasonPhrase(), "detail", "imported reports " + reports.size()));
            }
            return ResponseEntity.ok().body(Map.of("result", "success", "detail", "imported reports " + reports.size()));
        } catch (Exception ex) {
            return ResponseEntity.internalServerError().body(Map.of("error", HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), "detail", ex.getMessage()));
        }
    }
}

  1. modules/web/src/com/company/app/rest-dispatcher-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:security="http://www.springframework.org/schema/security">

    <!-- Define a base package for your controllers-->
    <context:component-scan base-package="com.company.app.controller"/>

    <security:http pattern="/rest/v1/cuba/reports/**"
                   create-session="stateless"
                   entry-point-ref="oauthAuthenticationEntryPoint"
                   xmlns="http://www.springframework.org/schema/security">
        <!-- Specify one or more protected URL patterns-->
        <intercept-url pattern="/rest/v1/cuba/reports/**" access="isAuthenticated()"/>
        <anonymous enabled="false"/>
        <csrf disabled="true"/>
        <cors configuration-source-ref="cuba_RestCorsSource"/>
        <custom-filter ref="resourceFilter" before="PRE_AUTH_FILTER"/>
        <custom-filter ref="cuba_AnonymousAuthenticationFilter" after="PRE_AUTH_FILTER"/>
    </security:http>
</beans>
  1. modules/web/src/com/company/app/web-app.properties
// add line:
cuba.restSpringContextConfig = +com/company/app/rest-dispatcher-spring.xml
  1. copy role “system-reports-full-access”, example “rest-reports-full-access”, and set permissions:
  • Security scope = REST
  • Specefic - REST API - Use REST API = allow
  1. create new user, example “rest_reports_user”, and set “role: rest-reports-full-access”
  2. example auth
curl -X POST 'http://localhost:8080/app/rest/v2/oauth/token' \
-H 'Content-type: application/x-www-form-urlencoded' \
-H 'Authorization: Basic Y2xpZW50OnNlY3JldA==' \
-d 'grant_type=password' \
-d 'username=rest_reports_user' \
-d 'password=rest_reports_password'
{
    "access_token": "baa70c61-952f-43f5-af38-ee7919e1cca5",
    "token_type": "bearer",
    "refresh_token": "2b5e464f-ea2d-460e-8a01-53fe2c5c431e",
    "expires_in": 43199,
    "scope": "rest-api"
}
  1. example import reports
curl -X POST 'http://localhost:8080/app/rest/v1/cuba/reports/import' \
-H 'Content-type: multipart/form-data' \
-H 'Authorization: Bearer baa70c61-952f-43f5-af38-ee7919e1cca5' \
-F 'file=@"/W:/home/user/reports/invoice.zip"'
{
    "result": "success",
    "detail": "imported reports 1"
}