diff --git a/iac/.gitignore b/iac/.gitignore new file mode 100644 index 00000000..3c323aa6 --- /dev/null +++ b/iac/.gitignore @@ -0,0 +1,16 @@ +.terraform +cdktf.out +cdktf.log +*terraform.*.tfstate* + +.classpath +.project +.idea +.settings +.vscode +*.iml +target/ +.gradle/ +build/ +src/main/java/imports/ +src/main/resources/imports/ diff --git a/iac/README.md b/iac/README.md new file mode 100644 index 00000000..75a4a2e3 --- /dev/null +++ b/iac/README.md @@ -0,0 +1,117 @@ +## Use CDKTF to deploy the infrastructure and the app + +### Install CDKTF + +* The [Terraform CLI](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli) (1.2+). +* [Node.js](https://nodejs.org/en) and npm v16+. +* Java [OpenJDK v17](https://openjdk.java.net/) and [Gradle](https://gradle.org/install/) + +__Note:__ The latest CDKTF for Java is using Gradle instead Maven, which provides faster synthesizing. + + +For more details, read the [Install CDKTF tutorial](https://developer.hashicorp.com/terraform/tutorials/cdktf/cdktf-install). + +### Build the container images + +The images need to be built and stored in a registry. For this application, you can build different types of images. The following commands provide an example: + +```bash +# Assuming from the root directory of the project +ROOT_DIR=$(pwd) + +cd $ROOT_DIR/services/audit +gcloud builds submit . --machine-type E2-HIGHCPU-32 --config cloudbuild-native.yaml + +cd $ROOT_DIR/services/faulty +gcloud builds submit . --machine-type E2-HIGHCPU-32 --config cloudbuild.yaml --substitutions=_TYPE=native + +cd $ROOT_DIR/services/bff +gcloud builds submit . --machine-type E2-HIGHCPU-32 --config cloudbuild.yaml --substitutions=_TYPE=native + +cd $ROOT_DIR/services/reference +gcloud builds submit . --machine-type E2-HIGHCPU-32 --config cloudbuild.yaml --substitutions=_TYPE=native + +cd $ROOT_DIR/services/quotes +gcloud builds submit . --machine-type E2-HIGHCPU-32 --config cloudbuild-native.yaml +``` + +### Deploy the infrastructure and the app + +You can use different backends to manage the Terraform state. Here is an example using the GCS backend. + +```bash +export PROJECT_ID=[Use your GCP project Id here] +export GCS_BACKEND_BUCKET_NAME=${PROJECT_ID}-cdktf-state +gcloud storage buckets create gs://${GCS_BACKEND_BUCKET_NAME} +``` + +Use the CDKTF CLI to deploy. Notice you can pass in various parameters for the deployment. +```bash +cd $ROOT_DIR/iac +cdktf deploy application-dev \ + --var='auditImageName=audit-native' \ + --var='referenceImageName=reference-native' \ + --var='bffImageName=bff-native' \ + --var='faultyImageName=faulty-native' \ + --var='quotesImageName=quotes-native' \ + --auto-approve +``` + +Wait for a few minutes and check the results. If the command runs successfully, you should see output like the following: +```terminal +... +bff-dev + bff-service-loadbalancer = http://34.36.147.241 + bff-service-url = https://bff-service-uzog2g4wga-uc.a.run.app +... +``` + +Run the following command to get a JSON output: +```bash +curl http://[load balancer ip]/quotes +``` +If you get an error like `Recv failure`, wait a few minutes and try again. + +### Destroy + +To destroy the deployment, you can run the following commands: +```bash +export PROJECT_ID=[Use your GCP project Id here] +export GCS_BACKEND_BUCKET_NAME=${PROJECT_ID}-cdktf-state +cdktf destroy application-dev --auto-approve +``` + +## Use scripts +Alternatively, you can use the scripts in this directory to automate some of the tasks. + +1. Run the following script to build the container images. You can update the script for different types of images: +```bash +./build-images.sh +``` + +2. Use Cloud Build to deploy or destroy the resources: + +Grant the required access to your Cloud Build service account: + +```bash +CLOUDBUILD_SA="$(gcloud projects describe $PROJECT_ID \ + --format 'value(projectNumber)')@cloudbuild.gserviceaccount.com" +gcloud projects add-iam-policy-binding $PROJECT_ID \ + --member serviceAccount:$CLOUDBUILD_SA --role roles/editor +gcloud projects add-iam-policy-binding $PROJECT_ID \ + --member serviceAccount:$CLOUDBUILD_SA --role roles/secretmanager.admin +gcloud projects add-iam-policy-binding $PROJECT_ID \ + --member serviceAccount:$CLOUDBUILD_SA --role roles/servicenetworking.networksAdmin +``` + +Deploy the resources: +```bash +gcloud builds submit --config=cloudbuild.yaml \ + --substitutions=_CDKTF_TYPE="deploy" +``` + +Destroy the resources: +```bash +gcloud builds submit --config=cloudbuild.yaml \ + --substitutions=_CDKTF_TYPE="destroy" +``` \ No newline at end of file diff --git a/iac/build-images.sh b/iac/build-images.sh new file mode 100755 index 00000000..1d3318cd --- /dev/null +++ b/iac/build-images.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# exit if a command returns a non-zero exit code and also print the commands and their args as they are executed. +set -e -x + +# Assuming from the root directory of the project +ROOT_DIR=`dirname $(realpath $0)`/../ + +cd $ROOT_DIR/services/audit +gcloud builds submit . --machine-type E2-HIGHCPU-32 --config cloudbuild-native.yaml + +cd $ROOT_DIR/services/faulty +gcloud builds submit . --machine-type E2-HIGHCPU-32 --config cloudbuild.yaml --substitutions=_TYPE=native + +cd $ROOT_DIR/services/bff +gcloud builds submit . --machine-type E2-HIGHCPU-32 --config cloudbuild.yaml --substitutions=_TYPE=native + +cd $ROOT_DIR/services/reference +gcloud builds submit . --machine-type E2-HIGHCPU-32 --config cloudbuild.yaml --substitutions=_TYPE=native + +cd $ROOT_DIR/services/quotes +gcloud builds submit . --machine-type E2-HIGHCPU-32 --config cloudbuild-native.yaml diff --git a/iac/build.gradle b/iac/build.gradle new file mode 100644 index 00000000..3fb2dc16 --- /dev/null +++ b/iac/build.gradle @@ -0,0 +1,55 @@ +plugins { + id "application" + id "java-library" + id "maven-publish" +} +apply plugin : "java" +ext { + javaMainClass = "com.mycompany.app.Main" +} + +application { + mainClassName = javaMainClass +} + +repositories { + mavenLocal() + maven { + url = uri("https://repo.maven.apache.org/maven2/") + } +} + +dependencies { + implementation group: 'com.hashicorp', name: 'cdktf-provider-docker', version: '9.0.0' + implementation group: 'com.hashicorp', name: 'cdktf-provider-google', version: '9.0.2' + implementation group: 'com.hashicorp', name: 'cdktf', version: '0.18.0' + implementation group: 'com.hashicorp', name: 'cdktf-provider-random', version: '9.0.0' + implementation group: 'software.constructs', name: 'constructs', version: '10.2.70' + testImplementation "junit:junit:4.13.2" + testImplementation "org.junit.jupiter:junit-jupiter:5.8.0" +} + +group = "com.mycompany.app" +version = "0.1" +description = "IaC" + +publishing { + publications { + maven(MavenPublication) { + from(components.java) + } + } +} + +tasks.withType(JavaCompile) { + options.encoding = "UTF-8" +} + +tasks.withType(Javadoc) { + options.encoding = "UTF-8" +} + +test { + useJUnit() + useJUnitPlatform() +} diff --git a/iac/cdktf.json b/iac/cdktf.json new file mode 100644 index 00000000..bcaef2fa --- /dev/null +++ b/iac/cdktf.json @@ -0,0 +1,11 @@ +{ + "language": "java", + "app": "./gradlew run", + "projectId": "122ffb2f-c6f1-4d7b-ac80-a352d36b5fc4", + "sendCrashReports": "false", + "codeMakerOutput": "src/main/java/imports", + "terraformProviders": [], + "terraformModules": [], + "context": { + } +} diff --git a/iac/cdktf.sh b/iac/cdktf.sh new file mode 100644 index 00000000..c193f3ae --- /dev/null +++ b/iac/cdktf.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# exit if a command returns a non-zero exit code and also print the commands and their args as they are executed. +set -e -x + +# Download and install required tools. +npm install -g cdktf-cli + +export GCS_BACKEND_BUCKET_NAME=${PROJECT_ID}-cdktf-state +gcloud storage buckets create gs://${GCS_BACKEND_BUCKET_NAME} 2>/dev/null || true + +case $CDKTF_TYPE in + destroy) + cdktf destroy application-dev --auto-approve + ;; + deploy) + cdktf deploy application-dev \ + --var='auditImageName=audit-native' \ + --var='referenceImageName=reference-native' \ + --var='bffImageName=bff-native' \ + --var='faultyImageName=faulty-native' \ + --var='quotesImageName=quotes-native' \ + --auto-approve + ;; + *) + cdktf synth * + ;; +esac + + + diff --git a/iac/cloudbuild.yaml b/iac/cloudbuild.yaml new file mode 100644 index 00000000..763b17e0 --- /dev/null +++ b/iac/cloudbuild.yaml @@ -0,0 +1,7 @@ +steps: +- name: 'hashicorp/jsii-terraform' + entrypoint: sh + args: ["-c","chmod +x *.sh && ./cdktf.sh"] + env: + - 'CDKTF_TYPE=$_CDKTF_TYPE' + - 'PROJECT_ID=$PROJECT_ID' diff --git a/iac/gradle.properties b/iac/gradle.properties new file mode 100644 index 00000000..609a9ced --- /dev/null +++ b/iac/gradle.properties @@ -0,0 +1 @@ +org.gradle.jvmargs=-Xmx4096m \ No newline at end of file diff --git a/iac/gradle/wrapper/gradle-wrapper.jar b/iac/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..7f93135c Binary files /dev/null and b/iac/gradle/wrapper/gradle-wrapper.jar differ diff --git a/iac/gradle/wrapper/gradle-wrapper.properties b/iac/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..ac72c34e --- /dev/null +++ b/iac/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/iac/gradlew b/iac/gradlew new file mode 100755 index 00000000..0adc8e1a --- /dev/null +++ b/iac/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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 +# +# https://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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/iac/gradlew.bat b/iac/gradlew.bat new file mode 100644 index 00000000..6689b85b --- /dev/null +++ b/iac/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/iac/help b/iac/help new file mode 100644 index 00000000..9cb76dc9 --- /dev/null +++ b/iac/help @@ -0,0 +1,35 @@ +======================================================================================================== + + Your cdktf Java project is ready! + + cat help Prints this message + + Compile: + ./gradlew run Compiles and runs your Java packages + + Synthesize: + cdktf synth [stack] Synthesize Terraform resources to cdktf.out/ + + Diff: + cdktf diff [stack] Perform a diff (terraform plan) for the given stack + + Deploy: + cdktf deploy [stack] Deploy the given stack + + Destroy: + cdktf destroy [stack] Destroy the given stack + + Learn more about using modules and providers https://cdk.tf/modules-and-providers + +Use Providers: + + You can add prebuilt providers (if available) or locally generated ones using the add command: + + cdktf provider add "aws@~>3.0" null kreuzwerker/docker + + All prebuilt providers are available on Maven Central: https://mvnrepository.com/artifact/com.hashicorp + You can also add these providers directly to your pom.xml file. + + You can also build any module or provider locally. Learn more: https://cdk.tf/modules-and-providers + +======================================================================================================== \ No newline at end of file diff --git a/iac/settings.gradle b/iac/settings.gradle new file mode 100644 index 00000000..c1dae33a --- /dev/null +++ b/iac/settings.gradle @@ -0,0 +1,5 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +rootProject.name = 'iac' diff --git a/iac/src/main/java/com/mycompany/app/ApplicationConfig.java b/iac/src/main/java/com/mycompany/app/ApplicationConfig.java new file mode 100644 index 00000000..59056c00 --- /dev/null +++ b/iac/src/main/java/com/mycompany/app/ApplicationConfig.java @@ -0,0 +1,72 @@ +package com.mycompany.app; + +import java.util.HashMap; +import java.util.Map; +import com.hashicorp.cdktf.providers.google.compute_network.ComputeNetwork; +import com.hashicorp.cdktf.providers.google.vpc_access_connector.VpcAccessConnector; + +public class ApplicationConfig { + private String environment; + private String project; + private String region; + private String imageName; + private ComputeNetwork serviceVpc; + private VpcAccessConnector connector; + private Map memory = new HashMap<>(); + + public ApplicationConfig(String environment, String project, String region) { + this.environment = environment; + this.project = project; + this.region = region; + // Default memory size for Cloud Run + memory.put("memory", "2Gi"); + } + + public Map getMemory() { + return memory; + } + + public void setMemory(Map memory) { + this.memory = memory; + } + + public String getEnvironment() { + return environment; + } + + public String getProject() { + return project; + } + + public String getRegion() { + return region; + } + + public String getImageName() { + return imageName; + } + + public void setImageName(String imageName) { + this.imageName = imageName; + } + + public ComputeNetwork getServiceVpc() { + return serviceVpc; + } + + public void setServiceVpc(ComputeNetwork serviceVpc) { + this.serviceVpc = serviceVpc; + } + + public VpcAccessConnector getConnector() { + return connector; + } + + public void setConnector(VpcAccessConnector connector) { + this.connector = connector; + } + + public String getImagePrefix() { + return "gcr.io/" + project + "/"; + } +} diff --git a/iac/src/main/java/com/mycompany/app/ApplicationStack.java b/iac/src/main/java/com/mycompany/app/ApplicationStack.java new file mode 100644 index 00000000..2b463262 --- /dev/null +++ b/iac/src/main/java/com/mycompany/app/ApplicationStack.java @@ -0,0 +1,97 @@ +package com.mycompany.app; + +import software.constructs.Construct; +import java.util.List; +import com.hashicorp.cdktf.TerraformStack; +import com.hashicorp.cdktf.TerraformVariable; +import com.hashicorp.cdktf.TerraformVariableConfig; +import com.mycompany.app.audit.AuditService; +import com.mycompany.app.bff.BffService; +import com.mycompany.app.faulty.FaultyService; +import com.mycompany.app.quotes.QuotesService; +import com.mycompany.app.reference.ReferenceService; +import com.hashicorp.cdktf.providers.docker.provider.DockerProvider; +import com.hashicorp.cdktf.providers.docker.provider.DockerProviderRegistryAuth; +import com.hashicorp.cdktf.providers.google.compute_network.ComputeNetwork; +import com.hashicorp.cdktf.providers.google.project_service.ProjectService; +import com.hashicorp.cdktf.providers.google.provider.GoogleProvider; +import com.hashicorp.cdktf.providers.google.vpc_access_connector.VpcAccessConnector; +import com.hashicorp.cdktf.providers.random_provider.provider.RandomProvider; + +public class ApplicationStack extends TerraformStack { + + public ApplicationStack(Construct scope, String name, ApplicationConfig appConfig) { + super(scope, name); + + String environment = appConfig.getEnvironment(); + String project = appConfig.getProject(); + String region = appConfig.getRegion(); + String imagePrefix = appConfig.getImagePrefix(); + + // Initialize the providers + GoogleProvider.Builder.create(this, "google-cloud").region(region).project(project).build(); + RandomProvider.Builder.create(this, "radnom-provider").build(); + DockerProvider.Builder.create(this, "docker") + .registryAuth( + List.of(DockerProviderRegistryAuth.builder().address("gcr.io").build())) + .build(); + + // Enable the cloud services + ProjectService.Builder.create(this, "enableCloudRun").disableOnDestroy(false) + .project(project).service("run.googleapis.com").build(); + ProjectService.Builder.create(this, "enableContainerRegistry").disableOnDestroy(false) + .project(project).service("containerregistry.googleapis.com").build(); + ProjectService.Builder.create(this, "enableCompute").disableOnDestroy(false) + .project(project).service("compute.googleapis.com").build(); + ProjectService.Builder.create(this, "enableVpcaccess").disableOnDestroy(false) + .project(project).service("vpcaccess.googleapis.com").build(); + + // Get the image names if they pass in as TF variables + TerraformVariable referenceImageName = new TerraformVariable(this, "referenceImageName", + TerraformVariableConfig.builder().type("string").defaultValue("reference") + .description("Container image name for the reference service").build()); + + TerraformVariable quotesImageName = new TerraformVariable(this, "quotesImageName", + TerraformVariableConfig.builder().type("string").defaultValue("quotes") + .description("Container image name for the quotes service").build()); + + TerraformVariable faultyImageName = new TerraformVariable(this, "faultyImageName", + TerraformVariableConfig.builder().type("string").defaultValue("faulty") + .description("Container image name for the faulty service").build()); + + TerraformVariable auditImageName = new TerraformVariable(this, "auditImageName", + TerraformVariableConfig.builder().type("string").defaultValue("audit") + .description("Container image name for the audit service").build()); + + TerraformVariable bffImageName = new TerraformVariable(this, "bffImageName", + TerraformVariableConfig.builder().type("string").defaultValue("bff") + .description("Container image name for the bff service").build()); + + // Create the VPC + ComputeNetwork serviceVpc = ComputeNetwork.Builder.create(this, "service-vpc") + .name("service-vpc").autoCreateSubnetworks(false).build(); + appConfig.setServiceVpc(serviceVpc); +; + // Create the VPC connector for Cloud Run services + VpcAccessConnector connector = VpcAccessConnector.Builder.create(this, "vpcConnector") + .name("vpc-connector").region(region).network(serviceVpc.getId()) + .ipCidrRange("10.128.0.0/28").build(); + appConfig.setConnector(connector); + + // Deploy the services + ReferenceService refSvc = new ReferenceService(this, "reference-" + environment, appConfig, + imagePrefix + referenceImageName.getStringValue()); + + QuotesService quotesSvc = new QuotesService(this, "quotes-" + environment, appConfig, + imagePrefix + quotesImageName.getStringValue()); + + FaultyService faultySvc = new FaultyService(this, "faulty-" + environment, appConfig, + imagePrefix + faultyImageName.getStringValue()); + + new AuditService(this, "audit-" + environment, appConfig, + imagePrefix + auditImageName.getStringValue()); + new BffService(this, "bff-" + environment, appConfig, refSvc.getSvcUrl(), + quotesSvc.getSvcUrl(), faultySvc.getSvcUrl(), + imagePrefix + bffImageName.getStringValue()); + } +} diff --git a/iac/src/main/java/com/mycompany/app/Main.java b/iac/src/main/java/com/mycompany/app/Main.java new file mode 100644 index 00000000..7f0e062c --- /dev/null +++ b/iac/src/main/java/com/mycompany/app/Main.java @@ -0,0 +1,69 @@ +package com.mycompany.app; + +import com.hashicorp.cdktf.*; + +public class Main { + public static void main(String[] args) { + final App app = new App(); + + // Choose a backend to store state. Default preference is a local file, GCS and remote. + // See more details at: + // https://developer.hashicorp.com/terraform/cdktf/concepts/remote-backends + // Default to use a GCS bucket to store state. You need to pre-create the bucket and + // provide the bucket name. + // If use a remote bakcend, you need to update the configurations such as organization + // and workspace. + // If you need to change backends, refer to + // https://developer.hashicorp.com/terraform/cdktf/concepts/remote-backends#migrate-local-state-storage-to-remote + + String backend = "LOCAL"; + String bucketName = null; + if (System.getenv("GCS_BACKEND_BUCKET_NAME") != null) { + backend = "GCS"; + bucketName = System.getenv("GCS_BACKEND_BUCKET_NAME"); + } else if ("true".equals(System.getenv("USE_REMOTE_BACKEND"))) { + backend = "REMTOE"; + } + + // Get current GCP project id and region from the environment + String projectId = System.getenv("PROJECT_ID"); + if (projectId == null) { + throw new RuntimeException("PROJECT_ID environment variable not set"); + } + + String region = System.getenv("REGION"); + if (region == null) { + System.out.println( + "Cloud Region is not found in environment variable. Defaulting to us-central1"); + region = "us-central1"; + } + + ApplicationConfig appDevConfig = new ApplicationConfig("dev", projectId, region); + ApplicationStack devStack = new ApplicationStack(app, "application-dev", appDevConfig); + if ("GCS".equals(backend)) { + GcsBackend.Builder.create(devStack).bucket(bucketName).prefix("terraform/state") + .build(); + } else if (!"LOCAL".equals(backend) && "REMOTE".equals(backend)) { + new RemoteBackend(devStack, RemoteBackendConfig.builder() + .organization("terraform-demo-serverless") + .workspaces( + new NamedRemoteWorkspace("cdktf-integration-serverless-java-example")) + .build()); + } + + ApplicationConfig appProdConfig = new ApplicationConfig("prod", projectId, region); + ApplicationStack prodStack = new ApplicationStack(app, "application-prod", appProdConfig); + if ("GCS".equals(backend)) { + GcsBackend.Builder.create(prodStack).bucket(bucketName).prefix("terraform/state") + .build(); + } else if (backend != "LOCAL" && backend == "REMOTE") { + new RemoteBackend(devStack, RemoteBackendConfig.builder() + .organization("terraform-demo-serverless") + .workspaces( + new NamedRemoteWorkspace("cdktf-integration-serverless-java-example")) + .build()); + } + + app.synth(); + } +} diff --git a/iac/src/main/java/com/mycompany/app/audit/AuditService.java b/iac/src/main/java/com/mycompany/app/audit/AuditService.java new file mode 100644 index 00000000..c349efd3 --- /dev/null +++ b/iac/src/main/java/com/mycompany/app/audit/AuditService.java @@ -0,0 +1,44 @@ +package com.mycompany.app.audit; + +import java.util.List; +import com.hashicorp.cdktf.TerraformOutput; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2Service; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2ServiceTemplate; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2ServiceTemplateContainers; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2ServiceTemplateContainersResources; +import com.hashicorp.cdktf.providers.google.project_service.ProjectService; +import com.mycompany.app.ApplicationConfig; +import software.constructs.Construct; + +public class AuditService extends Construct { + + private String svcUrl; + + public String getSvcUrl() { + return this.svcUrl; + } + + public AuditService(Construct scope, String id, ApplicationConfig config, String imageName) { + super(scope, id); + + String project = config.getProject(); + String region = config.getRegion(); + + ProjectService.Builder.create(this, "enableFirestoreService").disableOnDestroy(false) + .project(project).service("firestore.googleapis.com").build(); + + CloudRunV2Service cr = CloudRunV2Service.Builder.create(this, "audit-cr-service") + .name("audit-service").project(project).location(region) + .template(CloudRunV2ServiceTemplate.builder() + .containers(List.of(CloudRunV2ServiceTemplateContainers.builder() + .image(imageName) + .resources(CloudRunV2ServiceTemplateContainersResources.builder() + .limits(config.getMemory()).build()) + .build())) + .build()) + .build(); + + this.svcUrl = cr.getUri(); + TerraformOutput.Builder.create(this, "audit-service-url").value(svcUrl).build(); + } +} diff --git a/iac/src/main/java/com/mycompany/app/bff/BffService.java b/iac/src/main/java/com/mycompany/app/bff/BffService.java new file mode 100644 index 00000000..3ab1fc03 --- /dev/null +++ b/iac/src/main/java/com/mycompany/app/bff/BffService.java @@ -0,0 +1,103 @@ +package com.mycompany.app.bff; + +import java.util.List; +import com.hashicorp.cdktf.TerraformOutput; +import com.hashicorp.cdktf.providers.google.compute_url_map.ComputeUrlMap; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2Service; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2ServiceTemplate; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2ServiceTemplateContainers; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2ServiceTemplateContainersEnv; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2ServiceTemplateContainersResources; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service_iam_policy.CloudRunV2ServiceIamPolicy; +import com.hashicorp.cdktf.providers.google.compute_backend_service.ComputeBackendService; +import com.hashicorp.cdktf.providers.google.compute_backend_service.ComputeBackendServiceBackend; +import com.hashicorp.cdktf.providers.google.compute_global_address.ComputeGlobalAddress; +import com.hashicorp.cdktf.providers.google.compute_global_forwarding_rule.ComputeGlobalForwardingRule; +import com.hashicorp.cdktf.providers.google.compute_region_network_endpoint_group.ComputeRegionNetworkEndpointGroup; +import com.hashicorp.cdktf.providers.google.compute_region_network_endpoint_group.ComputeRegionNetworkEndpointGroupCloudRun; +import com.hashicorp.cdktf.providers.google.compute_target_http_proxy.ComputeTargetHttpProxy; +import com.hashicorp.cdktf.providers.google.data_google_iam_policy.DataGoogleIamPolicy; +import com.hashicorp.cdktf.providers.google.data_google_iam_policy.DataGoogleIamPolicyBinding; +import com.mycompany.app.ApplicationConfig; +import software.constructs.Construct; + +public class BffService extends Construct { + + private String svcUrl; + + public String getSvcUrl() { + return this.svcUrl; + } + + public BffService(Construct scope, String id, ApplicationConfig config, String refUrl, + String quotesUrl, String faultyUrl, String imageName) { + super(scope, id); + + String project = config.getProject(); + String region = config.getRegion(); + + CloudRunV2ServiceTemplateContainersEnv quotesSvcUrl = CloudRunV2ServiceTemplateContainersEnv + .builder().name("QUOTES_URL").value(quotesUrl).build(); + CloudRunV2ServiceTemplateContainersEnv refSvcUrl = CloudRunV2ServiceTemplateContainersEnv + .builder().name("REFERENCE_URL").value(refUrl).build(); + CloudRunV2ServiceTemplateContainersEnv faultySvcUrl = CloudRunV2ServiceTemplateContainersEnv + .builder().name("FAULTY_URL").value(faultyUrl).build(); + + CloudRunV2Service cr = CloudRunV2Service.Builder.create(this, "bff-cr-service") + .name("bff-service").project(project).location(region) + .ingress("INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER") + .template(CloudRunV2ServiceTemplate.builder() + .containers(List.of(CloudRunV2ServiceTemplateContainers.builder() + .env(List.of(quotesSvcUrl, refSvcUrl, faultySvcUrl)) + .image(imageName) + .resources(CloudRunV2ServiceTemplateContainersResources.builder() + .limits(config.getMemory()).build()) + .build())) + .build()) + .build(); + + DataGoogleIamPolicy crPolicy = + DataGoogleIamPolicy.Builder.create(this, "datanoauth") + .binding(List.of(DataGoogleIamPolicyBinding.builder() + .role("roles/run.invoker").members(List.of("allUsers")).build())) + .build(); + + CloudRunV2ServiceIamPolicy.Builder.create(this, "bff-policy").location(cr.getLocation()) + .project(cr.getProject()).name(cr.getName()).policyData(crPolicy.getPolicyData()) + .build(); + + this.svcUrl = cr.getUri(); + TerraformOutput.Builder.create(this, "bff-service-url").value(svcUrl).build(); + + // Get an IP + ComputeGlobalAddress ip = + ComputeGlobalAddress.Builder.create(this, "bff-ip").name("bff-ip").build(); + // Create a serverless NEG for the service + ComputeRegionNetworkEndpointGroup neg = + ComputeRegionNetworkEndpointGroup.Builder.create(this, "bff-neg").name("bff-neg") + .region(region).networkEndpointType("SERVERLESS") + .cloudRun(ComputeRegionNetworkEndpointGroupCloudRun.builder() + .service(cr.getName()).build()) + .build(); + // Create the backend service + ComputeBackendService bffsvc = ComputeBackendService.Builder.create(this, "bff-backend-svc") + .name("bff-backend-svc").timeoutSec(30).loadBalancingScheme("EXTERNAL") + .backend(List.of(ComputeBackendServiceBackend.builder().group(neg.getId()) + .balancingMode("UTILIZATION").capacityScaler(1.0).build())) + .build(); + // Create the URL map + ComputeUrlMap urlmap = ComputeUrlMap.Builder.create(this, "bff-url-map").name("bff-url-map") + .defaultService(bffsvc.getId()).build(); + // Create a proxy + ComputeTargetHttpProxy httpProxy = + ComputeTargetHttpProxy.Builder.create(this, "bff-http-proxy").name("bff-http-proxy") + .urlMap(urlmap.getId()).build(); + // Create a forwarding rule + ComputeGlobalForwardingRule.Builder.create(this, "bff-foward-rule").name("bff-forward-rule") + .loadBalancingScheme("EXTERNAL").ipAddress(ip.getAddress()) + .target(httpProxy.getId()).portRange("80").build(); + + TerraformOutput.Builder.create(this, "bff-service-loadbalancer") + .value("http://" + ip.getAddress()).build(); + } +} diff --git a/iac/src/main/java/com/mycompany/app/faulty/FaultyService.java b/iac/src/main/java/com/mycompany/app/faulty/FaultyService.java new file mode 100644 index 00000000..69c6f8f6 --- /dev/null +++ b/iac/src/main/java/com/mycompany/app/faulty/FaultyService.java @@ -0,0 +1,41 @@ +package com.mycompany.app.faulty; + +import java.util.List; +import com.hashicorp.cdktf.TerraformOutput; + +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2Service; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2ServiceTemplate; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2ServiceTemplateContainers; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2ServiceTemplateContainersResources; +import com.mycompany.app.ApplicationConfig; +import software.constructs.Construct; + +public class FaultyService extends Construct { + + private String svcUrl; + + public String getSvcUrl() { + return this.svcUrl; + } + + public FaultyService(Construct scope, String id, ApplicationConfig config, String imageName) { + super(scope, id); + + String project = config.getProject(); + String region = config.getRegion(); + + CloudRunV2Service cr = CloudRunV2Service.Builder.create(this, "faulty-cr-service") + .name("faulty-service").project(project).location(region) + .template(CloudRunV2ServiceTemplate.builder() + .containers(List.of(CloudRunV2ServiceTemplateContainers.builder() + .image(imageName) + .resources(CloudRunV2ServiceTemplateContainersResources.builder() + .limits(config.getMemory()).build()) + .build())) + .build()) + .build(); + + this.svcUrl = cr.getUri(); + TerraformOutput.Builder.create(this, "faulty-service-url").value(svcUrl).build(); + } +} diff --git a/iac/src/main/java/com/mycompany/app/quotes/QuotesService.java b/iac/src/main/java/com/mycompany/app/quotes/QuotesService.java new file mode 100644 index 00000000..5dc97ed0 --- /dev/null +++ b/iac/src/main/java/com/mycompany/app/quotes/QuotesService.java @@ -0,0 +1,208 @@ +package com.mycompany.app.quotes; + +import java.util.List; +import com.hashicorp.cdktf.TerraformOutput; +import com.hashicorp.cdktf.TerraformVariable; +import com.hashicorp.cdktf.TerraformVariableConfig; +import com.hashicorp.cdktf.providers.google.data_google_project.DataGoogleProject; +import com.hashicorp.cdktf.providers.random_provider.password.Password; +import com.mycompany.app.ApplicationConfig; +import com.hashicorp.cdktf.providers.google.secret_manager_secret_iam_member.SecretManagerSecretIamMember; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2Service; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2ServiceTemplate; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2ServiceTemplateContainers; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2ServiceTemplateContainersEnv; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2ServiceTemplateContainersEnvValueSource; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2ServiceTemplateContainersEnvValueSourceSecretKeyRef; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2ServiceTemplateContainersResources; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2ServiceTemplateVpcAccess; +import com.hashicorp.cdktf.providers.google.compute_global_address.ComputeGlobalAddress; +import com.hashicorp.cdktf.providers.google.compute_network.ComputeNetwork; +import com.hashicorp.cdktf.providers.google.sql_database.SqlDatabase; +import com.hashicorp.cdktf.providers.google.sql_database_instance.SqlDatabaseInstance; +import com.hashicorp.cdktf.providers.google.sql_database_instance.SqlDatabaseInstanceSettings; +import com.hashicorp.cdktf.providers.google.sql_database_instance.SqlDatabaseInstanceSettingsIpConfiguration; +import com.hashicorp.cdktf.providers.google.sql_user.SqlUser; +import com.hashicorp.cdktf.providers.google.vpc_access_connector.VpcAccessConnector; +import com.hashicorp.cdktf.providers.google.project_service.ProjectService; +import com.hashicorp.cdktf.providers.google.secret_manager_secret.SecretManagerSecret; +import com.hashicorp.cdktf.providers.google.secret_manager_secret.SecretManagerSecretReplication; +import com.hashicorp.cdktf.providers.google.secret_manager_secret_version.SecretManagerSecretVersion; +import com.hashicorp.cdktf.providers.google.service_networking_connection.ServiceNetworkingConnection; +import software.constructs.Construct; + +public class QuotesService extends Construct { + + private String svcUrl; + + public String getSvcUrl() { + return this.svcUrl; + } + + public QuotesService(Construct scope, String id, ApplicationConfig config, String imageName) { + super(scope, id); + + String project = config.getProject(); + String region = config.getRegion(); + ComputeNetwork serviceVpc = config.getServiceVpc(); + VpcAccessConnector connector = config.getConnector(); + + // Initialize the parameters + TerraformVariable dbInstnaceSize = new TerraformVariable(this, "dbInstnaceSize", + TerraformVariableConfig.builder().type("string").defaultValue("db-f1-micro") + .description("Cloud SQL instnace type").build()); + TerraformVariable databaseVersion = new TerraformVariable(this, "databaseVersion", + TerraformVariableConfig.builder().type("string").defaultValue("POSTGRES_15") + .description("Cloud SQL PostGres version").build()); + TerraformVariable dbUser = new TerraformVariable(this, "dbUser", + TerraformVariableConfig.builder().type("string").defaultValue("user") + .description("Cloud SQL DB user").build()); + TerraformVariable dbName = new TerraformVariable(this, "dbName", + TerraformVariableConfig.builder().type("string").defaultValue("quote_db") + .description("Cloud SQL DB name").build()); + + ProjectService sqlAdminService = + ProjectService.Builder.create(this, "enableSqlAdminService").disableOnDestroy(false) + .project(project).service("sqladmin.googleapis.com").build(); + + + ComputeGlobalAddress privateIp = ComputeGlobalAddress.Builder.create(this, "privateIp") + .name("private-ip").purpose("VPC_PEERING").addressType("INTERNAL").prefixLength(16) + .network(serviceVpc.getId()).build(); + ServiceNetworkingConnection serviceNetworkingConnection = + ServiceNetworkingConnection.Builder.create(this, "serviceNetworkingConnection") + .network(serviceVpc.getId()) + .reservedPeeringRanges(List.of(privateIp.getName())) + .service("servicenetworking.googleapis.com").build(); + + // Create the DB instance + SqlDatabaseInstance sqlDBinstnace = SqlDatabaseInstance.Builder + .create(this, "quotesDBinstance").name("serverless-db-instance").region(region) + .databaseVersion(databaseVersion.getStringValue()).deletionProtection(false) + .settings(SqlDatabaseInstanceSettings.builder() + .tier(dbInstnaceSize.getStringValue()) + .ipConfiguration(SqlDatabaseInstanceSettingsIpConfiguration.builder() + .enablePrivatePathForGoogleCloudServices(true) + .privateNetwork(serviceVpc.getId()).ipv4Enabled(false).build()) + .build()) + .dependsOn(List.of(sqlAdminService, serviceNetworkingConnection)).build(); + + + + // Create the DB + SqlDatabase.Builder.create(this, "quotesDb").name(dbName.getStringValue()) + .instance(sqlDBinstnace.getName()).build(); + + // Create the DB password and store it in the secret manager + Password pw = Password.Builder.create(this, "random-pw").length(12).build(); + + SecretManagerSecret dbSecret = + SecretManagerSecret.Builder.create(this, "db-secret").secretId("db-secret") + .replication( + SecretManagerSecretReplication.builder().automatic(true).build()) + .build(); + SecretManagerSecretVersion.Builder.create(this, "db-pass").secret(dbSecret.getId()) + .secretData(pw.getResult()).build(); + + // Create the SQL user + SqlUser.Builder.create(this, "sqlUser").name(dbUser.getStringValue()) + .deletionPolicy("ABANDON").password(pw.getResult()) + .instance(sqlDBinstnace.getName()).build(); + + DataGoogleProject deployProject = + DataGoogleProject.Builder.create(this, "project").projectId(project).build(); + + SecretManagerSecretIamMember.Builder + .create(this, "secret-access").secretId(dbSecret.getSecretId()) + .role("roles/secretmanager.secretAccessor").member("serviceAccount:" + + deployProject.getNumber() + "-compute@developer.gserviceaccount.com") + .build(); + + // Set the environment variables for the Cloud Run job + // CloudRunV2JobTemplateTemplateContainersEnv jobEnvSpringActiveProfile = + // CloudRunV2JobTemplateTemplateContainersEnv.builder().name("SPRING_PROFILES_ACTIVE") + // .value("cloud-" + config.getEnvironment()).build(); + // CloudRunV2JobTemplateTemplateContainersEnv jobEnvDbHost = + // CloudRunV2JobTemplateTemplateContainersEnv.builder().name("DB_HOST") + // .value(sqlDBinstnace.getFirstIpAddress()).build(); + // CloudRunV2JobTemplateTemplateContainersEnv jobEnvDbName = + // CloudRunV2JobTemplateTemplateContainersEnv.builder().name("DB_DATABASE") + // .value(dbName.getStringValue()).build(); + // CloudRunV2JobTemplateTemplateContainersEnv jobEnvDbUser = + // CloudRunV2JobTemplateTemplateContainersEnv.builder().name("DB_USERT") + // .value(dbUser.getStringValue()).build(); + // CloudRunV2JobTemplateTemplateContainersEnv jobEnvDbPasswd = + // CloudRunV2JobTemplateTemplateContainersEnv.builder().name("DB_PASS") + // .valueSource(CloudRunV2JobTemplateTemplateContainersEnvValueSource.builder() + // .secretKeyRef( + // CloudRunV2JobTemplateTemplateContainersEnvValueSourceSecretKeyRef + // .builder().secret(dbSecret.getSecretId()) + // .version("latest").build()) + // .build()) + // .build(); + // Deploy a Cloud Run job to migrate the database schema + + // CloudRunJobExec crjobexec = CloudRunJobExec.Builder.create(this, "quotes-cr-job") + // .name("quotes-cr-job").image(imageName).projectId(project).location(region) + // .argument(null) + // .envVars(List.of(Map.of("name", "DB_HOST", "value", sqlDBinstnace.getFirstIpAddress()), + // Map.of("name", "DB_DATABASE", "value", dbName.getStringValue()), + // Map.of("name", "DB_USER", "value", dbUser.getStringValue()), + // Map.of("name", "DB_PASS", "value", pw.getResult()))) + // // .exec(true) + // .dependsOn(List.of(quotesDb, dbSecret, user)).build(); + + + // CloudRunV2Job crJob = CloudRunV2Job.Builder.create(this, + // "quotes-cr-job").name("quotes-job") + // .project(project).location(region) + // .template(CloudRunV2JobTemplate.builder().template(CloudRunV2JobTemplateTemplate + // .builder() + // .containers(List.of(CloudRunV2JobTemplateTemplateContainers.builder() + // .image(imageName) + // .resources(CloudRunV2JobTemplateTemplateContainersResources + // .builder().limits(Map.of("memory", "2Gi")).build()) + // .env(List.of(jobEnvSpringActiveProfile, jobEnvDbHost, jobEnvDbName, + // jobEnvDbUser, jobEnvDbPasswd)) + // .build())) + // .build()).taskCount(1).build()) + + + // Set the environment variables for the Cloud Run service + CloudRunV2ServiceTemplateContainersEnv envSpringActiveProfile = + CloudRunV2ServiceTemplateContainersEnv.builder().name("SPRING_PROFILES_ACTIVE") + .value("cloud-" + config.getEnvironment()).build(); + CloudRunV2ServiceTemplateContainersEnv envDbHost = CloudRunV2ServiceTemplateContainersEnv + .builder().name("DB_HOST").value(sqlDBinstnace.getFirstIpAddress()).build(); + CloudRunV2ServiceTemplateContainersEnv envDbName = CloudRunV2ServiceTemplateContainersEnv + .builder().name("DB_DATABASE").value(dbName.getStringValue()).build(); + CloudRunV2ServiceTemplateContainersEnv envDbUser = CloudRunV2ServiceTemplateContainersEnv + .builder().name("DB_USERT").value(dbUser.getStringValue()).build(); + CloudRunV2ServiceTemplateContainersEnv envDbPasswd = CloudRunV2ServiceTemplateContainersEnv + .builder().name("DB_PASS") + .valueSource(CloudRunV2ServiceTemplateContainersEnvValueSource.builder() + .secretKeyRef(CloudRunV2ServiceTemplateContainersEnvValueSourceSecretKeyRef + .builder().secret(dbSecret.getSecretId()).version("latest").build()) + .build()) + .build(); + // Deploy the Cloud Run service + CloudRunV2Service cr = CloudRunV2Service.Builder.create(this, "quotes-cr-service") + .name("quotes-service").project(project).location(region) + .template(CloudRunV2ServiceTemplate.builder() + .vpcAccess(CloudRunV2ServiceTemplateVpcAccess.builder() + .connector(connector.getId()).build()) + .containers(List + .of(CloudRunV2ServiceTemplateContainers.builder().image(imageName) + .resources(CloudRunV2ServiceTemplateContainersResources + .builder().limits(config.getMemory()).build()) + .env(List.of(envSpringActiveProfile, envDbHost, envDbName, + envDbUser, envDbPasswd)) + .build())) + .build()) + // .dependsOn(List.of(crjobexec)) + .build(); + + this.svcUrl = cr.getUri(); + TerraformOutput.Builder.create(this, "quotes-service-url").value(svcUrl).build(); + } +} diff --git a/iac/src/main/java/com/mycompany/app/reference/ReferenceService.java b/iac/src/main/java/com/mycompany/app/reference/ReferenceService.java new file mode 100644 index 00000000..20e64e57 --- /dev/null +++ b/iac/src/main/java/com/mycompany/app/reference/ReferenceService.java @@ -0,0 +1,43 @@ +package com.mycompany.app.reference; + +import com.hashicorp.cdktf.TerraformOutput; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2Service; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2ServiceTemplate; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2ServiceTemplateContainers; +import com.hashicorp.cdktf.providers.google.cloud_run_v2_service.CloudRunV2ServiceTemplateContainersResources; +import com.mycompany.app.ApplicationConfig; +import software.constructs.Construct; + +import java.util.List; + +public class ReferenceService extends Construct { + + private String svcUrl; + + public String getSvcUrl() { + return this.svcUrl; + } + + public ReferenceService(Construct scope, String id, ApplicationConfig config, + String imageName) { + super(scope, id); + + String project = config.getProject(); + String region = config.getRegion(); + + CloudRunV2Service cr = CloudRunV2Service.Builder.create(this, "reference-cr-service") + .name("reference-service").project(project).location(region) + .template(CloudRunV2ServiceTemplate.builder() + .containers(List.of(CloudRunV2ServiceTemplateContainers.builder() + .image(imageName) + .resources(CloudRunV2ServiceTemplateContainersResources.builder() + .limits(config.getMemory()).build()) + .build())) + .build()) + .build(); + + + this.svcUrl = cr.getUri(); + TerraformOutput.Builder.create(this, "reference-service-url").value(svcUrl).build(); + } +} diff --git a/iac/src/test/java/MainTest.java b/iac/src/test/java/MainTest.java new file mode 100644 index 00000000..2155ab16 --- /dev/null +++ b/iac/src/test/java/MainTest.java @@ -0,0 +1,38 @@ +import com.hashicorp.cdktf.Testing; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertTrue; + +// The tests below are example tests, you can find more information at +// https://cdk.tf/testing +public class MainTest { + + @Test + void myAppTest() { + assertTrue(true); + } + //private final TerraformStack stack = new TerraformStack(Testing.app(), "stack"); + + //private final MyApplicationsAbstraction appAbstraction = new MyApplicationsAbstraction(stack, "resource"); + //private final String synthesized = Testing.synth(stack); + + //@Test + //void shouldContainContainer() { + // assertTrue(Testing.toHaveResource(synthesized, Container.TF_RESOURCE_TYPE) ); + //} + + //@Test + //void shouldUseUbuntuImage() { + // assertTrue(Testing + // .toHaveResourceWithProperties(synthesized, Image.TF_RESOURCE_TYPE, new HashMap() { + // { + // put("name", "ubuntu:latest"); + // } + // }) ); + //} + + //@Test + //void checkValidity() { + // assertTrue(Testing.toBeValidTerraform(Testing.fullSynth(stack)) ); + //} + +} \ No newline at end of file diff --git a/services/audit/cloudbuild-native.yaml b/services/audit/cloudbuild-native.yaml index 75cee4f7..1b748610 100644 --- a/services/audit/cloudbuild-native.yaml +++ b/services/audit/cloudbuild-native.yaml @@ -24,23 +24,24 @@ steps: gsutil cp gs://${PROJECT_ID}-cache-dependencies/cache/maven-dependencies.tgz maven-dependencies.tgz || exit 0 tar -zxf maven-dependencies.tgz --directory / || exit 0 - id: 'test' - name: maven:3.9-eclipse-temurin-17 + name: maven:3.9.6-eclipse-temurin-21 entrypoint: mvn volumes: - name: 'maven-repository' path: '/root/.m2' args: ["verify"] - id: 'build-project' - name: maven:3.9-eclipse-temurin-17 + name: maven:3.9.6-eclipse-temurin-21 entrypoint: bash args: - '-c' - | - bash <(curl -sL https://get.graalvm.org/jdk) graalvm-ce-java17-22.3.1 + curl -LO https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-21.0.2/graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz + tar xvfz graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz + pwd + export JAVA_HOME="/workspace/graalvm-community-openjdk-21.0.2+13.1" printenv - export JAVA_HOME="/workspace/graalvm-ce-java17-22.3.1" - gu install native-image - mvn spring-boot:build-image -Pnative -DskipTests + mvn spring-boot:build-image -Pnative -DskipTests -Dspring-boot.build-image.imageName=audit-native volumes: - name: 'maven-repository' path: '/root/.m2' diff --git a/services/audit/cloudbuild.yaml b/services/audit/cloudbuild.yaml index a3feb09d..cded5ec3 100644 --- a/services/audit/cloudbuild.yaml +++ b/services/audit/cloudbuild.yaml @@ -27,7 +27,7 @@ steps: # run tests first - id: 'test' - name: maven:3.9-eclipse-temurin-17 + name: maven:3.9.6-eclipse-temurin-21 entrypoint: mvn volumes: - name: 'maven-repository' @@ -36,12 +36,12 @@ steps: # build service image - id: 'build-project' - name: maven:3.9-eclipse-temurin-17 + name: maven:3.9.6-eclipse-temurin-21 entrypoint: mvn volumes: - name: 'maven-repository' path: '/root/.m2' - args: ["spring-boot:build-image", "-DskipTests"] + args: ["spring-boot:build-image", "-DskipTests", "-Dspring-boot.build-image.imageName=audit"] # store image in registry - id: 'store-image' @@ -61,4 +61,4 @@ steps: - | tar -zcf maven-dependencies.tgz /root/.m2 gsutil cp maven-dependencies.tgz gs://${PROJECT_ID}-cache-dependencies/cache/maven-dependencies.tgz -images: ["gcr.io/$PROJECT_ID/audit"] \ No newline at end of file +images: ["gcr.io/$PROJECT_ID/audit"] diff --git a/services/bff/cloudbuild.yaml b/services/bff/cloudbuild.yaml index c13eda59..babb9d10 100644 --- a/services/bff/cloudbuild.yaml +++ b/services/bff/cloudbuild.yaml @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. steps: -- name: maven:3-openjdk-17 +- name: maven:3.9.6-eclipse-temurin-21 entrypoint: mvn - args: ["spring-boot:build-image", "-P${_TYPE}", "-DskipTests"] + args: ["spring-boot:build-image", "-P${_TYPE}", "-DskipTests", "-Dspring-boot.build-image.imageName=bff-${_TYPE}"] - name: gcr.io/cloud-builders/docker args: ["tag", "bff-${_TYPE}", "gcr.io/$PROJECT_ID/bff-${_TYPE}"] images: ["gcr.io/$PROJECT_ID/bff-${_TYPE}"] diff --git a/services/bff/src/main/resources/META-INF/native-image/services/native-image.properties b/services/bff/src/main/resources/META-INF/native-image/services/native-image.properties new file mode 100644 index 00000000..ca7b51b7 --- /dev/null +++ b/services/bff/src/main/resources/META-INF/native-image/services/native-image.properties @@ -0,0 +1,13 @@ +Args=--initialize-at-build-time=\ + ch.qos.logback.core.CoreConstants,\ + ch.qos.logback.core.util.StatusPrinter,\ + org.apache.commons.logging.LogFactoryService,\ + ch.qos.logback.classic.Level,\ + ch.qos.logback.core.util.Loader,\ + org.apache.commons.logging.LogFactory,\ + org.slf4j.MDC,\ + ch.qos.logback.core.status.StatusBase,\ + ch.qos.logback.core.status.InfoStatus,\ + ch.qos.logback.classic.Logger,\ + org.slf4j.LoggerFactory + diff --git a/services/faulty/cloudbuild.yaml b/services/faulty/cloudbuild.yaml index a7016c78..2efc5423 100644 --- a/services/faulty/cloudbuild.yaml +++ b/services/faulty/cloudbuild.yaml @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. steps: -- name: maven:3-openjdk-17 +- name: maven:3.9.6-eclipse-temurin-21 entrypoint: mvn - args: ["spring-boot:build-image", "-P${_TYPE}"] + args: ["spring-boot:build-image", "-P${_TYPE}", "-Dspring-boot.build-image.imageName=faulty-${_TYPE}"] - name: gcr.io/cloud-builders/docker args: ["tag", "faulty-${_TYPE}", "gcr.io/$PROJECT_ID/faulty-${_TYPE}"] images: ["gcr.io/$PROJECT_ID/faulty-${_TYPE}"] diff --git a/services/quotes/pom.xml b/services/quotes/pom.xml index d329751d..d34a7723 100644 --- a/services/quotes/pom.xml +++ b/services/quotes/pom.xml @@ -102,7 +102,11 @@ toxiproxy test - + + org.springframework.data + spring-data-commons + 3.2.x-3025-SNAPSHOT + diff --git a/services/quotes/src/main/resources/application.yml b/services/quotes/src/main/resources/application.yml index 197d7dc0..956e07dd 100644 --- a/services/quotes/src/main/resources/application.yml +++ b/services/quotes/src/main/resources/application.yml @@ -13,22 +13,22 @@ management: target: local spring: -# config: -# activate: -# on-profile: cloud-prod -## datasource: -# url: 'jdbc:postgresql://${DB_HOST:127.0.0.1}/${DB_DATABASE:quote_db}' -# username: '${DB_USER:user}' -# password: '${DB_PASS:password}' -# jpa: -# properties: -# hibernate: -# jdbc: -# lob: -# non_contextual_creation: true -# dialect: org.hibernate.dialect.PostgreSQLDialect -# hibernate: -# ddl-auto: update + config: + activate: + on-profile: cloud-dev + datasource: + url: 'jdbc:postgresql://${DB_HOST:127.0.0.1}/${DB_DATABASE:quote_db}' + username: '${DB_USER:user}' + password: '${DB_PASS:password}' + jpa: + properties: + hibernate: + jdbc: + lob: + non_contextual_creation: true + dialect: org.hibernate.dialect.PostgreSQLDialect + hibernate: + ddl-auto: update # datasource: # url: jdbc:h2:mem:test @@ -38,4 +38,22 @@ spring: # jpa: # spring.jpa.database-platform: org.hibernate.dialect.H2Dialect # h2: -# console.enabled: true \ No newline at end of file +# console.enabled: true +--- +spring: + config: + activate: + on-profile: cloud-prod + datasource: + url: 'jdbc:postgresql://${DB_HOST:127.0.0.1}/${DB_DATABASE:quote_db}' + username: '${DB_USER:user}' + password: '${DB_PASS:password}' + jpa: + properties: + hibernate: + jdbc: + lob: + non_contextual_creation: true + dialect: org.hibernate.dialect.PostgreSQLDialect + hibernate: + ddl-auto: none \ No newline at end of file diff --git a/services/reference/cloudbuild.yaml b/services/reference/cloudbuild.yaml index 615ebaa7..e0537c42 100644 --- a/services/reference/cloudbuild.yaml +++ b/services/reference/cloudbuild.yaml @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. steps: -- name: maven:3-openjdk-17 +- name: maven:3.9.6-eclipse-temurin-21 entrypoint: mvn - args: ["spring-boot:build-image", "-P${_TYPE}"] + args: ["spring-boot:build-image", "-P${_TYPE}", "-DskipTests", "-Dspring-boot.build-image.imageName=reference-${_TYPE}"] - name: gcr.io/cloud-builders/docker args: ["tag", "reference-${_TYPE}", "gcr.io/$PROJECT_ID/reference-${_TYPE}"] images: ["gcr.io/$PROJECT_ID/reference-${_TYPE}"] diff --git a/services/reference/src/main/resources/META-INF/native-image/services/native-image.properties b/services/reference/src/main/resources/META-INF/native-image/services/native-image.properties new file mode 100644 index 00000000..ca7b51b7 --- /dev/null +++ b/services/reference/src/main/resources/META-INF/native-image/services/native-image.properties @@ -0,0 +1,13 @@ +Args=--initialize-at-build-time=\ + ch.qos.logback.core.CoreConstants,\ + ch.qos.logback.core.util.StatusPrinter,\ + org.apache.commons.logging.LogFactoryService,\ + ch.qos.logback.classic.Level,\ + ch.qos.logback.core.util.Loader,\ + org.apache.commons.logging.LogFactory,\ + org.slf4j.MDC,\ + ch.qos.logback.core.status.StatusBase,\ + ch.qos.logback.core.status.InfoStatus,\ + ch.qos.logback.classic.Logger,\ + org.slf4j.LoggerFactory +