Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / tools / admin / AppCfg.java
blobb6723ac866f34752ba55c6112e05f2a707559128
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.util.Action;
17 import com.google.appengine.tools.util.ActionsAndOptions;
18 import com.google.appengine.tools.util.ClientCookieManager;
19 import com.google.appengine.tools.util.Logging;
20 import com.google.appengine.tools.util.Option;
21 import com.google.appengine.tools.util.Parser;
22 import com.google.appengine.tools.util.Parser.ParseResult;
23 import com.google.appengine.tools.wargen.WarGenerator;
24 import com.google.appengine.tools.wargen.WarGeneratorFactory;
25 import com.google.apphosting.utils.config.AppEngineConfigException;
26 import com.google.apphosting.utils.config.BackendsXml;
27 import com.google.apphosting.utils.config.EarHelper;
28 import com.google.apphosting.utils.config.EarInfo;
29 import com.google.apphosting.utils.config.WebModule;
30 import com.google.common.base.Joiner;
31 import com.google.common.collect.ImmutableList;
33 import net.sourceforge.yamlbeans.YamlException;
34 import net.sourceforge.yamlbeans.YamlReader;
36 import java.io.BufferedReader;
37 import java.io.ByteArrayInputStream;
38 import java.io.File;
39 import java.io.FileInputStream;
40 import java.io.FileNotFoundException;
41 import java.io.FileWriter;
42 import java.io.IOException;
43 import java.io.InputStreamReader;
44 import java.io.ObjectInputStream;
45 import java.io.PrintWriter;
46 import java.io.Reader;
47 import java.io.StringReader;
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.HashMap;
51 import java.util.HashSet;
52 import java.util.Iterator;
53 import java.util.LinkedList;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.Properties;
57 import java.util.Set;
58 import java.util.TreeSet;
59 import java.util.logging.Level;
60 import java.util.logging.Logger;
61 import java.util.prefs.Preferences;
63 import javax.net.ssl.HostnameVerifier;
64 import javax.net.ssl.HttpsURLConnection;
65 import javax.net.ssl.SSLSession;
67 /**
68 * The command-line SDK tool for administration of App Engine applications.
71 public class AppCfg {
73 private static final String EXTERNAL_RESOURCE_DIR_ARG =
74 DevAppServerMain.EXTERNAL_RESOURCE_DIR_ARG;
75 private static final String GENERATE_WAR_ARG =
76 DevAppServerMain.GENERATE_WAR_ARG;
77 private static final String GENERATED_WAR_DIR_ARG =
78 DevAppServerMain.GENERATED_WAR_DIR_ARG;
79 private static final String OVERRIDE_MODULE_SHORT_ARG = "M";
80 private static final String OVERRIDE_MODULE_LONG_ARG = "module";
82 private final ConnectOptions connectOptions;
83 private String externalResourceDir;
84 private boolean generateWar = false;
85 private String generatedWarDir;
86 private AppCfgAction action;
87 private String applicationDirectory;
88 private String moduleName;
89 private AppAdmin admin;
90 private boolean passin;
91 private boolean doBatch = true;
92 private boolean doJarSplitting = false;
93 private Set<String> jarSplittingExcludeSuffixes = null;
94 private boolean disablePrompt = false;
95 private File logFile = null;
96 private String compileEncoding = null;
97 private LoginReader loginReader = null;
98 private String overrideAppId;
99 private String overrideModule;
100 private String overrideAppVersion;
101 private boolean oauth2;
102 private String oauth2RefreshToken = null;
103 private String oauth2ClientId = null;
104 private String oauth2ClientSecret = null;
105 private boolean useCookies = true;
106 private boolean doJarJSPs = true;
107 private boolean doJarClasses = false;
108 private boolean deleteJSPs = false;
109 private String runtime;
110 private boolean allowAnyRuntime = false;
111 private boolean disableUpdateCheck = false;
112 private boolean failOnPrecompilationError = false;
113 private boolean ignoreEndpointsFailures = true;
114 private boolean updateUsageReporting = true;
115 private boolean enableQuickstart = false;
117 public static void main(String[] args) {
118 Logging.initializeLogging();
119 new AppCfg(args);
122 protected AppCfg(String[] cmdLineArgs) {
123 this(new AppAdminFactory(), cmdLineArgs);
126 public AppCfg(AppAdminFactory factory, String[] cmdLineArgs) {
127 connectOptions = new ConnectOptions();
128 Parser parser = new Parser();
129 oauth2 = isOauth2EnabledByDefault();
131 PrintWriter logWriter;
133 try {
134 logFile = File.createTempFile("appcfg", ".log");
135 logWriter = new PrintWriter(new FileWriter(logFile), true);
136 } catch (IOException e) {
137 throw new RuntimeException("Unable to enable logging.", e);
140 try {
141 ParseResult result =
142 parser.parseArgs(actionsAndOptions.actions, actionsAndOptions.options, cmdLineArgs);
143 action = (AppCfgAction) result.getAction();
144 validateCommandLineForEar();
145 try {
146 result.applyArgs();
147 } catch (IllegalArgumentException e) {
148 e.printStackTrace(logWriter);
149 System.out.println("Bad argument: " + e.getMessage());
150 System.out.println(action.getHelpString());
151 System.exit(1);
153 if (System.getProperty("http.proxyHost") != null &&
154 System.getProperty("https.proxyHost") == null) {
155 System.setProperty("https.proxyHost",
156 System.getProperty("http.proxyHost"));
157 if (System.getProperty("http.proxyPort") != null &&
158 System.getProperty("https.proxyPort") == null) {
159 System.setProperty("https.proxyPort",
160 System.getProperty("http.proxyPort"));
164 if (applicationDirectory != null) {
165 File appDirectoryFile = new File(applicationDirectory);
166 validateApplicationDirectory(appDirectoryFile);
168 UpdateCheck updateCheck = new UpdateCheck(connectOptions.getServer(), appDirectoryFile,
169 connectOptions.getSecure());
170 if (!disableUpdateCheck) {
171 updateCheck.maybePrintNagScreen(System.out);
173 updateCheck.checkJavaVersion(System.out);
175 if (action.requiresAuth()) {
176 if (oauth2) {
177 authorizeOauth2(connectOptions);
178 } else {
179 loadCookies(connectOptions);
183 factory.setBatchMode(doBatch);
185 factory.setJarClassessEnabled(doJarClasses);
186 factory.setJarJSPsEnabled(doJarJSPs);
187 factory.setDeleteJSPs(deleteJSPs);
188 factory.setJarSplittingEnabled(doJarSplitting);
189 if (jarSplittingExcludeSuffixes != null) {
190 factory.setJarSplittingExcludes(jarSplittingExcludeSuffixes);
192 if (compileEncoding != null) {
193 factory.setCompileEncoding(compileEncoding);
195 factory.setRuntime(runtime);
196 factory.setAllowAnyRuntime(allowAnyRuntime);
197 factory.setFailOnPrecompilationError(failOnPrecompilationError);
198 factory.setIgnoreEndpointsFailures(ignoreEndpointsFailures);
199 factory.setQuickstart(enableQuickstart);
200 System.out.println("Reading application configuration data...");
202 Iterable<Application> applications = readApplication();
203 executeAction(factory, applications, logWriter, action);
204 System.out.println("Success.");
205 cleanStaging(applications);
207 } catch (IllegalArgumentException e) {
208 e.printStackTrace(logWriter);
209 System.out.println("Bad argument: " + e.getMessage());
210 printHelp();
211 System.exit(1);
212 } catch (AppEngineConfigException e) {
213 e.printStackTrace(logWriter);
214 System.out.println("Bad configuration: " + e.getMessage());
215 if (e.getCause() != null) {
216 System.out.println(" Caused by: " + e.getCause().getMessage());
218 printLogLocation();
219 System.exit(1);
220 } catch (Exception e) {
221 System.out.println("Encountered a problem: " + e.getMessage());
222 e.printStackTrace(logWriter);
223 printLogLocation();
224 System.exit(1);
228 boolean isOauth2EnabledByDefault() {
229 return true;
232 private void validateCommandLineForEar() {
233 if (EarHelper.isEar(applicationDirectory)) {
234 if (!action.isEarAction()) {
235 throw new IllegalArgumentException(
236 "The requested action does not support EAR configurations");
238 if (overrideModule != null) {
239 throw new IllegalArgumentException("With an EAR configuration " + "-"
240 + OVERRIDE_MODULE_SHORT_ARG + "/" + "--" + OVERRIDE_MODULE_LONG_ARG
241 + " is not allowed.");
243 if (externalResourceDir != null) {
244 throw new IllegalArgumentException("With an EAR configuration "
245 + "--" + EXTERNAL_RESOURCE_DIR_ARG + " is not allowed.");
250 private Iterable<Application> readApplication() throws IOException {
251 ImmutableList.Builder<Application> resultBuilder = ImmutableList.builder();
252 if (applicationDirectory != null) {
253 if (EarHelper.isEar(applicationDirectory, false)) {
254 EarInfo earInfo = EarHelper.readEarInfo(applicationDirectory,
255 new File(Application.getSdkDocsDir(), "appengine-application.xsd"));
256 String applicationId = overrideAppId != null ?
257 overrideAppId : earInfo.getAppengineApplicationXml().getApplicationId();
258 for (WebModule webModule : earInfo.getWebModules()) {
259 System.out.println("Processing module " + webModule.getModuleName());
260 resultBuilder.add(readWar(webModule.getApplicationDirectory().getAbsolutePath(),
261 applicationId, null));
262 String contextRootWarning =
263 "Ignoring application.xml context-root element, for details see "
264 + "https://developers.google.com/appengine/docs/java/modules/#config";
265 System.out.println(contextRootWarning);
267 } else {
268 resultBuilder.add(readWar(applicationDirectory,
269 overrideAppId, overrideModule));
272 return resultBuilder.build();
275 private Application readWar(String warDirectory,
276 String applicationIdOrNull, String moduleNameOrNull) throws IOException {
277 Application application = Application.readApplication(warDirectory,
278 applicationIdOrNull,
279 moduleNameOrNull,
280 overrideAppVersion);
281 if (externalResourceDir != null) {
282 application.setExternalResourceDir(externalResourceDir);
284 application.setListener(new UpdateListener() {
285 @Override
286 public void onProgress(UpdateProgressEvent event) {
287 System.out.println(event.getPercentageComplete() + "% " + event.getMessage());
290 @Override
291 public void onSuccess(UpdateSuccessEvent event) {
292 System.out.println("Operation complete.");
295 @Override
296 public void onFailure(UpdateFailureEvent event) {
297 System.out.println(event.getFailureMessage());
300 return application;
303 private void executeAction(AppAdminFactory factory, Iterable<Application> applications,
304 PrintWriter logWriter, AppCfgAction executeMe) {
305 try {
306 if (applications.iterator().hasNext()) {
307 boolean firstModule = true;
308 for (Application application : applications) {
309 factory.setCompileJsps(!application.getAppEngineWebXml().getUseVm());
310 moduleName = WebModule.getModuleName(application.getAppEngineWebXml());
311 try {
312 admin = factory.createAppAdmin(connectOptions, application, logWriter);
313 if (!firstModule) {
314 admin.getUpdateOptions().setUpdateGlobalConfigurations(false);
316 Version localVersion = SdkInfo.getLocalVersion();
317 String sdkVersion = String.format("Java/%s(%s)",
318 localVersion.getRelease(), localVersion.getTimestamp());
319 admin.getUpdateOptions().setSdkVersion(sdkVersion);
320 admin.getUpdateOptions().setUpdateUsageReporting(updateUsageReporting);
321 System.out.printf("%n%nBeginning interaction for module %s...%n", moduleName);
322 executeMe.execute();
323 } finally {
324 moduleName = null;
325 firstModule = false;
328 } else {
329 admin = factory.createAppAdmin(connectOptions, null, logWriter);
330 executeMe.execute();
332 } catch (AdminException ex) {
333 System.out.println(ex.getMessage());
334 ex.printStackTrace(logWriter);
335 printLogLocation();
336 System.exit(1);
337 } finally {
338 admin = null;
342 private void cleanStaging(Iterable<Application> applications) throws IOException {
343 for (Application application : applications) {
344 if (application != null) {
345 String moduleName = WebModule.getModuleName(application.getAppEngineWebXml());
346 if (!connectOptions.getRetainUploadDir()) {
347 System.out.printf("Cleaning up temporary files for module %s...%n", moduleName);
348 application.cleanStagingDirectory();
349 } else {
350 File stage = application.getStagingDir();
351 if (stage == null) {
352 System.out.printf(
353 "Temporary staging directory was not needed, and not created for module %s%n",
354 moduleName);
355 } else {
356 System.out.printf("Temporary staging for module %s directory left in %s%n", moduleName,
357 stage.getCanonicalPath());
365 * Prints a uniform message to direct the user to the given logfile for
366 * more information.
368 private void printLogLocation() {
369 if (logFile != null) {
370 System.out.println("Please see the logs [" + logFile.getAbsolutePath() +
371 "] for further information.");
375 private String loadCookies(final ConnectOptions options) {
376 Preferences prefs = Preferences.userNodeForPackage(ServerConnection.class);
377 String prefsEmail = prefs.get("email", null);
379 if (options.getUsePersistedCredentials() && prefsEmail != null) {
380 ClientCookieManager cookies = null;
381 byte[] serializedCookies = prefs.getByteArray("cookies", null);
382 if (serializedCookies != null) {
383 try {
384 cookies = (ClientCookieManager)
385 new ObjectInputStream(
386 new ByteArrayInputStream(serializedCookies)).readObject();
387 } catch (ClassNotFoundException ex) {
388 } catch (IOException ex) {
392 if (options.getUserId() == null ||
393 prefsEmail.equals(options.getUserId())) {
394 options.setCookies(cookies);
398 options.setPasswordPrompt(new AppAdminFactory.PasswordPrompt() {
399 @Override
400 public String getPassword() {
401 doPrompt();
402 options.setUserId(loginReader.getUsername());
403 return loginReader.getPassword();
406 return prefsEmail;
410 * Tries to get an OAuth2 access token and set it in the ConnectOptions.
411 * It exists with exit code 1 in case no token could be obtained.
413 private void authorizeOauth2(final ConnectOptions options){
414 OAuth2Native client =
415 new OAuth2Native(useCookies, oauth2ClientId, oauth2ClientSecret, oauth2RefreshToken);
416 Credential credential = client.authorize();
417 if (credential != null && credential.getAccessToken() != null) {
418 options.setOauthToken(credential.getAccessToken());
419 } else {
420 System.exit(1);
425 * Helper function for generating a war directory based on an app.yaml file located in an external
426 * resource directory. First the command line arguments are checked to ensure that they are
427 * appropriate for war generation. If there is a problem then a {@link RuntimeException} is
428 * thrown. Otherwise a war directory is generated and its path is returned, and a success
429 * message is written to standard out.
431 * @return The path of the generated war directory.
433 private String validateArgsAndGenerateWar() {
434 if (externalResourceDir == null) {
435 throw new IllegalArgumentException("When generating a war directory --"
436 + EXTERNAL_RESOURCE_DIR_ARG + " must also be specified.");
438 if (EarHelper.isEar(externalResourceDir, false)) {
439 throw new IllegalArgumentException(
440 "With an EAR configuration " + "--" + EXTERNAL_RESOURCE_DIR_ARG + " is not allowed.");
442 File externalResourceDirectory = new File(externalResourceDir);
443 if (!externalResourceDirectory.isDirectory()) {
444 throw new IllegalArgumentException(externalResourceDir + " is not an existing directory.");
446 File appYamlFile = new File(externalResourceDirectory, WarGenerator.APP_YAML);
447 if (!appYamlFile.isFile()) {
448 throw new IllegalArgumentException(appYamlFile.getPath() + " not found.");
450 File destination = (generatedWarDir == null ? null : new File(generatedWarDir));
451 try {
452 WarGenerator warGen =
453 WarGeneratorFactory.newWarGenerator(externalResourceDirectory, destination);
454 String warDir = warGen.generateWarDirectory().getPath();
455 System.out.println("Successfully generated war directory at " + warDir);
456 return warDir;
457 } catch (IOException e) {
458 throw new RuntimeException("Unable to generate a war directory.", e);
462 private void doPrompt() {
464 if (disablePrompt) {
465 System.out.println("Your authentication credentials can't be found and may have expired.\n" +
466 "Please run appcfg directly from the command line to re-establish your credentials.");
467 System.exit(1);
470 getLoginReader().doPrompt();
474 private LoginReader getLoginReader() {
475 if (loginReader == null) {
476 loginReader = LoginReaderFactory.createLoginReader(connectOptions, passin);
478 return loginReader;
481 private static final List<String> generalOptionNamesInHelpOrder =
482 ImmutableList.of(
483 "server",
484 "email",
485 "host",
486 "proxy",
487 "proxy_https",
488 "no_cookies",
489 "sdk_root",
490 "passin",
491 "insecure",
492 "ignore_bad_cert",
493 "application",
494 "module",
495 "version",
496 "oauth2",
497 "use_java7",
498 "noisy");
500 private static final List<String> optionNamesInHelpOrder =
501 ImmutableList.<String>builder().addAll(generalOptionNamesInHelpOrder).add(
502 "enable_jar_splitting",
503 "jar_splitting_excludes",
504 "disable_jar_jsps",
505 "enable_jar_classes",
506 "delete_jsps",
507 "retain_upload_dir",
508 "compile_encoding",
509 "num_days",
510 "severity",
511 "include_all",
512 "append",
513 "num_runs",
514 "force",
515 "no_usage_reporting"
516 ).build();
518 private static final List<String> actionNamesInHelpOrder =
519 ImmutableList.of(
520 "help",
521 "download_app",
522 "request_logs",
523 "rollback",
524 "start_module_version",
525 "stop_module_version",
526 "update",
527 "update_indexes",
528 "update_cron",
529 "update_queues",
530 "update_dispatch",
531 "update_dos",
532 "version",
533 "set_default_version",
534 "cron_info",
535 "resource_limits_info",
536 "vacuum_indexes",
537 "backends list",
538 "backends update",
539 "backends rollback",
540 "backends start",
541 "backends stop",
542 "backends delete",
543 "backends configure",
544 "list_versions",
545 "delete_version");
547 private String helpText = null;
548 private void printHelp() {
549 if (helpText == null) {
550 List<String> helpLines = new LinkedList<String>();
551 helpLines.add("usage: AppCfg [options] <action> [<app-dir>] [<argument>]");
552 helpLines.add("");
553 helpLines.add("Action must be one of:");
554 for (String actionName : actionsAndOptions.actionNames) {
555 Action action = actionsAndOptions.getAction(actionName);
556 if (action != null) {
557 helpLines.add(" " + actionName + ": " + action.getShortDescription());
560 helpLines.add("Use 'help <action>' for a detailed description.");
561 helpLines.add("");
562 helpLines.add("options:");
563 for (String optionName : actionsAndOptions.optionNames) {
564 Option option = actionsAndOptions.getOption(optionName);
565 helpLines.addAll(option.getHelpLines());
567 helpText = Joiner.on("\n").join(helpLines);
569 System.out.println(helpText);
570 System.out.println();
573 private final List<Option> builtInOptions = Arrays.asList(
575 new Option("h", "help", true) {
576 @Override
577 public List<String> getHelpLines() {
578 return ImmutableList.<String>of(
579 " -h, --help Show the help message and exit.");
581 @Override
582 public void apply() {
583 printHelp();
584 System.exit(1);
588 new Option("s", "server", false) {
589 @Override
590 public List<String> getHelpLines() {
591 return ImmutableList.<String>of(
592 " -s SERVER, --server=SERVER",
593 " The server to connect to.");
595 @Override
596 public void apply() {
597 connectOptions.setServer(getValue());
601 new Option("e", "email", false) {
602 @Override
603 public List<String> getHelpLines() {
604 return ImmutableList.<String>of(
605 " -e EMAIL, --email=EMAIL",
606 " The username to use. Will prompt if omitted.");
608 @Override
609 public void apply() {
610 connectOptions.setUserId(getValue());
614 new Option("H", "host", false) {
615 @Override
616 public List<String> getHelpLines() {
617 return ImmutableList.<String>of(
618 " -H HOST, --host=HOST Overrides the Host header sent with all RPCs.");
620 @Override
621 public void apply() {
622 connectOptions.setHost(getValue());
626 new Option("p", "proxy", false) {
627 @Override
628 public List<String> getHelpLines() {
629 return ImmutableList.<String>of(
630 " -p PROXYHOST[:PORT], --proxy=PROXYHOST[:PORT]",
631 " Proxies requests through the given proxy server.",
632 " If --proxy_https is also set, only HTTP will be",
633 " proxied here, otherwise both HTTP and HTTPS will.");
635 @Override
636 public void apply() {
637 HostPort hostport = new HostPort(getValue());
639 System.setProperty("http.proxyHost", hostport.getHost());
640 if (hostport.hasPort()) {
641 System.setProperty("http.proxyPort", hostport.getPort());
646 new Option(null, "proxy_https", false) {
647 @Override
648 public List<String> getHelpLines() {
649 return ImmutableList.<String>of(
650 " --proxy_https=PROXYHOST[:PORT]",
651 " Proxies HTTPS requests through the given proxy server.");
653 @Override
654 public void apply() {
655 HostPort hostport = new HostPort(getValue());
657 System.setProperty("https.proxyHost", hostport.getHost());
658 if (hostport.hasPort()) {
659 System.setProperty("https.proxyPort", hostport.getPort());
664 new Option(null, "insecure", true) {
665 @Override
666 public void apply() {
667 connectOptions.setSecure(false);
671 new Option(null, "ignore_bad_cert", true) {
672 @Override
673 public void apply() {
674 HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
675 @Override
676 public boolean verify(String hostname, SSLSession session) {
677 return true;
683 new Option(null, "no_cookies", true) {
684 @Override
685 public List<String> getHelpLines() {
686 return ImmutableList.<String>of(
688 " --no_cookies Do not save/load access credentials to/from disk.");
690 @Override
691 public void apply() {
692 useCookies = false;
693 connectOptions.setUsePersistedCredentials(false);
697 new Option("f", "force", true) {
698 @Override
699 public List<String> getHelpLines() {
700 return ImmutableList.<String>of(
701 " -f, --force Force deletion of indexes without being prompted.");
703 @Override
704 public void apply(){
705 if (action instanceof VacuumIndexesAction){
706 VacuumIndexesAction viAction = (VacuumIndexesAction) action;
707 viAction.promptUserForEachDelete = false;
712 new Option("a", "append", true) {
713 @Override
714 public List<String> getHelpLines() {
715 return ImmutableList.<String>of(
716 " -a, --append Append to existing file.");
718 @Override
719 public void apply() {
720 if (action instanceof RequestLogsAction) {
721 RequestLogsAction logsAction = (RequestLogsAction) action;
722 logsAction.append = true;
727 new Option("n", "num_days", false) {
728 @Override
729 public List<String> getHelpLines() {
730 return ImmutableList.<String>of(
731 " -n NUM_DAYS, --num_days=NUM_DAYS",
732 " Number of days worth of log data to get. The cut-off",
733 " point is midnight UTC. Use 0 to get all available",
734 " logs. Default is 1.");
736 @Override
737 public void apply() {
738 if (action instanceof RequestLogsAction) {
739 RequestLogsAction logsAction = (RequestLogsAction) action;
740 try {
741 logsAction.numDays = Integer.parseInt(getValue());
742 } catch (NumberFormatException e) {
743 throw new IllegalArgumentException("num_days must be an integral number.");
745 } else if (action instanceof CronInfoAction) {
746 CronInfoAction croninfoAction = (CronInfoAction) action;
747 croninfoAction.setNumRuns(getValue());
752 new Option(null, "num_runs", false) {
753 @Override
754 public List<String> getHelpLines() {
755 return ImmutableList.<String>of(
756 " -n NUM_RUNS, --num_runs=NUM_RUNS",
757 " Number of scheduled execution times to compute");
759 @Override
760 public void apply() {
761 if (action instanceof CronInfoAction) {
762 CronInfoAction croninfoAction = (CronInfoAction) action;
763 croninfoAction.setNumRuns(getValue());
768 new Option(null, "severity", false) {
769 @Override
770 public List<String> getHelpLines() {
771 return ImmutableList.<String>of(
772 " --severity=SEVERITY Severity of app-level log messages to get. The range",
773 " is 0 (DEBUG) through 4 (CRITICAL). If omitted, only",
774 " request logs are returned.");
776 @Override
777 public void apply() {
778 RequestLogsAction logsAction = (RequestLogsAction) action;
779 try {
780 int severity = Integer.parseInt(getValue());
781 int maxSeverity = LogSeverity.CRITICAL.ordinal();
782 if (severity < 0 || severity > maxSeverity) {
783 throw new IllegalArgumentException("severity must be between 0 and " + maxSeverity);
785 logsAction.severity = severity;
786 } catch (NumberFormatException e) {
787 for (Enum<LogSeverity> severity : LogSeverity.values()) {
788 if (getValue().equalsIgnoreCase(severity.toString())) {
789 logsAction.severity = severity.ordinal();
790 return;
793 throw new IllegalArgumentException("severity must be an integral "
794 + "number 0-4, or one of DEBUG, INFO, WARN, ERROR, CRITICAL");
799 new Option(null, "include_all", true) {
800 @Override
801 public List<String> getHelpLines() {
802 return ImmutableList.<String>of(
803 " --include_all Include everything in log messages.");
805 @Override
806 public void apply() {
807 RequestLogsAction logsAction = (RequestLogsAction) action;
808 logsAction.includeAll = true;
812 new Option(null, "sdk_root", false) {
813 @Override
814 public List<String> getHelpLines() {
815 return ImmutableList.<String>of(
816 " --sdk_root=root Overrides where the SDK is located.");
818 @Override
819 public void apply() {
820 connectOptions.setSdkRoot(getValue());
824 new Option(null, "disable_jar_jsps", true) {
825 @Override
826 public List<String> getHelpLines() {
827 return ImmutableList.<String>of(
828 " --disable_jar_jsps",
829 " Do not jar the classes generated from JSPs.");
832 @Override
833 public void apply() {
834 doJarJSPs = false;
838 new Option(null, "enable_jar_classes", true) {
839 @Override
840 public List<String> getHelpLines() {
841 return ImmutableList.<String>of(
842 " --enable_jar_classes",
843 " Jar the WEB-INF/classes content.");
846 @Override
847 public void apply() {
848 doJarClasses = true;
851 new Option(null, "delete_jsps", true) {
852 @Override
853 public List<String> getHelpLines() {
854 return ImmutableList.<String>of(
855 " --delete_jsps",
856 " Delete the JSP source files after compilation.");
859 @Override
860 public void apply() {
861 deleteJSPs = true;
865 new Option(null, "enable_jar_splitting", true) {
866 @Override
867 public List<String> getHelpLines() {
868 return ImmutableList.<String>of(
869 " --enable_jar_splitting",
870 " Split large jar files (> 10M) into smaller fragments.");
872 @Override
873 public void apply() {
874 doJarSplitting = true;
878 new Option(null, "jar_splitting_excludes", false) {
879 @Override
880 public List<String> getHelpLines() {
881 return ImmutableList.<String>of(
882 " --jar_splitting_excludes=SUFFIXES",
883 " When --enable-jar-splitting is set, files that match",
884 " the list of comma separated SUFFIXES will be excluded",
885 " from all jars.");
887 @Override
888 public void apply() {
889 jarSplittingExcludeSuffixes = new HashSet<String>(Arrays.asList(getValue().split(",")));
893 new Option(null, "retain_upload_dir", true) {
894 @Override
895 public List<String> getHelpLines() {
896 return ImmutableList.<String>of(
897 " --retain_upload_dir",
898 " Do not delete temporary (staging) directory used in",
899 " uploading.");
901 @Override
902 public void apply() {
903 connectOptions.setRetainUploadDir(true);
907 new Option(null, "passin", true) {
908 @Override
909 public List<String> getHelpLines() {
910 return ImmutableList.<String>of(
911 " --passin Always read the login password from stdin.");
913 @Override
914 public void apply() {
915 passin = true;
919 new Option(null, "no_batch", true) {
920 @Override
921 public void apply() {
922 doBatch = false;
925 new Option(null, "compile_encoding", false) {
926 @Override
927 public List<String> getHelpLines() {
928 return ImmutableList.<String>of(
929 " --compile_encoding",
930 " The character encoding to use when compiling JSPs.");
932 @Override
933 public void apply() {
934 compileEncoding = getValue();
938 new Option(null, "disable_prompt", true) {
939 @Override
940 public void apply() {
941 disablePrompt = true;
945 new Option(null, "disable_update_check", true) {
946 @Override
947 public void apply() {
948 disableUpdateCheck = true;
952 new Option("A", "application", false) {
953 @Override
954 public List<String> getHelpLines() {
955 return ImmutableList.<String>of(
956 " -A APP_ID, --application=APP_ID",
957 " Override application id from appengine-web.xml or app.yaml");
959 @Override
960 public void apply() {
961 overrideAppId = getValue();
965 new Option(OVERRIDE_MODULE_SHORT_ARG, OVERRIDE_MODULE_LONG_ARG, false) {
966 @Override
967 public List<String> getHelpLines() {
968 return ImmutableList.<String>of(
969 " -" + OVERRIDE_MODULE_SHORT_ARG + " MODULE, --" + OVERRIDE_MODULE_LONG_ARG
970 + "=MODULE",
971 " Override module from appengine-web.xml or app.yaml");
973 @Override
974 public void apply() {
975 overrideModule = getValue();
979 new Option("V", "version" , false) {
980 @Override
981 public List<String> getHelpLines() {
982 return ImmutableList.<String>of(
983 " -V VERSION, --version=VERSION",
984 " Override (major) version from appengine-web.xml " +
985 "or app.yaml");
987 @Override
988 public void apply() {
989 overrideAppVersion = getValue();
993 new Option(null, "oauth2", true) {
994 @Override
995 public List<String> getHelpLines() {
996 return ImmutableList.<String>of(
997 " --oauth2 Ignored (OAuth2 is the default).");
999 @Override
1000 public void apply() {
1004 new Option(null, "oauth2_refresh_token", false) {
1005 @Override
1006 public void apply() {
1007 oauth2RefreshToken = getValue();
1008 useCookies = false;
1012 new Option(null, "oauth2_client_id", false) {
1013 @Override
1014 public void apply() {
1015 oauth2ClientId = getValue();
1016 useCookies = false;
1020 new Option(null, "oauth2_client_secret", false) {
1021 @Override
1022 public void apply() {
1023 oauth2ClientSecret = getValue();
1024 useCookies = false;
1028 new Option(null, "oauth2_config_file", false) {
1029 @Override
1030 public void apply() {
1031 final Properties props = new Properties();
1032 try {
1033 props.load(new FileInputStream(getValue()));
1034 } catch (FileNotFoundException e) {
1035 throw new RuntimeException(
1036 String.format("OAuth2 configuration file does not exist: %s", getValue()), e);
1037 } catch (IOException e) {
1038 throw new RuntimeException(
1039 String.format("Could not read OAuth2 configuration file: %s", getValue()), e);
1042 oauth2RefreshToken = props.getProperty("oauth2_refresh_token");
1043 oauth2ClientId = props.getProperty("oauth2_client_id");
1044 oauth2ClientSecret = props.getProperty("oauth2_client_secret");
1046 if (oauth2RefreshToken != null ||
1047 oauth2ClientId != null ||
1048 oauth2ClientSecret != null) {
1049 useCookies = false;
1054 new Option(null, "no_usage_reporting", true) {
1055 @Override
1056 public List<String> getHelpLines() {
1057 return ImmutableList.<String>of(
1058 " --no_usage_reporting",
1059 " Disable usage reporting.");
1062 @Override
1063 public void apply() {
1064 updateUsageReporting = false;
1068 new Option(null, "use_java7", true) {
1069 @Override
1070 public void apply() {
1074 new Option(null, "noisy", true) {
1075 @Override
1076 public List<String> getHelpLines() {
1077 return ImmutableList.<String>of(
1078 " --noisy",
1079 " Log much more information about what the tool is doing.");
1082 @Override
1083 public void apply() {
1084 Logger rootLogger = Logger.getLogger("");
1085 rootLogger.getHandlers()[0].setLevel(Level.ALL);
1089 new Option("r", "runtime", false) {
1090 @Override
1091 public void apply() {
1092 runtime = getValue();
1096 new Option("R", "allow_any_runtime", true) {
1097 @Override
1098 public void apply() {
1099 allowAnyRuntime = true;
1103 new Option(null, EXTERNAL_RESOURCE_DIR_ARG, false) {
1104 @Override
1105 public void apply() {
1106 externalResourceDir = getValue();
1110 new Option(null, GENERATE_WAR_ARG, true) {
1111 @Override
1112 public void apply() {
1113 generateWar = true;
1117 new Option(null, GENERATED_WAR_DIR_ARG, false) {
1118 @Override
1119 public void apply() {
1120 generateWar = true;
1121 generatedWarDir = getValue();
1125 new Option(null, "fail_on_precompilation_error", true) {
1126 @Override
1127 public void apply() {
1128 failOnPrecompilationError = true;
1132 new Option(null, "ignore_endpoints_failures", true) {
1133 @Override
1134 public List<String> getHelpLines() {
1135 return ImmutableList.<String>of(
1136 " --ignore_endpoints_failures",
1137 " When uploading an app that uses Google Cloud Endpoints,"
1138 + " if there's a a failure to update the configuration on the "
1139 + " Endpoints server, deployment should still complete.");
1142 @Override
1143 public void apply() {
1144 ignoreEndpointsFailures = true;
1148 new Option(null, "no_ignore_endpoints_failures", true) {
1149 @Override
1150 public List<String> getHelpLines() {
1151 return ImmutableList.<String>of(
1152 " --no_ignore_endpoints_failures",
1153 " When uploading an app that uses Google Cloud Endpoints,"
1154 + " if there's a a failure to update the configuration on the "
1155 + " Endpoints server, deployment should fail and rollback.");
1158 @Override
1159 public void apply() {
1160 ignoreEndpointsFailures = false;
1164 new Option(null, "use_remote_resource_limits", true) {
1166 @Override
1167 public List<String> getHelpLines() {
1168 return ImmutableList.<String>of(
1169 " --use_remote_resource_limits",
1170 " Get resource limits from server when staging");
1173 @Override
1174 public void apply() {
1175 if (action instanceof StagingAction){
1176 StagingAction stagingAction = (StagingAction) action;
1177 stagingAction.useRemoteResourceLimits = true;
1182 new Option(null, "enable_quickstart", true) {
1184 @Override
1185 public List<String> getHelpLines() {
1186 return ImmutableList.<String>of(
1187 " --enable_quickstart",
1188 " Use jetty quickstart to process servlet annotations");
1191 @Override
1192 public void apply() {
1193 enableQuickstart = true;
1198 private final List<Action> builtInActions = Arrays.<Action>asList(
1199 new UpdateAction(),
1200 new RequestLogsAction(),
1201 new RollbackAction(),
1202 new UpdateIndexesAction(),
1203 new UpdateCronAction(),
1204 new UpdateDispatchAction(),
1205 new UpdateDosAction(),
1206 new UpdateQueueAction(),
1207 new CronInfoAction(),
1208 new VacuumIndexesAction(),
1209 new HelpAction(),
1210 new DownloadAppAction(),
1211 new VersionAction(),
1212 new SetDefaultVersionAction(),
1213 new ResourceLimitsInfoAction(),
1214 new StartModuleVersionAction(),
1215 new StopModuleVersionAction(),
1216 new BackendsListAction(),
1217 new BackendsRollbackAction(),
1218 new BackendsUpdateAction(),
1219 new BackendsStartAction(),
1220 new BackendsStopAction(),
1221 new BackendsDeleteAction(),
1222 new BackendsConfigureAction(),
1223 new BackendsAction(),
1224 new ListVersionsAction(),
1225 new DeleteVersionAction(),
1226 new DebugAction(),
1227 new MigrateTrafficAction(),
1228 new StagingAction()
1231 private Map<String, Option> builtInOptionMap;
1233 private List<Option> builtInOptions(String... optionNames) {
1234 if (builtInOptionMap == null) {
1235 builtInOptionMap = new HashMap<String, Option>(builtInOptions.size());
1236 for (Option option : builtInOptions){
1237 builtInOptionMap.put(option.getLongName(), option);
1240 List<Option> options = new LinkedList<Option>();
1241 for (String name : optionNames) {
1242 Option option = builtInOptionMap.get(name);
1243 if (option != null) {
1244 options.add(option);
1247 return options;
1250 private final ActionsAndOptions actionsAndOptions = buildActionsAndOptions();
1252 private ActionsAndOptions buildActionsAndOptions() {
1253 ActionsAndOptions actionsAndOptions = getBuiltInActionsAndOptions();
1254 return actionsAndOptions;
1258 * Builds the collection of built-in Actions and Options.
1260 private ActionsAndOptions getBuiltInActionsAndOptions() {
1261 ActionsAndOptions actionsAndOptions = new ActionsAndOptions();
1262 actionsAndOptions.actions = builtInActions;
1263 actionsAndOptions.actionNames = actionNamesInHelpOrder;
1264 actionsAndOptions.options = builtInOptions;
1265 actionsAndOptions.optionNames = optionNamesInHelpOrder;
1266 actionsAndOptions.generalOptionNames = generalOptionNamesInHelpOrder;
1267 return actionsAndOptions;
1270 abstract class AppCfgAction extends Action {
1272 AppCfgAction(String... names) {
1273 this(null, names);
1276 AppCfgAction(List<Option> options, String... names) {
1277 super(options, names);
1280 @Override
1281 protected void setArgs(List<String> args) {
1282 super.setArgs(args);
1285 @Override
1286 public void apply() {
1287 if (generateWar) {
1288 applicationDirectory = validateArgsAndGenerateWar();
1289 List<String> args = getArgs();
1290 List<String> newArgs = new ArrayList<String>(args.size() + 1);
1291 newArgs.add(applicationDirectory);
1292 newArgs.addAll(args);
1293 setArgs(newArgs);
1294 } else {
1295 if (getArgs().size() < 1) {
1296 throw new IllegalArgumentException("Expected the application directory"
1297 + " as an argument after the action name.");
1299 applicationDirectory = getArgs().get(0);
1300 validateCommandLineForEar();
1303 public abstract void execute();
1305 @Override
1306 protected List<String> getHelpLines() {
1307 List<String> helpLines = new LinkedList<String>();
1308 helpLines.addAll(getInitialHelpLines());
1309 helpLines.add("");
1310 helpLines.add("Options:");
1311 for (String optionName : actionsAndOptions.generalOptionNames) {
1312 Option option = actionsAndOptions.getOption(optionName);
1313 if (option != null) {
1314 helpLines.addAll(option.getHelpLines());
1317 if (extraOptions != null) {
1318 for (Option option : extraOptions) {
1319 helpLines.addAll(option.getHelpLines());
1322 return helpLines;
1326 * Returns a list of Strings to be displayed as the initial lines of a help text. Subclasses
1327 * should override this method.
1328 * <p>
1329 * The text returned by this method should describe the base Action without any of its options.
1330 * Text describing the options will be added in lines below this text.
1332 protected List<String> getInitialHelpLines() {
1333 return ImmutableList.of();
1336 protected boolean isEarAction() {
1337 return false;
1340 protected boolean requiresAuth() {
1341 return true;
1344 protected void outputBackendsMessage() {
1345 System.out.println("Warning: This application uses Backends, a deprecated feature that " +
1346 "has been replaced by Modules, which offers additional functionality. Please " +
1347 "convert your backends to modules as described at: https://developers.google.com/" +
1348 "appengine/docs/java/modules/converting.");
1352 class UpdateAction extends AppCfgAction {
1353 UpdateAction() {
1354 super(builtInOptions("enable_jar_splitting", "jar_splitting_excludes", "retain_upload_dir",
1355 "compile_encoding", "disable_jar_jsps", "delete_jsps", "enable_jar_classes"), "update");
1356 shortDescription = "Create or update an app version.";
1359 @Override
1360 public void execute() {
1361 admin.update(new AppCfgUpdateModuleListener());
1364 @Override
1365 protected List<String> getInitialHelpLines() {
1366 return ImmutableList.of(
1367 "AppCfg [options] update <app-dir>",
1369 "Installs a new version of the application onto the server, as the",
1370 "default version for end users.");
1373 @Override
1374 protected boolean isEarAction() {
1375 return true;
1379 class RequestLogsAction extends AppCfgAction {
1380 String outputFile;
1381 int numDays = 1;
1382 int severity = -1;
1383 boolean includeAll = false;
1384 boolean append = false;
1386 RequestLogsAction() {
1387 super(builtInOptions(
1388 "num_days", "severity", "include_all", "append"), "request_logs");
1389 shortDescription = "Write request logs in Apache common log format.";
1391 @Override
1392 public void apply() {
1393 super.apply();
1394 if (getArgs().size() != 2) {
1395 throw new IllegalArgumentException("Expected the application directory"
1396 + " and log file as arguments after the request_logs action name.");
1398 outputFile = getArgs().get(1);
1400 @Override
1401 public void execute() {
1402 Reader reader = admin.requestLogs(numDays,
1403 severity >= 0 ? LogSeverity.values()[severity] : null, includeAll);
1404 if (reader == null) {
1405 return;
1408 BufferedReader r = new BufferedReader(reader);
1409 PrintWriter writer = null;
1410 try {
1411 if (outputFile.equals("-")) {
1412 writer = new PrintWriter(System.out);
1413 } else {
1414 writer = new PrintWriter(new FileWriter(outputFile, append));
1416 String line = null;
1417 while ((line = r.readLine()) != null) {
1418 writer.println(line);
1420 } catch (IOException e) {
1421 throw new RuntimeException("Failed to read logs: " + e);
1422 } finally {
1423 if (writer != null) {
1424 writer.close();
1426 try {
1427 r.close();
1428 } catch (IOException e) {
1432 @Override
1433 protected List<String> getInitialHelpLines() {
1434 return ImmutableList.of(
1435 "AppCfg [options] request_logs <app-dir> <output-file>",
1437 "Populates the output-file with recent logs from the application.");
1441 class RollbackAction extends AppCfgAction {
1442 RollbackAction() {
1443 super("rollback");
1444 shortDescription = "Rollback an in-progress update.";
1446 @Override
1447 public void execute() {
1448 admin.rollback();
1450 @Override
1451 protected List<String> getInitialHelpLines() {
1452 return ImmutableList.of(
1453 "AppCfg [options] rollback <app-dir>",
1455 "The 'update' command requires a server-side transaction.",
1456 "Use 'rollback' if you experience an error during 'update'",
1457 "and want to begin a new update transaction.");
1461 class UpdateIndexesAction extends AppCfgAction {
1462 UpdateIndexesAction() {
1463 super("update_indexes");
1464 shortDescription = "Update application indexes.";
1466 @Override
1467 public void execute() {
1468 admin.updateIndexes();
1470 @Override
1471 protected List<String> getInitialHelpLines() {
1472 return ImmutableList.of(
1473 "AppCfg [options] update_indexes <app-dir>",
1475 "Updates the datastore indexes for the server to add any in the current",
1476 "application directory. Does not alter the running application version, nor",
1477 "remove any existing indexes.");
1481 class UpdateCronAction extends AppCfgAction {
1482 UpdateCronAction() {
1483 super("update_cron");
1484 shortDescription = "Update application cron jobs.";
1486 @Override
1487 public void execute() {
1488 admin.updateCron();
1489 shortDescription = "Update application cron jobs.";
1491 @Override
1492 protected List<String> getInitialHelpLines() {
1493 return ImmutableList.of(
1494 "AppCfg [options] update_cron <app-dir>",
1496 "Updates the cron jobs for the application. Updates any new, removed or changed",
1497 "cron jobs. Does not otherwise alter the running application version.");
1501 class UpdateDispatchAction extends AppCfgAction {
1502 UpdateDispatchAction() {
1503 super("update_dispatch");
1504 shortDescription = "Update the application dispatch configuration.";
1506 @Override
1507 public void execute() {
1508 admin.updateDispatch();
1510 @Override
1511 protected List<String> getInitialHelpLines() {
1512 return ImmutableList.of(
1513 "AppCfg [options] update_dispatch <app-dir>",
1515 "Updates the application dispatch configuration.",
1516 "Does not otherwise alter the running application version.");
1520 class UpdateDosAction extends AppCfgAction {
1521 UpdateDosAction() {
1522 super("update_dos");
1523 shortDescription = "Update application DoS protection configuration.";
1525 @Override
1526 public void execute() {
1527 admin.updateDos();
1529 @Override
1530 protected List<String> getInitialHelpLines() {
1531 return ImmutableList.of(
1532 "AppCfg [options] update_dos <app-dir>",
1534 "Updates the DoS protection configuration for the application.",
1535 "Does not otherwise alter the running application version.");
1539 class UpdateQueueAction extends AppCfgAction {
1540 UpdateQueueAction() {
1541 super("update_queues");
1542 shortDescription = "Update application task queue definitions.";
1544 @Override
1545 public void execute() {
1546 admin.updateQueues();
1548 @Override
1549 protected List<String> getInitialHelpLines() {
1550 return ImmutableList.of(
1551 "AppCfg [options] " + getNameString() + " <app-dir>",
1553 "Updates any new, removed or changed task queue definitions.",
1554 "Does not otherwise alter the running application version.");
1558 class CronInfoAction extends AppCfgAction {
1559 int numRuns = 5;
1561 CronInfoAction() {
1562 super(builtInOptions("num_runs"), "cron_info");
1563 shortDescription = "Displays times for the next several runs of each cron job.";
1565 @Override
1566 public void execute() {
1567 List<CronEntry> entries = admin.cronInfo();
1568 if (entries.isEmpty()) {
1569 System.out.println("No cron jobs defined.");
1570 } else {
1571 System.out.println(entries.size() + " cron entries defined.\n");
1572 for (CronEntry entry : entries) {
1573 System.out.println(entry.toXml());
1574 System.out.println("Next " + numRuns + " execution times:");
1575 Iterator<String> iter = entry.getNextTimesIterator();
1576 for (int i = 0; i < numRuns; i++) {
1577 System.out.println(" " + iter.next());
1579 System.out.println("");
1583 @Override
1584 protected List<String> getInitialHelpLines() {
1585 return ImmutableList.of(
1586 "AppCfg [options] cron_info <app-dir>",
1588 "Displays times for the next several runs of each cron job.");
1590 public void setNumRuns(String numberString) {
1591 try {
1592 numRuns = Integer.parseInt(numberString);
1593 } catch (NumberFormatException e) {
1594 throw new IllegalArgumentException("num_runs must be an integral number.");
1596 if (numRuns < 0) {
1597 throw new IllegalArgumentException("num_runs must be positive.");
1602 class VacuumIndexesAction extends AppCfgAction {
1603 public boolean promptUserForEachDelete = true;
1605 VacuumIndexesAction() {
1606 super(builtInOptions("force"), "vacuum_indexes");
1607 shortDescription = "Delete unused indexes from application.";
1610 @Override
1611 public void execute() {
1612 ConfirmationCallback<IndexDeleter.DeleteIndexAction> callback = null;
1613 if (promptUserForEachDelete) {
1614 callback = new ConfirmationCallback<IndexDeleter.DeleteIndexAction>() {
1615 @Override
1616 public Response confirmAction(DeleteIndexAction action) {
1617 while (true) {
1618 String prompt = "\n" + action.getPrompt() + " (N/y/a): ";
1619 System.out.print(prompt);
1620 System.out.flush();
1621 BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
1622 String response;
1623 try {
1624 response = in.readLine();
1625 } catch (IOException ioe) {
1626 response = null;
1628 response = (null == response ? "" : response.trim().toLowerCase());
1629 if ("y".equals(response)) {
1630 return Response.YES;
1632 if ("n".equals(response) || response.isEmpty()) {
1633 return Response.NO;
1635 if ("a".equals(response)) {
1636 return Response.YES_ALL;
1642 admin.vacuumIndexes(callback, new AppCfgVacuumIndexesListener());
1645 @Override
1646 protected List<String> getInitialHelpLines() {
1647 return ImmutableList.of(
1648 "AppCfg [options] vacuum_indexes <app-dir>",
1650 "Deletes indexes on the server that are not present in the local",
1651 "index configuration file. The user is prompted before each delete.");
1655 class HelpAction extends AppCfgAction {
1656 HelpAction() {
1657 super("help");
1658 shortDescription = "Print help for a specific action.";
1660 @Override
1661 public void apply() {
1662 if (getArgs().isEmpty()) {
1663 printHelp();
1664 } else {
1665 Action foundAction = Parser.lookupAction(actionsAndOptions.actions,
1666 getArgs().toArray(new String[0]), 0);
1667 if (foundAction == null) {
1668 System.out.println("No such command \"" + getArgs().get(0) + "\"\n\n");
1669 printHelp();
1670 } else {
1671 System.out.println(foundAction.getHelpString());
1672 System.out.println();
1675 System.exit(1);
1677 @Override
1678 public void execute() {
1680 @Override
1681 protected List<String> getHelpLines() {
1682 return ImmutableList.of("AppCfg help <command>",
1684 "Prints help about a specific command.",
1685 "");
1689 class DownloadAppAction extends AppCfgAction {
1690 DownloadAppAction() {
1691 super("download_app");
1692 shortDescription = "Download a previously uploaded app version.";
1694 @Override
1695 public void apply() {
1696 if (getArgs().size() != 1) {
1697 throw new IllegalArgumentException("Expected download directory"
1698 + " as an argument after download_app.");
1700 File downloadDir = new File(getArgs().get(0));
1701 if (overrideAppId == null) {
1702 throw new IllegalArgumentException("You must specify an app ID via -A or --application");
1705 if (oauth2) {
1706 authorizeOauth2(connectOptions);
1707 } else {
1708 loadCookies(connectOptions);
1711 AppDownload appDownload =
1712 new AppDownload(ServerConnectionFactory.getServerConnection(connectOptions),
1713 new AppCfgListener("download_app"));
1714 int exitCode = appDownload.download(overrideAppId,
1715 overrideModule,
1716 overrideAppVersion,
1717 downloadDir) ? 0 : 1;
1718 System.exit(exitCode);
1720 @Override
1721 public void execute() {
1723 @Override
1724 protected List<String> getInitialHelpLines() {
1725 return ImmutableList.of(
1726 "AppCfg [options] -A app_id [ -M module ] [ -V version ] download_app <out-dir>",
1728 "Download a previously-uploaded app to the specified directory. The app",
1729 "ID is specified by the \"-A\" option. The optional module is specified by the \"-M\" ",
1730 "option and the optional version is specified by the \"-V\" option.");
1734 class VersionAction extends AppCfgAction {
1735 VersionAction() {
1736 super("version");
1737 shortDescription = "Prints version information.";
1739 @Override
1740 public void apply() {
1741 System.out.println(SupportInfo.getVersionString());
1742 System.exit(0);
1744 @Override
1745 public void execute() {
1747 @Override
1748 protected List<String> getHelpLines() {
1749 return ImmutableList.of(
1750 "AppCfg version",
1752 "Prints version information.");
1756 class SetDefaultVersionAction extends AppCfgAction {
1757 SetDefaultVersionAction() {
1758 super("set_default_version");
1759 shortDescription = "Set the default serving version.";
1761 @Override
1762 public void execute() {
1763 admin.setDefaultVersion();
1765 @Override
1766 protected List<String> getInitialHelpLines() {
1767 return ImmutableList.of(
1768 "AppCfg [options] set_default_version <app-dir>",
1770 "Sets the default (serving) version of the app. Defaults to using",
1771 "the application, version and module specified in your app directory.",
1772 "Use the --application, --version and --module flags to override these",
1773 "values. The --module flag can also be a comma-delimited string of",
1774 "several modules. (ex. module1,module2,module3) In this case, the default",
1775 "version of each module will be changed to the version specified.");
1779 class ResourceLimitsInfoAction extends AppCfgAction {
1780 public ResourceLimitsInfoAction() {
1781 super("resource_limits_info");
1782 shortDescription = "Display resource limits.";
1785 @Override
1786 public void execute() {
1787 ResourceLimits resourceLimits = admin.getResourceLimits();
1788 for (String key : new TreeSet<String>(resourceLimits.keySet())) {
1789 System.out.println(key + ": " + resourceLimits.get(key));
1793 @Override
1794 protected List<String> getInitialHelpLines() {
1795 return ImmutableList.of(
1796 "AppCfg [options] resource_limits_info <app-dir>",
1798 "Displays the resource limits available to the app. An app will",
1799 "not update if any of the app's resources are larger than the",
1800 "appropriate resource limit.");
1804 class ListVersionsAction extends AppCfgAction {
1805 ListVersionsAction() {
1806 super("list_versions");
1807 shortDescription = "List the currently uploaded versions.";
1810 @Override
1811 public void execute() {
1812 String response = admin.listVersions();
1813 YamlReader yaml = new YamlReader(new StringReader(response));
1814 try {
1815 Object obj = yaml.read();
1816 if (obj != null) {
1817 @SuppressWarnings("unchecked")
1818 Map<String, ArrayList<String>> responseMap = (Map<String, ArrayList<String>>) obj;
1819 if (!responseMap.isEmpty()) {
1820 System.out.println(response);
1821 } else {
1822 System.out.println("No versions uploaded for application.");
1824 return;
1826 } catch (YamlException exc) {
1827 } catch (ClassCastException exc) {
1829 System.out.println("There was a problem retrieving the list of versions.");
1832 @Override
1833 protected List<String> getInitialHelpLines() {
1834 return ImmutableList.of(
1835 "AppCfg [options] list_versions <app-dir>",
1837 "List the currently configured versions.");
1841 class DeleteVersionAction extends AppCfgAction {
1842 DeleteVersionAction() {
1843 super("delete_version");
1844 shortDescription = "Delete the specified version.";
1847 @Override
1848 public void execute() {
1849 if (overrideAppVersion == null) {
1850 throw new IllegalArgumentException("You must specify a version ID via -V or --version");
1853 String response = admin.deleteVersion(overrideAppId,
1854 overrideModule,
1855 overrideAppVersion);
1856 System.out.println(response);
1859 @Override
1860 protected List<String> getInitialHelpLines() {
1861 return ImmutableList.of(
1862 "AppCfg [options] delete_version <app-dir> -V version [-M module]",
1864 "Deletes the specified version.");
1868 class DebugAction extends AppCfgAction {
1869 DebugAction() {
1870 super("debug");
1871 shortDescription = "Debug a vm runtime application.";
1874 @Override
1875 public void execute() {
1876 String debugResponse = admin.debugVersion();
1877 System.out.println(debugResponse);
1878 boolean done = false;
1879 int retries = 0;
1880 int nextSleep = 1000;
1881 int maxSleep = 6000;
1882 try {
1883 while (!done && retries < 20) {
1884 Map<?, ?> yaml = (Map<?, ?>) new YamlReader(admin.debugVersionState()).read();
1885 String message = (String) yaml.get("message");
1886 System.out.println(message);
1887 String state = (String) yaml.get("state");
1888 done = !state.equals("PENDING");
1889 if (!done) {
1890 try {
1891 Thread.sleep(nextSleep);
1892 } catch (InterruptedException ex) {
1894 retries++;
1895 nextSleep = nextSleep * 2;
1896 if (nextSleep > maxSleep) {
1897 nextSleep = maxSleep;
1901 } catch (YamlException ex) {
1902 System.out.println("Error waiting for debug request status: " + ex.toString());
1906 @Override
1907 protected List<String> getInitialHelpLines() {
1908 return ImmutableList.of(
1909 "AppCfg [options] -A app_id -V version [-M module] debug <app_dir>",
1911 "Configures a vm runtime version to be accessible for debugging.");
1916 class BackendsListAction extends AppCfgAction {
1917 BackendsListAction() {
1918 super("backends", "list");
1919 shortDescription = "List the currently configured backends.";
1922 @Override
1923 public void execute() {
1924 outputBackendsMessage();
1925 for (BackendsXml.Entry backend : admin.listBackends()) {
1926 System.out.println(backend);
1930 @Override
1931 protected List<String> getInitialHelpLines() {
1932 return ImmutableList.of(
1933 "AppCfg [options] backends list <app-dir>",
1935 "List the currently configured backends.");
1939 class BackendsRollbackAction extends AppCfgAction {
1940 private String backendName;
1942 BackendsRollbackAction() {
1943 super("backends", "rollback");
1944 shortDescription = "Roll back a previously in-progress update.";
1947 @Override
1948 public void apply() {
1949 super.apply();
1950 if (getArgs().size() < 1 || getArgs().size() > 2) {
1951 throw new IllegalArgumentException("Expected <app-dir> [<backend-name>]");
1952 } else if (getArgs().size() == 2) {
1953 backendName = getArgs().get(1);
1957 @Override
1958 public void execute() {
1959 outputBackendsMessage();
1960 List<String> backends;
1961 if (backendName != null) {
1962 admin.rollbackBackend(backendName);
1963 } else {
1964 admin.rollbackAllBackends();
1968 @Override
1969 protected List<String> getInitialHelpLines() {
1970 return ImmutableList.of(
1971 "AppCfg [options] backends rollback <app-dir> [<backend-name>]",
1973 "The 'backends update' command requires a server-side transaction.",
1974 "Use 'backends rollback' if you experience an error during 'backends update'",
1975 "and want to begin a new update transaction.");
1979 class BackendsUpdateAction extends AppCfgAction {
1980 private String backendName;
1982 BackendsUpdateAction() {
1983 super(builtInOptions("enable_jar_splitting", "jar_splitting_excludes", "retain_upload_dir",
1984 "compile_encoding", "disable_jar_jsps", "delete_jsps", "enable_jar_classes"),
1985 "backends", "update");
1986 shortDescription = "Update the specified backend or all backends.";
1989 @Override
1990 public void apply() {
1991 super.apply();
1992 if (getArgs().size() < 1 || getArgs().size() > 2) {
1993 throw new IllegalArgumentException("Expected <app-dir> [<backend-name>]");
1994 } else if (getArgs().size() == 2) {
1995 backendName = getArgs().get(1);
1999 @Override
2000 public void execute() {
2001 outputBackendsMessage();
2002 List<String> backends;
2003 if (backendName != null) {
2004 admin.updateBackend(backendName, new AppCfgUpdateBackendListener());
2005 } else {
2006 admin.updateAllBackends(new AppCfgUpdateBackendListener());
2010 @Override
2011 protected List<String> getInitialHelpLines() {
2012 return ImmutableList.of(
2013 "AppCfg [options] backends update <app-dir> [<backend-name>]",
2015 "Update the specified backend or all backends.");
2019 class BackendsStartAction extends AppCfgAction {
2020 private String backendName;
2022 BackendsStartAction() {
2023 super("backends", "start");
2024 shortDescription = "Start 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);
2036 @Override
2037 public void execute() {
2038 outputBackendsMessage();
2039 admin.setBackendState(backendName, BackendsXml.State.START);
2042 @Override
2043 protected List<String> getInitialHelpLines() {
2044 return ImmutableList.of(
2045 "AppCfg [options] backends start <app-dir> <backend>",
2047 "Starts the backend with the specified name.");
2051 class BackendsStopAction extends AppCfgAction {
2052 private String backendName;
2054 BackendsStopAction() {
2055 super("backends", "stop");
2056 shortDescription = "Stop the specified backend.";
2059 @Override
2060 public void apply() {
2061 super.apply();
2062 if (getArgs().size() != 2) {
2063 throw new IllegalArgumentException("Expected the backend name");
2065 backendName = getArgs().get(1);
2067 @Override
2068 public void execute() {
2069 outputBackendsMessage();
2070 admin.setBackendState(backendName, BackendsXml.State.STOP);
2073 @Override
2074 protected List<String> getInitialHelpLines() {
2075 return ImmutableList.of(
2076 "AppCfg [options] backends stop <app-dir> <backend>",
2078 "Stops the backend with the specified name.");
2082 class BackendsDeleteAction extends AppCfgAction {
2083 private String backendName;
2085 BackendsDeleteAction() {
2086 super("backends", "delete");
2087 shortDescription = "Delete the specified backend.";
2090 @Override
2091 public void apply() {
2092 super.apply();
2093 if (getArgs().size() != 2) {
2094 throw new IllegalArgumentException("Expected the backend name");
2096 backendName = getArgs().get(1);
2098 @Override
2099 public void execute() {
2100 outputBackendsMessage();
2101 admin.deleteBackend(backendName);
2104 @Override
2105 protected List<String> getInitialHelpLines() {
2106 return ImmutableList.of(
2107 "AppCfg [options] backends delete",
2109 "Deletes the specified backend.");
2113 class BackendsConfigureAction extends AppCfgAction {
2114 private String backendName;
2116 BackendsConfigureAction() {
2117 super("backends", "configure");
2118 shortDescription = "Configure the specified backend.";
2121 @Override
2122 public void apply() {
2123 super.apply();
2124 if (getArgs().size() != 2) {
2125 throw new IllegalArgumentException("Expected the backend name");
2127 backendName = getArgs().get(1);
2129 @Override
2130 public void execute() {
2131 outputBackendsMessage();
2132 admin.configureBackend(backendName);
2135 @Override
2136 protected List<String> getInitialHelpLines() {
2137 return ImmutableList.of(
2138 "AppCfg [options] backends configure <app-dir> <backend>",
2140 "Updates the configuration of the backend with the specified name, without",
2141 "stopping instances that are currently running. Only valid for certain",
2142 "settings (instances, options: failfast, options: public).");
2147 * This is a catchall for the case where the user enters "appcfg.sh
2148 * backends app-dir sub-command" rather than "appcfg.sh backends
2149 * sub-command app-dir". It was added to maintain compatibility
2150 * with Python. It simply remaps the arguments and dispatches the
2151 * appropriate action.
2153 class BackendsAction extends AppCfgAction {
2154 private AppCfgAction subAction;
2156 BackendsAction() {
2157 super("backends");
2160 @Override
2161 public void apply() {
2162 super.apply();
2163 if (getArgs().size() < 2) {
2164 throw new IllegalArgumentException("Expected backends <app-dir> <sub-command> [...]");
2167 String dir = getArgs().get(0);
2168 String subCommand = getArgs().get(1);
2169 subAction = (AppCfgAction) Parser.lookupAction(actionsAndOptions.actions,
2170 new String[] {"backends", subCommand},
2172 if (subAction instanceof BackendsAction) {
2173 throw new IllegalArgumentException("Unknown backends subcommand.");
2175 List<String> newArgs = new ArrayList<String>();
2176 newArgs.add(dir);
2177 newArgs.addAll(getArgs().subList(2, getArgs().size()));
2178 subAction.setArgs(newArgs);
2179 subAction.apply();
2182 @Override
2183 public void execute() {
2184 outputBackendsMessage();
2185 subAction.execute();
2188 @Override
2189 protected List<String> getHelpLines() {
2190 return ImmutableList.of(
2191 "AppCfg [options] backends list: List the currently configured backends.",
2192 "AppCfg [options] backends update: Update the specified backend or all backends.",
2193 "AppCfg [options] backends rollback: Roll back a previously in-progress update.",
2194 "AppCfg [options] backends start: Start the specified backend.",
2195 "AppCfg [options] backends stop: Stop the specified backend.",
2196 "AppCfg [options] backends delete: Delete the specified backend.",
2197 "AppCfg [options] backends configure: Configure the specified backend.");
2201 class StartModuleVersionAction extends AppCfgAction {
2202 StartModuleVersionAction() {
2203 super("start_module_version");
2204 shortDescription = "Start the specified module version.";
2207 @Override
2208 public void execute() {
2209 admin.startModuleVersion();
2212 @Override
2213 protected List<String> getInitialHelpLines() {
2214 return ImmutableList.of(
2215 "AppCfg [options] start_module_version <app-dir>",
2217 "Starts the specified module version.");
2221 class StopModuleVersionAction extends AppCfgAction {
2222 StopModuleVersionAction() {
2223 super("stop_module_version");
2224 shortDescription = "Stop the specified module version.";
2227 @Override
2228 public void execute() {
2229 admin.stopModuleVersion();
2232 @Override
2233 protected List<String> getInitialHelpLines() {
2234 return ImmutableList.of(
2235 "AppCfg [options] stop_module_version <app-dir>",
2237 "Stops the specified module version.");
2241 class MigrateTrafficAction extends AppCfgAction {
2242 MigrateTrafficAction() {
2243 super("migrate_traffic");
2244 shortDescription = "Change the default version, but more gently than set_default_version.";
2247 @Override
2248 public void execute() {
2249 admin.migrateTraffic();
2252 @Override
2253 protected List<String> getInitialHelpLines() {
2254 return ImmutableList.of(
2255 "AppCfg [options] migrate_traffic <app-dir>",
2258 "Changes the default version, but more gently than set_default_version.");
2262 class StagingAction extends AppCfgAction {
2263 private File stagingDir;
2264 private boolean useRemoteResourceLimits = false;
2265 private boolean useQuickstart = false;
2267 StagingAction() {
2268 super(builtInOptions("enable_jar_splitting", "use_remote_resource_limits", "quickstart",
2269 "jar_splitting_excludes", "retain_upload_dir", "compile_encoding", "disable_jar_jsps",
2270 "delete_jsps", "enable_jar_classes"), "stage");
2271 shortDescription = "Generate a deploy-ready application directory";
2274 @Override
2275 public void apply() {
2276 super.apply();
2277 if (getArgs().size() != 2) {
2278 throw new IllegalArgumentException("Expected <app-dir> <staging-dir>");
2281 stagingDir = new File(getArgs().get(1));
2284 @Override
2285 public void execute() {
2286 connectOptions.setRetainUploadDir(true);
2287 if (useRemoteResourceLimits) {
2288 admin.stageApplicationWithRemoteResourceLimits(stagingDir);
2289 } else {
2290 admin.stageApplicationWithDefaultResourceLimits(stagingDir);
2294 @Override
2295 protected List<String> getInitialHelpLines() {
2296 return ImmutableList.of(
2297 "AppCfg [options] stage <app-dir> <staging-dir>",
2299 "Generate a deploy-ready application directory");
2302 @Override
2303 protected boolean requiresAuth() {
2304 return useRemoteResourceLimits;
2308 private static class AppCfgListener implements UpdateListener {
2309 private final String operationName;
2311 AppCfgListener(String opName){
2312 operationName = opName;
2314 @Override
2315 public void onProgress(UpdateProgressEvent event) {
2316 System.out.println(event.getPercentageComplete() + "% " + event.getMessage());
2319 @Override
2320 public void onSuccess(UpdateSuccessEvent event) {
2321 String details = event.getDetails();
2322 if (details.length() > 0) {
2323 System.out.println();
2324 System.out.println("Details:");
2325 System.out.println(details);
2328 System.out.println();
2329 System.out.println(getSuccessSummaryMessage());
2332 @Override
2333 public void onFailure(UpdateFailureEvent event) {
2334 String details = event.getDetails();
2335 if (details.length() > 0) {
2336 System.out.println();
2337 System.out.println("Error Details:");
2338 System.out.println(details);
2341 System.out.println();
2342 String failMsg = event.getFailureMessage();
2343 System.out.println(failMsg);
2344 if (event.getCause() instanceof ClientAuthFailException) {
2345 System.out.println("Consider using the -e EMAIL option if that"
2346 + " email address is incorrect.");
2350 protected String getOperationName() {
2351 return operationName;
2354 protected String getSuccessSummaryMessage() {
2355 return getOperationName() + " completed successfully.";
2359 private class AppCfgUpdateModuleListener extends AppCfgListener {
2360 AppCfgUpdateModuleListener(){
2361 super("Update");
2364 @Override
2365 protected String getSuccessSummaryMessage() {
2366 return getOperationName() + " for module " + moduleName + " completed successfully.";
2370 private static class AppCfgUpdateBackendListener extends AppCfgListener {
2371 AppCfgUpdateBackendListener(){
2372 super("Update");
2376 private static class AppCfgVacuumIndexesListener extends AppCfgListener {
2377 AppCfgVacuumIndexesListener(){
2378 super("vacuum_indexes");
2382 private static class HostPort {
2383 private final String host;
2384 private final String port;
2386 public HostPort(String hostport) {
2387 int colon = hostport.indexOf(':');
2388 host = colon < 0 ? hostport : hostport.substring(0, colon);
2389 port = colon < 0 ? "" : hostport.substring(colon + 1);
2392 public String getHost() {
2393 return host;
2396 public String getPort() {
2397 return port;
2400 public boolean hasPort() {
2401 return port.length() > 0;
2405 private void validateApplicationDirectory(File war) {
2406 if (!war.exists()) {
2407 System.out.println("Unable to find the webapp directory " + war);
2408 printHelp();
2409 System.exit(1);
2410 } else if (!war.isDirectory()) {
2411 System.out.println("appcfg only accepts webapp directories, not war files.");
2412 printHelp();
2413 System.exit(1);