LfsStore.java

  1. /*
  2.  * Copyright (C) 2015, Sasa Zivkov <sasa.zivkov@sap.com>
  3.  * and other copyright owners as documented in the project's IP log.
  4.  *
  5.  * This program and the accompanying materials are made available
  6.  * under the terms of the Eclipse Distribution License v1.0 which
  7.  * accompanies this distribution, is reproduced below, and is
  8.  * available at http://www.eclipse.org/org/documents/edl-v10.php
  9.  *
  10.  * All rights reserved.
  11.  *
  12.  * Redistribution and use in source and binary forms, with or
  13.  * without modification, are permitted provided that the following
  14.  * conditions are met:
  15.  *
  16.  * - Redistributions of source code must retain the above copyright
  17.  *   notice, this list of conditions and the following disclaimer.
  18.  *
  19.  * - Redistributions in binary form must reproduce the above
  20.  *   copyright notice, this list of conditions and the following
  21.  *   disclaimer in the documentation and/or other materials provided
  22.  *   with the distribution.
  23.  *
  24.  * - Neither the name of the Eclipse Foundation, Inc. nor the
  25.  *   names of its contributors may be used to endorse or promote
  26.  *   products derived from this software without specific prior
  27.  *   written permission.
  28.  *
  29.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30.  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31.  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32.  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33.  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34.  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36.  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38.  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39.  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40.  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41.  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42.  */

  43. package org.eclipse.jgit.pgm.debug;

  44. import java.io.File;
  45. import java.io.IOException;
  46. import java.net.InetAddress;
  47. import java.net.URI;
  48. import java.net.URISyntaxException;
  49. import java.net.UnknownHostException;
  50. import java.nio.file.Path;
  51. import java.nio.file.Paths;
  52. import java.text.MessageFormat;

  53. import org.eclipse.jetty.server.Connector;
  54. import org.eclipse.jetty.server.HttpConfiguration;
  55. import org.eclipse.jetty.server.HttpConnectionFactory;
  56. import org.eclipse.jetty.server.Server;
  57. import org.eclipse.jetty.server.ServerConnector;
  58. import org.eclipse.jetty.server.handler.ContextHandlerCollection;
  59. import org.eclipse.jetty.servlet.ServletContextHandler;
  60. import org.eclipse.jetty.servlet.ServletHolder;
  61. import org.eclipse.jgit.errors.ConfigInvalidException;
  62. import org.eclipse.jgit.lfs.server.LargeFileRepository;
  63. import org.eclipse.jgit.lfs.server.LfsProtocolServlet;
  64. import org.eclipse.jgit.lfs.server.fs.FileLfsRepository;
  65. import org.eclipse.jgit.lfs.server.fs.FileLfsServlet;
  66. import org.eclipse.jgit.lfs.server.s3.S3Config;
  67. import org.eclipse.jgit.lfs.server.s3.S3Repository;
  68. import org.eclipse.jgit.pgm.Command;
  69. import org.eclipse.jgit.pgm.TextBuiltin;
  70. import org.eclipse.jgit.pgm.internal.CLIText;
  71. import org.eclipse.jgit.storage.file.FileBasedConfig;
  72. import org.eclipse.jgit.util.FS;
  73. import org.kohsuke.args4j.Argument;
  74. import org.kohsuke.args4j.Option;

  75. @Command(common = true, usage = "usage_runLfsStore")
  76. class LfsStore extends TextBuiltin {

  77.     /**
  78.      * Tiny web application server for testing
  79.      */
  80.     static class AppServer {

  81.         private final Server server;

  82.         private final ServerConnector connector;

  83.         private final ContextHandlerCollection contexts;

  84.         private URI uri;

  85.         AppServer(int port) {
  86.             server = new Server();

  87.             HttpConfiguration http_config = new HttpConfiguration();
  88.             http_config.setOutputBufferSize(32768);

  89.             connector = new ServerConnector(server,
  90.                     new HttpConnectionFactory(http_config));
  91.             connector.setPort(port);
  92.             try {
  93.                 String host = InetAddress.getByName("localhost") //$NON-NLS-1$
  94.                         .getHostAddress();
  95.                 connector.setHost(host);
  96.                 if (host.contains(":") && !host.startsWith("[")) //$NON-NLS-1$ //$NON-NLS-2$
  97.                     host = "[" + host + "]"; //$NON-NLS-1$//$NON-NLS-2$
  98.                 uri = new URI("http://" + host + ":" + port); //$NON-NLS-1$ //$NON-NLS-2$
  99.             } catch (UnknownHostException e) {
  100.                 throw new RuntimeException("Cannot find localhost", e); //$NON-NLS-1$
  101.             } catch (URISyntaxException e) {
  102.                 throw new RuntimeException("Unexpected URI error on " + uri, e); //$NON-NLS-1$
  103.             }

  104.             contexts = new ContextHandlerCollection();
  105.             server.setHandler(contexts);
  106.             server.setConnectors(new Connector[] { connector });
  107.         }

  108.         /**
  109.          * Create a new servlet context within the server.
  110.          * <p>
  111.          * This method should be invoked before the server is started, once for
  112.          * each context the caller wants to register.
  113.          *
  114.          * @param path
  115.          *            path of the context; use "/" for the root context if
  116.          *            binding to the root is desired.
  117.          * @return the context to add servlets into.
  118.          */
  119.         ServletContextHandler addContext(String path) {
  120.             assertNotRunning();
  121.             if ("".equals(path)) //$NON-NLS-1$
  122.                 path = "/"; //$NON-NLS-1$

  123.             ServletContextHandler ctx = new ServletContextHandler();
  124.             ctx.setContextPath(path);
  125.             contexts.addHandler(ctx);

  126.             return ctx;
  127.         }

  128.         void start() throws Exception {
  129.             server.start();
  130.         }

  131.         void stop() throws Exception {
  132.             server.stop();
  133.         }

  134.         URI getURI() {
  135.             return uri;
  136.         }

  137.         private void assertNotRunning() {
  138.             if (server.isRunning()) {
  139.                 throw new IllegalStateException("server is running"); //$NON-NLS-1$
  140.             }
  141.         }
  142.     }

  143.     private static enum StoreType {
  144.         FS, S3;
  145.     }

  146.     private static enum StorageClass {
  147.         REDUCED_REDUNDANCY, STANDARD
  148.     }

  149.     private static final String OBJECTS = "objects/"; //$NON-NLS-1$

  150.     private static final String STORE_PATH = "/" + OBJECTS + "*"; //$NON-NLS-1$//$NON-NLS-2$

  151.     private static final String PROTOCOL_PATH = "/lfs/objects/batch"; //$NON-NLS-1$

  152.     @Option(name = "--port", aliases = {"-p" },
  153.             metaVar = "metaVar_port", usage = "usage_LFSPort")
  154.     int port;

  155.     @Option(name = "--store", metaVar = "metaVar_lfsStorage", usage = "usage_LFSRunStore")
  156.     StoreType storeType;

  157.     @Option(name = "--store-url", aliases = {"-u" }, metaVar = "metaVar_url",
  158.             usage = "usage_LFSStoreUrl")
  159.     String storeUrl;

  160.     @Option(name = "--region", aliases = {"-r" },
  161.             metaVar = "metaVar_s3Region", usage = "usage_S3Region")
  162.     String region; // $NON-NLS-1$

  163.     @Option(name = "--bucket", aliases = {"-b" },
  164.             metaVar = "metaVar_s3Bucket", usage = "usage_S3Bucket")
  165.     String bucket; // $NON-NLS-1$

  166.     @Option(name = "--storage-class", aliases = {"-c" },
  167.             metaVar = "metaVar_s3StorageClass", usage = "usage_S3StorageClass")
  168.     StorageClass storageClass = StorageClass.REDUCED_REDUNDANCY;

  169.     @Option(name = "--expire", aliases = {"-e" },
  170.             metaVar = "metaVar_seconds", usage = "usage_S3Expiration")
  171.     int expirationSeconds = 600;

  172.     @Option(name = "--no-ssl-verify", usage = "usage_S3NoSslVerify")
  173.     boolean disableSslVerify = false;

  174.     @Argument(required = false, metaVar = "metaVar_directory", usage = "usage_LFSDirectory")
  175.     String directory;

  176.     String protocolUrl;

  177.     String accessKey;

  178.     String secretKey;

  179.     /** {@inheritDoc} */
  180.     @Override
  181.     protected boolean requiresRepository() {
  182.         return false;
  183.     }

  184.     /** {@inheritDoc} */
  185.     @Override
  186.     protected void run() throws Exception {
  187.         AppServer server = new AppServer(port);
  188.         URI baseURI = server.getURI();
  189.         ServletContextHandler app = server.addContext("/"); //$NON-NLS-1$

  190.         final LargeFileRepository repository;
  191.         switch (storeType) {
  192.         case FS:
  193.             Path dir = Paths.get(directory);
  194.             FileLfsRepository fsRepo = new FileLfsRepository(
  195.                     getStoreUrl(baseURI), dir);
  196.             FileLfsServlet content = new FileLfsServlet(fsRepo, 30000);
  197.             app.addServlet(new ServletHolder(content), STORE_PATH);
  198.             repository = fsRepo;
  199.             break;

  200.         case S3:
  201.             readAWSKeys();
  202.             checkOptions();
  203.             S3Config config = new S3Config(region, bucket,
  204.                     storageClass.toString(), accessKey, secretKey,
  205.                     expirationSeconds, disableSslVerify);
  206.             repository = new S3Repository(config);
  207.             break;
  208.         default:
  209.             throw new IllegalArgumentException(MessageFormat
  210.                     .format(CLIText.get().lfsUnknownStoreType, storeType));
  211.         }

  212.         LfsProtocolServlet protocol = new LfsProtocolServlet() {

  213.             private static final long serialVersionUID = 1L;

  214.             @Override
  215.             protected LargeFileRepository getLargeFileRepository(
  216.                     LfsRequest request, String path, String auth) {
  217.                 return repository;
  218.             }
  219.         };
  220.         app.addServlet(new ServletHolder(protocol), PROTOCOL_PATH);

  221.         server.start();

  222.         outw.println(MessageFormat.format(CLIText.get().lfsProtocolUrl,
  223.                 getProtocolUrl(baseURI)));
  224.         if (storeType == StoreType.FS) {
  225.             outw.println(MessageFormat.format(CLIText.get().lfsStoreDirectory,
  226.                     directory));
  227.             outw.println(MessageFormat.format(CLIText.get().lfsStoreUrl,
  228.                     getStoreUrl(baseURI)));
  229.         }
  230.     }

  231.     private void checkOptions() {
  232.         if (bucket == null || bucket.length() == 0) {
  233.             throw die(MessageFormat.format(CLIText.get().s3InvalidBucket,
  234.                     bucket));
  235.         }
  236.     }

  237.     private void readAWSKeys() throws IOException, ConfigInvalidException {
  238.         String credentialsPath = System.getProperty("user.home") //$NON-NLS-1$
  239.                 + "/.aws/credentials"; //$NON-NLS-1$
  240.         FileBasedConfig c = new FileBasedConfig(new File(credentialsPath),
  241.                 FS.DETECTED);
  242.         c.load();
  243.         accessKey = c.getString("default", null, "accessKey"); //$NON-NLS-1$//$NON-NLS-2$
  244.         secretKey = c.getString("default", null, "secretKey"); //$NON-NLS-1$ //$NON-NLS-2$
  245.         if (accessKey == null || accessKey.isEmpty()) {
  246.             throw die(MessageFormat.format(CLIText.get().lfsNoAccessKey,
  247.                     credentialsPath));
  248.         }
  249.         if (secretKey == null || secretKey.isEmpty()) {
  250.             throw die(MessageFormat.format(CLIText.get().lfsNoSecretKey,
  251.                     credentialsPath));
  252.         }
  253.     }

  254.     private String getStoreUrl(URI baseURI) {
  255.         if (storeUrl == null) {
  256.             if (storeType == StoreType.FS) {
  257.                 storeUrl = baseURI + "/" + OBJECTS; //$NON-NLS-1$
  258.             } else {
  259.                 die("Local store not running and no --store-url specified"); //$NON-NLS-1$
  260.             }
  261.         }
  262.         return storeUrl;
  263.     }

  264.     private String getProtocolUrl(URI baseURI) {
  265.         if (protocolUrl == null) {
  266.             protocolUrl = baseURI + PROTOCOL_PATH;
  267.         }
  268.         return protocolUrl;
  269.     }
  270. }