Skip to content

Commit 9644a41

Browse files
ludocheamonnmcmanus
authored andcommitted
Add a mvn function:deploy goal to deploy a Java function.
We are reusing the Cloud SDK logic already available in the new App Engine Maven plugin to get a Cloud SDK and update it automatically when needed. PiperOrigin-RevId: 297620209
1 parent 33a43dd commit 9644a41

File tree

3 files changed

+421
-0
lines changed

3 files changed

+421
-0
lines changed

invoker/function-maven-plugin/pom.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,19 @@
4747
<version>1.0.0-alpha-2-rc4-SNAPSHOT</version>
4848
</dependency>
4949

50+
<dependency>
51+
<groupId>com.google.cloud.tools</groupId>
52+
<artifactId>appengine-maven-plugin</artifactId>
53+
<version>2.2.0</version>
54+
<type>jar</type>
55+
</dependency>
56+
57+
<dependency>
58+
<groupId>com.google.truth</groupId>
59+
<artifactId>truth</artifactId>
60+
<version>1.0.1</version>
61+
<scope>test</scope>
62+
</dependency>
5063
<dependency>
5164
<groupId>junit</groupId>
5265
<artifactId>junit</artifactId>
Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
package com.google.cloud.functions.plugin;
2+
3+
import com.google.cloud.tools.appengine.operations.CloudSdk;
4+
import com.google.cloud.tools.appengine.operations.Gcloud;
5+
import com.google.cloud.tools.appengine.operations.cloudsdk.CloudSdkNotFoundException;
6+
import com.google.cloud.tools.appengine.operations.cloudsdk.CloudSdkOutOfDateException;
7+
import com.google.cloud.tools.appengine.operations.cloudsdk.CloudSdkVersionFileException;
8+
import com.google.cloud.tools.appengine.operations.cloudsdk.process.ProcessHandlerException;
9+
import com.google.cloud.tools.managedcloudsdk.BadCloudSdkVersionException;
10+
import com.google.cloud.tools.managedcloudsdk.ManagedCloudSdk;
11+
import com.google.cloud.tools.managedcloudsdk.UnsupportedOsException;
12+
import com.google.cloud.tools.managedcloudsdk.Version;
13+
import com.google.cloud.tools.maven.cloudsdk.CloudSdkChecker;
14+
import com.google.cloud.tools.maven.cloudsdk.CloudSdkDownloader;
15+
import com.google.cloud.tools.maven.cloudsdk.CloudSdkMojo;
16+
import com.google.common.base.Joiner;
17+
import com.google.common.base.Strings;
18+
import java.io.IOException;
19+
import java.util.ArrayList;
20+
import java.util.Collections;
21+
import java.util.List;
22+
import java.util.Map;
23+
import java.util.function.Function;
24+
import java.util.logging.Level;
25+
import java.util.logging.Logger;
26+
import org.apache.maven.plugin.MojoExecutionException;
27+
import org.apache.maven.plugins.annotations.Execute;
28+
import org.apache.maven.plugins.annotations.LifecyclePhase;
29+
import org.apache.maven.plugins.annotations.Mojo;
30+
import org.apache.maven.plugins.annotations.Parameter;
31+
import org.apache.maven.plugins.annotations.ResolutionScope;
32+
33+
/** Deploy a Java function via mvn functions:deploy with optional flags. */
34+
@Mojo(
35+
name = "deploy",
36+
defaultPhase = LifecyclePhase.GENERATE_RESOURCES,
37+
requiresDependencyResolution = ResolutionScope.NONE,
38+
requiresDependencyCollection = ResolutionScope.NONE)
39+
@Execute(phase = LifecyclePhase.NONE)
40+
public class DeployFunction extends CloudSdkMojo {
41+
42+
/** The Google Cloud Platform project Id to use for this invocation. */
43+
@Parameter(alias = "deploy.projectId", property = "function.deploy.projectId")
44+
protected String projectId;
45+
46+
/**
47+
* ID of the function or fully qualified identifier for the function. This property must be
48+
* specified if any of the other arguments in this group are specified.
49+
*/
50+
@Parameter(alias = "deploy.name", property = "function.deploy.name", required = true)
51+
String name;
52+
53+
/**
54+
* The Cloud region for the function. Overrides the default functions/region property value for
55+
* this command invocation.
56+
*/
57+
@Parameter(alias = "deploy.region", property = "function.deploy.region")
58+
String region;
59+
60+
/**
61+
* If set, makes this a public function. This will allow all callers, without checking
62+
* authentication.
63+
*/
64+
@Parameter(
65+
alias = "deploy.allowunauthenticated",
66+
property = "function.deploy.allowunauthenticated",
67+
defaultValue = "false")
68+
boolean allowUnauthenticated;
69+
70+
/**
71+
* Name of a Google Cloud Function (as defined in source code) that will be executed. Defaults to
72+
* the resource name suffix, if not specified.
73+
*
74+
* <p>For Java this is fully qualified class name implementing the function, for example
75+
* `com.google.testfunction.HelloWorld`.
76+
*/
77+
@Parameter(alias = "deploy.functiontarget", property = "function.deploy.functiontarget")
78+
String functionTarget;
79+
80+
/** Override the .gcloudignore file and use the specified file instead. */
81+
@Parameter(alias = "deploy.ignorefile", property = "function.deploy.ignorefile")
82+
String ignoreFile;
83+
84+
/**
85+
* Limit on the amount of memory the function can use.
86+
*
87+
* <p>Allowed values are: 128MB, 256MB, 512MB, 1024MB, and 2048MB. By default, a new function is
88+
* limited to 256MB of memory. When deploying an update to an existing function, the function will
89+
* keep its old memory limit unless you specify this flag.
90+
*/
91+
@Parameter(alias = "deploy.memory", property = "function.deploy.memory")
92+
String memory;
93+
94+
/** If specified, then the function will be retried in case of a failure. */
95+
@Parameter(alias = "deploy.retry", property = "function.deploy.retry")
96+
String retry;
97+
98+
/**
99+
* Runtime in which to run the function.
100+
*
101+
* <p>Required when deploying a new function; optional when updating an existing function. Default
102+
* to Java11.
103+
*/
104+
@Parameter(
105+
alias = "deploy.runtime",
106+
defaultValue = "java11",
107+
property = "function.deploy.runtime")
108+
String runtime = "java11";
109+
110+
/**
111+
* The email address of the IAM service account associated with the function at runtime. The
112+
* service account represents the identity of the running function, and determines what
113+
* permissions the function has.
114+
*
115+
* <p>If not provided, the function will use the project's default service account.
116+
*/
117+
@Parameter(alias = "deploy.serviceaccount", property = "function.deploy.serviceaccount")
118+
String serviceAccount;
119+
120+
/** Location of source code to deploy. */
121+
@Parameter(alias = "deploy.source", property = "function.deploy.source")
122+
String source;
123+
124+
/**
125+
* This flag's value is the name of the Google Cloud Storage bucket in which source code will be
126+
* stored.
127+
*/
128+
@Parameter(alias = "deploy.stagebucket", property = "function.deploy.stagebucket")
129+
String stageBucket;
130+
131+
/**
132+
* The function execution timeout, e.g. 30s for 30 seconds. Defaults to original value for
133+
* existing function or 60 seconds for new functions. Cannot be more than 540s.
134+
*/
135+
@Parameter(alias = "deploy.timeout", property = "function.deploy.timeout")
136+
String timeout;
137+
138+
/**
139+
* List of label KEY=VALUE pairs to update. If a label exists its value is modified, otherwise a
140+
* new label is created.
141+
*/
142+
@Parameter(alias = "deploy.updatelabels", property = "function.deploy.updatelabels")
143+
List<String> updateLabels;
144+
145+
/**
146+
* Function will be assigned an endpoint, which you can view by using the describe command. Any
147+
* HTTP request (of a supported type) to the endpoint will trigger function execution. Supported
148+
* HTTP request types are: POST, PUT, GET, DELETE, and OPTIONS.
149+
*/
150+
@Parameter(alias = "deploy.triggerhttp", property = "function.deploy.triggerhttp")
151+
Boolean triggerHttp;
152+
153+
/**
154+
* Name of Pub/Sub topic. Every message published in this topic will trigger function execution
155+
* with message contents passed as input data.
156+
*/
157+
@Parameter(alias = "deploy.triggertopic", property = "function.deploy.triggertopic")
158+
String triggerTopic;
159+
160+
/**
161+
* Specifies which action should trigger the function. For a list of acceptable values, call
162+
* gcloud functions event-types list.
163+
*/
164+
@Parameter(alias = "deploy.triggerevent", property = "function.deploy.triggerevent")
165+
String triggerEvent;
166+
167+
/**
168+
* Specifies which resource from {@link #triggerEvent} is being observed. E.g. if {@link
169+
* #triggerEvent} is providers/cloud.storage/eventTypes/object.change, {@link #triggerResource}
170+
* must be a bucket name. For a list of expected resources, run {@code gcloud functions
171+
* event-types list}.
172+
*/
173+
@Parameter(alias = "deploy.triggerresource", property = "function.deploy.triggerresource")
174+
String triggerResource;
175+
176+
/**
177+
* The VPC Access connector that the function can connect to. It can be either the fully-qualified
178+
* URI, or the short name of the VPC Access connector resource. If the short name is used, the
179+
* connector must belong to the same project. The format of this field is either
180+
* projects/${PROJECT}/locations/${LOCATION}/connectors/${CONNECTOR} or ${CONNECTOR}, where
181+
* ${CONNECTOR} is the short name of the VPC Access connector.
182+
*/
183+
@Parameter(alias = "deploy.vpcconnector", property = "function.deploy.vpcconnector")
184+
String vpcConnector;
185+
/**
186+
* Sets the maximum number of instances for the function. A function execution that would exceed
187+
* max-instances times out.
188+
*/
189+
@Parameter(alias = "deploy.maxinstances", property = "function.deploy.maxinstances")
190+
Integer maxInstances;
191+
/**
192+
* List of key-value pairs to set as environment variables. All existing environment variables
193+
* will be removed first.
194+
*/
195+
@Parameter(alias = "deploy.setenvvars", property = "function.deploy.setenvvars")
196+
Map<String, String> environmentVariables;
197+
/**
198+
* Path to a local YAML file with definitions for all environment variables. All existing
199+
* environment variables will be removed before the new environment variables are added.
200+
*/
201+
@Parameter(alias = "deploy.envvarsfile", property = "function.deploy.envvarsfile")
202+
String envVarsFile;
203+
204+
boolean hasEnvVariables() {
205+
return (this.environmentVariables != null && !this.environmentVariables.isEmpty());
206+
}
207+
208+
// Select a downloaded Cloud SDK or a user defined Cloud SDK version.
209+
static Function<String, ManagedCloudSdk> newManagedSdkFactory() {
210+
return version -> {
211+
try {
212+
if (Strings.isNullOrEmpty(version)) {
213+
return ManagedCloudSdk.newManagedSdk();
214+
} else {
215+
return ManagedCloudSdk.newManagedSdk(new Version(version));
216+
}
217+
} catch (UnsupportedOsException | BadCloudSdkVersionException ex) {
218+
throw new RuntimeException(ex);
219+
}
220+
};
221+
}
222+
223+
CloudSdk buildCloudSdkMinimal() {
224+
return buildCloudSdk(
225+
(CloudSdkMojo) this, new CloudSdkChecker(), new CloudSdkDownloader(newManagedSdkFactory()));
226+
}
227+
228+
static CloudSdk buildCloudSdk(
229+
CloudSdkMojo mojo, CloudSdkChecker cloudSdkChecker, CloudSdkDownloader cloudSdkDownloader) {
230+
231+
try {
232+
if (mojo.getCloudSdkHome() != null) {
233+
// Check if the user has defined a specific Cloud SDK.
234+
CloudSdk cloudSdk = new CloudSdk.Builder().sdkPath(mojo.getCloudSdkHome()).build();
235+
236+
if (mojo.getCloudSdkVersion() != null) {
237+
cloudSdkChecker.checkCloudSdk(cloudSdk, mojo.getCloudSdkVersion());
238+
}
239+
240+
return cloudSdk;
241+
} else {
242+
243+
return new CloudSdk.Builder()
244+
.sdkPath(
245+
cloudSdkDownloader.downloadIfNecessary(
246+
mojo.getCloudSdkVersion(),
247+
mojo.getLog(),
248+
Collections.emptyList(),
249+
mojo.getMavenSession().isOffline()))
250+
.build();
251+
}
252+
} catch (CloudSdkNotFoundException
253+
| CloudSdkOutOfDateException
254+
| CloudSdkVersionFileException ex) {
255+
throw new RuntimeException(ex);
256+
}
257+
}
258+
259+
/** Return a Gcloud instance using global configuration. */
260+
public Gcloud getGcloud() {
261+
return Gcloud.builder(buildCloudSdkMinimal())
262+
.setMetricsEnvironment(this.getArtifactId(), this.getArtifactVersion())
263+
.setCredentialFile(this.getServiceAccountKeyFile())
264+
.build();
265+
}
266+
267+
/** Return the list of command parameters to give to the Cloud SDK for execution */
268+
public List<String> getCommands() {
269+
List<String> commands = new ArrayList<>();
270+
271+
commands.add("functions");
272+
commands.add("deploy");
273+
commands.add(name);
274+
if (region != null) {
275+
commands.add("--region=" + region);
276+
}
277+
if (triggerResource == null && triggerTopic == null && triggerEvent == null) {
278+
commands.add("--trigger-http");
279+
}
280+
if (triggerResource != null) {
281+
commands.add("--trigger-resource=" + triggerResource);
282+
}
283+
if (triggerTopic != null) {
284+
commands.add("--trigger-topic=" + triggerTopic);
285+
}
286+
if (triggerEvent != null) {
287+
commands.add("--trigger-event=" + triggerEvent);
288+
}
289+
if (allowUnauthenticated) {
290+
commands.add("--allow-unauthenticated");
291+
}
292+
if (functionTarget != null) {
293+
commands.add("--entry-point=" + functionTarget);
294+
}
295+
if (ignoreFile != null) {
296+
commands.add("--ignore-file=" + ignoreFile);
297+
}
298+
if (memory != null) {
299+
commands.add("--memory=" + memory);
300+
}
301+
if (retry != null) {
302+
commands.add("--retry=" + retry);
303+
}
304+
if (serviceAccount != null) {
305+
commands.add("--service-account=" + serviceAccount);
306+
}
307+
if (source != null) {
308+
commands.add("--source=" + source);
309+
}
310+
if (stageBucket != null) {
311+
commands.add("--stage-bucket=" + stageBucket);
312+
}
313+
if (timeout != null) {
314+
commands.add("--timeout=" + timeout);
315+
}
316+
if (updateLabels != null && !updateLabels.isEmpty()) {
317+
commands.add("--update-labels=" + String.join(",", updateLabels));
318+
}
319+
320+
if (vpcConnector != null) {
321+
commands.add("--vpc-connector=" + vpcConnector);
322+
}
323+
if (maxInstances != null) {
324+
commands.add("--max-instances=" + maxInstances);
325+
}
326+
327+
if (hasEnvVariables()) {
328+
Joiner.MapJoiner mapJoiner = Joiner.on(",").withKeyValueSeparator("=");
329+
commands.add("--set-env-vars=" + mapJoiner.join(environmentVariables));
330+
}
331+
if (envVarsFile != null) {
332+
commands.add("--env-var-file=" + envVarsFile);
333+
}
334+
commands.add("--runtime=" + runtime);
335+
336+
if (projectId != null) {
337+
commands.add("--project=" + projectId);
338+
}
339+
return Collections.unmodifiableList(commands);
340+
}
341+
342+
@Override
343+
public void execute() throws MojoExecutionException {
344+
try {
345+
Gcloud gcloud = getGcloud();
346+
List<String> params = getCommands();
347+
System.out.println("Executing Cloud SDK command: gcloud " + String.join(" ", params));
348+
gcloud.runCommand(params);
349+
} catch (CloudSdkNotFoundException | IOException | ProcessHandlerException ex) {
350+
Logger.getLogger(DeployFunction.class.getName()).log(Level.SEVERE, null, ex);
351+
}
352+
}
353+
}

0 commit comments

Comments
 (0)