App Engine SDK 1.8.4 release.
[gae.git] / java / src / main / com / google / appengine / tools / admin / AppCfg.java
blobf35bab6b4a8734e97d1d6c8e8b0154ba9770d1b3
2 // Copyright 2008 Google Inc. All Rights Reserved.
4 package com.google.appengine.tools.admin;
6 import com.google.api.client.auth.oauth2.Credential;
7 import com.google.appengine.tools.admin.AppAdmin.LogSeverity;
8 import com.google.appengine.tools.admin.AppAdminFactory.ConnectOptions;
9 import com.google.appengine.tools.admin.ClientLoginServerConnection.ClientAuthFailException;
10 import com.google.appengine.tools.admin.IndexDeleter.DeleteIndexAction;
11 import com.google.appengine.tools.development.DevAppServerMain;
12 import com.google.appengine.tools.info.SupportInfo;
13 import com.google.appengine.tools.info.UpdateCheck;
14 import com.google.appengine.tools.plugins.ActionsAndOptions;
15 import com.google.appengine.tools.plugins.SDKPluginManager;
16 import com.google.appengine.tools.plugins.SDKRuntimePlugin;
17 import com.google.appengine.tools.plugins.SDKRuntimePlugin.ApplicationDirectories;
18 import com.google.appengine.tools.util.Action;
19 import com.google.appengine.tools.util.ClientCookieManager;
20 import com.google.appengine.tools.util.Logging;
21 import com.google.appengine.tools.util.Option;
22 import com.google.appengine.tools.util.Parser;
23 import com.google.appengine.tools.util.Parser.ParseResult;
24 import com.google.appengine.tools.wargen.WarGenerator;
25 import com.google.appengine.tools.wargen.WarGeneratorFactory;
26 import com.google.apphosting.utils.config.AppEngineConfigException;
27 import com.google.apphosting.utils.config.BackendsXml;
28 import com.google.apphosting.utils.config.EarHelper;
29 import com.google.apphosting.utils.config.EarInfo;
30 import com.google.apphosting.utils.config.WebModule;
31 import com.google.common.base.Joiner;
32 import com.google.common.collect.ImmutableList;
34 import java.io.BufferedReader;
35 import java.io.ByteArrayInputStream;
36 import java.io.File;
37 import java.io.FileInputStream;
38 import java.io.FileNotFoundException;
39 import java.io.FileWriter;
40 import java.io.IOException;
41 import java.io.InputStreamReader;
42 import java.io.ObjectInputStream;
43 import java.io.PrintWriter;
44 import java.io.Reader;
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.HashMap;
48 import java.util.HashSet;
49 import java.util.Iterator;
50 import java.util.LinkedList;
51 import java.util.List;
52 import java.util.Map;
53 import java.util.Properties;
54 import java.util.Set;
55 import java.util.TreeSet;
56 import java.util.prefs.Preferences;
58 import javax.net.ssl.HostnameVerifier;
59 import javax.net.ssl.HttpsURLConnection;
60 import javax.net.ssl.SSLSession;
62 /**
63 * The command-line SDK tool for administration of App Engine applications.
66 public class AppCfg {
68 public static final String USE_JAVA6_SYSTEM_PROP = "com.google.apphosting.runtime.use_java6";
70 private static final String EXTERNAL_RESOURCE_DIR_ARG =
71 DevAppServerMain.EXTERNAL_RESOURCE_DIR_ARG;
72 private static final String GENERATE_WAR_ARG =
73 DevAppServerMain.GENERATE_WAR_ARG;
74 private static final String GENERATED_WAR_DIR_ARG =
75 DevAppServerMain.GENERATED_WAR_DIR_ARG;
76 private static final String OVERRIDE_MODULE_SHORT_ARG = "M";
77 private static final String OVERRIDE_MODULE_LONG_ARG = "module";
79 private final ConnectOptions connectOptions;
80 private String externalResourceDir;
81 private boolean generateWar = false;
82 private String generatedWarDir;
83 private AppCfgAction action;
84 private String applicationDirectory;
85 private String moduleName;
86 private AppAdmin admin;
87 private boolean passin;
88 private boolean doBatch = true;
89 private boolean doJarSplitting = false;
90 private Set<String> jarSplittingExcludeSuffixes = null;
91 private boolean disablePrompt = false;
92 private File logFile = null;
93 private String compileEncoding = null;
94 private LoginReader loginReader = null;
95 private String overrideAppId;
96 private String overrideModule;
97 private String overrideAppVersion;
98 private boolean oauth2 = false;
99 private String oauth2RefreshToken = null;
100 private String oauth2ClientId = null;
101 private String oauth2ClientSecret = null;
102 private boolean useCookies = true;
103 private boolean doJarJSPs = true;
104 private boolean doJarClasses = false;
105 private boolean deleteJSPs = false;
106 private String runtime;
107 private boolean allowAnyRuntime = false;
108 private boolean disableUpdateCheck = false;
109 private boolean failOnPrecompilationError = false;
111 public static void main(String[] args) {
112 Logging.initializeLogging();
113 new AppCfg(args);
116 protected AppCfg(String[] cmdLineArgs) {
117 this(new AppAdminFactory(), cmdLineArgs);
120 public AppCfg(AppAdminFactory factory, String[] cmdLineArgs) {
121 connectOptions = new ConnectOptions();
122 Parser parser = new Parser();
124 PrintWriter logWriter;
126 try {
127 logFile = File.createTempFile("appcfg", ".log");
128 logWriter = new PrintWriter(new FileWriter(logFile), true);
129 } catch (IOException e) {
130 throw new RuntimeException("Unable to enable logging.", e);
133 try {
134 ParseResult result =
135 parser.parseArgs(actionsAndOptions.actions, actionsAndOptions.options, cmdLineArgs);
136 action = (AppCfgAction) result.getAction();
137 validateCommandLineForEar();
138 try {
139 result.applyArgs();
140 } catch (IllegalArgumentException e) {
141 e.printStackTrace(logWriter);
142 System.out.println("Bad argument: " + e.getMessage());
143 System.out.println(action.getHelpString());
144 System.exit(1);
146 if (System.getProperty("http.proxyHost") != null &&
147 System.getProperty("https.proxyHost") == null) {
148 System.setProperty("https.proxyHost",
149 System.getProperty("http.proxyHost"));
150 if (System.getProperty("http.proxyPort") != null &&
151 System.getProperty("https.proxyPort") == null) {
152 System.setProperty("https.proxyPort",
153 System.getProperty("http.proxyPort"));
157 if (applicationDirectory != null) {
158 File appDirectoryFile = new File(applicationDirectory);
159 validateApplicationDirectory(appDirectoryFile);
161 UpdateCheck updateCheck = new UpdateCheck(connectOptions.getServer(), appDirectoryFile,
162 connectOptions.getSecure());
163 if (!disableUpdateCheck) {
164 updateCheck.maybePrintNagScreen(System.out);
166 updateCheck.checkJavaVersion(System.out);
168 if (oauth2) {
169 authorizeOauth2(connectOptions);
170 } else {
171 loadCookies(connectOptions);
174 factory.setBatchMode(doBatch);
176 factory.setJarClassessEnabled(doJarClasses);
177 factory.setJarJSPsEnabled(doJarJSPs);
178 factory.setDeleteJSPs(deleteJSPs);
179 factory.setJarSplittingEnabled(doJarSplitting);
180 if (jarSplittingExcludeSuffixes != null) {
181 factory.setJarSplittingExcludes(jarSplittingExcludeSuffixes);
183 if (compileEncoding != null) {
184 factory.setCompileEncoding(compileEncoding);
186 factory.setRuntime(runtime);
187 factory.setAllowAnyRuntime(allowAnyRuntime);
188 factory.setFailOnPrecompilationError(failOnPrecompilationError);
189 System.out.println("Reading application configuration data...");
191 Iterable<Application> applications = readApplication(factory);
192 executeAction(factory, applications, logWriter, action);
193 System.out.println("Success.");
194 cleanStaging(applications);
196 } catch (IllegalArgumentException e) {
197 e.printStackTrace(logWriter);
198 System.out.println("Bad argument: " + e.getMessage());
199 printHelp();
200 System.exit(1);
201 } catch (AppEngineConfigException e) {
202 e.printStackTrace(logWriter);
203 System.out.println("Bad configuration: " + e.getMessage());
204 if (e.getCause() != null) {
205 System.out.println(" Caused by: " + e.getCause().getMessage());
207 printLogLocation();
208 System.exit(1);
209 } catch (Exception e) {
210 System.out.println("Encountered a problem: " + e.getMessage());
211 e.printStackTrace(logWriter);
212 printLogLocation();
213 System.exit(1);
217 private void validateCommandLineForEar() {
218 if (EarHelper.isEar(applicationDirectory)) {
219 if (!action.isEarAction()) {
220 throw new IllegalArgumentException(
221 "The requested action does not support EAR configurations");
223 if (overrideModule != null) {
224 throw new IllegalArgumentException("With an EAR configuration " + "-"
225 + OVERRIDE_MODULE_SHORT_ARG + "/" + "--" + OVERRIDE_MODULE_LONG_ARG
226 + " is not allowed.");
228 if (externalResourceDir != null) {
229 throw new IllegalArgumentException("With an EAR configuration "
230 + "--" + EXTERNAL_RESOURCE_DIR_ARG + " is not allowed.");
235 private Iterable<Application> readApplication(AppAdminFactory factory) throws IOException {
236 ImmutableList.Builder<Application> resultBuilder = ImmutableList.builder();
237 if (applicationDirectory != null) {
238 if (EarHelper.isEar(applicationDirectory, false)) {
239 EarInfo earInfo = EarHelper.readEarInfo(applicationDirectory,
240 new File(Application.getSdkDocsDir(), "appengine-application.xsd"));
241 String applicationId = overrideAppId != null ?
242 overrideAppId : earInfo.getAppengineApplicationXml().getApplicationId();
243 for (WebModule webModule : earInfo.getWebModules()) {
244 System.out.println("Processing module " + webModule.getModuleName());
245 resultBuilder.add(readWar(factory, webModule.getApplicationDirectory().getAbsolutePath(),
246 applicationId, null));
247 String contextRootWarning =
248 "Ignoring application.xml context-root element, for details see "
249 + "https://developers.google.com/appengine/docs/java/modules/#config";
250 System.out.println(contextRootWarning);
252 } else {
253 resultBuilder.add(readWar(factory, applicationDirectory,
254 overrideAppId, overrideModule));
257 return resultBuilder.build();
260 private Application readWar(AppAdminFactory factory, String warDirectory,
261 String applicationIdOrNull, String moduleNameOrNull) throws IOException {
262 Application application = Application.readApplication(warDirectory,
263 applicationIdOrNull,
264 moduleNameOrNull,
265 overrideAppVersion);
266 if (externalResourceDir != null) {
267 application.setExternalResourceDir(externalResourceDir);
269 if (application.getAppEngineWebXml().getUseVm()) {
270 factory.setCompileJsps(false);
272 application.setListener(new UpdateListener() {
273 @Override
274 public void onProgress(UpdateProgressEvent event) {
275 System.out.println(event.getPercentageComplete() + "% " + event.getMessage());
278 @Override
279 public void onSuccess(UpdateSuccessEvent event) {
280 System.out.println("Operation complete.");
283 @Override
284 public void onFailure(UpdateFailureEvent event) {
285 System.out.println(event.getFailureMessage());
288 return application;
291 private void executeAction(AppAdminFactory factory, Iterable<Application> applications,
292 PrintWriter logWriter, AppCfgAction executeMe) {
293 try {
294 if (applications.iterator().hasNext()) {
295 boolean firstModule = true;
296 for (Application application : applications) {
297 moduleName = WebModule.getModuleName(application.getAppEngineWebXml());
298 try {
299 admin = factory.createAppAdmin(connectOptions, application, logWriter);
300 if (!firstModule) {
301 admin.getUpdateOptions().setUpdateGlobalConfigurations(false);
303 System.out.println(String.format("\n\nBeginning interaction for module %s...",
304 moduleName));
305 executeMe.execute();
306 } finally {
307 moduleName = null;
308 firstModule = false;
311 } else {
312 admin = factory.createAppAdmin(connectOptions, null, logWriter);
313 executeMe.execute();
315 } catch (AdminException ex) {
316 System.out.println(ex.getMessage());
317 ex.printStackTrace(logWriter);
318 printLogLocation();
319 System.exit(1);
320 } finally {
321 admin = null;
325 private void cleanStaging(Iterable<Application> applications) throws IOException {
326 for (Application application : applications) {
327 if (application != null) {
328 String moduleName = WebModule.getModuleName(application.getAppEngineWebXml());
329 if (!connectOptions.getRetainUploadDir()) {
330 System.out.println(String.format("Cleaning up temporary files for module %s...",
331 moduleName));
332 application.cleanStagingDirectory();
333 } else {
334 File stage = application.getStagingDir();
335 if (stage == null) {
336 System.out.println(String.format(
337 "Temporary staging directory was not needed, and not created for module %s",
338 moduleName));
339 } else {
340 System.out.println(String.format("Temporary staging for module %s directory left in %s",
341 moduleName, stage.getCanonicalPath()));
349 * Prints a uniform message to direct the user to the given logfile for
350 * more information.
352 private void printLogLocation() {
353 if (logFile != null) {
354 System.out.println("Please see the logs [" + logFile.getAbsolutePath() +
355 "] for further information.");
359 private String loadCookies(final ConnectOptions options) {
360 Preferences prefs = Preferences.userNodeForPackage(ServerConnection.class);
361 String prefsEmail = prefs.get("email", null);
363 if (options.getUsePersistedCredentials() && prefsEmail != null) {
364 ClientCookieManager cookies = null;
365 byte[] serializedCookies = prefs.getByteArray("cookies", null);
366 if (serializedCookies != null) {
367 try {
368 cookies = (ClientCookieManager)
369 new ObjectInputStream(
370 new ByteArrayInputStream(serializedCookies)).readObject();
371 } catch (ClassNotFoundException ex) {
372 } catch (IOException ex) {
376 if (options.getUserId() == null ||
377 prefsEmail.equals(options.getUserId())) {
378 options.setCookies(cookies);
382 options.setPasswordPrompt(new AppAdminFactory.PasswordPrompt() {
383 @Override
384 public String getPassword() {
385 doPrompt();
386 options.setUserId(loginReader.getUsername());
387 return loginReader.getPassword();
390 return prefsEmail;
394 * Tries to get an OAuth2 access token and set it in the ConnectOptions.
395 * It exists with exit code 1 in case no token could be obtained.
397 private void authorizeOauth2(final ConnectOptions options){
398 OAuth2Native client =
399 new OAuth2Native(useCookies, oauth2ClientId, oauth2ClientSecret, oauth2RefreshToken);
400 Credential credential = client.authorize();
401 if (credential != null && credential.getAccessToken() != null) {
402 options.setOauthToken(credential.getAccessToken());
403 } else {
404 System.exit(1);
409 * Helper function for generating a war directory based on an app.yaml file located in an external
410 * resource directory. First the command line arguments are checked to ensure that they are
411 * appropriate for war generation. If there is a problem then a {@link RuntimeException} is
412 * thrown. Otherwise a war directory is generated and its path is returned, and a success
413 * message is written to standard out.
415 * @return The path of the generated war directory.
417 private String validateArgsAndGenerateWar() {
418 if (externalResourceDir == null) {
419 throw new IllegalArgumentException("When generating a war directory --"
420 + EXTERNAL_RESOURCE_DIR_ARG + " must also be specified.");
422 if (EarHelper.isEar(externalResourceDir, false)) {
423 throw new IllegalArgumentException(
424 "With an EAR configuration " + "--" + EXTERNAL_RESOURCE_DIR_ARG + " is not allowed.");
426 File externalResourceDirectory = new File(externalResourceDir);
427 if (!externalResourceDirectory.isDirectory()) {
428 throw new IllegalArgumentException(externalResourceDir + " is not an existing directory.");
430 File appYamlFile = new File(externalResourceDirectory, WarGenerator.APP_YAML);
431 if (!appYamlFile.isFile()) {
432 throw new IllegalArgumentException(appYamlFile.getPath() + " not found.");
434 File destination = (generatedWarDir == null ? null : new File(generatedWarDir));
435 try {
436 WarGenerator warGen =
437 WarGeneratorFactory.newWarGenerator(externalResourceDirectory, destination);
438 String warDir = warGen.generateWarDirectory().getPath();
439 System.out.println("Successfully generated war directory at " + warDir);
440 return warDir;
441 } catch (IOException e) {
442 throw new RuntimeException("Unable to generate a war directory.", e);
446 private void doPrompt() {
448 if (disablePrompt) {
449 System.out.println("Your authentication credentials can't be found and may have expired.\n" +
450 "Please run appcfg directly from the command line to re-establish your credentials.");
451 System.exit(1);
454 getLoginReader().doPrompt();
458 private LoginReader getLoginReader() {
459 if (loginReader == null) {
460 loginReader = LoginReaderFactory.createLoginReader(connectOptions, passin);
462 return loginReader;
465 private static final List<String> generalOptionNamesInHelpOrder =
466 ImmutableList.of(
467 "server",
468 "email",
469 "host",
470 "proxy",
471 "proxy_https",
472 "no_cookies",
473 "sdk_root",
474 "passin",
475 "insecure",
476 "ignore_bad_cert",
477 "application",
478 "version",
479 "oauth2",
480 "use_java7");
482 private static final List<String> optionNamesInHelpOrder =
483 ImmutableList.<String>builder().addAll(generalOptionNamesInHelpOrder).add(
484 "enable_jar_splitting",
485 "jar_splitting_excludes",
486 "disable_jar_jsps",
487 "enable_jar_classes",
488 "delete_jsps",
489 "retain_upload_dir",
490 "compile_encoding",
491 "num_days",
492 "severity",
493 "append",
494 "num_runs",
495 "force"
496 ).build();
498 private static final List<String> actionNamesInHelpOrder =
499 ImmutableList.of(
500 "help",
501 "download_app",
502 "request_logs",
503 "rollback",
504 "start",
505 "stop",
506 "update",
507 "update_indexes",
508 "update_cron",
509 "update_queues",
510 "update_dispatch",
511 "update_dos",
512 "version",
513 "set_default_version",
514 "cron_info",
515 "resource_limits_info",
516 "vacuum_indexes",
517 "backends list",
518 "backends update",
519 "backends rollback",
520 "backends start",
521 "backends stop",
522 "backends delete",
523 "backends configure");
525 private String helpText = null;
526 private void printHelp() {
527 if (helpText == null) {
528 List<String> helpLines = new LinkedList<String>();
529 helpLines.add("usage: AppCfg [options] <action> [<app-dir>] [<argument>]");
530 helpLines.add("");
531 helpLines.add("Action must be one of:");
532 for (String actionName : actionsAndOptions.actionNames) {
533 Action action = actionsAndOptions.getAction(actionName);
534 if (action != null) {
535 helpLines.add(" " + actionName + ": " + action.getShortDescription());
538 helpLines.add("Use 'help <action>' for a detailed description.");
539 helpLines.add("");
540 helpLines.add("options:");
541 for (String optionName : actionsAndOptions.optionNames) {
542 Option option = actionsAndOptions.getOption(optionName);
543 helpLines.addAll(option.getHelpLines());
545 helpText = Joiner.on("\n").join(helpLines);
547 System.out.println(helpText);
548 System.out.println();
551 private final List<Option> builtInOptions = Arrays.asList(
553 new Option("h", "help", true) {
554 @Override
555 public List<String> getHelpLines() {
556 return ImmutableList.<String>of(
557 " -h, --help Show the help message and exit.");
559 @Override
560 public void apply() {
561 printHelp();
562 System.exit(1);
566 new Option("s", "server", false) {
567 @Override
568 public List<String> getHelpLines() {
569 return ImmutableList.<String>of(
570 " -s SERVER, --server=SERVER",
571 " The server to connect to.");
573 @Override
574 public void apply() {
575 connectOptions.setServer(getValue());
579 new Option("e", "email", false) {
580 @Override
581 public List<String> getHelpLines() {
582 return ImmutableList.<String>of(
583 " -e EMAIL, --email=EMAIL",
584 " The username to use. Will prompt if omitted.");
586 @Override
587 public void apply() {
588 connectOptions.setUserId(getValue());
592 new Option("H", "host", false) {
593 @Override
594 public List<String> getHelpLines() {
595 return ImmutableList.<String>of(
596 " -H HOST, --host=HOST Overrides the Host header sent with all RPCs.");
598 @Override
599 public void apply() {
600 connectOptions.setHost(getValue());
604 new Option("p", "proxy", false) {
605 @Override
606 public List<String> getHelpLines() {
607 return ImmutableList.<String>of(
608 " -p PROXYHOST[:PORT], --proxy=PROXYHOST[:PORT]",
609 " Proxies requests through the given proxy server.",
610 " If --proxy_https is also set, only HTTP will be",
611 " proxied here, otherwise both HTTP and HTTPS will.");
613 @Override
614 public void apply() {
615 HostPort hostport = new HostPort(getValue());
617 System.setProperty("http.proxyHost", hostport.getHost());
618 if (hostport.hasPort()) {
619 System.setProperty("http.proxyPort", hostport.getPort());
624 new Option(null, "proxy_https", false) {
625 @Override
626 public List<String> getHelpLines() {
627 return ImmutableList.<String>of(
628 " --proxy_https=PROXYHOST[:PORT]",
629 " Proxies HTTPS requests through the given proxy server.");
631 @Override
632 public void apply() {
633 HostPort hostport = new HostPort(getValue());
635 System.setProperty("https.proxyHost", hostport.getHost());
636 if (hostport.hasPort()) {
637 System.setProperty("https.proxyPort", hostport.getPort());
642 new Option(null, "insecure", true) {
643 @Override
644 public void apply() {
645 connectOptions.setSecure(false);
649 new Option(null, "ignore_bad_cert", true) {
650 @Override
651 public void apply() {
652 HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
653 @Override
654 public boolean verify(String hostname, SSLSession session) {
655 return true;
661 new Option(null, "no_cookies", true) {
662 @Override
663 public List<String> getHelpLines() {
664 return ImmutableList.<String>of(
666 " --no_cookies Do not save/load access credentials to/from disk.");
668 @Override
669 public void apply() {
670 useCookies = false;
671 connectOptions.setUsePersistedCredentials(false);
675 new Option("f", "force", true) {
676 @Override
677 public List<String> getHelpLines() {
678 return ImmutableList.<String>of(
679 " -f, --force Force deletion of indexes without being prompted.");
681 @Override
682 public void apply(){
683 if (action instanceof VacuumIndexesAction){
684 VacuumIndexesAction viAction = (VacuumIndexesAction) action;
685 viAction.promptUserForEachDelete = false;
690 new Option("a", "append", true) {
691 @Override
692 public List<String> getHelpLines() {
693 return ImmutableList.<String>of(
694 " -a, --append Append to existing file.");
696 @Override
697 public void apply() {
698 if (action instanceof RequestLogsAction) {
699 RequestLogsAction logsAction = (RequestLogsAction) action;
700 logsAction.append = true;
705 new Option("n", "num_days", false) {
706 @Override
707 public List<String> getHelpLines() {
708 return ImmutableList.<String>of(
709 " -n NUM_DAYS, --num_days=NUM_DAYS",
710 " Number of days worth of log data to get. The cut-off",
711 " point is midnight UTC. Use 0 to get all available",
712 " logs. Default is 1.");
714 @Override
715 public void apply() {
716 if (action instanceof RequestLogsAction) {
717 RequestLogsAction logsAction = (RequestLogsAction) action;
718 try {
719 logsAction.numDays = Integer.parseInt(getValue());
720 } catch (NumberFormatException e) {
721 throw new IllegalArgumentException("num_days must be an integral number.");
723 } else if (action instanceof CronInfoAction) {
724 CronInfoAction croninfoAction = (CronInfoAction) action;
725 croninfoAction.setNumRuns(getValue());
730 new Option(null, "num_runs", false) {
731 @Override
732 public List<String> getHelpLines() {
733 return ImmutableList.<String>of(
734 " -n NUM_RUNS, --num_runs=NUM_RUNS",
735 " Number of scheduled execution times to compute");
737 @Override
738 public void apply() {
739 if (action instanceof CronInfoAction) {
740 CronInfoAction croninfoAction = (CronInfoAction) action;
741 croninfoAction.setNumRuns(getValue());
746 new Option(null, "severity", false) {
747 @Override
748 public List<String> getHelpLines() {
749 return ImmutableList.<String>of(
750 " --severity=SEVERITY Severity of app-level log messages to get. The range",
751 " is 0 (DEBUG) through 4 (CRITICAL). If omitted, only",
752 " request logs are returned.");
754 @Override
755 public void apply() {
756 RequestLogsAction logsAction = (RequestLogsAction) action;
757 try {
758 int severity = Integer.parseInt(getValue());
759 int maxSeverity = LogSeverity.values().length;
760 if (severity < 0 || severity > maxSeverity) {
761 throw new IllegalArgumentException("severity must be between 0 and " + maxSeverity);
763 logsAction.severity = severity;
764 } catch (NumberFormatException e) {
765 for (Enum<LogSeverity> severity : LogSeverity.values()) {
766 if (getValue().equalsIgnoreCase(severity.toString())) {
767 logsAction.severity = severity.ordinal();
768 return;
771 throw new IllegalArgumentException("severity must be an integral "
772 + "number 0-4, or one of DEBUG, INFO, WARN, ERROR, CRITICAL");
777 new Option(null, "sdk_root", false) {
778 @Override
779 public List<String> getHelpLines() {
780 return ImmutableList.<String>of(
781 " --sdk_root=root Overrides where the SDK is located.");
783 @Override
784 public void apply() {
785 connectOptions.setSdkRoot(getValue());
789 new Option(null, "disable_jar_jsps", true) {
790 @Override
791 public List<String> getHelpLines() {
792 return ImmutableList.<String>of(
793 " --disable_jar_jsps",
794 " Do not jar the classes generated from JSPs.");
797 @Override
798 public void apply() {
799 doJarJSPs = false;
803 new Option(null, "enable_jar_classes", true) {
804 @Override
805 public List<String> getHelpLines() {
806 return ImmutableList.<String>of(
807 " --enable_jar_classes",
808 " Jar the WEB-INF/classes content.");
811 @Override
812 public void apply() {
813 doJarClasses = true;
816 new Option(null, "delete_jsps", true) {
817 @Override
818 public List<String> getHelpLines() {
819 return ImmutableList.<String>of(
820 " --delete_jsps",
821 " Delete the JSP source files after compilation.");
824 @Override
825 public void apply() {
826 deleteJSPs = true;
830 new Option(null, "enable_jar_splitting", true) {
831 @Override
832 public List<String> getHelpLines() {
833 return ImmutableList.<String>of(
834 " --enable_jar_splitting",
835 " Split large jar files (> 10M) into smaller fragments.");
837 @Override
838 public void apply() {
839 doJarSplitting = true;
843 new Option(null, "jar_splitting_excludes", false) {
844 @Override
845 public List<String> getHelpLines() {
846 return ImmutableList.<String>of(
847 " --jar_splitting_excludes=SUFFIXES",
848 " When --enable-jar-splitting is set, files that match",
849 " the list of comma separated SUFFIXES will be excluded",
850 " from all jars.");
852 @Override
853 public void apply() {
854 jarSplittingExcludeSuffixes = new HashSet<String>(Arrays.asList(getValue().split(",")));
858 new Option(null, "retain_upload_dir", true) {
859 @Override
860 public List<String> getHelpLines() {
861 return ImmutableList.<String>of(
862 " --retain_upload_dir",
863 " Do not delete temporary (staging) directory used in",
864 " uploading.");
866 @Override
867 public void apply() {
868 connectOptions.setRetainUploadDir(true);
872 new Option(null, "passin", true) {
873 @Override
874 public List<String> getHelpLines() {
875 return ImmutableList.<String>of(
876 " --passin Always read the login password from stdin.");
878 @Override
879 public void apply() {
880 passin = true;
884 new Option(null, "no_batch", true) {
885 @Override
886 public void apply() {
887 doBatch = false;
890 new Option(null, "compile_encoding", false) {
891 @Override
892 public List<String> getHelpLines() {
893 return ImmutableList.<String>of(
894 " --compile_encoding",
895 " The character encoding to use when compiling JSPs.");
897 @Override
898 public void apply() {
899 compileEncoding = getValue();
903 new Option(null, "disable_prompt", true) {
904 @Override
905 public void apply() {
906 disablePrompt = true;
910 new Option(null, "disable_update_check", true) {
911 @Override
912 public void apply() {
913 disableUpdateCheck = true;
917 new Option("A", "application", false) {
918 @Override
919 public List<String> getHelpLines() {
920 return ImmutableList.<String>of(
921 " -A APP_ID, --application=APP_ID",
922 " Override application id from appengine-web.xml or app.yaml");
924 @Override
925 public void apply() {
926 overrideAppId = getValue();
930 new Option(OVERRIDE_MODULE_SHORT_ARG, OVERRIDE_MODULE_LONG_ARG, false) {
931 @Override
932 public List<String> getHelpLines() {
933 return ImmutableList.<String>of(
934 " -" + OVERRIDE_MODULE_SHORT_ARG + " MODULE, --" + OVERRIDE_MODULE_LONG_ARG
935 + "=MODULE",
936 " Override module from appengine-web.xml or app.yaml");
938 @Override
939 public void apply() {
940 overrideModule = getValue();
944 new Option("V", "version" , false) {
945 @Override
946 public List<String> getHelpLines() {
947 return ImmutableList.<String>of(
948 " -V VERSION, --version=VERSION",
949 " Override (major) version from appengine-web.xml " +
950 "or app.yaml");
952 @Override
953 public void apply() {
954 overrideAppVersion = getValue();
958 new Option(null, "oauth2", true) {
959 @Override
960 public List<String> getHelpLines() {
961 return ImmutableList.<String>of(
962 " --oauth2 Use OAuth2 instead of password auth.");
964 @Override
965 public void apply() {
966 oauth2 = true;
970 new Option(null, "oauth2_refresh_token", false) {
971 @Override
972 public void apply() {
973 oauth2RefreshToken = getValue();
974 useCookies = false;
978 new Option(null, "oauth2_client_id", false) {
979 @Override
980 public void apply() {
981 oauth2ClientId = getValue();
982 useCookies = false;
986 new Option(null, "oauth2_client_secret", false) {
987 @Override
988 public void apply() {
989 oauth2ClientSecret = getValue();
990 useCookies = false;
994 new Option(null, "oauth2_config_file", false) {
995 @Override
996 public void apply() {
997 final Properties props = new Properties();
998 try {
999 props.load(new FileInputStream(getValue()));
1000 } catch (FileNotFoundException e) {
1001 throw new RuntimeException(
1002 String.format("OAuth2 configuration file does not exist: %s", getValue()), e);
1003 } catch (IOException e) {
1004 throw new RuntimeException(
1005 String.format("Could not read OAuth2 configuration file: %s", getValue()), e);
1008 oauth2RefreshToken = props.getProperty("oauth2_refresh_token");
1009 oauth2ClientId = props.getProperty("oauth2_client_id");
1010 oauth2ClientSecret = props.getProperty("oauth2_client_secret");
1012 if (oauth2RefreshToken != null ||
1013 oauth2ClientId != null ||
1014 oauth2ClientSecret != null) {
1015 useCookies = false;
1020 new Option(null, "use_java7", true) {
1021 @Override
1022 public void apply() {
1023 System.setProperty(USE_JAVA6_SYSTEM_PROP, "false");
1027 new Option("r", "runtime", false) {
1028 @Override
1029 public void apply() {
1030 runtime = getValue();
1034 new Option("R", "allow_any_runtime", true) {
1035 @Override
1036 public void apply() {
1037 allowAnyRuntime = true;
1041 new Option(null, EXTERNAL_RESOURCE_DIR_ARG, false) {
1042 @Override
1043 public void apply() {
1044 externalResourceDir = getValue();
1048 new Option(null, GENERATE_WAR_ARG, true) {
1049 @Override
1050 public void apply() {
1051 generateWar = true;
1055 new Option(null, "use_java6", true) {
1056 @Override
1057 public void apply() {
1058 System.setProperty(USE_JAVA6_SYSTEM_PROP, "true");
1062 new Option(null, GENERATED_WAR_DIR_ARG, false) {
1063 @Override
1064 public void apply() {
1065 generateWar = true;
1066 generatedWarDir = getValue();
1070 new Option(null, "fail_on_precompilation_error", true) {
1071 @Override
1072 public void apply() {
1073 failOnPrecompilationError = true;
1077 private final List<Action> builtInActions = Arrays.<Action>asList(
1078 new UpdateAction(),
1079 new RequestLogsAction(),
1080 new RollbackAction(),
1081 new UpdateIndexesAction(),
1082 new UpdateCronAction(),
1083 new UpdateDispatchAction(),
1084 new UpdateDosAction(),
1085 new UpdateQueueAction(),
1086 new CronInfoAction(),
1087 new VacuumIndexesAction(),
1088 new HelpAction(),
1089 new DownloadAppAction(),
1090 new VersionAction(),
1091 new SetDefaultVersionAction(),
1092 new ResourceLimitsInfoAction(),
1093 new StartAction(),
1094 new StopAction(),
1095 new BackendsListAction(),
1096 new BackendsRollbackAction(),
1097 new BackendsUpdateAction(),
1098 new BackendsStartAction(),
1099 new BackendsStopAction(),
1100 new BackendsDeleteAction(),
1101 new BackendsConfigureAction(),
1102 new BackendsAction()
1105 private Map<String, Option> builtInOptionMap;
1107 private List<Option> builtInOptions(String... optionNames) {
1108 if (builtInOptionMap == null) {
1109 builtInOptionMap = new HashMap<String, Option>(builtInOptions.size());
1110 for (Option option : builtInOptions){
1111 builtInOptionMap.put(option.getLongName(), option);
1114 List<Option> options = new LinkedList<Option>();
1115 for (String name : optionNames) {
1116 Option option = builtInOptionMap.get(name);
1117 if (option != null) {
1118 options.add(option);
1121 return options;
1124 private final ActionsAndOptions actionsAndOptions = buildActionsAndOptions();
1126 private ActionsAndOptions buildActionsAndOptions() {
1127 ActionsAndOptions actionsAndOptions = getBuiltInActionsAndOptions();
1128 for (SDKRuntimePlugin runtimePlugin : SDKPluginManager.findAllRuntimePlugins()) {
1129 runtimePlugin.customizeAppCfgActionsAndOptions(actionsAndOptions);
1131 return actionsAndOptions;
1135 * Builds the collection of built-in Actions and Options.
1137 private ActionsAndOptions getBuiltInActionsAndOptions() {
1138 ActionsAndOptions actionsAndOptions = new ActionsAndOptions();
1139 actionsAndOptions.actions = builtInActions;
1140 actionsAndOptions.actionNames = actionNamesInHelpOrder;
1141 actionsAndOptions.options = builtInOptions;
1142 actionsAndOptions.optionNames = optionNamesInHelpOrder;
1143 actionsAndOptions.generalOptionNames = generalOptionNamesInHelpOrder;
1144 return actionsAndOptions;
1147 abstract class AppCfgAction extends Action {
1149 AppCfgAction(String... names) {
1150 this(null, names);
1153 AppCfgAction(List<Option> options, String... names) {
1154 super(options, names);
1157 @Override
1158 protected void setArgs(List<String> args) {
1159 super.setArgs(args);
1162 @Override
1163 public void apply() {
1164 if (generateWar) {
1165 applicationDirectory = validateArgsAndGenerateWar();
1166 List<String> args = getArgs();
1167 List<String> newArgs = new ArrayList<String>(args.size() + 1);
1168 newArgs.add(applicationDirectory);
1169 newArgs.addAll(args);
1170 setArgs(newArgs);
1171 } else {
1172 if (getArgs().size() < 1) {
1173 throw new IllegalArgumentException("Expected the application directory"
1174 + " as an argument after the action name.");
1176 applicationDirectory = getArgs().get(0);
1177 validateCommandLineForEar();
1179 SDKRuntimePlugin runtimePlugin = SDKPluginManager.findRuntimePlugin(
1180 new File(applicationDirectory));
1181 if (runtimePlugin != null) {
1182 try {
1183 ApplicationDirectories appDirs = runtimePlugin.generateApplicationDirectories(
1184 new File(applicationDirectory));
1185 applicationDirectory = appDirs.getWarDir().getPath();
1186 getArgs().set(0, applicationDirectory);
1187 externalResourceDir = appDirs.getExternalResourceDir().getPath();
1188 } catch (IOException e) {
1189 throw new RuntimeException("Unable to generate the war directory", e);
1195 public abstract void execute();
1197 @Override
1198 protected List<String> getHelpLines() {
1199 List<String> helpLines = new LinkedList<String>();
1200 helpLines.addAll(getInitialHelpLines());
1201 helpLines.add("");
1202 helpLines.add("Options:");
1203 for (String optionName : actionsAndOptions.generalOptionNames) {
1204 Option option = actionsAndOptions.getOption(optionName);
1205 if (option != null) {
1206 helpLines.addAll(option.getHelpLines());
1209 if (extraOptions != null) {
1210 for (Option option : extraOptions) {
1211 helpLines.addAll(option.getHelpLines());
1214 return helpLines;
1218 * Returns a list of Strings to be displayed as the initial lines of a help text. Subclasses
1219 * should override this method.
1220 * <p>
1221 * The text returned by this method should describe the base Action without any of its options.
1222 * Text describing the options will be added in lines below this text.
1224 protected List<String> getInitialHelpLines() {
1225 return ImmutableList.of();
1228 protected boolean isEarAction() {
1229 return false;
1232 protected void outputBackendsMessage() {
1233 System.out.println("Looks like you're using Backends. We suggest that you " +
1234 "make the switch to App Engine Modules. See the modules documentation " +
1235 "to learn more about converting: https://developers.google.com/appengine/" +
1236 "docs/java/modules/converting");
1240 class UpdateAction extends AppCfgAction {
1241 UpdateAction() {
1242 super(builtInOptions("enable_jar_splitting", "jar_splitting_excludes", "retain_upload_dir",
1243 "compile_encoding", "disable_jar_jsps", "delete_jsps", "enable_jar_classes"), "update");
1244 shortDescription = "Create or update an app version.";
1247 @Override
1248 public void execute() {
1249 admin.update(new AppCfgUpdateModuleListener());
1252 @Override
1253 protected List<String> getInitialHelpLines() {
1254 return ImmutableList.of(
1255 "AppCfg [options] update <app-dir>",
1257 "Installs a new version of the application onto the server, as the",
1258 "default version for end users.");
1261 @Override
1262 protected boolean isEarAction() {
1263 return true;
1267 class RequestLogsAction extends AppCfgAction {
1268 String outputFile;
1269 int numDays = 1;
1270 int severity = -1;
1271 boolean append = false;
1273 RequestLogsAction() {
1274 super(builtInOptions("num_days", "severity", "append"), "request_logs");
1275 shortDescription = "Write request logs in Apache common log format.";
1277 @Override
1278 public void apply() {
1279 super.apply();
1280 if (getArgs().size() != 2) {
1281 throw new IllegalArgumentException("Expected the application directory"
1282 + " and log file as arguments after the request_logs action name.");
1284 outputFile = getArgs().get(1);
1286 @Override
1287 public void execute() {
1288 Reader reader = admin.requestLogs(numDays,
1289 severity >= 0 ? LogSeverity.values()[severity] : null);
1290 if (reader == null) {
1291 return;
1294 BufferedReader r = new BufferedReader(reader);
1295 PrintWriter writer = null;
1296 try {
1297 if (outputFile.equals("-")) {
1298 writer = new PrintWriter(System.out);
1299 } else {
1300 writer = new PrintWriter(new FileWriter(outputFile, append));
1302 String line = null;
1303 while ((line = r.readLine()) != null) {
1304 writer.println(line);
1306 } catch (IOException e) {
1307 throw new RuntimeException("Failed to read logs: " + e);
1308 } finally {
1309 if (writer != null) {
1310 writer.close();
1312 try {
1313 r.close();
1314 } catch (IOException e) {
1318 @Override
1319 protected List<String> getInitialHelpLines() {
1320 return ImmutableList.of(
1321 "AppCfg [options] request_logs <app-dir> <output-file>",
1323 "Populates the output-file with recent logs from the application.");
1327 class RollbackAction extends AppCfgAction {
1328 RollbackAction() {
1329 super("rollback");
1330 shortDescription = "Rollback an in-progress update.";
1332 @Override
1333 public void execute() {
1334 admin.rollback();
1336 @Override
1337 protected List<String> getInitialHelpLines() {
1338 return ImmutableList.of(
1339 "AppCfg [options] rollback <app-dir>",
1341 "The 'update' command requires a server-side transaction.",
1342 "Use 'rollback' if you experience an error during 'update'",
1343 "and want to begin a new update transaction.");
1347 class UpdateIndexesAction extends AppCfgAction {
1348 UpdateIndexesAction() {
1349 super("update_indexes");
1350 shortDescription = "Update application indexes.";
1352 @Override
1353 public void execute() {
1354 admin.updateIndexes();
1356 @Override
1357 protected List<String> getInitialHelpLines() {
1358 return ImmutableList.of(
1359 "AppCfg [options] update_indexes <app-dir>",
1361 "Updates the datastore indexes for the server to add any in the current",
1362 "application directory. Does not alter the running application version, nor",
1363 "remove any existing indexes.");
1367 class UpdateCronAction extends AppCfgAction {
1368 UpdateCronAction() {
1369 super("update_cron");
1370 shortDescription = "Update application cron jobs.";
1372 @Override
1373 public void execute() {
1374 admin.updateCron();
1375 shortDescription = "Update application cron jobs.";
1377 @Override
1378 protected List<String> getInitialHelpLines() {
1379 return ImmutableList.of(
1380 "AppCfg [options] update_cron <app-dir>",
1382 "Updates the cron jobs for the application. Updates any new, removed or changed",
1383 "cron jobs. Does not otherwise alter the running application version.");
1387 class UpdateDispatchAction extends AppCfgAction {
1388 UpdateDispatchAction() {
1389 super("update_dispatch");
1390 shortDescription = "Update the application dispatch configuration.";
1392 @Override
1393 public void execute() {
1394 admin.updateDispatch();
1396 @Override
1397 protected List<String> getInitialHelpLines() {
1398 return ImmutableList.of(
1399 "AppCfg [options] update_dispatch <app-dir>",
1401 "Updates the application dispatch configuration.",
1402 "Does not otherwise alter the running application version.");
1406 class UpdateDosAction extends AppCfgAction {
1407 UpdateDosAction() {
1408 super("update_dos");
1409 shortDescription = "Update application DoS protection configuration.";
1411 @Override
1412 public void execute() {
1413 admin.updateDos();
1415 @Override
1416 protected List<String> getInitialHelpLines() {
1417 return ImmutableList.of(
1418 "AppCfg [options] update_dos <app-dir>",
1420 "Updates the DoS protection configuration for the application.",
1421 "Does not otherwise alter the running application version.");
1425 class UpdateQueueAction extends AppCfgAction {
1426 UpdateQueueAction() {
1427 super("update_queues");
1428 shortDescription = "Update application task queue definitions.";
1430 @Override
1431 public void execute() {
1432 admin.updateQueues();
1434 @Override
1435 protected List<String> getInitialHelpLines() {
1436 return ImmutableList.of(
1437 "AppCfg [options] " + getNameString() + " <app-dir>",
1439 "Updates any new, removed or changed task queue definitions.",
1440 "Does not otherwise alter the running application version.");
1444 class CronInfoAction extends AppCfgAction {
1445 int numRuns = 5;
1447 CronInfoAction() {
1448 super(builtInOptions("num_runs"), "cron_info");
1449 shortDescription = "Displays times for the next several runs of each cron job.";
1451 @Override
1452 public void execute() {
1453 List<CronEntry> entries = admin.cronInfo();
1454 if (entries.size() == 0) {
1455 System.out.println("No cron jobs defined.");
1456 } else {
1457 System.out.println(entries.size() + " cron entries defined.\n");
1458 for (CronEntry entry : entries) {
1459 System.out.println(entry.toXml());
1460 System.out.println("Next " + numRuns + " execution times:");
1461 Iterator<String> iter = entry.getNextTimesIterator();
1462 for (int i = 0; i < numRuns; i++) {
1463 System.out.println(" " + iter.next());
1465 System.out.println("");
1469 @Override
1470 protected List<String> getInitialHelpLines() {
1471 return ImmutableList.of(
1472 "AppCfg [options] cron_info <app-dir>",
1474 "Displays times for the next several runs of each cron job.");
1476 public void setNumRuns(String numberString) {
1477 try {
1478 numRuns = Integer.parseInt(numberString);
1479 } catch (NumberFormatException e) {
1480 throw new IllegalArgumentException("num_runs must be an integral number.");
1482 if (numRuns < 0) {
1483 throw new IllegalArgumentException("num_runs must be positive.");
1488 class VacuumIndexesAction extends AppCfgAction {
1489 public boolean promptUserForEachDelete = true;
1491 VacuumIndexesAction() {
1492 super(builtInOptions("force"), "vacuum_indexes");
1493 shortDescription = "Delete unused indexes from application.";
1496 @Override
1497 public void execute() {
1498 ConfirmationCallback<IndexDeleter.DeleteIndexAction> callback = null;
1499 if (promptUserForEachDelete) {
1500 callback = new ConfirmationCallback<IndexDeleter.DeleteIndexAction>() {
1501 @Override
1502 public Response confirmAction(DeleteIndexAction action) {
1503 while (true) {
1504 String prompt = "\n" + action.getPrompt() + " (N/y/a): ";
1505 System.out.print(prompt);
1506 System.out.flush();
1507 BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
1508 String response;
1509 try {
1510 response = in.readLine();
1511 } catch (IOException ioe) {
1512 response = null;
1514 response = (null == response ? "" : response.trim().toLowerCase());
1515 if ("y".equals(response)) {
1516 return Response.YES;
1518 if ("n".equals(response) || response.isEmpty()) {
1519 return Response.NO;
1521 if ("a".equals(response)) {
1522 return Response.YES_ALL;
1528 admin.vacuumIndexes(callback, new AppCfgVacuumIndexesListener());
1531 @Override
1532 protected List<String> getInitialHelpLines() {
1533 return ImmutableList.of(
1534 "AppCfg [options] vacuum_indexes <app-dir>",
1536 "Deletes indexes on the server that are not present in the local",
1537 "index configuration file. The user is prompted before each delete.");
1541 class HelpAction extends AppCfgAction {
1542 HelpAction() {
1543 super("help");
1544 shortDescription = "Print help for a specific action.";
1546 @Override
1547 public void apply() {
1548 if (getArgs().isEmpty()) {
1549 printHelp();
1550 } else {
1551 Action foundAction = Parser.lookupAction(actionsAndOptions.actions,
1552 getArgs().toArray(new String[0]), 0);
1553 if (foundAction == null) {
1554 System.out.println("No such command \"" + getArgs().get(0) + "\"\n\n");
1555 printHelp();
1556 } else {
1557 System.out.println(foundAction.getHelpString());
1558 System.out.println();
1561 System.exit(1);
1563 @Override
1564 public void execute() {
1566 @Override
1567 protected List<String> getHelpLines() {
1568 return ImmutableList.of("AppCfg help <command>",
1570 "Prints help about a specific command.",
1571 "");
1575 class DownloadAppAction extends AppCfgAction {
1576 DownloadAppAction() {
1577 super("download_app");
1578 shortDescription = "Download a previously uploaded app version.";
1580 @Override
1581 public void apply() {
1582 if (getArgs().size() != 1) {
1583 throw new IllegalArgumentException("Expected download directory"
1584 + " as an argument after download_app.");
1586 File downloadDir = new File(getArgs().get(0));
1587 if (overrideAppId == null) {
1588 throw new IllegalArgumentException("You must specify an app ID via -A or --application");
1591 if (oauth2) {
1592 authorizeOauth2(connectOptions);
1593 } else {
1594 loadCookies(connectOptions);
1597 AppDownload appDownload =
1598 new AppDownload(ServerConnectionFactory.getServerConnection(connectOptions),
1599 new AppCfgListener("download_app"));
1600 int exitCode = appDownload.download(overrideAppId,
1601 overrideModule,
1602 overrideAppVersion,
1603 downloadDir) ? 0 : 1;
1604 System.exit(exitCode);
1606 @Override
1607 public void execute() {
1609 @Override
1610 protected List<String> getInitialHelpLines() {
1611 return ImmutableList.of(
1612 "AppCfg [options] -A app_id [ -M module ] [ -V version ] download_app <out-dir>",
1614 "Download a previously-uploaded app to the specified directory. The app",
1615 "ID is specified by the \"-A\" option. The optional module is specified by the \"-M\" ",
1616 "option and the optional version is specified by the \"-V\" option.");
1620 class VersionAction extends AppCfgAction {
1621 VersionAction() {
1622 super("version");
1623 shortDescription = "Prints version information.";
1625 @Override
1626 public void apply() {
1627 System.out.println(SupportInfo.getVersionString());
1628 System.exit(0);
1630 @Override
1631 public void execute() {
1633 @Override
1634 protected List<String> getHelpLines() {
1635 return ImmutableList.of(
1636 "AppCfg version",
1638 "Prints version information.");
1642 class SetDefaultVersionAction extends AppCfgAction {
1643 SetDefaultVersionAction() {
1644 super("set_default_version");
1645 shortDescription = "Set the default serving version.";
1647 @Override
1648 public void execute() {
1649 admin.setDefaultVersion();
1651 @Override
1652 protected List<String> getInitialHelpLines() {
1653 return ImmutableList.of(
1654 "AppCfg [options] set_default_version <app-dir>",
1656 "Sets the default (serving) version");
1660 class ResourceLimitsInfoAction extends AppCfgAction {
1661 public ResourceLimitsInfoAction() {
1662 super("resource_limits_info");
1663 shortDescription = "Display resource limits.";
1666 @Override
1667 public void execute() {
1668 ResourceLimits resourceLimits = admin.getResourceLimits();
1669 for (String key : new TreeSet<String>(resourceLimits.keySet())) {
1670 System.out.println(key + ": " + resourceLimits.get(key));
1674 @Override
1675 protected List<String> getInitialHelpLines() {
1676 return ImmutableList.of(
1677 "AppCfg [options] resource_limits_info <app-dir>",
1679 "Displays the resource limits available to the app. An app will",
1680 "not update if any of the app's resources are larger than the",
1681 "appropriate resource limit.");
1685 class BackendsListAction extends AppCfgAction {
1686 BackendsListAction() {
1687 super("backends", "list");
1688 shortDescription = "List the currently configured backends.";
1691 @Override
1692 public void execute() {
1693 outputBackendsMessage();
1694 for (BackendsXml.Entry backend : admin.listBackends()) {
1695 System.out.println(backend.toString());
1699 @Override
1700 protected List<String> getInitialHelpLines() {
1701 return ImmutableList.of(
1702 "AppCfg [options] backends list <app-dir>",
1704 "List the currently configured backends.");
1708 class BackendsRollbackAction extends AppCfgAction {
1709 private String backendName;
1711 BackendsRollbackAction() {
1712 super("backends", "rollback");
1713 shortDescription = "Roll back a previously in-progress update.";
1716 @Override
1717 public void apply() {
1718 super.apply();
1719 if (getArgs().size() < 1 || getArgs().size() > 2) {
1720 throw new IllegalArgumentException("Expected <app-dir> [<backend-name>]");
1721 } else if (getArgs().size() == 2) {
1722 backendName = getArgs().get(1);
1726 @Override
1727 public void execute() {
1728 outputBackendsMessage();
1729 List<String> backends;
1730 if (backendName != null) {
1731 admin.rollbackBackend(backendName);
1732 } else {
1733 admin.rollbackAllBackends();
1737 @Override
1738 protected List<String> getInitialHelpLines() {
1739 return ImmutableList.of(
1740 "AppCfg [options] backends rollback <app-dir> [<backend-name>]",
1742 "The 'backends update' command requires a server-side transaction.",
1743 "Use 'backends rollback' if you experience an error during 'backends update'",
1744 "and want to begin a new update transaction.");
1748 class BackendsUpdateAction extends AppCfgAction {
1749 private String backendName;
1751 BackendsUpdateAction() {
1752 super(builtInOptions("enable_jar_splitting", "jar_splitting_excludes", "retain_upload_dir",
1753 "compile_encoding", "disable_jar_jsps", "delete_jsps", "enable_jar_classes"),
1754 "backends", "update");
1755 shortDescription = "Update the specified backend or all backends.";
1758 @Override
1759 public void apply() {
1760 super.apply();
1761 if (getArgs().size() < 1 || getArgs().size() > 2) {
1762 throw new IllegalArgumentException("Expected <app-dir> [<backend-name>]");
1763 } else if (getArgs().size() == 2) {
1764 backendName = getArgs().get(1);
1768 @Override
1769 public void execute() {
1770 outputBackendsMessage();
1771 List<String> backends;
1772 if (backendName != null) {
1773 admin.updateBackend(backendName, new AppCfgUpdateBackendListener());
1774 } else {
1775 admin.updateAllBackends(new AppCfgUpdateBackendListener());
1779 @Override
1780 protected List<String> getInitialHelpLines() {
1781 return ImmutableList.of(
1782 "AppCfg [options] backends update <app-dir> [<backend-name>]",
1784 "Update the specified backend or all backends.");
1788 class BackendsStartAction extends AppCfgAction {
1789 private String backendName;
1791 BackendsStartAction() {
1792 super("backends", "start");
1793 shortDescription = "Start the specified backend.";
1796 @Override
1797 public void apply() {
1798 super.apply();
1799 if (getArgs().size() != 2) {
1800 throw new IllegalArgumentException("Expected the backend name");
1802 backendName = getArgs().get(1);
1805 @Override
1806 public void execute() {
1807 outputBackendsMessage();
1808 admin.setBackendState(backendName, BackendsXml.State.START);
1811 @Override
1812 protected List<String> getInitialHelpLines() {
1813 return ImmutableList.of(
1814 "AppCfg [options] backends start <app-dir> <backend>",
1816 "Starts the backend with the specified name.");
1820 class BackendsStopAction extends AppCfgAction {
1821 private String backendName;
1823 BackendsStopAction() {
1824 super("backends", "stop");
1825 shortDescription = "Stop the specified backend.";
1828 @Override
1829 public void apply() {
1830 super.apply();
1831 if (getArgs().size() != 2) {
1832 throw new IllegalArgumentException("Expected the backend name");
1834 backendName = getArgs().get(1);
1836 @Override
1837 public void execute() {
1838 outputBackendsMessage();
1839 admin.setBackendState(backendName, BackendsXml.State.STOP);
1842 @Override
1843 protected List<String> getInitialHelpLines() {
1844 return ImmutableList.of(
1845 "AppCfg [options] backends stop <app-dir> <backend>",
1847 "Stops the backend with the specified name.");
1851 class BackendsDeleteAction extends AppCfgAction {
1852 private String backendName;
1854 BackendsDeleteAction() {
1855 super("backends", "delete");
1856 shortDescription = "Delete the specified backend.";
1859 @Override
1860 public void apply() {
1861 super.apply();
1862 if (getArgs().size() != 2) {
1863 throw new IllegalArgumentException("Expected the backend name");
1865 backendName = getArgs().get(1);
1867 @Override
1868 public void execute() {
1869 outputBackendsMessage();
1870 admin.deleteBackend(backendName);
1873 @Override
1874 protected List<String> getInitialHelpLines() {
1875 return ImmutableList.of(
1876 "AppCfg [options] backends delete",
1878 "Deletes the specified backend.");
1882 class BackendsConfigureAction extends AppCfgAction {
1883 private String backendName;
1885 BackendsConfigureAction() {
1886 super("backends", "configure");
1887 shortDescription = "Configure the specified backend.";
1890 @Override
1891 public void apply() {
1892 super.apply();
1893 if (getArgs().size() != 2) {
1894 throw new IllegalArgumentException("Expected the backend name");
1896 backendName = getArgs().get(1);
1898 @Override
1899 public void execute() {
1900 outputBackendsMessage();
1901 admin.configureBackend(backendName);
1904 @Override
1905 protected List<String> getInitialHelpLines() {
1906 return ImmutableList.of(
1907 "AppCfg [options] backends configure <app-dir> <backend>",
1909 "Updates the configuration of the backend with the specified name, without",
1910 "stopping instances that are currently running. Only valid for certain",
1911 "settings (instances, options: failfast, options: public).");
1916 * This is a catchall for the case where the user enters "appcfg.sh
1917 * backends app-dir sub-command" rather than "appcfg.sh backends
1918 * sub-command app-dir". It was added to maintain compatibility
1919 * with Python. It simply remaps the arguments and dispatches the
1920 * appropriate action.
1922 class BackendsAction extends AppCfgAction {
1923 private AppCfgAction subAction;
1925 BackendsAction() {
1926 super("backends");
1929 @Override
1930 public void apply() {
1931 super.apply();
1932 if (getArgs().size() < 2) {
1933 throw new IllegalArgumentException("Expected backends <app-dir> <sub-command> [...]");
1936 String dir = getArgs().get(0);
1937 String subCommand = getArgs().get(1);
1938 subAction = (AppCfgAction) Parser.lookupAction(actionsAndOptions.actions,
1939 new String[] {"backends", subCommand},
1941 if (subAction instanceof BackendsAction) {
1942 throw new IllegalArgumentException("Unknown backends subcommand.");
1944 List<String> newArgs = new ArrayList<String>();
1945 newArgs.add(dir);
1946 newArgs.addAll(getArgs().subList(2, getArgs().size()));
1947 subAction.setArgs(newArgs);
1948 subAction.apply();
1951 @Override
1952 public void execute() {
1953 outputBackendsMessage();
1954 subAction.execute();
1957 @Override
1958 protected List<String> getHelpLines() {
1959 return ImmutableList.of(
1960 "AppCfg [options] backends list: List the currently configured backends.",
1961 "AppCfg [options] backends update: Update the specified backend or all backends.",
1962 "AppCfg [options] backends rollback: Roll back a previously in-progress update.",
1963 "AppCfg [options] backends start: Start the specified backend.",
1964 "AppCfg [options] backends stop: Stop the specified backend.",
1965 "AppCfg [options] backends delete: Delete the specified backend.",
1966 "AppCfg [options] backends configure: Configure the specified backend.");
1970 class StartAction extends AppCfgAction {
1971 StartAction() {
1972 super("start");
1973 shortDescription = "Start the specified module version.";
1976 @Override
1977 public void execute() {
1978 admin.startModule();
1981 @Override
1982 protected List<String> getInitialHelpLines() {
1983 return ImmutableList.of(
1984 "AppCfg [options] start <app-dir>",
1986 "Starts the specified module version.");
1990 class StopAction extends AppCfgAction {
1991 StopAction() {
1992 super("stop");
1993 shortDescription = "Stop the specified module version.";
1996 @Override
1997 public void execute() {
1998 admin.stopModule();
2001 @Override
2002 protected List<String> getInitialHelpLines() {
2003 return ImmutableList.of(
2004 "AppCfg [options] stop <app-dir>",
2006 "Stops the specified module version.");
2010 private static class AppCfgListener implements UpdateListener {
2011 private final String operationName;
2013 AppCfgListener(String opName){
2014 operationName = opName;
2016 @Override
2017 public void onProgress(UpdateProgressEvent event) {
2018 System.out.println(event.getPercentageComplete() + "% " + event.getMessage());
2021 @Override
2022 public void onSuccess(UpdateSuccessEvent event) {
2023 String details = event.getDetails();
2024 if (details.length() > 0) {
2025 System.out.println();
2026 System.out.println("Details:");
2027 System.out.println(details);
2030 System.out.println();
2031 System.out.println(getSuccessSummaryMessage());
2034 @Override
2035 public void onFailure(UpdateFailureEvent event) {
2036 String details = event.getDetails();
2037 if (details.length() > 0) {
2038 System.out.println();
2039 System.out.println("Error Details:");
2040 System.out.println(details);
2043 System.out.println();
2044 String failMsg = event.getFailureMessage();
2045 System.out.println(failMsg);
2046 if (event.getCause() instanceof ClientAuthFailException) {
2047 System.out.println("Consider using the -e EMAIL option if that"
2048 + " email address is incorrect.");
2052 protected String getOperationName() {
2053 return operationName;
2056 protected String getSuccessSummaryMessage() {
2057 return getOperationName() + " completed successfully.";
2061 private class AppCfgUpdateModuleListener extends AppCfgListener {
2062 AppCfgUpdateModuleListener(){
2063 super("Update");
2066 @Override
2067 protected String getSuccessSummaryMessage() {
2068 return getOperationName() + " for module " + moduleName + " completed successfully.";
2072 private static class AppCfgUpdateBackendListener extends AppCfgListener {
2073 AppCfgUpdateBackendListener(){
2074 super("Update");
2078 private static class AppCfgVacuumIndexesListener extends AppCfgListener {
2079 AppCfgVacuumIndexesListener(){
2080 super("vacuum_indexes");
2084 private static class HostPort {
2085 private final String host;
2086 private final String port;
2088 public HostPort(String hostport) {
2089 int colon = hostport.indexOf(':');
2090 host = colon < 0 ? hostport : hostport.substring(0, colon);
2091 port = colon < 0 ? "" : hostport.substring(colon + 1);
2094 public String getHost() {
2095 return host;
2098 public String getPort() {
2099 return port;
2102 public boolean hasPort() {
2103 return port.length() > 0;
2107 private void validateApplicationDirectory(File war) {
2108 if (!war.exists()) {
2109 System.out.println("Unable to find the webapp directory " + war);
2110 printHelp();
2111 System.exit(1);
2112 } else if (!war.isDirectory()) {
2113 System.out.println("appcfg only accepts webapp directories, not war files.");
2114 printHelp();
2115 System.exit(1);