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