Skip to content

Commit af962b4

Browse files
authored
Add GCF Compute sample (GoogleCloudPlatform#6432)
* Draft sample * Update tests * update directory * lint
1 parent a672f7a commit af962b4

3 files changed

Lines changed: 322 additions & 0 deletions

File tree

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<!--
4+
Copyright 2021 Google LLC
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
-->
18+
19+
<project xmlns="http://maven.apache.org/POM/4.0.0"
20+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
21+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
22+
<modelVersion>4.0.0</modelVersion>
23+
24+
<groupId>com.example.cloud.functions</groupId>
25+
<artifactId>label-compute-instance</artifactId>
26+
27+
<parent>
28+
<groupId>com.google.cloud.samples</groupId>
29+
<artifactId>shared-configuration</artifactId>
30+
<version>1.0.23</version>
31+
</parent>
32+
33+
<properties>
34+
<maven.compiler.target>11</maven.compiler.target>
35+
<maven.compiler.source>11</maven.compiler.source>
36+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
37+
</properties>
38+
39+
<dependencies>
40+
<!-- Required for Function primitives -->
41+
<dependency>
42+
<groupId>com.google.cloud.functions</groupId>
43+
<artifactId>functions-framework-api</artifactId>
44+
<version>1.0.4</version>
45+
<scope>provided</scope>
46+
</dependency>
47+
<dependency>
48+
<groupId>com.google.cloud</groupId>
49+
<artifactId>google-cloud-compute</artifactId>
50+
<version>1.5.0-alpha</version>
51+
</dependency>
52+
<dependency>
53+
<groupId>com.google.code.gson</groupId>
54+
<artifactId>gson</artifactId>
55+
<version>2.8.8</version>
56+
<scope>compile</scope>
57+
</dependency>
58+
<dependency>
59+
<groupId>com.google.protobuf</groupId>
60+
<artifactId>protobuf-java-util</artifactId>
61+
<version>4.0.0-rc-2</version>
62+
</dependency>
63+
64+
<!-- The following dependencies are only required for testing -->
65+
<dependency>
66+
<groupId>com.google.truth</groupId>
67+
<artifactId>truth</artifactId>
68+
<version>1.1.3</version>
69+
<scope>test</scope>
70+
</dependency>
71+
<dependency>
72+
<groupId>com.google.guava</groupId>
73+
<artifactId>guava-testlib</artifactId>
74+
<version>31.0-jre</version>
75+
<scope>test</scope>
76+
</dependency>
77+
<dependency>
78+
<groupId>io.cloudevents</groupId>
79+
<artifactId>cloudevents-core</artifactId>
80+
<version>2.2.0</version>
81+
<scope>test</scope>
82+
</dependency>
83+
</dependencies>
84+
85+
<build>
86+
<plugins>
87+
<plugin>
88+
<!--
89+
Google Cloud Functions Framework Maven plugin
90+
91+
This plugin allows you to run Cloud Functions Java code
92+
locally. Use the following terminal command to run a
93+
given function locally:
94+
95+
mvn function:run -Drun.functionTarget=your.package.yourFunction
96+
-->
97+
<groupId>com.google.cloud.functions</groupId>
98+
<artifactId>function-maven-plugin</artifactId>
99+
<version>0.10.0</version>
100+
<configuration>
101+
<functionTarget>functions.AutoLabelInstance</functionTarget>
102+
</configuration>
103+
</plugin>
104+
<plugin>
105+
<groupId>org.apache.maven.plugins</groupId>
106+
<artifactId>maven-surefire-plugin</artifactId>
107+
<!-- version 3.0.0-M4 does not load JUnit5 correctly -->
108+
<!-- see https://issues.apache.org/jira/browse/SUREFIRE-1750 -->
109+
<version>3.0.0-M5</version>
110+
<configuration>
111+
<includes>
112+
<include>**/*Test.java</include>
113+
</includes>
114+
<skipTests>${skipTests}</skipTests>
115+
<reportNameSuffix>sponge_log</reportNameSuffix>
116+
<trimStackTrace>false</trimStackTrace>
117+
</configuration>
118+
</plugin>
119+
</plugins>
120+
</build>
121+
</project>
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright 2021 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package functions;
18+
19+
import com.google.cloud.compute.v1.GetInstanceRequest;
20+
import com.google.cloud.compute.v1.Instance;
21+
import com.google.cloud.compute.v1.InstancesClient;
22+
import com.google.cloud.compute.v1.InstancesSetLabelsRequest;
23+
import com.google.cloud.compute.v1.SetLabelsInstanceRequest;
24+
import com.google.cloud.functions.CloudEventsFunction;
25+
import com.google.gson.Gson;
26+
import com.google.gson.JsonObject;
27+
import com.google.gson.JsonSyntaxException;
28+
import io.cloudevents.CloudEvent;
29+
import java.nio.charset.StandardCharsets;
30+
import java.util.logging.Logger;
31+
32+
public class AutoLabelInstance implements CloudEventsFunction {
33+
private static final Logger logger = Logger.getLogger(AutoLabelInstance.class.getName());
34+
35+
@Override
36+
public void accept(CloudEvent event) throws Exception {
37+
// Extract CloudEvent data
38+
if (event.getData() != null) {
39+
String cloudEventData = new String(event.getData().toBytes(), StandardCharsets.UTF_8);
40+
41+
// Convert data to JSON
42+
JsonObject eventData;
43+
try {
44+
Gson gson = new Gson();
45+
eventData = gson.fromJson(cloudEventData, JsonObject.class);
46+
} catch (JsonSyntaxException error) {
47+
throw new RuntimeException("CloudEvent data is not valid JSON: " + error.getMessage());
48+
}
49+
50+
// Extract the Cloud Audit Logging entry from the data's protoPayload
51+
JsonObject payload = eventData.getAsJsonObject("protoPayload");
52+
JsonObject auth = payload.getAsJsonObject("authenticationInfo");
53+
54+
// Extract the email address of the authenticated user
55+
// (or service account on behalf of third party principal) making the request
56+
String creator = auth.get("principalEmail").getAsString();
57+
if (creator == null) {
58+
throw new RuntimeException("`principalEmail` not found in protoPayload.");
59+
}
60+
// Format the 'creator' parameter to match GCE label validation requirements
61+
creator = creator.toLowerCase().replaceAll("\\W", "-");
62+
63+
// Get relevant VM instance details from the CloudEvent `subject` property
64+
// Example: compute.googleapis.com/projects/<PROJECT>/zones/<ZONE>/instances/<INSTANCE>
65+
String subject = event.getSubject();
66+
if (subject == null || subject == "") {
67+
throw new RuntimeException("Missing CloudEvent `subject`.");
68+
}
69+
String[] params = subject.split("/");
70+
71+
// Validate data
72+
if (params.length < 7) {
73+
throw new RuntimeException("Can not parse resource from CloudEvent `subject`: " + subject);
74+
}
75+
String project = params[2];
76+
String zone = params[4];
77+
String instanceName = params[6];
78+
79+
// Instantiate the Compute Instances client
80+
try (InstancesClient instancesClient = InstancesClient.create()) {
81+
// Get the newly-created VM instance's label fingerprint
82+
// This is required by the Compute Engine API to prevent duplicate labels
83+
GetInstanceRequest getInstanceRequest =
84+
GetInstanceRequest.newBuilder()
85+
.setInstance(instanceName)
86+
.setProject(project)
87+
.setZone(zone)
88+
.build();
89+
Instance instance = instancesClient.get(getInstanceRequest);
90+
String fingerPrint = instance.getLabelFingerprint();
91+
92+
// Label the instance with its creator
93+
SetLabelsInstanceRequest setLabelRequest =
94+
SetLabelsInstanceRequest.newBuilder()
95+
.setInstance(instanceName)
96+
.setProject(project)
97+
.setZone(zone)
98+
.setInstancesSetLabelsRequestResource(
99+
InstancesSetLabelsRequest.newBuilder()
100+
.putLabels("creator", creator)
101+
.setLabelFingerprint(fingerPrint)
102+
.build())
103+
.build();
104+
105+
instancesClient.setLabels(setLabelRequest);
106+
logger.info(
107+
String.format(
108+
"Adding label, \"{'creator': '%s'}\", to instance, \"%s\".",
109+
creator, instanceName));
110+
} catch (Exception error) {
111+
throw new RuntimeException(
112+
String.format(
113+
"Error trying to label VM instance, %s: %s", instanceName, error.toString()));
114+
}
115+
}
116+
}
117+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright 2021 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package functions;
18+
19+
import static com.google.common.truth.Truth.assertThat;
20+
21+
import com.google.common.testing.TestLogHandler;
22+
import com.google.gson.Gson;
23+
import com.google.gson.JsonObject;
24+
import io.cloudevents.CloudEvent;
25+
import io.cloudevents.core.builder.CloudEventBuilder;
26+
import java.net.URI;
27+
import java.util.logging.Logger;
28+
import org.junit.BeforeClass;
29+
import org.junit.Test;
30+
import org.junit.runner.RunWith;
31+
import org.junit.runners.JUnit4;
32+
33+
@RunWith(JUnit4.class)
34+
public class AutoLabelInstanceTest {
35+
private static final Logger logger = Logger.getLogger(AutoLabelInstance.class.getName());
36+
private static final TestLogHandler logHandler = new TestLogHandler();
37+
38+
private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT");
39+
private static final String ZONE = "us-central1-a";
40+
private static final String INSTANCE = "lite1";
41+
42+
@BeforeClass
43+
public static void beforeClass() {
44+
logger.addHandler(logHandler);
45+
}
46+
47+
@Test
48+
public void functionsAutoLabelInstance() throws Exception {
49+
// Build a CloudEvent Log Entry
50+
JsonObject protoPayload = new JsonObject();
51+
JsonObject authInfo = new JsonObject();
52+
String email = "test@gmail.com";
53+
authInfo.addProperty("principalEmail", email);
54+
55+
protoPayload.add("authenticationInfo", authInfo);
56+
57+
String resource =
58+
String.format(
59+
"compute.googleapis.com/projects/%s/zones/%s/instances/%s", PROJECT_ID, ZONE, INSTANCE);
60+
protoPayload.addProperty("resourceName", resource);
61+
protoPayload.addProperty("methodName", "beta.compute.instances.insert");
62+
63+
JsonObject encodedData = new JsonObject();
64+
encodedData.add("protoPayload", protoPayload);
65+
encodedData.addProperty("name", "test name");
66+
67+
CloudEvent event =
68+
CloudEventBuilder.v1()
69+
.withId("0")
70+
.withSubject(resource)
71+
.withType("google.cloud.audit.log.v1.written")
72+
.withSource(URI.create("https://example.com"))
73+
.withData(new Gson().toJson(encodedData).getBytes())
74+
.build();
75+
76+
new AutoLabelInstance().accept(event);
77+
78+
assertThat(
79+
String.format(
80+
"Adding label, \"{'creator': '%s'}\", to instance, \"%s\".",
81+
"test-gmail-com", INSTANCE))
82+
.isEqualTo(logHandler.getStoredLogRecords().get(0).getMessage());
83+
}
84+
}

0 commit comments

Comments
 (0)