diff --git a/.github/workflows/build-demo-app.yaml b/.github/workflows/build-demo-app.yaml new file mode 100644 index 0000000..2fdbfa3 --- /dev/null +++ b/.github/workflows/build-demo-app.yaml @@ -0,0 +1,45 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# 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. + +name: Build Demo App +on: + push: + branches: + - main + paths: + - 'demo-app/**' + pull_request: + branches: + - main + paths: + - 'demo-app/**' + +jobs: + build: + name: Build + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./demo-app + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'zulu' + - name: Build + run: mvn --batch-mode --update-snapshots package + \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4291e90..4ba5d3d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,9 +17,13 @@ on: push: branches: - main + paths-ignore: + - 'demo-app/**' pull_request: branches: - main + paths-ignore: + - 'demo-app/**' jobs: build: diff --git a/.github/workflows/release-demo-app.yaml b/.github/workflows/release-demo-app.yaml new file mode 100644 index 0000000..b3f6d12 --- /dev/null +++ b/.github/workflows/release-demo-app.yaml @@ -0,0 +1,61 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# 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. + +name: Spring Rest JPA Release +on: + push: + tags: + - "demo-app/v*" + +jobs: + build: + name: Build + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./demo-app + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'zulu' + - name: Build + run: mvn --batch-mode --update-snapshots package + - name: Docker login + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Version from tag + id: version-from-tag + run: echo version=${GITHUB_REF_NAME//demo-app\/} >> $GITHUB_OUTPUT + - name: Docker Extract metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: ghcr.io/google/spring-demo-app + tags: | + type=semver,pattern={{major}}.{{minor}}.{{patch}},value=${{ steps.version-from-tag.outputs.version }} + - name: Docker build and push + uses: docker/build-push-action@v4 + with: + context: ./demo-app + file: ./demo-app/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/demo-app/Dockerfile b/demo-app/Dockerfile new file mode 100644 index 0000000..36b8174 --- /dev/null +++ b/demo-app/Dockerfile @@ -0,0 +1,19 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# 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. + +FROM azul/zulu-openjdk-alpine:17-latest +VOLUME /tmp +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar ${0} ${@}"] \ No newline at end of file diff --git a/demo-app/README.md b/demo-app/README.md new file mode 100644 index 0000000..acac142 --- /dev/null +++ b/demo-app/README.md @@ -0,0 +1,49 @@ +# spring-demo-app + +The folder contains source code of a simple Java application that uses Spring Boot framework +and exposes the REST endpoint. The data is fetched from the relational database with +Java Persistence API. + +## Usage + +The service exposes below endpoints: + +* `/books` returns collection of books + +In the addition to above, Spring Boot actuator endpoints are exposed per configuration. + +### Source code + +Prerequisites: + +* [JDK](https://openjdk.org/projects/jdk/17/) 17 or newer +* [Maven](https://maven.apache.org/download.cgi) + +Build application from the source code. + +```sh +mvn package +java -jar target/spring-demo-app-0.0.1-SNAPSHOT.jar +``` + +### Container image + +1. Adjust `application.yaml` according to your needs +2. Run the container + + ```sh + docker run -d --name spring-demo-app \ + -p 8080:8080 \ + -v "`pwd`/application.yaml:/application.yaml" \ + ghcr.io/google/spring-demo-app:latest + ``` + +### Kubernetes + +1. Adjust `application.yaml` according to your needs +2. Adjust `kustomization.yaml` according to your needs +3. Customize and apply Kubernetes manifests + + ```sh + kubectl kustomize | kubectl apply -f + ``` diff --git a/demo-app/application.yaml b/demo-app/application.yaml new file mode 100644 index 0000000..2744f7f --- /dev/null +++ b/demo-app/application.yaml @@ -0,0 +1,32 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# 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. + +spring: + datasource: + url: jdbc:h2:mem:mydb + username: sa + password: password + driverClassName: org.h2.Driver + jpa: + hibernate: + ddl-auto: create + database-platform: org.hibernate.dialect.H2Dialect + defer-datasource-initialization: true +management: + endpoints: + web: + exposure: + include: + - health + - prometheus diff --git a/demo-app/examples/spring-demo-app-gmp.yaml b/demo-app/examples/spring-demo-app-gmp.yaml new file mode 100644 index 0000000..c31b2df --- /dev/null +++ b/demo-app/examples/spring-demo-app-gmp.yaml @@ -0,0 +1,26 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# 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. + +apiVersion: monitoring.googleapis.com/v1 +kind: PodMonitoring +metadata: + name: spring-demo-app +spec: + selector: + matchLabels: + app.kubernetes.io/name: spring-demo-app + endpoints: + - port: http + path: /actuator/prometheus + interval: 5s diff --git a/demo-app/examples/spring-demo-app.yaml b/demo-app/examples/spring-demo-app.yaml new file mode 100644 index 0000000..20ed991 --- /dev/null +++ b/demo-app/examples/spring-demo-app.yaml @@ -0,0 +1,82 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# 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. + +apiVersion: v1 +kind: ServiceAccount +metadata: + name: spring-demo-app +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: spring-demo-app +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: spring-demo-app + template: + metadata: + labels: + app.kubernetes.io/name: spring-demo-app + spec: + serviceAccountName: spring-demo-app + containers: + - name: spring-demo-app + image: ghcr.io/google/spring-demo-app:latest + args: + - --spring.config.location=file:/config/application.yaml + env: + - name: JAVA_OPTS + value: -XX:MaxRAMPercentage=75 + volumeMounts: + - name: spring-demo-app-config + mountPath: /config + ports: + - name: http + containerPort: 8080 + startupProbe: + periodSeconds: 2 + failureThreshold: 60 + httpGet: + path: /actuator/health + port: http + scheme: HTTP + livenessProbe: + httpGet: + path: /actuator/health + port: http + scheme: HTTP + resources: + limits: + cpu: "1" + memory: 512Mi + requests: + cpu: 500m + memory: 512Mi + volumes: + - name: spring-demo-app-config + configMap: + name: spring-demo-app-config +--- +apiVersion: v1 +kind: Service +metadata: + name: spring-demo-app +spec: + selector: + app.kubernetes.io/name: spring-demo-app + ports: + - port: 8080 + targetPort: http diff --git a/demo-app/kustomization.yaml b/demo-app/kustomization.yaml new file mode 100644 index 0000000..90148c2 --- /dev/null +++ b/demo-app/kustomization.yaml @@ -0,0 +1,28 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# 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. + +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +- examples/spring-demo-app.yaml +# Uncomment for Google Managed Prometheus metrics scraping +#- examples/spring-app-demo-gmp.yaml + +namespace: demo + +configMapGenerator: +- name: spring-demo-app-config + files: + - application.yaml diff --git a/demo-app/pom.xml b/demo-app/pom.xml new file mode 100644 index 0000000..a1bbce2 --- /dev/null +++ b/demo-app/pom.xml @@ -0,0 +1,77 @@ + + + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.1.1 + + + com.example + spring-demo-app + 0.0.1-SNAPSHOT + spring-demo-app + Demo Spring Boot app with REST controller and JPA + + 17 + + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.springframework.boot + spring-boot-starter-web + + + io.micrometer + micrometer-registry-prometheus + runtime + + + com.h2database + h2 + runtime + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/demo-app/src/main/java/dev/stefaniak/demo/DemoApplication.java b/demo-app/src/main/java/dev/stefaniak/demo/DemoApplication.java new file mode 100644 index 0000000..c4cf93e --- /dev/null +++ b/demo-app/src/main/java/dev/stefaniak/demo/DemoApplication.java @@ -0,0 +1,25 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// 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. + +package com.example.demo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DemoApplication { + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } +} diff --git a/demo-app/src/main/java/dev/stefaniak/demo/db/Book.java b/demo-app/src/main/java/dev/stefaniak/demo/db/Book.java new file mode 100644 index 0000000..11b8365 --- /dev/null +++ b/demo-app/src/main/java/dev/stefaniak/demo/db/Book.java @@ -0,0 +1,69 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// 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. + +package com.example.demo.db; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; + +@Entity +public class Book { + @Id + @GeneratedValue + private Long id; + private String title; + private String author; + private String category; + + public Book() {} + + public Book(String title, String author, String category) { + this.title = title; + this.author = author; + this.category = category; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } +} diff --git a/demo-app/src/main/java/dev/stefaniak/demo/db/BookRepository.java b/demo-app/src/main/java/dev/stefaniak/demo/db/BookRepository.java new file mode 100644 index 0000000..81d42b3 --- /dev/null +++ b/demo-app/src/main/java/dev/stefaniak/demo/db/BookRepository.java @@ -0,0 +1,23 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// 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. + +package com.example.demo.db; + +import java.util.List; +import java.util.Optional; +import org.springframework.data.repository.CrudRepository; + +public interface BookRepository extends CrudRepository { + List findAll(); +} diff --git a/demo-app/src/main/java/dev/stefaniak/demo/rest/Book.java b/demo-app/src/main/java/dev/stefaniak/demo/rest/Book.java new file mode 100644 index 0000000..91ea61b --- /dev/null +++ b/demo-app/src/main/java/dev/stefaniak/demo/rest/Book.java @@ -0,0 +1,18 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// 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. + +package com.example.demo.rest; + +public record Book(Long id, String title, String author, String category) { +} diff --git a/demo-app/src/main/java/dev/stefaniak/demo/rest/BookRestController.java b/demo-app/src/main/java/dev/stefaniak/demo/rest/BookRestController.java new file mode 100644 index 0000000..4e24b01 --- /dev/null +++ b/demo-app/src/main/java/dev/stefaniak/demo/rest/BookRestController.java @@ -0,0 +1,50 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// 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. + +package com.example.demo.rest; + +import com.example.demo.service.BookService; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class BookRestController { + private BookService service; + + @ResponseStatus(value = HttpStatus.NOT_FOUND) + public class ResourceNotFoundException extends RuntimeException { + } + public BookRestController(BookService service) { + this.service = service; + } + + @GetMapping("/books") + public List books() { + return service.getAll().stream() + .map(b -> new Book(b.getId(), b.getTitle(), b.getAuthor(), b.getCategory())) + .collect(Collectors.toList()); + } + + @GetMapping("/book/{id}") + public Book book(@PathVariable Long id) { + return service.get(id) + .map(b -> new Book(b.getId(), b.getTitle(), b.getAuthor(), b.getCategory())) + .orElseThrow(ResourceNotFoundException::new); + } +} diff --git a/demo-app/src/main/java/dev/stefaniak/demo/service/Book.java b/demo-app/src/main/java/dev/stefaniak/demo/service/Book.java new file mode 100644 index 0000000..9ccf27c --- /dev/null +++ b/demo-app/src/main/java/dev/stefaniak/demo/service/Book.java @@ -0,0 +1,61 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// 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. + +package com.example.demo.service; + +public class Book { + private Long id; + private String title; + private String author; + private String category; + + public Book(Long id, String title, String author, String category) { + this.id = id; + this.title = title; + this.author = author; + this.category = category; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } +} diff --git a/demo-app/src/main/java/dev/stefaniak/demo/service/BookService.java b/demo-app/src/main/java/dev/stefaniak/demo/service/BookService.java new file mode 100644 index 0000000..90dd356 --- /dev/null +++ b/demo-app/src/main/java/dev/stefaniak/demo/service/BookService.java @@ -0,0 +1,41 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// 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. + +package com.example.demo.service; + +import com.example.demo.db.BookRepository; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import org.springframework.stereotype.Service; + +@Service +public class BookService { + private BookRepository repository; + + public BookService(BookRepository repository) { + this.repository = repository; + } + + public List getAll() { + return repository.findAll().stream() + .map(b -> new Book(b.getId(), b.getTitle(), b.getAuthor(), b.getCategory())) + .collect(Collectors.toList()); + } + + public Optional get(Long id) { + return repository.findById(id) + .map(b -> new Book(b.getId(), b.getTitle(), b.getAuthor(), b.getCategory())); + } +} diff --git a/demo-app/src/main/resources/data.sql b/demo-app/src/main/resources/data.sql new file mode 100644 index 0000000..c8c05b4 --- /dev/null +++ b/demo-app/src/main/resources/data.sql @@ -0,0 +1,18 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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. + */ + +INSERT INTO BOOK(ID, TITLE, AUTHOR, CATEGORY) VALUES (1, 'Ubik', 'Philip K. Dick', 'Science Fiction'); +INSERT INTO BOOK(ID, TITLE, AUTHOR, CATEGORY) VALUES (2, 'Enders game', 'Orson Scott Card', 'Science Fiction'); \ No newline at end of file