View Javadoc
1   package org.cyclopsgroup.gitcon.jgit;
2   
3   import java.io.File;
4   import java.io.IOException;
5   import org.apache.commons.io.FileUtils;
6   import org.apache.commons.lang.StringUtils;
7   import org.apache.commons.lang.SystemUtils;
8   import org.apache.commons.lang.Validate;
9   import org.apache.commons.logging.Log;
10  import org.apache.commons.logging.LogFactory;
11  import org.cyclopsgroup.gitcon.FileSystemSource;
12  import org.eclipse.jgit.api.CloneCommand;
13  import org.eclipse.jgit.api.Git;
14  import org.eclipse.jgit.api.PullResult;
15  import org.eclipse.jgit.api.errors.GitAPIException;
16  import org.eclipse.jgit.lib.Ref;
17  import org.eclipse.jgit.transport.CredentialsProvider;
18  import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
19  
20  /**
21   * Implementation of {@link FileSystemSource} that gets file from a Git repository. There are
22   * several ways of authenticating access to git.
23   *
24   * <ol>
25   *   <li>Username and password is supported, but it's the least recommended approach. User can call
26   *       {@link JGitSource#setUserPassword(String, String)} or {@link
27   *       JGitSource#setUserPassword(String)} to pass username and password to the instance.
28   *   <li>If the OS default SSH for current run-as-user is good to access Git repo, nothing needs to
29   *       be configured and default setting will pick up system SSH key in {@literal
30   *       $user.home/.ssh/}.
31   *   <li>Specify an SSH private key by calling {@link JGitSource#setSshIdentity(String)} or {@link
32   *       JGitSource#setSshIdentityFile(File)}
33   *   <li>Gitcon library comes with a build-in SSH private key for user {@literal gitconreader} in
34   *       both Github and BitBucket. Calling {@link JGitSource#setBuildInSshIdentityUsed(boolean)}
35   *       with {@literal true} or {@link JGitSource#setSshIdentity(String)} with {@literal buildin}
36   *       tells the class to use the build-in private key. Beaware that since everyone can get this
37   *       build-in private key from Gitcon jar file, exposing your Git repo to user {@literal
38   *       gitconreader} is equivalent to exposing it to public.
39   * </ol>
40   */
41  public class JGitSource implements FileSystemSource {
42    private static final Log LOG = LogFactory.getLog(JGitSource.class);
43  
44    private volatile String branchOrCommit;
45  
46    private String sshIdentity;
47  
48    private Boolean buildInSshIdentityUsed;
49  
50    private CredentialsProvider credentialsProvider;
51  
52    private JGitCallExecutor executor;
53  
54    private Git git;
55  
56    private final String repoUri;
57  
58    /**
59     * Constructor that uses a working directory under system temporary directory
60     *
61     * @param repoUri Git repository URI
62     */
63    public JGitSource(String repoUri) {
64      Validate.notNull(repoUri, "Git repository URI can not be NULL");
65      this.repoUri = repoUri;
66    }
67  
68    public String getBranchOrCommmit() {
69      return branchOrCommit;
70    }
71  
72    @Override
73    public File initWorkingDirectory(File workingDirectory) throws GitAPIException, IOException {
74      // Create git repo local directory
75      File sourceDirectory =
76          new File(workingDirectory.getAbsolutePath() + SystemUtils.FILE_SEPARATOR + "gitrepo");
77      if (sourceDirectory.mkdirs()) {
78        LOG.info("Created GIT repo directory " + sourceDirectory);
79      }
80  
81      // Create local file for build-in SSH private key
82      File sshKey = null;
83      if (buildInSshIdentityUsed == Boolean.TRUE
84          || StringUtils.equalsIgnoreCase(sshIdentity, "buildin")) {
85        sshKey =
86            new File(
87                workingDirectory.getAbsolutePath()
88                    + SystemUtils.FILE_SEPARATOR
89                    + "gitconreader-ssh.key");
90        FileUtils.copyURLToFile(
91            getClass().getClassLoader().getResource("META-INF/gitcon/gitconreader-ssh.key"), sshKey);
92        LOG.info("Build-in SSH private key is copied into " + sshKey);
93  
94      } else if (sshIdentity != null && !sshIdentity.equalsIgnoreCase("default")) {
95        sshKey = new File(sshIdentity);
96        LOG.info("About to use specified SSH key " + sshIdentity);
97      } else {
98        LOG.info("Default system SSH key will apply");
99      }
100 
101     if (sshKey == null) {
102       executor = JGitCallExecutor.synchronize(JGitCallExecutor.direct());
103     } else if (!sshKey.canRead()) {
104       throw new IllegalStateException(
105           "Configured SSH private key " + sshKey + " is not accessible");
106     } else {
107       executor = JGitCallExecutor.withSshPrivateKey(sshKey.getAbsolutePath());
108       LOG.info("JGit executor is set with build-in SSH key " + sshKey);
109     }
110 
111     // Clone the repo
112     final CloneCommand clone = Git.cloneRepository().setDirectory(sourceDirectory).setURI(repoUri);
113     if (credentialsProvider != null) {
114       clone.setCredentialsProvider(credentialsProvider);
115     }
116     LOG.info("Running git clone " + repoUri + " against " + sourceDirectory);
117     git = executor.invokeCall(clone::call);
118 
119     // If branch or commit is specified, call git checkout
120     if (branchOrCommit != null) {
121       LOG.info("Calling git checkout " + branchOrCommit + " ...");
122       Ref result = executor.invokeCall(() -> git.checkout().setName(branchOrCommit).call());
123       LOG.info("Git checkout returned " + result);
124     }
125     return sourceDirectory;
126   }
127 
128   /**
129    * Caller sets branchOrCommit in order to checkout files from a non-default branch or specified
130    * commit. When specified, a {@literal git checkout} command will be called right after {@literal
131    * git clone} when application starts.
132    *
133    * @param branch Branch or commit in Git repository
134    */
135   public void setBranchOrCommit(String branch) {
136     this.branchOrCommit = branch;
137   }
138 
139   /**
140    * Gitcon jar file comes with a build-in SSH private key for user {@literal gitconreader} in both
141    * Github and BitBucket. This method tells {@link JGitSource} to use the build-in SSH key.
142    *
143    * @param buildInSshIdentityUsed True to use the build-in SSH priavate key
144    */
145   public void setBuildInSshIdentityUsed(boolean buildInSshIdentityUsed) {
146     this.buildInSshIdentityUsed = buildInSshIdentityUsed;
147   }
148 
149   /**
150    * Use specified SSH private key for authentication
151    *
152    * @param privateKeyPath Path to SSH private key. However if value is {@literal default}, the
153    *     default OS SSH key will be used. If value is {@literal buildin}, a build-in SSH private
154    *     key, which is registered for user {@literal gitconreader} in Github and BitBucket will be
155    *     used.
156    * @see #setBuildInSshIdentityUsed(boolean)
157    */
158   public void setSshIdentity(String privateKeyPath) {
159     this.sshIdentity = privateKeyPath;
160   }
161 
162   /**
163    * An alternative of {@link #setSshIdentity(String)} that takes a {@link File} instead of file
164    * path.
165    *
166    * @param privateKeyFile SSH private key file object
167    */
168   public void setSshIdentityFile(File privateKeyFile) {
169     setSshIdentity(privateKeyFile == null ? null : privateKeyFile.getAbsolutePath());
170   }
171 
172   /**
173    * An alternative method of {@link #setUserPassword(String, String)} which takes parameters from
174    * one single string
175    *
176    * @param userAndPassword One string in form of {@literal <user>:<password>}
177    */
178   public void setUserPassword(String userAndPassword) {
179     int position = userAndPassword.indexOf(':');
180     Validate.isTrue(
181         position > 0 && position < userAndPassword.length() - 1,
182         "Input must be <username>:<password>, but it is " + userAndPassword);
183     setUserPassword(
184         userAndPassword.substring(0, position), userAndPassword.substring(position + 1));
185   }
186 
187   /**
188    * Set user and password used to authenticate access to Git repository. It's often used when Git
189    * repo URI starts with {@literal https} where basic authentication is used.
190    *
191    * @param user Login user name
192    * @param password Login password
193    */
194   public void setUserPassword(String user, String password) {
195     Validate.notNull(user, "User name must be supplied");
196     Validate.notNull(password, "Password must be supplied");
197 
198     if (credentialsProvider != null) {
199       throw new IllegalStateException(
200           "Credentials provider can only be set for once. It's already " + credentialsProvider);
201     }
202 
203     this.credentialsProvider = new UsernamePasswordCredentialsProvider(user, password);
204     LOG.info("Credentials provider is set to user/password instance");
205   }
206 
207   /**
208    * Update local repository by running a {@literal git pull} command
209    *
210    * @throws GitAPIException Allows JGit exceptions
211    */
212   @Override
213   public void updateWorkingDirectory(File workingDirectory) throws GitAPIException {
214     LOG.info("Running a git pull command ... ");
215     PullResult result = executor.invokeCall(() -> git.pull().call());
216     LOG.info("Pull command returned " + result);
217   }
218 }