1
2
3
4
5
6
7
8
9
10
11 package org.eclipse.jgit.transport;
12
13 import java.io.BufferedReader;
14 import java.io.File;
15 import java.io.FileNotFoundException;
16 import java.io.IOException;
17 import java.io.InputStream;
18 import java.io.OutputStream;
19 import java.net.URLConnection;
20 import java.text.MessageFormat;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.EnumSet;
25 import java.util.HashSet;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Properties;
29 import java.util.Set;
30 import java.util.TreeMap;
31
32 import org.eclipse.jgit.errors.NotSupportedException;
33 import org.eclipse.jgit.errors.TransportException;
34 import org.eclipse.jgit.internal.JGitText;
35 import org.eclipse.jgit.lib.Constants;
36 import org.eclipse.jgit.lib.ObjectId;
37 import org.eclipse.jgit.lib.ObjectIdRef;
38 import org.eclipse.jgit.lib.ProgressMonitor;
39 import org.eclipse.jgit.lib.Ref;
40 import org.eclipse.jgit.lib.Ref.Storage;
41 import org.eclipse.jgit.lib.Repository;
42 import org.eclipse.jgit.lib.SymbolicRef;
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69 public class TransportAmazonS3 extends HttpTransport implements WalkTransport {
70 static final String S3_SCHEME = "amazon-s3";
71
72 static final TransportProtocolcol.html#TransportProtocol">TransportProtocol PROTO_S3 = new TransportProtocol() {
73 @Override
74 public String getName() {
75 return "Amazon S3";
76 }
77
78 @Override
79 public Set<String> getSchemes() {
80 return Collections.singleton(S3_SCHEME);
81 }
82
83 @Override
84 public Set<URIishField> getRequiredFields() {
85 return Collections.unmodifiableSet(EnumSet.of(URIishField.USER,
86 URIishField.HOST, URIishField.PATH));
87 }
88
89 @Override
90 public Set<URIishField> getOptionalFields() {
91 return Collections.unmodifiableSet(EnumSet.of(URIishField.PASS));
92 }
93
94 @Override
95 public Transport open(URIish uri, Repository local, String remoteName)
96 throws NotSupportedException {
97 return new TransportAmazonS3(local, uri);
98 }
99 };
100
101
102 final AmazonS3 s3;
103
104
105 final String bucket;
106
107
108
109
110
111
112
113
114
115
116
117
118 private final String keyPrefix;
119
120 TransportAmazonS3(final Repository local, final URIish uri)
121 throws NotSupportedException {
122 super(local, uri);
123
124 Properties props = loadProperties();
125 File directory = local.getDirectory();
126 if (!props.containsKey("tmpdir") && directory != null)
127 props.put("tmpdir", directory.getPath());
128
129 s3 = new AmazonS3(props);
130 bucket = uri.getHost();
131
132 String p = uri.getPath();
133 if (p.startsWith("/"))
134 p = p.substring(1);
135 if (p.endsWith("/"))
136 p = p.substring(0, p.length() - 1);
137 keyPrefix = p;
138 }
139
140 private Properties loadProperties() throws NotSupportedException {
141 if (local.getDirectory() != null) {
142 File propsFile = new File(local.getDirectory(), uri.getUser());
143 if (propsFile.isFile())
144 return loadPropertiesFile(propsFile);
145 }
146
147 File propsFile = new File(local.getFS().userHome(), uri.getUser());
148 if (propsFile.isFile())
149 return loadPropertiesFile(propsFile);
150
151 Properties props = new Properties();
152 String user = uri.getUser();
153 String pass = uri.getPass();
154 if (user != null && pass != null) {
155 props.setProperty("accesskey", user);
156 props.setProperty("secretkey", pass);
157 } else
158 throw new NotSupportedException(MessageFormat.format(
159 JGitText.get().cannotReadFile, propsFile));
160 return props;
161 }
162
163 private static Properties loadPropertiesFile(File propsFile)
164 throws NotSupportedException {
165 try {
166 return AmazonS3.properties(propsFile);
167 } catch (IOException e) {
168 throw new NotSupportedException(MessageFormat.format(
169 JGitText.get().cannotReadFile, propsFile), e);
170 }
171 }
172
173
174 @Override
175 public FetchConnection openFetch() throws TransportException {
176 final DatabaseS3 c = new DatabaseS3(bucket, keyPrefix + "/objects");
177 final WalkFetchConnectionConnection.html#WalkFetchConnection">WalkFetchConnection r = new WalkFetchConnection(this, c);
178 r.available(c.readAdvertisedRefs());
179 return r;
180 }
181
182
183 @Override
184 public PushConnection openPush() throws TransportException {
185 final DatabaseS3 c = new DatabaseS3(bucket, keyPrefix + "/objects");
186 final WalkPushConnectionConnection.html#WalkPushConnection">WalkPushConnection r = new WalkPushConnection(this, c);
187 r.available(c.readAdvertisedRefs());
188 return r;
189 }
190
191
192 @Override
193 public void close() {
194
195 }
196
197 class DatabaseS3 extends WalkRemoteObjectDatabase {
198 private final String bucketName;
199
200 private final String objectsKey;
201
202 DatabaseS3(final String b, final String o) {
203 bucketName = b;
204 objectsKey = o;
205 }
206
207 private String resolveKey(String subpath) {
208 if (subpath.endsWith("/"))
209 subpath = subpath.substring(0, subpath.length() - 1);
210 String k = objectsKey;
211 while (subpath.startsWith(ROOT_DIR)) {
212 k = k.substring(0, k.lastIndexOf('/'));
213 subpath = subpath.substring(3);
214 }
215 return k + "/" + subpath;
216 }
217
218 @Override
219 URIish getURI() {
220 URIish u = new URIish();
221 u = u.setScheme(S3_SCHEME);
222 u = u.setHost(bucketName);
223 u = u.setPath("/" + objectsKey);
224 return u;
225 }
226
227 @Override
228 Collection<WalkRemoteObjectDatabase> getAlternates() throws IOException {
229 try {
230 return readAlternates(Constants.INFO_ALTERNATES);
231 } catch (FileNotFoundException err) {
232
233 }
234 return null;
235 }
236
237 @Override
238 WalkRemoteObjectDatabase openAlternate(String location)
239 throws IOException {
240 return new DatabaseS3(bucketName, resolveKey(location));
241 }
242
243 @Override
244 Collection<String> getPackNames() throws IOException {
245
246
247 final List<String> packList = s3.list(bucket, resolveKey("pack"));
248 final HashSet<String> have = new HashSet<>();
249 have.addAll(packList);
250
251 final Collection<String> packs = new ArrayList<>();
252 for (String n : packList) {
253 if (!n.startsWith("pack-") || !n.endsWith(".pack"))
254 continue;
255
256 final String in = n.substring(0, n.length() - 5) + ".idx";
257 if (have.contains(in))
258 packs.add(n);
259 }
260 return packs;
261 }
262
263 @Override
264 FileStream open(String path) throws IOException {
265 final URLConnection c = s3.get(bucket, resolveKey(path));
266 final InputStream raw = c.getInputStream();
267 final InputStream in = s3.decrypt(c);
268 final int len = c.getContentLength();
269 return new FileStream(in, raw == in ? len : -1);
270 }
271
272 @Override
273 void deleteFile(String path) throws IOException {
274 s3.delete(bucket, resolveKey(path));
275 }
276
277 @Override
278 OutputStream writeFile(final String path,
279 final ProgressMonitor monitor, final String monitorTask)
280 throws IOException {
281 return s3.beginPut(bucket, resolveKey(path), monitor, monitorTask);
282 }
283
284 @Override
285 void writeFile(String path, byte[] data) throws IOException {
286 s3.put(bucket, resolveKey(path), data);
287 }
288
289 Map<String, Ref> readAdvertisedRefs() throws TransportException {
290 final TreeMap<String, Ref> avail = new TreeMap<>();
291 readPackedRefs(avail);
292 readLooseRefs(avail);
293 readRef(avail, Constants.HEAD);
294 return avail;
295 }
296
297 private void readLooseRefs(TreeMap<String, Ref> avail)
298 throws TransportException {
299 try {
300 for (final String n : s3.list(bucket, resolveKey(ROOT_DIR
301 + "refs")))
302 readRef(avail, "refs/" + n);
303 } catch (IOException e) {
304 throw new TransportException(getURI(), JGitText.get().cannotListRefs, e);
305 }
306 }
307
308 private Ref readRef(TreeMap<String, Ref> avail, String rn)
309 throws TransportException {
310 final String s;
311 String ref = ROOT_DIR + rn;
312 try {
313 try (BufferedReader br = openReader(ref)) {
314 s = br.readLine();
315 }
316 } catch (FileNotFoundException noRef) {
317 return null;
318 } catch (IOException err) {
319 throw new TransportException(getURI(), MessageFormat.format(
320 JGitText.get().transportExceptionReadRef, ref), err);
321 }
322
323 if (s == null)
324 throw new TransportException(getURI(), MessageFormat.format(JGitText.get().transportExceptionEmptyRef, rn));
325
326 if (s.startsWith("ref: ")) {
327 final String target = s.substring("ref: ".length());
328 Ref r = avail.get(target);
329 if (r == null)
330 r = readRef(avail, target);
331 if (r == null)
332 r = new ObjectIdRef.Unpeeled(Ref.Storage.NEW, target, null);
333 r = new SymbolicRef(rn, r);
334 avail.put(r.getName(), r);
335 return r;
336 }
337
338 if (ObjectId.isId(s)) {
339 final Ref r = new ObjectIdRef.Unpeeled(loose(avail.get(rn)),
340 rn, ObjectId.fromString(s));
341 avail.put(r.getName(), r);
342 return r;
343 }
344
345 throw new TransportException(getURI(), MessageFormat.format(JGitText.get().transportExceptionBadRef, rn, s));
346 }
347
348 private Storage loose(Ref r) {
349 if (r != null && r.getStorage() == Storage.PACKED)
350 return Storage.LOOSE_PACKED;
351 return Storage.LOOSE;
352 }
353
354 @Override
355 void close() {
356
357 }
358 }
359 }