Jindo CLI is an Alibaba Cloud command-line interface (CLI) for managing the OSS-HDFS service. This guide explains how to wrap Jindo CLI commands into API operations with Java Spring Boot to programmatically manage the OSS-HDFS service. We cover two architectures, centralized deployment and decoupled deployment, suitable for operations and maintenance (O&M) control systems, automation scripts, and CI/CD tools.
Deployment options
Option 1: Centralized deployment
The business application and the Jindo CLI tool are deployed on the same server and integrated through local process calls. As shown in the following figure, the business application on the business server directly calls Jindo CLI to access the OSS-HDFS service.
-
Advantages: Simple implementation with no network overhead.
-
Disadvantages: The CLI execution consumes resources on the business application's server.
-
Use cases: This option is suitable for quick verification, internal automation scripts, and single management platforms.
Option 2: Decoupled deployment
The business application (Server A) is separated from Jindo CLI (Server B). The CLI is wrapped as an independent HTTP Agent service, and the two components communicate through RESTful APIs. As shown in the following figure, the business application remotely calls the HTTP Agent. The Agent then calls Jindo CLI to access the OSS-HDFS service.
-
Advantages: The AccessKey is isolated on the tool server. The business application and the tool are decoupled, which supports independent upgrades and cross-language calls. The tool can serve multiple business platforms at the same time.
-
Disadvantages: This option requires additional maintenance for the Agent service and introduces network overhead. By default, RESTful API calls do not require authentication. You must implement security checks, such as an authentication token mechanism.
-
Use cases: This option is suitable for production environments, especially for systems with high scalability requirements and scenarios that serve multiple business platforms.
Prerequisites
Before you begin, make sure the following prerequisites are met.
-
OSS-HDFS service: You have a bucket with the OSS-HDFS service enabled. For more information, see Enable the OSS-HDFS service.
-
Permissions: A RAM user has permissions to access the OSS-HDFS service. Follow the principle of least privilege. For example, if you only need read permissions, do not grant write or delete permissions. For more information, see Grant permissions to access the OSS-HDFS service.
-
Server environment: You have at least one server, such as an ECS instance or a self-managed server. The server must be in the same region as the bucket, be able to access OSS over the internal network, and have the Java Development Kit (JDK) installed.
-
Jindo SDK: The Jindo SDK is installed and configured.
-
Centralized deployment: Install the SDK on the business server.
-
Decoupled deployment: Install the SDK only on the Agent server (Server B).
-
Option 1: Centralized deployment
The business application and Jindo CLI are on the same server. The business application directly calls Jindo CLI commands. This option is suitable for quick verification.
Step 1: Develop the backend API operation
Create a Spring Boot API operation that wraps the jindo fs -ls command.
The main command can vary based on the Jindo SDK version. Some recent versions use jindo, while some earlier versions use jindofs.
Before you copy the code, we recommend that you manually run jindo -v or jindofs -v in the command line on your server to confirm the correct command for your environment. All examples in this article use the jindo command.
package com.example.jindotest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
@RestController
public class JindoController {
@PostMapping("/jindo-ls")
public ResponseEntity<String> executeJindoLs(@RequestParam("path") String path) {
// Validate the path format.
if (path == null || !path.startsWith("oss://")) {
return ResponseEntity.badRequest().body("Invalid OSS path");
}
try {
// Build the Jindo CLI command.
ProcessBuilder processBuilder = new ProcessBuilder("jindo", "fs", "-ls", path);
processBuilder.redirectErrorStream(true);
// Start the process.
Process process = processBuilder.start();
// Read the command output.
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder result = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
result.append(line).append("\n");
}
// Wait for the process to finish and check the exit code.
int exitCode = process.waitFor();
if (exitCode == 0) {
// Return the command execution result.
return ResponseEntity.ok(result.toString());
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Command failed with exit code: " + exitCode);
}
} catch (IOException | InterruptedException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("Error executing command: " + e.getMessage());
}
}
}
Step 2: Verify the results
After deployment, use one of the following methods to verify the integration.
-
Verify on the web interface
Refer to the sample web interface code in the Appendix. Change the request path in the code to
/jindo-ls. Accesshttp://<public IP of the business server>:8080to test the integration. You must open port 8080 for inbound traffic on the business server. Enter a file path, such asoss://my-bucket.cn-hangzhou.oss-dls.aliyuncs.com/, and click "List Files". The integration is successful if the interface displays the OSS file list. -
Verify with an API call (alternative)
If you cannot deploy a frontend, you can use
curlto test the backend API operation directly.curl -X POST "http://localhost:8080/jindo-ls?path=oss://<bucket-name>.<oss-hdfs-endpoint>/"If a list of files is returned, the integration is successful.
After verification, you can integrate other commands. For more information, see Common Jindo CLI commands in the appendix.
Option 2: Decoupled deployment
This option is recommended for a production environment. In this architecture, Jindo CLI is wrapped as an independent Agent service to separate responsibilities. This architecture is suitable for an internal network or scenarios with relatively low security requirements. For scenarios with high security requirements, you must implement an API access verification mechanism.
Step 1: Start the Agent service on Server B
-
Create the Agent script
Create a
JindoCliHttpAgent.pyfile. The script is implemented in Python 3. You can use a similar approach for other technology stacks.#!/usr/bin/env python3 # -*- coding: utf-8 -*- import http.server import json import subprocess import socketserver import shlex class CommandHandler(http.server.SimpleHTTPRequestHandler): def do_POST(self): if self.path == '/execute': # Read the request body. content_length = int(self.headers.get('Content-Length', 0)) post_data = self.rfile.read(content_length) try: # Parse the JSON request. data = json.loads(post_data) command = data.get('command', '') # Validate the command parameter. if not command: self.send_response(400) self.send_header('Content-Type', 'application/json') self.end_headers() self.wfile.write(json.dumps({'error': 'Missing "command" parameter'}).encode('utf-8')) return # Securely execute the command. command_list = shlex.split(command) output = subprocess.check_output(command_list, stderr=subprocess.STDOUT) output_str = output.decode('utf-8').strip() # Return the execution result. self.send_response(200) self.send_header('Content-Type', 'application/json') self.end_headers() self.wfile.write(json.dumps({'output': output_str}).encode('utf-8')) except Exception as e: # Return the error message. self.send_response(500) self.send_header('Content-Type', 'application/json') self.end_headers() self.wfile.write(json.dumps({'error': str(e)}).encode('utf-8')) else: self.send_error(404, 'Not Found') # Multi-threaded HTTP server. class ThreadedHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer): pass if __name__ == '__main__': # Start the HTTP Agent service. server_address = ('0.0.0.0', 8000) httpd = ThreadedHTTPServer(server_address, CommandHandler) print('Starting secure HTTP Agent on port 8000...') httpd.serve_forever() -
Configure the firewall
Open port 8000 for inbound traffic on Server B. Allow access only from the private IP of the business server (Server A) and deny public network access.
-
Add execute permissions
chmod +x JindoCliHttpAgent.py -
Start the HTTP Agent service
python3 JindoCliHttpAgent.pyAfter the service starts, it listens for requests on port 8000. The startup output will be similar to the following:
$ python JindoCliHttpAgent.py Starting HTTP Agent on port 8000... -
Verify the Agent service
Open a new terminal to test whether the Agent is working properly. Replace <Bucketname> and <EndPoint> with the actual values. For example:
oss://my-bucket.cn-hangzhou.oss-dls.aliyuncs.com/curl -X POST http://localhost:8000/execute \ -H "Content-Type: application/json" \ -d '{"command": "jindo fs -ls oss://<bucket-name>.<oss-hdfs-endpoint>/"}'If a file list in JSON format is returned, as shown in the following example, the Agent is deployed successfully.
$curl -X POST http://localhost:8000/execute \ > -H "Content-Type: application/json" \ > -d '{"command": "jindo fs -ls oss://xxx.cn-hangzhou.oss-dls.aliyuncs.com/"}' {"output": "drwxrwxrwx\tadmin \t2025-08-21 15:35\toss://xxx.cn-hangzhou.oss-dls.aliyuncs.com/.sysinfo\n-rwxrwxrwx\tadmin \t2025-08-21 15:51\toss://xxx.cn-hangzhou.oss-dls.aliyuncs.com/a.txt\n-rwxrwxrwx\tadmin \t \t2025-08-21 16:41\toss://xxx.cn-hangzhou.oss-dls.aliyuncs.com/local_file.txt\ndrwxrwxrwx\tadmin \t2025-08-21 17:30\toss://xxx.cn-hangzhou.oss-dls.aliyuncs.com/test/"}
Step 2: Integrate with the business system on Server A
The main command can vary based on the Jindo SDK version. Some recent versions use jindo, while some earlier versions use jindofs.
Before you copy the code, we recommend that you first manually run jindo -v or jindofs -v on the command line of your server to confirm the correct command for your environment. All examples in this topic use jindo.
Create a Java API operation to call the Agent service. Replace <private IP of Server B> in the code with the actual private IP address of Server B.
package com.example.jindotest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
@RestController
public class JindoAgentController {
@PostMapping("/jindo-agent-ls")
public ResponseEntity<String> executeJindoLs(@RequestParam String path) {
// Validate the path format.
if (path == null || !path.startsWith("oss://")) {
return ResponseEntity.badRequest().body("Invalid OSS path");
}
try {
// Construct the JSON request body.
String requestBody = String.format("{\"command\": \"jindo fs -ls %s\"}", path);
// Call the Agent service by using curl.
ProcessBuilder processBuilder = new ProcessBuilder(
"curl", "-X", "POST", "http://<private IP of Server B>:8000/execute",
"-H", "Content-Type: application/json",
"-d", requestBody
);
// Start the process.
Process process = processBuilder.start();
// Read the output stream.
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder result = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
result.append(line).append("\n");
}
// Check the execution result.
int exitCode = process.waitFor();
if (exitCode == 0) {
return ResponseEntity.ok(result.toString());
} else {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Command failed with exit code: " + exitCode);
}
} catch (IOException | InterruptedException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error executing command: " + e.getMessage());
}
}
}
In a production environment, use an HTTP client library, such as RestTemplate or WebClient, instead of curl to achieve better performance and error handling.
Step 3: Verify the results
-
Check network connectivity
If you use a decoupled deployment, you must ensure network connectivity between the servers. On Server A, execute
ping <private IP of Server B>. A stable ping response with no packet loss proves that the two servers are connected.[ecs-assist-user@ixxx ~]$ ping 1xxx PING xxx (xxx) 56(84) bytes of data. 64 bytes from 1xxx: icmp_seq=1 ttl=64 time=0.212 ms 64 bytes from 1xxx: icmp_seq=2 ttl=64 time=0.138 ms 64 bytes from 1xxx: icmp_seq=3 ttl=64 time=0.117 ms 64 bytes from 1xxx: icmp_seq=4 ttl=64 time=0.130 ms ^C --- 1xxx ping statistics --- 4 packets transmitted, 4 received, 0% packet loss, time 3000ms rtt min/avg/max/mdev = 0.117/0.149/0.212/0.038 ms -
Verify on the web interface
Refer to the sample web interface code in the Appendix. Change the request path in the code to
/jindo-agent-ls. Accesshttp://<public IP of Server A>:8080to test the integration. You must open port 8080 for inbound traffic on Server A. Enter a file path, such asoss://my-bucket.cn-hangzhou.oss-dls.aliyuncs.com/, and click "List Files". The integration is successful if the interface displays the OSS file list. -
Verify with an API call (alternative)
If you cannot deploy a frontend, you can use
curlto test the backend API operation directly.curl -X POST "http://localhost:8080/jindo-agent-ls?path=oss://<bucket-name>.<oss-hdfs-endpoint>/"If a list of files is returned, the integration is successful.
After verification, you can integrate other commands. For more information, see Common Jindo CLI commands in the appendix.
Security recommendations
Jindo CLI can manage OSS-HDFS service configurations and delete data. Improper use can have serious consequences. Before you deploy the service in a production environment, test it thoroughly in a test environment.
Core principles
-
Least privilege: Grant only the minimum permissions required to complete a task. Avoid excessive authorization.
-
Access control: Implement strict authentication and authorization mechanisms on the client side.
-
Concurrency control: Limit the number of concurrent operations to prevent configuration conflicts and data management issues.
Network security
-
Decoupled deployment: In a decoupled deployment, allow the Agent server to be accessed only from the private IP of the business server. Do not expose the Agent server to the internet.
-
Port management: Use a security group or firewall to precisely control access to ports.
References
Appendix
Common Jindo CLI commands
|
Command |
Description |
Example |
|
stat |
Displays the status of a file. |
|
|
ls |
Lists the files in a directory. The optional -R parameter recursively lists files. |
|
|
du |
Displays the size of all files in a directory. The following parameters are optional:
|
|
|
count |
Displays the file size and the number of files. Use the optional -h parameter to display the file size in a human-readable format. |
|
|
listUserGroupsMappings |
Lists all user and group mappings. |
|
|
dumpInventory |
Exports file metadata. |
|
|
putConfig |
Sets service features, such as directory protection. |
|
|
getConfig |
Gets configuration information, such as directory protection information. |
|
Sample web interface code
Save the following code as index.html and place it in the src/main/resources/static/ directory of your Spring Boot project:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Jindo CLI Management Interface</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1000px;
margin: 50px auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background-color: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
border-bottom: 2px solid #007bff;
padding-bottom: 10px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: #555;
}
input[type="text"] {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
box-sizing: border-box;
}
button {
background-color: #007bff;
color: white;
padding: 12px 24px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
margin-right: 10px;
}
button:hover {
background-color: #0056b3;
}
button:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
.result {
margin-top: 20px;
padding: 15px;
border: 1px solid #ddd;
border-radius: 4px;
background-color: #f8f9fa;
min-height: 100px;
font-family: monospace;
white-space: pre-wrap;
font-size: 14px;
}
.loading {
color: #007bff;
font-style: italic;
}
.error {
color: #dc3545;
background-color: #f8d7da;
border-color: #f5c6cb;
}
.success {
color: #155724;
background-color: #d4edda;
border-color: #c3e6cb;
}
</style>
</head>
<body>
<div class="container">
<h1> Jindo CLI Management Interface</h1>
<div class="form-group">
<label for="ossPath">OSS Path:</label>
<input type="text" id="ossPath"
value="oss://your-bucket-name.cn-hangzhou.oss-dls.aliyuncs.com/"
placeholder="Enter OSS path (e.g., oss://bucket-name.endpoint/)">
</div>
<div class="form-group">
<button onclick="listFiles()">List Files (ls)</button>
<button onclick="clearResult()">Clear Result</button>
</div>
<div id="result" class="result">
Ready to execute Jindo CLI commands...
</div>
</div>
<script>
function listFiles() {
const path = document.getElementById('ossPath').value;
const resultDiv = document.getElementById('result');
if (!path || !path.startsWith('oss://')) {
resultDiv.innerHTML = 'Error: Please enter a valid OSS path starting with "oss://"';
resultDiv.className = 'result error';
return;
}
// Show loading
resultDiv.innerHTML = 'Loading... Please wait';
resultDiv.className = 'result loading';
// Call backend API
fetch('/jindo-ls', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'path=' + encodeURIComponent(path)
})
.then(response => {
if (response.ok) {
return response.text();
} else {
throw new Error('HTTP ' + response.status + ': ' + response.statusText);
}
})
.then(data => {
resultDiv.innerHTML = 'Success:\n\n' + data;
resultDiv.className = 'result success';
})
.catch(error => {
resultDiv.innerHTML = 'Error:\n\n' + error.message;
resultDiv.className = 'result error';
});
}
function clearResult() {
document.getElementById('result').innerHTML = 'Ready to execute Jindo CLI commands...';
document.getElementById('result').className = 'result';
}
// Allow Enter key to trigger listFiles
document.getElementById('ossPath').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
listFiles();
}
});
</script>
</body>
</html>