Posted by James Ward, Developer Advocate
Recently I completed the migration of my blog from Wordpress to Hugo and I wanted to take advantage of it now being a static site by hosting it on a Content Delivery Network (CDN). With Hugo the source content is plain files instead of rows in a database. In the case of my blog those files are in git on GitHub. But when the source files change, the site needs to be regenerated and redeployed to the CDN. Also, sometimes it is nice to have drafts available for review. I setup a continuous delivery pipeline which deploys changes to my prod site on Firebase Hosting and drafts on Cloud Run, using Cloud Build. Read on for instructions for how to set all this up.
If you have an existing Hugo project you'll need to add some files to it:
.firebaserc
{ "projects": { "production": "hello-hugo" } }
cloudbuild-draft.yaml
steps: - name: 'gcr.io/cloud-builders/git' entrypoint: '/bin/sh' args: - '-c' - | # Get the theme git submodule THEME_URL=$(git config -f .gitmodules --get-regexp '^submodule\..*\.url$' | awk '{ print $2 }') THEME_DIR=$(git config -f .gitmodules --get-regexp '^submodule\..*\.path$' | awk '{ print $2 }') rm -rf themes git clone $$THEME_URL $$THEME_DIR - name: 'gcr.io/cloud-builders/docker' entrypoint: '/bin/sh' args: - '-c' - | docker build -t gcr.io/$PROJECT_ID/$REPO_NAME-$BRANCH_NAME:$COMMIT_SHA -f - . << EOF FROM klakegg/hugo:latest WORKDIR /workspace COPY . /workspace ENTRYPOINT hugo -D -p \$$PORT --bind \$$HUGO_BIND --renderToDisk --disableLiveReload --watch=false serve EOF docker push gcr.io/$PROJECT_ID/$REPO_NAME-$BRANCH_NAME:$COMMIT_SHA - name: 'gcr.io/cloud-builders/gcloud' args: - run - deploy - --image=gcr.io/$PROJECT_ID/$REPO_NAME-$BRANCH_NAME:$COMMIT_SHA - --platform=managed - --project=$PROJECT_ID - --region=us-central1 - --memory=512Mi - --allow-unauthenticated - $REPO_NAME-$BRANCH_NAME
cloudbuild.yaml
steps: - name: 'gcr.io/cloud-builders/git' entrypoint: '/bin/sh' args: - '-c' - | # Get the theme git submodule THEME_URL=$(git config -f .gitmodules --get-regexp '^submodule\..*\.url$' | awk '{ print $2 }') THEME_DIR=$(git config -f .gitmodules --get-regexp '^submodule\..*\.path$' | awk '{ print $2 }') rm -rf themes git clone $$THEME_URL $$THEME_DIR - name: 'gcr.io/cloud-builders/curl' entrypoint: '/bin/sh' args: - '-c' - | curl -sL https://github.com/gohugoio/hugo/releases/download/v0.69.2/hugo_0.69.2_Linux-64bit.tar.gz | tar -zxv ./hugo - name: 'gcr.io/cloud-builders/wget' entrypoint: '/bin/sh' args: - '-c' - | # Get firebase CLI wget -O firebase https://firebase.tools/bin/linux/latest chmod +x firebase # Deploy site ./firebase deploy --project=$PROJECT_ID --only=hosting
firebase.json
{ "hosting": { "public": "public" } }
^master$
drafts-trigger
.*
@cloudbuild.gserviceaccount.com
Now you'll need your project id, which can be found in the URL on the Firebase Project Overview page. The URL for my project is:
console.firebase.google.com/project/jw-demo/overview
Which means my project id is: jw-demo
Now copy your project id go into your GitHub fork, select the .firebaserc file and click the "pencil" / edit button:
Replace the hello-hugo string with your project id and commit the changes. This commit will trigger two new builds, one for the production site and one for the drafts site on Cloud Run. You can check the status of those builds on the Cloud Build History page. Once the default trigger (the one for Firebase hosting) finishes, check out your Hugo site running on Firebase Hosting by navigating to (replacing YOUR_PROJECT_ID with the project id you used above): https://YOUR_PROJECT_ID.web.app/
hello-hugo
Your prod and drafts sites are now automatically deploying on new commits!
There are many themes for Hugo and they are easy to change. Typically themes are pulled into Hugo sites using git submodules. To change the theme, edit your .gitmodules file and set the subdirectories and url. As an example, here is the content when using the mainroad theme:
.gitmodules
[submodule "themes/mainroad"] path = themes/mainroad url = https://github.com/vimux/mainroad.git
You will also need to change the theme value in your config.toml file to match the directory name in the themes directory. For example:
theme
config.toml
themes
theme = "mainroad"
Note: At the time of writing this, Cloud Build does not clone git submodules so the cloudbuild.yaml does the cloning instead.
To setup local editing you will first need to clone your fork. You can do this with the GitHub desktop app. Or from the command line:
git clone --recurse-submodules https://github.com/USER/REPO.git
Once you have the files locally, install Hugo, and from inside the repo's directory, run:
hugo -D serve
This will serve the drafts in the site. You can check out the site at: localhost:1313
Committing non-draft changes to master and pushing those changes to GitHub will kick off the build which will deploy them on your prod site. Committing draft to any branch will kick off the build which will deploy them on a Cloud Run site.
Hopefully that all helps you with hosting your Hugo sites! Let me know if you run into any problems.
Posted by Priyanka Vergadia, Developer Advocate
Google Cloud is a cloud computing platform that can be used to build and deploy applications. It allows you to take advantage of the flexibility of development while scaling the infrastructure as needed.
I'm often asked by developers to provide a list of Google Cloud architectures that help to get started on the cloud journey. Last month, I decided to start a mini-series on Twitter called “#13DaysOfGCP" where I shared the most common use cases on Google Cloud. I have compiled the list of all 13 architectures in this post. Some of the topics covered are hybrid cloud, mobile app backends, microservices, serverless, CICD and more. If you were not able to catch it, or if you missed a few days, here we bring to you the summary!
Series kickoff #13DaysOfGCP
Day 1
Day 2
Day 3
Day 4
Day 5
Day 6
Day 7
Day 8
Day 9
Day 10
Day 11
Day 12
Day 13
Wrap up!
We hope you enjoy this list of the most common reference architectures. Please let us know your thoughts in the comments below!
Posted by Guillaume Laforge, Developer Advocate for Google Cloud
With the beta of the new Java 11 runtime for Google Cloud Functions, Java developers can now write their functions using the Java programming language (a language often used in enterprises) in addition to Node.js, Go, or Python. Cloud Functions allow you to run bits of code locally or in the cloud, without provisioning or managing servers: Deploy your code, and let the platform handle scaling up and down for you. Just focus on your code: handle incoming HTTP requests or respond to some cloud events, like messages coming from Cloud Pub/Sub or new files uploaded in Cloud Storage buckets.
In this article, let’s focus on what functions look like, how you can write portable functions, how to run and debug them locally or deploy them in the cloud or on-premises, thanks to the Functions Framework, an open source library that runs your functions. But you will also learn about third-party frameworks that you might be familiar with, that also let you create functions using common programming paradigms.
There are two types of functions: HTTP functions, and background functions. HTTP functions respond to incoming HTTP requests, whereas background functions react to cloud-related events.
The Java Functions Framework provides an API that you can use to author your functions, as well as an invoker which can be called to run your functions locally on your machine, or anywhere with a Java 11 environment.
To get started with this API, you will need to add a dependency in your build files. If you use Maven, add the following dependency tag in pom.xml:
pom.xml
<dependency> <groupId>com.google.cloud.functions</groupId> <artifactId>functions-framework-api</artifactId> <version>1.0.1</version> <scope>provided</scope> </dependency>
If you are using Gradle, add this dependency declaration in build.gradle:
build.gradle
compileOnly("com.google.cloud.functions:functions-framework-api")
A Java function that receives an incoming HTTP request implements the HttpFunction interface:
HttpFunction
import com.google.cloud.functions.*; import java.io.*; public class Example implements HttpFunction { @Override public void service(HttpRequest request, HttpResponse response) throws IOException { var writer = response.getWriter(); writer.write("Hello developers!"); } }
The service() method provides an HttpRequest and an HttpResponse object. From the request, you can get information about the HTTP headers, the payload body, or the request parameters. It’s also possible to handle multipart requests. With the response, you can set a status code or headers, define a body payload and a content-type.
service()
HttpRequest
HttpResponse
Background functions respond to events coming from the cloud, like new Pub/Sub messages, Cloud Storage file updates, or new or updated data in Cloud Firestore. There are actually two ways to implement such functions, either by dealing with the JSON payloads representing those events, or by taking advantage of object marshalling thanks to the Gson library, which takes care of the parsing transparently for the developer.
With a RawBackgroundFunction, the responsibility is on you to handle the incoming cloud event JSON-encoded payload. You receive a JSON string, so you are free to parse it however you like, with your JSON parser of your choice:
import com.google.cloud.functions.Context; import com.google.cloud.functions.RawBackgroundFunction; public class RawFunction implements RawBackgroundFunction { @Override public void accept(String json, Context context) { ... } }
But you also have the option to write a BackgroundFunction which uses Gson for unmarshalling a JSON representation into a Java class (a POJO, Plain-Old-Java-Object) representing that payload. To that end, you have to provide the POJO as a generic argument:
import com.google.cloud.functions.Context; import com.google.cloud.functions.BackgroundFunction; public class PubSubFunction implements BackgroundFunction<PubSubMsg> { @Override public void accept(PubSubMsg msg, Context context) { System.out.println("Received message ID: " + msg.messageId); } } public class PubSubMsg { String data; Map<String, String> attributes; String messageId; String publishTime; }
The Context parameter contains various metadata fields like timestamps, the type of events, and other attributes.
Which type of background function should you use? It depends on the control you need to have on the incoming payload, or if the Gson unmarshalling doesn’t fully fit your needs. But having the unmarshalling covered by the framework definitely streamlines the writing of your function.
Coding is always great, but seeing your code actually running is even more rewarding. The Functions Framework comes with the API we used above, but also with an invoker tool that you can use to run functions locally. For improving developer productivity, having a direct and local feedback loop on your own computer makes it much more comfortable than deploying in the cloud for each change you make to your code.
If you’re building your functions with Maven, you can install the Function Maven plugin in your pom.xml:
<plugin> <groupId>com.google.cloud.functions</groupId> <artifactId>function-maven-plugin</artifactId> <version>0.9.2</version> <configuration> <functionTarget>com.example.Example</functionTarget> </configuration> </plugin>
On the command-line, you can then run:
$ mvn function:run
You can pass extra parameters like --target to define a different function to run (in case your project contains several functions), --port to specify the port to listen to, or --classpath to explicitly set the classpath needed by the function to run. These are the parameters of the underlying Invoker class. However, to set these parameters via the Maven plugin, you’ll have to pass properties with -Drun.functionTarget=com.example.Example and -Drun.port.
--target
--port
--classpath
Invoker
-Drun.functionTarget=com.example.Example
-Drun.port
With Gradle, there is no dedicated plugin, but it’s easy to configure build.gradle to let you run functions.
First, define a dedicated configuration for the invoker:
configurations { invoker }
In the dependencies, add the Invoker library:
dependencies { invoker 'com.google.cloud.functions.invoker:java-function-invoker:1.0.0-beta1' }
And then, create a new task to run the Invoker:
tasks.register("runFunction", JavaExec) { main = 'com.google.cloud.functions.invoker.runner.Invoker' classpath(configurations.invoker) inputs.files(configurations.runtimeClasspath, sourceSets.main.output) args('--target', project.findProperty('runFunction.target') ?: 'com.example.Example', '--port', project.findProperty('runFunction.port') ?: 8080 ) doFirst { args('--classpath', files(configurations.runtimeClasspath, sourceSets.main.output).asPath) } }
By default, the above launches the function com.example.Example on port 8080, but you can override those on the command-line, when running gradle or the gradle wrapper:
com.example.Example
$ gradle runFunction -PrunFunction.target=com.example.HelloWorld \ -PrunFunction.port=8080
What’s interesting about the Functions Framework is that you are not tied to the Cloud Functions platform for deploying your functions. As long as, in your target environment, you can run your functions with the Invoker class, you can run your functions on Cloud Run, on Google Kubernetes Engine, on Knative environments, on other clouds when you can run Java, or more generally on any servers on-premises. It makes your functions highly portable between environments. But let’s have a closer look at deployment now.
You can deploy functions with the Maven plugin as well, with various parameters to tweak for defining regions, memory size, etc. But here, we’ll focus on using the cloud SDK, with its gcloud command-line, to deploy our functions.
gcloud
For example, to deploy an HTTP function, you would type:
$ gcloud functions deploy exampleFn \ --region europe-west1 \ --trigger-http \ --allow-unauthenticated \ --runtime java11 \ --entry-point com.example.Example \ --memory 512MB
For a background function that would be notified of new messages on a Pub/Sub topic, you would launch:
$ gcloud functions deploy exampleFn \ --region europe-west1 \ --trigger-topic msg-topic \ --runtime java11 \ --entry-point com.example.PubSubFunction \ --memory 512MB
Note that deployments come in two flavors as well, although the above commands are the same: functions are deployed from source with a pom.xml and built in Google Cloud, but when using a build tool other than Maven, you can also use the same command to deploy a pre-compiled JAR that contains your function implementation. Of course, you’ll have to create that JAR first.
So far, we looked at Java and the plain Functions Framework, but you can definitely use alternative JVM languages such as Apache Groovy, Kotlin, or Scala, and third-party frameworks that integrate with Cloud Functions like Micronaut and Spring Boot!
Without covering all those combinations, let’s have a look at two examples. What would an HTTP function look like in Groovy?
The first step will be to add Apache Groovy as a dependency in your pom.xml:
<dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-all</artifactId> <version>3.0.4</version> <type>pom</type> </dependency>
You will also need the GMaven compiler plugin to compile the Groovy code:
<plugin> <groupId>org.codehaus.gmavenplus</groupId> <artifactId>gmavenplus-plugin</artifactId> <version>1.9.0</version> <executions> <execution> <goals> <goal>addSources</goal> <goal>addTestSources</goal> <goal>compile</goal> <goal>compileTests</goal> </goals> </execution> </executions> </plugin>
When writing the function code, just use Groovy instead of Java:
import com.google.cloud.functions.* class HelloWorldFunction implements HttpFunction { void service(HttpRequest request, HttpResponse response) { response.writer.write "Hello Groovy World!" } }
The same explanations regarding running your function locally or deploying it still applies: the Java platform is pretty open to alternative languages too! And the Cloud Functions builder will happily build your Groovy code in the cloud, since Maven lets you compile this code thanks to the Groovy library.
Third-party frameworks also offer a dedicated Cloud Functions integration. Let’s have a look at Micronaut.
Micronaut is a “modern, JVM-based, full-stack framework for building modular, easily testable microservice and serverless applications”, as explained on its website. It supports the notion of serverless functions, web apps and microservices, and has a dedicated integration for Google Cloud Functions.
In addition to being a very efficient framework with super fast startup times (which is important, to avoid long cold starts on serverless services), what’s interesting about using Micronaut is that you can use Micronaut’s own programming model, including Dependency Injection, annotation-driven bean declaration, etc.
For HTTP functions, you can use the framework’s own @Controller / @Get annotations, instead of the Functions Framework’s own interfaces. So for example, a Micronaut HTTP function would look like:
@Controller
@Get
import io.micronaut.http.annotation.*; @Controller("/hello") public class HelloController { @Get(uri="/", produces="text/plain") public String index() { return "Example Response"; } }
This is the standard way in Micronaut to define a Web microservice, but it transparently builds upon the Functions Framework to run this service as a Cloud Function. Furthermore, this programming model offered by Micronaut is portable across other environments, since Micronaut runs in many different contexts.
Last but not least, if you are using the Micronaut Launch project (hosted on Cloud Run) which allows you to scaffold new projects easily (from the command-line or from a nice UI), you can opt for adding the google-cloud-function support module, and even choose your favorite language, build tool, or testing framework:
google-cloud-function
Be sure to check out the documentation for the Micronaut Cloud Functions support, and Spring Cloud Function support.
Now it’s your turn to try Cloud Functions for Java 11 today, with your favorite JVM language or third-party frameworks. Read the getting started guide, and try this for free with Google Cloud Platform free trial. Explore Cloud Functions’ features and use cases, take a look at the quickstarts, perhaps even contribute to the open source Functions Framework. And we’re looking forward to seeing what functions you’re going to build on this platform!