diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..39abf1c --- /dev/null +++ b/.classpath @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..878fc8f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,21 @@ +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +target + +# IDE files +.idea +*.iml + +# env file +.env \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..3c9cccb --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,72 @@ +# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: Java CI with Maven + +on: + push: + branches: [ master ] + paths-ignore: + - "**/README.md" + - "terraform/**" + pull_request: + branches: [ master ] + paths-ignore: + - "**/README.md" + - "terraform/**" + +jobs: + + build_local: + + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + cache: maven + + - name: Docker run mysql + run: docker run -p 3306:3306 --env MYSQL_ROOT_PASSWORD=${{secrets.DatabasePassword}} --env MYSQL_DATABASE='springboot_mysql_example' --env MYSQL_USER='osama' --env MYSQL_PASSWORD=${{secrets.DatabasePassword}} -d mysql + + + + - name: Build the JAR file + run: mvn clean install -DskipTests + + + - name: Test + run: mvn test + env: + MYSQL_DB_HOST: 'localhost' + MYSQL_DB_PORT: '3306' + MYSQL_DB_USERNAME: 'osama' + MYSQL_DB_PASSWORD: ${{secrets.DatabasePassword}} + MYSQL_DB_DNAME: 'springboot_mysql_example' + + + + build_docker: + needs: build_local #as we don't push the image unless the build job is done + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v2 + + + - name: Build & push Docker image + uses: mr-smithers-excellent/docker-build-push@v5 + with: + image: osamamagdy/online_store + tags: v1, latest + registry: docker.io + dockerfile: Dockerfile + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} diff --git a/.gitignore b/.gitignore index d46f5ca..8a0d56f 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,14 @@ target # IDE files .idea *.iml + +# env file +.env +k8s_yaml/secret.yaml + +#terraform files +terraform/* + +## except the *.tf files + +!terraform/*.tf \ No newline at end of file diff --git a/.project b/.project new file mode 100644 index 0000000..33768c5 --- /dev/null +++ b/.project @@ -0,0 +1,34 @@ + + + spring-boot-mysql + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + + + 1636074914226 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..839d647 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding/=UTF-8 diff --git a/.settings/org.eclipse.jdt.apt.core.prefs b/.settings/org.eclipse.jdt.apt.core.prefs new file mode 100644 index 0000000..d4313d4 --- /dev/null +++ b/.settings/org.eclipse.jdt.apt.core.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.apt.aptEnabled=false diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..951833c --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,10 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.methodParameters=generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.processAnnotations=disabled +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ca906c2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,12 @@ +FROM maven:latest as builder +WORKDIR /app +COPY . . + +RUN ["mvn","clean","install","-DskipTests"] + +FROM openjdk:latest +COPY --from=builder /app/target/spring-boot-mysql-0.0.1-SNAPSHOT.jar /usr/src/myapp/ +WORKDIR /usr/src/myapp +EXPOSE 8080 + +CMD ["java", "-jar","spring-boot-mysql-0.0.1-SNAPSHOT.jar"] \ No newline at end of file diff --git a/README.md b/README.md index 2e18ca6..f190d79 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,88 @@ -# Spring Boot MySQL Example +# Online store -You can learn more about my courses [here](http://courses.springframework.guru/courses/) on my site. \ No newline at end of file +## Resources + The original spring-boot application was inspired from [here](https://github.com/springframeworkguru/spring-boot-mysql-example) +## Steps to build locally +### 1 - First, ensure to have Java JDK/JRE and maven installed on your local machine
+  a - install Java JDK by running the command `sudo apt install default-jdk -y`
+  b - install Java JRE by running the command `sudo apt install default-jre -y`
+  c - install maven bwith `sudo apt install maven -y`
+### 2 - Second, install and configure mysql
+  a - install from [here](https://linuxize.com/post/how-to-install-mysql-on-ubuntu-18-04/)
+  b - configure mysql by creating a user with `mysql_native_password` for the authentication method. You can take guidance from [here](https://linuxize.com/post/how-to-manage-mysql-databases-and-users-from-the-command-line/#create-a-new-mysql-user-account)
+  c - create database for the application ( use the same guide above )
+### 3 - Put the information in a new `.env` file +``` +MYSQL_DB_HOST= +MYSQL_DB_PORT= +MYSQL_DB_USERNAME= +MYSQL_DB_PASSWORD= +MYSQL_DB_DNAME= +``` +  Note: all names and passwords will be updated in the `src/main/resources/application.properties` file
+### 4 - Build the jar file and run
+  a - Run the command `mvn clean install`. This will create a directory named `target`.
+  b - navigate to the `target` directory and run the jar file with `java -jar `
+## Build locally using Docker +### 1 - Install Docker & Docker-Compose
+  a - Install docker from the official documentation [here](https://docs.docker.com/engine/install/ubuntu/)
+  b - Install docker-compose from the official documentation [here](https://docs.docker.com/compose/install/)
+  Note: If you choose to build with docker, you will not need all the previous installation for running the project locally. You only need to install docker and docker-compose and it saves you the hassle of installing JDK, JRE, maven, and even mysql. + +### 2 - Put the information in a new `.env` file +  For the sake of consistency, we use the same name while building locally
+``` +MYSQL_DB_HOST=db #this one is necessary as it is the service name in docker-compose.yaml file +MYSQL_DB_PORT=3306 #this is the default port used by mysql official image so don't choose any port else +MYSQL_DB_USERNAME= +MYSQL_DB_PASSWORD= +MYSQL_DB_DNAME= +``` +### 3 - Build and Run +  Run the command `docker-compose up` in the directory where docker files are. Docker will first pull the needed images and start working.
+  If you want to edit the configurations and build again, run `docker-compose up --build` to ignore chache and start building again.
+ +## Deploy with kubernetes +### 1 - Install minikube
+  a - For testing purposes, we can deploy all our kubernetes deployments on a locally created single-node cluster with minikube [here](https://minikube.sigs.k8s.io/docs/start/)
+  Note: If you are running your ubuntu on a virtual machine upon other OS, you may need to follow this [guide](https://webme.ie/how-to-run-minikube-on-a-virtualbox-vm/) instead.
+ +### 2 - Put the information in a new `secret.yaml` file (all files are in the k8s_yaml directory) +  a - Here, we will replace the `.env` file with two files. A configmap.yaml which contains non-sensetive data to be accessible by all cluster resources (this one is created for you and is present in the repository.
+  b - A secret.yaml which contains sensetive data that not to be shared on publuic repository so create the file first and fill the data below
+ +``` +apiVersion: v1 +kind: Secret +metadata: + name: mysql-secret +type: Opaque +data: + MYSQL_DB_PORT: + MYSQL_DB_USERNAME: + MYSQL_DB_PASSWORD: + MYSQL_DB_DNAME: +``` +  Important Note: you can not just place the values in the secrets file as a plain text, first you will need to encode them to base64 (as k8s will decode them by default). And the way to do this in linux `echo -n text | base64 `, or you can use a website like [here](https://www.base64decode.org/)
+ +### 3 - Build and Run with external ip +  a - You will need to run `kubectl apply -f .yaml` to apply the configurations of each file. But due to using secrets and services, you will need to execute them in specific order
+   I - secrets file
+   II - mysql deployment files
+   III - mysql service files
+   IV - config map file
+   V - online-store deployment file
+   VI - online-store-service file
+  b - The loadbalancer used in online-store-service will assign an external ip → with using minikube, it will be in pending state until you allow it to take ip by typing “minikube service ” + + Note: If you want to edit any of the configurations, run `kubectl apply -f .yaml`.
+ +### 4 - Build and Run with ingress and domain name +  a - The configuration files used for this are online-store-ingress.yaml and online-store-internal-service.yaml (this will be applied instead of online-store-service.yaml)
+  b - Install the ingress-controller pod in your k8s cluster → with minikube you type `minikube addons enable ingress`and it will be added in the kube-system namespace
+  c - apply online-store-internal-service.yaml ( note that if you used the above steps exactly, you will have 2 services mapping to the same deployment and it is fine for testing. Otherwise, delete the service online-store-service )
+  d - apply the online-store-ingress.yaml ( note that you will replace the file in step 6 above with )
+  e - this will create an IP address to the hostname osama.com → type `kubectl get ingress` to know
+  f - For testing and with using minikube, this is a dummy hostname that is not available publicly. So we have to map it explicitly to the address created by minkube
+  g - to map in your linux, type `sudo nano /etc/hosts` → add the IP address created in step 5 and the hostname associated with it osama.com
+  h - Go to osama.com in your browser to open the application
diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..b2fd539 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,40 @@ +version: '3' +services: + db: + image: mysql:5.7 + restart: always + environment: + MYSQL_DATABASE: "$MYSQL_DB_DNAME" + # So you don't have to use root, but you can if you like + MYSQL_USER: "$MYSQL_DB_USERNAME" + # You can use whatever password you like + MYSQL_PASSWORD: "$MYSQL_DB_PASSWORD" + # Password for root access + MYSQL_ROOT_PASSWORD: "$MYSQL_DB_PASSWORD" + ports: + # : < MySQL Port running inside container> + #Host port is used for outside communication + #MYSQL internal port used for Networked service-to-service communication + - '3000:3306' + volumes: + - my-db:/var/lib/mysql + web-app: + depends_on: + - db + restart: on-failure + links: + - "db:db" + environment: + MYSQL_DB_HOST: ${MYSQL_DB_HOST} + MYSQL_DB_PORT: ${MYSQL_DB_PORT} + MYSQL_DB_USERNAME: ${MYSQL_DB_USERNAME} + MYSQL_DB_PASSWORD: ${MYSQL_DB_PASSWORD} + MYSQL_DB_DNAME: ${MYSQL_DB_DNAME} + build: + context: . + dockerfile: Dockerfile + ports: + - "8080:8080" +## Names our volume +volumes: + my-db: \ No newline at end of file diff --git a/k8s_yaml/configmap.yaml b/k8s_yaml/configmap.yaml new file mode 100644 index 0000000..fd7ef6c --- /dev/null +++ b/k8s_yaml/configmap.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: online-store-configmap +data: + MYSQL_DB_HOST: mysql-service \ No newline at end of file diff --git a/k8s_yaml/mysql-deployment.yaml b/k8s_yaml/mysql-deployment.yaml new file mode 100644 index 0000000..58f304e --- /dev/null +++ b/k8s_yaml/mysql-deployment.yaml @@ -0,0 +1,42 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mysql-deployment + labels: + app: mysql +spec: + replicas: 1 + selector: + matchLabels: + app: mysql + template: + metadata: + labels: + app: mysql + spec: + containers: + - name: mysql + image: mysql + ports: + - containerPort: 3306 + env: + - name: MYSQL_DATABASE + valueFrom: + secretKeyRef: + name: mysql-secret + key: MYSQL_DB_DNAME + - name: MYSQL_USER + valueFrom: + secretKeyRef: + name: mysql-secret + key: MYSQL_DB_USERNAME + - name: MYSQL_PASSWORD + valueFrom: + secretKeyRef: + name: mysql-secret + key: MYSQL_DB_PASSWORD + - name: MYSQL_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: mysql-secret + key: MYSQL_DB_PASSWORD diff --git a/k8s_yaml/mysql-service.yaml b/k8s_yaml/mysql-service.yaml new file mode 100644 index 0000000..d67f415 --- /dev/null +++ b/k8s_yaml/mysql-service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: mysql-service +spec: + selector: + app: mysql + ports: + - protocol: TCP + port: 3306 + targetPort: 3306 \ No newline at end of file diff --git a/k8s_yaml/online-store-deployment.yaml b/k8s_yaml/online-store-deployment.yaml new file mode 100644 index 0000000..5b87071 --- /dev/null +++ b/k8s_yaml/online-store-deployment.yaml @@ -0,0 +1,47 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: online-store + labels: + app: online-store +spec: + replicas: 1 + selector: + matchLabels: + app: online-store + template: + metadata: + labels: + app: online-store + spec: + containers: + - name: online-store + image: osamamagdy/online_store + ports: + - containerPort: 8080 + env: + - name: MYSQL_DB_HOST + valueFrom: + configMapKeyRef: + name: online-store-configmap + key: MYSQL_DB_HOST + - name: MYSQL_DB_PORT + valueFrom: + secretKeyRef: + name: mysql-secret + key: MYSQL_DB_PORT + - name: MYSQL_DB_USERNAME + valueFrom: + secretKeyRef: + name: mysql-secret + key: MYSQL_DB_USERNAME + - name: MYSQL_DB_PASSWORD + valueFrom: + secretKeyRef: + name: mysql-secret + key: MYSQL_DB_PASSWORD + - name: MYSQL_DB_DNAME + valueFrom: + secretKeyRef: + name: mysql-secret + key: MYSQL_DB_DNAME \ No newline at end of file diff --git a/k8s_yaml/online-store-ingress.yaml b/k8s_yaml/online-store-ingress.yaml new file mode 100644 index 0000000..dfc69dd --- /dev/null +++ b/k8s_yaml/online-store-ingress.yaml @@ -0,0 +1,16 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: online-store-ingress +spec: + rules: + - host: osama.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: online-store-internal-service + port: + number: 8080 \ No newline at end of file diff --git a/k8s_yaml/online-store-internal-service.yaml b/k8s_yaml/online-store-internal-service.yaml new file mode 100644 index 0000000..80f0e49 --- /dev/null +++ b/k8s_yaml/online-store-internal-service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: online-store-internal-service #this is the one used bby ingress controller +spec: + selector: + app: online-store + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 \ No newline at end of file diff --git a/k8s_yaml/online-store-service.yaml b/k8s_yaml/online-store-service.yaml new file mode 100644 index 0000000..a945adb --- /dev/null +++ b/k8s_yaml/online-store-service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: online-store-service +spec: + selector: + app: online-store + type: LoadBalancer + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 + nodePort: 30000 #this is the port for external ip address that you will type into your browser \ No newline at end of file diff --git a/pom.xml b/pom.xml index 0002268..29e2e68 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ org.springframework.boot spring-boot-starter-parent - 2.0.0.RELEASE + 2.1.6.RELEASE @@ -47,6 +47,11 @@ spring-boot-starter-test test + + javax.xml.bind + jaxb-api + 2.3.0 + @@ -55,6 +60,11 @@ org.springframework.boot spring-boot-maven-plugin + + org.apache.maven.plugins + maven-surefire-plugin + 2.19.1 + @@ -95,4 +105,4 @@ -
+ \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8e4417a..0d5c1d3 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -2,9 +2,9 @@ # = DATA SOURCE # =============================== # Set here configurations for the database connection -spring.datasource.url=jdbc:mysql://localhost:3306/springboot_mysql_example -spring.datasource.username=root -#spring.datasource.password=YOUR_DB_PASSWORD +spring.datasource.url=jdbc:mysql://${MYSQL_DB_HOST}:${MYSQL_DB_PORT}/${MYSQL_DB_DNAME}?enabledTLSProtocols=TLSv1.2 +spring.datasource.username=${MYSQL_DB_USERNAME} +spring.datasource.password=${MYSQL_DB_PASSWORD} spring.datasource.driver-class-name=com.mysql.jdbc.Driver # Keep the connection alive if idle for a long time (needed in production) @@ -22,4 +22,4 @@ spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy # Allows Hibernate to generate SQL optimized for a particular DBMS -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect \ No newline at end of file +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..6631829 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,63 @@ +// Configure the Google Cloud provider +provider "google" { + credentials = file("CRED.json") + project = "even-sun-331510" + region = "us-west1" +} + +// Terraform plugin for creating random ids +resource "random_id" "instance_id" { + byte_length = 8 +} + + +// A single Compute Engine instance +resource "google_compute_instance" "default" { + name = "capiter-${random_id.instance_id.hex}" + machine_type = "f1-micro" //As the server is intended to run calculate cimplex equations + zone = "us-west1-a" + + boot_disk { + initialize_params { + image = "debian-cloud/debian-9" + size = "20" + } + } + metadata = { + ssh-keys = "osamamagdy174@gmail.com:${file("~/.ssh/id_ed25519.pub")}" + } + + network_interface { + network = "default" + + access_config { + // Include this section to give the VM an external ip address + } + } + + lifecycle { + ignore_changes = [attached_disk] + } +} + +resource "google_compute_disk" "default" { + name = "capiter-disk" + type = "pd-ssd" //better for intensive applications with low latency + zone = "us-west1-a" + size = "100" + labels = { + environment = "dev" + } + physical_block_size_bytes = 4096 +} + +//Attach the resource disk to be attached to the compute instance +resource "google_compute_attached_disk" "default" { + disk = google_compute_disk.default.id + instance = google_compute_instance.default.id +} + +// A variable for extracting the external IP address of the instance +output "ip" { + value = google_compute_instance.default.network_interface.0.access_config.0.nat_ip +} \ No newline at end of file