1.9.5
[gae.git] / java / src / main / com / google / appengine / tools / admin / AppCfg.java
blob1c0e2bcfd2061e15c45c9d0fbe6c60d7a3a58448
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.SdkInfo;
13 import com.google.appengine.tools.info.SupportInfo;
14 import com.google.appengine.tools.info.UpdateCheck;
15 import com.google.appengine.tools.info.Version;
16 import com.google.appengine.tools.plugins.ActionsAndOptions;
17 import com.google.appengine.tools.plugins.SDKPluginManager;
18 import com.google.appengine.tools.plugins.SDKRuntimePlugin;
19 import com.google.appengine.tools.plugins.SDKRuntimePlugin.ApplicationDirectories;
20 import com.google.appengine.tools.util.Action;
21 import com.google.appengine.tools.util.ClientCookieManager;
22 import com.google.appengine.tools.util.Logging;
23 import com.google.appengine.tools.util.Option;
24 import com.google.appengine.tools.util.Parser;
25 import com.google.appengine.tools.util.Parser.ParseResult;
26 import com.google.appengine.tools.wargen.WarGenerator;
27 import com.google.appengine.tools.wargen.WarGeneratorFactory;
28 import com.google.apphosting.utils.config.AppEngineConfigException;
29 import com.google.apphosting.utils.config.BackendsXml;
30 import com.google.apphosting.utils.config.EarHelper;
31 import com.google.apphosting.utils.config.EarInfo;
32 import com.google.apphosting.utils.config.WebModule;
33 import com.google.common.base.Joiner;
34 import com.google.common.collect.ImmutableList;
36 import net.sourceforge.yamlbeans.YamlException;
37 import net.sourceforge.yamlbeans.YamlReader;
39 import java.io.BufferedReader;
40 import java.io.ByteArrayInputStream;
41 import java.io.File;
42 import java.io.FileInputStream;
43 import java.io.FileNotFoundException;
44 import java.io.FileWriter;
45 import java.io.IOException;
46 import java.io.InputStreamReader;
47 import java.io.ObjectInputStream;
48 import java.io.PrintWriter;
49 import java.io.Reader;
50 import java.io.StringReader;
51 import java.util.ArrayList;
52 import java.util.Arrays;
53 import java.util.HashMap;
54 import java.util.HashSet;
55 import java.util.Iterator;
56 import java.util.LinkedList;
57 import java.util.List;
58 import java.util.Map;
59 import java.util.Properties;
60 import java.util.Set;
61 import java.util.TreeSet;
62 import java.util.prefs.Preferences;
64 import javax.net.ssl.HostnameVerifier;
65 import javax.net.ssl.HttpsURLConnection;
66 import javax.net.ssl.SSLSession;
68 /**
69 * The command-line SDK tool for administration of App Engine applications.
72 public class AppCfg {
74 private static final String EXTERNAL_RESOURCE_DIR_ARG =
75 DevAppServerMain.EXTERNAL_RESOURCE_DIR_ARG;
76 private static final String GENERATE_WAR_ARG =
77 DevAppServerMain.GENERATE_WAR_ARG;
78 private static final String GENERATED_WAR_DIR_ARG =
79 DevAppServerMain.GENERATED_WAR_DIR_ARG;
80 private static final String OVERRIDE_MODULE_SHORT_ARG = "M";
81 private static final String OVERRIDE_MODULE_LONG_ARG = "module";
83 private final ConnectOptions connectOptions;
84 private String externalResourceDir;
85 private boolean generateWar = false;
86 private String generatedWarDir;
87 private AppCfgAction action;
88 private String applicationDirectory;
89 private String moduleName;
90 private AppAdmin admin;
91 private boolean passin;
92 private boolean doBatch = true;
93 private boolean doJarSplitting = false;
94 private Set<String> jarSplittingExcludeSuffixes = null;
95 private boolean disablePrompt = false;
96 private File logFile = null;
97 private String compileEncoding = null;
98 private LoginReader loginReader = null;
99 private String overrideAppId;
100 private String overrideModule;
101 private String overrideAppVersion;
102 private boolean oauth2 = false;
103 private String oauth2RefreshToken = null;
104 private String oauth2ClientId = null;
105 private String oauth2ClientSecret = null;
106 private boolean useCookies = true;
107 private boolean doJarJSPs = true;
108 private boolean doJarClasses = false;
109 private boolean deleteJSPs = false;
110 private String runtime;
111 private boolean allowAnyRuntime = false;
112 private boolean disableUpdateCheck = false;
113 private boolean failOnPrecompilationError = false;
114 private boolean updateUsageReporting = true;
116 public static void main(String[] args) {
117 Logging.initializeLogging();
118 new AppCfg(args);
121 protected AppCfg(String[] cmdLineArgs) {
122 this(new AppAdminFactory(), cmdLineArgs);
125 public AppCfg(AppAdminFactory factory, String[] cmdLineArgs) {
126 connectOptions = new ConnectOptions();
127 Parser parser = new Parser();
129 PrintWriter logWriter;
131 try {
132 logFile = File.createTempFile("appcfg", ".log");
133 logWriter = new PrintWriter(new FileWriter(logFile), true);
134 } catch (IOException e) {
135 throw new RuntimeException("Unable to enable logging.", e);
138 try {
139 ParseResult result =
140 parser.parseArgs(actionsAndOptions.actions, actionsAndOptions.options, cmdLineArgs);
141 action = (AppCfgAction) result.getAction();
142 validateCommandLineForEar();
143 try {
144 result.applyArgs();
145 } catch (IllegalArgumentException e) {
146 e.printStackTrace(logWriter);
147 System.out.println("Bad argument: " + e.getMessage());
148 System.out.println(action.getHelpString());
149 System.exit(1);
151 if (System.getProperty("http.proxyHost") != null &&
152 System.getProperty("https.proxyHost") == null) {
153 System.setProperty("https.proxyHost",
154 System.getProperty("http.proxyHost"));
155 if (System.getProperty("http.proxyPort") != null &&
156 System.getProperty("https.proxyPort") == null) {
157 System.setProperty("https.proxyPort",
158 System.getProperty("http.proxyPort"));
162 if (applicationDirectory != null) {
163 File appDirectoryFile = new File(applicationDirectory);
164 validateApplicationDirectory(appDirectoryFile);
166 UpdateCheck updateCheck = new UpdateCheck(connectOptions.getServer(), appDirectoryFile,
167 connectOptions.getSecure());
168 if (!disableUpdateCheck) {
169 updateCheck.maybePrintNagScreen(System.out);
171 updateCheck.checkJavaVersion(System.out);
173 if (oauth2) {
174 authorizeOauth2(connectOptions);
175 } else {
176 loadCookies(connectOptions);
179 factory.setBatchMode(doBatch);
181 factory.setJarClassessEnabled(doJarClasses);
182 factory.setJarJSPsEnabled(doJarJSPs);
183 factory.setDeleteJSPs(deleteJSPs);
184 factory.setJarSplittingEnabled(doJarSplitting);
185 if (jarSplittingExcludeSuffixes != null) {
186 factory.setJarSplittingExcludes(jarSplittingExcludeSuffixes);
188 if (compileEncoding != null) {
189 factory.setCompileEncoding(compileEncoding);
191 factory.setRuntime(runtime);
192 factory.setAllowAnyRuntime(allowAnyRuntime);
193 factory.setFailOnPrecompilationError(failOnPrecompilationError);
194 System.out.println("Reading application configuration data...");
196 Iterable<Application> applications = readApplication();
197 executeAction(factory, applications, logWriter, action);
198 System.out.println("Success.");
199 cleanStaging(applications);
201 } catch (IllegalArgumentException e) {
202 e.printStackTrace(logWriter);
203 System.out.println("Bad argument: " + e.getMessage());
204 printHelp();
205 System.exit(1);
206 } catch (AppEngineConfigException e) {
207 e.printStackTrace(logWriter);
208 System.out.println("Bad configuration: " + e.getMessage());
209 if (e.getCause() != null) {
210 System.out.println(" Caused by: " + e.getCause().getMessage());
212 printLogLocation();
213 System.exit(1);
214 } catch (Exception e) {
215 System.out.println("Encountered a problem: " + e.getMessage());
216 e.printStackTrace(logWriter);
217 printLogLocation();
218 System.exit(1);
222 private void validateCommandLineForEar() {
223 if (EarHelper.isEar(applicationDirectory)) {
224 if (!action.isEarAction()) {
225 throw new IllegalArgumentException(
226 "The requested action does not support EAR configurations");
228 if (overrideModule != null) {
229 throw new IllegalArgumentException("With an EAR configuration " + "-"
230 + OVERRIDE_MODULE_SHORT_ARG + "/" + "--" + OVERRIDE_MODULE_LONG_ARG
231 + " is not allowed.");
233 if (externalResourceDir != null) {
234 throw new IllegalArgumentException("With an EAR configuration "
235 + "--" + EXTERNAL_RESOURCE_DIR_ARG + " is not allowed.");
240 private Iterable<Application> readApplication() throws IOException {
241 ImmutableList.Builder<Application> resultBuilder = ImmutableList.builder();
242 if (applicationDirectory != null) {
243 if (EarHelper.isEar(applicationDirectory, false)) {
244 EarInfo earInfo = EarHelper.readEarInfo(applicationDirectory,
245 new File(Application.getSdkDocsDir(), "appengine-application.xsd"));
246 String applicationId = overrideAppId != null ?
247 overrideAppId : earInfo.getAppengineApplicationXml().getApplicationId();
248 for (WebModule webModule : earInfo.getWebModules()) {
249 System.out.println("Processing module " + webModule.getModuleName());
250 resultBuilder.add(readWar(webModule.getApplicationDirectory().getAbsolutePath(),
251 applicationId, null));
252 String contextRootWarning =
253 "Ignoring application.xml context-root element, for details see "
254 + "https://developers.google.com/appengine/docs/java/modules/#config";
255 System.out.println(contextRootWarning);
257 } else {
258 resultBuilder.add(readWar(applicationDirectory,
259 overrideAppId, overrideModule));
262 return resultBuilder.build();
265 private Application readWar(String warDirectory,
266 String applicationIdOrNull, String moduleNameOrNull) throws IOException {
267 Application application = Application.readApplication(warDirectory,
268 applicationIdOrNull,
269 moduleNameOrNull,
270 overrideAppVersion);
271 if (externalResourceDir != null) {
272 application.setExternalResourceDir(externalResourceDir);
274 application.setListener(new UpdateListener() {
275 @Override
276 public void onProgress(UpdateProgressEvent event) {
277 System.out.println(event.getPercentageComplete() + "% " + event.getMessage());
280 @Override
281 public void onSuccess(UpdateSuccessEvent event) {
282 System.out.println("Operation complete.");
285 @Override
286 public void onFailure(UpdateFailureEvent event) {
287 System.out.println(event.getFailureMessage());
290 return application;
293 private void executeAction(AppAdminFactory factory, Iterable<Application> applications,
294 PrintWriter logWriter, AppCfgAction executeMe) {
295 try {
296 if (applications.iterator().hasNext()) {
297 boolean firstModule = true;
298 for (Application application : applications) {
299 factory.setCompileJsps(!application.getAppEngineWebXml().getUseVm());
300 moduleName = WebModule.getModuleName(application.getAppEngineWebXml());
301 try {
302 admin = factory.createAppAdmin(connectOptions, application, logWriter);
303 if (!firstModule) {
304 admin.getUpdateOptions().setUpdateGlobalConfigurations(false);
306 Version localVersion = SdkInfo.getLocalVersion();
307 String sdkVersion = String.format("Java/%s(%s)",
308 localVersion.getRelease(), localVersion.getTimestamp());
309 admin.getUpdateOptions().setSdkVersion(sdkVersion);
310 admin.getUpdateOptions().setUpdateUsageReporting(updateUsageReporting);
311 System.out.printf("%n%nBeginning interaction for module %s...%n", moduleName);
312 executeMe.execute();
313 } finally {
314 moduleName = null;
315 firstModule = false;
318 } else {
319 admin = factory.createAppAdmin(connectOptions, null, logWriter);
320 executeMe.execute();
322 } catch (AdminException ex) {
323 System.out.println(ex.getMessage());
324 ex.printStackTrace(logWriter);
325 printLogLocation();
326 System.exit(1);
327 } finally {
328 admin = null;
332 private void cleanStaging(Iterable<Application> applications) throws IOException {
333 for (Application application : applications) {
334 if (application != null) {
335 String moduleName = WebModule.getModuleName(application.getAppEngineWebXml());
336 if (!connectOptions.getRetainUploadDir()) {
337 System.out.printf("Cleaning up temporary files for module %s...%n", moduleName);
338 application.cleanStagingDirectory();
339 } else {
340 File stage = application.getStagingDir();
341 if (stage == null) {
342 System.out.printf(
343 "Temporary staging directory was not needed, and not created for module %s%n",
344 moduleName);
345 } else {
346 System.out.printf("Temporary staging for module %s directory left in %s%n", moduleName,
347 stage.getCanonicalPath());
355 * Prints a uniform message to direct the user to the given logfile for
356 * more information.
358 private void printLogLocation() {
359 if (logFile != null) {
360 System.out.println("Please see the logs [" + logFile.getAbsolutePath() +
361 "] for further information.");
365 private String loadCookies(final ConnectOptions options) {
366 Preferences prefs = Preferences.userNodeForPackage(ServerConnection.class);
367 String prefsEmail = prefs.get("email", null);
369 if (options.getUsePersistedCredentials() && prefsEmail != null) {
370 ClientCookieManager cookies = null;
371 byte[] serializedCookies = prefs.getByteArray("cookies", null);
372 if (serializedCookies != null) {
373 try {
374 cookies = (ClientCookieManager)
375 new ObjectInputStream(
376 new ByteArrayInputStream(serializedCookies)).readObject();
377 } catch (ClassNotFoundException ex) {
378 } catch (IOException ex) {
382 if (options.getUserId() == null ||
383 prefsEmail.equals(options.getUserId())) {
384 options.setCookies(cookies);
388 options.setPasswordPrompt(new AppAdminFactory.PasswordPrompt() {
389 @Override
390 public String getPassword() {
391 doPrompt();
392 options.setUserId(loginReader.getUsername());
393 return loginReader.getPassword();
396 return prefsEmail;
400 * Tries to get an OAuth2 access token and set it in the ConnectOptions.
401 * It exists with exit code 1 in case no token could be obtained.
403 private void authorizeOauth2(final ConnectOptions options){
404 OAuth2Native client =
405 new OAuth2Native(useCookies, oauth2ClientId, oauth2ClientSecret, oauth2RefreshToken);
406 Credential credential = client.authorize();
407 if (credential != null && credential.getAccessToken() != null) {
408 options.setOauthToken(credential.getAccessToken());
409 } else {
410 System.exit(1);
415 * Helper function for generating a war directory based on an app.yaml file located in an external
416 * resource directory. First the command line arguments are checked to ensure that they are
417 * appropriate for war generation. If there is a problem then a {@link RuntimeException} is
418 * thrown. Otherwise a war directory is generated and its path is returned, and a success
419 * message is written to standard out.
421 * @return The path of the generated war directory.
423 private String validateArgsAndGenerateWar() {
424 if (externalResourceDir == null) {
425 throw new IllegalArgumentException("When generating a war directory --"
426 + EXTERNAL_RESOURCE_DIR_ARG + " must also be specified.");
428 if (EarHelper.isEar(externalResourceDir, false)) {
429 throw new IllegalArgumentException(
430 "With an EAR configuration " + "--" + EXTERNAL_RESOURCE_DIR_ARG + " is not allowed.");
432 File externalResourceDirectory = new File(externalResourceDir);
433 if (!externalResourceDirectory.isDirectory()) {
434 throw new IllegalArgumentException(externalResourceDir + " is not an existing directory.");
436 File appYamlFile = new File(externalResourceDirectory, WarGenerator.APP_YAML);
437 if (!appYamlFile.isFile()) {
438 throw new IllegalArgumentException(appYamlFile.getPath() + " not found.");
440 File destination = (generatedWarDir == null ? null : new File(generatedWarDir));
441 try {
442 WarGenerator warGen =
443 WarGeneratorFactory.newWarGenerator(externalResourceDirectory, destination);
444 String warDir = warGen.generateWarDirectory().getPath();
445 System.out.println("Successfully generated war directory at " + warDir);
446 return warDir;
447 } catch (IOException e) {
448 throw new RuntimeException("Unable to generate a war directory.", e);
452 private void doPrompt() {
454 if (disablePrompt) {
455 System.out.println("Your authentication credentials can't be found and may have expired.\n" +
456 "Please run appcfg directly from the command line to re-establish your credentials.");
457 System.exit(1);
460 getLoginReader().doPrompt();
464 private LoginReader getLoginReader() {
465 if (loginReader == null) {
466 loginReader = LoginReaderFactory.createLoginReader(connectOptions, passin);
468 return loginReader;
471 private static final List<String> generalOptionNamesInHelpOrder =
472 ImmutableList.of(
473 "server",
474 "email",
475 "host",
476 "proxy",
477 "proxy_https",
478 "no_cookies",
479 "sdk_root",
480 "passin",
481 "insecure",
482 "ignore_bad_cert",
483 "application",
484 "module",
485 "version",
486 "oauth2",
487 "use_java7");
489 private static final List<String> optionNamesInHelpOrder =
490 ImmutableList.<String>builder().addAll(generalOptionNamesInHelpOrder).add(
491 "enable_jar_splitting",
492 "jar_splitting_excludes",
493 "disable_jar_jsps",
494 "enable_jar_classes",
495 "delete_jsps",
496 "retain_upload_dir",
497 "compile_encoding",
498 "num_days",
499 "severity",
500 "append",
501 "num_runs",
502 "force",
503 "no_usage_reporting"
504 ).build();
506 private static final List<String> actionNamesInHelpOrder =
507 ImmutableList.of(
508 "help",
509 "download_app",
510 "request_logs",
511 "rollback",
512 "start_module_version",
513 "stop_module_version",
514 "update",
515 "update_indexes",
516 "update_cron",
517 "update_queues",
518 "update_dispatch",
519 "update_dos",
520 "version",
521 "set_default_version",
522 "cron_info",
523 "resource_limits_info",
524 "vacuum_indexes",
525 "backends list",
526 "backends update",
527 "backends rollback",
528 "backends start",
529 "backends stop",
530 "backends delete",
531 "backends configure",
532 "list_versions",
533 "delete_version");
535 private String helpText = null;
536 private void printHelp() {
537 if (helpText == null) {
538 List<String> helpLines = new LinkedList<String>();
539 helpLines.add("usage: AppCfg [options] <action> [<app-dir>] [<argument>]");
540 helpLines.add("");
541 helpLines.add("Action must be one of:");
542 for (String actionName : actionsAndOptions.actionNames) {
543 Action action = actionsAndOptions.getAction(actionName);
544 if (action != null) {
545 helpLines.add(" " + actionName + ": " + action.getShortDescription());
548 helpLines.add("Use 'help <action>' for a detailed description.");
549 helpLines.add("");
550 helpLines.add("options:");
551 for (String optionName : actionsAndOptions.optionNames) {
552 Option option = actionsAndOptions.getOption(optionName);
553 helpLines.addAll(option.getHelpLines());
555 helpText = Joiner.on("\n").join(helpLines);
557 System.out.println(helpText);
558 System.out.println();
561 private final List<Option> builtInOptions = Arrays.asList(
563 new Option("h", "help", true) {
564 @Override
565 public List<String> getHelpLines() {
566 return ImmutableList.<String>of(
567 " -h, --help Show the help message and exit.");
569 @Override
570 public void apply() {
571 printHelp();
572 System.exit(1);
576 new Option("s", "server", false) {
577 @Override
578 public List<String> getHelpLines() {
579 return ImmutableList.<String>of(
580 " -s SERVER, --server=SERVER",
581 " The server to connect to.");
583 @Override
584 public void apply() {
585 connectOptions.setServer(getValue());
589 new Option("e", "email", false) {
590 @Override
591 public List<String> getHelpLines() {
592 return ImmutableList.<String>of(
593 " -e EMAIL, --email=EMAIL",
594 " The username to use. Will prompt if omitted.");
596 @Override
597 public void apply() {
598 connectOptions.setUserId(getValue());
602 new Option("H", "host", false) {
603 @Override
604 public List<String> getHelpLines() {
605 return ImmutableList.<String>of(
606 " -H HOST, --host=HOST Overrides the Host header sent with all RPCs.");
608 @Override
609 public void apply() {
610 connectOptions.setHost(getValue());
614 new Option("p", "proxy", false) {
615 @Override
616 public List<String> getHelpLines() {
617 return ImmutableList.<String>of(
618 " -p PROXYHOST[:PORT], --proxy=PROXYHOST[:PORT]",
619 " Proxies requests through the given proxy server.",
620 " If --proxy_https is also set, only HTTP will be",
621 " proxied here, otherwise both HTTP and HTTPS will.");
623 @Override
624 public void apply() {
625 HostPort hostport = new HostPort(getValue());
627 System.setProperty("http.proxyHost", hostport.getHost());
628 if (hostport.hasPort()) {
629 System.setProperty("http.proxyPort", hostport.getPort());
634 new Option(null, "proxy_https", false) {
635 @Override
636 public List<String> getHelpLines() {
637 return ImmutableList.<String>of(
638 " --proxy_https=PROXYHOST[:PORT]",
639 " Proxies HTTPS requests through the given proxy server.");
641 @Override
642 public void apply() {
643 HostPort hostport = new HostPort(getValue());
645 System.setProperty("https.proxyHost", hostport.getHost());
646 if (hostport.hasPort()) {
647 System.setProperty("https.proxyPort", hostport.getPort());
652 new Option(null, "insecure", true) {
653 @Override
654 public void apply() {
655 connectOptions.setSecure(false);
659 new Option(null, "ignore_bad_cert", true) {
660 @Override
661 public void apply() {
662 HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
663 @Override
664 public boolean verify(String hostname, SSLSession session) {
665 return true;
671 new Option(null, "no_cookies", true) {
672 @Override
673 public List<String> getHelpLines() {
674 return ImmutableList.<String>of(
676 " --no_cookies Do not save/load access credentials to/from disk.");
678 @Override
679 public void apply() {
680 useCookies = false;
681 connectOptions.setUsePersistedCredentials(false);
685 new Option("f", "force", true) {
686 @Override
687 public List<String> getHelpLines() {
688 return ImmutableList.<String>of(
689 " -f, --force Force deletion of indexes without being prompted.");
691 @Override
692 public void apply(){
693 if (action instanceof VacuumIndexesAction){
694 VacuumIndexesAction viAction = (VacuumIndexesAction) action;
695 viAction.promptUserForEachDelete = false;
700 new Option("a", "append", true) {
701 @Override
702 public List<String> getHelpLines() {
703 return ImmutableList.<String>of(
704 " -a, --append Append to existing file.");
706 @Override
707 public void apply() {
708 if (action instanceof RequestLogsAction) {
709 RequestLogsAction logsAction = (RequestLogsAction) action;
710 logsAction.append = true;
715 new Option("n", "num_days", false) {
716 @Override
717 public List<String> getHelpLines() {
718 return ImmutableList.<String>of(
719 " -n NUM_DAYS, --num_days=NUM_DAYS",
720 " Number of days worth of log data to get. The cut-off",
721 " point is midnight UTC. Use 0 to get all available",
722 " logs. Default is 1.");
724 @Override
725 public void apply() {
726 if (action instanceof RequestLogsAction) {
727 RequestLogsAction logsAction = (RequestLogsAction) action;
728 try {
729 logsAction.numDays = Integer.parseInt(getValue());
730 } catch (NumberFormatException e) {
731 throw new IllegalArgumentException("num_days must be an integral number.");
733 } else if (action instanceof CronInfoAction) {
734 CronInfoAction croninfoAction = (CronInfoAction) action;
735 croninfoAction.setNumRuns(getValue());
740 new Option(null, "num_runs", false) {
741 @Override
742 public List<String> getHelpLines() {
743 return ImmutableList.<String>of(
744 " -n NUM_RUNS, --num_runs=NUM_RUNS",
745 " Number of scheduled execution times to compute");
747 @Override
748 public void apply() {
749 if (action instanceof CronInfoAction) {
750 CronInfoAction croninfoAction = (CronInfoAction) action;
751 croninfoAction.setNumRuns(getValue());
756 new Option(null, "severity", false) {
757 @Override
758 public List<String> getHelpLines() {
759 return ImmutableList.<String>of(
760 " --severity=SEVERITY Severity of app-level log messages to get. The range",
761 " is 0 (DEBUG) through 4 (CRITICAL). If omitted, only",
762 " request logs are returned.");
764 @Override
765 public void apply() {
766 RequestLogsAction logsAction = (RequestLogsAction) action;
767 try {
768 int severity = Integer.parseInt(getValue());
769 int maxSeverity = LogSeverity.CRITICAL.ordinal();
770 if (severity < 0 || severity > maxSeverity) {
771 throw new IllegalArgumentException("severity must be between 0 and " + maxSeverity);
773 logsAction.severity = severity;
774 } catch (NumberFormatException e) {
775 for (Enum<LogSeverity> severity : LogSeverity.values()) {
776 if (getValue().equalsIgnoreCase(severity.toString())) {
777 logsAction.severity = severity.ordinal();
778 return;
781 throw new IllegalArgumentException("severity must be an integral "
782 + "number 0-4, or one of DEBUG, INFO, WARN, ERROR, CRITICAL");
787 new Option(null, "sdk_root", false) {
788 @Override
789 public List<String> getHelpLines() {
790 return ImmutableList.<String>of(
791 " --sdk_root=root Overrides where the SDK is located.");
793 @Override
794 public void apply() {
795 connectOptions.setSdkRoot(getValue());
799 new Option(null, "disable_jar_jsps", true) {
800 @Override
801 public List<String> getHelpLines() {
802 return ImmutableList.<String>of(
803 " --disable_jar_jsps",
804 " Do not jar the classes generated from JSPs.");
807 @Override
808 public void apply() {
809 doJarJSPs = false;
813 new Option(null, "enable_jar_classes", true) {
814 @Override
815 public List<String> getHelpLines() {
816 return ImmutableList.<String>of(
817 " --enable_jar_classes",
818 " Jar the WEB-INF/classes content.");
821 @Override
822 public void apply() {
823 doJarClasses = true;
826 new Option(null, "delete_jsps", true) {
827 @Override
828 public List<String> getHelpLines() {
829 return ImmutableList.<String>of(
830 " --delete_jsps",
831 " Delete the JSP source files after compilation.");
834 @Override
835 public void apply() {
836 deleteJSPs = true;
840 new Option(null, "enable_jar_splitting", true) {
841 @Override
842 public List<String> getHelpLines() {
843 return ImmutableList.<String>of(
844 " --enable_jar_splitting",
845 " Split large jar files (> 10M) into smaller fragments.");
847 @Override
848 public void apply() {
849 doJarSplitting = true;
853 new Option(null, "jar_splitting_excludes", false) {
854 @Override
855 public List<String> getHelpLines() {
856 return ImmutableList.<String>of(
857 " --jar_splitting_excludes=SUFFIXES",
858 " When --enable-jar-splitting is set, files that match",
859 " the list of comma separated SUFFIXES will be excluded",
860 " from all jars.");
862 @Override
863 public void apply() {
864 jarSplittingExcludeSuffixes = new HashSet<String>(Arrays.asList(getValue().split(",")));
868 new Option(null, "retain_upload_dir", true) {
869 @Override
870 public List<String> getHelpLines() {
871 return ImmutableList.<String>of(
872 " --retain_upload_dir",
873 " Do not delete temporary (staging) directory used in",
874 " uploading.");
876 @Override
877 public void apply() {
878 connectOptions.setRetainUploadDir(true);
882 new Option(null, "passin", true) {
883 @Override
884 public List<String> getHelpLines() {
885 return ImmutableList.<String>of(
886 " --passin Always read the login password from stdin.");
888 @Override
889 public void apply() {
890 passin = true;
894 new Option(null, "no_batch", true) {
895 @Override
896 public void apply() {
897 doBatch = false;
900 new Option(null, "compile_encoding", false) {
901 @Override
902 public List<String> getHelpLines() {
903 return ImmutableList.<String>of(
904 " --compile_encoding",
905 " The character encoding to use when compiling JSPs.");
907 @Override
908 public void apply() {
909 compileEncoding = getValue();
913 new Option(null, "disable_prompt", true) {
914 @Override
915 public void apply() {
916 disablePrompt = true;
920 new Option(null, "disable_update_check", true) {
921 @Override
922 public void apply() {
923 disableUpdateCheck = true;
927 new Option("A", "application", false) {
928 @Override
929 public List<String> getHelpLines() {
930 return ImmutableList.<String>of(
931 " -A APP_ID, --application=APP_ID",
932 " Override application id from appengine-web.xml or app.yaml");
934 @Override
935 public void apply() {
936 overrideAppId = getValue();
940 new Option(OVERRIDE_MODULE_SHORT_ARG, OVERRIDE_MODULE_LONG_ARG, false) {
941 @Override
942 public List<String> getHelpLines() {
943 return ImmutableList.<String>of(
944 " -" + OVERRIDE_MODULE_SHORT_ARG + " MODULE, --" + OVERRIDE_MODULE_LONG_ARG
945 + "=MODULE",
946 " Override module from appengine-web.xml or app.yaml");
948 @Override
949 public void apply() {
950 overrideModule = getValue();
954 new Option("V", "version" , false) {
955 @Override
956 public List<String> getHelpLines() {
957 return ImmutableList.<String>of(
958 " -V VERSION, --version=VERSION",
959 " Override (major) version from appengine-web.xml " +
960 "or app.yaml");
962 @Override
963 public void apply() {
964 overrideAppVersion = getValue();
968 new Option(null, "oauth2", true) {
969 @Override
970 public List<String> getHelpLines() {
971 return ImmutableList.<String>of(
972 " --oauth2 Use OAuth2 instead of password auth.");
974 @Override
975 public void apply() {
976 oauth2 = true;
980 new Option(null, "oauth2_refresh_token", false) {
981 @Override
982 public void apply() {
983 oauth2RefreshToken = getValue();
984 useCookies = false;
988 new Option(null, "oauth2_client_id", false) {
989 @Override
990 public void apply() {
991 oauth2ClientId = getValue();
992 useCookies = false;
996 new Option(null, "oauth2_client_secret", false) {
997 @Override
998 public void apply() {
999 oauth2ClientSecret = getValue();
1000 useCookies = false;
1004 new Option(null, "oauth2_config_file", false) {
1005 @Override
1006 public void apply() {
1007 final Properties props = new Properties();
1008 try {
1009 props.load(new FileInputStream(getValue()));
1010 } catch (FileNotFoundException e) {
1011 throw new RuntimeException(
1012 String.format("OAuth2 configuration file does not exist: %s", getValue()), e);
1013 } catch (IOException e) {
1014 throw new RuntimeException(
1015 String.format("Could not read OAuth2 configuration file: %s", getValue()), e);
1018 oauth2RefreshToken = props.getProperty("oauth2_refresh_token");
1019 oauth2ClientId = props.getProperty("oauth2_client_id");
1020 oauth2ClientSecret = props.getProperty("oauth2_client_secret");
1022 if (oauth2RefreshToken != null ||
1023 oauth2ClientId != null ||
1024 oauth2ClientSecret != null) {
1025 useCookies = false;
1030 new Option(null, "no_usage_reporting", true) {
1031 @Override
1032 public List<String> getHelpLines() {
1033 return ImmutableList.<String>of(
1034 " --no_usage_reporting",
1035 " Disable usage reporting.");
1038 @Override
1039 public void apply() {
1040 updateUsageReporting = false;
1044 new Option(null, "use_java7", true) {
1045 @Override
1046 public void apply() {
1050 new Option("r", "runtime", false) {
1051 @Override
1052 public void apply() {
1053 runtime = getValue();
1057 new Option("R", "allow_any_runtime", true) {
1058 @Override
1059 public void apply() {
1060 allowAnyRuntime = true;
1064 new Option(null, EXTERNAL_RESOURCE_DIR_ARG, false) {
1065 @Override
1066 public void apply() {
1067 externalResourceDir = getValue();
1071 new Option(null, GENERATE_WAR_ARG, true) {
1072 @Override
1073 public void apply() {
1074 generateWar = true;
1078 new Option(null, GENERATED_WAR_DIR_ARG, false) {
1079 @Override
1080 public void apply() {
1081 generateWar = true;
1082 generatedWarDir = getValue();
1086 new Option(null, "fail_on_precompilation_error", true) {
1087 @Override
1088 public void apply() {
1089 failOnPrecompilationError = true;
1093 private final List<Action> builtInActions = Arrays.<Action>asList(
1094 new UpdateAction(),
1095 new RequestLogsAction(),
1096 new RollbackAction(),
1097 new UpdateIndexesAction(),
1098 new UpdateCronAction(),
1099 new UpdateDispatchAction(),
1100 new UpdateDosAction(),
1101 new UpdateQueueAction(),
1102 new CronInfoAction(),
1103 new VacuumIndexesAction(),
1104 new HelpAction(),
1105 new DownloadAppAction(),
1106 new VersionAction(),
1107 new SetDefaultVersionAction(),
1108 new ResourceLimitsInfoAction(),
1109 new StartModuleVersionAction(),
1110 new StopModuleVersionAction(),
1111 new BackendsListAction(),
1112 new BackendsRollbackAction(),
1113 new BackendsUpdateAction(),
1114 new BackendsStartAction(),
1115 new BackendsStopAction(),
1116 new BackendsDeleteAction(),
1117 new BackendsConfigureAction(),
1118 new BackendsAction(),
1119 new ListVersionsAction(),
1120 new DeleteVersionAction(),
1121 new DebugAction()
1124 private Map<String, Option> builtInOptionMap;
1126 private List<Option> builtInOptions(String... optionNames) {
1127 if (builtInOptionMap == null) {
1128 builtInOptionMap = new HashMap<String, Option>(builtInOptions.size());
1129 for (Option option : builtInOptions){
1130 builtInOptionMap.put(option.getLongName(), option);
1133 List<Option> options = new LinkedList<Option>();
1134 for (String name : optionNames) {
1135 Option option = builtInOptionMap.get(name);
1136 if (option != null) {
1137 options.add(option);
1140 return options;
1143 private final ActionsAndOptions actionsAndOptions = buildActionsAndOptions();
1145 private ActionsAndOptions buildActionsAndOptions() {
1146 ActionsAndOptions actionsAndOptions = getBuiltInActionsAndOptions();
1147 for (SDKRuntimePlugin runtimePlugin : SDKPluginManager.findAllRuntimePlugins()) {
1148 runtimePlugin.customizeAppCfgActionsAndOptions(actionsAndOptions);
1150 return actionsAndOptions;
1154 * Builds the collection of built-in Actions and Options.
1156 private ActionsAndOptions getBuiltInActionsAndOptions() {
1157 ActionsAndOptions actionsAndOptions = new ActionsAndOptions();
1158 actionsAndOptions.actions = builtInActions;
1159 actionsAndOptions.actionNames = actionNamesInHelpOrder;
1160 actionsAndOptions.options = builtInOptions;
1161 actionsAndOptions.optionNames = optionNamesInHelpOrder;
1162 actionsAndOptions.generalOptionNames = generalOptionNamesInHelpOrder;
1163 return actionsAndOptions;
1166 abstract class AppCfgAction extends Action {
1168 AppCfgAction(String... names) {
1169 this(null, names);
1172 AppCfgAction(List<Option> options, String... names) {
1173 super(options, names);
1176 @Override
1177 protected void setArgs(List<String> args) {
1178 super.setArgs(args);
1181 @Override
1182 public void apply() {
1183 if (generateWar) {
1184 applicationDirectory = validateArgsAndGenerateWar();
1185 List<String> args = getArgs();
1186 List<String> newArgs = new ArrayList<String>(args.size() + 1);
1187 newArgs.add(applicationDirectory);
1188 newArgs.addAll(args);
1189 setArgs(newArgs);
1190 } else {
1191 if (getArgs().size() < 1) {
1192 throw new IllegalArgumentException("Expected the application directory"
1193 + " as an argument after the action name.");
1195 applicationDirectory = getArgs().get(0);
1196 validateCommandLineForEar();
1198 SDKRuntimePlugin runtimePlugin = SDKPluginManager.findRuntimePlugin(
1199 new File(applicationDirectory));
1200 if (runtimePlugin != null) {
1201 try {
1202 ApplicationDirectories appDirs = runtimePlugin.generateApplicationDirectories(
1203 new File(applicationDirectory));
1204 applicationDirectory = appDirs.getWarDir().getPath();
1205 getArgs().set(0, applicationDirectory);
1206 externalResourceDir = appDirs.getExternalResourceDir().getPath();
1207 } catch (IOException e) {
1208 throw new RuntimeException("Unable to generate the war directory", e);
1214 public abstract void execute();
1216 @Override
1217 protected List<String> getHelpLines() {
1218 List<String> helpLines = new LinkedList<String>();
1219 helpLines.addAll(getInitialHelpLines());
1220 helpLines.add("");
1221 helpLines.add("Options:");
1222 for (String optionName : actionsAndOptions.generalOptionNames) {
1223 Option option = actionsAndOptions.getOption(optionName);
1224 if (option != null) {
1225 helpLines.addAll(option.getHelpLines());
1228 if (extraOptions != null) {
1229 for (Option option : extraOptions) {
1230 helpLines.addAll(option.getHelpLines());
1233 return helpLines;
1237 * Returns a list of Strings to be displayed as the initial lines of a help text. Subclasses
1238 * should override this method.
1239 * <p>
1240 * The text returned by this method should describe the base Action without any of its options.
1241 * Text describing the options will be added in lines below this text.
1243 protected List<String> getInitialHelpLines() {
1244 return ImmutableList.of();
1247 protected boolean isEarAction() {
1248 return false;
1251 protected void outputBackendsMessage() {
1252 System.out.println("Warning: This application uses Backends, a deprecated feature that " +
1253 "has been replaced by Modules, which offers additional functionality. Please " +
1254 "convert your backends to modules as described at: https://developers.google.com/" +
1255 "appengine/docs/java/modules/converting.");
1259 class UpdateAction extends AppCfgAction {
1260 UpdateAction() {
1261 super(builtInOptions("enable_jar_splitting", "jar_splitting_excludes", "retain_upload_dir",
1262 "compile_encoding", "disable_jar_jsps", "delete_jsps", "enable_jar_classes"), "update");
1263 shortDescription = "Create or update an app version.";
1266 @Override
1267 public void execute() {
1268 admin.update(new AppCfgUpdateModuleListener());
1271 @Override
1272 protected List<String> getInitialHelpLines() {
1273 return ImmutableList.of(
1274 "AppCfg [options] update <app-dir>",
1276 "Installs a new version of the application onto the server, as the",
1277 "default version for end users.");
1280 @Override
1281 protected boolean isEarAction() {
1282 return true;
1286 class RequestLogsAction extends AppCfgAction {
1287 String outputFile;
1288 int numDays = 1;
1289 int severity = -1;
1290 boolean append = false;
1292 RequestLogsAction() {
1293 super(builtInOptions("num_days", "severity", "append"), "request_logs");
1294 shortDescription = "Write request logs in Apache common log format.";
1296 @Override
1297 public void apply() {
1298 super.apply();
1299 if (getArgs().size() != 2) {
1300 throw new IllegalArgumentException("Expected the application directory"
1301 + " and log file as arguments after the request_logs action name.");
1303 outputFile = getArgs().get(1);
1305 @Override
1306 public void execute() {
1307 Reader reader = admin.requestLogs(numDays,
1308 severity >= 0 ? LogSeverity.values()[severity] : null);
1309 if (reader == null) {
1310 return;
1313 BufferedReader r = new BufferedReader(reader);
1314 PrintWriter writer = null;
1315 try {
1316 if (outputFile.equals("-")) {
1317 writer = new PrintWriter(System.out);
1318 } else {
1319 writer = new PrintWriter(new FileWriter(outputFile, append));
1321 String line = null;
1322 while ((line = r.readLine()) != null) {
1323 writer.println(line);
1325 } catch (IOException e) {
1326 throw new RuntimeException("Failed to read logs: " + e);
1327 } finally {
1328 if (writer != null) {
1329 writer.close();
1331 try {
1332 r.close();
1333 } catch (IOException e) {
1337 @Override
1338 protected List<String> getInitialHelpLines() {
1339 return ImmutableList.of(
1340 "AppCfg [options] request_logs <app-dir> <output-file>",
1342 "Populates the output-file with recent logs from the application.");
1346 class RollbackAction extends AppCfgAction {
1347 RollbackAction() {
1348 super("rollback");
1349 shortDescription = "Rollback an in-progress update.";
1351 @Override
1352 public void execute() {
1353 admin.rollback();
1355 @Override
1356 protected List<String> getInitialHelpLines() {
1357 return ImmutableList.of(
1358 "AppCfg [options] rollback <app-dir>",
1360 "The 'update' command requires a server-side transaction.",
1361 "Use 'rollback' if you experience an error during 'update'",
1362 "and want to begin a new update transaction.");
1366 class UpdateIndexesAction extends AppCfgAction {
1367 UpdateIndexesAction() {
1368 super("update_indexes");
1369 shortDescription = "Update application indexes.";
1371 @Override
1372 public void execute() {
1373 admin.updateIndexes();
1375 @Override
1376 protected List<String> getInitialHelpLines() {
1377 return ImmutableList.of(
1378 "AppCfg [options] update_indexes <app-dir>",
1380 "Updates the datastore indexes for the server to add any in the current",
1381 "application directory. Does not alter the running application version, nor",
1382 "remove any existing indexes.");
1386 class UpdateCronAction extends AppCfgAction {
1387 UpdateCronAction() {
1388 super("update_cron");
1389 shortDescription = "Update application cron jobs.";
1391 @Override
1392 public void execute() {
1393 admin.updateCron();
1394 shortDescription = "Update application cron jobs.";
1396 @Override
1397 protected List<String> getInitialHelpLines() {
1398 return ImmutableList.of(
1399 "AppCfg [options] update_cron <app-dir>",
1401 "Updates the cron jobs for the application. Updates any new, removed or changed",
1402 "cron jobs. Does not otherwise alter the running application version.");
1406 class UpdateDispatchAction extends AppCfgAction {
1407 UpdateDispatchAction() {
1408 super("update_dispatch");
1409 shortDescription = "Update the application dispatch configuration.";
1411 @Override
1412 public void execute() {
1413 admin.updateDispatch();
1415 @Override
1416 protected List<String> getInitialHelpLines() {
1417 return ImmutableList.of(
1418 "AppCfg [options] update_dispatch <app-dir>",
1420 "Updates the application dispatch configuration.",
1421 "Does not otherwise alter the running application version.");
1425 class UpdateDosAction extends AppCfgAction {
1426 UpdateDosAction() {
1427 super("update_dos");
1428 shortDescription = "Update application DoS protection configuration.";
1430 @Override
1431 public void execute() {
1432 admin.updateDos();
1434 @Override
1435 protected List<String> getInitialHelpLines() {
1436 return ImmutableList.of(
1437 "AppCfg [options] update_dos <app-dir>",
1439 "Updates the DoS protection configuration for the application.",
1440 "Does not otherwise alter the running application version.");
1444 class UpdateQueueAction extends AppCfgAction {
1445 UpdateQueueAction() {
1446 super("update_queues");
1447 shortDescription = "Update application task queue definitions.";
1449 @Override
1450 public void execute() {
1451 admin.updateQueues();
1453 @Override
1454 protected List<String> getInitialHelpLines() {
1455 return ImmutableList.of(
1456 "AppCfg [options] " + getNameString() + " <app-dir>",
1458 "Updates any new, removed or changed task queue definitions.",
1459 "Does not otherwise alter the running application version.");
1463 class CronInfoAction extends AppCfgAction {
1464 int numRuns = 5;
1466 CronInfoAction() {
1467 super(builtInOptions("num_runs"), "cron_info");
1468 shortDescription = "Displays times for the next several runs of each cron job.";
1470 @Override
1471 public void execute() {
1472 List<CronEntry> entries = admin.cronInfo();
1473 if (entries.isEmpty()) {
1474 System.out.println("No cron jobs defined.");
1475 } else {
1476 System.out.println(entries.size() + " cron entries defined.\n");
1477 for (CronEntry entry : entries) {
1478 System.out.println(entry.toXml());
1479 System.out.println("Next " + numRuns + " execution times:");
1480 Iterator<String> iter = entry.getNextTimesIterator();
1481 for (int i = 0; i < numRuns; i++) {
1482 System.out.println(" " + iter.next());
1484 System.out.println("");
1488 @Override
1489 protected List<String> getInitialHelpLines() {
1490 return ImmutableList.of(
1491 "AppCfg [options] cron_info <app-dir>",
1493 "Displays times for the next several runs of each cron job.");
1495 public void setNumRuns(String numberString) {
1496 try {
1497 numRuns = Integer.parseInt(numberString);
1498 } catch (NumberFormatException e) {
1499 throw new IllegalArgumentException("num_runs must be an integral number.");
1501 if (numRuns < 0) {
1502 throw new IllegalArgumentException("num_runs must be positive.");
1507 class VacuumIndexesAction extends AppCfgAction {
1508 public boolean promptUserForEachDelete = true;
1510 VacuumIndexesAction() {
1511 super(builtInOptions("force"), "vacuum_indexes");
1512 shortDescription = "Delete unused indexes from application.";
1515 @Override
1516 public void execute() {
1517 ConfirmationCallback<IndexDeleter.DeleteIndexAction> callback = null;
1518 if (promptUserForEachDelete) {
1519 callback = new ConfirmationCallback<IndexDeleter.DeleteIndexAction>() {
1520 @Override
1521 public Response confirmAction(DeleteIndexAction action) {
1522 while (true) {
1523 String prompt = "\n" + action.getPrompt() + " (N/y/a): ";
1524 System.out.print(prompt);
1525 System.out.flush();
1526 BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
1527 String response;
1528 try {
1529 response = in.readLine();
1530 } catch (IOException ioe) {
1531 response = null;
1533 response = (null == response ? "" : response.trim().toLowerCase());
1534 if ("y".equals(response)) {
1535 return Response.YES;
1537 if ("n".equals(response) || response.isEmpty()) {
1538 return Response.NO;
1540 if ("a".equals(response)) {
1541 return Response.YES_ALL;
1547 admin.vacuumIndexes(callback, new AppCfgVacuumIndexesListener());
1550 @Override
1551 protected List<String> getInitialHelpLines() {
1552 return ImmutableList.of(
1553 "AppCfg [options] vacuum_indexes <app-dir>",
1555 "Deletes indexes on the server that are not present in the local",
1556 "index configuration file. The user is prompted before each delete.");
1560 class HelpAction extends AppCfgAction {
1561 HelpAction() {
1562 super("help");
1563 shortDescription = "Print help for a specific action.";
1565 @Override
1566 public void apply() {
1567 if (getArgs().isEmpty()) {
1568 printHelp();
1569 } else {
1570 Action foundAction = Parser.lookupAction(actionsAndOptions.actions,
1571 getArgs().toArray(new String[0]), 0);
1572 if (foundAction == null) {
1573 System.out.println("No such command \"" + getArgs().get(0) + "\"\n\n");
1574 printHelp();
1575 } else {
1576 System.out.println(foundAction.getHelpString());
1577 System.out.println();
1580 System.exit(1);
1582 @Override
1583 public void execute() {
1585 @Override
1586 protected List<String> getHelpLines() {
1587 return ImmutableList.of("AppCfg help <command>",
1589 "Prints help about a specific command.",
1590 "");
1594 class DownloadAppAction extends AppCfgAction {
1595 DownloadAppAction() {
1596 super("download_app");
1597 shortDescription = "Download a previously uploaded app version.";
1599 @Override
1600 public void apply() {
1601 if (getArgs().size() != 1) {
1602 throw new IllegalArgumentException("Expected download directory"
1603 + " as an argument after download_app.");
1605 File downloadDir = new File(getArgs().get(0));
1606 if (overrideAppId == null) {
1607 throw new IllegalArgumentException("You must specify an app ID via -A or --application");
1610 if (oauth2) {
1611 authorizeOauth2(connectOptions);
1612 } else {
1613 loadCookies(connectOptions);
1616 AppDownload appDownload =
1617 new AppDownload(ServerConnectionFactory.getServerConnection(connectOptions),
1618 new AppCfgListener("download_app"));
1619 int exitCode = appDownload.download(overrideAppId,
1620 overrideModule,
1621 overrideAppVersion,
1622 downloadDir) ? 0 : 1;
1623 System.exit(exitCode);
1625 @Override
1626 public void execute() {
1628 @Override
1629 protected List<String> getInitialHelpLines() {
1630 return ImmutableList.of(
1631 "AppCfg [options] -A app_id [ -M module ] [ -V version ] download_app <out-dir>",
1633 "Download a previously-uploaded app to the specified directory. The app",
1634 "ID is specified by the \"-A\" option. The optional module is specified by the \"-M\" ",
1635 "option and the optional version is specified by the \"-V\" option.");
1639 class VersionAction extends AppCfgAction {
1640 VersionAction() {
1641 super("version");
1642 shortDescription = "Prints version information.";
1644 @Override
1645 public void apply() {
1646 System.out.println(SupportInfo.getVersionString());
1647 System.exit(0);
1649 @Override
1650 public void execute() {
1652 @Override
1653 protected List<String> getHelpLines() {
1654 return ImmutableList.of(
1655 "AppCfg version",
1657 "Prints version information.");
1661 class SetDefaultVersionAction extends AppCfgAction {
1662 SetDefaultVersionAction() {
1663 super("set_default_version");
1664 shortDescription = "Set the default serving version.";
1666 @Override
1667 public void execute() {
1668 admin.setDefaultVersion();
1670 @Override
1671 protected List<String> getInitialHelpLines() {
1672 return ImmutableList.of(
1673 "AppCfg [options] set_default_version <app-dir>",
1675 "Sets the default (serving) version of the app. Defaults to using",
1676 "the application, version and module specified in your app directory.",
1677 "Use the --application, --version and --module flags to override these",
1678 "values. The --module flag can also be a comma-delimited string of",
1679 "several modules. (ex. module1,module2,module3) In this case, the default",
1680 "version of each module will be changed to the version specified.");
1684 class ResourceLimitsInfoAction extends AppCfgAction {
1685 public ResourceLimitsInfoAction() {
1686 super("resource_limits_info");
1687 shortDescription = "Display resource limits.";
1690 @Override
1691 public void execute() {
1692 ResourceLimits resourceLimits = admin.getResourceLimits();
1693 for (String key : new TreeSet<String>(resourceLimits.keySet())) {
1694 System.out.println(key + ": " + resourceLimits.get(key));
1698 @Override
1699 protected List<String> getInitialHelpLines() {
1700 return ImmutableList.of(
1701 "AppCfg [options] resource_limits_info <app-dir>",
1703 "Displays the resource limits available to the app. An app will",
1704 "not update if any of the app's resources are larger than the",
1705 "appropriate resource limit.");
1709 class ListVersionsAction extends AppCfgAction {
1710 ListVersionsAction() {
1711 super("list_versions");
1712 shortDescription = "List the currently uploaded versions.";
1715 @Override
1716 public void execute() {
1717 String response = admin.listVersions();
1718 YamlReader yaml = new YamlReader(new StringReader(response));
1719 try {
1720 Object obj = yaml.read();
1721 if (obj != null) {
1722 @SuppressWarnings("unchecked")
1723 Map<String, ArrayList<String>> responseMap = (Map<String, ArrayList<String>>) obj;
1724 if (!responseMap.isEmpty()) {
1725 System.out.println(response);
1726 } else {
1727 System.out.println("No versions uploaded for application.");
1729 return;
1731 } catch (YamlException exc) {
1732 } catch (ClassCastException exc) {
1734 System.out.println("There was a problem retrieving the list of versions.");
1737 @Override
1738 protected List<String> getInitialHelpLines() {
1739 return ImmutableList.of(
1740 "AppCfg [options] list_versions <app-dir>",
1742 "List the currently configured versions.");
1746 class DeleteVersionAction extends AppCfgAction {
1747 DeleteVersionAction() {
1748 super("delete_version");
1749 shortDescription = "Delete the specified version.";
1752 @Override
1753 public void execute() {
1754 if (overrideAppVersion == null) {
1755 throw new IllegalArgumentException("You must specify a version ID via -V or --version");
1758 String response = admin.deleteVersion(overrideAppId,
1759 overrideModule,
1760 overrideAppVersion);
1761 System.out.println(response);
1764 @Override
1765 protected List<String> getInitialHelpLines() {
1766 return ImmutableList.of(
1767 "AppCfg [options] delete_version <app-dir> -V version [-M module]",
1769 "Deletes the specified version.");
1773 class DebugAction extends AppCfgAction {
1774 DebugAction() {
1775 super("debug");
1776 shortDescription = "Debug a vm runtime application.";
1779 @Override
1780 public void execute() {
1781 String debugResponse = admin.debugVersion();
1782 System.out.println(debugResponse);
1783 boolean done = false;
1784 int retries = 0;
1785 int nextSleep = 1000;
1786 int maxSleep = 6000;
1787 try {
1788 while (!done && retries < 20) {
1789 Map<Object, Object> yaml = (Map<Object, Object>)
1790 new YamlReader(admin.debugVersionState()).read();
1791 String message = (String) yaml.get("message");
1792 System.out.println(message);
1793 String state = (String) yaml.get("state");
1794 done = !state.equals("PENDING");
1795 if (!done) {
1796 try {
1797 Thread.sleep(nextSleep);
1798 } catch (InterruptedException ex) {
1800 retries++;
1801 nextSleep = nextSleep * 2;
1802 if (nextSleep > maxSleep) {
1803 nextSleep = maxSleep;
1807 } catch (YamlException ex) {
1808 System.out.println("Error waiting for debug request status: " + ex.toString());
1812 @Override
1813 protected List<String> getInitialHelpLines() {
1814 return ImmutableList.of(
1815 "AppCfg [options] -A app_id -V version [-M module] debug <app_dir>",
1817 "Configures a vm runtime version to be accessible for debugging.");
1822 class BackendsListAction extends AppCfgAction {
1823 BackendsListAction() {
1824 super("backends", "list");
1825 shortDescription = "List the currently configured backends.";
1828 @Override
1829 public void execute() {
1830 outputBackendsMessage();
1831 for (BackendsXml.Entry backend : admin.listBackends()) {
1832 System.out.println(backend);
1836 @Override
1837 protected List<String> getInitialHelpLines() {
1838 return ImmutableList.of(
1839 "AppCfg [options] backends list <app-dir>",
1841 "List the currently configured backends.");
1845 class BackendsRollbackAction extends AppCfgAction {
1846 private String backendName;
1848 BackendsRollbackAction() {
1849 super("backends", "rollback");
1850 shortDescription = "Roll back a previously in-progress update.";
1853 @Override
1854 public void apply() {
1855 super.apply();
1856 if (getArgs().size() < 1 || getArgs().size() > 2) {
1857 throw new IllegalArgumentException("Expected <app-dir> [<backend-name>]");
1858 } else if (getArgs().size() == 2) {
1859 backendName = getArgs().get(1);
1863 @Override
1864 public void execute() {
1865 outputBackendsMessage();
1866 List<String> backends;
1867 if (backendName != null) {
1868 admin.rollbackBackend(backendName);
1869 } else {
1870 admin.rollbackAllBackends();
1874 @Override
1875 protected List<String> getInitialHelpLines() {
1876 return ImmutableList.of(
1877 "AppCfg [options] backends rollback <app-dir> [<backend-name>]",
1879 "The 'backends update' command requires a server-side transaction.",
1880 "Use 'backends rollback' if you experience an error during 'backends update'",
1881 "and want to begin a new update transaction.");
1885 class BackendsUpdateAction extends AppCfgAction {
1886 private String backendName;
1888 BackendsUpdateAction() {
1889 super(builtInOptions("enable_jar_splitting", "jar_splitting_excludes", "retain_upload_dir",
1890 "compile_encoding", "disable_jar_jsps", "delete_jsps", "enable_jar_classes"),
1891 "backends", "update");
1892 shortDescription = "Update the specified backend or all backends.";
1895 @Override
1896 public void apply() {
1897 super.apply();
1898 if (getArgs().size() < 1 || getArgs().size() > 2) {
1899 throw new IllegalArgumentException("Expected <app-dir> [<backend-name>]");
1900 } else if (getArgs().size() == 2) {
1901 backendName = getArgs().get(1);
1905 @Override
1906 public void execute() {
1907 outputBackendsMessage();
1908 List<String> backends;
1909 if (backendName != null) {
1910 admin.updateBackend(backendName, new AppCfgUpdateBackendListener());
1911 } else {
1912 admin.updateAllBackends(new AppCfgUpdateBackendListener());
1916 @Override
1917 protected List<String> getInitialHelpLines() {
1918 return ImmutableList.of(
1919 "AppCfg [options] backends update <app-dir> [<backend-name>]",
1921 "Update the specified backend or all backends.");
1925 class BackendsStartAction extends AppCfgAction {
1926 private String backendName;
1928 BackendsStartAction() {
1929 super("backends", "start");
1930 shortDescription = "Start the specified backend.";
1933 @Override
1934 public void apply() {
1935 super.apply();
1936 if (getArgs().size() != 2) {
1937 throw new IllegalArgumentException("Expected the backend name");
1939 backendName = getArgs().get(1);
1942 @Override
1943 public void execute() {
1944 outputBackendsMessage();
1945 admin.setBackendState(backendName, BackendsXml.State.START);
1948 @Override
1949 protected List<String> getInitialHelpLines() {
1950 return ImmutableList.of(
1951 "AppCfg [options] backends start <app-dir> <backend>",
1953 "Starts the backend with the specified name.");
1957 class BackendsStopAction extends AppCfgAction {
1958 private String backendName;
1960 BackendsStopAction() {
1961 super("backends", "stop");
1962 shortDescription = "Stop the specified backend.";
1965 @Override
1966 public void apply() {
1967 super.apply();
1968 if (getArgs().size() != 2) {
1969 throw new IllegalArgumentException("Expected the backend name");
1971 backendName = getArgs().get(1);
1973 @Override
1974 public void execute() {
1975 outputBackendsMessage();
1976 admin.setBackendState(backendName, BackendsXml.State.STOP);
1979 @Override
1980 protected List<String> getInitialHelpLines() {
1981 return ImmutableList.of(
1982 "AppCfg [options] backends stop <app-dir> <backend>",
1984 "Stops the backend with the specified name.");
1988 class BackendsDeleteAction extends AppCfgAction {
1989 private String backendName;
1991 BackendsDeleteAction() {
1992 super("backends", "delete");
1993 shortDescription = "Delete the specified backend.";
1996 @Override
1997 public void apply() {
1998 super.apply();
1999 if (getArgs().size() != 2) {
2000 throw new IllegalArgumentException("Expected the backend name");
2002 backendName = getArgs().get(1);
2004 @Override
2005 public void execute() {
2006 outputBackendsMessage();
2007 admin.deleteBackend(backendName);
2010 @Override
2011 protected List<String> getInitialHelpLines() {
2012 return ImmutableList.of(
2013 "AppCfg [options] backends delete",
2015 "Deletes the specified backend.");
2019 class BackendsConfigureAction extends AppCfgAction {
2020 private String backendName;
2022 BackendsConfigureAction() {
2023 super("backends", "configure");
2024 shortDescription = "Configure the specified backend.";
2027 @Override
2028 public void apply() {
2029 super.apply();
2030 if (getArgs().size() != 2) {
2031 throw new IllegalArgumentException("Expected the backend name");
2033 backendName = getArgs().get(1);
2035 @Override
2036 public void execute() {
2037 outputBackendsMessage();
2038 admin.configureBackend(backendName);
2041 @Override
2042 protected List<String> getInitialHelpLines() {
2043 return ImmutableList.of(
2044 "AppCfg [options] backends configure <app-dir> <backend>",
2046 "Updates the configuration of the backend with the specified name, without",
2047 "stopping instances that are currently running. Only valid for certain",
2048 "settings (instances, options: failfast, options: public).");
2053 * This is a catchall for the case where the user enters "appcfg.sh
2054 * backends app-dir sub-command" rather than "appcfg.sh backends
2055 * sub-command app-dir". It was added to maintain compatibility
2056 * with Python. It simply remaps the arguments and dispatches the
2057 * appropriate action.
2059 class BackendsAction extends AppCfgAction {
2060 private AppCfgAction subAction;
2062 BackendsAction() {
2063 super("backends");
2066 @Override
2067 public void apply() {
2068 super.apply();
2069 if (getArgs().size() < 2) {
2070 throw new IllegalArgumentException("Expected backends <app-dir> <sub-command> [...]");
2073 String dir = getArgs().get(0);
2074 String subCommand = getArgs().get(1);
2075 subAction = (AppCfgAction) Parser.lookupAction(actionsAndOptions.actions,
2076 new String[] {"backends", subCommand},
2078 if (subAction instanceof BackendsAction) {
2079 throw new IllegalArgumentException("Unknown backends subcommand.");
2081 List<String> newArgs = new ArrayList<String>();
2082 newArgs.add(dir);
2083 newArgs.addAll(getArgs().subList(2, getArgs().size()));
2084 subAction.setArgs(newArgs);
2085 subAction.apply();
2088 @Override
2089 public void execute() {
2090 outputBackendsMessage();
2091 subAction.execute();
2094 @Override
2095 protected List<String> getHelpLines() {
2096 return ImmutableList.of(
2097 "AppCfg [options] backends list: List the currently configured backends.",
2098 "AppCfg [options] backends update: Update the specified backend or all backends.",
2099 "AppCfg [options] backends rollback: Roll back a previously in-progress update.",
2100 "AppCfg [options] backends start: Start the specified backend.",
2101 "AppCfg [options] backends stop: Stop the specified backend.",
2102 "AppCfg [options] backends delete: Delete the specified backend.",
2103 "AppCfg [options] backends configure: Configure the specified backend.");
2107 class StartModuleVersionAction extends AppCfgAction {
2108 StartModuleVersionAction() {
2109 super("start_module_version");
2110 shortDescription = "Start the specified module version.";
2113 @Override
2114 public void execute() {
2115 admin.startModuleVersion();
2118 @Override
2119 protected List<String> getInitialHelpLines() {
2120 return ImmutableList.of(
2121 "AppCfg [options] start_module_version <app-dir>",
2123 "Starts the specified module version.");
2127 class StopModuleVersionAction extends AppCfgAction {
2128 StopModuleVersionAction() {
2129 super("stop_module_version");
2130 shortDescription = "Stop the specified module version.";
2133 @Override
2134 public void execute() {
2135 admin.stopModuleVersion();
2138 @Override
2139 protected List<String> getInitialHelpLines() {
2140 return ImmutableList.of(
2141 "AppCfg [options] stop_module_version <app-dir>",
2143 "Stops the specified module version.");
2147 private static class AppCfgListener implements UpdateListener {
2148 private final String operationName;
2150 AppCfgListener(String opName){
2151 operationName = opName;
2153 @Override
2154 public void onProgress(UpdateProgressEvent event) {
2155 System.out.println(event.getPercentageComplete() + "% " + event.getMessage());
2158 @Override
2159 public void onSuccess(UpdateSuccessEvent event) {
2160 String details = event.getDetails();
2161 if (details.length() > 0) {
2162 System.out.println();
2163 System.out.println("Details:");
2164 System.out.println(details);
2167 System.out.println();
2168 System.out.println(getSuccessSummaryMessage());
2171 @Override
2172 public void onFailure(UpdateFailureEvent event) {
2173 String details = event.getDetails();
2174 if (details.length() > 0) {
2175 System.out.println();
2176 System.out.println("Error Details:");
2177 System.out.println(details);
2180 System.out.println();
2181 String failMsg = event.getFailureMessage();
2182 System.out.println(failMsg);
2183 if (event.getCause() instanceof ClientAuthFailException) {
2184 System.out.println("Consider using the -e EMAIL option if that"
2185 + " email address is incorrect.");
2189 protected String getOperationName() {
2190 return operationName;
2193 protected String getSuccessSummaryMessage() {
2194 return getOperationName() + " completed successfully.";
2198 private class AppCfgUpdateModuleListener extends AppCfgListener {
2199 AppCfgUpdateModuleListener(){
2200 super("Update");
2203 @Override
2204 protected String getSuccessSummaryMessage() {
2205 return getOperationName() + " for module " + moduleName + " completed successfully.";
2209 private static class AppCfgUpdateBackendListener extends AppCfgListener {
2210 AppCfgUpdateBackendListener(){
2211 super("Update");
2215 private static class AppCfgVacuumIndexesListener extends AppCfgListener {
2216 AppCfgVacuumIndexesListener(){
2217 super("vacuum_indexes");
2221 private static class HostPort {
2222 private final String host;
2223 private final String port;
2225 public HostPort(String hostport) {
2226 int colon = hostport.indexOf(':');
2227 host = colon < 0 ? hostport : hostport.substring(0, colon);
2228 port = colon < 0 ? "" : hostport.substring(colon + 1);
2231 public String getHost() {
2232 return host;
2235 public String getPort() {
2236 return port;
2239 public boolean hasPort() {
2240 return port.length() > 0;
2244 private void validateApplicationDirectory(File war) {
2245 if (!war.exists()) {
2246 System.out.println("Unable to find the webapp directory " + war);
2247 printHelp();
2248 System.exit(1);
2249 } else if (!war.isDirectory()) {
2250 System.out.println("appcfg only accepts webapp directories, not war files.");
2251 printHelp();
2252 System.exit(1);