Skip to main content

Console API

JuiceFS Web Console provides API for all file system interactions that a user can perform through a web browser, e.g. create file systems, browse file system, check the trash directory or configure client ACL.

Since the Web Console does not provide file system modification capabilities, you cannot do that with the APIs as well. If you need to programmatically modify your file systems, use S3 Gateway.

This chapter only introduces the API authentication process, check the API schema to learn the specifics.

Usage

Code example

import base64
import hashlib
import hmac
import json
import time
from typing import Dict, List, Literal, Optional, Union
from urllib.parse import quote_plus, urlencode, urlsplit

import requests

access_key = 'ac7418402ce0ce838ba87eb3a6be72af313cd7028e18007799c0d5651c326925'
secret_key = '5f0c5a5d51515947788fa7b8244acebe166aedd9de28b26ef716888a613c3d92'


def sign(
secret_key: str,
timestamp: int,
method: Literal['GET', 'POST', 'PATCH', 'PUT', 'DELETE'],
path: str,
headers: Dict[str, str],
query_params: Optional[Dict[str, Union[str, List[str]]]],
body: Optional[bytes],
) -> str:
sorted_headers = []
for h in ['Host']:
v = headers.get(h, '')
if not v:
raise ValueError(f'header {h} is required')
sorted_headers.append(f'{h.lower()}:{v}')
sorted_headers = '\n'.join(sorted_headers)
sorted_qs = ''
if query_params:
sorted_qs = sorted(query_params.items(), key=lambda item: item[0])
for i, (k, values) in enumerate(sorted_qs):
if not isinstance(values, list):
values = [values]
sorted_qs[i] = (k, sorted(values))
sorted_qs = urlencode(sorted_qs, doseq=True, safe='', quote_via=quote_plus)

payload_hash = hashlib.sha256(body).hexdigest() if body else ''
parts = [str(timestamp), method, path, sorted_headers, sorted_qs, payload_hash]
data = '\n'.join(parts)
signature = hmac.new(
secret_key.encode('utf-8'), data.encode('utf-8'), hashlib.sha256
).hexdigest()

return signature


def request(
method: Literal['GET', 'POST', 'PATCH', 'PUT', 'DELETE'],
url: str,
query_params: Optional[dict] = None,
data: Optional[dict] = None,
):
timestamp = int(time.time())
parts = urlsplit(url)
path = parts.path
headers = {'Host': parts.netloc}

req = requests.Request(method, url, params=query_params, json=data).prepare()
body = req.body or b''

print(f'secret_key: {secret_key[:10]}...{secret_key[-10:]}')
print(f'timestamp: {timestamp}')
print(f'method: {method}')
print(f'path: {path}')
print(f'headers: {headers}')
print(f'query_params: {query_params if query_params else "empty"}')
print(f'body: {body if body else "empty"}')

signature = sign(secret_key, timestamp, method, path, headers, query_params, body)
print(f'signature: {signature}')

auth = {
'access_key': access_key,
'timestamp': timestamp,
'signature': signature,
'version': 1,
}
token = base64.b64encode(json.dumps(auth).encode()).decode()
print(f'token: {token}')

req.headers['Authorization'] = token
resp = requests.Session().send(req)

print()
print(json.dumps(resp.json(), indent=2))


# API schema doc: https://juicefs.com/api/v1/docs


API_URL = 'http://localhost:8080/api/v1'


def get_volumes():
url = f'{API_URL}/volumes'
request('GET', url)


def delete_volume():
url = f'{API_URL}/volumes/1'
request('DELETE', url)


def get_volume_exports():
url = f'{API_URL}/volumes/1/exports'
request('GET', url)


def create_volume_export():
url = f'{API_URL}/volumes/1/exports'
request(
'POST',
url,
data={
'desc': 'for mount',
'iprange': '192.168.0.1/24',
'apionly': False,
'readonly': False,
'appendonly': False,
},
)


def update_volume_export():
url = f'{API_URL}/volumes/1/exports/1'
request('PUT', url, data={'desc': 'for mount', 'iprange': '192.168.100.1/24'})


def get_volume_quotas():
url = f'{API_URL}/volumes/1/quotas'
request('GET', url)


def create_volume_quota():
url = f'{API_URL}/volumes/1/quotas'
request(
'POST',
url,
data={'path': '/path/to/subdir', 'inodes': 1 << 20, 'size': 1 << 30},
)


def update_volume_quota():
url = f'{API_URL}/volumes/1/quotas/9'
request('PUT', url, data={'path': '/foo', 'size': 10 << 30})