diff --git a/auth-api/Dockerfile b/auth-api/Dockerfile new file mode 100644 index 000000000..6be1d76cd --- /dev/null +++ b/auth-api/Dockerfile @@ -0,0 +1,42 @@ + +# Phase 1: Defining build phase to compile code. +FROM golang:1.18-alpine AS builder + +WORKDIR /app + +COPY . . + +# Env variable useful to define newer module system tha was implemented on Go 1.11. + +ENV GO111MODULE=on + +# Download dependencies and compile application process. + +RUN go mod init github.com/bortizf/microservice-app-example/tree/master/auth-api && \ + go mod tidy &&\ + go build -o auth-api . + +# Phase 2: Image for runtime + +FROM alpine:latest + +# DevSecOps practice: Avoid the use of root user on container by adding other user with limited permissions. + +RUN addgroup -S appgroup && adduser -S appuser -G appgroup + +WORKDIR /app + +# From phase 1 we copy the compiled file and then execute it (Remenber the alias "builder" assign in phase 1). + +COPY --from=builder /app/auth-api . + +RUN chown appuser:appgroup /app/auth-api + +# Must specify appuser to avoid the use of root user. + +USER appuser + +CMD ["./auth-api"] + + + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..9ff5fcd50 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,124 @@ +services: + + #-------------------------User-auth context------------------------- + + #Useful to visualize and analize microservices request tracking. + zipkin: + image: openzipkin/zipkin + ports: + - "${ZIPKIN_PORT}:${ZIPKIN_PORT}" + environment: + - CORS_ALLOWED_ORIGINS=* + networks: + - logger + - users-auth + + auth-api: + build: + context: ./auth-api/ + dockerfile: Dockerfile + + #TODO: must implement arg to pass env variables from github secrets. + environment: + JWT_SECRET: ${JWT_SECRET} + USERS_API_ADDRESS: ${USERS_API_ADDRESS} + AUTH_API_PORT: ${AUTH_API_PORT} + + ports: + - "${AUTH_API_PORT}:${AUTH_API_PORT}" + networks: + - users-auth + depends_on: + - users-api + + users-api: + build: + context: ./users-api/ + dockerfile: Dockerfile + environment: + #TODO: must implement arg to pass env variables from github secrets. + JWT_SECRET: ${JWT_SECRET} + SERVER_PORT: ${USERS_API_PORT} + ports: + - "${USERS_API_PORT}:${USERS_API_PORT}" + networks: + - users-auth + + + +#-------------------------Logs and Messaging------------------------- + + redis: + image: redis:7.0 + ports: + - "${REDIS_PORT}:${REDIS_PORT}" + networks: + - logger + + + logg-processor: + build: + context: ./log-message-processor/ + dockerfile: Dockerfile + environment: + #TODO: must implement arg to pass env variables from github secrets. + REDIS_HOST: "${REDIS_HOST}" + REDIS_PORT: "${REDIS_PORT}" + REDIS_CHANNEL: "${REDIS_CHANNEL}" + networks: + - logger + depends_on: + - redis + + todos-api: + build: + context: ./todos-api/ + dockerfile: Dockerfile + environment: + #TODO: must implement arg to pass env variables from github secrets. + JWT_SECRET: ${JWT_SECRET} + TODO_API_PORT: ${TODO_API_PORT} + REDIS_HOST: ${REDIS_HOST} + REDIS_PORT: ${REDIS_PORT} + REDIS_CHANNEL: ${REDIS_CHANNEL} + ports: + - "${TODO_API_PORT}:${TODO_API_PORT}" + networks: + - logger + depends_on: + - auth-api + +#-------------------------Frontend------------------------- + + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + args: + - VUE_APP_ZIPKIN_URL="${ZIPKIN_URL}" + + environment: + AUTH_API_ADDRESS: "http://auth-api:${AUTH_API_PORT}" + TODOS_API_ADDRESS: "http://todos-api:${TODO_API_PORT}" + VUE_APP_ZIPKIN_URL: "${ZIPKIN_URL}" + ports: + - "80:80" + networks: + - users-auth + - logger + depends_on: + - auth-api + - todos-api + + +#-------------------------Networks------------------------- + +networks: + users-auth: + driver: bridge + logger: + driver: bridge + + + \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore index 1622bc4c1..dc444b3dc 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -11,3 +11,4 @@ yarn-error.log* *.ntvs* *.njsproj *.sln +.env diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 000000000..72dae29e5 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,42 @@ +# Phase 1: Defining build phase to compile code. + +FROM node:8.17.0-slim as builder + +# Ensuring the env variables can be use on building phase +ARG VUE_APP_ZIPKIN_URL +ENV VUE_APP_ZIPKIN_URL=$VUE_APP_ZIPKIN_URL + +WORKDIR /app + +# Copy all dependecies to be install +COPY package*.json ./ +RUN npm install npm@6.13.4 -g && \ + npm install + +COPY . . +RUN npm run build + +# Phase 2: Web server to expose frontend, must redirect request trought nginx proxy. +FROM nginx:alpine + +# We must install gettex to insert env varibales on default.conf.template to be used as +# nginx configuration. +RUN apk add --no-cache gettext + +# We remove nginx previous files. +RUN rm -rf /etc/nginx/conf.d/* + +#Copy nginx template or blueprint where env variables will be insert. +COPY default.conf.template /etc/nginx/conf.d/default.conf.template + +#Must execute .sh to apply gettext process and execute nginx web server +COPY entrypoint.sh /entrypoint.sh + +# From phase 1 we copy the compiled file and then execute it (Remenber the alias "builder" assign in phase 1). + +COPY --from=builder /app/dist /usr/share/nginx/html + +#Convert entrypoint executable +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] \ No newline at end of file diff --git a/frontend/default.conf.template b/frontend/default.conf.template new file mode 100644 index 000000000..89c6055dc --- /dev/null +++ b/frontend/default.conf.template @@ -0,0 +1,76 @@ +server { + listen 80; + server_name localhost; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /auth-api/ { + + #We must add "/" at the end to avoid the use of "/auth-api" prefix + + proxy_pass ${AUTH_API_ADDRESS}/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + + # CORS configuration + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization'; + + # Preflight requests manager + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'text/plain; charset=utf-8'; + add_header 'Content-Length' 0; + return 204; + } + } + + location /todos-api/ { + + #Define URL based on env varibles. + + #We must add "/" at the end to avoid the use of "/auth-api" prefix + + proxy_pass ${TODOS_API_ADDRESS}/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + + # CORS configuration + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization'; + + # Management of preflight requests, and define allowed headers + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'text/plain; charset=utf-8'; + add_header 'Content-Length' 0; + return 204; + } + } + + location ~* \.(?:css|js|json|woff2?|ttf|eot|svg|png|jpg|jpeg|gif|ico)$ { + expires 1y; + add_header Cache-Control "public, max-age=31536000"; + } + + add_header X-Content-Type-Options nosniff; + add_header X-Frame-Options DENY; + add_header X-XSS-Protection "1; mode=block"; +} \ No newline at end of file diff --git a/frontend/entrypoint.sh b/frontend/entrypoint.sh new file mode 100644 index 000000000..e4734cdc6 --- /dev/null +++ b/frontend/entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +# Currently we are using gettext to insert this env variables on the specified file. +envsubst '${AUTH_API_ADDRESS} ${TODOS_API_ADDRESS}' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf + +# Run Nginx web server. +exec nginx -g 'daemon off;' \ No newline at end of file diff --git a/frontend/src/auth.js b/frontend/src/auth.js index 88d135435..aff4fd0fb 100644 --- a/frontend/src/auth.js +++ b/frontend/src/auth.js @@ -5,14 +5,15 @@ import decode from 'jwt-decode' /** * @var{string} LOGIN_URL The endpoint for logging in. This endpoint should be proxied by Webpack dev server - * and maybe nginx in production (cleaner calls and avoids CORS issues). + * and maybe nginx in production (cleaner calls and avoids CORS issues). Notice we add "/auth-api/" prefix + * to redirect request with best practices. */ -const LOGIN_URL = window.location.protocol + '//' + window.location.host + '/login' +const LOGIN_URL = window.location.protocol + '//' + window.location.host + '/auth-api/login' const ROLE_ADMIN = 'ADMIN' /** * Auth Plugin -* +* * (see https://vuejs.org/v2/guide/plugins.html for more info on Vue.js plugins) * * Handles login and token authentication using OAuth2. diff --git a/frontend/src/components/Todos.vue b/frontend/src/components/Todos.vue index 801545d55..b47124349 100644 --- a/frontend/src/components/Todos.vue +++ b/frontend/src/components/Todos.vue @@ -57,7 +57,10 @@ import AppNav from '@/components/AppNav' import TodoItem from '@/components/TodoItem' import Spinner from '@/components/common/Spinner' +const BASE_URL = "/todos-api" + export default { + name: 'todos', components: {AppNav, TodoItem, Spinner}, props: { @@ -71,7 +74,8 @@ export default { return { isProcessing: false, errorMessage: '', - newTask: '' + newTask: '', + url: BASE_URL } }, created () { @@ -86,7 +90,7 @@ export default { loadTasks () { this.isProcessing = true this.errorMessage = '' - this.$http.get('/todos').then(response => { + this.$http.get( `${BASE_URL}/todos`).then(response => { for (var i in response.body) { this.tasks.push(response.body[i]) } @@ -106,7 +110,7 @@ export default { content: this.newTask } - this.$http.post('/todos', task).then(response => { + this.$http.post(`${BASE_URL}/todos`, task).then(response => { this.newTask = '' this.isProcessing = false this.tasks.push(task) @@ -123,7 +127,7 @@ export default { this.isProcessing = true this.errorMessage = '' - this.$http.delete('/todos/' + item.id).then(response => { + this.$http.delete(`${BASE_URL}/todos/` + item.id).then(response => { this.isProcessing = false this.tasks.splice(index, 1) }, error => { diff --git a/frontend/src/zipkin.js b/frontend/src/zipkin.js index 82ced7d2d..fc05b9d80 100644 --- a/frontend/src/zipkin.js +++ b/frontend/src/zipkin.js @@ -6,7 +6,7 @@ import { } from 'zipkin' import {HttpLogger} from 'zipkin-transport-http' import {zipkinInterceptor} from 'zipkin-instrumentation-vue-resource' -const ZIPKIN_URL = window.location.protocol + '//' + window.location.host + '/zipkin' +const ZIPKIN_URL = process.env.VUE_APP_ZIPKIN_URL || 'http://localhost:9411/api/v2/spans'; /** * Tracing plugin that uses Zipkin. Initiates new traces with outgoing requests * and injects appropriate headers. diff --git a/log-message-processor/Dockerfile b/log-message-processor/Dockerfile new file mode 100644 index 000000000..a604c70a1 --- /dev/null +++ b/log-message-processor/Dockerfile @@ -0,0 +1,23 @@ + +FROM python:3.6-slim + +WORKDIR /app + +# Install build dependencies necesaries to install dependecies +RUN apt-get update && apt-get install -y \ + build-essential \ + gcc \ + python3-dev \ + && rm -rf /var/lib/apt/lists/* + + +COPY requirements.txt . + +#Install code dependecies. + +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +CMD [ "python3", "main.py" ] + diff --git a/setEnvs.sh b/setEnvs.sh new file mode 100755 index 000000000..daacd78d8 --- /dev/null +++ b/setEnvs.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Set environment variables + +#AUTH-API +export JWT_SECRET="PRFT" +export AUTH_API_PORT="8000" + +#USER-API +export USERS_API_ADDRESS="http://users-api:8083" +export USERS_API_PORT="8083" +export ZIPKIN_URL="http://zipkin:9411/api/v2/spans" +export ZIPKIN_PORT="9411" + +#PYTHON-LOG +export REDIS_HOST="redis" +export REDIS_PORT="6379" +export REDIS_CHANNEL="log_channel" +export TODO_API_PORT="8082" + +#FRONTEND +export FRONTEND_PORT="80" + diff --git a/todos-api/Dockerfile b/todos-api/Dockerfile new file mode 100644 index 000000000..933a3f23d --- /dev/null +++ b/todos-api/Dockerfile @@ -0,0 +1,16 @@ +#We ensure node version specified. +FROM node:8.17 + +WORKDIR /app + +# Ensure npm version specified + +RUN npm install -g npm@6.13.4 + +COPY package*.json ./ + +RUN npm install + +COPY . . + +CMD ["npm", "start"] \ No newline at end of file diff --git a/todos-api/server.js b/todos-api/server.js index 7e2570991..28aafc5f1 100644 --- a/todos-api/server.js +++ b/todos-api/server.js @@ -3,7 +3,7 @@ const express = require('express') const bodyParser = require("body-parser") const jwt = require('express-jwt') -const ZIPKIN_URL = process.env.ZIPKIN_URL || 'http://127.0.0.1:9411/api/v2/spans'; +const ZIPKIN_URL = process.env.ZIPKIN_URL || 'http://zipkin:9411/api/v2/spans'; const {Tracer, BatchRecorder, jsonEncoder: {JSON_V2}} = require('zipkin'); diff --git a/users-api/.gitignore b/users-api/.gitignore index 2af7cefb0..85ab9f344 100644 --- a/users-api/.gitignore +++ b/users-api/.gitignore @@ -21,4 +21,5 @@ build/ nbbuild/ dist/ nbdist/ -.nb-gradle/ \ No newline at end of file +.nb-gradle/ +setEnvs.sh diff --git a/users-api/Dockerfile b/users-api/Dockerfile new file mode 100644 index 000000000..e6a99aaa8 --- /dev/null +++ b/users-api/Dockerfile @@ -0,0 +1,21 @@ +# Phase 1: Defining build phase to compile code. + +FROM maven:3.8.4-openjdk-8-slim AS builder +WORKDIR /app +COPY . . + +# Build using the Maven installation in the image +#TODO: Verify why can not use maven wrapper. +RUN mvn clean install + +FROM openjdk:8-jre-slim +WORKDIR /app + + +# From phase 1 we copy the compiled file and then execute it (Remenber the alias "builder" assign in phase 1). + +COPY --from=builder /app/target/users-api-0.0.1-SNAPSHOT.jar /app/users-api.jar + +# Execute .jar from building phaser + +CMD ["java", "-jar", "/app/users-api.jar"] \ No newline at end of file