S3Repository.java

  1. /*
  2.  * Copyright (C) 2015, Matthias Sohn <matthias.sohn@sap.com>
  3.  * Copyright (C) 2015, Sasa Zivkov <sasa.zivkov@sap.com> and others
  4.  *
  5.  * This program and the accompanying materials are made available under the
  6.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  7.  * https://www.eclipse.org/org/documents/edl-v10.php.
  8.  *
  9.  * SPDX-License-Identifier: BSD-3-Clause
  10.  */
  11. package org.eclipse.jgit.lfs.server.s3;

  12. import static javax.servlet.http.HttpServletResponse.SC_OK;
  13. import static org.eclipse.jgit.lfs.server.s3.SignerV4.UNSIGNED_PAYLOAD;
  14. import static org.eclipse.jgit.lfs.server.s3.SignerV4.X_AMZ_CONTENT_SHA256;
  15. import static org.eclipse.jgit.lfs.server.s3.SignerV4.X_AMZ_EXPIRES;
  16. import static org.eclipse.jgit.lfs.server.s3.SignerV4.X_AMZ_STORAGE_CLASS;
  17. import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_LENGTH;
  18. import static org.eclipse.jgit.util.HttpSupport.METHOD_GET;
  19. import static org.eclipse.jgit.util.HttpSupport.METHOD_HEAD;
  20. import static org.eclipse.jgit.util.HttpSupport.METHOD_PUT;

  21. import java.io.IOException;
  22. import java.net.MalformedURLException;
  23. import java.net.Proxy;
  24. import java.net.ProxySelector;
  25. import java.net.URL;
  26. import java.text.MessageFormat;
  27. import java.util.HashMap;
  28. import java.util.Map;

  29. import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
  30. import org.eclipse.jgit.lfs.server.LargeFileRepository;
  31. import org.eclipse.jgit.lfs.server.Response;
  32. import org.eclipse.jgit.lfs.server.Response.Action;
  33. import org.eclipse.jgit.lfs.server.internal.LfsServerText;
  34. import org.eclipse.jgit.transport.http.HttpConnection;
  35. import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory;
  36. import org.eclipse.jgit.util.HttpSupport;

  37. /**
  38.  * Repository storing LFS objects in Amazon S3
  39.  *
  40.  * @since 4.3
  41.  */
  42. public class S3Repository implements LargeFileRepository {

  43.     private S3Config s3Config;

  44.     /**
  45.      * Construct a LFS repository storing large objects in Amazon S3
  46.      *
  47.      * @param config
  48.      *            AWS S3 storage bucket configuration
  49.      */
  50.     public S3Repository(S3Config config) {
  51.         validateConfig(config);
  52.         this.s3Config = config;
  53.     }

  54.     /** {@inheritDoc} */
  55.     @Override
  56.     public Response.Action getDownloadAction(AnyLongObjectId oid) {
  57.         URL endpointUrl = getObjectUrl(oid);
  58.         Map<String, String> queryParams = new HashMap<>();
  59.         queryParams.put(X_AMZ_EXPIRES,
  60.                 Integer.toString(s3Config.getExpirationSeconds()));
  61.         Map<String, String> headers = new HashMap<>();
  62.         String authorizationQueryParameters = SignerV4.createAuthorizationQuery(
  63.                 s3Config, endpointUrl, METHOD_GET, headers, queryParams,
  64.                 UNSIGNED_PAYLOAD);

  65.         Response.Action a = new Response.Action();
  66.         a.href = endpointUrl.toString() + "?" + authorizationQueryParameters; //$NON-NLS-1$
  67.         return a;
  68.     }

  69.     /** {@inheritDoc} */
  70.     @Override
  71.     public Response.Action getUploadAction(AnyLongObjectId oid, long size) {
  72.         cacheObjectMetaData(oid, size);
  73.         URL objectUrl = getObjectUrl(oid);
  74.         Map<String, String> headers = new HashMap<>();
  75.         headers.put(X_AMZ_CONTENT_SHA256, oid.getName());
  76.         headers.put(HDR_CONTENT_LENGTH, Long.toString(size));
  77.         headers.put(X_AMZ_STORAGE_CLASS, s3Config.getStorageClass());
  78.         headers.put(HttpSupport.HDR_CONTENT_TYPE, "application/octet-stream"); //$NON-NLS-1$
  79.         headers = SignerV4.createHeaderAuthorization(s3Config, objectUrl,
  80.                 METHOD_PUT, headers, oid.getName());

  81.         Response.Action a = new Response.Action();
  82.         a.href = objectUrl.toString();
  83.         a.header = new HashMap<>();
  84.         a.header.putAll(headers);
  85.         return a;
  86.     }

  87.     /** {@inheritDoc} */
  88.     @Override
  89.     public Action getVerifyAction(AnyLongObjectId id) {
  90.         return null; // TODO(ms) implement this
  91.     }

  92.     /** {@inheritDoc} */
  93.     @Override
  94.     public long getSize(AnyLongObjectId oid) throws IOException {
  95.         URL endpointUrl = getObjectUrl(oid);
  96.         Map<String, String> queryParams = new HashMap<>();
  97.         queryParams.put(X_AMZ_EXPIRES,
  98.                 Integer.toString(s3Config.getExpirationSeconds()));
  99.         Map<String, String> headers = new HashMap<>();

  100.         String authorizationQueryParameters = SignerV4.createAuthorizationQuery(
  101.                 s3Config, endpointUrl, METHOD_HEAD, headers, queryParams,
  102.                 UNSIGNED_PAYLOAD);
  103.         String href = endpointUrl.toString() + "?" //$NON-NLS-1$
  104.                 + authorizationQueryParameters;

  105.         Proxy proxy = HttpSupport.proxyFor(ProxySelector.getDefault(),
  106.                 endpointUrl);
  107.         HttpClientConnectionFactory f = new HttpClientConnectionFactory();
  108.         HttpConnection conn = f.create(new URL(href), proxy);
  109.         if (s3Config.isDisableSslVerify()) {
  110.             HttpSupport.disableSslVerify(conn);
  111.         }
  112.         conn.setRequestMethod(METHOD_HEAD);
  113.         conn.connect();
  114.         int status = conn.getResponseCode();
  115.         if (status == SC_OK) {
  116.             String contentLengthHeader = conn
  117.                     .getHeaderField(HDR_CONTENT_LENGTH);
  118.             if (contentLengthHeader != null) {
  119.                 return Integer.parseInt(contentLengthHeader);
  120.             }
  121.         }
  122.         return -1;
  123.     }

  124.     /**
  125.      * Cache metadata (size) for an object to avoid extra roundtrip to S3 in
  126.      * order to retrieve this metadata for a given object. Subclasses can
  127.      * implement a local cache and override {{@link #getSize(AnyLongObjectId)}
  128.      * to retrieve the object size from the local cache to eliminate the need
  129.      * for another roundtrip to S3
  130.      *
  131.      * @param oid
  132.      *            the object id identifying the object to be cached
  133.      * @param size
  134.      *            the object's size (in bytes)
  135.      */
  136.     protected void cacheObjectMetaData(AnyLongObjectId oid, long size) {
  137.         // no caching
  138.     }

  139.     private void validateConfig(S3Config config) {
  140.         assertNotEmpty(LfsServerText.get().undefinedS3AccessKey,
  141.                 config.getAccessKey());
  142.         assertNotEmpty(LfsServerText.get().undefinedS3Bucket,
  143.                 config.getBucket());
  144.         assertNotEmpty(LfsServerText.get().undefinedS3Region,
  145.                 config.getRegion());
  146.         assertNotEmpty(LfsServerText.get().undefinedS3Hostname,
  147.                 config.getHostname());
  148.         assertNotEmpty(LfsServerText.get().undefinedS3SecretKey,
  149.                 config.getSecretKey());
  150.         assertNotEmpty(LfsServerText.get().undefinedS3StorageClass,
  151.                 config.getStorageClass());
  152.     }

  153.     private void assertNotEmpty(String message, String value) {
  154.         if (value == null || value.trim().length() == 0) {
  155.             throw new IllegalArgumentException(message);
  156.         }
  157.     }

  158.     private URL getObjectUrl(AnyLongObjectId oid) {
  159.         try {
  160.             return new URL(String.format("https://%s/%s/%s", //$NON-NLS-1$
  161.                     s3Config.getHostname(), s3Config.getBucket(),
  162.                     getPath(oid)));
  163.         } catch (MalformedURLException e) {
  164.             throw new IllegalArgumentException(MessageFormat.format(
  165.                     LfsServerText.get().unparsableEndpoint, e.getMessage()));
  166.         }
  167.     }

  168.     private String getPath(AnyLongObjectId oid) {
  169.         return oid.getName();
  170.     }
  171. }