/*
* Copyright 2014 the original author or authors.
*
* 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 org.gradle.internal.resource.transport.aws.s3;
import java.io.InputStream;
import java.net.URI;
import java.util.List;
import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.BasicSessionCredentials;
import com.amazonaws.regions.Region;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.S3ClientOptions;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.amazonaws.services.s3.model.ListObjectsRequest;
import com.amazonaws.services.s3.model.ObjectListing;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.S3Object;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import org.gradle.api.GradleException;
import org.gradle.api.Incubating;
import org.gradle.api.JavaVersion;
import org.gradle.api.artifacts.repositories.PasswordCredentials;
import org.gradle.api.credentials.AwsCredentials;
import org.gradle.internal.resource.ResourceExceptions;
import org.gradle.internal.resource.transport.http.HttpProxySettings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class S3Client {
private static final Logger LOGGER = LoggerFactory.getLogger(S3Client.class);
private S3ResourceResolver resourceResolver = new S3ResourceResolver();
private AmazonS3Client amazonS3Client;
private final S3ConnectionProperties s3ConnectionProperties;
public S3Client(AmazonS3Client amazonS3Client, S3ConnectionProperties s3ConnectionProperties) {
this.s3ConnectionProperties = s3ConnectionProperties;
this.amazonS3Client = amazonS3Client;
}
/**
* Constructor without privided credentials to deleguate to the default provider chain.
* @since 3.1
*/
@Incubating
public S3Client(S3ConnectionProperties s3ConnectionProperties) {
this.s3ConnectionProperties = s3ConnectionProperties;
amazonS3Client = new AmazonS3Client(createConnectionProperties());
setAmazonS3ConnectionEndpoint();
}
public S3Client(AwsCredentials awsCredentials, S3ConnectionProperties s3ConnectionProperties) {
this.s3ConnectionProperties = s3ConnectionProperties;
AWSCredentials credentials = null;
if (awsCredentials != null) {
if (awsCredentials.getSessionToken() == null) {
credentials = new BasicAWSCredentials(awsCredentials.getAccessKey(), awsCredentials.getSecretKey());
} else {
credentials = new BasicSessionCredentials(awsCredentials.getAccessKey(), awsCredentials.getSecretKey(), awsCredentials.getSessionToken());
}
}
amazonS3Client = new AmazonS3Client(credentials, createConnectionProperties());
setAmazonS3ConnectionEndpoint();
}
private void setAmazonS3ConnectionEndpoint() {
S3ClientOptions.Builder clientOptionsBuilder = S3ClientOptions.builder();
Optional<URI> endpoint = s3ConnectionProperties.getEndpoint();
if (endpoint.isPresent()) {
amazonS3Client.setEndpoint(endpoint.get().toString());
clientOptionsBuilder.setPathStyleAccess(true);
}
amazonS3Client.setS3ClientOptions(clientOptionsBuilder.build());
}
private void checkRequiredJigsawModuleIsOnPath() {
if (JavaVersion.current().isJava9Compatible()) {
try {
Class.forName("javax.xml.bind.DatatypeConverter");
} catch (ClassNotFoundException e) {
throw new GradleException("Cannot publish to S3 since the module 'java.xml.bind' is not available. "
+ "Please add \"-addmods java.xml.bind '-Dorg.gradle.jvmargs=-addmods java.xml.bind'\" to your GRADLE_OPTS.");
}
}
}
private ClientConfiguration createConnectionProperties() {
ClientConfiguration clientConfiguration = new ClientConfiguration();
Optional<HttpProxySettings.HttpProxy> proxyOptional = s3ConnectionProperties.getProxy();
if (proxyOptional.isPresent()) {
HttpProxySettings.HttpProxy proxy = s3ConnectionProperties.getProxy().get();
clientConfiguration.setProxyHost(proxy.host);
clientConfiguration.setProxyPort(proxy.port);
PasswordCredentials credentials = proxy.credentials;
if (credentials != null) {
clientConfiguration.setProxyUsername(credentials.getUsername());
clientConfiguration.setProxyPassword(credentials.getPassword());
}
}
Optional<Integer> maxErrorRetryCount = s3ConnectionProperties.getMaxErrorRetryCount();
if (maxErrorRetryCount.isPresent()) {
clientConfiguration.setMaxErrorRetry(maxErrorRetryCount.get());
}
return clientConfiguration;
}
public void put(InputStream inputStream, Long contentLength, URI destination) {
checkRequiredJigsawModuleIsOnPath();
try {
S3RegionalResource s3RegionalResource = new S3RegionalResource(destination);
String bucketName = s3RegionalResource.getBucketName();
String s3BucketKey = s3RegionalResource.getKey();
configureClient(s3RegionalResource);
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentLength(contentLength);
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, s3BucketKey, inputStream, objectMetadata);
LOGGER.debug("Attempting to put resource:[{}] into s3 bucket [{}]", s3BucketKey, bucketName);
amazonS3Client.putObject(putObjectRequest);
} catch (AmazonClientException e) {
throw ResourceExceptions.putFailed(destination, e);
}
}
public S3Object getMetaData(URI uri) {
LOGGER.debug("Attempting to get s3 meta-data: [{}]", uri.toString());
//Would typically use GetObjectMetadataRequest but it does not work with v4 signatures
return doGetS3Object(uri, true);
}
public S3Object getResource(URI uri) {
LOGGER.debug("Attempting to get s3 resource: [{}]", uri.toString());
return doGetS3Object(uri, false);
}
public List<String> listDirectChildren(URI parent) {
S3RegionalResource s3RegionalResource = new S3RegionalResource(parent);
String bucketName = s3RegionalResource.getBucketName();
String s3BucketKey = s3RegionalResource.getKey();
configureClient(s3RegionalResource);
ListObjectsRequest listObjectsRequest = new ListObjectsRequest()
.withBucketName(bucketName)
.withPrefix(s3BucketKey)
.withMaxKeys(1000)
.withDelimiter("/");
ObjectListing objectListing = amazonS3Client.listObjects(listObjectsRequest);
ImmutableList.Builder<String> builder = ImmutableList.builder();
builder.addAll(resourceResolver.resolveResourceNames(objectListing));
while (objectListing.isTruncated()) {
objectListing = amazonS3Client.listNextBatchOfObjects(objectListing);
builder.addAll(resourceResolver.resolveResourceNames(objectListing));
}
return builder.build();
}
private S3Object doGetS3Object(URI uri, boolean isLightWeight) {
S3RegionalResource s3RegionalResource = new S3RegionalResource(uri);
String bucketName = s3RegionalResource.getBucketName();
String s3BucketKey = s3RegionalResource.getKey();
configureClient(s3RegionalResource);
GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, s3BucketKey);
if (isLightWeight) {
//Skip content download
getObjectRequest.setRange(0, 0);
}
try {
return amazonS3Client.getObject(getObjectRequest);
} catch (AmazonServiceException e) {
String errorCode = e.getErrorCode();
if (null != errorCode && errorCode.equalsIgnoreCase("NoSuchKey")) {
return null;
}
throw ResourceExceptions.getFailed(uri, e);
}
}
private void configureClient(S3RegionalResource s3RegionalResource) {
Optional<URI> endpoint = s3ConnectionProperties.getEndpoint();
if (endpoint.isPresent()) {
amazonS3Client.setEndpoint(endpoint.get().toString());
} else {
Optional<Region> region = s3RegionalResource.getRegion();
if (region.isPresent()) {
amazonS3Client.setRegion(region.get());
}
}
}
}