Skip to content

Commit 7a7d297

Browse files
authored
[Cloud Run] System Packages (GoogleCloudPlatform#1610)
* draft * Update comments/linting * Add test and lint * remove print * fix linting * Fix lint * Remove shaded
1 parent 506c8e7 commit 7a7d297

File tree

6 files changed

+336
-0
lines changed

6 files changed

+336
-0
lines changed

run/system-package/.dockerignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Dockerfile
2+
.dockerignore
3+
target

run/system-package/Dockerfile

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Copyright 2019 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# Use the official maven/Java 11 image to create a build artifact.
16+
# https://hub.docker.com/_/maven
17+
FROM maven:3.6.2-jdk-11-slim as builder
18+
19+
# Copy local code to the container image.
20+
WORKDIR /app
21+
COPY pom.xml .
22+
COPY src ./src
23+
24+
# Build a release artifact.
25+
RUN mvn compile assembly:single
26+
27+
# Use the Official OpenJDK image for a lean production stage of our multi-stage build.
28+
# https://hub.docker.com/r/adoptopenjdk/openjdk11/
29+
# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
30+
FROM adoptopenjdk/openjdk11:alpine
31+
32+
# [START run_system_package_alpine]
33+
RUN apk --no-cache add graphviz ttf-ubuntu-font-family
34+
# [END run_system_package_alpine]
35+
36+
# Copy the jar to the production image from the builder stage.
37+
COPY --from=builder /app/target/system-package-*.jar /system-package.jar
38+
39+
# Run the web service on container startup.
40+
CMD ["java","-jar","/system-package.jar"]

run/system-package/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Cloud Run System Package Sample
2+
3+
This sample shows how to use a CLI tool installed as a system package as part of a web service.
4+
5+
Use it with the [Using system packages tutorial](https://cloud.google.com/run/docs/tutorials/system-packages).
6+
7+
For more details on how to work with this sample read the [Google Cloud Run Java Samples README](https://github.com/GoogleCloudPlatform/java-docs-samples/run).
8+
9+
## Dependencies
10+
11+
* **Spark**: Web server framework.
12+
* **Junit**: [development] Test running framework.

run/system-package/pom.xml

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
Copyright 2019 Google LLC
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+
http://www.apache.org/licenses/LICENSE-2.0
8+
Unless required by applicable law or agreed to in writing, software
9+
distributed under the License is distributed on an "AS IS" BASIS,
10+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
See the License for the specific language governing permissions and
12+
limitations under the License.
13+
-->
14+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
15+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
16+
<modelVersion>4.0.0</modelVersion>
17+
<groupId>com.example.cloudrun</groupId>
18+
<artifactId>system-package</artifactId>
19+
<version>1.0-SNAPSHOT</version>
20+
21+
<!--
22+
The parent pom defines common style checks and testing strategies for our samples.
23+
Removing or replacing it should not affect the execution of the samples in anyway.
24+
-->
25+
<parent>
26+
<groupId>com.google.cloud.samples</groupId>
27+
<artifactId>shared-configuration</artifactId>
28+
<version>1.0.11</version>
29+
</parent>
30+
31+
<properties>
32+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
33+
<maven.compiler.source>11</maven.compiler.source>
34+
<maven.compiler.target>11</maven.compiler.target>
35+
</properties>
36+
37+
<dependencies>
38+
<dependency>
39+
<groupId>com.sparkjava</groupId>
40+
<artifactId>spark-core</artifactId>
41+
<version>2.8.0</version>
42+
</dependency>
43+
<dependency>
44+
<groupId>org.slf4j</groupId>
45+
<artifactId>slf4j-simple</artifactId>
46+
<version>1.7.26</version>
47+
</dependency>
48+
<dependency>
49+
<groupId>junit</groupId>
50+
<artifactId>junit</artifactId>
51+
<version>4.11</version>
52+
<scope>test</scope>
53+
</dependency>
54+
</dependencies>
55+
56+
<build>
57+
<plugins>
58+
<plugin>
59+
<groupId>org.apache.maven.plugins</groupId>
60+
<artifactId>maven-surefire-plugin</artifactId>
61+
<version>2.18.1</version>
62+
<configuration>
63+
<redirectTestOutputToFile>true</redirectTestOutputToFile>
64+
</configuration>
65+
</plugin>
66+
<plugin>
67+
<artifactId>maven-compiler-plugin</artifactId>
68+
<version>3.8.0</version>
69+
</plugin>
70+
<plugin>
71+
<artifactId>maven-assembly-plugin</artifactId>
72+
<configuration>
73+
<archive>
74+
<manifest>
75+
<mainClass>com.example.cloudrun.App</mainClass>
76+
</manifest>
77+
</archive>
78+
<descriptorRefs>
79+
<descriptorRef>jar-with-dependencies</descriptorRef>
80+
</descriptorRefs>
81+
</configuration>
82+
</plugin>
83+
</plugins>
84+
</build>
85+
</project>
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright 2019 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 com.example.cloudrun;
18+
19+
import static spark.Spark.get;
20+
import static spark.Spark.port;
21+
22+
import java.io.InputStream;
23+
import java.io.OutputStream;
24+
import java.io.OutputStreamWriter;
25+
import java.io.Writer;
26+
import java.util.ArrayList;
27+
import java.util.List;
28+
29+
public class App {
30+
public static void main(String[] args) {
31+
int port = Integer.parseInt(System.getenv().getOrDefault("PORT", "8080"));
32+
port(port);
33+
// [START run_system_package_handler]
34+
get(
35+
"/diagram.png",
36+
(req, res) -> {
37+
InputStream image = null;
38+
try {
39+
String dot = req.queryParams("dot");
40+
image = createDiagram(dot);
41+
res.header("Content-Type", "image/png");
42+
res.header("Content-Length", Integer.toString(image.available()));
43+
res.header("Cache-Control", "public, max-age=86400");
44+
} catch (Exception e) {
45+
if (e.getMessage().contains("syntax")) {
46+
res.status(400);
47+
return String.format("Bad Request: %s", e.getMessage());
48+
} else {
49+
res.status(500);
50+
return "Internal Server Error";
51+
}
52+
}
53+
return image;
54+
});
55+
// [END run_system_package_handler]
56+
}
57+
58+
// [START run_system_package_exec]
59+
// Generate a diagram based on a graphviz DOT diagram description.
60+
public static InputStream createDiagram(String dot) {
61+
if (dot == null || dot.isEmpty()) {
62+
throw new NullPointerException("syntax: no graphviz definition provided");
63+
}
64+
// Adds a watermark to the dot graphic.
65+
List<String> args = new ArrayList<String>();
66+
args.add("/usr/bin/dot");
67+
args.add("-Glabel=\"Made on Cloud Run\"");
68+
args.add("-Gfontsize=10");
69+
args.add("-Glabeljust=right");
70+
args.add("-Glabelloc=bottom");
71+
args.add("-Gfontcolor=gray");
72+
args.add("-Tpng");
73+
74+
StringBuilder output = new StringBuilder();
75+
InputStream stdout = null;
76+
try {
77+
ProcessBuilder pb = new ProcessBuilder(args);
78+
Process process = pb.start();
79+
OutputStream stdin = process.getOutputStream();
80+
stdout = process.getInputStream();
81+
// The Graphviz dot program reads from stdin.
82+
Writer writer = new OutputStreamWriter(stdin, "UTF-8");
83+
writer.write(dot);
84+
writer.close();
85+
process.waitFor();
86+
} catch (Exception e) {
87+
System.out.println(e);
88+
}
89+
return stdout;
90+
}
91+
// [END run_system_package_exec]
92+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2019 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 com.example.cloudrun;
18+
19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertTrue;
21+
import static spark.Spark.awaitInitialization;
22+
import static spark.Spark.stop;
23+
24+
import java.io.IOException;
25+
import java.net.HttpURLConnection;
26+
import java.net.URL;
27+
import org.junit.AfterClass;
28+
import org.junit.BeforeClass;
29+
import org.junit.Test;
30+
import spark.utils.IOUtils;
31+
32+
public class AppTest {
33+
34+
private static String BASE_URL = "/diagram.png";
35+
private static String DOT = "?dot=";
36+
37+
@BeforeClass
38+
public static void beforeClass() {
39+
App app = new App();
40+
app.main(new String[] {});
41+
awaitInitialization();
42+
}
43+
44+
@AfterClass
45+
public static void afterClass() {
46+
stop();
47+
}
48+
49+
@Test
50+
public void shouldFailWithNoQuery() {
51+
try {
52+
TestResponse response = executeRequest("GET", BASE_URL);
53+
assertEquals(true, response);
54+
} catch (IOException e) {
55+
assertTrue(e.getMessage().startsWith("Server returned HTTP response code: 400 for URL"));
56+
}
57+
}
58+
59+
@Test
60+
public void shouldFailWithEmptyDotParam() {
61+
try {
62+
executeRequest("GET", BASE_URL + DOT);
63+
} catch (IOException e) {
64+
assertTrue(e.getMessage().startsWith("Server returned HTTP response code: 400 for URL"));
65+
}
66+
}
67+
68+
@Test
69+
public void shouldFailWithInvalidPayload() {
70+
try {
71+
executeRequest("GET", BASE_URL + DOT + "digraph");
72+
} catch (IOException e) {
73+
assertTrue(e.getMessage().startsWith("Server returned HTTP response code: 400 for URL"));
74+
}
75+
}
76+
77+
@Test
78+
public void shouldSucceed() throws IOException {
79+
String query = "digraph%20G%20{%20A%20->%20{B,%20C,%20D}%20->%20{F}%20}";
80+
TestResponse response = executeRequest("GET", BASE_URL + DOT + query);
81+
assertEquals(200, response.status);
82+
}
83+
84+
private static TestResponse executeRequest(String method, String path) throws IOException {
85+
URL url = new URL("http://localhost:8080" + path);
86+
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
87+
connection.setRequestMethod(method);
88+
connection.setDoOutput(true);
89+
connection.connect();
90+
String body = IOUtils.toString(connection.getInputStream());
91+
return new TestResponse(connection.getResponseCode(), body);
92+
}
93+
94+
public static class TestResponse {
95+
96+
public final String body;
97+
public final int status;
98+
99+
public TestResponse(int status, String body) {
100+
this.status = status;
101+
this.body = body;
102+
}
103+
}
104+
}

0 commit comments

Comments
 (0)