com.wl4g.infra.common.minio.v8_4.MinioAdminClient Maven / Gradle / Ivy
The newest version!
/*
* MinIO Java SDK for Amazon S3 Compatible Cloud Storage,
* (C) 2021 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wl4g.infra.common.minio.v8_4;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.MapType;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.minio.Digest;
import io.minio.MinioProperties;
import io.minio.S3Escaper;
import io.minio.Signer;
import io.minio.Time;
import io.minio.credentials.Credentials;
import io.minio.credentials.Provider;
import io.minio.credentials.StaticProvider;
import io.minio.http.HttpUtils;
import io.minio.http.Method;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.bouncycastle.crypto.InvalidCipherTextException;
/** Client to perform MinIO administration operations. */
public class MinioAdminClient {
private enum Command {
ADD_USER("add-user"), USER_INFO("user-info"), LIST_USERS("list-users"), REMOVE_USER("remove-user"), ADD_CANNED_POLICY(
"add-canned-policy"), SET_USER_OR_GROUP_POLICY("set-user-or-group-policy"), LIST_CANNED_POLICIES(
"list-canned-policies"), REMOVE_CANNED_POLICY("remove-canned-policy"), SET_BUCKET_QUOTA(
"set-bucket-quota"), GET_BUCKET_QUOTA("get-bucket-quota");
private final String value;
private Command(String value) {
this.value = value;
}
public String toString() {
return this.value;
}
}
private static final long DEFAULT_CONNECTION_TIMEOUT = TimeUnit.MINUTES.toMillis(1);
private static final MediaType DEFAULT_MEDIA_TYPE = MediaType.parse("application/octet-stream");
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private String userAgent = MinioProperties.INSTANCE.getDefaultUserAgent();
private PrintWriter traceStream;
private HttpUrl baseUrl;
private String region;
private Provider provider;
private OkHttpClient httpClient;
private MinioAdminClient(HttpUrl baseUrl, String region, Provider provider, OkHttpClient httpClient) {
this.baseUrl = baseUrl;
this.region = region;
this.provider = provider;
this.httpClient = httpClient;
}
private Credentials getCredentials() {
Credentials creds = provider.fetch();
if (creds == null)
throw new RuntimeException("Credential provider returns null credential");
return creds;
}
private Response execute(Method method, Command command, Multimap queryParamMap, byte[] body)
throws InvalidKeyException, IOException, NoSuchAlgorithmException {
Credentials creds = getCredentials();
HttpUrl.Builder urlBuilder = this.baseUrl.newBuilder().host(this.baseUrl.host()).addEncodedPathSegments(
S3Escaper.encodePath("minio/admin/v3/" + command.toString()));
if (queryParamMap != null) {
for (Map.Entry entry : queryParamMap.entries()) {
urlBuilder.addEncodedQueryParameter(S3Escaper.encode(entry.getKey()), S3Escaper.encode(entry.getValue()));
}
}
HttpUrl url = urlBuilder.build();
Request.Builder requestBuilder = new Request.Builder();
requestBuilder.url(url);
requestBuilder.header("Host", HttpUtils.getHostHeader(url));
requestBuilder.header("Accept-Encoding", "identity"); // Disable default
// gzip
// compression.
requestBuilder.header("User-Agent", this.userAgent);
requestBuilder.header("x-amz-date", ZonedDateTime.now().format(Time.AMZ_DATE_FORMAT));
if (creds.sessionToken() != null) {
requestBuilder.header("X-Amz-Security-Token", creds.sessionToken());
}
if (body == null && (method != Method.GET && method != Method.HEAD)) {
body = HttpUtils.EMPTY_BODY;
}
if (body != null) {
requestBuilder.header("x-amz-content-sha256", Digest.sha256Hash(body, body.length));
requestBuilder.method(method.toString(), RequestBody.create(body, DEFAULT_MEDIA_TYPE));
} else {
requestBuilder.header("x-amz-content-sha256", Digest.ZERO_SHA256_HASH);
}
Request request = requestBuilder.build();
request = Signer.signV4S3(request, region, creds.accessKey(), creds.secretKey(), request.header("x-amz-content-sha256"));
PrintWriter traceStream = this.traceStream;
if (traceStream != null) {
StringBuilder traceBuilder = new StringBuilder();
traceBuilder.append("---------START-HTTP---------\n");
String encodedPath = request.url().encodedPath();
String encodedQuery = request.url().encodedQuery();
if (encodedQuery != null)
encodedPath += "?" + encodedQuery;
traceBuilder.append(request.method()).append(" ").append(encodedPath).append(" HTTP/1.1\n");
traceBuilder
.append(request.headers().toString().replaceAll("Signature=([0-9a-f]+)", "Signature=*REDACTED*").replaceAll(
"Credential=([^/]+)", "Credential=*REDACTED*"));
if (body != null)
traceBuilder.append("\n").append(new String(body, StandardCharsets.UTF_8));
traceStream.println(traceBuilder.toString());
}
OkHttpClient httpClient = this.httpClient;
Response response = httpClient.newCall(request).execute();
if (traceStream != null) {
String trace = response.protocol().toString().toUpperCase(Locale.US) + " " + response.code() + "\n"
+ response.headers();
traceStream.println(trace);
ResponseBody responseBody = response.peekBody(1024 * 1024);
traceStream.println(responseBody.string());
traceStream.println("----------END-HTTP----------");
}
if (response.isSuccessful())
return response;
throw new RuntimeException("Request failed with response: " + response.body().string());
}
/**
* Adds a user with the specified access and secret key.
*
* @param accessKey
* Access key.
* @param status
* Status.
* @param secretKey
* Secret key.
* @param policyName
* Policy name.
* @param memberOf
* List of group.
* @throws NoSuchAlgorithmException
* thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws InvalidKeyException
* thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException
* thrown to indicate I/O error on MinIO REST operation.
* @throws InvalidCipherTextException
* thrown to indicate data cannot be encrypted/decrypted.
*/
public void addUser(
@Nonnull String accessKey,
@Nonnull UserInfo.Status status,
@Nullable String secretKey,
@Nullable String policyName,
@Nullable List memberOf)
throws NoSuchAlgorithmException, InvalidKeyException, IOException, InvalidCipherTextException {
if (accessKey == null || accessKey.isEmpty()) {
throw new IllegalArgumentException("access key must be provided");
}
UserInfo userInfo = new UserInfo(status, secretKey, policyName, memberOf);
Credentials creds = getCredentials();
try (Response response = execute(Method.PUT, Command.ADD_USER, ImmutableMultimap.of("accessKey", accessKey),
Crypto.encrypt(creds.secretKey(), OBJECT_MAPPER.writeValueAsBytes(userInfo)))) {
}
}
/**
* Obtains user info for a specified MinIO user.
*
* @param accessKey
* Access Key.
* @return user info for the specified accessKey.
* @throws NoSuchAlgorithmException
* thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws InvalidKeyException
* thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException
* thrown to indicate I/O error on MinIO REST operation.
*/
public UserInfo getUserInfo(String accessKey) throws NoSuchAlgorithmException, InvalidKeyException, IOException {
try (Response response = execute(Method.GET, Command.USER_INFO, ImmutableMultimap.of("accessKey", accessKey), null)) {
byte[] jsonData = response.body().bytes();
return OBJECT_MAPPER.readValue(jsonData, UserInfo.class);
}
}
/**
* Obtains a list of all MinIO users.
*
* @return List of all users.
* @throws NoSuchAlgorithmException
* thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws InvalidKeyException
* thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException
* thrown to indicate I/O error on MinIO REST operation.
* @throws InvalidCipherTextException
* thrown to indicate data cannot be encrypted/decrypted.
*/
public Map listUsers()
throws NoSuchAlgorithmException, InvalidKeyException, IOException, InvalidCipherTextException {
try (Response response = execute(Method.GET, Command.LIST_USERS, null, null)) {
Credentials creds = getCredentials();
byte[] jsonData = Crypto.decrypt(creds.secretKey(), response.body().bytes());
MapType mapType = OBJECT_MAPPER.getTypeFactory().constructMapType(HashMap.class, String.class, UserInfo.class);
return OBJECT_MAPPER.readValue(jsonData, mapType);
}
}
/**
* Deletes a user by it's access key
*
* @param accessKey
* Access Key.
* @throws NoSuchAlgorithmException
* thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws InvalidKeyException
* thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException
* thrown to indicate I/O error on MinIO REST operation.
*/
public void deleteUser(@Nonnull String accessKey) throws NoSuchAlgorithmException, InvalidKeyException, IOException {
if (accessKey == null || accessKey.isEmpty()) {
throw new IllegalArgumentException("access key must be provided");
}
try (Response response = execute(Method.DELETE, Command.REMOVE_USER, ImmutableMultimap.of("accessKey", accessKey),
null)) {
}
}
/**
* set bucket quota size
*
* @param bucketName
* bucketName
* @param size
* the capacity of the bucket
* @param unit
* the quota unit of the size argument
* @throws NoSuchAlgorithmException
* thrown to indicate missing of MD5 or SHA-256 digest library.
* @throws InvalidKeyException
* thrown to indicate missing of HMAC SHA-256 library.
* @throws IOException
* thrown to indicate I/O error on MinIO REST operation.
*/
public void setBucketQuota(@Nonnull String bucketName, long size, @Nonnull QuotaUnit unit)
throws IOException, NoSuchAlgorithmException, InvalidKeyException {
Map quotaEntity = new HashMap<>();
if (size > 0) {
quotaEntity.put("quotatype", "hard");
}
quotaEntity.put("quota", unit.toBytes(size));
try (Response response = execute(Method.PUT, Command.SET_BUCKET_QUOTA, ImmutableMultimap.of("bucket", bucketName),
OBJECT_MAPPER.writeValueAsBytes(quotaEntity))) {
}
}
/**
* get bucket quota size
*
* @param bucketName
* bucketName
* @return bytes of bucket
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
*/
public long getBucketQuota(String bucketName) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
try (Response response = execute(Method.GET, Command.GET_BUCKET_QUOTA, ImmutableMultimap.of("bucket", bucketName),
null)) {
MapType mapType = OBJECT_MAPPER.getTypeFactory().constructMapType(HashMap.class, String.class, JsonNode.class);
return OBJECT_MAPPER.