View Javadoc
1   /**
2    * Copyright (c) 2012-2014, jcabi.com
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met: 1) Redistributions of source code must retain the above
8    * copyright notice, this list of conditions and the following
9    * disclaimer. 2) Redistributions in binary form must reproduce the above
10   * copyright notice, this list of conditions and the following
11   * disclaimer in the documentation and/or other materials provided
12   * with the distribution. 3) Neither the name of the jcabi.com nor
13   * the names of its contributors may be used to endorse or promote
14   * products derived from this software without specific prior written
15   * permission.
16   *
17   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
19   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21   * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
28   * OF THE POSSIBILITY OF SUCH DAMAGE.
29   */
30  package com.jcabi.heroku.maven.plugin;
31  
32  import com.jcabi.aspects.Immutable;
33  import com.jcabi.aspects.RetryOnFailure;
34  import com.jcabi.log.Logger;
35  import com.jcabi.log.VerboseProcess;
36  import java.io.File;
37  import java.io.IOException;
38  import java.util.ArrayList;
39  import java.util.List;
40  import javax.validation.constraints.NotNull;
41  import lombok.EqualsAndHashCode;
42  import lombok.ToString;
43  import org.apache.commons.io.FileUtils;
44  import org.apache.commons.lang3.StringUtils;
45  
46  /**
47   * Git engine.
48   *
49   * @author Yegor Bugayenko (yegor@tpc2.com)
50   * @version $Id$
51   * @since 0.4
52   * @checkstyle ClassDataAbstractionCoupling (500 lines)
53   */
54  @Immutable
55  @ToString
56  @EqualsAndHashCode(of = "script")
57  final class Git {
58  
59      /**
60       * Permissions to set to SSH key file.
61       */
62      @SuppressWarnings("PMD.AvoidUsingOctalValues")
63      private static final int PERMS = 0600;
64  
65      /**
66       * Default SSH location.
67       */
68      private static final String SSH = "/usr/bin/ssh";
69  
70      /**
71       * Location of shell script.
72       */
73      private final transient String script;
74  
75      /**
76       * Public ctor.
77       * @param key Location of SSH key
78       * @param temp Temp directory
79       * @throws IOException If some error inside
80       */
81      public Git(@NotNull final File key,
82          @NotNull final File temp) throws IOException {
83          if (!new File(Git.SSH).exists()) {
84              throw new IllegalStateException(
85                  String.format("SSH is not installed at '%s'", Git.SSH)
86              );
87          }
88          final File kfile = new File(temp, "heroku.pem");
89          FileUtils.copyFile(key, kfile);
90          this.chmod(kfile, Git.PERMS);
91          final File file = new File(temp, "git-ssh.sh");
92          this.script = file.getAbsolutePath();
93          FileUtils.writeStringToFile(
94              new File(this.script),
95              String.format(
96                  // @checkstyle LineLength (1 line)
97                  "set -x && %s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i '%s' $@",
98                  Git.SSH,
99                  kfile.getAbsolutePath()
100             )
101         );
102         file.setExecutable(true);
103     }
104 
105     /**
106      * Execute git with these arguments.
107      * @param dir In which directory to run it
108      * @param args Arguments to pass to it
109      * @return Stdout
110      * @checkstyle MagicNumber (2 lines)
111      */
112     @RetryOnFailure(delay = 3000, attempts = 2)
113     public String exec(@NotNull final File dir, @NotNull final String... args) {
114         final List<String> commands = new ArrayList<String>(args.length + 1);
115         commands.add("git");
116         for (final String arg : args) {
117             commands.add(arg);
118         }
119         Logger.info(this, "%s:...", StringUtils.join(commands, " "));
120         final ProcessBuilder builder = new ProcessBuilder(commands);
121         builder.directory(dir);
122         builder.environment().put("GIT_SSH", this.script);
123         return new VerboseProcess(builder).stdout();
124     }
125 
126     /**
127      * Change file permissions.
128      * @param file The file to change
129      * @param mode Permissions to set
130      * @throws IOException If some error inside
131      * @see http://stackoverflow.com/questions/664432
132      * @see http://stackoverflow.com/questions/1556119
133      */
134     private void chmod(final File file, final int mode) throws IOException {
135         new VerboseProcess(
136             new ProcessBuilder(
137                 "chmod",
138                 String.format("%04o", mode),
139                 file.getAbsolutePath()
140             )
141         ).stdout();
142         Logger.debug(
143             this,
144             "chmod(%s, %3o): succeeded",
145             file,
146             mode
147         );
148     }
149 
150 }