You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

S3Repository.java 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  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. }