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
;
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
;
56 import java
.util
.Properties
;
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
;
68 * The command-line SDK tool for administration of App Engine applications.
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();
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
;
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
);
142 parser
.parseArgs(actionsAndOptions
.actions
, actionsAndOptions
.options
, cmdLineArgs
);
143 action
= (AppCfgAction
) result
.getAction();
144 validateCommandLineForEar();
147 } catch (IllegalArgumentException e
) {
148 e
.printStackTrace(logWriter
);
149 System
.out
.println("Bad argument: " + e
.getMessage());
150 System
.out
.println(action
.getHelpString());
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()) {
177 authorizeOauth2(connectOptions
);
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());
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());
220 } catch (Exception e
) {
221 System
.out
.println("Encountered a problem: " + e
.getMessage());
222 e
.printStackTrace(logWriter
);
228 boolean isOauth2EnabledByDefault() {
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
);
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
,
281 if (externalResourceDir
!= null) {
282 application
.setExternalResourceDir(externalResourceDir
);
284 application
.setListener(new UpdateListener() {
286 public void onProgress(UpdateProgressEvent event
) {
287 System
.out
.println(event
.getPercentageComplete() + "% " + event
.getMessage());
291 public void onSuccess(UpdateSuccessEvent event
) {
292 System
.out
.println("Operation complete.");
296 public void onFailure(UpdateFailureEvent event
) {
297 System
.out
.println(event
.getFailureMessage());
303 private void executeAction(AppAdminFactory factory
, Iterable
<Application
> applications
,
304 PrintWriter logWriter
, AppCfgAction executeMe
) {
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());
312 admin
= factory
.createAppAdmin(connectOptions
, application
, logWriter
);
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
);
329 admin
= factory
.createAppAdmin(connectOptions
, null, logWriter
);
332 } catch (AdminException ex
) {
333 System
.out
.println(ex
.getMessage());
334 ex
.printStackTrace(logWriter
);
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();
350 File stage
= application
.getStagingDir();
353 "Temporary staging directory was not needed, and not created for module %s%n",
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
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) {
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() {
400 public String
getPassword() {
402 options
.setUserId(loginReader
.getUsername());
403 return loginReader
.getPassword();
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());
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
));
452 WarGenerator warGen
=
453 WarGeneratorFactory
.newWarGenerator(externalResourceDirectory
, destination
);
454 String warDir
= warGen
.generateWarDirectory().getPath();
455 System
.out
.println("Successfully generated war directory at " + warDir
);
457 } catch (IOException e
) {
458 throw new RuntimeException("Unable to generate a war directory.", e
);
462 private void doPrompt() {
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.");
470 getLoginReader().doPrompt();
474 private LoginReader
getLoginReader() {
475 if (loginReader
== null) {
476 loginReader
= LoginReaderFactory
.createLoginReader(connectOptions
, passin
);
481 private static final List
<String
> generalOptionNamesInHelpOrder
=
500 private static final List
<String
> optionNamesInHelpOrder
=
501 ImmutableList
.<String
>builder().addAll(generalOptionNamesInHelpOrder
).add(
502 "enable_jar_splitting",
503 "jar_splitting_excludes",
505 "enable_jar_classes",
518 private static final List
<String
> actionNamesInHelpOrder
=
524 "start_module_version",
525 "stop_module_version",
533 "set_default_version",
535 "resource_limits_info",
543 "backends configure",
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>]");
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.");
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) {
577 public List
<String
> getHelpLines() {
578 return ImmutableList
.<String
>of(
579 " -h, --help Show the help message and exit.");
582 public void apply() {
588 new Option("s", "server", false) {
590 public List
<String
> getHelpLines() {
591 return ImmutableList
.<String
>of(
592 " -s SERVER, --server=SERVER",
593 " The server to connect to.");
596 public void apply() {
597 connectOptions
.setServer(getValue());
601 new Option("e", "email", false) {
603 public List
<String
> getHelpLines() {
604 return ImmutableList
.<String
>of(
605 " -e EMAIL, --email=EMAIL",
606 " The username to use. Will prompt if omitted.");
609 public void apply() {
610 connectOptions
.setUserId(getValue());
614 new Option("H", "host", false) {
616 public List
<String
> getHelpLines() {
617 return ImmutableList
.<String
>of(
618 " -H HOST, --host=HOST Overrides the Host header sent with all RPCs.");
621 public void apply() {
622 connectOptions
.setHost(getValue());
626 new Option("p", "proxy", false) {
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.");
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) {
648 public List
<String
> getHelpLines() {
649 return ImmutableList
.<String
>of(
650 " --proxy_https=PROXYHOST[:PORT]",
651 " Proxies HTTPS requests through the given proxy server.");
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) {
666 public void apply() {
667 connectOptions
.setSecure(false);
671 new Option(null, "ignore_bad_cert", true) {
673 public void apply() {
674 HttpsURLConnection
.setDefaultHostnameVerifier(new HostnameVerifier() {
676 public boolean verify(String hostname
, SSLSession session
) {
683 new Option(null, "no_cookies", true) {
685 public List
<String
> getHelpLines() {
686 return ImmutableList
.<String
>of(
688 " --no_cookies Do not save/load access credentials to/from disk.");
691 public void apply() {
693 connectOptions
.setUsePersistedCredentials(false);
697 new Option("f", "force", true) {
699 public List
<String
> getHelpLines() {
700 return ImmutableList
.<String
>of(
701 " -f, --force Force deletion of indexes without being prompted.");
705 if (action
instanceof VacuumIndexesAction
){
706 VacuumIndexesAction viAction
= (VacuumIndexesAction
) action
;
707 viAction
.promptUserForEachDelete
= false;
712 new Option("a", "append", true) {
714 public List
<String
> getHelpLines() {
715 return ImmutableList
.<String
>of(
716 " -a, --append Append to existing file.");
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) {
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.");
737 public void apply() {
738 if (action
instanceof RequestLogsAction
) {
739 RequestLogsAction logsAction
= (RequestLogsAction
) action
;
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) {
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");
760 public void apply() {
761 if (action
instanceof CronInfoAction
) {
762 CronInfoAction croninfoAction
= (CronInfoAction
) action
;
763 croninfoAction
.setNumRuns(getValue());
768 new Option(null, "severity", false) {
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.");
777 public void apply() {
778 RequestLogsAction logsAction
= (RequestLogsAction
) action
;
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();
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) {
801 public List
<String
> getHelpLines() {
802 return ImmutableList
.<String
>of(
803 " --include_all Include everything in log messages.");
806 public void apply() {
807 RequestLogsAction logsAction
= (RequestLogsAction
) action
;
808 logsAction
.includeAll
= true;
812 new Option(null, "sdk_root", false) {
814 public List
<String
> getHelpLines() {
815 return ImmutableList
.<String
>of(
816 " --sdk_root=root Overrides where the SDK is located.");
819 public void apply() {
820 connectOptions
.setSdkRoot(getValue());
824 new Option(null, "disable_jar_jsps", true) {
826 public List
<String
> getHelpLines() {
827 return ImmutableList
.<String
>of(
828 " --disable_jar_jsps",
829 " Do not jar the classes generated from JSPs.");
833 public void apply() {
838 new Option(null, "enable_jar_classes", true) {
840 public List
<String
> getHelpLines() {
841 return ImmutableList
.<String
>of(
842 " --enable_jar_classes",
843 " Jar the WEB-INF/classes content.");
847 public void apply() {
851 new Option(null, "delete_jsps", true) {
853 public List
<String
> getHelpLines() {
854 return ImmutableList
.<String
>of(
856 " Delete the JSP source files after compilation.");
860 public void apply() {
865 new Option(null, "enable_jar_splitting", true) {
867 public List
<String
> getHelpLines() {
868 return ImmutableList
.<String
>of(
869 " --enable_jar_splitting",
870 " Split large jar files (> 10M) into smaller fragments.");
873 public void apply() {
874 doJarSplitting
= true;
878 new Option(null, "jar_splitting_excludes", false) {
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",
888 public void apply() {
889 jarSplittingExcludeSuffixes
= new HashSet
<String
>(Arrays
.asList(getValue().split(",")));
893 new Option(null, "retain_upload_dir", true) {
895 public List
<String
> getHelpLines() {
896 return ImmutableList
.<String
>of(
897 " --retain_upload_dir",
898 " Do not delete temporary (staging) directory used in",
902 public void apply() {
903 connectOptions
.setRetainUploadDir(true);
907 new Option(null, "passin", true) {
909 public List
<String
> getHelpLines() {
910 return ImmutableList
.<String
>of(
911 " --passin Always read the login password from stdin.");
914 public void apply() {
919 new Option(null, "no_batch", true) {
921 public void apply() {
925 new Option(null, "compile_encoding", false) {
927 public List
<String
> getHelpLines() {
928 return ImmutableList
.<String
>of(
929 " --compile_encoding",
930 " The character encoding to use when compiling JSPs.");
933 public void apply() {
934 compileEncoding
= getValue();
938 new Option(null, "disable_prompt", true) {
940 public void apply() {
941 disablePrompt
= true;
945 new Option(null, "disable_update_check", true) {
947 public void apply() {
948 disableUpdateCheck
= true;
952 new Option("A", "application", false) {
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");
960 public void apply() {
961 overrideAppId
= getValue();
965 new Option(OVERRIDE_MODULE_SHORT_ARG
, OVERRIDE_MODULE_LONG_ARG
, false) {
967 public List
<String
> getHelpLines() {
968 return ImmutableList
.<String
>of(
969 " -" + OVERRIDE_MODULE_SHORT_ARG
+ " MODULE, --" + OVERRIDE_MODULE_LONG_ARG
971 " Override module from appengine-web.xml or app.yaml");
974 public void apply() {
975 overrideModule
= getValue();
979 new Option("V", "version" , false) {
981 public List
<String
> getHelpLines() {
982 return ImmutableList
.<String
>of(
983 " -V VERSION, --version=VERSION",
984 " Override (major) version from appengine-web.xml " +
988 public void apply() {
989 overrideAppVersion
= getValue();
993 new Option(null, "oauth2", true) {
995 public List
<String
> getHelpLines() {
996 return ImmutableList
.<String
>of(
997 " --oauth2 Ignored (OAuth2 is the default).");
1000 public void apply() {
1004 new Option(null, "oauth2_refresh_token", false) {
1006 public void apply() {
1007 oauth2RefreshToken
= getValue();
1012 new Option(null, "oauth2_client_id", false) {
1014 public void apply() {
1015 oauth2ClientId
= getValue();
1020 new Option(null, "oauth2_client_secret", false) {
1022 public void apply() {
1023 oauth2ClientSecret
= getValue();
1028 new Option(null, "oauth2_config_file", false) {
1030 public void apply() {
1031 final Properties props
= new Properties();
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) {
1054 new Option(null, "no_usage_reporting", true) {
1056 public List
<String
> getHelpLines() {
1057 return ImmutableList
.<String
>of(
1058 " --no_usage_reporting",
1059 " Disable usage reporting.");
1063 public void apply() {
1064 updateUsageReporting
= false;
1068 new Option(null, "use_java7", true) {
1070 public void apply() {
1074 new Option(null, "noisy", true) {
1076 public List
<String
> getHelpLines() {
1077 return ImmutableList
.<String
>of(
1079 " Log much more information about what the tool is doing.");
1083 public void apply() {
1084 Logger rootLogger
= Logger
.getLogger("");
1085 rootLogger
.getHandlers()[0].setLevel(Level
.ALL
);
1089 new Option("r", "runtime", false) {
1091 public void apply() {
1092 runtime
= getValue();
1096 new Option("R", "allow_any_runtime", true) {
1098 public void apply() {
1099 allowAnyRuntime
= true;
1103 new Option(null, EXTERNAL_RESOURCE_DIR_ARG
, false) {
1105 public void apply() {
1106 externalResourceDir
= getValue();
1110 new Option(null, GENERATE_WAR_ARG
, true) {
1112 public void apply() {
1117 new Option(null, GENERATED_WAR_DIR_ARG
, false) {
1119 public void apply() {
1121 generatedWarDir
= getValue();
1125 new Option(null, "fail_on_precompilation_error", true) {
1127 public void apply() {
1128 failOnPrecompilationError
= true;
1132 new Option(null, "ignore_endpoints_failures", true) {
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.");
1143 public void apply() {
1144 ignoreEndpointsFailures
= true;
1148 new Option(null, "no_ignore_endpoints_failures", true) {
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.");
1159 public void apply() {
1160 ignoreEndpointsFailures
= false;
1164 new Option(null, "use_remote_resource_limits", true) {
1167 public List
<String
> getHelpLines() {
1168 return ImmutableList
.<String
>of(
1169 " --use_remote_resource_limits",
1170 " Get resource limits from server when staging");
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) {
1185 public List
<String
> getHelpLines() {
1186 return ImmutableList
.<String
>of(
1187 " --enable_quickstart",
1188 " Use jetty quickstart to process servlet annotations");
1192 public void apply() {
1193 enableQuickstart
= true;
1198 private final List
<Action
> builtInActions
= Arrays
.<Action
>asList(
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(),
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(),
1227 new MigrateTrafficAction(),
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
);
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
) {
1276 AppCfgAction(List
<Option
> options
, String
... names
) {
1277 super(options
, names
);
1281 protected void setArgs(List
<String
> args
) {
1282 super.setArgs(args
);
1286 public void apply() {
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
);
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();
1306 protected List
<String
> getHelpLines() {
1307 List
<String
> helpLines
= new LinkedList
<String
>();
1308 helpLines
.addAll(getInitialHelpLines());
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());
1326 * Returns a list of Strings to be displayed as the initial lines of a help text. Subclasses
1327 * should override this method.
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() {
1340 protected boolean requiresAuth() {
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
{
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.";
1360 public void execute() {
1361 admin
.update(new AppCfgUpdateModuleListener());
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.");
1374 protected boolean isEarAction() {
1379 class RequestLogsAction
extends AppCfgAction
{
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.";
1392 public void 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);
1401 public void execute() {
1402 Reader reader
= admin
.requestLogs(numDays
,
1403 severity
>= 0 ? LogSeverity
.values()[severity
] : null, includeAll
);
1404 if (reader
== null) {
1408 BufferedReader r
= new BufferedReader(reader
);
1409 PrintWriter writer
= null;
1411 if (outputFile
.equals("-")) {
1412 writer
= new PrintWriter(System
.out
);
1414 writer
= new PrintWriter(new FileWriter(outputFile
, append
));
1417 while ((line
= r
.readLine()) != null) {
1418 writer
.println(line
);
1420 } catch (IOException e
) {
1421 throw new RuntimeException("Failed to read logs: " + e
);
1423 if (writer
!= null) {
1428 } catch (IOException e
) {
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
{
1444 shortDescription
= "Rollback an in-progress update.";
1447 public void execute() {
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.";
1467 public void execute() {
1468 admin
.updateIndexes();
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.";
1487 public void execute() {
1489 shortDescription
= "Update application cron jobs.";
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.";
1507 public void execute() {
1508 admin
.updateDispatch();
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
{
1522 super("update_dos");
1523 shortDescription
= "Update application DoS protection configuration.";
1526 public void execute() {
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.";
1545 public void execute() {
1546 admin
.updateQueues();
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
{
1562 super(builtInOptions("num_runs"), "cron_info");
1563 shortDescription
= "Displays times for the next several runs of each cron job.";
1566 public void execute() {
1567 List
<CronEntry
> entries
= admin
.cronInfo();
1568 if (entries
.isEmpty()) {
1569 System
.out
.println("No cron jobs defined.");
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("");
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
) {
1592 numRuns
= Integer
.parseInt(numberString
);
1593 } catch (NumberFormatException e
) {
1594 throw new IllegalArgumentException("num_runs must be an integral number.");
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.";
1611 public void execute() {
1612 ConfirmationCallback
<IndexDeleter
.DeleteIndexAction
> callback
= null;
1613 if (promptUserForEachDelete
) {
1614 callback
= new ConfirmationCallback
<IndexDeleter
.DeleteIndexAction
>() {
1616 public Response
confirmAction(DeleteIndexAction action
) {
1618 String prompt
= "\n" + action
.getPrompt() + " (N/y/a): ";
1619 System
.out
.print(prompt
);
1621 BufferedReader in
= new BufferedReader(new InputStreamReader(System
.in
));
1624 response
= in
.readLine();
1625 } catch (IOException ioe
) {
1628 response
= (null == response ?
"" : response
.trim().toLowerCase());
1629 if ("y".equals(response
)) {
1630 return Response
.YES
;
1632 if ("n".equals(response
) || response
.isEmpty()) {
1635 if ("a".equals(response
)) {
1636 return Response
.YES_ALL
;
1642 admin
.vacuumIndexes(callback
, new AppCfgVacuumIndexesListener());
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
{
1658 shortDescription
= "Print help for a specific action.";
1661 public void apply() {
1662 if (getArgs().isEmpty()) {
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");
1671 System
.out
.println(foundAction
.getHelpString());
1672 System
.out
.println();
1678 public void execute() {
1681 protected List
<String
> getHelpLines() {
1682 return ImmutableList
.of("AppCfg help <command>",
1684 "Prints help about a specific command.",
1689 class DownloadAppAction
extends AppCfgAction
{
1690 DownloadAppAction() {
1691 super("download_app");
1692 shortDescription
= "Download a previously uploaded app version.";
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");
1706 authorizeOauth2(connectOptions
);
1708 loadCookies(connectOptions
);
1711 AppDownload appDownload
=
1712 new AppDownload(ServerConnectionFactory
.getServerConnection(connectOptions
),
1713 new AppCfgListener("download_app"));
1714 int exitCode
= appDownload
.download(overrideAppId
,
1717 downloadDir
) ?
0 : 1;
1718 System
.exit(exitCode
);
1721 public void execute() {
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
{
1737 shortDescription
= "Prints version information.";
1740 public void apply() {
1741 System
.out
.println(SupportInfo
.getVersionString());
1745 public void execute() {
1748 protected List
<String
> getHelpLines() {
1749 return ImmutableList
.of(
1752 "Prints version information.");
1756 class SetDefaultVersionAction
extends AppCfgAction
{
1757 SetDefaultVersionAction() {
1758 super("set_default_version");
1759 shortDescription
= "Set the default serving version.";
1762 public void execute() {
1763 admin
.setDefaultVersion();
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.";
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
));
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.";
1811 public void execute() {
1812 String response
= admin
.listVersions();
1813 YamlReader yaml
= new YamlReader(new StringReader(response
));
1815 Object obj
= yaml
.read();
1817 @SuppressWarnings("unchecked")
1818 Map
<String
, ArrayList
<String
>> responseMap
= (Map
<String
, ArrayList
<String
>>) obj
;
1819 if (!responseMap
.isEmpty()) {
1820 System
.out
.println(response
);
1822 System
.out
.println("No versions uploaded for application.");
1826 } catch (YamlException exc
) {
1827 } catch (ClassCastException exc
) {
1829 System
.out
.println("There was a problem retrieving the list of versions.");
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.";
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
,
1855 overrideAppVersion
);
1856 System
.out
.println(response
);
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
{
1871 shortDescription
= "Debug a vm runtime application.";
1875 public void execute() {
1876 String debugResponse
= admin
.debugVersion();
1877 System
.out
.println(debugResponse
);
1878 boolean done
= false;
1880 int nextSleep
= 1000;
1881 int maxSleep
= 6000;
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");
1891 Thread
.sleep(nextSleep
);
1892 } catch (InterruptedException ex
) {
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());
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.";
1923 public void execute() {
1924 outputBackendsMessage();
1925 for (BackendsXml
.Entry backend
: admin
.listBackends()) {
1926 System
.out
.println(backend
);
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.";
1948 public void 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);
1958 public void execute() {
1959 outputBackendsMessage();
1960 List
<String
> backends
;
1961 if (backendName
!= null) {
1962 admin
.rollbackBackend(backendName
);
1964 admin
.rollbackAllBackends();
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.";
1990 public void 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);
2000 public void execute() {
2001 outputBackendsMessage();
2002 List
<String
> backends
;
2003 if (backendName
!= null) {
2004 admin
.updateBackend(backendName
, new AppCfgUpdateBackendListener());
2006 admin
.updateAllBackends(new AppCfgUpdateBackendListener());
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.";
2028 public void apply() {
2030 if (getArgs().size() != 2) {
2031 throw new IllegalArgumentException("Expected the backend name");
2033 backendName
= getArgs().get(1);
2037 public void execute() {
2038 outputBackendsMessage();
2039 admin
.setBackendState(backendName
, BackendsXml
.State
.START
);
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.";
2060 public void apply() {
2062 if (getArgs().size() != 2) {
2063 throw new IllegalArgumentException("Expected the backend name");
2065 backendName
= getArgs().get(1);
2068 public void execute() {
2069 outputBackendsMessage();
2070 admin
.setBackendState(backendName
, BackendsXml
.State
.STOP
);
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.";
2091 public void apply() {
2093 if (getArgs().size() != 2) {
2094 throw new IllegalArgumentException("Expected the backend name");
2096 backendName
= getArgs().get(1);
2099 public void execute() {
2100 outputBackendsMessage();
2101 admin
.deleteBackend(backendName
);
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.";
2122 public void apply() {
2124 if (getArgs().size() != 2) {
2125 throw new IllegalArgumentException("Expected the backend name");
2127 backendName
= getArgs().get(1);
2130 public void execute() {
2131 outputBackendsMessage();
2132 admin
.configureBackend(backendName
);
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
;
2161 public void 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
>();
2177 newArgs
.addAll(getArgs().subList(2, getArgs().size()));
2178 subAction
.setArgs(newArgs
);
2183 public void execute() {
2184 outputBackendsMessage();
2185 subAction
.execute();
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.";
2208 public void execute() {
2209 admin
.startModuleVersion();
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.";
2228 public void execute() {
2229 admin
.stopModuleVersion();
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.";
2248 public void execute() {
2249 admin
.migrateTraffic();
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;
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";
2275 public void apply() {
2277 if (getArgs().size() != 2) {
2278 throw new IllegalArgumentException("Expected <app-dir> <staging-dir>");
2281 stagingDir
= new File(getArgs().get(1));
2285 public void execute() {
2286 connectOptions
.setRetainUploadDir(true);
2287 if (useRemoteResourceLimits
) {
2288 admin
.stageApplicationWithRemoteResourceLimits(stagingDir
);
2290 admin
.stageApplicationWithDefaultResourceLimits(stagingDir
);
2295 protected List
<String
> getInitialHelpLines() {
2296 return ImmutableList
.of(
2297 "AppCfg [options] stage <app-dir> <staging-dir>",
2299 "Generate a deploy-ready application directory");
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
;
2315 public void onProgress(UpdateProgressEvent event
) {
2316 System
.out
.println(event
.getPercentageComplete() + "% " + event
.getMessage());
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());
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(){
2365 protected String
getSuccessSummaryMessage() {
2366 return getOperationName() + " for module " + moduleName
+ " completed successfully.";
2370 private static class AppCfgUpdateBackendListener
extends AppCfgListener
{
2371 AppCfgUpdateBackendListener(){
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() {
2396 public String
getPort() {
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
);
2410 } else if (!war
.isDirectory()) {
2411 System
.out
.println("appcfg only accepts webapp directories, not war files.");