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 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. /*
  2. * Copyright (C) 2015, Matthias Sohn <matthias.sohn@sap.com>
  3. * Copyright (C) 2015, Sasa Zivkov <sasa.zivkov@sap.com>
  4. * and other copyright owners as documented in the project's IP log.
  5. *
  6. * This program and the accompanying materials are made available
  7. * under the terms of the Eclipse Distribution License v1.0 which
  8. * accompanies this distribution, is reproduced below, and is
  9. * available at http://www.eclipse.org/org/documents/edl-v10.php
  10. *
  11. * All rights reserved.
  12. *
  13. * Redistribution and use in source and binary forms, with or
  14. * without modification, are permitted provided that the following
  15. * conditions are met:
  16. *
  17. * - Redistributions of source code must retain the above copyright
  18. * notice, this list of conditions and the following disclaimer.
  19. *
  20. * - Redistributions in binary form must reproduce the above
  21. * copyright notice, this list of conditions and the following
  22. * disclaimer in the documentation and/or other materials provided
  23. * with the distribution.
  24. *
  25. * - Neither the name of the Eclipse Foundation, Inc. nor the
  26. * names of its contributors may be used to endorse or promote
  27. * products derived from this software without specific prior
  28. * written permission.
  29. *
  30. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  31. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  32. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  33. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  34. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  35. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  36. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  37. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  38. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  39. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  40. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  41. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  42. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  43. */
  44. package org.eclipse.jgit.lfs.server.s3;
  45. import static javax.servlet.http.HttpServletResponse.SC_OK;
  46. import static org.eclipse.jgit.lfs.server.s3.SignerV4.UNSIGNED_PAYLOAD;
  47. import static org.eclipse.jgit.lfs.server.s3.SignerV4.X_AMZ_CONTENT_SHA256;
  48. import static org.eclipse.jgit.lfs.server.s3.SignerV4.X_AMZ_EXPIRES;
  49. import static org.eclipse.jgit.lfs.server.s3.SignerV4.X_AMZ_STORAGE_CLASS;
  50. import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_LENGTH;
  51. import static org.eclipse.jgit.util.HttpSupport.METHOD_GET;
  52. import static org.eclipse.jgit.util.HttpSupport.METHOD_HEAD;
  53. import static org.eclipse.jgit.util.HttpSupport.METHOD_PUT;
  54. import java.io.IOException;
  55. import java.net.MalformedURLException;
  56. import java.net.Proxy;
  57. import java.net.ProxySelector;
  58. import java.net.URL;
  59. import java.text.MessageFormat;
  60. import java.util.HashMap;
  61. import java.util.Map;
  62. import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
  63. import org.eclipse.jgit.lfs.server.LargeFileRepository;
  64. import org.eclipse.jgit.lfs.server.Response;
  65. import org.eclipse.jgit.lfs.server.Response.Action;
  66. import org.eclipse.jgit.lfs.server.internal.LfsServerText;
  67. import org.eclipse.jgit.transport.http.HttpConnection;
  68. import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory;
  69. import org.eclipse.jgit.util.HttpSupport;
  70. /**
  71. * Repository storing LFS objects in Amazon S3
  72. *
  73. * @since 4.3
  74. */
  75. public class S3Repository implements LargeFileRepository {
  76. private S3Config s3Config;
  77. /**
  78. * Construct a LFS repository storing large objects in Amazon S3
  79. *
  80. * @param config
  81. * AWS S3 storage bucket configuration
  82. */
  83. public S3Repository(S3Config config) {
  84. validateConfig(config);
  85. this.s3Config = config;
  86. }
  87. @Override
  88. public Response.Action getDownloadAction(AnyLongObjectId oid) {
  89. URL endpointUrl = getObjectUrl(oid);
  90. Map<String, String> queryParams = new HashMap<>();
  91. queryParams.put(X_AMZ_EXPIRES,
  92. Integer.toString(s3Config.getExpirationSeconds()));
  93. Map<String, String> headers = new HashMap<>();
  94. String authorizationQueryParameters = SignerV4.createAuthorizationQuery(
  95. s3Config, endpointUrl, METHOD_GET, headers, queryParams,
  96. UNSIGNED_PAYLOAD);
  97. Response.Action a = new Response.Action();
  98. a.href = endpointUrl.toString() + "?" + authorizationQueryParameters; //$NON-NLS-1$
  99. return a;
  100. }
  101. @Override
  102. public Response.Action getUploadAction(AnyLongObjectId oid, long size) {
  103. cacheObjectMetaData(oid, size);
  104. URL objectUrl = getObjectUrl(oid);
  105. Map<String, String> headers = new HashMap<>();
  106. headers.put(X_AMZ_CONTENT_SHA256, oid.getName());
  107. headers.put(HDR_CONTENT_LENGTH, Long.toString(size));
  108. headers.put(X_AMZ_STORAGE_CLASS, s3Config.getStorageClass());
  109. headers.put(HttpSupport.HDR_CONTENT_TYPE, "application/octet-stream"); //$NON-NLS-1$
  110. headers = SignerV4.createHeaderAuthorization(s3Config, objectUrl,
  111. METHOD_PUT, headers, oid.getName());
  112. Response.Action a = new Response.Action();
  113. a.href = objectUrl.toString();
  114. a.header = new HashMap<>();
  115. a.header.putAll(headers);
  116. return a;
  117. }
  118. @Override
  119. public Action getVerifyAction(AnyLongObjectId id) {
  120. return null; // TODO(ms) implement this
  121. }
  122. @Override
  123. public long getSize(AnyLongObjectId oid) throws IOException {
  124. URL endpointUrl = getObjectUrl(oid);
  125. Map<String, String> queryParams = new HashMap<>();
  126. queryParams.put(X_AMZ_EXPIRES,
  127. Integer.toString(s3Config.getExpirationSeconds()));
  128. Map<String, String> headers = new HashMap<>();
  129. String authorizationQueryParameters = SignerV4.createAuthorizationQuery(
  130. s3Config, endpointUrl, METHOD_HEAD, headers, queryParams,
  131. UNSIGNED_PAYLOAD);
  132. String href = endpointUrl.toString() + "?" //$NON-NLS-1$
  133. + authorizationQueryParameters;
  134. Proxy proxy = HttpSupport.proxyFor(ProxySelector.getDefault(),
  135. endpointUrl);
  136. HttpClientConnectionFactory f = new HttpClientConnectionFactory();
  137. HttpConnection conn = f.create(new URL(href), proxy);
  138. if (s3Config.isDisableSslVerify()) {
  139. HttpSupport.disableSslVerify(conn);
  140. }
  141. conn.setRequestMethod(METHOD_HEAD);
  142. conn.connect();
  143. int status = conn.getResponseCode();
  144. if (status == SC_OK) {
  145. String contentLengthHeader = conn
  146. .getHeaderField(HDR_CONTENT_LENGTH);
  147. if (contentLengthHeader != null) {
  148. return Integer.parseInt(contentLengthHeader);
  149. }
  150. }
  151. return -1;
  152. }
  153. /**
  154. * Cache metadata (size) for an object to avoid extra roundtrip to S3 in
  155. * order to retrieve this metadata for a given object. Subclasses can
  156. * implement a local cache and override {{@link #getSize(AnyLongObjectId)}
  157. * to retrieve the object size from the local cache to eliminate the need
  158. * for another roundtrip to S3
  159. *
  160. * @param oid
  161. * the object id identifying the object to be cached
  162. * @param size
  163. * the object's size (in bytes)
  164. */
  165. protected void cacheObjectMetaData(AnyLongObjectId oid, long size) {
  166. // no caching
  167. }
  168. private void validateConfig(S3Config config) {
  169. assertNotEmpty(LfsServerText.get().undefinedS3AccessKey,
  170. config.getAccessKey());
  171. assertNotEmpty(LfsServerText.get().undefinedS3Bucket,
  172. config.getBucket());
  173. assertNotEmpty(LfsServerText.get().undefinedS3Region,
  174. config.getRegion());
  175. assertNotEmpty(LfsServerText.get().undefinedS3SecretKey,
  176. config.getSecretKey());
  177. assertNotEmpty(LfsServerText.get().undefinedS3StorageClass,
  178. config.getStorageClass());
  179. }
  180. private void assertNotEmpty(String message, String value) {
  181. if (value == null || value.trim().length() == 0) {
  182. throw new IllegalArgumentException(message);
  183. }
  184. }
  185. private URL getObjectUrl(AnyLongObjectId oid) {
  186. try {
  187. return new URL(String.format("https://s3-%s.amazonaws.com/%s/%s", //$NON-NLS-1$
  188. s3Config.getRegion(), s3Config.getBucket(),
  189. getPath(oid)));
  190. } catch (MalformedURLException e) {
  191. throw new IllegalArgumentException(MessageFormat.format(
  192. LfsServerText.get().unparsableEndpoint, e.getMessage()));
  193. }
  194. }
  195. private String getPath(AnyLongObjectId oid) {
  196. return oid.getName();
  197. }
  198. }