Project

General

Profile

Download (9.89 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2020 EDIT
3
* European Distributed Institute of Taxonomy
4
* http://www.e-taxonomy.eu
5
*
6
* The contents of this file are subject to the Mozilla Public License Version 1.1
7
* See LICENSE.TXT at the top of this package for the full license terms.
8
*/
9
package eu.etaxonomy.drush;
10

    
11
import java.io.File;
12
import java.io.IOException;
13
import java.io.InputStream;
14
import java.net.URI;
15
import java.net.URISyntaxException;
16
import java.util.ArrayList;
17
import java.util.Arrays;
18
import java.util.List;
19
import java.util.Scanner;
20
import java.util.regex.Matcher;
21
import java.util.regex.Pattern;
22

    
23
import org.apache.commons.io.IOUtils;
24
import org.apache.commons.lang3.SystemUtils;
25
import org.apache.log4j.Level;
26
import org.apache.log4j.Logger;
27

    
28
/**
29
 * Java executor for drush (https://www.drush.org/).
30
 *
31
 * <h3>Local usage:</h3> Set the {@link #setDrupalRoot(File) drupal root folder}
32
 * and the {@link #setSiteURI(URI) site uri}.
33
 *
34
 * <h3>Remote usage:</h3> In addition to the above properties you can also
35
 * define an {@link #setSshHost(String) ssh host} and optionally the according
36
 * {@link #setSshUser(String) ssh user} to execute drush on a remote machine.
37
 *
38
 * @author a.kohlbecker
39
 * @since Jul 31, 2020
40
 */
41
public class DrushExecuter {
42

    
43
    public static final Logger logger = Logger.getLogger(DrushExecuter.class);
44

    
45
    private URI siteURI = null;
46

    
47
    private File drupalRoot = null;
48

    
49
    private String sshUser = null;
50

    
51
    private String sshHost = null;
52

    
53
    public DrushExecuter() throws IOException, InterruptedException {
54
        findDrushCommand();
55
    }
56

    
57
    /**
58
     * List indexes returned from
59
     * <code>DrushExecuter.execute(DrushCommand cmd, String... value)</code>:
60
     *
61
     * <ol>
62
     * <li>major</li>
63
     * <li>minor</li>
64
     * <li>patch</li>
65
     * <ol>
66
     */
67
    public static DrushCommand version = new DrushCommand(Arrays.asList("--version"),
68
            "Drush Version\\s+:\\s+(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)", null);
69

    
70
    public static DrushCommand help = new DrushCommand(Arrays.asList("help"), null, null);
71

    
72
    public static DrushCommand coreStatus = new DrushCommand(Arrays.asList("core-status"), null, null);
73

    
74
    /**
75
     * List indexes returned from
76
     * <code>DrushExecuter.execute(DrushCommand cmd, String... value)</code>:
77
     * Multiple matches are possible:
78
     * <ol>
79
     * <li>value</li>
80
     * <li>value</li>
81
     * <li>value</li>
82
     * <ol>
83
     */
84
    public static DrushCommand variableGet = new DrushCommand(Arrays.asList("vget", "%s"), ".*:\\s+'(?<value>.*)'",
85
            null);
86

    
87
    /**
88
     * List indexes returned from
89
     * <code>DrushExecuter.execute(DrushCommand cmd, String... value)</code>:
90
     * Multiple matches are possible:
91
     * <ol>
92
     * <li>value</li>
93
     * <li>status</li>
94
     * <ol>
95
     */
96
    public static DrushCommand variableSet = new DrushCommand(Arrays.asList("--yes", "vset", "%s", "%s"), null,
97
            "[^\\\"]*\\\"(?<value>.*)\\\"\\.\\s+\\[(?<status>\\w+)\\]");
98

    
99
    /**
100
     * @throws IOException
101
     *             if an I/O error occurs in the ProcessBuilder
102
     * @throws InterruptedException
103
     *             if the Process was interrupted
104
     */
105
    private void findDrushCommand() throws IOException, InterruptedException {
106

    
107
        if (SystemUtils.IS_OS_WINDOWS) {
108
            throw new RuntimeException("not yet implmented for Windows");
109
        }
110

    
111
        List<String> matches = execute(version);
112
        assert !matches.get(0).isEmpty() : "No suitable drush command found in the system";
113
        String majorVersion = matches.get(0);
114
        if (Integer.valueOf(majorVersion) < 8) {
115
            throw new RuntimeException("drush version >= 8 required");
116
        }
117

    
118
    }
119

    
120
    /**
121
     * @throws IOException
122
     *             if an I/O error occurs in the ProcessBuilder
123
     * @throws InterruptedException
124
     *             if the Process was interrupted
125
     */
126
    public List<String> execute(DrushCommand cmd, String... value) throws IOException, InterruptedException {
127

    
128
        List<String> executableWithArgs = new ArrayList<>();
129

    
130
        if (sshHost != null) {
131
            executableWithArgs.add("ssh");
132
            String userHostArg = sshHost;
133
            if (sshUser != null) {
134
                userHostArg = sshUser + "@" + sshHost;
135
            }
136
            executableWithArgs.add(userHostArg);
137
        }
138

    
139
        executableWithArgs.add("drush");
140

    
141
        if (drupalRoot != null) {
142
            executableWithArgs.add("--root=" + drupalRoot.toString());
143
        }
144
        if (siteURI != null) {
145
            executableWithArgs.add("--uri=" + siteURI.toString());
146
        }
147
        int commandSubstitutions = 0;
148
        for (String arg : cmd.args) {
149
            if (arg.contains("%s")) {
150
                executableWithArgs.add(String.format(arg, value[commandSubstitutions]));
151
                commandSubstitutions++;
152
            } else {
153
                executableWithArgs.add(arg);
154
            }
155
        }
156

    
157
        List<String> matches = new ArrayList<>();
158

    
159
        ProcessBuilder pb = new ProcessBuilder(executableWithArgs);
160
        logger.debug("Command: " + pb.command().toString());
161
        Process process = pb.start();
162
        int exitCode = process.waitFor();
163

    
164
        if (exitCode == 0) {
165
            String out = readExecutionResponse(matches, process.getInputStream(), cmd.outRegex);
166
            String error = readExecutionResponse(matches, process.getErrorStream(), cmd.errRegex);
167
            if (out != null && !out.isEmpty()) {
168
                logger.error(error);
169
            }
170
            if (error != null && !error.isEmpty()) {
171
                logger.error(error);
172
            }
173
        } else {
174
            throw new RuntimeException(IOUtils.toString(process.getErrorStream()));
175
        }
176
        return matches;
177
    }
178

    
179
    protected String readExecutionResponse(List<String> matches, InputStream stream, Pattern regex) throws IOException {
180
        String out;
181
        if (regex != null) {
182
            Scanner scanner = new Scanner(stream);
183
            while (true) {
184
                out = scanner.findWithinHorizon(regex, 0);
185
                if (out == null) {
186
                    break;
187
                }
188
                if (out != null) {
189
                    Matcher m = regex.matcher(out);
190
                    int patternMatchCount = 0;
191
                    while (m.find()) {
192
                        patternMatchCount++;
193
                        if (m.groupCount() > 0) {
194
                            for (int g = 1; g <= m.groupCount(); g++) {
195
                                matches.add(m.group(g));
196
                                logger.debug("match[" + patternMatchCount + "." + g + "]: " + m.group(g));
197
                            }
198
                        } else {
199
                            matches.add(m.group(0));
200
                            logger.debug("entire pattern match[" + patternMatchCount + ".0]: " + m.group(0));
201
                        }
202
                    }
203
                }
204
            }
205
            scanner.close();
206
            return null;
207
        } else {
208
            out = IOUtils.toString(stream);
209
            logger.debug(out);
210
            return out;
211
        }
212

    
213
    }
214

    
215
    public static class DrushCommand {
216

    
217
        Pattern outRegex;
218
        Pattern errRegex;
219
        List<String> args = new ArrayList<>();
220

    
221
        public DrushCommand(List<String> args, String outRegex, String errRegex) {
222
            this.args = args;
223
            if (outRegex != null) {
224
                this.outRegex = Pattern.compile(outRegex, Pattern.MULTILINE);
225
            }
226
            if (errRegex != null) {
227
                this.errRegex = Pattern.compile(errRegex, Pattern.MULTILINE);
228
            }
229
        }
230
    }
231

    
232
    /**
233
     * These tests have not been implemented as jUnit tests since the execution
234
     * it too much dependent from the local environment. Once the
235
     * <code>DrushExecuter</code> is being used in the selenium test suite will
236
     * be tested implicitly anyway.
237
     */
238
    public static void main(String[] args) throws URISyntaxException {
239
        DrushExecuter.logger.setLevel(Level.DEBUG);
240
        try {
241
            DrushExecuter dex = new DrushExecuter();
242
            dex.setDrupalRoot(new File("/home/andreas/workspaces/www/drupal-7"));
243
            dex.setSiteURI(new URI("http://edit.test/d7/caryophyllales/"));
244
            dex.execute(coreStatus);
245
            dex.execute(help);
246
            List<String> results = dex.execute(variableSet, "cdm_webservice_url",
247
                    "http://api.cybertaxonomy.org/cyprus/");
248
            if (!results.get(0).equals("http://api.cybertaxonomy.org/cyprus/")) {
249
                throw new RuntimeException("unexpected result item 0: " + results.get(0));
250
            }
251
            if (!results.get(1).equals("success")) {
252
                throw new RuntimeException("unexpected result item 1: " + results.get(0));
253
            }
254
            // testing remote execution via ssh
255
            dex.sshHost = "edit-int";
256
            dex.setDrupalRoot(new File("/var/www/drupal-7"));
257
            dex.setSiteURI(new URI("http://int.e-taxonomy.eu/dataportal/integration/cyprus"));
258
            results = dex.execute(variableGet, "cdm_webservice_url");
259
            if (!results.get(0).equals("http://int.e-taxonomy.eu/cdmserver/integration_cyprus/")) {
260
                throw new RuntimeException("unexpected result item 0: " + results.get(0));
261
            }
262

    
263
        } catch (IOException | InterruptedException | AssertionError e) {
264
            e.printStackTrace();
265
        }
266
    }
267

    
268
    public URI getSiteURI() {
269
        return siteURI;
270
    }
271

    
272
    public void setSiteURI(URI siteURI) {
273
        this.siteURI = siteURI;
274
    }
275

    
276
    public File getDrupalRoot() {
277
        return drupalRoot;
278
    }
279

    
280
    public void setDrupalRoot(File drupalRoot) {
281
        this.drupalRoot = drupalRoot;
282
    }
283

    
284
    public String getSshUser() {
285
        return sshUser;
286
    }
287

    
288
    public void setSshUser(String sshUser) {
289
        this.sshUser = sshUser;
290
    }
291

    
292
    public String getSshHost() {
293
        return sshHost;
294
    }
295

    
296
    public void setSshHost(String sshHost) {
297
        this.sshHost = sshHost;
298
    }
299
}
    (1-1/1)