Beta version of storage provider that saves/loads avatars from a S3 bucket
This commit is contained in:
parent
157e5f46aa
commit
382cbe5f8d
|
@ -0,0 +1,126 @@
|
|||
package org.gcube.keycloak.avatar.storage.s3;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import org.gcube.keycloak.avatar.storage.AvatarStorageProvider;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
|
||||
import io.minio.GetObjectArgs;
|
||||
import io.minio.MinioClient;
|
||||
import io.minio.PutObjectArgs;
|
||||
import io.minio.RemoveObjectArgs;
|
||||
import io.minio.errors.ErrorResponseException;
|
||||
|
||||
public class MinioAvatarStorageProvider implements AvatarStorageProvider {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(MinioAvatarStorageProvider.class);
|
||||
|
||||
private static final String AVATAR_FOLDER = "avatar";
|
||||
private static final int CHUNK_SIZE = 5242880;
|
||||
|
||||
private final Configuration configuration;
|
||||
|
||||
public MinioAvatarStorageProvider(Configuration configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAvatarImage(RealmModel realmModel, UserModel userModel, InputStream input) {
|
||||
execute(minioClient -> minioClient
|
||||
.putObject(PutObjectArgs.builder().bucket(configuration.rootBucket).object(getAvatarFilePath(
|
||||
realmModel, userModel)).stream(input, -1, CHUNK_SIZE).build()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream loadAvatarImage(RealmModel realmModel, UserModel userModel) {
|
||||
logger.debug("Loading avatar from S3");
|
||||
return execute(new Executor<InputStream>() {
|
||||
public InputStream execute(MinioClient minioClient) throws Exception {
|
||||
try {
|
||||
return minioClient
|
||||
.getObject(GetObjectArgs.builder().bucket(configuration.rootBucket).object(getAvatarFilePath(
|
||||
realmModel, userModel)).build());
|
||||
} catch (ErrorResponseException e) {
|
||||
if (e.response().code() == 404) {
|
||||
logger.debugf("Avatar file not found for user '%s' in realm '%s'", userModel.getUsername(), realmModel.getName());
|
||||
return null;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAvatarImage(RealmModel realmModel, UserModel userModel) {
|
||||
logger.debug("Deeleting avatar from S3");
|
||||
execute(minioClient -> {
|
||||
minioClient.removeObject(RemoveObjectArgs.builder().bucket(configuration.rootBucket)
|
||||
.object(getAvatarFilePath(realmModel, userModel)).build());
|
||||
return true;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
public String getAvatarFilePath(RealmModel realmModel, UserModel userModel) {
|
||||
return AVATAR_FOLDER + "/" + realmModel.getName() + "/" + userModel.getUsername();
|
||||
}
|
||||
|
||||
public <T> T execute(Executor<T> executor) {
|
||||
try {
|
||||
MinioClient minioClient = MinioClient.builder()
|
||||
.endpoint(configuration.getServerUrl())
|
||||
.credentials(configuration.getAccessKey(), configuration.getSecretKey())
|
||||
.build();
|
||||
|
||||
return executor.execute(minioClient);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Executing operation on S3 persistnce", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Configuration {
|
||||
|
||||
private final String serverUrl;
|
||||
private final String accessKey;
|
||||
private final String secretKey;
|
||||
private final String rootBucket;
|
||||
|
||||
public Configuration(String serverUrl, String accessKey, String secretKey, String rootBucket) {
|
||||
this.serverUrl = serverUrl;
|
||||
this.accessKey = accessKey;
|
||||
this.secretKey = secretKey;
|
||||
this.rootBucket = rootBucket;
|
||||
}
|
||||
|
||||
public String getServerUrl() {
|
||||
return serverUrl;
|
||||
}
|
||||
|
||||
public String getAccessKey() {
|
||||
return accessKey;
|
||||
}
|
||||
|
||||
public String getSecretKey() {
|
||||
return secretKey;
|
||||
}
|
||||
|
||||
public String getRootBucket() {
|
||||
return rootBucket;
|
||||
}
|
||||
}
|
||||
|
||||
public interface Executor<T> {
|
||||
|
||||
T execute(MinioClient minioClient) throws Exception;
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package org.gcube.keycloak.avatar.storage.s3;
|
||||
|
||||
import org.gcube.keycloak.avatar.storage.AvatarStorageProvider;
|
||||
import org.gcube.keycloak.avatar.storage.AvatarStorageProviderFactory;
|
||||
import org.gcube.keycloak.avatar.storage.s3.MinioAvatarStorageProvider.Configuration;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
public class MinioAvatarStorageProviderFactory implements AvatarStorageProviderFactory {
|
||||
|
||||
private Configuration minioConfig;
|
||||
|
||||
@Override
|
||||
public AvatarStorageProvider create(KeycloakSession session) {
|
||||
return new MinioAvatarStorageProvider(minioConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
String serverUrl = config.get("server-url");
|
||||
String accessKey = config.get("access-key");
|
||||
String secretKey = config.get("secret-key");
|
||||
String rootBucket = config.get("root-bucket");
|
||||
|
||||
this.minioConfig = new Configuration(serverUrl, accessKey, secretKey, rootBucket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "avatar-storage-s3";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package org.gcube.keycloak.avatar.storage.s3;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
import org.gcube.keycloak.avatar.storage.s3.MinioAvatarStorageProvider.Configuration;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
public class MinioAvatarStorageTest {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(MinioAvatarStorageTest.class);
|
||||
|
||||
private static final String ENDPOINT_URL = "https://isti-cloud.isti.cnr.it:13808";
|
||||
private static final String ACCESS_KEY_ENV = "ACCESS_KEY";
|
||||
private static final String SECRET_KEY_ENV = "SECRET_KEY";
|
||||
private static final String BUCKET = "d4s-container-registry-dev";
|
||||
|
||||
@Test
|
||||
public void test() throws FileNotFoundException {
|
||||
String accessKey = System.getenv(ACCESS_KEY_ENV);
|
||||
String secretKey = System.getenv(SECRET_KEY_ENV);
|
||||
if (accessKey == null || secretKey == null) {
|
||||
logger.error("Cannot proceed with tests without access and secret keys");
|
||||
}
|
||||
Configuration minioConfig = new Configuration(ENDPOINT_URL, accessKey, secretKey, BUCKET);
|
||||
|
||||
MinioAvatarStorageProvider minioAvatarStorageProvider = new MinioAvatarStorageProvider(minioConfig);
|
||||
RealmModel realmModel = Mockito.mock(RealmModel.class);
|
||||
when(realmModel.getName()).thenReturn("testRealm");
|
||||
UserModel userModel = Mockito.mock(UserModel.class);
|
||||
when(userModel.getUsername()).thenReturn("test.user");
|
||||
minioAvatarStorageProvider.saveAvatarImage(realmModel, userModel,
|
||||
this.getClass().getClassLoader().getResourceAsStream("TM_Emoji.jpg"));
|
||||
|
||||
assertNotNull(minioAvatarStorageProvider.loadAvatarImage(realmModel, userModel));
|
||||
minioAvatarStorageProvider.deleteAvatarImage(realmModel, userModel);
|
||||
assertNull(minioAvatarStorageProvider.loadAvatarImage(realmModel, userModel));
|
||||
// Delete of a non existing resource must not raise an exception, assuring it with a proper test
|
||||
minioAvatarStorageProvider.deleteAvatarImage(realmModel, userModel);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<log4j:configuration
|
||||
xmlns="http://jakarta.apache.org/log4j/"
|
||||
xmlns:log4j="http://jakarta.apache.org/log4j/">
|
||||
|
||||
<appender name="console"
|
||||
class="org.apache.log4j.ConsoleAppender">
|
||||
<layout class="org.apache.log4j.PatternLayout">
|
||||
<param name="ConversionPattern"
|
||||
value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n" />
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
<logger name="org.gcube" additivity="false">
|
||||
<level value="TRACE" />
|
||||
<appender-ref ref="console" />
|
||||
</logger>
|
||||
|
||||
<root>
|
||||
<level value="INFO" />
|
||||
<appender-ref ref="console" />
|
||||
</root>
|
||||
|
||||
</log4j:configuration>
|
Binary file not shown.
After Width: | Height: | Size: 124 KiB |
Loading…
Reference in New Issue