Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions functions/v2/label-compute-instance/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?xml version="1.0" encoding="UTF-8"?>

<!--
Copyright 2021 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.example.cloud.functions</groupId>
<artifactId>label-compute-instance</artifactId>

<parent>
<groupId>com.google.cloud.samples</groupId>
<artifactId>shared-configuration</artifactId>
<version>1.0.23</version>
</parent>

<properties>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<!-- Required for Function primitives -->
<dependency>
<groupId>com.google.cloud.functions</groupId>
<artifactId>functions-framework-api</artifactId>
<version>1.0.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-compute</artifactId>
<version>1.5.0-alpha</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.8</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>4.0.0-rc-2</version>
</dependency>

<!-- The following dependencies are only required for testing -->
<dependency>
<groupId>com.google.truth</groupId>
<artifactId>truth</artifactId>
<version>1.1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-testlib</artifactId>
<version>31.0-jre</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cloudevents</groupId>
<artifactId>cloudevents-core</artifactId>
<version>2.2.0</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<!--
Google Cloud Functions Framework Maven plugin

This plugin allows you to run Cloud Functions Java code
locally. Use the following terminal command to run a
given function locally:

mvn function:run -Drun.functionTarget=your.package.yourFunction
-->
<groupId>com.google.cloud.functions</groupId>
<artifactId>function-maven-plugin</artifactId>
<version>0.10.0</version>
<configuration>
<functionTarget>functions.AutoLabelInstance</functionTarget>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<!-- version 3.0.0-M4 does not load JUnit5 correctly -->
<!-- see https://issues.apache.org/jira/browse/SUREFIRE-1750 -->
<version>3.0.0-M5</version>
<configuration>
<includes>
<include>**/*Test.java</include>
</includes>
<skipTests>${skipTests}</skipTests>
<reportNameSuffix>sponge_log</reportNameSuffix>
<trimStackTrace>false</trimStackTrace>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package functions;

import com.google.cloud.compute.v1.GetInstanceRequest;
import com.google.cloud.compute.v1.Instance;
import com.google.cloud.compute.v1.InstancesClient;
import com.google.cloud.compute.v1.InstancesSetLabelsRequest;
import com.google.cloud.compute.v1.SetLabelsInstanceRequest;
import com.google.cloud.functions.CloudEventsFunction;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import io.cloudevents.CloudEvent;
import java.nio.charset.StandardCharsets;
import java.util.logging.Logger;

public class AutoLabelInstance implements CloudEventsFunction {
private static final Logger logger = Logger.getLogger(AutoLabelInstance.class.getName());

@Override
public void accept(CloudEvent event) throws Exception {
// Extract CloudEvent data
if (event.getData() != null) {
String cloudEventData = new String(event.getData().toBytes(), StandardCharsets.UTF_8);

// Convert data to JSON
JsonObject eventData;
try {
Gson gson = new Gson();
eventData = gson.fromJson(cloudEventData, JsonObject.class);
} catch (JsonSyntaxException error) {
throw new RuntimeException("CloudEvent data is not valid JSON: " + error.getMessage());
}

// Extract the Cloud Audit Logging entry from the data's protoPayload
JsonObject payload = eventData.getAsJsonObject("protoPayload");
JsonObject auth = payload.getAsJsonObject("authenticationInfo");

// Extract the email address of the authenticated user
// (or service account on behalf of third party principal) making the request
String creator = auth.get("principalEmail").getAsString();
if (creator == null) {
throw new RuntimeException("`principalEmail` not found in protoPayload.");
}
// Format the 'creator' parameter to match GCE label validation requirements
creator = creator.toLowerCase().replaceAll("\\W", "-");

// Get relevant VM instance details from the CloudEvent `subject` property
// Example: compute.googleapis.com/projects/<PROJECT>/zones/<ZONE>/instances/<INSTANCE>
String subject = event.getSubject();
if (subject == null || subject == "") {
throw new RuntimeException("Missing CloudEvent `subject`.");
}
String[] params = subject.split("/");

// Validate data
if (params.length < 7) {
throw new RuntimeException("Can not parse resource from CloudEvent `subject`: " + subject);
}
String project = params[2];
String zone = params[4];
String instanceName = params[6];

// Instantiate the Compute Instances client
try (InstancesClient instancesClient = InstancesClient.create()) {
// Get the newly-created VM instance's label fingerprint
// This is required by the Compute Engine API to prevent duplicate labels
GetInstanceRequest getInstanceRequest =
GetInstanceRequest.newBuilder()
.setInstance(instanceName)
.setProject(project)
.setZone(zone)
.build();
Instance instance = instancesClient.get(getInstanceRequest);
String fingerPrint = instance.getLabelFingerprint();

// Label the instance with its creator
SetLabelsInstanceRequest setLabelRequest =
SetLabelsInstanceRequest.newBuilder()
.setInstance(instanceName)
.setProject(project)
.setZone(zone)
.setInstancesSetLabelsRequestResource(
InstancesSetLabelsRequest.newBuilder()
.putLabels("creator", creator)
.setLabelFingerprint(fingerPrint)
.build())
.build();

instancesClient.setLabels(setLabelRequest);
logger.info(
String.format(
"Adding label, \"{'creator': '%s'}\", to instance, \"%s\".",
creator, instanceName));
} catch (Exception error) {
throw new RuntimeException(
String.format(
"Error trying to label VM instance, %s: %s", instanceName, error.toString()));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package functions;

import static com.google.common.truth.Truth.assertThat;

import com.google.common.testing.TestLogHandler;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import io.cloudevents.CloudEvent;
import io.cloudevents.core.builder.CloudEventBuilder;
import java.net.URI;
import java.util.logging.Logger;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public class AutoLabelInstanceTest {
private static final Logger logger = Logger.getLogger(AutoLabelInstance.class.getName());
private static final TestLogHandler logHandler = new TestLogHandler();

private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT");
private static final String ZONE = "us-central1-a";
private static final String INSTANCE = "lite1";

@BeforeClass
public static void beforeClass() {
logger.addHandler(logHandler);
}

@Test
public void functionsAutoLabelInstance() throws Exception {
// Build a CloudEvent Log Entry
JsonObject protoPayload = new JsonObject();
JsonObject authInfo = new JsonObject();
String email = "test@gmail.com";
authInfo.addProperty("principalEmail", email);

protoPayload.add("authenticationInfo", authInfo);

String resource =
String.format(
"compute.googleapis.com/projects/%s/zones/%s/instances/%s", PROJECT_ID, ZONE, INSTANCE);
protoPayload.addProperty("resourceName", resource);
protoPayload.addProperty("methodName", "beta.compute.instances.insert");

JsonObject encodedData = new JsonObject();
encodedData.add("protoPayload", protoPayload);
encodedData.addProperty("name", "test name");

CloudEvent event =
CloudEventBuilder.v1()
.withId("0")
.withSubject(resource)
.withType("google.cloud.audit.log.v1.written")
.withSource(URI.create("https://example.com"))
.withData(new Gson().toJson(encodedData).getBytes())
.build();

new AutoLabelInstance().accept(event);

assertThat(
String.format(
"Adding label, \"{'creator': '%s'}\", to instance, \"%s\".",
"test-gmail-com", INSTANCE))
.isEqualTo(logHandler.getStoredLogRecords().get(0).getMessage());
}
}