1.9.30 sync.
[gae.git] / java / src / main / com / google / appengine / tools / admin / AppCfg.java
blobac043065eadacfb8e608535ad3aa66b936007671
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.api.client.googleapis.auth.oauth2.GoogleCredential;
8 import com.google.api.client.http.javanet.NetHttpTransport;
9 import com.google.api.client.json.jackson.JacksonFactory;
10 import com.google.appengine.tools.admin.AppAdmin.LogSeverity;
11 import com.google.appengine.tools.admin.AppAdminFactory.ConnectOptions;
12 import com.google.appengine.tools.admin.ClientLoginServerConnection.ClientAuthFailException;
13 import com.google.appengine.tools.admin.IndexDeleter.DeleteIndexAction;
14 import com.google.appengine.tools.development.DevAppServerMain;
15 import com.google.appengine.tools.info.SdkInfo;
16 import com.google.appengine.tools.info.SupportInfo;
17 import com.google.appengine.tools.info.UpdateCheck;
18 import com.google.appengine.tools.info.Version;
19 import com.google.appengine.tools.util.Action;
20 import com.google.appengine.tools.util.ActionsAndOptions;
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.logging.Level;
63 import java.util.logging.Logger;
64 import java.util.prefs.Preferences;
66 import javax.net.ssl.HostnameVerifier;
67 import javax.net.ssl.HttpsURLConnection;
68 import javax.net.ssl.SSLSession;
70 /**
71 * The command-line SDK tool for administration of App Engine applications.
74 public class AppCfg {
76 private static final String EXTERNAL_RESOURCE_DIR_ARG =
77 DevAppServerMain.EXTERNAL_RESOURCE_DIR_ARG;
78 private static final String GENERATE_WAR_ARG = DevAppServerMain.GENERATE_WAR_ARG;
79 private static final String GENERATED_WAR_DIR_ARG = 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;
103 private String oauth2RefreshToken = null;
104 private String oauth2ClientId = null;
105 private String oauth2ClientSecret = null;
106 private String jsonKeyPath = null;
107 private boolean useCookies = true;
108 private boolean doJarJSPs = true;
109 private boolean doJarClasses = false;
110 private boolean deleteJSPs = false;
111 private String runtime;
112 private boolean allowAnyRuntime = false;
113 private boolean disableUpdateCheck = false;
114 private boolean failOnPrecompilationError = false;
115 private boolean ignoreEndpointsFailures = true;
116 private boolean updateUsageReporting = true;
117 private boolean enableQuickstart = false;
119 public static void main(String[] args) {
120 Logging.initializeLogging();
121 new AppCfg(args);
124 protected AppCfg(String[] cmdLineArgs) {
125 this(new AppAdminFactory(), cmdLineArgs);
128 public AppCfg(AppAdminFactory factory, String[] cmdLineArgs) {
129 connectOptions = new ConnectOptions();
130 Parser parser = new Parser();
131 oauth2 = isOauth2EnabledByDefault();
133 PrintWriter logWriter;
135 try {
136 logFile = File.createTempFile("appcfg", ".log");
137 logWriter = new PrintWriter(new FileWriter(logFile), true);
138 } catch (IOException e) {
139 throw new RuntimeException("Unable to enable logging.", e);
142 try {
143 ParseResult result =
144 parser.parseArgs(actionsAndOptions.actions, actionsAndOptions.options, cmdLineArgs);
145 action = (AppCfgAction) result.getAction();
146 validateCommandLineForEar();
147 try {
148 result.applyArgs();
149 } catch (IllegalArgumentException e) {
150 e.printStackTrace(logWriter);
151 System.out.println("Bad argument: " + e.getMessage());
152 System.out.println(action.getHelpString());
153 System.exit(1);
155 if (System.getProperty("http.proxyHost") != null
156 && System.getProperty("https.proxyHost") == null) {
157 System.setProperty("https.proxyHost", System.getProperty("http.proxyHost"));
158 if (System.getProperty("http.proxyPort") != null
159 && System.getProperty("https.proxyPort") == null) {
160 System.setProperty("https.proxyPort", System.getProperty("http.proxyPort"));
164 if (applicationDirectory != null) {
165 File appDirectoryFile = new File(applicationDirectory);
166 validateApplicationDirectory(appDirectoryFile);
168 UpdateCheck updateCheck =
169 new UpdateCheck(
170 connectOptions.getServer(), appDirectoryFile, connectOptions.getSecure());
171 if (!disableUpdateCheck) {
172 updateCheck.maybePrintNagScreen(System.out);
174 updateCheck.checkJavaVersion(System.out);
176 if (action.requiresAuth()) {
177 if (oauth2) {
178 authorizeOauth2(connectOptions);
179 } else {
180 loadCookies(connectOptions);
184 factory.setBatchMode(doBatch);
186 factory.setJarClassessEnabled(doJarClasses);
187 factory.setJarJSPsEnabled(doJarJSPs);
188 factory.setDeleteJSPs(deleteJSPs);
189 factory.setJarSplittingEnabled(doJarSplitting);
190 if (jarSplittingExcludeSuffixes != null) {
191 factory.setJarSplittingExcludes(jarSplittingExcludeSuffixes);
193 if (compileEncoding != null) {
194 factory.setCompileEncoding(compileEncoding);
196 factory.setRuntime(runtime);
197 factory.setAllowAnyRuntime(allowAnyRuntime);
198 factory.setFailOnPrecompilationError(failOnPrecompilationError);
199 factory.setIgnoreEndpointsFailures(ignoreEndpointsFailures);
200 factory.setQuickstart(enableQuickstart);
201 System.out.println("Reading application configuration data...");
203 Iterable<Application> applications = readApplication();
204 executeAction(factory, applications, logWriter, action);
205 System.out.println("Success.");
206 cleanStaging(applications);
208 } catch (IllegalArgumentException e) {
209 e.printStackTrace(logWriter);
210 System.out.println("Bad argument: " + e.getMessage());
211 printHelp();
212 System.exit(1);
213 } catch (AppEngineConfigException e) {
214 e.printStackTrace(logWriter);
215 System.out.println("Bad configuration: " + e.getMessage());
216 if (e.getCause() != null) {
217 System.out.println(" Caused by: " + e.getCause().getMessage());
219 printLogLocation();
220 System.exit(1);
221 } catch (Exception e) {
222 System.out.println("Encountered a problem: " + e.getMessage());
223 e.printStackTrace(logWriter);
224 printLogLocation();
225 System.exit(1);
229 boolean isOauth2EnabledByDefault() {
230 return true;
233 private void validateCommandLineForEar() {
234 if (EarHelper.isEar(applicationDirectory)) {
235 if (!action.isEarAction()) {
236 throw new IllegalArgumentException(
237 "The requested action does not support EAR configurations");
239 if (overrideModule != null) {
240 throw new IllegalArgumentException(
241 "With an EAR configuration "
242 + "-"
243 + OVERRIDE_MODULE_SHORT_ARG
244 + "/"
245 + "--"
246 + OVERRIDE_MODULE_LONG_ARG
247 + " is not allowed.");
249 if (externalResourceDir != null) {
250 throw new IllegalArgumentException(
251 "With an EAR configuration " + "--" + EXTERNAL_RESOURCE_DIR_ARG + " is not allowed.");
256 private Iterable<Application> readApplication() throws IOException {
257 ImmutableList.Builder<Application> resultBuilder = ImmutableList.builder();
258 if (applicationDirectory != null) {
259 if (EarHelper.isEar(applicationDirectory, false)) {
260 EarInfo earInfo =
261 EarHelper.readEarInfo(
262 applicationDirectory,
263 new File(Application.getSdkDocsDir(), "appengine-application.xsd"));
264 String applicationId =
265 overrideAppId != null
266 ? overrideAppId
267 : earInfo.getAppengineApplicationXml().getApplicationId();
268 for (WebModule webModule : earInfo.getWebModules()) {
269 System.out.println("Processing module " + webModule.getModuleName());
270 resultBuilder.add(
271 readWar(webModule.getApplicationDirectory().getAbsolutePath(), applicationId, null));
272 String contextRootWarning =
273 "Ignoring application.xml context-root element, for details see "
274 + "https://developers.google.com/appengine/docs/java/modules/#config";
275 System.out.println(contextRootWarning);
277 } else {
278 resultBuilder.add(readWar(applicationDirectory, overrideAppId, overrideModule));
281 return resultBuilder.build();
284 private Application readWar(
285 String warDirectory, String applicationIdOrNull, String moduleNameOrNull) throws IOException {
286 Application application =
287 Application.readApplication(
288 warDirectory, applicationIdOrNull, moduleNameOrNull, overrideAppVersion);
289 if (externalResourceDir != null) {
290 application.setExternalResourceDir(externalResourceDir);
292 application.setListener(
293 new UpdateListener() {
294 @Override
295 public void onProgress(UpdateProgressEvent event) {
296 System.out.println(event.getPercentageComplete() + "% " + event.getMessage());
299 @Override
300 public void onSuccess(UpdateSuccessEvent event) {
301 System.out.println("Operation complete.");
304 @Override
305 public void onFailure(UpdateFailureEvent event) {
306 System.out.println(event.getFailureMessage());
309 return application;
312 private void executeAction(
313 AppAdminFactory factory,
314 Iterable<Application> applications,
315 PrintWriter logWriter,
316 AppCfgAction executeMe) {
317 try {
318 if (applications.iterator().hasNext()) {
319 boolean firstModule = true;
320 for (Application application : applications) {
321 boolean doJsps =
322 (!application.getAppEngineWebXml().getUseVm()
323 && (!application.getAppEngineWebXml().getEnv().equals("2")));
324 factory.setCompileJsps(doJsps);
325 moduleName = WebModule.getModuleName(application.getAppEngineWebXml());
326 try {
327 admin = factory.createAppAdmin(connectOptions, application, logWriter);
328 if (!firstModule) {
329 admin.getUpdateOptions().setUpdateGlobalConfigurations(false);
331 Version localVersion = SdkInfo.getLocalVersion();
332 String sdkVersion =
333 String.format(
334 "Java/%s(%s)", localVersion.getRelease(), localVersion.getTimestamp());
335 admin.getUpdateOptions().setSdkVersion(sdkVersion);
336 admin.getUpdateOptions().setUpdateUsageReporting(updateUsageReporting);
337 System.out.printf("%n%nBeginning interaction for module %s...%n", moduleName);
338 executeMe.execute();
339 } finally {
340 moduleName = null;
341 firstModule = false;
344 } else {
345 admin = factory.createAppAdmin(connectOptions, null, logWriter);
346 executeMe.execute();
348 } catch (AdminException ex) {
349 System.out.println(ex.getMessage());
350 ex.printStackTrace(logWriter);
351 printLogLocation();
352 System.exit(1);
353 } finally {
354 admin = null;
358 private void cleanStaging(Iterable<Application> applications) throws IOException {
359 for (Application application : applications) {
360 if (application != null) {
361 String moduleName = WebModule.getModuleName(application.getAppEngineWebXml());
362 if (!connectOptions.getRetainUploadDir()) {
363 System.out.printf("Cleaning up temporary files for module %s...%n", moduleName);
364 application.cleanStagingDirectory();
365 } else {
366 File stage = application.getStagingDir();
367 if (stage == null) {
368 System.out.printf(
369 "Temporary staging directory was not needed, and not created for module %s%n",
370 moduleName);
371 } else {
372 System.out.printf(
373 "Temporary staging for module %s directory left in %s%n",
374 moduleName,
375 stage.getCanonicalPath());
383 * Prints a uniform message to direct the user to the given logfile for
384 * more information.
386 private void printLogLocation() {
387 if (logFile != null) {
388 System.out.println(
389 "Please see the logs [" + logFile.getAbsolutePath() + "] for further information.");
393 private String loadCookies(final ConnectOptions options) {
394 Preferences prefs = Preferences.userNodeForPackage(ServerConnection.class);
395 String prefsEmail = prefs.get("email", null);
397 if (options.getUsePersistedCredentials() && prefsEmail != null) {
398 ClientCookieManager cookies = null;
399 byte[] serializedCookies = prefs.getByteArray("cookies", null);
400 if (serializedCookies != null) {
401 try {
402 cookies =
403 (ClientCookieManager)
404 new ObjectInputStream(new ByteArrayInputStream(serializedCookies)).readObject();
405 } catch (ClassNotFoundException ex) {
406 } catch (IOException ex) {
410 if (options.getUserId() == null || prefsEmail.equals(options.getUserId())) {
411 options.setCookies(cookies);
415 options.setPasswordPrompt(
416 new AppAdminFactory.PasswordPrompt() {
417 @Override
418 public String getPassword() {
419 doPrompt();
420 options.setUserId(loginReader.getUsername());
421 return loginReader.getPassword();
424 return prefsEmail;
428 * Tries to get an OAuth2 access token and set it in the ConnectOptions.
429 * It exists with exit code 1 in case no token could be obtained.
431 private void authorizeOauth2(final ConnectOptions options){
432 Credential credential = null;
433 if (jsonKeyPath != null) {
434 credential = getServiceAccountCredential(jsonKeyPath);
435 } else {
436 OAuth2Native client =
437 new OAuth2Native(useCookies, oauth2ClientId, oauth2ClientSecret, oauth2RefreshToken);
438 credential = client.authorize();
440 if (credential != null && credential.getAccessToken() != null) {
441 options.setOauthToken(credential.getAccessToken());
442 } else {
443 System.exit(1);
447 private Credential getServiceAccountCredential(String jsonKeyPath) {
448 try (FileInputStream jsonKey = new FileInputStream(jsonKeyPath)) {
449 Credential credential =
450 GoogleCredential.fromStream(jsonKey, new NetHttpTransport(), new JacksonFactory())
451 .createScoped(Arrays.asList(OAuth2Native.OAUTH2_SCOPE));
452 credential.refreshToken();
453 return credential;
454 } catch (FileNotFoundException e) {
455 throw new RuntimeException(String.format("JSON key file does not exist: %s", jsonKeyPath), e);
456 } catch (IOException e) {
457 throw new RuntimeException(String.format("Could not read JSON key file: %s", jsonKeyPath), e);
462 * Helper function for generating a war directory based on an app.yaml file located in an external
463 * resource directory. First the command line arguments are checked to ensure that they are
464 * appropriate for war generation. If there is a problem then a {@link RuntimeException} is
465 * thrown. Otherwise a war directory is generated and its path is returned, and a success
466 * message is written to standard out.
468 * @return The path of the generated war directory.
470 private String validateArgsAndGenerateWar() {
471 if (externalResourceDir == null) {
472 throw new IllegalArgumentException(
473 "When generating a war directory --"
474 + EXTERNAL_RESOURCE_DIR_ARG
475 + " must also be specified.");
477 if (EarHelper.isEar(externalResourceDir, false)) {
478 throw new IllegalArgumentException(
479 "With an EAR configuration " + "--" + EXTERNAL_RESOURCE_DIR_ARG + " is not allowed.");
481 File externalResourceDirectory = new File(externalResourceDir);
482 if (!externalResourceDirectory.isDirectory()) {
483 throw new IllegalArgumentException(externalResourceDir + " is not an existing directory.");
485 File appYamlFile = new File(externalResourceDirectory, WarGenerator.APP_YAML);
486 if (!appYamlFile.isFile()) {
487 throw new IllegalArgumentException(appYamlFile.getPath() + " not found.");
489 File destination = (generatedWarDir == null ? null : new File(generatedWarDir));
490 try {
491 WarGenerator warGen =
492 WarGeneratorFactory.newWarGenerator(externalResourceDirectory, destination);
493 String warDir = warGen.generateWarDirectory().getPath();
494 System.out.println("Successfully generated war directory at " + warDir);
495 return warDir;
496 } catch (IOException e) {
497 throw new RuntimeException("Unable to generate a war directory.", e);
501 private void doPrompt() {
503 if (disablePrompt) {
504 System.out.println(
505 "Your authentication credentials can't be found and may have expired.\n"
506 + "Please run appcfg directly from the command line to re-establish "
507 + "your credentials.");
508 System.exit(1);
511 getLoginReader().doPrompt();
514 private LoginReader getLoginReader() {
515 if (loginReader == null) {
516 loginReader = LoginReaderFactory.createLoginReader(connectOptions, passin);
518 return loginReader;
521 private static final List<String> generalOptionNamesInHelpOrder =
522 ImmutableList.of(
523 "server",
524 "email",
525 "host",
526 "proxy",
527 "proxy_https",
528 "no_cookies",
529 "sdk_root",
530 "passin",
531 "insecure",
532 "ignore_bad_cert",
533 "application",
534 "module",
535 "version",
536 "oauth2",
537 "use_java7",
538 "noisy");
540 private static final List<String> optionNamesInHelpOrder =
541 ImmutableList.<String>builder()
542 .addAll(generalOptionNamesInHelpOrder)
543 .add(
544 "enable_jar_splitting",
545 "jar_splitting_excludes",
546 "disable_jar_jsps",
547 "enable_jar_classes",
548 "delete_jsps",
549 "retain_upload_dir",
550 "compile_encoding",
551 "num_days",
552 "severity",
553 "include_all",
554 "append",
555 "num_runs",
556 "force",
557 "no_usage_reporting")
558 .build();
560 private static final List<String> actionNamesInHelpOrder =
561 ImmutableList.of(
562 "help",
563 "download_app",
564 "request_logs",
565 "rollback",
566 "start_module_version",
567 "stop_module_version",
568 "update",
569 "update_indexes",
570 "update_cron",
571 "update_queues",
572 "update_dispatch",
573 "update_dos",
574 "version",
575 "set_default_version",
576 "cron_info",
577 "resource_limits_info",
578 "vacuum_indexes",
579 "backends list",
580 "backends update",
581 "backends rollback",
582 "backends start",
583 "backends stop",
584 "backends delete",
585 "backends configure",
586 "list_versions",
587 "delete_version");
589 private String helpText = null;
591 private void printHelp() {
592 if (helpText == null) {
593 List<String> helpLines = new LinkedList<String>();
594 helpLines.add("usage: AppCfg [options] <action> [<app-dir>] [<argument>]");
595 helpLines.add("");
596 helpLines.add("Action must be one of:");
597 for (String actionName : actionsAndOptions.actionNames) {
598 Action action = actionsAndOptions.getAction(actionName);
599 if (action != null) {
600 helpLines.add(" " + actionName + ": " + action.getShortDescription());
603 helpLines.add("Use 'help <action>' for a detailed description.");
604 helpLines.add("");
605 helpLines.add("options:");
606 for (String optionName : actionsAndOptions.optionNames) {
607 Option option = actionsAndOptions.getOption(optionName);
608 helpLines.addAll(option.getHelpLines());
610 helpText = Joiner.on("\n").join(helpLines);
612 System.out.println(helpText);
613 System.out.println();
616 private final List<Option> builtInOptions =
617 Arrays.asList(
619 new Option("h", "help", true) {
620 @Override
621 public List<String> getHelpLines() {
622 return ImmutableList.<String>of(
623 " -h, --help Show the help message and exit.");
626 @Override
627 public void apply() {
628 printHelp();
629 System.exit(1);
633 new Option("s", "server", false) {
634 @Override
635 public List<String> getHelpLines() {
636 return ImmutableList.<String>of(
637 " -s SERVER, --server=SERVER",
638 " The server to connect to.");
641 @Override
642 public void apply() {
643 connectOptions.setServer(getValue());
647 new Option("e", "email", false) {
648 @Override
649 public List<String> getHelpLines() {
650 return ImmutableList.<String>of(
651 " -e EMAIL, --email=EMAIL",
652 " The username to use. Will prompt if omitted.");
655 @Override
656 public void apply() {
657 connectOptions.setUserId(getValue());
661 new Option("H", "host", false) {
662 @Override
663 public List<String> getHelpLines() {
664 return ImmutableList.<String>of(
665 " -H HOST, --host=HOST Overrides the Host header sent with all RPCs.");
668 @Override
669 public void apply() {
670 connectOptions.setHost(getValue());
674 new Option("p", "proxy", false) {
675 @Override
676 public List<String> getHelpLines() {
677 return ImmutableList.<String>of(
678 " -p PROXYHOST[:PORT], --proxy=PROXYHOST[:PORT]",
679 " Proxies requests through the given proxy server.",
680 " If --proxy_https is also set, only HTTP will be",
681 " proxied here, otherwise both HTTP and HTTPS will.");
684 @Override
685 public void apply() {
686 HostPort hostport = new HostPort(getValue());
688 System.setProperty("http.proxyHost", hostport.getHost());
689 if (hostport.hasPort()) {
690 System.setProperty("http.proxyPort", hostport.getPort());
695 new Option(null, "proxy_https", false) {
696 @Override
697 public List<String> getHelpLines() {
698 return ImmutableList.<String>of(
699 " --proxy_https=PROXYHOST[:PORT]",
700 " Proxies HTTPS requests through the given proxy server.");
703 @Override
704 public void apply() {
705 HostPort hostport = new HostPort(getValue());
707 System.setProperty("https.proxyHost", hostport.getHost());
708 if (hostport.hasPort()) {
709 System.setProperty("https.proxyPort", hostport.getPort());
714 new Option(null, "insecure", true) {
715 @Override
716 public void apply() {
717 connectOptions.setSecure(false);
721 new Option(null, "ignore_bad_cert", true) {
722 @Override
723 public void apply() {
724 HttpsURLConnection.setDefaultHostnameVerifier(
725 new HostnameVerifier() {
726 @Override
727 public boolean verify(String hostname, SSLSession session) {
728 return true;
734 new Option(null, "no_cookies", true) {
735 @Override
736 public List<String> getHelpLines() {
737 return ImmutableList.<String>of(
739 " --no_cookies Do not save/load access credentials to/from disk.");
742 @Override
743 public void apply() {
744 useCookies = false;
745 connectOptions.setUsePersistedCredentials(false);
749 new Option("f", "force", true) {
750 @Override
751 public List<String> getHelpLines() {
752 return ImmutableList.<String>of(
753 " -f, --force Force deletion of indexes without being prompted.");
756 @Override
757 public void apply() {
758 if (action instanceof VacuumIndexesAction) {
759 VacuumIndexesAction viAction = (VacuumIndexesAction) action;
760 viAction.promptUserForEachDelete = false;
765 new Option("a", "append", true) {
766 @Override
767 public List<String> getHelpLines() {
768 return ImmutableList.<String>of(" -a, --append Append to existing file.");
771 @Override
772 public void apply() {
773 if (action instanceof RequestLogsAction) {
774 RequestLogsAction logsAction = (RequestLogsAction) action;
775 logsAction.append = true;
780 new Option("n", "num_days", false) {
781 @Override
782 public List<String> getHelpLines() {
783 return ImmutableList.<String>of(
784 " -n NUM_DAYS, --num_days=NUM_DAYS",
785 " Number of days worth of log data to get. The cut-off",
786 " point is midnight UTC. Use 0 to get all available",
787 " logs. Default is 1.");
790 @Override
791 public void apply() {
792 if (action instanceof RequestLogsAction) {
793 RequestLogsAction logsAction = (RequestLogsAction) action;
794 try {
795 logsAction.numDays = Integer.parseInt(getValue());
796 } catch (NumberFormatException e) {
797 throw new IllegalArgumentException("num_days must be an integral number.");
799 } else if (action instanceof CronInfoAction) {
800 CronInfoAction croninfoAction = (CronInfoAction) action;
801 croninfoAction.setNumRuns(getValue());
806 new Option(null, "num_runs", false) {
807 @Override
808 public List<String> getHelpLines() {
809 return ImmutableList.<String>of(
810 " -n NUM_RUNS, --num_runs=NUM_RUNS",
811 " Number of scheduled execution times to compute");
814 @Override
815 public void apply() {
816 if (action instanceof CronInfoAction) {
817 CronInfoAction croninfoAction = (CronInfoAction) action;
818 croninfoAction.setNumRuns(getValue());
823 new Option(null, "severity", false) {
824 @Override
825 public List<String> getHelpLines() {
826 return ImmutableList.<String>of(
827 " --severity=SEVERITY Severity of app-level log messages to get. The range",
828 " is 0 (DEBUG) through 4 (CRITICAL). If omitted, only",
829 " request logs are returned.");
832 @Override
833 public void apply() {
834 RequestLogsAction logsAction = (RequestLogsAction) action;
835 try {
836 int severity = Integer.parseInt(getValue());
837 int maxSeverity = LogSeverity.CRITICAL.ordinal();
838 if (severity < 0 || severity > maxSeverity) {
839 throw new IllegalArgumentException(
840 "severity must be between 0 and " + maxSeverity);
842 logsAction.severity = severity;
843 } catch (NumberFormatException e) {
844 for (Enum<LogSeverity> severity : LogSeverity.values()) {
845 if (getValue().equalsIgnoreCase(severity.toString())) {
846 logsAction.severity = severity.ordinal();
847 return;
850 throw new IllegalArgumentException(
851 "severity must be an integral "
852 + "number 0-4, or one of DEBUG, INFO, WARN, ERROR, CRITICAL");
857 new Option(null, "include_all", true) {
858 @Override
859 public List<String> getHelpLines() {
860 return ImmutableList.<String>of(
861 " --include_all Include everything in log messages.");
864 @Override
865 public void apply() {
866 RequestLogsAction logsAction = (RequestLogsAction) action;
867 logsAction.includeAll = true;
871 new Option(null, "sdk_root", false) {
872 @Override
873 public List<String> getHelpLines() {
874 return ImmutableList.<String>of(
875 " --sdk_root=root Overrides where the SDK is located.");
878 @Override
879 public void apply() {
880 connectOptions.setSdkRoot(getValue());
884 new Option(null, "disable_jar_jsps", true) {
885 @Override
886 public List<String> getHelpLines() {
887 return ImmutableList.<String>of(
888 " --disable_jar_jsps",
889 " Do not jar the classes generated from JSPs.");
892 @Override
893 public void apply() {
894 doJarJSPs = false;
898 new Option(null, "enable_jar_classes", true) {
899 @Override
900 public List<String> getHelpLines() {
901 return ImmutableList.<String>of(
902 " --enable_jar_classes",
903 " Jar the WEB-INF/classes content.");
906 @Override
907 public void apply() {
908 doJarClasses = true;
911 new Option(null, "delete_jsps", true) {
912 @Override
913 public List<String> getHelpLines() {
914 return ImmutableList.<String>of(
915 " --delete_jsps",
916 " Delete the JSP source files after compilation.");
919 @Override
920 public void apply() {
921 deleteJSPs = true;
925 new Option(null, "enable_jar_splitting", true) {
926 @Override
927 public List<String> getHelpLines() {
928 return ImmutableList.<String>of(
929 " --enable_jar_splitting",
930 " Split large jar files (> 10M) into smaller fragments.");
933 @Override
934 public void apply() {
935 doJarSplitting = true;
939 new Option(null, "jar_splitting_excludes", false) {
940 @Override
941 public List<String> getHelpLines() {
942 return ImmutableList.<String>of(
943 " --jar_splitting_excludes=SUFFIXES",
944 " When --enable-jar-splitting is set, files that match",
945 " the list of comma separated SUFFIXES will be excluded",
946 " from all jars.");
949 @Override
950 public void apply() {
951 jarSplittingExcludeSuffixes =
952 new HashSet<String>(Arrays.asList(getValue().split(",")));
956 new Option(null, "retain_upload_dir", true) {
957 @Override
958 public List<String> getHelpLines() {
959 return ImmutableList.<String>of(
960 " --retain_upload_dir",
961 " Do not delete temporary (staging) directory used in",
962 " uploading.");
965 @Override
966 public void apply() {
967 connectOptions.setRetainUploadDir(true);
971 new Option(null, "passin", true) {
972 @Override
973 public List<String> getHelpLines() {
974 return ImmutableList.<String>of(
975 " --passin Always read the login password from stdin.");
978 @Override
979 public void apply() {
980 passin = true;
984 new Option(null, "no_batch", true) {
985 @Override
986 public void apply() {
987 doBatch = false;
990 new Option(null, "compile_encoding", false) {
991 @Override
992 public List<String> getHelpLines() {
993 return ImmutableList.<String>of(
994 " --compile_encoding",
995 " The character encoding to use when compiling JSPs.");
998 @Override
999 public void apply() {
1000 compileEncoding = getValue();
1004 new Option(null, "disable_prompt", true) {
1005 @Override
1006 public void apply() {
1007 disablePrompt = true;
1011 new Option(null, "disable_update_check", true) {
1012 @Override
1013 public void apply() {
1014 disableUpdateCheck = true;
1018 new Option("A", "application", false) {
1019 @Override
1020 public List<String> getHelpLines() {
1021 return ImmutableList.<String>of(
1022 " -A APP_ID, --application=APP_ID",
1024 + "Override application id from appengine-web.xml or app.yaml");
1027 @Override
1028 public void apply() {
1029 overrideAppId = getValue();
1033 new Option(OVERRIDE_MODULE_SHORT_ARG, OVERRIDE_MODULE_LONG_ARG, false) {
1034 @Override
1035 public List<String> getHelpLines() {
1036 return ImmutableList.<String>of(
1037 " -"
1038 + OVERRIDE_MODULE_SHORT_ARG
1039 + " MODULE, --"
1040 + OVERRIDE_MODULE_LONG_ARG
1041 + "=MODULE",
1042 " Override module from appengine-web.xml or app.yaml");
1045 @Override
1046 public void apply() {
1047 overrideModule = getValue();
1051 new Option("V", "version", false) {
1052 @Override
1053 public List<String> getHelpLines() {
1054 return ImmutableList.<String>of(
1055 " -V VERSION, --version=VERSION",
1056 " Override (major) version from appengine-web.xml "
1057 + "or app.yaml");
1060 @Override
1061 public void apply() {
1062 overrideAppVersion = getValue();
1066 new Option(null, "oauth2", true) {
1067 @Override
1068 public List<String> getHelpLines() {
1069 return ImmutableList.<String>of(
1070 " --oauth2 Ignored (OAuth2 is the default).");
1073 @Override
1074 public void apply() {}
1077 new Option(null, "oauth2_refresh_token", false) {
1078 @Override
1079 public void apply() {
1080 oauth2RefreshToken = getValue();
1081 useCookies = false;
1085 new Option(null, "oauth2_client_id", false) {
1086 @Override
1087 public void apply() {
1088 oauth2ClientId = getValue();
1089 useCookies = false;
1093 new Option(null, "oauth2_client_secret", false) {
1094 @Override
1095 public void apply() {
1096 oauth2ClientSecret = getValue();
1097 useCookies = false;
1101 new Option(null, "oauth2_config_file", false) {
1102 @Override
1103 public void apply() {
1104 final Properties props = new Properties();
1105 try {
1106 props.load(new FileInputStream(getValue()));
1107 } catch (FileNotFoundException e) {
1108 throw new RuntimeException(
1109 String.format("OAuth2 configuration file does not exist: %s", getValue()), e);
1110 } catch (IOException e) {
1111 throw new RuntimeException(
1112 String.format("Could not read OAuth2 configuration file: %s", getValue()), e);
1115 oauth2RefreshToken = props.getProperty("oauth2_refresh_token");
1116 oauth2ClientId = props.getProperty("oauth2_client_id");
1117 oauth2ClientSecret = props.getProperty("oauth2_client_secret");
1119 if (oauth2RefreshToken != null
1120 || oauth2ClientId != null
1121 || oauth2ClientSecret != null) {
1122 useCookies = false;
1127 new Option(null, "service_account_json_key_file", false) {
1128 @Override
1129 public void apply() {
1130 jsonKeyPath = getValue();
1131 useCookies = false;
1135 new Option(null, "no_usage_reporting", true) {
1136 @Override
1137 public List<String> getHelpLines() {
1138 return ImmutableList.<String>of(
1139 " --no_usage_reporting", " Disable usage reporting.");
1142 @Override
1143 public void apply() {
1144 updateUsageReporting = false;
1148 new Option(null, "use_java7", true) {
1149 @Override
1150 public void apply() {
1154 new Option(null, "noisy", true) {
1155 @Override
1156 public List<String> getHelpLines() {
1157 return ImmutableList.<String>of(
1158 " --noisy",
1160 + "Log much more information about what the tool is doing.");
1163 @Override
1164 public void apply() {
1165 Logger rootLogger = Logger.getLogger("");
1166 rootLogger.getHandlers()[0].setLevel(Level.ALL);
1170 new Option("r", "runtime", false) {
1171 @Override
1172 public void apply() {
1173 runtime = getValue();
1177 new Option("R", "allow_any_runtime", true) {
1178 @Override
1179 public void apply() {
1180 allowAnyRuntime = true;
1184 new Option(null, EXTERNAL_RESOURCE_DIR_ARG, false) {
1185 @Override
1186 public void apply() {
1187 externalResourceDir = getValue();
1191 new Option(null, GENERATE_WAR_ARG, true) {
1192 @Override
1193 public void apply() {
1194 generateWar = true;
1198 new Option(null, GENERATED_WAR_DIR_ARG, false) {
1199 @Override
1200 public void apply() {
1201 generateWar = true;
1202 generatedWarDir = getValue();
1206 new Option(null, "fail_on_precompilation_error", true) {
1207 @Override
1208 public void apply() {
1209 failOnPrecompilationError = true;
1213 new Option(null, "ignore_endpoints_failures", true) {
1214 @Override
1215 public List<String> getHelpLines() {
1216 return ImmutableList.<String>of(
1217 " --ignore_endpoints_failures",
1218 " When uploading an app that uses Google Cloud Endpoints,"
1219 + " "
1220 + "if there's a a failure to update the configuration on the "
1221 + " "
1222 + "Endpoints server, deployment should still complete.");
1225 @Override
1226 public void apply() {
1227 ignoreEndpointsFailures = true;
1231 new Option(null, "no_ignore_endpoints_failures", true) {
1232 @Override
1233 public List<String> getHelpLines() {
1234 return ImmutableList.<String>of(
1235 " --no_ignore_endpoints_failures",
1236 " When uploading an app that uses Google Cloud Endpoints,"
1237 + " "
1238 + "if there's a a failure to update the configuration on the "
1239 + " "
1240 + "Endpoints server, deployment should fail and rollback.");
1243 @Override
1244 public void apply() {
1245 ignoreEndpointsFailures = false;
1249 new Option(null, "use_remote_resource_limits", true) {
1251 @Override
1252 public List<String> getHelpLines() {
1253 return ImmutableList.<String>of(
1254 " --use_remote_resource_limits",
1255 " Get resource limits from server when staging");
1258 @Override
1259 public void apply() {
1260 if (action instanceof StagingAction) {
1261 StagingAction stagingAction = (StagingAction) action;
1262 stagingAction.useRemoteResourceLimits = true;
1267 new Option(null, "enable_quickstart", true) {
1269 @Override
1270 public List<String> getHelpLines() {
1271 return ImmutableList.<String>of(
1272 " --enable_quickstart",
1273 " Use jetty quickstart to process servlet annotations");
1276 @Override
1277 public void apply() {
1278 enableQuickstart = true;
1282 private final List<Action> builtInActions =
1283 Arrays.<Action>asList(
1284 new UpdateAction(),
1285 new RequestLogsAction(),
1286 new RollbackAction(),
1287 new UpdateIndexesAction(),
1288 new UpdateCronAction(),
1289 new UpdateDispatchAction(),
1290 new UpdateDosAction(),
1291 new UpdateQueueAction(),
1292 new CronInfoAction(),
1293 new VacuumIndexesAction(),
1294 new HelpAction(),
1295 new DownloadAppAction(),
1296 new VersionAction(),
1297 new SetDefaultVersionAction(),
1298 new ResourceLimitsInfoAction(),
1299 new StartModuleVersionAction(),
1300 new StopModuleVersionAction(),
1301 new BackendsListAction(),
1302 new BackendsRollbackAction(),
1303 new BackendsUpdateAction(),
1304 new BackendsStartAction(),
1305 new BackendsStopAction(),
1306 new BackendsDeleteAction(),
1307 new BackendsConfigureAction(),
1308 new BackendsAction(),
1309 new ListVersionsAction(),
1310 new DeleteVersionAction(),
1311 new DebugAction(),
1312 new MigrateTrafficAction(),
1313 new StagingAction());
1315 private Map<String, Option> builtInOptionMap;
1317 private List<Option> builtInOptions(String... optionNames) {
1318 if (builtInOptionMap == null) {
1319 builtInOptionMap = new HashMap<String, Option>(builtInOptions.size());
1320 for (Option option : builtInOptions) {
1321 builtInOptionMap.put(option.getLongName(), option);
1324 List<Option> options = new LinkedList<Option>();
1325 for (String name : optionNames) {
1326 Option option = builtInOptionMap.get(name);
1327 if (option != null) {
1328 options.add(option);
1331 return options;
1334 private final ActionsAndOptions actionsAndOptions = buildActionsAndOptions();
1336 private ActionsAndOptions buildActionsAndOptions() {
1337 ActionsAndOptions actionsAndOptions = getBuiltInActionsAndOptions();
1338 return actionsAndOptions;
1342 * Builds the collection of built-in Actions and Options.
1344 private ActionsAndOptions getBuiltInActionsAndOptions() {
1345 ActionsAndOptions actionsAndOptions = new ActionsAndOptions();
1346 actionsAndOptions.actions = builtInActions;
1347 actionsAndOptions.actionNames = actionNamesInHelpOrder;
1348 actionsAndOptions.options = builtInOptions;
1349 actionsAndOptions.optionNames = optionNamesInHelpOrder;
1350 actionsAndOptions.generalOptionNames = generalOptionNamesInHelpOrder;
1351 return actionsAndOptions;
1354 abstract class AppCfgAction extends Action {
1356 AppCfgAction(String... names) {
1357 this(null, names);
1360 AppCfgAction(List<Option> options, String... names) {
1361 super(options, names);
1364 @Override
1365 protected void setArgs(List<String> args) {
1366 super.setArgs(args);
1369 @Override
1370 public void apply() {
1371 if (generateWar) {
1372 applicationDirectory = validateArgsAndGenerateWar();
1373 List<String> args = getArgs();
1374 List<String> newArgs = new ArrayList<String>(args.size() + 1);
1375 newArgs.add(applicationDirectory);
1376 newArgs.addAll(args);
1377 setArgs(newArgs);
1378 } else {
1379 if (getArgs().size() < 1) {
1380 throw new IllegalArgumentException(
1381 "Expected the application directory" + " as an argument after the action name.");
1383 applicationDirectory = getArgs().get(0);
1384 validateCommandLineForEar();
1388 public abstract void execute();
1390 @Override
1391 protected List<String> getHelpLines() {
1392 List<String> helpLines = new LinkedList<String>();
1393 helpLines.addAll(getInitialHelpLines());
1394 helpLines.add("");
1395 helpLines.add("Options:");
1396 for (String optionName : actionsAndOptions.generalOptionNames) {
1397 Option option = actionsAndOptions.getOption(optionName);
1398 if (option != null) {
1399 helpLines.addAll(option.getHelpLines());
1402 if (extraOptions != null) {
1403 for (Option option : extraOptions) {
1404 helpLines.addAll(option.getHelpLines());
1407 return helpLines;
1411 * Returns a list of Strings to be displayed as the initial lines of a help text. Subclasses
1412 * should override this method.
1413 * <p>
1414 * The text returned by this method should describe the base Action without any of its options.
1415 * Text describing the options will be added in lines below this text.
1417 protected List<String> getInitialHelpLines() {
1418 return ImmutableList.of();
1421 protected boolean isEarAction() {
1422 return false;
1425 protected boolean requiresAuth() {
1426 return true;
1429 protected void outputBackendsMessage() {
1430 System.out.println(
1431 "Warning: This application uses Backends, a deprecated feature that "
1432 + "has been replaced by Modules, which offers additional functionality. Please "
1433 + "convert your backends to modules as described at: https://developers.google.com/"
1434 + "appengine/docs/java/modules/converting.");
1438 class UpdateAction extends AppCfgAction {
1439 UpdateAction() {
1440 super(
1441 builtInOptions(
1442 "enable_jar_splitting",
1443 "jar_splitting_excludes",
1444 "retain_upload_dir",
1445 "compile_encoding",
1446 "disable_jar_jsps",
1447 "delete_jsps",
1448 "enable_jar_classes"),
1449 "update");
1450 shortDescription = "Create or update an app version.";
1453 @Override
1454 public void execute() {
1455 admin.update(new AppCfgUpdateModuleListener());
1458 @Override
1459 protected List<String> getInitialHelpLines() {
1460 return ImmutableList.of(
1461 "AppCfg [options] update <app-dir>",
1463 "Installs a new version of the application onto the server, as the",
1464 "default version for end users.");
1467 @Override
1468 protected boolean isEarAction() {
1469 return true;
1473 class RequestLogsAction extends AppCfgAction {
1474 String outputFile;
1475 int numDays = 1;
1476 int severity = -1;
1477 boolean includeAll = false;
1478 boolean append = false;
1480 RequestLogsAction() {
1481 super(builtInOptions("num_days", "severity", "include_all", "append"), "request_logs");
1482 shortDescription = "Write request logs in Apache common log format.";
1485 @Override
1486 public void apply() {
1487 super.apply();
1488 if (getArgs().size() != 2) {
1489 throw new IllegalArgumentException(
1490 "Expected the application directory"
1491 + " and log file as arguments after the request_logs action name.");
1493 outputFile = getArgs().get(1);
1496 @Override
1497 public void execute() {
1498 Reader reader =
1499 admin.requestLogs(
1500 numDays, severity >= 0 ? LogSeverity.values()[severity] : null, includeAll);
1501 if (reader == null) {
1502 return;
1505 BufferedReader r = new BufferedReader(reader);
1506 PrintWriter writer = null;
1507 try {
1508 if (outputFile.equals("-")) {
1509 writer = new PrintWriter(System.out);
1510 } else {
1511 writer = new PrintWriter(new FileWriter(outputFile, append));
1513 String line = null;
1514 while ((line = r.readLine()) != null) {
1515 writer.println(line);
1517 } catch (IOException e) {
1518 throw new RuntimeException("Failed to read logs: " + e);
1519 } finally {
1520 if (writer != null) {
1521 writer.close();
1523 try {
1524 r.close();
1525 } catch (IOException e) {
1530 @Override
1531 protected List<String> getInitialHelpLines() {
1532 return ImmutableList.of(
1533 "AppCfg [options] request_logs <app-dir> <output-file>",
1535 "Populates the output-file with recent logs from the application.");
1539 class RollbackAction extends AppCfgAction {
1540 RollbackAction() {
1541 super("rollback");
1542 shortDescription = "Rollback an in-progress update.";
1545 @Override
1546 public void execute() {
1547 admin.rollback();
1550 @Override
1551 protected List<String> getInitialHelpLines() {
1552 return ImmutableList.of(
1553 "AppCfg [options] rollback <app-dir>",
1555 "The 'update' command requires a server-side transaction.",
1556 "Use 'rollback' if you experience an error during 'update'",
1557 "and want to begin a new update transaction.");
1561 class UpdateIndexesAction extends AppCfgAction {
1562 UpdateIndexesAction() {
1563 super("update_indexes");
1564 shortDescription = "Update application indexes.";
1567 @Override
1568 public void execute() {
1569 admin.updateIndexes();
1572 @Override
1573 protected List<String> getInitialHelpLines() {
1574 return ImmutableList.of(
1575 "AppCfg [options] update_indexes <app-dir>",
1577 "Updates the datastore indexes for the server to add any in the current",
1578 "application directory. Does not alter the running application version, nor",
1579 "remove any existing indexes.");
1583 class UpdateCronAction extends AppCfgAction {
1584 UpdateCronAction() {
1585 super("update_cron");
1586 shortDescription = "Update application cron jobs.";
1589 @Override
1590 public void execute() {
1591 admin.updateCron();
1592 shortDescription = "Update application cron jobs.";
1595 @Override
1596 protected List<String> getInitialHelpLines() {
1597 return ImmutableList.of(
1598 "AppCfg [options] update_cron <app-dir>",
1600 "Updates the cron jobs for the application. Updates any new, removed or changed",
1601 "cron jobs. Does not otherwise alter the running application version.");
1605 class UpdateDispatchAction extends AppCfgAction {
1606 UpdateDispatchAction() {
1607 super("update_dispatch");
1608 shortDescription = "Update the application dispatch configuration.";
1611 @Override
1612 public void execute() {
1613 admin.updateDispatch();
1616 @Override
1617 protected List<String> getInitialHelpLines() {
1618 return ImmutableList.of(
1619 "AppCfg [options] update_dispatch <app-dir>",
1621 "Updates the application dispatch configuration.",
1622 "Does not otherwise alter the running application version.");
1626 class UpdateDosAction extends AppCfgAction {
1627 UpdateDosAction() {
1628 super("update_dos");
1629 shortDescription = "Update application DoS protection configuration.";
1632 @Override
1633 public void execute() {
1634 admin.updateDos();
1637 @Override
1638 protected List<String> getInitialHelpLines() {
1639 return ImmutableList.of(
1640 "AppCfg [options] update_dos <app-dir>",
1642 "Updates the DoS protection configuration for the application.",
1643 "Does not otherwise alter the running application version.");
1647 class UpdateQueueAction extends AppCfgAction {
1648 UpdateQueueAction() {
1649 super("update_queues");
1650 shortDescription = "Update application task queue definitions.";
1653 @Override
1654 public void execute() {
1655 admin.updateQueues();
1658 @Override
1659 protected List<String> getInitialHelpLines() {
1660 return ImmutableList.of(
1661 "AppCfg [options] " + getNameString() + " <app-dir>",
1663 "Updates any new, removed or changed task queue definitions.",
1664 "Does not otherwise alter the running application version.");
1668 class CronInfoAction extends AppCfgAction {
1669 int numRuns = 5;
1671 CronInfoAction() {
1672 super(builtInOptions("num_runs"), "cron_info");
1673 shortDescription = "Displays times for the next several runs of each cron job.";
1676 @Override
1677 public void execute() {
1678 List<CronEntry> entries = admin.cronInfo();
1679 if (entries.isEmpty()) {
1680 System.out.println("No cron jobs defined.");
1681 } else {
1682 System.out.println(entries.size() + " cron entries defined.\n");
1683 for (CronEntry entry : entries) {
1684 System.out.println(entry.toXml());
1685 System.out.println("Next " + numRuns + " execution times:");
1686 Iterator<String> iter = entry.getNextTimesIterator();
1687 for (int i = 0; i < numRuns; i++) {
1688 System.out.println(" " + iter.next());
1690 System.out.println("");
1695 @Override
1696 protected List<String> getInitialHelpLines() {
1697 return ImmutableList.of(
1698 "AppCfg [options] cron_info <app-dir>",
1700 "Displays times for the next several runs of each cron job.");
1703 public void setNumRuns(String numberString) {
1704 try {
1705 numRuns = Integer.parseInt(numberString);
1706 } catch (NumberFormatException e) {
1707 throw new IllegalArgumentException("num_runs must be an integral number.");
1709 if (numRuns < 0) {
1710 throw new IllegalArgumentException("num_runs must be positive.");
1715 class VacuumIndexesAction extends AppCfgAction {
1716 public boolean promptUserForEachDelete = true;
1718 VacuumIndexesAction() {
1719 super(builtInOptions("force"), "vacuum_indexes");
1720 shortDescription = "Delete unused indexes from application.";
1723 @Override
1724 public void execute() {
1725 ConfirmationCallback<IndexDeleter.DeleteIndexAction> callback = null;
1726 if (promptUserForEachDelete) {
1727 callback =
1728 new ConfirmationCallback<IndexDeleter.DeleteIndexAction>() {
1729 @Override
1730 public Response confirmAction(DeleteIndexAction action) {
1731 while (true) {
1732 String prompt = "\n" + action.getPrompt() + " (N/y/a): ";
1733 System.out.print(prompt);
1734 System.out.flush();
1735 BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
1736 String response;
1737 try {
1738 response = in.readLine();
1739 } catch (IOException ioe) {
1740 response = null;
1742 response = (null == response ? "" : response.trim().toLowerCase());
1743 if ("y".equals(response)) {
1744 return Response.YES;
1746 if ("n".equals(response) || response.isEmpty()) {
1747 return Response.NO;
1749 if ("a".equals(response)) {
1750 return Response.YES_ALL;
1756 admin.vacuumIndexes(callback, new AppCfgVacuumIndexesListener());
1759 @Override
1760 protected List<String> getInitialHelpLines() {
1761 return ImmutableList.of(
1762 "AppCfg [options] vacuum_indexes <app-dir>",
1764 "Deletes indexes on the server that are not present in the local",
1765 "index configuration file. The user is prompted before each delete.");
1769 class HelpAction extends AppCfgAction {
1770 HelpAction() {
1771 super("help");
1772 shortDescription = "Print help for a specific action.";
1775 @Override
1776 public void apply() {
1777 if (getArgs().isEmpty()) {
1778 printHelp();
1779 } else {
1780 Action foundAction =
1781 Parser.lookupAction(actionsAndOptions.actions, getArgs().toArray(new String[0]), 0);
1782 if (foundAction == null) {
1783 System.out.println("No such command \"" + getArgs().get(0) + "\"\n\n");
1784 printHelp();
1785 } else {
1786 System.out.println(foundAction.getHelpString());
1787 System.out.println();
1790 System.exit(1);
1793 @Override
1794 public void execute() {
1797 @Override
1798 protected List<String> getHelpLines() {
1799 return ImmutableList.of(
1800 "AppCfg help <command>", "", "Prints help about a specific command.", "");
1804 class DownloadAppAction extends AppCfgAction {
1805 DownloadAppAction() {
1806 super("download_app");
1807 shortDescription = "Download a previously uploaded app version.";
1810 @Override
1811 public void apply() {
1812 if (getArgs().size() != 1) {
1813 throw new IllegalArgumentException(
1814 "Expected download directory" + " as an argument after download_app.");
1816 File downloadDir = new File(getArgs().get(0));
1817 if (overrideAppId == null) {
1818 throw new IllegalArgumentException("You must specify an app ID via -A or --application");
1821 if (oauth2) {
1822 authorizeOauth2(connectOptions);
1823 } else {
1824 loadCookies(connectOptions);
1827 AppDownload appDownload =
1828 new AppDownload(
1829 ServerConnectionFactory.getServerConnection(connectOptions),
1830 new AppCfgListener("download_app"));
1831 int exitCode =
1832 appDownload.download(overrideAppId, overrideModule, overrideAppVersion, downloadDir)
1834 : 1;
1835 System.exit(exitCode);
1838 @Override
1839 public void execute() {
1842 @Override
1843 protected List<String> getInitialHelpLines() {
1844 return ImmutableList.of(
1845 "AppCfg [options] -A app_id [ -M module ] [ -V version ] download_app <out-dir>",
1847 "Download a previously-uploaded app to the specified directory. The app",
1848 "ID is specified by the \"-A\" option. The optional module is specified by the \"-M\" ",
1849 "option and the optional version is specified by the \"-V\" option.");
1853 class VersionAction extends AppCfgAction {
1854 VersionAction() {
1855 super("version");
1856 shortDescription = "Prints version information.";
1859 @Override
1860 public void apply() {
1861 System.out.println(SupportInfo.getVersionString());
1862 System.exit(0);
1865 @Override
1866 public void execute() {}
1868 @Override
1869 protected List<String> getHelpLines() {
1870 return ImmutableList.of("AppCfg version", "", "Prints version information.");
1874 class SetDefaultVersionAction extends AppCfgAction {
1875 SetDefaultVersionAction() {
1876 super("set_default_version");
1877 shortDescription = "Set the default serving version.";
1880 @Override
1881 public void execute() {
1882 admin.setDefaultVersion();
1885 @Override
1886 protected List<String> getInitialHelpLines() {
1887 return ImmutableList.of(
1888 "AppCfg [options] set_default_version <app-dir>",
1890 "Sets the default (serving) version of the app. Defaults to using",
1891 "the application, version and module specified in your app directory.",
1892 "Use the --application, --version and --module flags to override these",
1893 "values. The --module flag can also be a comma-delimited string of",
1894 "several modules. (ex. module1,module2,module3) In this case, the default",
1895 "version of each module will be changed to the version specified.");
1899 class ResourceLimitsInfoAction extends AppCfgAction {
1900 public ResourceLimitsInfoAction() {
1901 super("resource_limits_info");
1902 shortDescription = "Display resource limits.";
1905 @Override
1906 public void execute() {
1907 ResourceLimits resourceLimits = admin.getResourceLimits();
1908 for (String key : new TreeSet<String>(resourceLimits.keySet())) {
1909 System.out.println(key + ": " + resourceLimits.get(key));
1913 @Override
1914 protected List<String> getInitialHelpLines() {
1915 return ImmutableList.of(
1916 "AppCfg [options] resource_limits_info <app-dir>",
1918 "Displays the resource limits available to the app. An app will",
1919 "not update if any of the app's resources are larger than the",
1920 "appropriate resource limit.");
1924 class ListVersionsAction extends AppCfgAction {
1925 ListVersionsAction() {
1926 super("list_versions");
1927 shortDescription = "List the currently uploaded versions.";
1930 @Override
1931 public void execute() {
1932 String response = admin.listVersions();
1933 YamlReader yaml = new YamlReader(new StringReader(response));
1934 try {
1935 Object obj = yaml.read();
1936 if (obj != null) {
1937 @SuppressWarnings("unchecked")
1938 Map<String, ArrayList<String>> responseMap = (Map<String, ArrayList<String>>) obj;
1939 if (!responseMap.isEmpty()) {
1940 System.out.println(response);
1941 } else {
1942 System.out.println("No versions uploaded for application.");
1944 return;
1946 } catch (YamlException exc) {
1947 } catch (ClassCastException exc) {
1949 System.out.println("There was a problem retrieving the list of versions.");
1952 @Override
1953 protected List<String> getInitialHelpLines() {
1954 return ImmutableList.of(
1955 "AppCfg [options] list_versions <app-dir>",
1957 "List the currently configured versions.");
1961 class DeleteVersionAction extends AppCfgAction {
1962 DeleteVersionAction() {
1963 super("delete_version");
1964 shortDescription = "Delete the specified version.";
1967 @Override
1968 public void execute() {
1969 if (overrideAppVersion == null) {
1970 throw new IllegalArgumentException("You must specify a version ID via -V or --version");
1973 String response = admin.deleteVersion(overrideAppId, overrideModule, overrideAppVersion);
1974 System.out.println(response);
1977 @Override
1978 protected List<String> getInitialHelpLines() {
1979 return ImmutableList.of(
1980 "AppCfg [options] delete_version <app-dir> -V version [-M module]",
1982 "Deletes the specified version.");
1986 class DebugAction extends AppCfgAction {
1987 DebugAction() {
1988 super("debug");
1989 shortDescription = "Debug a vm runtime application.";
1992 @Override
1993 public void execute() {
1994 String debugResponse = admin.debugVersion();
1995 System.out.println(debugResponse);
1996 boolean done = false;
1997 int retries = 0;
1998 int nextSleep = 1000;
1999 int maxSleep = 6000;
2000 try {
2001 while (!done && retries < 20) {
2002 Map<?, ?> yaml = (Map<?, ?>) new YamlReader(admin.debugVersionState()).read();
2003 String message = (String) yaml.get("message");
2004 System.out.println(message);
2005 String state = (String) yaml.get("state");
2006 done = !state.equals("PENDING");
2007 if (!done) {
2008 try {
2009 Thread.sleep(nextSleep);
2010 } catch (InterruptedException ex) {
2012 retries++;
2013 nextSleep = nextSleep * 2;
2014 if (nextSleep > maxSleep) {
2015 nextSleep = maxSleep;
2019 } catch (YamlException ex) {
2020 System.out.println("Error waiting for debug request status: " + ex.toString());
2024 @Override
2025 protected List<String> getInitialHelpLines() {
2026 return ImmutableList.of(
2027 "AppCfg [options] -A app_id -V version [-M module] debug <app_dir>",
2029 "Configures a vm runtime version to be accessible for debugging.");
2033 class BackendsListAction extends AppCfgAction {
2034 BackendsListAction() {
2035 super("backends", "list");
2036 shortDescription = "List the currently configured backends.";
2039 @Override
2040 public void execute() {
2041 outputBackendsMessage();
2042 for (BackendsXml.Entry backend : admin.listBackends()) {
2043 System.out.println(backend);
2047 @Override
2048 protected List<String> getInitialHelpLines() {
2049 return ImmutableList.of(
2050 "AppCfg [options] backends list <app-dir>",
2052 "List the currently configured backends.");
2056 class BackendsRollbackAction extends AppCfgAction {
2057 private String backendName;
2059 BackendsRollbackAction() {
2060 super("backends", "rollback");
2061 shortDescription = "Roll back a previously in-progress update.";
2064 @Override
2065 public void apply() {
2066 super.apply();
2067 if (getArgs().size() < 1 || getArgs().size() > 2) {
2068 throw new IllegalArgumentException("Expected <app-dir> [<backend-name>]");
2069 } else if (getArgs().size() == 2) {
2070 backendName = getArgs().get(1);
2074 @Override
2075 public void execute() {
2076 outputBackendsMessage();
2077 List<String> backends;
2078 if (backendName != null) {
2079 admin.rollbackBackend(backendName);
2080 } else {
2081 admin.rollbackAllBackends();
2085 @Override
2086 protected List<String> getInitialHelpLines() {
2087 return ImmutableList.of(
2088 "AppCfg [options] backends rollback <app-dir> [<backend-name>]",
2090 "The 'backends update' command requires a server-side transaction.",
2091 "Use 'backends rollback' if you experience an error during 'backends update'",
2092 "and want to begin a new update transaction.");
2096 class BackendsUpdateAction extends AppCfgAction {
2097 private String backendName;
2099 BackendsUpdateAction() {
2100 super(
2101 builtInOptions(
2102 "enable_jar_splitting",
2103 "jar_splitting_excludes",
2104 "retain_upload_dir",
2105 "compile_encoding",
2106 "disable_jar_jsps",
2107 "delete_jsps",
2108 "enable_jar_classes"),
2109 "backends",
2110 "update");
2111 shortDescription = "Update the specified backend or all backends.";
2114 @Override
2115 public void apply() {
2116 super.apply();
2117 if (getArgs().size() < 1 || getArgs().size() > 2) {
2118 throw new IllegalArgumentException("Expected <app-dir> [<backend-name>]");
2119 } else if (getArgs().size() == 2) {
2120 backendName = getArgs().get(1);
2124 @Override
2125 public void execute() {
2126 outputBackendsMessage();
2127 List<String> backends;
2128 if (backendName != null) {
2129 admin.updateBackend(backendName, new AppCfgUpdateBackendListener());
2130 } else {
2131 admin.updateAllBackends(new AppCfgUpdateBackendListener());
2135 @Override
2136 protected List<String> getInitialHelpLines() {
2137 return ImmutableList.of(
2138 "AppCfg [options] backends update <app-dir> [<backend-name>]",
2140 "Update the specified backend or all backends.");
2144 class BackendsStartAction extends AppCfgAction {
2145 private String backendName;
2147 BackendsStartAction() {
2148 super("backends", "start");
2149 shortDescription = "Start the specified backend.";
2152 @Override
2153 public void apply() {
2154 super.apply();
2155 if (getArgs().size() != 2) {
2156 throw new IllegalArgumentException("Expected the backend name");
2158 backendName = getArgs().get(1);
2161 @Override
2162 public void execute() {
2163 outputBackendsMessage();
2164 admin.setBackendState(backendName, BackendsXml.State.START);
2167 @Override
2168 protected List<String> getInitialHelpLines() {
2169 return ImmutableList.of(
2170 "AppCfg [options] backends start <app-dir> <backend>",
2172 "Starts the backend with the specified name.");
2176 class BackendsStopAction extends AppCfgAction {
2177 private String backendName;
2179 BackendsStopAction() {
2180 super("backends", "stop");
2181 shortDescription = "Stop the specified backend.";
2184 @Override
2185 public void apply() {
2186 super.apply();
2187 if (getArgs().size() != 2) {
2188 throw new IllegalArgumentException("Expected the backend name");
2190 backendName = getArgs().get(1);
2193 @Override
2194 public void execute() {
2195 outputBackendsMessage();
2196 admin.setBackendState(backendName, BackendsXml.State.STOP);
2199 @Override
2200 protected List<String> getInitialHelpLines() {
2201 return ImmutableList.of(
2202 "AppCfg [options] backends stop <app-dir> <backend>",
2204 "Stops the backend with the specified name.");
2208 class BackendsDeleteAction extends AppCfgAction {
2209 private String backendName;
2211 BackendsDeleteAction() {
2212 super("backends", "delete");
2213 shortDescription = "Delete the specified backend.";
2216 @Override
2217 public void apply() {
2218 super.apply();
2219 if (getArgs().size() != 2) {
2220 throw new IllegalArgumentException("Expected the backend name");
2222 backendName = getArgs().get(1);
2225 @Override
2226 public void execute() {
2227 outputBackendsMessage();
2228 admin.deleteBackend(backendName);
2231 @Override
2232 protected List<String> getInitialHelpLines() {
2233 return ImmutableList.of(
2234 "AppCfg [options] backends delete", "", "Deletes the specified backend.");
2238 class BackendsConfigureAction extends AppCfgAction {
2239 private String backendName;
2241 BackendsConfigureAction() {
2242 super("backends", "configure");
2243 shortDescription = "Configure the specified backend.";
2246 @Override
2247 public void apply() {
2248 super.apply();
2249 if (getArgs().size() != 2) {
2250 throw new IllegalArgumentException("Expected the backend name");
2252 backendName = getArgs().get(1);
2255 @Override
2256 public void execute() {
2257 outputBackendsMessage();
2258 admin.configureBackend(backendName);
2261 @Override
2262 protected List<String> getInitialHelpLines() {
2263 return ImmutableList.of(
2264 "AppCfg [options] backends configure <app-dir> <backend>",
2266 "Updates the configuration of the backend with the specified name, without",
2267 "stopping instances that are currently running. Only valid for certain",
2268 "settings (instances, options: failfast, options: public).");
2273 * This is a catchall for the case where the user enters "appcfg.sh
2274 * backends app-dir sub-command" rather than "appcfg.sh backends
2275 * sub-command app-dir". It was added to maintain compatibility
2276 * with Python. It simply remaps the arguments and dispatches the
2277 * appropriate action.
2279 class BackendsAction extends AppCfgAction {
2280 private AppCfgAction subAction;
2282 BackendsAction() {
2283 super("backends");
2286 @Override
2287 public void apply() {
2288 super.apply();
2289 if (getArgs().size() < 2) {
2290 throw new IllegalArgumentException("Expected backends <app-dir> <sub-command> [...]");
2293 String dir = getArgs().get(0);
2294 String subCommand = getArgs().get(1);
2295 subAction =
2296 (AppCfgAction)
2297 Parser.lookupAction(
2298 actionsAndOptions.actions, new String[] {"backends", subCommand}, 0);
2299 if (subAction instanceof BackendsAction) {
2300 throw new IllegalArgumentException("Unknown backends subcommand.");
2302 List<String> newArgs = new ArrayList<String>();
2303 newArgs.add(dir);
2304 newArgs.addAll(getArgs().subList(2, getArgs().size()));
2305 subAction.setArgs(newArgs);
2306 subAction.apply();
2309 @Override
2310 public void execute() {
2311 outputBackendsMessage();
2312 subAction.execute();
2315 @Override
2316 protected List<String> getHelpLines() {
2317 return ImmutableList.of(
2318 "AppCfg [options] backends list: List the currently configured backends.",
2319 "AppCfg [options] backends update: Update the specified backend or all backends.",
2320 "AppCfg [options] backends rollback: Roll back a previously in-progress update.",
2321 "AppCfg [options] backends start: Start the specified backend.",
2322 "AppCfg [options] backends stop: Stop the specified backend.",
2323 "AppCfg [options] backends delete: Delete the specified backend.",
2324 "AppCfg [options] backends configure: Configure the specified backend.");
2328 class StartModuleVersionAction extends AppCfgAction {
2329 StartModuleVersionAction() {
2330 super("start_module_version");
2331 shortDescription = "Start the specified module version.";
2334 @Override
2335 public void execute() {
2336 admin.startModuleVersion();
2339 @Override
2340 protected List<String> getInitialHelpLines() {
2341 return ImmutableList.of(
2342 "AppCfg [options] start_module_version <app-dir>",
2344 "Starts the specified module version.");
2348 class StopModuleVersionAction extends AppCfgAction {
2349 StopModuleVersionAction() {
2350 super("stop_module_version");
2351 shortDescription = "Stop the specified module version.";
2354 @Override
2355 public void execute() {
2356 admin.stopModuleVersion();
2359 @Override
2360 protected List<String> getInitialHelpLines() {
2361 return ImmutableList.of(
2362 "AppCfg [options] stop_module_version <app-dir>",
2364 "Stops the specified module version.");
2368 class MigrateTrafficAction extends AppCfgAction {
2369 MigrateTrafficAction() {
2370 super("migrate_traffic");
2371 shortDescription = "Change the default version, but more gently than set_default_version.";
2374 @Override
2375 public void execute() {
2376 admin.migrateTraffic();
2379 @Override
2380 protected List<String> getInitialHelpLines() {
2381 return ImmutableList.of(
2382 "AppCfg [options] migrate_traffic <app-dir>",
2384 "Changes the default version, but more gently than set_default_version.");
2388 class StagingAction extends AppCfgAction {
2389 private File stagingDir;
2390 private boolean useRemoteResourceLimits = false;
2391 private boolean useQuickstart = false;
2393 StagingAction() {
2394 super(
2395 builtInOptions(
2396 "enable_jar_splitting",
2397 "use_remote_resource_limits",
2398 "quickstart",
2399 "jar_splitting_excludes",
2400 "retain_upload_dir",
2401 "compile_encoding",
2402 "disable_jar_jsps",
2403 "delete_jsps",
2404 "enable_jar_classes"),
2405 "stage");
2406 shortDescription = "Generate a deploy-ready application directory";
2409 @Override
2410 public void apply() {
2411 super.apply();
2412 if (getArgs().size() != 2) {
2413 throw new IllegalArgumentException("Expected <app-dir> <staging-dir>");
2416 stagingDir = new File(getArgs().get(1));
2419 @Override
2420 public void execute() {
2421 connectOptions.setRetainUploadDir(true);
2422 if (useRemoteResourceLimits) {
2423 admin.stageApplicationWithRemoteResourceLimits(stagingDir);
2424 } else {
2425 admin.stageApplicationWithDefaultResourceLimits(stagingDir);
2429 @Override
2430 protected List<String> getInitialHelpLines() {
2431 return ImmutableList.of(
2432 "AppCfg [options] stage <app-dir> <staging-dir>",
2434 "Generate a deploy-ready application directory");
2437 @Override
2438 protected boolean requiresAuth() {
2439 return useRemoteResourceLimits;
2443 private static class AppCfgListener implements UpdateListener {
2444 private final String operationName;
2446 AppCfgListener(String opName) {
2447 operationName = opName;
2450 @Override
2451 public void onProgress(UpdateProgressEvent event) {
2452 System.out.println(event.getPercentageComplete() + "% " + event.getMessage());
2455 @Override
2456 public void onSuccess(UpdateSuccessEvent event) {
2457 String details = event.getDetails();
2458 if (details.length() > 0) {
2459 System.out.println();
2460 System.out.println("Details:");
2461 System.out.println(details);
2464 System.out.println();
2465 System.out.println(getSuccessSummaryMessage());
2468 @Override
2469 public void onFailure(UpdateFailureEvent event) {
2470 String details = event.getDetails();
2471 if (details.length() > 0) {
2472 System.out.println();
2473 System.out.println("Error Details:");
2474 System.out.println(details);
2477 System.out.println();
2478 String failMsg = event.getFailureMessage();
2479 System.out.println(failMsg);
2480 if (event.getCause() instanceof ClientAuthFailException) {
2481 System.out.println(
2482 "Consider using the -e EMAIL option if that" + " email address is incorrect.");
2486 protected String getOperationName() {
2487 return operationName;
2490 protected String getSuccessSummaryMessage() {
2491 return getOperationName() + " completed successfully.";
2495 private class AppCfgUpdateModuleListener extends AppCfgListener {
2496 AppCfgUpdateModuleListener() {
2497 super("Update");
2500 @Override
2501 protected String getSuccessSummaryMessage() {
2502 return getOperationName() + " for module " + moduleName + " completed successfully.";
2506 private static class AppCfgUpdateBackendListener extends AppCfgListener {
2507 AppCfgUpdateBackendListener() {
2508 super("Update");
2512 private static class AppCfgVacuumIndexesListener extends AppCfgListener {
2513 AppCfgVacuumIndexesListener() {
2514 super("vacuum_indexes");
2518 private static class HostPort {
2519 private final String host;
2520 private final String port;
2522 public HostPort(String hostport) {
2523 int colon = hostport.indexOf(':');
2524 host = colon < 0 ? hostport : hostport.substring(0, colon);
2525 port = colon < 0 ? "" : hostport.substring(colon + 1);
2528 public String getHost() {
2529 return host;
2532 public String getPort() {
2533 return port;
2536 public boolean hasPort() {
2537 return port.length() > 0;
2541 private void validateApplicationDirectory(File war) {
2542 if (!war.exists()) {
2543 System.out.println("Unable to find the webapp directory " + war);
2544 printHelp();
2545 System.exit(1);
2546 } else if (!war.isDirectory()) {
2547 System.out.println("appcfg only accepts webapp directories, not war files.");
2548 printHelp();
2549 System.exit(1);