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