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