|
| 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