LfsStore.java

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

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

  11. import java.io.File;
  12. import java.io.IOException;
  13. import java.net.InetAddress;
  14. import java.net.URI;
  15. import java.net.URISyntaxException;
  16. import java.net.UnknownHostException;
  17. import java.nio.file.Path;
  18. import java.nio.file.Paths;
  19. import java.text.MessageFormat;

  20. import org.eclipse.jetty.server.Connector;
  21. import org.eclipse.jetty.server.HttpConfiguration;
  22. import org.eclipse.jetty.server.HttpConnectionFactory;
  23. import org.eclipse.jetty.server.Server;
  24. import org.eclipse.jetty.server.ServerConnector;
  25. import org.eclipse.jetty.server.handler.ContextHandlerCollection;
  26. import org.eclipse.jetty.servlet.ServletContextHandler;
  27. import org.eclipse.jetty.servlet.ServletHolder;
  28. import org.eclipse.jgit.errors.ConfigInvalidException;
  29. import org.eclipse.jgit.lfs.server.LargeFileRepository;
  30. import org.eclipse.jgit.lfs.server.LfsProtocolServlet;
  31. import org.eclipse.jgit.lfs.server.fs.FileLfsRepository;
  32. import org.eclipse.jgit.lfs.server.fs.FileLfsServlet;
  33. import org.eclipse.jgit.lfs.server.s3.S3Config;
  34. import org.eclipse.jgit.lfs.server.s3.S3Repository;
  35. import org.eclipse.jgit.pgm.Command;
  36. import org.eclipse.jgit.pgm.TextBuiltin;
  37. import org.eclipse.jgit.pgm.internal.CLIText;
  38. import org.eclipse.jgit.storage.file.FileBasedConfig;
  39. import org.eclipse.jgit.util.FS;
  40. import org.kohsuke.args4j.Argument;
  41. import org.kohsuke.args4j.Option;

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

  44.     /**
  45.      * Tiny web application server for testing
  46.      */
  47.     static class AppServer {

  48.         private final Server server;

  49.         private final ServerConnector connector;

  50.         private final ContextHandlerCollection contexts;

  51.         private URI uri;

  52.         AppServer(int port) {
  53.             server = new Server();

  54.             HttpConfiguration http_config = new HttpConfiguration();
  55.             http_config.setOutputBufferSize(32768);

  56.             connector = new ServerConnector(server,
  57.                     new HttpConnectionFactory(http_config));
  58.             connector.setPort(port);
  59.             try {
  60.                 String host = InetAddress.getByName("localhost") //$NON-NLS-1$
  61.                         .getHostAddress();
  62.                 connector.setHost(host);
  63.                 if (host.contains(":") && !host.startsWith("[")) //$NON-NLS-1$ //$NON-NLS-2$
  64.                     host = "[" + host + "]"; //$NON-NLS-1$//$NON-NLS-2$
  65.                 uri = new URI("http://" + host + ":" + port); //$NON-NLS-1$ //$NON-NLS-2$
  66.             } catch (UnknownHostException e) {
  67.                 throw new RuntimeException("Cannot find localhost", e); //$NON-NLS-1$
  68.             } catch (URISyntaxException e) {
  69.                 throw new RuntimeException("Unexpected URI error on " + uri, e); //$NON-NLS-1$
  70.             }

  71.             contexts = new ContextHandlerCollection();
  72.             server.setHandler(contexts);
  73.             server.setConnectors(new Connector[] { connector });
  74.         }

  75.         /**
  76.          * Create a new servlet context within the server.
  77.          * <p>
  78.          * This method should be invoked before the server is started, once for
  79.          * each context the caller wants to register.
  80.          *
  81.          * @param path
  82.          *            path of the context; use "/" for the root context if
  83.          *            binding to the root is desired.
  84.          * @return the context to add servlets into.
  85.          */
  86.         ServletContextHandler addContext(String path) {
  87.             assertNotRunning();
  88.             if ("".equals(path)) //$NON-NLS-1$
  89.                 path = "/"; //$NON-NLS-1$

  90.             ServletContextHandler ctx = new ServletContextHandler();
  91.             ctx.setContextPath(path);
  92.             contexts.addHandler(ctx);

  93.             return ctx;
  94.         }

  95.         void start() throws Exception {
  96.             server.start();
  97.         }

  98.         void stop() throws Exception {
  99.             server.stop();
  100.         }

  101.         URI getURI() {
  102.             return uri;
  103.         }

  104.         private void assertNotRunning() {
  105.             if (server.isRunning()) {
  106.                 throw new IllegalStateException("server is running"); //$NON-NLS-1$
  107.             }
  108.         }
  109.     }

  110.     private enum StoreType {
  111.         FS, S3;
  112.     }

  113.     private enum StorageClass {
  114.         REDUCED_REDUNDANCY, STANDARD
  115.     }

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

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

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

  119.     @Option(name = "--port", aliases = {"-p" },
  120.             metaVar = "metaVar_port", usage = "usage_LFSPort")
  121.     int port;

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

  124.     @Option(name = "--store-url", aliases = {"-u" }, metaVar = "metaVar_url",
  125.             usage = "usage_LFSStoreUrl")
  126.     String storeUrl;

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

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

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

  136.     @Option(name = "--expire", aliases = {"-e" },
  137.             metaVar = "metaVar_seconds", usage = "usage_S3Expiration")
  138.     int expirationSeconds = 600;

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

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

  143.     String protocolUrl;

  144.     String accessKey;

  145.     String secretKey;

  146.     /** {@inheritDoc} */
  147.     @Override
  148.     protected boolean requiresRepository() {
  149.         return false;
  150.     }

  151.     /** {@inheritDoc} */
  152.     @Override
  153.     protected void run() throws Exception {
  154.         AppServer server = new AppServer(port);
  155.         URI baseURI = server.getURI();
  156.         ServletContextHandler app = server.addContext("/"); //$NON-NLS-1$

  157.         final LargeFileRepository repository;
  158.         switch (storeType) {
  159.         case FS:
  160.             Path dir = Paths.get(directory);
  161.             FileLfsRepository fsRepo = new FileLfsRepository(
  162.                     getStoreUrl(baseURI), dir);
  163.             FileLfsServlet content = new FileLfsServlet(fsRepo, 30000);
  164.             app.addServlet(new ServletHolder(content), STORE_PATH);
  165.             repository = fsRepo;
  166.             break;

  167.         case S3:
  168.             readAWSKeys();
  169.             checkOptions();
  170.             S3Config config = new S3Config(region, bucket,
  171.                     storageClass.toString(), accessKey, secretKey,
  172.                     expirationSeconds, disableSslVerify);
  173.             repository = new S3Repository(config);
  174.             break;
  175.         default:
  176.             throw new IllegalArgumentException(MessageFormat
  177.                     .format(CLIText.get().lfsUnknownStoreType, storeType));
  178.         }

  179.         LfsProtocolServlet protocol = new LfsProtocolServlet() {

  180.             private static final long serialVersionUID = 1L;

  181.             @Override
  182.             protected LargeFileRepository getLargeFileRepository(
  183.                     LfsRequest request, String path, String auth) {
  184.                 return repository;
  185.             }
  186.         };
  187.         app.addServlet(new ServletHolder(protocol), PROTOCOL_PATH);

  188.         server.start();

  189.         outw.println(MessageFormat.format(CLIText.get().lfsProtocolUrl,
  190.                 getProtocolUrl(baseURI)));
  191.         if (storeType == StoreType.FS) {
  192.             outw.println(MessageFormat.format(CLIText.get().lfsStoreDirectory,
  193.                     directory));
  194.             outw.println(MessageFormat.format(CLIText.get().lfsStoreUrl,
  195.                     getStoreUrl(baseURI)));
  196.         }
  197.     }

  198.     private void checkOptions() {
  199.         if (bucket == null || bucket.length() == 0) {
  200.             throw die(MessageFormat.format(CLIText.get().s3InvalidBucket,
  201.                     bucket));
  202.         }
  203.     }

  204.     private void readAWSKeys() throws IOException, ConfigInvalidException {
  205.         String credentialsPath = System.getProperty("user.home") //$NON-NLS-1$
  206.                 + "/.aws/credentials"; //$NON-NLS-1$
  207.         FileBasedConfig c = new FileBasedConfig(new File(credentialsPath),
  208.                 FS.DETECTED);
  209.         c.load();
  210.         accessKey = c.getString("default", null, "accessKey"); //$NON-NLS-1$//$NON-NLS-2$
  211.         secretKey = c.getString("default", null, "secretKey"); //$NON-NLS-1$ //$NON-NLS-2$
  212.         if (accessKey == null || accessKey.isEmpty()) {
  213.             throw die(MessageFormat.format(CLIText.get().lfsNoAccessKey,
  214.                     credentialsPath));
  215.         }
  216.         if (secretKey == null || secretKey.isEmpty()) {
  217.             throw die(MessageFormat.format(CLIText.get().lfsNoSecretKey,
  218.                     credentialsPath));
  219.         }
  220.     }

  221.     private String getStoreUrl(URI baseURI) {
  222.         if (storeUrl == null) {
  223.             if (storeType == StoreType.FS) {
  224.                 storeUrl = baseURI + "/" + OBJECTS; //$NON-NLS-1$
  225.             } else {
  226.                 die("Local store not running and no --store-url specified"); //$NON-NLS-1$
  227.             }
  228.         }
  229.         return storeUrl;
  230.     }

  231.     private String getProtocolUrl(URI baseURI) {
  232.         if (protocolUrl == null) {
  233.             protocolUrl = baseURI + PROTOCOL_PATH;
  234.         }
  235.         return protocolUrl;
  236.     }
  237. }