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
.SupportInfo
;
13 import com
.google
.appengine
.tools
.info
.UpdateCheck
;
14 import com
.google
.appengine
.tools
.plugins
.ActionsAndOptions
;
15 import com
.google
.appengine
.tools
.plugins
.SDKPluginManager
;
16 import com
.google
.appengine
.tools
.plugins
.SDKRuntimePlugin
;
17 import com
.google
.appengine
.tools
.plugins
.SDKRuntimePlugin
.ApplicationDirectories
;
18 import com
.google
.appengine
.tools
.util
.Action
;
19 import com
.google
.appengine
.tools
.util
.ClientCookieManager
;
20 import com
.google
.appengine
.tools
.util
.Logging
;
21 import com
.google
.appengine
.tools
.util
.Option
;
22 import com
.google
.appengine
.tools
.util
.Parser
;
23 import com
.google
.appengine
.tools
.util
.Parser
.ParseResult
;
24 import com
.google
.appengine
.tools
.wargen
.WarGenerator
;
25 import com
.google
.appengine
.tools
.wargen
.WarGeneratorFactory
;
26 import com
.google
.apphosting
.utils
.config
.AppEngineConfigException
;
27 import com
.google
.apphosting
.utils
.config
.BackendsXml
;
28 import com
.google
.apphosting
.utils
.config
.EarHelper
;
29 import com
.google
.apphosting
.utils
.config
.EarInfo
;
30 import com
.google
.apphosting
.utils
.config
.WebModule
;
31 import com
.google
.common
.base
.Joiner
;
32 import com
.google
.common
.collect
.ImmutableList
;
34 import java
.io
.BufferedReader
;
35 import java
.io
.ByteArrayInputStream
;
37 import java
.io
.FileInputStream
;
38 import java
.io
.FileNotFoundException
;
39 import java
.io
.FileWriter
;
40 import java
.io
.IOException
;
41 import java
.io
.InputStreamReader
;
42 import java
.io
.ObjectInputStream
;
43 import java
.io
.PrintWriter
;
44 import java
.io
.Reader
;
45 import java
.util
.ArrayList
;
46 import java
.util
.Arrays
;
47 import java
.util
.HashMap
;
48 import java
.util
.HashSet
;
49 import java
.util
.Iterator
;
50 import java
.util
.LinkedList
;
51 import java
.util
.List
;
53 import java
.util
.Properties
;
55 import java
.util
.TreeSet
;
56 import java
.util
.prefs
.Preferences
;
58 import javax
.net
.ssl
.HostnameVerifier
;
59 import javax
.net
.ssl
.HttpsURLConnection
;
60 import javax
.net
.ssl
.SSLSession
;
63 * The command-line SDK tool for administration of App Engine applications.
68 public static final String USE_JAVA6_SYSTEM_PROP
= "com.google.apphosting.runtime.use_java6";
70 private static final String EXTERNAL_RESOURCE_DIR_ARG
=
71 DevAppServerMain
.EXTERNAL_RESOURCE_DIR_ARG
;
72 private static final String GENERATE_WAR_ARG
=
73 DevAppServerMain
.GENERATE_WAR_ARG
;
74 private static final String GENERATED_WAR_DIR_ARG
=
75 DevAppServerMain
.GENERATED_WAR_DIR_ARG
;
76 private static final String OVERRIDE_MODULE_SHORT_ARG
= "M";
77 private static final String OVERRIDE_MODULE_LONG_ARG
= "module";
79 private final ConnectOptions connectOptions
;
80 private String externalResourceDir
;
81 private boolean generateWar
= false;
82 private String generatedWarDir
;
83 private AppCfgAction action
;
84 private String applicationDirectory
;
85 private String moduleName
;
86 private AppAdmin admin
;
87 private boolean passin
;
88 private boolean doBatch
= true;
89 private boolean doJarSplitting
= false;
90 private Set
<String
> jarSplittingExcludeSuffixes
= null;
91 private boolean disablePrompt
= false;
92 private File logFile
= null;
93 private String compileEncoding
= null;
94 private LoginReader loginReader
= null;
95 private String overrideAppId
;
96 private String overrideModule
;
97 private String overrideAppVersion
;
98 private boolean oauth2
= false;
99 private String oauth2RefreshToken
= null;
100 private String oauth2ClientId
= null;
101 private String oauth2ClientSecret
= null;
102 private boolean useCookies
= true;
103 private boolean doJarJSPs
= true;
104 private boolean doJarClasses
= false;
105 private boolean deleteJSPs
= false;
106 private String runtime
;
107 private boolean allowAnyRuntime
= false;
108 private boolean disableUpdateCheck
= false;
109 private boolean failOnPrecompilationError
= false;
111 public static void main(String
[] args
) {
112 Logging
.initializeLogging();
116 protected AppCfg(String
[] cmdLineArgs
) {
117 this(new AppAdminFactory(), cmdLineArgs
);
120 public AppCfg(AppAdminFactory factory
, String
[] cmdLineArgs
) {
121 connectOptions
= new ConnectOptions();
122 Parser parser
= new Parser();
124 PrintWriter logWriter
;
127 logFile
= File
.createTempFile("appcfg", ".log");
128 logWriter
= new PrintWriter(new FileWriter(logFile
), true);
129 } catch (IOException e
) {
130 throw new RuntimeException("Unable to enable logging.", e
);
135 parser
.parseArgs(actionsAndOptions
.actions
, actionsAndOptions
.options
, cmdLineArgs
);
136 action
= (AppCfgAction
) result
.getAction();
137 validateCommandLineForEar();
140 } catch (IllegalArgumentException e
) {
141 e
.printStackTrace(logWriter
);
142 System
.out
.println("Bad argument: " + e
.getMessage());
143 System
.out
.println(action
.getHelpString());
146 if (System
.getProperty("http.proxyHost") != null &&
147 System
.getProperty("https.proxyHost") == null) {
148 System
.setProperty("https.proxyHost",
149 System
.getProperty("http.proxyHost"));
150 if (System
.getProperty("http.proxyPort") != null &&
151 System
.getProperty("https.proxyPort") == null) {
152 System
.setProperty("https.proxyPort",
153 System
.getProperty("http.proxyPort"));
157 if (applicationDirectory
!= null) {
158 File appDirectoryFile
= new File(applicationDirectory
);
159 validateApplicationDirectory(appDirectoryFile
);
161 UpdateCheck updateCheck
= new UpdateCheck(connectOptions
.getServer(), appDirectoryFile
,
162 connectOptions
.getSecure());
163 if (!disableUpdateCheck
) {
164 updateCheck
.maybePrintNagScreen(System
.out
);
166 updateCheck
.checkJavaVersion(System
.out
);
169 authorizeOauth2(connectOptions
);
171 loadCookies(connectOptions
);
174 factory
.setBatchMode(doBatch
);
176 factory
.setJarClassessEnabled(doJarClasses
);
177 factory
.setJarJSPsEnabled(doJarJSPs
);
178 factory
.setDeleteJSPs(deleteJSPs
);
179 factory
.setJarSplittingEnabled(doJarSplitting
);
180 if (jarSplittingExcludeSuffixes
!= null) {
181 factory
.setJarSplittingExcludes(jarSplittingExcludeSuffixes
);
183 if (compileEncoding
!= null) {
184 factory
.setCompileEncoding(compileEncoding
);
186 factory
.setRuntime(runtime
);
187 factory
.setAllowAnyRuntime(allowAnyRuntime
);
188 factory
.setFailOnPrecompilationError(failOnPrecompilationError
);
189 System
.out
.println("Reading application configuration data...");
191 Iterable
<Application
> applications
= readApplication(factory
);
192 executeAction(factory
, applications
, logWriter
, action
);
193 System
.out
.println("Success.");
194 cleanStaging(applications
);
196 } catch (IllegalArgumentException e
) {
197 e
.printStackTrace(logWriter
);
198 System
.out
.println("Bad argument: " + e
.getMessage());
201 } catch (AppEngineConfigException e
) {
202 e
.printStackTrace(logWriter
);
203 System
.out
.println("Bad configuration: " + e
.getMessage());
204 if (e
.getCause() != null) {
205 System
.out
.println(" Caused by: " + e
.getCause().getMessage());
209 } catch (Exception e
) {
210 System
.out
.println("Encountered a problem: " + e
.getMessage());
211 e
.printStackTrace(logWriter
);
217 private void validateCommandLineForEar() {
218 if (EarHelper
.isEar(applicationDirectory
)) {
219 if (!action
.isEarAction()) {
220 throw new IllegalArgumentException(
221 "The requested action does not support EAR configurations");
223 if (overrideModule
!= null) {
224 throw new IllegalArgumentException("With an EAR configuration " + "-"
225 + OVERRIDE_MODULE_SHORT_ARG
+ "/" + "--" + OVERRIDE_MODULE_LONG_ARG
226 + " is not allowed.");
228 if (externalResourceDir
!= null) {
229 throw new IllegalArgumentException("With an EAR configuration "
230 + "--" + EXTERNAL_RESOURCE_DIR_ARG
+ " is not allowed.");
235 private Iterable
<Application
> readApplication(AppAdminFactory factory
) throws IOException
{
236 ImmutableList
.Builder
<Application
> resultBuilder
= ImmutableList
.builder();
237 if (applicationDirectory
!= null) {
238 if (EarHelper
.isEar(applicationDirectory
, false)) {
239 EarInfo earInfo
= EarHelper
.readEarInfo(applicationDirectory
,
240 new File(Application
.getSdkDocsDir(), "appengine-application.xsd"));
241 String applicationId
= overrideAppId
!= null ?
242 overrideAppId
: earInfo
.getAppengineApplicationXml().getApplicationId();
243 for (WebModule webModule
: earInfo
.getWebModules()) {
244 System
.out
.println("Processing module " + webModule
.getModuleName());
245 resultBuilder
.add(readWar(factory
, webModule
.getApplicationDirectory().getAbsolutePath(),
246 applicationId
, null));
247 String contextRootWarning
=
248 "Ignoring application.xml context-root element, for details see "
249 + "https://developers.google.com/appengine/docs/java/modules/#config";
250 System
.out
.println(contextRootWarning
);
253 resultBuilder
.add(readWar(factory
, applicationDirectory
,
254 overrideAppId
, overrideModule
));
257 return resultBuilder
.build();
260 private Application
readWar(AppAdminFactory factory
, String warDirectory
,
261 String applicationIdOrNull
, String moduleNameOrNull
) throws IOException
{
262 Application application
= Application
.readApplication(warDirectory
,
266 if (externalResourceDir
!= null) {
267 application
.setExternalResourceDir(externalResourceDir
);
269 if (application
.getAppEngineWebXml().getUseVm()) {
270 factory
.setCompileJsps(false);
272 application
.setListener(new UpdateListener() {
274 public void onProgress(UpdateProgressEvent event
) {
275 System
.out
.println(event
.getPercentageComplete() + "% " + event
.getMessage());
279 public void onSuccess(UpdateSuccessEvent event
) {
280 System
.out
.println("Operation complete.");
284 public void onFailure(UpdateFailureEvent event
) {
285 System
.out
.println(event
.getFailureMessage());
291 private void executeAction(AppAdminFactory factory
, Iterable
<Application
> applications
,
292 PrintWriter logWriter
, AppCfgAction executeMe
) {
294 if (applications
.iterator().hasNext()) {
295 boolean firstModule
= true;
296 for (Application application
: applications
) {
297 moduleName
= WebModule
.getModuleName(application
.getAppEngineWebXml());
299 admin
= factory
.createAppAdmin(connectOptions
, application
, logWriter
);
301 admin
.getUpdateOptions().setUpdateGlobalConfigurations(false);
303 System
.out
.println(String
.format("\n\nBeginning interaction for module %s...",
312 admin
= factory
.createAppAdmin(connectOptions
, null, logWriter
);
315 } catch (AdminException ex
) {
316 System
.out
.println(ex
.getMessage());
317 ex
.printStackTrace(logWriter
);
325 private void cleanStaging(Iterable
<Application
> applications
) throws IOException
{
326 for (Application application
: applications
) {
327 if (application
!= null) {
328 String moduleName
= WebModule
.getModuleName(application
.getAppEngineWebXml());
329 if (!connectOptions
.getRetainUploadDir()) {
330 System
.out
.println(String
.format("Cleaning up temporary files for module %s...",
332 application
.cleanStagingDirectory();
334 File stage
= application
.getStagingDir();
336 System
.out
.println(String
.format(
337 "Temporary staging directory was not needed, and not created for module %s",
340 System
.out
.println(String
.format("Temporary staging for module %s directory left in %s",
341 moduleName
, stage
.getCanonicalPath()));
349 * Prints a uniform message to direct the user to the given logfile for
352 private void printLogLocation() {
353 if (logFile
!= null) {
354 System
.out
.println("Please see the logs [" + logFile
.getAbsolutePath() +
355 "] for further information.");
359 private String
loadCookies(final ConnectOptions options
) {
360 Preferences prefs
= Preferences
.userNodeForPackage(ServerConnection
.class);
361 String prefsEmail
= prefs
.get("email", null);
363 if (options
.getUsePersistedCredentials() && prefsEmail
!= null) {
364 ClientCookieManager cookies
= null;
365 byte[] serializedCookies
= prefs
.getByteArray("cookies", null);
366 if (serializedCookies
!= null) {
368 cookies
= (ClientCookieManager
)
369 new ObjectInputStream(
370 new ByteArrayInputStream(serializedCookies
)).readObject();
371 } catch (ClassNotFoundException ex
) {
372 } catch (IOException ex
) {
376 if (options
.getUserId() == null ||
377 prefsEmail
.equals(options
.getUserId())) {
378 options
.setCookies(cookies
);
382 options
.setPasswordPrompt(new AppAdminFactory
.PasswordPrompt() {
384 public String
getPassword() {
386 options
.setUserId(loginReader
.getUsername());
387 return loginReader
.getPassword();
394 * Tries to get an OAuth2 access token and set it in the ConnectOptions.
395 * It exists with exit code 1 in case no token could be obtained.
397 private void authorizeOauth2(final ConnectOptions options
){
398 OAuth2Native client
=
399 new OAuth2Native(useCookies
, oauth2ClientId
, oauth2ClientSecret
, oauth2RefreshToken
);
400 Credential credential
= client
.authorize();
401 if (credential
!= null && credential
.getAccessToken() != null) {
402 options
.setOauthToken(credential
.getAccessToken());
409 * Helper function for generating a war directory based on an app.yaml file located in an external
410 * resource directory. First the command line arguments are checked to ensure that they are
411 * appropriate for war generation. If there is a problem then a {@link RuntimeException} is
412 * thrown. Otherwise a war directory is generated and its path is returned, and a success
413 * message is written to standard out.
415 * @return The path of the generated war directory.
417 private String
validateArgsAndGenerateWar() {
418 if (externalResourceDir
== null) {
419 throw new IllegalArgumentException("When generating a war directory --"
420 + EXTERNAL_RESOURCE_DIR_ARG
+ " must also be specified.");
422 if (EarHelper
.isEar(externalResourceDir
, false)) {
423 throw new IllegalArgumentException(
424 "With an EAR configuration " + "--" + EXTERNAL_RESOURCE_DIR_ARG
+ " is not allowed.");
426 File externalResourceDirectory
= new File(externalResourceDir
);
427 if (!externalResourceDirectory
.isDirectory()) {
428 throw new IllegalArgumentException(externalResourceDir
+ " is not an existing directory.");
430 File appYamlFile
= new File(externalResourceDirectory
, WarGenerator
.APP_YAML
);
431 if (!appYamlFile
.isFile()) {
432 throw new IllegalArgumentException(appYamlFile
.getPath() + " not found.");
434 File destination
= (generatedWarDir
== null ?
null : new File(generatedWarDir
));
436 WarGenerator warGen
=
437 WarGeneratorFactory
.newWarGenerator(externalResourceDirectory
, destination
);
438 String warDir
= warGen
.generateWarDirectory().getPath();
439 System
.out
.println("Successfully generated war directory at " + warDir
);
441 } catch (IOException e
) {
442 throw new RuntimeException("Unable to generate a war directory.", e
);
446 private void doPrompt() {
449 System
.out
.println("Your authentication credentials can't be found and may have expired.\n" +
450 "Please run appcfg directly from the command line to re-establish your credentials.");
454 getLoginReader().doPrompt();
458 private LoginReader
getLoginReader() {
459 if (loginReader
== null) {
460 loginReader
= LoginReaderFactory
.createLoginReader(connectOptions
, passin
);
465 private static final List
<String
> generalOptionNamesInHelpOrder
=
482 private static final List
<String
> optionNamesInHelpOrder
=
483 ImmutableList
.<String
>builder().addAll(generalOptionNamesInHelpOrder
).add(
484 "enable_jar_splitting",
485 "jar_splitting_excludes",
487 "enable_jar_classes",
498 private static final List
<String
> actionNamesInHelpOrder
=
513 "set_default_version",
515 "resource_limits_info",
523 "backends configure");
525 private String helpText
= null;
526 private void printHelp() {
527 if (helpText
== null) {
528 List
<String
> helpLines
= new LinkedList
<String
>();
529 helpLines
.add("usage: AppCfg [options] <action> [<app-dir>] [<argument>]");
531 helpLines
.add("Action must be one of:");
532 for (String actionName
: actionsAndOptions
.actionNames
) {
533 Action action
= actionsAndOptions
.getAction(actionName
);
534 if (action
!= null) {
535 helpLines
.add(" " + actionName
+ ": " + action
.getShortDescription());
538 helpLines
.add("Use 'help <action>' for a detailed description.");
540 helpLines
.add("options:");
541 for (String optionName
: actionsAndOptions
.optionNames
) {
542 Option option
= actionsAndOptions
.getOption(optionName
);
543 helpLines
.addAll(option
.getHelpLines());
545 helpText
= Joiner
.on("\n").join(helpLines
);
547 System
.out
.println(helpText
);
548 System
.out
.println();
551 private final List
<Option
> builtInOptions
= Arrays
.asList(
553 new Option("h", "help", true) {
555 public List
<String
> getHelpLines() {
556 return ImmutableList
.<String
>of(
557 " -h, --help Show the help message and exit.");
560 public void apply() {
566 new Option("s", "server", false) {
568 public List
<String
> getHelpLines() {
569 return ImmutableList
.<String
>of(
570 " -s SERVER, --server=SERVER",
571 " The server to connect to.");
574 public void apply() {
575 connectOptions
.setServer(getValue());
579 new Option("e", "email", false) {
581 public List
<String
> getHelpLines() {
582 return ImmutableList
.<String
>of(
583 " -e EMAIL, --email=EMAIL",
584 " The username to use. Will prompt if omitted.");
587 public void apply() {
588 connectOptions
.setUserId(getValue());
592 new Option("H", "host", false) {
594 public List
<String
> getHelpLines() {
595 return ImmutableList
.<String
>of(
596 " -H HOST, --host=HOST Overrides the Host header sent with all RPCs.");
599 public void apply() {
600 connectOptions
.setHost(getValue());
604 new Option("p", "proxy", false) {
606 public List
<String
> getHelpLines() {
607 return ImmutableList
.<String
>of(
608 " -p PROXYHOST[:PORT], --proxy=PROXYHOST[:PORT]",
609 " Proxies requests through the given proxy server.",
610 " If --proxy_https is also set, only HTTP will be",
611 " proxied here, otherwise both HTTP and HTTPS will.");
614 public void apply() {
615 HostPort hostport
= new HostPort(getValue());
617 System
.setProperty("http.proxyHost", hostport
.getHost());
618 if (hostport
.hasPort()) {
619 System
.setProperty("http.proxyPort", hostport
.getPort());
624 new Option(null, "proxy_https", false) {
626 public List
<String
> getHelpLines() {
627 return ImmutableList
.<String
>of(
628 " --proxy_https=PROXYHOST[:PORT]",
629 " Proxies HTTPS requests through the given proxy server.");
632 public void apply() {
633 HostPort hostport
= new HostPort(getValue());
635 System
.setProperty("https.proxyHost", hostport
.getHost());
636 if (hostport
.hasPort()) {
637 System
.setProperty("https.proxyPort", hostport
.getPort());
642 new Option(null, "insecure", true) {
644 public void apply() {
645 connectOptions
.setSecure(false);
649 new Option(null, "ignore_bad_cert", true) {
651 public void apply() {
652 HttpsURLConnection
.setDefaultHostnameVerifier(new HostnameVerifier() {
654 public boolean verify(String hostname
, SSLSession session
) {
661 new Option(null, "no_cookies", true) {
663 public List
<String
> getHelpLines() {
664 return ImmutableList
.<String
>of(
666 " --no_cookies Do not save/load access credentials to/from disk.");
669 public void apply() {
671 connectOptions
.setUsePersistedCredentials(false);
675 new Option("f", "force", true) {
677 public List
<String
> getHelpLines() {
678 return ImmutableList
.<String
>of(
679 " -f, --force Force deletion of indexes without being prompted.");
683 if (action
instanceof VacuumIndexesAction
){
684 VacuumIndexesAction viAction
= (VacuumIndexesAction
) action
;
685 viAction
.promptUserForEachDelete
= false;
690 new Option("a", "append", true) {
692 public List
<String
> getHelpLines() {
693 return ImmutableList
.<String
>of(
694 " -a, --append Append to existing file.");
697 public void apply() {
698 if (action
instanceof RequestLogsAction
) {
699 RequestLogsAction logsAction
= (RequestLogsAction
) action
;
700 logsAction
.append
= true;
705 new Option("n", "num_days", false) {
707 public List
<String
> getHelpLines() {
708 return ImmutableList
.<String
>of(
709 " -n NUM_DAYS, --num_days=NUM_DAYS",
710 " Number of days worth of log data to get. The cut-off",
711 " point is midnight UTC. Use 0 to get all available",
712 " logs. Default is 1.");
715 public void apply() {
716 if (action
instanceof RequestLogsAction
) {
717 RequestLogsAction logsAction
= (RequestLogsAction
) action
;
719 logsAction
.numDays
= Integer
.parseInt(getValue());
720 } catch (NumberFormatException e
) {
721 throw new IllegalArgumentException("num_days must be an integral number.");
723 } else if (action
instanceof CronInfoAction
) {
724 CronInfoAction croninfoAction
= (CronInfoAction
) action
;
725 croninfoAction
.setNumRuns(getValue());
730 new Option(null, "num_runs", false) {
732 public List
<String
> getHelpLines() {
733 return ImmutableList
.<String
>of(
734 " -n NUM_RUNS, --num_runs=NUM_RUNS",
735 " Number of scheduled execution times to compute");
738 public void apply() {
739 if (action
instanceof CronInfoAction
) {
740 CronInfoAction croninfoAction
= (CronInfoAction
) action
;
741 croninfoAction
.setNumRuns(getValue());
746 new Option(null, "severity", false) {
748 public List
<String
> getHelpLines() {
749 return ImmutableList
.<String
>of(
750 " --severity=SEVERITY Severity of app-level log messages to get. The range",
751 " is 0 (DEBUG) through 4 (CRITICAL). If omitted, only",
752 " request logs are returned.");
755 public void apply() {
756 RequestLogsAction logsAction
= (RequestLogsAction
) action
;
758 int severity
= Integer
.parseInt(getValue());
759 int maxSeverity
= LogSeverity
.values().length
;
760 if (severity
< 0 || severity
> maxSeverity
) {
761 throw new IllegalArgumentException("severity must be between 0 and " + maxSeverity
);
763 logsAction
.severity
= severity
;
764 } catch (NumberFormatException e
) {
765 for (Enum
<LogSeverity
> severity
: LogSeverity
.values()) {
766 if (getValue().equalsIgnoreCase(severity
.toString())) {
767 logsAction
.severity
= severity
.ordinal();
771 throw new IllegalArgumentException("severity must be an integral "
772 + "number 0-4, or one of DEBUG, INFO, WARN, ERROR, CRITICAL");
777 new Option(null, "sdk_root", false) {
779 public List
<String
> getHelpLines() {
780 return ImmutableList
.<String
>of(
781 " --sdk_root=root Overrides where the SDK is located.");
784 public void apply() {
785 connectOptions
.setSdkRoot(getValue());
789 new Option(null, "disable_jar_jsps", true) {
791 public List
<String
> getHelpLines() {
792 return ImmutableList
.<String
>of(
793 " --disable_jar_jsps",
794 " Do not jar the classes generated from JSPs.");
798 public void apply() {
803 new Option(null, "enable_jar_classes", true) {
805 public List
<String
> getHelpLines() {
806 return ImmutableList
.<String
>of(
807 " --enable_jar_classes",
808 " Jar the WEB-INF/classes content.");
812 public void apply() {
816 new Option(null, "delete_jsps", true) {
818 public List
<String
> getHelpLines() {
819 return ImmutableList
.<String
>of(
821 " Delete the JSP source files after compilation.");
825 public void apply() {
830 new Option(null, "enable_jar_splitting", true) {
832 public List
<String
> getHelpLines() {
833 return ImmutableList
.<String
>of(
834 " --enable_jar_splitting",
835 " Split large jar files (> 10M) into smaller fragments.");
838 public void apply() {
839 doJarSplitting
= true;
843 new Option(null, "jar_splitting_excludes", false) {
845 public List
<String
> getHelpLines() {
846 return ImmutableList
.<String
>of(
847 " --jar_splitting_excludes=SUFFIXES",
848 " When --enable-jar-splitting is set, files that match",
849 " the list of comma separated SUFFIXES will be excluded",
853 public void apply() {
854 jarSplittingExcludeSuffixes
= new HashSet
<String
>(Arrays
.asList(getValue().split(",")));
858 new Option(null, "retain_upload_dir", true) {
860 public List
<String
> getHelpLines() {
861 return ImmutableList
.<String
>of(
862 " --retain_upload_dir",
863 " Do not delete temporary (staging) directory used in",
867 public void apply() {
868 connectOptions
.setRetainUploadDir(true);
872 new Option(null, "passin", true) {
874 public List
<String
> getHelpLines() {
875 return ImmutableList
.<String
>of(
876 " --passin Always read the login password from stdin.");
879 public void apply() {
884 new Option(null, "no_batch", true) {
886 public void apply() {
890 new Option(null, "compile_encoding", false) {
892 public List
<String
> getHelpLines() {
893 return ImmutableList
.<String
>of(
894 " --compile_encoding",
895 " The character encoding to use when compiling JSPs.");
898 public void apply() {
899 compileEncoding
= getValue();
903 new Option(null, "disable_prompt", true) {
905 public void apply() {
906 disablePrompt
= true;
910 new Option(null, "disable_update_check", true) {
912 public void apply() {
913 disableUpdateCheck
= true;
917 new Option("A", "application", false) {
919 public List
<String
> getHelpLines() {
920 return ImmutableList
.<String
>of(
921 " -A APP_ID, --application=APP_ID",
922 " Override application id from appengine-web.xml or app.yaml");
925 public void apply() {
926 overrideAppId
= getValue();
930 new Option(OVERRIDE_MODULE_SHORT_ARG
, OVERRIDE_MODULE_LONG_ARG
, false) {
932 public List
<String
> getHelpLines() {
933 return ImmutableList
.<String
>of(
934 " -" + OVERRIDE_MODULE_SHORT_ARG
+ " MODULE, --" + OVERRIDE_MODULE_LONG_ARG
936 " Override module from appengine-web.xml or app.yaml");
939 public void apply() {
940 overrideModule
= getValue();
944 new Option("V", "version" , false) {
946 public List
<String
> getHelpLines() {
947 return ImmutableList
.<String
>of(
948 " -V VERSION, --version=VERSION",
949 " Override (major) version from appengine-web.xml " +
953 public void apply() {
954 overrideAppVersion
= getValue();
958 new Option(null, "oauth2", true) {
960 public List
<String
> getHelpLines() {
961 return ImmutableList
.<String
>of(
962 " --oauth2 Use OAuth2 instead of password auth.");
965 public void apply() {
970 new Option(null, "oauth2_refresh_token", false) {
972 public void apply() {
973 oauth2RefreshToken
= getValue();
978 new Option(null, "oauth2_client_id", false) {
980 public void apply() {
981 oauth2ClientId
= getValue();
986 new Option(null, "oauth2_client_secret", false) {
988 public void apply() {
989 oauth2ClientSecret
= getValue();
994 new Option(null, "oauth2_config_file", false) {
996 public void apply() {
997 final Properties props
= new Properties();
999 props
.load(new FileInputStream(getValue()));
1000 } catch (FileNotFoundException e
) {
1001 throw new RuntimeException(
1002 String
.format("OAuth2 configuration file does not exist: %s", getValue()), e
);
1003 } catch (IOException e
) {
1004 throw new RuntimeException(
1005 String
.format("Could not read OAuth2 configuration file: %s", getValue()), e
);
1008 oauth2RefreshToken
= props
.getProperty("oauth2_refresh_token");
1009 oauth2ClientId
= props
.getProperty("oauth2_client_id");
1010 oauth2ClientSecret
= props
.getProperty("oauth2_client_secret");
1012 if (oauth2RefreshToken
!= null ||
1013 oauth2ClientId
!= null ||
1014 oauth2ClientSecret
!= null) {
1020 new Option(null, "use_java7", true) {
1022 public void apply() {
1023 System
.setProperty(USE_JAVA6_SYSTEM_PROP
, "false");
1027 new Option("r", "runtime", false) {
1029 public void apply() {
1030 runtime
= getValue();
1034 new Option("R", "allow_any_runtime", true) {
1036 public void apply() {
1037 allowAnyRuntime
= true;
1041 new Option(null, EXTERNAL_RESOURCE_DIR_ARG
, false) {
1043 public void apply() {
1044 externalResourceDir
= getValue();
1048 new Option(null, GENERATE_WAR_ARG
, true) {
1050 public void apply() {
1055 new Option(null, "use_java6", true) {
1057 public void apply() {
1058 System
.setProperty(USE_JAVA6_SYSTEM_PROP
, "true");
1062 new Option(null, GENERATED_WAR_DIR_ARG
, false) {
1064 public void apply() {
1066 generatedWarDir
= getValue();
1070 new Option(null, "fail_on_precompilation_error", true) {
1072 public void apply() {
1073 failOnPrecompilationError
= true;
1077 private final List
<Action
> builtInActions
= Arrays
.<Action
>asList(
1079 new RequestLogsAction(),
1080 new RollbackAction(),
1081 new UpdateIndexesAction(),
1082 new UpdateCronAction(),
1083 new UpdateDispatchAction(),
1084 new UpdateDosAction(),
1085 new UpdateQueueAction(),
1086 new CronInfoAction(),
1087 new VacuumIndexesAction(),
1089 new DownloadAppAction(),
1090 new VersionAction(),
1091 new SetDefaultVersionAction(),
1092 new ResourceLimitsInfoAction(),
1095 new BackendsListAction(),
1096 new BackendsRollbackAction(),
1097 new BackendsUpdateAction(),
1098 new BackendsStartAction(),
1099 new BackendsStopAction(),
1100 new BackendsDeleteAction(),
1101 new BackendsConfigureAction(),
1102 new BackendsAction()
1105 private Map
<String
, Option
> builtInOptionMap
;
1107 private List
<Option
> builtInOptions(String
... optionNames
) {
1108 if (builtInOptionMap
== null) {
1109 builtInOptionMap
= new HashMap
<String
, Option
>(builtInOptions
.size());
1110 for (Option option
: builtInOptions
){
1111 builtInOptionMap
.put(option
.getLongName(), option
);
1114 List
<Option
> options
= new LinkedList
<Option
>();
1115 for (String name
: optionNames
) {
1116 Option option
= builtInOptionMap
.get(name
);
1117 if (option
!= null) {
1118 options
.add(option
);
1124 private final ActionsAndOptions actionsAndOptions
= buildActionsAndOptions();
1126 private ActionsAndOptions
buildActionsAndOptions() {
1127 ActionsAndOptions actionsAndOptions
= getBuiltInActionsAndOptions();
1128 for (SDKRuntimePlugin runtimePlugin
: SDKPluginManager
.findAllRuntimePlugins()) {
1129 runtimePlugin
.customizeAppCfgActionsAndOptions(actionsAndOptions
);
1131 return actionsAndOptions
;
1135 * Builds the collection of built-in Actions and Options.
1137 private ActionsAndOptions
getBuiltInActionsAndOptions() {
1138 ActionsAndOptions actionsAndOptions
= new ActionsAndOptions();
1139 actionsAndOptions
.actions
= builtInActions
;
1140 actionsAndOptions
.actionNames
= actionNamesInHelpOrder
;
1141 actionsAndOptions
.options
= builtInOptions
;
1142 actionsAndOptions
.optionNames
= optionNamesInHelpOrder
;
1143 actionsAndOptions
.generalOptionNames
= generalOptionNamesInHelpOrder
;
1144 return actionsAndOptions
;
1147 abstract class AppCfgAction
extends Action
{
1149 AppCfgAction(String
... names
) {
1153 AppCfgAction(List
<Option
> options
, String
... names
) {
1154 super(options
, names
);
1158 protected void setArgs(List
<String
> args
) {
1159 super.setArgs(args
);
1163 public void apply() {
1165 applicationDirectory
= validateArgsAndGenerateWar();
1166 List
<String
> args
= getArgs();
1167 List
<String
> newArgs
= new ArrayList
<String
>(args
.size() + 1);
1168 newArgs
.add(applicationDirectory
);
1169 newArgs
.addAll(args
);
1172 if (getArgs().size() < 1) {
1173 throw new IllegalArgumentException("Expected the application directory"
1174 + " as an argument after the action name.");
1176 applicationDirectory
= getArgs().get(0);
1177 validateCommandLineForEar();
1179 SDKRuntimePlugin runtimePlugin
= SDKPluginManager
.findRuntimePlugin(
1180 new File(applicationDirectory
));
1181 if (runtimePlugin
!= null) {
1183 ApplicationDirectories appDirs
= runtimePlugin
.generateApplicationDirectories(
1184 new File(applicationDirectory
));
1185 applicationDirectory
= appDirs
.getWarDir().getPath();
1186 getArgs().set(0, applicationDirectory
);
1187 externalResourceDir
= appDirs
.getExternalResourceDir().getPath();
1188 } catch (IOException e
) {
1189 throw new RuntimeException("Unable to generate the war directory", e
);
1195 public abstract void execute();
1198 protected List
<String
> getHelpLines() {
1199 List
<String
> helpLines
= new LinkedList
<String
>();
1200 helpLines
.addAll(getInitialHelpLines());
1202 helpLines
.add("Options:");
1203 for (String optionName
: actionsAndOptions
.generalOptionNames
) {
1204 Option option
= actionsAndOptions
.getOption(optionName
);
1205 if (option
!= null) {
1206 helpLines
.addAll(option
.getHelpLines());
1209 if (extraOptions
!= null) {
1210 for (Option option
: extraOptions
) {
1211 helpLines
.addAll(option
.getHelpLines());
1218 * Returns a list of Strings to be displayed as the initial lines of a help text. Subclasses
1219 * should override this method.
1221 * The text returned by this method should describe the base Action without any of its options.
1222 * Text describing the options will be added in lines below this text.
1224 protected List
<String
> getInitialHelpLines() {
1225 return ImmutableList
.of();
1228 protected boolean isEarAction() {
1232 protected void outputBackendsMessage() {
1233 System
.out
.println("Looks like you're using Backends. We suggest that you " +
1234 "make the switch to App Engine Modules. See the modules documentation " +
1235 "to learn more about converting: https://developers.google.com/appengine/" +
1236 "docs/java/modules/converting");
1240 class UpdateAction
extends AppCfgAction
{
1242 super(builtInOptions("enable_jar_splitting", "jar_splitting_excludes", "retain_upload_dir",
1243 "compile_encoding", "disable_jar_jsps", "delete_jsps", "enable_jar_classes"), "update");
1244 shortDescription
= "Create or update an app version.";
1248 public void execute() {
1249 admin
.update(new AppCfgUpdateModuleListener());
1253 protected List
<String
> getInitialHelpLines() {
1254 return ImmutableList
.of(
1255 "AppCfg [options] update <app-dir>",
1257 "Installs a new version of the application onto the server, as the",
1258 "default version for end users.");
1262 protected boolean isEarAction() {
1267 class RequestLogsAction
extends AppCfgAction
{
1271 boolean append
= false;
1273 RequestLogsAction() {
1274 super(builtInOptions("num_days", "severity", "append"), "request_logs");
1275 shortDescription
= "Write request logs in Apache common log format.";
1278 public void apply() {
1280 if (getArgs().size() != 2) {
1281 throw new IllegalArgumentException("Expected the application directory"
1282 + " and log file as arguments after the request_logs action name.");
1284 outputFile
= getArgs().get(1);
1287 public void execute() {
1288 Reader reader
= admin
.requestLogs(numDays
,
1289 severity
>= 0 ? LogSeverity
.values()[severity
] : null);
1290 if (reader
== null) {
1294 BufferedReader r
= new BufferedReader(reader
);
1295 PrintWriter writer
= null;
1297 if (outputFile
.equals("-")) {
1298 writer
= new PrintWriter(System
.out
);
1300 writer
= new PrintWriter(new FileWriter(outputFile
, append
));
1303 while ((line
= r
.readLine()) != null) {
1304 writer
.println(line
);
1306 } catch (IOException e
) {
1307 throw new RuntimeException("Failed to read logs: " + e
);
1309 if (writer
!= null) {
1314 } catch (IOException e
) {
1319 protected List
<String
> getInitialHelpLines() {
1320 return ImmutableList
.of(
1321 "AppCfg [options] request_logs <app-dir> <output-file>",
1323 "Populates the output-file with recent logs from the application.");
1327 class RollbackAction
extends AppCfgAction
{
1330 shortDescription
= "Rollback an in-progress update.";
1333 public void execute() {
1337 protected List
<String
> getInitialHelpLines() {
1338 return ImmutableList
.of(
1339 "AppCfg [options] rollback <app-dir>",
1341 "The 'update' command requires a server-side transaction.",
1342 "Use 'rollback' if you experience an error during 'update'",
1343 "and want to begin a new update transaction.");
1347 class UpdateIndexesAction
extends AppCfgAction
{
1348 UpdateIndexesAction() {
1349 super("update_indexes");
1350 shortDescription
= "Update application indexes.";
1353 public void execute() {
1354 admin
.updateIndexes();
1357 protected List
<String
> getInitialHelpLines() {
1358 return ImmutableList
.of(
1359 "AppCfg [options] update_indexes <app-dir>",
1361 "Updates the datastore indexes for the server to add any in the current",
1362 "application directory. Does not alter the running application version, nor",
1363 "remove any existing indexes.");
1367 class UpdateCronAction
extends AppCfgAction
{
1368 UpdateCronAction() {
1369 super("update_cron");
1370 shortDescription
= "Update application cron jobs.";
1373 public void execute() {
1375 shortDescription
= "Update application cron jobs.";
1378 protected List
<String
> getInitialHelpLines() {
1379 return ImmutableList
.of(
1380 "AppCfg [options] update_cron <app-dir>",
1382 "Updates the cron jobs for the application. Updates any new, removed or changed",
1383 "cron jobs. Does not otherwise alter the running application version.");
1387 class UpdateDispatchAction
extends AppCfgAction
{
1388 UpdateDispatchAction() {
1389 super("update_dispatch");
1390 shortDescription
= "Update the application dispatch configuration.";
1393 public void execute() {
1394 admin
.updateDispatch();
1397 protected List
<String
> getInitialHelpLines() {
1398 return ImmutableList
.of(
1399 "AppCfg [options] update_dispatch <app-dir>",
1401 "Updates the application dispatch configuration.",
1402 "Does not otherwise alter the running application version.");
1406 class UpdateDosAction
extends AppCfgAction
{
1408 super("update_dos");
1409 shortDescription
= "Update application DoS protection configuration.";
1412 public void execute() {
1416 protected List
<String
> getInitialHelpLines() {
1417 return ImmutableList
.of(
1418 "AppCfg [options] update_dos <app-dir>",
1420 "Updates the DoS protection configuration for the application.",
1421 "Does not otherwise alter the running application version.");
1425 class UpdateQueueAction
extends AppCfgAction
{
1426 UpdateQueueAction() {
1427 super("update_queues");
1428 shortDescription
= "Update application task queue definitions.";
1431 public void execute() {
1432 admin
.updateQueues();
1435 protected List
<String
> getInitialHelpLines() {
1436 return ImmutableList
.of(
1437 "AppCfg [options] " + getNameString() + " <app-dir>",
1439 "Updates any new, removed or changed task queue definitions.",
1440 "Does not otherwise alter the running application version.");
1444 class CronInfoAction
extends AppCfgAction
{
1448 super(builtInOptions("num_runs"), "cron_info");
1449 shortDescription
= "Displays times for the next several runs of each cron job.";
1452 public void execute() {
1453 List
<CronEntry
> entries
= admin
.cronInfo();
1454 if (entries
.size() == 0) {
1455 System
.out
.println("No cron jobs defined.");
1457 System
.out
.println(entries
.size() + " cron entries defined.\n");
1458 for (CronEntry entry
: entries
) {
1459 System
.out
.println(entry
.toXml());
1460 System
.out
.println("Next " + numRuns
+ " execution times:");
1461 Iterator
<String
> iter
= entry
.getNextTimesIterator();
1462 for (int i
= 0; i
< numRuns
; i
++) {
1463 System
.out
.println(" " + iter
.next());
1465 System
.out
.println("");
1470 protected List
<String
> getInitialHelpLines() {
1471 return ImmutableList
.of(
1472 "AppCfg [options] cron_info <app-dir>",
1474 "Displays times for the next several runs of each cron job.");
1476 public void setNumRuns(String numberString
) {
1478 numRuns
= Integer
.parseInt(numberString
);
1479 } catch (NumberFormatException e
) {
1480 throw new IllegalArgumentException("num_runs must be an integral number.");
1483 throw new IllegalArgumentException("num_runs must be positive.");
1488 class VacuumIndexesAction
extends AppCfgAction
{
1489 public boolean promptUserForEachDelete
= true;
1491 VacuumIndexesAction() {
1492 super(builtInOptions("force"), "vacuum_indexes");
1493 shortDescription
= "Delete unused indexes from application.";
1497 public void execute() {
1498 ConfirmationCallback
<IndexDeleter
.DeleteIndexAction
> callback
= null;
1499 if (promptUserForEachDelete
) {
1500 callback
= new ConfirmationCallback
<IndexDeleter
.DeleteIndexAction
>() {
1502 public Response
confirmAction(DeleteIndexAction action
) {
1504 String prompt
= "\n" + action
.getPrompt() + " (N/y/a): ";
1505 System
.out
.print(prompt
);
1507 BufferedReader in
= new BufferedReader(new InputStreamReader(System
.in
));
1510 response
= in
.readLine();
1511 } catch (IOException ioe
) {
1514 response
= (null == response ?
"" : response
.trim().toLowerCase());
1515 if ("y".equals(response
)) {
1516 return Response
.YES
;
1518 if ("n".equals(response
) || response
.isEmpty()) {
1521 if ("a".equals(response
)) {
1522 return Response
.YES_ALL
;
1528 admin
.vacuumIndexes(callback
, new AppCfgVacuumIndexesListener());
1532 protected List
<String
> getInitialHelpLines() {
1533 return ImmutableList
.of(
1534 "AppCfg [options] vacuum_indexes <app-dir>",
1536 "Deletes indexes on the server that are not present in the local",
1537 "index configuration file. The user is prompted before each delete.");
1541 class HelpAction
extends AppCfgAction
{
1544 shortDescription
= "Print help for a specific action.";
1547 public void apply() {
1548 if (getArgs().isEmpty()) {
1551 Action foundAction
= Parser
.lookupAction(actionsAndOptions
.actions
,
1552 getArgs().toArray(new String
[0]), 0);
1553 if (foundAction
== null) {
1554 System
.out
.println("No such command \"" + getArgs().get(0) + "\"\n\n");
1557 System
.out
.println(foundAction
.getHelpString());
1558 System
.out
.println();
1564 public void execute() {
1567 protected List
<String
> getHelpLines() {
1568 return ImmutableList
.of("AppCfg help <command>",
1570 "Prints help about a specific command.",
1575 class DownloadAppAction
extends AppCfgAction
{
1576 DownloadAppAction() {
1577 super("download_app");
1578 shortDescription
= "Download a previously uploaded app version.";
1581 public void apply() {
1582 if (getArgs().size() != 1) {
1583 throw new IllegalArgumentException("Expected download directory"
1584 + " as an argument after download_app.");
1586 File downloadDir
= new File(getArgs().get(0));
1587 if (overrideAppId
== null) {
1588 throw new IllegalArgumentException("You must specify an app ID via -A or --application");
1592 authorizeOauth2(connectOptions
);
1594 loadCookies(connectOptions
);
1597 AppDownload appDownload
=
1598 new AppDownload(ServerConnectionFactory
.getServerConnection(connectOptions
),
1599 new AppCfgListener("download_app"));
1600 int exitCode
= appDownload
.download(overrideAppId
,
1603 downloadDir
) ?
0 : 1;
1604 System
.exit(exitCode
);
1607 public void execute() {
1610 protected List
<String
> getInitialHelpLines() {
1611 return ImmutableList
.of(
1612 "AppCfg [options] -A app_id [ -M module ] [ -V version ] download_app <out-dir>",
1614 "Download a previously-uploaded app to the specified directory. The app",
1615 "ID is specified by the \"-A\" option. The optional module is specified by the \"-M\" ",
1616 "option and the optional version is specified by the \"-V\" option.");
1620 class VersionAction
extends AppCfgAction
{
1623 shortDescription
= "Prints version information.";
1626 public void apply() {
1627 System
.out
.println(SupportInfo
.getVersionString());
1631 public void execute() {
1634 protected List
<String
> getHelpLines() {
1635 return ImmutableList
.of(
1638 "Prints version information.");
1642 class SetDefaultVersionAction
extends AppCfgAction
{
1643 SetDefaultVersionAction() {
1644 super("set_default_version");
1645 shortDescription
= "Set the default serving version.";
1648 public void execute() {
1649 admin
.setDefaultVersion();
1652 protected List
<String
> getInitialHelpLines() {
1653 return ImmutableList
.of(
1654 "AppCfg [options] set_default_version <app-dir>",
1656 "Sets the default (serving) version");
1660 class ResourceLimitsInfoAction
extends AppCfgAction
{
1661 public ResourceLimitsInfoAction() {
1662 super("resource_limits_info");
1663 shortDescription
= "Display resource limits.";
1667 public void execute() {
1668 ResourceLimits resourceLimits
= admin
.getResourceLimits();
1669 for (String key
: new TreeSet
<String
>(resourceLimits
.keySet())) {
1670 System
.out
.println(key
+ ": " + resourceLimits
.get(key
));
1675 protected List
<String
> getInitialHelpLines() {
1676 return ImmutableList
.of(
1677 "AppCfg [options] resource_limits_info <app-dir>",
1679 "Displays the resource limits available to the app. An app will",
1680 "not update if any of the app's resources are larger than the",
1681 "appropriate resource limit.");
1685 class BackendsListAction
extends AppCfgAction
{
1686 BackendsListAction() {
1687 super("backends", "list");
1688 shortDescription
= "List the currently configured backends.";
1692 public void execute() {
1693 outputBackendsMessage();
1694 for (BackendsXml
.Entry backend
: admin
.listBackends()) {
1695 System
.out
.println(backend
.toString());
1700 protected List
<String
> getInitialHelpLines() {
1701 return ImmutableList
.of(
1702 "AppCfg [options] backends list <app-dir>",
1704 "List the currently configured backends.");
1708 class BackendsRollbackAction
extends AppCfgAction
{
1709 private String backendName
;
1711 BackendsRollbackAction() {
1712 super("backends", "rollback");
1713 shortDescription
= "Roll back a previously in-progress update.";
1717 public void apply() {
1719 if (getArgs().size() < 1 || getArgs().size() > 2) {
1720 throw new IllegalArgumentException("Expected <app-dir> [<backend-name>]");
1721 } else if (getArgs().size() == 2) {
1722 backendName
= getArgs().get(1);
1727 public void execute() {
1728 outputBackendsMessage();
1729 List
<String
> backends
;
1730 if (backendName
!= null) {
1731 admin
.rollbackBackend(backendName
);
1733 admin
.rollbackAllBackends();
1738 protected List
<String
> getInitialHelpLines() {
1739 return ImmutableList
.of(
1740 "AppCfg [options] backends rollback <app-dir> [<backend-name>]",
1742 "The 'backends update' command requires a server-side transaction.",
1743 "Use 'backends rollback' if you experience an error during 'backends update'",
1744 "and want to begin a new update transaction.");
1748 class BackendsUpdateAction
extends AppCfgAction
{
1749 private String backendName
;
1751 BackendsUpdateAction() {
1752 super(builtInOptions("enable_jar_splitting", "jar_splitting_excludes", "retain_upload_dir",
1753 "compile_encoding", "disable_jar_jsps", "delete_jsps", "enable_jar_classes"),
1754 "backends", "update");
1755 shortDescription
= "Update the specified backend or all backends.";
1759 public void apply() {
1761 if (getArgs().size() < 1 || getArgs().size() > 2) {
1762 throw new IllegalArgumentException("Expected <app-dir> [<backend-name>]");
1763 } else if (getArgs().size() == 2) {
1764 backendName
= getArgs().get(1);
1769 public void execute() {
1770 outputBackendsMessage();
1771 List
<String
> backends
;
1772 if (backendName
!= null) {
1773 admin
.updateBackend(backendName
, new AppCfgUpdateBackendListener());
1775 admin
.updateAllBackends(new AppCfgUpdateBackendListener());
1780 protected List
<String
> getInitialHelpLines() {
1781 return ImmutableList
.of(
1782 "AppCfg [options] backends update <app-dir> [<backend-name>]",
1784 "Update the specified backend or all backends.");
1788 class BackendsStartAction
extends AppCfgAction
{
1789 private String backendName
;
1791 BackendsStartAction() {
1792 super("backends", "start");
1793 shortDescription
= "Start the specified backend.";
1797 public void apply() {
1799 if (getArgs().size() != 2) {
1800 throw new IllegalArgumentException("Expected the backend name");
1802 backendName
= getArgs().get(1);
1806 public void execute() {
1807 outputBackendsMessage();
1808 admin
.setBackendState(backendName
, BackendsXml
.State
.START
);
1812 protected List
<String
> getInitialHelpLines() {
1813 return ImmutableList
.of(
1814 "AppCfg [options] backends start <app-dir> <backend>",
1816 "Starts the backend with the specified name.");
1820 class BackendsStopAction
extends AppCfgAction
{
1821 private String backendName
;
1823 BackendsStopAction() {
1824 super("backends", "stop");
1825 shortDescription
= "Stop the specified backend.";
1829 public void apply() {
1831 if (getArgs().size() != 2) {
1832 throw new IllegalArgumentException("Expected the backend name");
1834 backendName
= getArgs().get(1);
1837 public void execute() {
1838 outputBackendsMessage();
1839 admin
.setBackendState(backendName
, BackendsXml
.State
.STOP
);
1843 protected List
<String
> getInitialHelpLines() {
1844 return ImmutableList
.of(
1845 "AppCfg [options] backends stop <app-dir> <backend>",
1847 "Stops the backend with the specified name.");
1851 class BackendsDeleteAction
extends AppCfgAction
{
1852 private String backendName
;
1854 BackendsDeleteAction() {
1855 super("backends", "delete");
1856 shortDescription
= "Delete the specified backend.";
1860 public void apply() {
1862 if (getArgs().size() != 2) {
1863 throw new IllegalArgumentException("Expected the backend name");
1865 backendName
= getArgs().get(1);
1868 public void execute() {
1869 outputBackendsMessage();
1870 admin
.deleteBackend(backendName
);
1874 protected List
<String
> getInitialHelpLines() {
1875 return ImmutableList
.of(
1876 "AppCfg [options] backends delete",
1878 "Deletes the specified backend.");
1882 class BackendsConfigureAction
extends AppCfgAction
{
1883 private String backendName
;
1885 BackendsConfigureAction() {
1886 super("backends", "configure");
1887 shortDescription
= "Configure the specified backend.";
1891 public void apply() {
1893 if (getArgs().size() != 2) {
1894 throw new IllegalArgumentException("Expected the backend name");
1896 backendName
= getArgs().get(1);
1899 public void execute() {
1900 outputBackendsMessage();
1901 admin
.configureBackend(backendName
);
1905 protected List
<String
> getInitialHelpLines() {
1906 return ImmutableList
.of(
1907 "AppCfg [options] backends configure <app-dir> <backend>",
1909 "Updates the configuration of the backend with the specified name, without",
1910 "stopping instances that are currently running. Only valid for certain",
1911 "settings (instances, options: failfast, options: public).");
1916 * This is a catchall for the case where the user enters "appcfg.sh
1917 * backends app-dir sub-command" rather than "appcfg.sh backends
1918 * sub-command app-dir". It was added to maintain compatibility
1919 * with Python. It simply remaps the arguments and dispatches the
1920 * appropriate action.
1922 class BackendsAction
extends AppCfgAction
{
1923 private AppCfgAction subAction
;
1930 public void apply() {
1932 if (getArgs().size() < 2) {
1933 throw new IllegalArgumentException("Expected backends <app-dir> <sub-command> [...]");
1936 String dir
= getArgs().get(0);
1937 String subCommand
= getArgs().get(1);
1938 subAction
= (AppCfgAction
) Parser
.lookupAction(actionsAndOptions
.actions
,
1939 new String
[] {"backends", subCommand
},
1941 if (subAction
instanceof BackendsAction
) {
1942 throw new IllegalArgumentException("Unknown backends subcommand.");
1944 List
<String
> newArgs
= new ArrayList
<String
>();
1946 newArgs
.addAll(getArgs().subList(2, getArgs().size()));
1947 subAction
.setArgs(newArgs
);
1952 public void execute() {
1953 outputBackendsMessage();
1954 subAction
.execute();
1958 protected List
<String
> getHelpLines() {
1959 return ImmutableList
.of(
1960 "AppCfg [options] backends list: List the currently configured backends.",
1961 "AppCfg [options] backends update: Update the specified backend or all backends.",
1962 "AppCfg [options] backends rollback: Roll back a previously in-progress update.",
1963 "AppCfg [options] backends start: Start the specified backend.",
1964 "AppCfg [options] backends stop: Stop the specified backend.",
1965 "AppCfg [options] backends delete: Delete the specified backend.",
1966 "AppCfg [options] backends configure: Configure the specified backend.");
1970 class StartAction
extends AppCfgAction
{
1973 shortDescription
= "Start the specified module version.";
1977 public void execute() {
1978 admin
.startModule();
1982 protected List
<String
> getInitialHelpLines() {
1983 return ImmutableList
.of(
1984 "AppCfg [options] start <app-dir>",
1986 "Starts the specified module version.");
1990 class StopAction
extends AppCfgAction
{
1993 shortDescription
= "Stop the specified module version.";
1997 public void execute() {
2002 protected List
<String
> getInitialHelpLines() {
2003 return ImmutableList
.of(
2004 "AppCfg [options] stop <app-dir>",
2006 "Stops the specified module version.");
2010 private static class AppCfgListener
implements UpdateListener
{
2011 private final String operationName
;
2013 AppCfgListener(String opName
){
2014 operationName
= opName
;
2017 public void onProgress(UpdateProgressEvent event
) {
2018 System
.out
.println(event
.getPercentageComplete() + "% " + event
.getMessage());
2022 public void onSuccess(UpdateSuccessEvent event
) {
2023 String details
= event
.getDetails();
2024 if (details
.length() > 0) {
2025 System
.out
.println();
2026 System
.out
.println("Details:");
2027 System
.out
.println(details
);
2030 System
.out
.println();
2031 System
.out
.println(getSuccessSummaryMessage());
2035 public void onFailure(UpdateFailureEvent event
) {
2036 String details
= event
.getDetails();
2037 if (details
.length() > 0) {
2038 System
.out
.println();
2039 System
.out
.println("Error Details:");
2040 System
.out
.println(details
);
2043 System
.out
.println();
2044 String failMsg
= event
.getFailureMessage();
2045 System
.out
.println(failMsg
);
2046 if (event
.getCause() instanceof ClientAuthFailException
) {
2047 System
.out
.println("Consider using the -e EMAIL option if that"
2048 + " email address is incorrect.");
2052 protected String
getOperationName() {
2053 return operationName
;
2056 protected String
getSuccessSummaryMessage() {
2057 return getOperationName() + " completed successfully.";
2061 private class AppCfgUpdateModuleListener
extends AppCfgListener
{
2062 AppCfgUpdateModuleListener(){
2067 protected String
getSuccessSummaryMessage() {
2068 return getOperationName() + " for module " + moduleName
+ " completed successfully.";
2072 private static class AppCfgUpdateBackendListener
extends AppCfgListener
{
2073 AppCfgUpdateBackendListener(){
2078 private static class AppCfgVacuumIndexesListener
extends AppCfgListener
{
2079 AppCfgVacuumIndexesListener(){
2080 super("vacuum_indexes");
2084 private static class HostPort
{
2085 private final String host
;
2086 private final String port
;
2088 public HostPort(String hostport
) {
2089 int colon
= hostport
.indexOf(':');
2090 host
= colon
< 0 ? hostport
: hostport
.substring(0, colon
);
2091 port
= colon
< 0 ?
"" : hostport
.substring(colon
+ 1);
2094 public String
getHost() {
2098 public String
getPort() {
2102 public boolean hasPort() {
2103 return port
.length() > 0;
2107 private void validateApplicationDirectory(File war
) {
2108 if (!war
.exists()) {
2109 System
.out
.println("Unable to find the webapp directory " + war
);
2112 } else if (!war
.isDirectory()) {
2113 System
.out
.println("appcfg only accepts webapp directories, not war files.");