/** * Copyright 2012 Google Inc. All Rights Reserved. */ package com.google.appengine.appcfg; import com.google.appengine.SdkResolver; import com.google.appengine.tools.admin.AppCfg; import com.google.appengine.tools.admin.Application; import com.google.apphosting.utils.config.AppEngineWebXml; import com.google.apphosting.utils.config.AppEngineWebXmlReader; import com.google.apphosting.utils.config.EarHelper; import com.google.apphosting.utils.config.EarInfo; import com.google.common.base.Joiner; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.project.MavenProject; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.repository.RemoteRepository; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * Abstract class for supporting appcfg commands. * * @author Matt Stephenson <[email protected]> */ public abstract class AbstractAppCfgMojo extends AbstractMojo { private static final String USER_AGENT_KEY = "appengine.useragent"; /** * The entry point to Aether, i.e. the component doing all the work. * * @component */ protected RepositorySystem repoSystem; /** * The current repository/network configuration of Maven. * * @parameter default-value="${repositorySystemSession}" * @readonly */ protected RepositorySystemSession repoSession; /** * The project's remote repositories to use for the resolution of project * dependencies. * * @parameter default-value="${project.remoteProjectRepositories}" * @readonly */ protected List<RemoteRepository> projectRepos; /** * The project's remote repositories to use for the resolution of plugins and * their dependencies. * * @parameter default-value="${project.remotePluginRepositories}" * @readonly */ protected List<RemoteRepository> pluginRepos; /** * The server to connect to. * * @parameter expression="${appengine.server}" */ protected String server; /** * The username to use. * * @parameter expression="${appengine.email}" */ protected String email; /** * Override for the Host header setn with all RPCs. * * @parameter expression="${appengine.host}" */ protected String host; /** * Proxies requests through the given proxy server. If --proxy_https is also * set, only HTTP will be proxied here, otherwise both HTTP and HTTPS will. * * @parameter expression="${appengine.proxyHost}" */ protected String proxyHost; /** * Proxies HTTPS requests through the given proxy server. * * @parameter expression="${appengine.proxyHttps}" */ protected String proxyHttps; /** * Do not save/load access credentials to/from disk. * * @parameter expression="${appengine.noCookies}" */ protected boolean noCookies; /** * Always read the login password from stdin. * * @parameter expression="${appengine.passin}" */ protected boolean passin; /** * Do not use HTTPS to communicate with the Admin Console. * * @parameter expression="${appengine.insecure}" */ protected boolean insecure; /** * Override application id from appengine-web.xml or app.yaml. * * @parameter expression="${appengine.appId}" */ protected String appId; /** * Override version from appengine-web.xml or app.yaml. * * @parameter expression="${appengine.version}" */ protected String version; /** * Use OAuth2 instead of password auth. Defaults to true. * * @parameter default-value=true expression="${appengine.oauth2}" */ protected boolean oauth2; /** * Split large jar files (> 10M) into smaller fragments. * * @parameter expression="${appengine.enableJarSplitting}" */ protected boolean enableJarSplitting; /** * When --enable-jar-splitting is set, files that match the list of comma * separated SUFFIXES will be excluded from all jars. * * @parameter expression="${appengine.jarSplittingExcludes}" */ protected String jarSplittingExcludes; /** * Do not delete temporary (staging) directory used in uploading. * * @parameter expression="${appengine.retainUploadDir}" */ protected boolean retainUploadDir; /** * The character encoding to use when compiling JSPs. * * @parameter expression="${appengine.compileEncoding}" */ protected boolean compileEncoding; /** * Number of days worth of log data to get. The cut-off point is midnight UTC. * Use 0 to get all available logs. Default is 1. * * @parameter expression="${appengine.numDays}" */ protected Integer numDays; /** * Severity of app-level log messages to get. The range is 0 (DEBUG) through 4 * (CRITICAL). If omitted, only request logs are returned. * * @parameter expression="${appengine.severity}" */ protected String severity; /** * Append to existing file. * * @parameter expression="${appengine.append}" */ protected boolean append; /** * Number of scheduled execution times to compute. * * @parameter expression="${appengine.numRuns}" */ protected Integer numRuns; /** * Force deletion of indexes without being prompted. * * @parameter expression="${appengine.force}" */ protected boolean force; /** * The name of the backend to perform actions on. * * @parameter expression="${appengine.backendName}" */ protected String backendName; /** * Delete the JSP source files after compilation. * * @parameter expression="${appengine.deleteJsps}" */ protected boolean deleteJsps; /** * Jar the WEB-INF/classes content. * * @parameter expression="${appengine.enableJarClasses}" */ protected boolean enableJarClasses; /** * Maven project. * * @parameter expression="${project}" * @required * @readonly */ protected MavenProject mavenProject; /** * The Google Cloud Platform project name (same as appId) * * @parameter expression="${project}" */ protected String project; /** * Instance id to for vm debug. * * @parameter expression="${appengine.instance}" */ protected String instance; /** * Additional parameters to pass through to AppCfg. * * @parameter expression="${appengine.additionalParams}" */ protected String[] additionalParams; protected void executeAppCfgCommand(String action, String appDir) throws MojoExecutionException { ArrayList<String> arguments = collectParameters(); arguments.add(action); arguments.add(appDir); getLog().info("Running " + Joiner.on(" ").join(arguments)); try { AppCfg.main(arguments.toArray(new String[arguments.size()])); } catch (Exception ex) { throw new MojoExecutionException("Error executing appcfg command=" + arguments, ex); } } protected void executeAppCfgBackendsCommand(String action, String appDir) throws MojoExecutionException { ArrayList<String> arguments = collectParameters(); arguments.add("backends"); arguments.add(action); arguments.add(appDir); arguments.add(backendName); try { AppCfg.main(arguments.toArray(new String[arguments.size()])); } catch (Exception ex) { throw new MojoExecutionException("Error executing appcfg command=" + arguments, ex); } } protected ArrayList<String> collectParameters() throws MojoExecutionException { String userDefinedAppId = null; String userDefinedVersion = null; boolean isEAR = false; String appDir = mavenProject.getBuild().getDirectory() + "/" + mavenProject.getBuild().getFinalName(); File f = new File(appDir, "WEB-INF/appengine-web.xml"); if (f.exists()) { AppEngineWebXmlReader aewebReader = new AppEngineWebXmlReader(appDir); AppEngineWebXml appEngineWebXml = aewebReader.readAppEngineWebXml(); userDefinedAppId = appEngineWebXml.getAppId(); userDefinedVersion = appEngineWebXml.getMajorVersionId(); } else if (EarHelper.isEar(appDir, false)) { EarInfo earInfo = EarHelper.readEarInfo(appDir, new File(Application.getSdkDocsDir(), "appengine-application.xsd")); userDefinedAppId = earInfo.getAppengineApplicationXml().getApplicationId(); isEAR = true; } // For appcfg user agent metric. System.setProperty(USER_AGENT_KEY, "appengine-maven-plugin"); ArrayList<String> arguments = new ArrayList<>(); if (server != null && !server.isEmpty()) { arguments.add("-s"); arguments.add(server); } if (email != null && !email.isEmpty()) { arguments.add("-e"); arguments.add(email); } if (host != null && !host.isEmpty()) { arguments.add("-H"); arguments.add(host); } if (proxyHost != null && !proxyHost.isEmpty()) { arguments.add("--proxy=" + proxyHost); } if (proxyHttps != null && !proxyHttps.isEmpty()) { arguments.add("--proxy_https=" + proxyHttps); } if (noCookies) { arguments.add("--no_cookies"); } if (passin) { arguments.add("--passin"); } if (insecure) { arguments.add("--insecure"); } if (appId != null && !appId.isEmpty()) { userDefinedAppId = appId; } else if (project != null && !project.isEmpty()) { userDefinedAppId = project; } if (userDefinedAppId != null) { validateAppIdOrVersion(userDefinedAppId); arguments.add("-A"); arguments.add(userDefinedAppId); } else { throw new MojoExecutionException( "No <application> defined in appengine-web.xml, nor <appId>" + " <configuration> defined in the pom.xml."); } if (version != null && !version.isEmpty()) { userDefinedVersion = version; } if (userDefinedVersion != null) { validateAppIdOrVersion(userDefinedVersion); arguments.add("-V"); arguments.add(userDefinedVersion); } else { if (!isEAR) { // EAR structure would need to define versions per service/module... throw new MojoExecutionException( "No <version> defined in appengine-web.xml, nor <version>" + " <configuration> defined in the pom.xml."); } } if (oauth2) { arguments.add("--oauth2"); } if (enableJarSplitting) { arguments.add("--enable_jar_splitting"); } if (jarSplittingExcludes != null && !jarSplittingExcludes.isEmpty()) { arguments.add("--jar_splitting_excludes=" + jarSplittingExcludes); } if (retainUploadDir) { arguments.add("--retain_upload_dir"); } if (compileEncoding) { arguments.add("--compile_encoding"); } if (numDays != null) { arguments.add("--num_days=" + numDays.toString()); } if (severity != null && !severity.isEmpty()) { arguments.add("--severity=" + severity); } if (append) { arguments.add("-a"); } if (numRuns != null) { arguments.add("--num_runs=" + numRuns.toString()); } if (force) { arguments.add("-f"); } if (deleteJsps) { arguments.add("--delete_jsps"); } if (enableJarClasses) { arguments.add("--enable_jar_classes"); } if (additionalParams != null) { for (String param : additionalParams) { if (param != null && !param.isEmpty()) { arguments.add(param); } } } return arguments; } private void validateAppIdOrVersion(String value) throws MojoExecutionException { boolean hasUppercase = !value.equals(value.toLowerCase()); if (hasUppercase) { throw new MojoExecutionException( "\nError: App Engine Application Id or version cannot contain uppercase: " + value); } // Support cases like google.com:foo, ie accept . only when there is a : if (!value.contains(":") && value.contains(".")) { throw new MojoExecutionException( "\nError: App Engine Application Id or version cannot contain dot: " + value); } } String getAppDir() { return mavenProject.getBuild().getDirectory() + "/" + mavenProject.getBuild().getFinalName(); } String getProjectId() throws MojoExecutionException { if (project != null) { return project; } if (appId != null) { return appId; } File f = new File(getAppDir(), "WEB-INF/appengine-web.xml"); if (f.exists()) { AppEngineWebXmlReader aewebReader = new AppEngineWebXmlReader(getAppDir()); AppEngineWebXml appEngineWebXml = aewebReader.readAppEngineWebXml(); return appEngineWebXml.getAppId(); } else if (EarHelper.isEar(getAppDir(), false)) { EarInfo earInfo = EarHelper.readEarInfo(getAppDir(), new File(Application.getSdkDocsDir(), "appengine-application.xsd")); return earInfo.getAppengineApplicationXml().getApplicationId(); } throw new MojoExecutionException( "No <application> defined in appengine-web.xml, nor <project>" + " <configuration> defined in the pom.xml."); } protected void resolveAndSetSdkRoot() throws MojoExecutionException { File sdkBaseDir = SdkResolver.getSdk(mavenProject, repoSystem, repoSession, pluginRepos, projectRepos); try { System.setProperty("appengine.sdk.root", sdkBaseDir.getCanonicalPath()); } catch (IOException e) { throw new MojoExecutionException("Could not open SDK zip archive.", e); } } }