diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml
new file mode 100644
index 0000000..3fc61a0
--- /dev/null
+++ b/.github/workflows/docker-image.yml
@@ -0,0 +1,25 @@
+name: Docker Image CI
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: docker login
+ env:
+ DOCKER_USER: ${{secrets.DOCKER_USERNAME}}
+ DOCKER_PASSWORD: ${{secrets.DOCKER_PASSWORD}}
+ run: |
+ echo "$DOCKER_PASSWORD" | docker login -u $DOCKER_USER --password-stdin
+ - name: Build the kanban-app Docker image
+ run: cd kanban-app && docker build . --file Dockerfile --tag lakshmi1995/kanban-app:v${{ github.run_id }}
+ - name: Build the kanban-ui Docker image
+ run: cd kanban-ui && docker build . --file Dockerfile --tag lakshmi1995/kanban-ui:v${{ github.run_id }}
+ - name: Docker Push kanban-ui
+ run: docker push lakshmi1995/kanban-ui:v${{ github.run_id }}
+ - name: Docker Push kanban-app
+ run: docker push lakshmi1995/kanban-app:v${{ github.run_id }}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 723ef36..3808401 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
-.idea
\ No newline at end of file
+.idea
+Welcome back to git
diff --git a/Charts/Chart.yaml b/Charts/Chart.yaml
new file mode 100644
index 0000000..266b48e
--- /dev/null
+++ b/Charts/Chart.yaml
@@ -0,0 +1,10 @@
+name: postgresql
+version: 0.11.1
+description: Object-relational database management system (ORDBMS) with an emphasis
+ on extensibility and on standards-compliance.
+engine: gotpl
+home: https://www.postgresql.org/
+
+keywords:
+- postgresql
+- postgres
diff --git a/Charts/files/entrypoint.sh b/Charts/files/entrypoint.sh
new file mode 100644
index 0000000..7a1696c
--- /dev/null
+++ b/Charts/files/entrypoint.sh
@@ -0,0 +1,187 @@
+#!/usr/bin/env bash
+set -e
+
+if [ "$1" = 'postgres' ]; then
+ [[ ${POD_NAME} =~ -([0-9]+)$ ]] || exit 1
+ ordinal=${BASH_REMATCH[1]}
+ if [ $STATEFUL_TYPE == "master" ]; then
+ node_id=$((${ordinal} + 1))
+ service=${MASTER_SERVICE}
+ else
+ node_id=$((${ordinal} + 100))
+ service=${POD_NAME}
+ fi
+
+ sed \
+ -e "s|^#cluster=.*$|cluster=default|" \
+ -e "s|^#node=.*$|node=${node_id}|" \
+ -e "s|^#node_name=.*$|node_name=${POD_NAME}|" \
+ -e "s|^#conninfo=.*$|conninfo='host=${service} dbname=repmgr user=repmgr password=${REPMGR_PASSWORD} application_name=repmgrd'|" \
+ -e "s|^#use_replication_slots=.*$|use_replication_slots=1|" \
+ /etc/repmgr.conf.tpl > /etc/repmgr.conf
+
+ if [ ! -s "${PGDATA}/PG_VERSION" ]; then
+ if [ ${STATEFUL_TYPE} == "master" ]; then
+ docker-entrypoint.sh "$@" --boot
+
+ sed -i \
+ -e "s|^listen_addresses = .*|listen_addresses = '*'|" \
+ -e "s|^#hot_standby = .*|hot_standby = on|" \
+ -e "s|^#wal_level = .*|wal_level = hot_standby|" \
+ -e "s|^#max_wal_senders = .*|max_wal_senders = 10|" \
+ -e "s|^#max_replication_slots = .*|max_replication_slots = 10|" \
+ -e "s|^#archive_mode = .*|archive_mode = on|" \
+ -e "s|^#archive_command = .*|archive_command = '/bin/true'|" \
+ -e "s|^#shared_preload_libraries = .*|shared_preload_libraries = 'repmgr_funcs'|" \
+ ${PGDATA}/postgresql.conf
+
+ host_type="host"
+ options=""
+
+ if [ -f "/certs/server.key" ]; then
+ host_type="hostssl"
+
+ if [ -f "/certs/postgresql.key" ]; then
+ options="clientcert=1"
+ fi
+
+ # Server Certificate
+ cp -f /certs/{server,root}.* ${PGDATA}/
+ chown postgres:postgres ${PGDATA}/{root,server}.*
+ chmod -R 0600 ${PGDATA}/{root,server}.*
+
+ # Client Certificate
+ mkdir -p /home/postgres/.postgresql/
+ cp -f /certs/{postgresql,root}.* /home/postgres/.postgresql/
+ chown -R postgres:postgres /home/postgres
+ chmod -R 0600 /home/postgres/.postgresql/*
+
+ sed -i \
+ -e "s|^#ssl = .*|ssl = on|" \
+ -e "s|^#ssl_ciphers = .*|ssl_ciphers = 'HIGH'|" \
+ -e "s|^#ssl_cert_file = .*|ssl_cert_file = 'server.crt'|" \
+ -e "s|^#ssl_key_file = .*|ssl_key_file = 'server.key'|" \
+ -e "s|^#ssl_ca_file = .*|ssl_ca_file = 'root.crt'|" \
+ -e "s|^#ssl_crl_file = .*|ssl_crl_file = 'root.crl'|" \
+ ${PGDATA}/postgresql.conf
+
+ sed -i \
+ -E "s|^host([ \\t]+all){3}.*|hostnossl all all all reject\n${host_type} all all all md5 ${options}|" \
+ ${PGDATA}/pg_hba.conf
+ fi
+
+ cat >> ${PGDATA}/pg_hba.conf <<-EOF
+
+ # repmgr
+ ${host_type} repmgr repmgr all md5 ${options}
+ ${host_type} replication repmgr all md5 ${options}
+ EOF
+
+ gosu postgres pg_ctl start -w
+
+ gosu postgres psql <<-EOF
+ CREATE USER repmgr SUPERUSER LOGIN ENCRYPTED PASSWORD '${REPMGR_PASSWORD}';
+ CREATE DATABASE repmgr OWNER repmgr;
+ EOF
+
+ while ! gosu postgres pg_isready --host ${MASTER_SERVICE} --quiet
+ do
+ sleep 1
+ done
+
+ gosu postgres repmgr master register
+
+ gosu postgres psql -U repmgr -d repmgr <<-EOF
+ ALTER TABLE repmgr_default.repl_monitor SET UNLOGGED;
+ EOF
+ else
+ while ! gosu postgres pg_isready --host ${MASTER_SERVICE} --quiet
+ do
+ sleep 1
+ done
+
+ mkdir -p "$PGDATA"
+ chown -R postgres "$PGDATA"
+ chmod 700 "$PGDATA"
+
+ if [ -f "/certs/root.crt" ]; then
+ # Client Certificate
+ mkdir -p /home/postgres/.postgresql/
+ cp -f /certs/{postgresql,root}.* /home/postgres/.postgresql/
+ chown -R postgres:postgres /home/postgres
+ chmod -R 0600 /home/postgres/.postgresql/*
+ fi
+
+ gosu postgres repmgr \
+ --dbname="host=${MASTER_SERVICE} dbname=repmgr user=repmgr password=${REPMGR_PASSWORD}" \
+ standby clone
+
+ if [ -f "/certs/server.crt" ]; then
+ # Server Certificate
+ cp -f /certs/{server,root}.* ${PGDATA}/
+ chown postgres:postgres ${PGDATA}/{root,server}.*
+ chmod -R 0600 ${PGDATA}/{root,server}.*
+ fi
+
+ gosu postgres pg_ctl -w start
+
+ while ! pg_isready --host 127.0.0.1 --quiet
+ do
+ sleep 1
+ done
+
+ gosu postgres repmgr standby register
+ fi
+
+ gosu postgres pg_ctl -w stop
+ exit 0
+ else
+ if [ -f "/certs/server.key" ]; then
+ # Server Certificate
+ cp -f /certs/{server,root}.* ${PGDATA}/
+ chown postgres:postgres ${PGDATA}/{root,server}.*
+ chmod -R 0600 ${PGDATA}/{root,server}.*
+ fi
+
+ if [ -f "/certs/root.crt" ]; then
+ # Client Certificate
+ mkdir -p /home/postgres/.postgresql/
+ cp -f /certs/{postgresql,root}.* /home/postgres/.postgresql/
+ chown -R postgres:postgres /home/postgres
+ chmod -R 0600 /home/postgres/.postgresql/*
+ fi
+ fi
+
+ exec docker-entrypoint.sh "$@" & pid=$!
+
+ while ! gosu postgres pg_isready --host ${service} --quiet
+ do
+ sleep 1
+ done
+
+ supervisorctl start repmgrd
+
+ wait ${pid}
+ exit 0
+fi
+
+if [ "$1" = 'repmgrd' ] && [ "$(id -u)" = '0' ]; then
+ exec gosu postgres "$@"
+fi
+
+if [ "$1" = 'cleanup' ]; then
+ if [ ${STATEFUL_TYPE} == "master" ]; then
+ while true
+ do
+ sleep 3600
+
+ if pg_isready --host 127.0.0.1 --quiet; then
+ gosu postgres repmgr --keep-history=1 cluster cleanup || true
+ fi
+ done
+ fi
+
+ exit 0
+fi
+
+exec "$@"
diff --git a/Charts/files/supervisord.conf b/Charts/files/supervisord.conf
new file mode 100644
index 0000000..c313c4f
--- /dev/null
+++ b/Charts/files/supervisord.conf
@@ -0,0 +1,29 @@
+[supervisord]
+nodaemon=true
+
+[program:postgres]
+autorestart=true
+command=bash entrypoint.sh postgres
+stderr_logfile=/dev/stderr
+stderr_logfile_maxbytes=0
+stdout_logfile=/dev/stdout
+stdout_logfile_maxbytes=0
+
+[program:repmgrd]
+autostart=false
+autorestart=true
+startretries=999
+command=bash entrypoint.sh repmgrd --monitoring-history
+stderr_logfile=/dev/stderr
+stderr_logfile_maxbytes=0
+stdout_logfile=/dev/stdout
+stdout_logfile_maxbytes=0
+
+[program:cleanup]
+autorestart=false
+startsecs=0
+command=bash entrypoint.sh cleanup
+stderr_logfile=/dev/stderr
+stderr_logfile_maxbytes=0
+stdout_logfile=/dev/stdout
+stdout_logfile_maxbytes=0
diff --git a/Charts/templates/_helpers.tpl b/Charts/templates/_helpers.tpl
new file mode 100644
index 0000000..4c80318
--- /dev/null
+++ b/Charts/templates/_helpers.tpl
@@ -0,0 +1,95 @@
+{{/* vim: set filetype=mustache: */}}
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "postgresql.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
+{{- end -}}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+*/}}
+{{- define "postgresql.fullname" -}}
+{{- $name := default .Chart.Name .Values.nameOverride -}}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
+{{- end -}}
+
+{{/*
+Create a default fully qualified master name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+*/}}
+{{- define "postgresql.master.fullname" -}}
+{{- $name := default .Chart.Name .Values.nameOverride -}}
+{{- printf "%s-%s-%s" .Release.Name $name .Values.postgres.name | trunc 63 | trimSuffix "-" -}}
+{{- end -}}
+
+
+{{/*
+Overridable deployment annotations
+*/}}
+{{- define "postgresql.deploymentAnnotations" }}
+checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
+checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
+{{- end -}}
+
+{{/*
+Return the appropriate apiVersion for networkpolicy.
+*/}}
+{{- define "postgresql.networkPolicy.apiVersion" -}}
+{{- if and (ge .Capabilities.KubeVersion.Minor "4") (le .Capabilities.KubeVersion.Minor "6") -}}
+"extensions/v1beta1"
+{{- else if ge .Capabilities.KubeVersion.Minor "7" -}}
+"networking.k8s.io/v1"
+{{- end -}}
+{{- end -}}
+
+{{- define "postgresql.environment" }}
+- name: PGDATA
+ value: /var/lib/postgresql/data/pgdata
+- name: MASTER_SERVICE
+ value: {{ template "postgresql.master.fullname" . }}
+- name: POD_NAME
+ valueFrom:
+ fieldRef:
+ fieldPath: metadata.name
+- name: POD_IP
+ valueFrom:
+ fieldRef:
+ fieldPath: status.podIP
+{{- $fullname := (include "postgresql.fullname" .) -}}
+{{- range tuple "POSTGRES_DB" "POSTGRES_USER" "POSTGRES_PASSWORD" "POSTGRES_INITDB_ARGS" }}
+- name: {{ . }}
+ valueFrom:
+ secretKeyRef:
+ name: {{ $fullname }}
+ key: {{ . }}
+{{- end }}
+- name: PGUSER
+ valueFrom:
+ secretKeyRef:
+ name: {{ $fullname }}
+ key: POSTGRES_USER
+{{- end -}}
+
+{{- define "postgresql.volumes" }}
+- name: config-volume
+ configMap:
+ name: {{ template "postgresql.fullname" . }}
+- name: secret-volume
+ secret:
+ secretName: {{ template "postgresql.fullname" . }}
+{{- end -}}
+
+{{- define "postgresql.volumeMounts" }}
+- name: data
+ mountPath: /var/lib/postgresql/data
+ subPath: pgdata
+- name: config-volume
+ mountPath: /etc/supervisor/conf.d/supervisord.conf
+ subPath: supervisord.conf
+ readOnly: true
+- name: config-volume
+ mountPath: /usr/local/bin/entrypoint.sh
+ subPath: entrypoint.sh
+{{- end }}
\ No newline at end of file
diff --git a/Charts/templates/app-svc.yaml b/Charts/templates/app-svc.yaml
new file mode 100644
index 0000000..e7e4a86
--- /dev/null
+++ b/Charts/templates/app-svc.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ .Values.kanban_app.fullname }}
+ labels:
+ app.kubernetes.io/name: {{ .Values.kanban_app.fullname }}
+ app.kubernetes.io/instance: {{ .Release.Name }}
+ app.kubernetes.io/managed-by: {{ .Release.Service }}
+spec:
+ type: {{ .Values.kanban_app.serviceType }}
+ selector:
+ app: {{ .Values.kanban_app.fullname }}
+ ports:
+ - port: 8080
+ targetPort: 8080
\ No newline at end of file
diff --git a/Charts/templates/configmap.yaml b/Charts/templates/configmap.yaml
new file mode 100644
index 0000000..e9438d4
--- /dev/null
+++ b/Charts/templates/configmap.yaml
@@ -0,0 +1,17 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: {{ template "postgresql.fullname" . }}
+ labels:
+ app: {{ template "postgresql.name" . }}
+ chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
+ heritage: {{ .Release.Service }}
+ release: {{ .Release.Name }}
+ app.kubernetes.io/name: {{ template "postgresql.fullname" . }}-configmap
+ app.kubernetes.io/instance: {{ .Release.Name }}
+ app.kubernetes.io/managed-by: {{ .Release.Service }}
+data:
+{{- range $key, $value := merge .Values.configs ((.Files.Glob "files/*").AsConfig | fromYaml) }}
+ {{ $key }}: |-
+{{ $value | indent 4 }}
+{{- end }}
diff --git a/Charts/templates/kanban-app.yaml b/Charts/templates/kanban-app.yaml
new file mode 100644
index 0000000..9de40c7
--- /dev/null
+++ b/Charts/templates/kanban-app.yaml
@@ -0,0 +1,30 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: {{ .Values.kanban_app.fullname }}
+ labels:
+ app.kubernetes.io/name: {{ .Values.kanban_app.fullname }}
+ app.kubernetes.io/instance: {{ .Release.Name }}
+ app.kubernetes.io/managed-by: {{ .Release.Service }}
+spec:
+ replicas: {{ .Values.kanban_app.replicas }}
+ selector:
+ matchLabels:
+ app: {{ .Values.kanban_app.fullname }}
+ template:
+ metadata:
+ labels:
+ app: {{ .Values.kanban_app.fullname }}
+ spec:
+ containers:
+ - name: {{ .Values.kanban_app.fullname }}
+ image: {{ .Values.kanban_app.image.repository }}:{{ .Values.kanban_app.image.tag }}
+ imagePullPolicy: {{ .Values.kanban_app.image.pullPolicy }}
+ ports:
+ - containerPort: 8080
+ envFrom:
+ - configMapRef:
+ name: {{ .Values.secrets.fullname }}
+ env:
+ - name: DB_SERVER
+ value: postgres
\ No newline at end of file
diff --git a/Charts/templates/kanban-ui.yaml b/Charts/templates/kanban-ui.yaml
new file mode 100644
index 0000000..ed4c9c4
--- /dev/null
+++ b/Charts/templates/kanban-ui.yaml
@@ -0,0 +1,26 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: {{ .Values.kanban_ui.fullname }}
+ labels:
+ app: {{ .Values.kanban_ui.fullname }}
+ group: frontend
+ app.kubernetes.io/name: {{ .Values.kanban_ui.fullname }}
+ app.kubernetes.io/instance: {{ .Release.Name }}
+ app.kubernetes.io/managed-by: {{ .Release.Service }}
+spec:
+ replicas: {{ .Values.kanban_ui.replicas }}
+ selector:
+ matchLabels:
+ app: {{ .Values.kanban_ui.fullname }}
+ template:
+ metadata:
+ labels:
+ app: {{ .Values.kanban_ui.fullname }}
+ spec:
+ containers:
+ - name: {{ .Values.kanban_ui.fullname }}
+ image: {{ .Values.kanban_ui.image.repository }}:{{ .Values.kanban_ui.image.tag }}
+ imagePullPolicy: {{ .Values.kanban_ui.image.pullPolicy }}
+ ports:
+ - containerPort: 80
\ No newline at end of file
diff --git a/Charts/templates/postgres-config.yaml b/Charts/templates/postgres-config.yaml
new file mode 100644
index 0000000..066fe93
--- /dev/null
+++ b/Charts/templates/postgres-config.yaml
@@ -0,0 +1,12 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: {{ .Values.secrets.fullname }}
+ labels:
+ app.kubernetes.io/name: {{ .Values.secrets.fullname }}
+ app.kubernetes.io/instance: {{ .Release.Name }}
+ app.kubernetes.io/managed-by: {{ .Release.Service }}
+data:
+ POSTGRES_DB: {{ .Values.secrets.POSTGRES_DB }}
+ POSTGRES_USER: {{ .Values.secrets.POSTGRES_USER }}
+ POSTGRES_PASSWORD: {{ .Values.secrets.POSTGRES_PASSWORD }}
\ No newline at end of file
diff --git a/Charts/templates/postgres-svc.yaml b/Charts/templates/postgres-svc.yaml
new file mode 100644
index 0000000..17341a9
--- /dev/null
+++ b/Charts/templates/postgres-svc.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ template "postgresql.master.fullname" . }}-svc
+ labels:
+ app.kubernetes.io/name: {{ template "postgresql.master.fullname" . }}
+ app.kubernetes.io/instance: {{ .Release.Name }}
+ app.kubernetes.io/managed-by: {{ .Release.Service }}
+spec:
+ type: {{ .Values.postgres.serviceType }}
+ selector:
+ app: {{ template "postgresql.master.fullname" . }}
+ ports:
+ - port: 5432
+ targetPort: 5432
\ No newline at end of file
diff --git a/Charts/templates/postgres.pvc.yaml b/Charts/templates/postgres.pvc.yaml
new file mode 100644
index 0000000..47e4ec8
--- /dev/null
+++ b/Charts/templates/postgres.pvc.yaml
@@ -0,0 +1,19 @@
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: {{ template "postgresql.name" . }}-persistent-volume-claim
+ labels:
+ app: {{ template "postgresql.name" . }}
+ chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
+ component: "{{ .Values.postgres.name }}"
+ heritage: {{ .Release.Service }}
+ release: {{ .Release.Name }}
+ app.kubernetes.io/name: {{ template "postgresql.fullname" . }}
+ app.kubernetes.io/instance: {{ .Release.Name }}
+ app.kubernetes.io/managed-by: {{ .Release.Service }}
+spec:
+ accessModes:
+ - {{ .Values.postgres.persistence.accessMode }}
+ resources:
+ requests:
+ storage: {{ .Values.postgres.persistence.size }}
\ No newline at end of file
diff --git a/Charts/templates/postgres.yaml b/Charts/templates/postgres.yaml
new file mode 100644
index 0000000..626ad71
--- /dev/null
+++ b/Charts/templates/postgres.yaml
@@ -0,0 +1,38 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: {{ template "postgresql.master.fullname" . }}
+ labels:
+ app: {{ template "postgresql.name" . }}
+ chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
+ component: "{{ .Values.postgres.name }}"
+ heritage: {{ .Release.Service }}
+ release: {{ .Release.Name }}
+ app.kubernetes.io/name: {{ template "postgresql.fullname" . }}
+ app.kubernetes.io/instance: {{ .Release.Name }}
+ app.kubernetes.io/managed-by: {{ .Release.Service }}
+spec:
+ replicas: {{ .Values.postgres.replicas }}
+ selector:
+ matchLabels:
+ app: {{ template "postgresql.name" . }}
+ template:
+ metadata:
+ labels:
+ app: {{ template "postgresql.name" . }}
+ spec:
+ volumes:
+ - name: postgres-storage
+ persistentVolumeClaim:
+ claimName: {{ template "postgresql.name" . }}-persistent-volume-claim
+ containers:
+ - name: {{ template "postgresql.name" . }}
+ image: {{ .Values.postgres.image.repository }}:{{ .Values.postgres.image.tag }}
+ ports:
+ - containerPort: 5432
+ envFrom:
+ - configMapRef:
+ name: {{ .Values.secrets.fullname }}
+ volumeMounts:
+ - name: postgres-storage
+ mountPath: /var/lib/postgresql/data
\ No newline at end of file
diff --git a/Charts/templates/secret.yaml b/Charts/templates/secret.yaml
new file mode 100644
index 0000000..a61a8cd
--- /dev/null
+++ b/Charts/templates/secret.yaml
@@ -0,0 +1,17 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: {{ template "postgresql.fullname" . }}
+ labels:
+ app: {{ template "postgresql.name" . }}
+ chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
+ heritage: {{ .Release.Service }}
+ release: {{ .Release.Name }}
+ app.kubernetes.io/name: {{ template "postgresql.fullname" . }}-secret
+ app.kubernetes.io/instance: {{ .Release.Name }}
+ app.kubernetes.io/managed-by: {{ .Release.Service }}
+type: Opaque
+data:
+{{- range $key, $value := .Values.secrets }}
+ {{ $key }}: {{ $value | b64enc | quote }}
+{{- end }}
\ No newline at end of file
diff --git a/Charts/templates/ui-svc.yaml b/Charts/templates/ui-svc.yaml
new file mode 100644
index 0000000..ed5f0a4
--- /dev/null
+++ b/Charts/templates/ui-svc.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ .Values.kanban_ui.fullname }}
+ labels:
+ app.kubernetes.io/name: {{ template "postgresql.fullname" . }}
+ app.kubernetes.io/instance: {{ .Release.Name }}
+ app.kubernetes.io/managed-by: {{ .Release.Service }}
+spec:
+ type: {{ .Values.kanban_ui.serviceType }}
+ selector:
+ app: {{ .Values.kanban_ui.fullname }}
+ ports:
+ - port: 80
+ targetPort: 80
\ No newline at end of file
diff --git a/Charts/values.yaml b/Charts/values.yaml
new file mode 100644
index 0000000..ab92596
--- /dev/null
+++ b/Charts/values.yaml
@@ -0,0 +1,45 @@
+kanban_ui:
+ image:
+ repository: lakshmi1995/kanban-ui
+ tag: v1067550938
+ pullPolicy: Always
+ replicas: 1
+ fullname: kanban-ui
+ serviceType: LoadBalancer
+
+kanban_app:
+ image:
+ repository: lakshmi1995/kanban-app
+ tag: v1067550938
+ pullPolicy: Always
+ replicas: 1
+ fullname: kanban-app
+ serviceType: ClusterIP
+
+postgres:
+ image:
+ repository: postgres
+ tag: 9.6-alpine
+ pullPolicy: Always
+ replicas: 1
+ name: master
+ resources: {}
+ persistence:
+ enabled: true
+ accessMode: ReadWriteOnce
+ size: 4Gi
+ service:
+ type: ClusterIP
+ port: 5432
+ externalIPs: []
+ terminationGracePeriodSeconds: 3600
+ serviceType: ClusterIP
+
+secrets:
+ fullname: postgres-config
+ POSTGRES_DB: postgres
+ POSTGRES_USER: postgres
+ POSTGRES_PASSWORD: password
+ POSTGRES_INITDB_ARGS: --data-checksums
+
+configs: {}
diff --git a/README.md b/README.md
index 654563f..36ddd86 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,41 @@
-### Kanaban full-stack app
+# kanban-board-k8s-demo
-Work in Progress
\ No newline at end of file
+## Overview
+
+This sample project is forked from [kanban-board](https://github.com/wkrzywiec/kanban-board)
+
+The repo represents a working example of a Web-App that uses a backend API service (`kanban-app`) with a PostgreSQL database,
+and a separate AngularJS frontend (`kanban-ui`).
+Everything is containerised with Docker and a sample docker-compose.yml file is available to test it locally.
+
+## Exercise
+
+Your task is to take this sample Web-App and port it to Kubernetes. The components of the app should, as much as possible, be separately configurable and scalable.
+The end result should be usable for deployments to different kubernetes namespaces and/or clusters.
+Optionally, the PostgreSQL database should be able to be replaced with a cloud Managed SQL instance
+(e.g. [DigitalOcean](https://docs.digitalocean.com/products/databases/), [Google Cloud Platform](https://cloud.google.com/sql), or another cloud provider), but
+by default should support a PostgreSQL instance running in the kubernetes cluster.
+
+### Things to consider in your solution
+
+* Fork this repo on GitHub and commit all your work to your fork. When completed send the link to your fork of the repo to john.kirkham@ratehub.ca, fritz@ratehub.ca, and allison.colin-thome@ratehub.ca
+* The Dockerfiles exist already for the `kanban-app` and `kanban-ui` but you will need to build and push the Docker images to a public Docker registry for
+them to be accessable from within a kubernetes cluster. ([DockerHub](https://hub.docker.com/) or [GCR](https://cloud.google.com/container-registry/)
+are possible solutions for this.)
+* To manage making this installation reconfigurable and relocatable it is strongly recommended that you use a templating solution like [Helm](https://helm.sh/),
+an overlay system like [Kustomize](https://kustomize.io/), or a combination of the two. Similar alternative are acceptable.
+* As part of the solution, include a sample deployment. This means Helm values.yaml file(s) for a sample deployment and/or environment overlay files for Kustomize (or the equivalent if another approach is taken). Places these under an `example_env` directory in the root of the repo. (We do not need to see the running instance hosted by your own kubernetes cluster but we should be able to use your example to easily deploy it to our own clusters.)
+* Try to follow best practices, especially with regards to basic security in your kubernetes deployment.
+* Tools like [MiniKube](https://minikube.sigs.k8s.io/docs/start/), [KinD](https://kind.sigs.k8s.io/docs/user/quick-start/), [microk8s](https://microk8s.io/),
+or similar will be useful in developing and testing your solution. Alternatively, free trials are offered by cloud providers like DigitalOcean,
+GCP, Azure, AWS, etc. and may be used for this.
+* If you encounter any problems with any part of the task or are blocked by something, please add a `KNOWN-ISSUES.md` file to your repo and document it there. (Note: there are known issues with the App. You don't have to fix the code; we just want to see you can work with Docker and Kubernetes.)
+* If you have questions and need clarifications to complete the exercise please send them to john.kirkham@ratehub.ca
+
+### Bonus/Optional Tasks
+
+* Demonstrate how to expose this externally (viewable outside the kubernetes cluster).
+* Demonstrate some form of secrets-management for security sensitive configurations such as the database credentials or connection string.
+* Improve front-end security by upgrading the base container used for the `kanban-ui` Docker image.
+* Can you optimize the size of the backend `kanban-app` Docker image?
+* Use GitHub Actions (workflows) to automate parts of the build and deployment process.
diff --git a/Solution.md b/Solution.md
new file mode 100644
index 0000000..5648587
--- /dev/null
+++ b/Solution.md
@@ -0,0 +1,56 @@
+# Kanban Dashboard Application
+
+## Install Application in K8S platform.
+
+```sh
+cd Charts
+helm install kanbanapp -n namespace ./
+```
+
+## Accessing the UI component:
+
+* The UI component is accessed via Load balancer.
+* If the envionment already has a Ingress solution then we can use it as well.
+
+## Access the kanban Backend:
+
+* Backend is only accessible within the cluster as it is running on cluster IP configuration.
+
+## Docker Image Build
+
+* The CI is performed using Github actions.
+* The images are built and pushed as part of GitHub Actions.
+
+
+## Kanban App Helm Variables
+
+| Name | Description | Type | Required |
+|------|-------------|:----:|:-----:|
+| image.repository | Name of the image to deploy | string | yes |
+| image.tag | Image Tag to deploy | string | yes |
+| image.pullPolicy | Image pullPolicy | string | yes |
+| replicas | Number of repica to deploy | string | yes |
+| fullname | Name of the application | string | yes |
+| serviceType | Application service type | string | yes |
+
+## Kanban UI Helm Variables
+
+| Name | Description | Type | Required |
+|------|-------------|:----:|:-----:|
+| image.repository | Name of the image to deploy | string | yes |
+| image.tag | Image Tag to deploy | string | yes |
+| image.pullPolicy | Image pullPolicy | string | yes |
+| replicas | Number of repica to deploy | string | yes |
+| fullname | Name of the application | string | yes |
+| serviceType | Application service type | string | yes |
+
+
+## Helm secrets Variables
+
+| Name | Description | Type | Required |
+|------|-------------|:----:|:-----:|
+| fullname | Name of the config | string | yes |
+| POSTGRES_DB | Postgres DB Name | string | yes |
+| POSTGRES_USER | Postgres DB Username | string | yes |
+| POSTGRES_PASSWORD | Postgres DB Password | string | yes |
+| POSTGRES_INITDB_ARGS | Postgres DB Init Arguments| list | string |
diff --git a/assets/kanban.gif b/assets/kanban.gif
new file mode 100644
index 0000000..fdfdb41
Binary files /dev/null and b/assets/kanban.gif differ
diff --git a/assets/swagger.png b/assets/swagger.png
new file mode 100644
index 0000000..70ddd4e
Binary files /dev/null and b/assets/swagger.png differ
diff --git a/docker-compose-README.md b/docker-compose-README.md
new file mode 100644
index 0000000..2052596
--- /dev/null
+++ b/docker-compose-README.md
@@ -0,0 +1,102 @@
+## Kanban Application
+
+This is a simple implementation of a Kanban Board, a tool that helps visualize and manage work. Originally it was first created in Toyota automotive, but nowadays it's widely used in software development.
+
+A Kanban Board is usually made of 3 columns - *TODO*, *InProgres*s & *Done*. In each column there are Post-it notes that represents task and their status.
+
+As already stated this project is an implementation of such board and made of 3 separate Docker containers that holds:
+
+- PostgreSQL database
+- Java backend (Spring Boot)
+- Angular frontend
+
+The entry point for a user is a website which is available under the address: **http://localhost:4200/**
+
+![Kanban](https://github.com/wkrzywiec/kanban-board/blob/master/assets/kanban.gif)
+
+More information about this project you can found in blog post: https://medium.com/@wkrzywiec/how-to-run-database-backend-and-frontend-in-a-single-click-with-docker-compose-4bcda66f6de
+
+---
+
+### Prerequisites
+
+In order to run this application you need to install two tools: **Docker** & **Docker Compose**.
+
+Instructions how to install **Docker** on [Ubuntu](https://docs.docker.com/install/linux/docker-ce/ubuntu/), [Windows](https://docs.docker.com/docker-for-windows/install/), [Mac](https://docs.docker.com/docker-for-mac/install/).
+
+**Docker Compose** is already included in installation packs for *Windows* and *Mac*, so only Ubuntu users needs to follow [this instructions](https://docs.docker.com/compose/install/).
+
+
+### How to run it?
+
+The entire application can be run with a single command on a terminal:
+
+```
+$ docker-compose up -d
+```
+
+If you want to stop it, use the following command:
+
+```
+$ docker-compose down
+```
+
+---
+
+#### kanban-postgres (Database)
+
+PostgreSQL database contains only single schema with two tables - kanban
+and task table.
+
+After running the app it can be accessible using these connectors:
+
+- Host: *localhost*
+- Database: *kanban*
+- User: *kanban*
+- Password: *kanban*
+
+
+Like other parts of application Postgres database is containerized and
+the definition of its Docker container can be found in
+*docker-compose.yml* file.
+
+```yml
+kanban-postgres:
+ image: "postgres:9.6-alpine"
+ container_name: kanban-postgres
+ volumes:
+ - kanban-data:/var/lib/postgresql/data
+ ports:
+ - 5432:5432
+ environment:
+ - POSTGRES_DB:kanban
+ - POSTGRES_USER:kanban
+ - POSTGRES_PASSWORD:kanban
+```
+
+#### kanban-app (REST API)
+
+This is a Spring Boot (Java) based application that connects with a
+database that and expose the REST endpoints that can be consumed by
+frontend. It supports multiple HTTP REST methods like GET, POST, PUT and
+DELETE for two resources - kanban & task.
+
+Full list of available REST endpoints could be found in Swagger UI,
+which could be called using link: **http://localhost:8080/api/swagger-ui.html**
+
+
+![swagger-ui](https://github.com/wkrzywiec/kanban-board/blob/master/assets/swagger.png)
+
+
+This app is also put in Docker container and its definition can be found
+in a file *kanban-app/Dockerfile*.
+
+
+
+#### kanban-ui (Frontend)
+
+This is a real endpoint for a user where they can manipulate their
+kanbans and tasks. It consumes the REST API endpoints provided by
+*kanban-app*.
+
+It can be entered using link: **http://localhost:4200/**
diff --git a/docker-compose.yml b/docker-compose.yml
index 208db9c..c9e1756 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -9,15 +9,18 @@ services:
ports:
- 5432:5432
environment:
- - POSTGRES_DB:kanban
- - POSTGRES_USER:kanban
- - POSTGRES_PASSWORD:kanban
+ - POSTGRES_DB=kanban
+ - POSTGRES_USER=kanban
+ - POSTGRES_PASSWORD=kanban
kanban-app:
build: ./kanban-app
container_name: kanban-app
environment:
- DB_SERVER=kanban-postgres
+ - POSTGRES_DB=kanban
+ - POSTGRES_USER=kanban
+ - POSTGRES_PASSWORD=kanban
ports:
- 8080:8080
links:
diff --git a/kanban-app/pom.xml b/kanban-app/pom.xml
index 6ba6003..7566608 100644
--- a/kanban-app/pom.xml
+++ b/kanban-app/pom.xml
@@ -6,16 +6,18 @@
org.springframework.boot
spring-boot-starter-parent
2.1.6.RELEASE
-
+
com.wkrzywiec.medium
- kanban
+ kanban-app
0.0.1-SNAPSHOT
- kanban
+ kanban-app
Backend app for Kanban app
1.8
+ false
+ **/model/**,**/config/**,**/KanbanApplication.java
@@ -77,7 +79,65 @@
org.springframework.boot
spring-boot-maven-plugin
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 3.0.0
+
+
+ add-integration-test-sources
+ generate-test-sources
+
+ add-test-source
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ 3.0.0-M3
+
+
+ failsafe-integration-tests
+ integration-test
+
+ integration-test
+ verify
+
+
+ ${skip.integration.tests}
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.4
+
+
+ default-prepare-agent
+
+ prepare-agent
+
+
+
+ default-report
+ prepare-package
+
+ report
+
+
+
+
-
diff --git a/kanban-app/src/integration-test/java/com.wkrzywiec.medium.kanban/config/H2DatabaseConfig4Test.java b/kanban-app/src/integration-test/java/com.wkrzywiec.medium.kanban/config/H2DatabaseConfig4Test.java
new file mode 100644
index 0000000..9cbf97c
--- /dev/null
+++ b/kanban-app/src/integration-test/java/com.wkrzywiec.medium.kanban/config/H2DatabaseConfig4Test.java
@@ -0,0 +1,50 @@
+package com.wkrzywiec.medium.kanban.config;
+
+import com.wkrzywiec.medium.kanban.model.Kanban;
+import com.wkrzywiec.medium.kanban.repository.KanbanRepository;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+import org.springframework.orm.jpa.JpaTransactionManager;
+import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
+import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
+import org.springframework.transaction.PlatformTransactionManager;
+
+import javax.persistence.EntityManagerFactory;
+import javax.sql.DataSource;
+
+@TestConfiguration
+@EnableJpaRepositories( basePackageClasses = KanbanRepository.class )
+public class H2DatabaseConfig4Test {
+
+ @Bean
+ public DataSource dataSource() {
+ DriverManagerDataSource dataSource = new DriverManagerDataSource();
+ dataSource.setDriverClassName("org.h2.Driver");
+ dataSource.setUrl("jdbc:h2:mem:db;DB_CLOSE_DELAY=-1");
+
+ return dataSource;
+ }
+
+ @Bean
+ public EntityManagerFactory entityManagerFactory() {
+ HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
+ vendorAdapter.setGenerateDdl( true );
+
+ LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
+ factory.setJpaVendorAdapter( vendorAdapter );
+ factory.setPackagesToScan( Kanban.class.getPackage().getName() );
+ factory.setDataSource( dataSource() );
+ factory.afterPropertiesSet();
+
+ return factory.getObject();
+ }
+
+ @Bean
+ public PlatformTransactionManager transactionManager() {
+ JpaTransactionManager txManager = new JpaTransactionManager();
+ txManager.setEntityManagerFactory( entityManagerFactory() );
+ return txManager;
+ }
+}
diff --git a/kanban-app/src/test/java/com/wkrzywiec/medium/kanban/integration/CommonTest.java b/kanban-app/src/integration-test/java/com.wkrzywiec.medium.kanban/controller/CommonITCase.java
similarity index 81%
rename from kanban-app/src/test/java/com/wkrzywiec/medium/kanban/integration/CommonTest.java
rename to kanban-app/src/integration-test/java/com.wkrzywiec.medium.kanban/controller/CommonITCase.java
index 8635bd2..46e960a 100644
--- a/kanban-app/src/test/java/com/wkrzywiec/medium/kanban/integration/CommonTest.java
+++ b/kanban-app/src/integration-test/java/com.wkrzywiec.medium.kanban/controller/CommonITCase.java
@@ -1,6 +1,10 @@
-package com.wkrzywiec.medium.kanban.integration;
+package com.wkrzywiec.medium.kanban.controller;
-import com.wkrzywiec.medium.kanban.model.*;
+import com.wkrzywiec.medium.kanban.model.Kanban;
+import com.wkrzywiec.medium.kanban.model.KanbanDTO;
+import com.wkrzywiec.medium.kanban.model.Task;
+import com.wkrzywiec.medium.kanban.model.TaskDTO;
+import com.wkrzywiec.medium.kanban.model.TaskStatus;
import com.wkrzywiec.medium.kanban.repository.KanbanRepository;
import com.wkrzywiec.medium.kanban.repository.TaskRepository;
import org.springframework.beans.factory.annotation.Autowired;
@@ -9,9 +13,11 @@
import java.util.ArrayList;
import java.util.Optional;
-@TestPropertySource(
- locations = "classpath:application-integrationtest.properties")
-public class CommonTest {
+@TestPropertySource( properties = {
+ "spring.datasource.url=jdbc:h2:mem:test",
+ "spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.H2Dialect"
+})
+public class CommonITCase {
@Autowired
private KanbanRepository kanbanRepository;
diff --git a/kanban-app/src/test/java/com/wkrzywiec/medium/kanban/integration/KanbanControllerTest.java b/kanban-app/src/integration-test/java/com.wkrzywiec.medium.kanban/controller/KanbanControllerITCase.java
similarity index 93%
rename from kanban-app/src/test/java/com/wkrzywiec/medium/kanban/integration/KanbanControllerTest.java
rename to kanban-app/src/integration-test/java/com.wkrzywiec.medium.kanban/controller/KanbanControllerITCase.java
index 053f136..141fab6 100644
--- a/kanban-app/src/test/java/com/wkrzywiec/medium/kanban/integration/KanbanControllerTest.java
+++ b/kanban-app/src/integration-test/java/com.wkrzywiec.medium.kanban/controller/KanbanControllerITCase.java
@@ -1,4 +1,4 @@
-package com.wkrzywiec.medium.kanban.integration;
+package com.wkrzywiec.medium.kanban.controller;
import com.wkrzywiec.medium.kanban.model.Kanban;
import com.wkrzywiec.medium.kanban.model.Task;
@@ -10,16 +10,24 @@
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.core.ParameterizedTypeReference;
-import org.springframework.http.*;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
-import static org.junit.Assert.*;
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
-public class KanbanControllerTest extends CommonTest {
+public class KanbanControllerITCase extends CommonITCase {
private String baseURL;
@@ -215,5 +223,4 @@ public void whenDeleteSingleKanbanById_thenItIsDeletedFromDb(){
assertEquals(String.format("Kanban with id: %d was deleted", kanban.getId()), response.getBody());
assertFalse(findKanbanInDbById(kanban.getId()).isPresent());
}
-
}
diff --git a/kanban-app/src/test/java/com/wkrzywiec/medium/kanban/integration/TaskControllerTest.java b/kanban-app/src/integration-test/java/com.wkrzywiec.medium.kanban/controller/TaskControllerITCase.java
similarity index 91%
rename from kanban-app/src/test/java/com/wkrzywiec/medium/kanban/integration/TaskControllerTest.java
rename to kanban-app/src/integration-test/java/com.wkrzywiec.medium.kanban/controller/TaskControllerITCase.java
index 05f71be..dc175c8 100644
--- a/kanban-app/src/test/java/com/wkrzywiec/medium/kanban/integration/TaskControllerTest.java
+++ b/kanban-app/src/integration-test/java/com.wkrzywiec.medium.kanban/controller/TaskControllerITCase.java
@@ -1,4 +1,4 @@
-package com.wkrzywiec.medium.kanban.integration;
+package com.wkrzywiec.medium.kanban.controller;
import com.wkrzywiec.medium.kanban.model.Task;
import com.wkrzywiec.medium.kanban.repository.KanbanRepository;
@@ -10,16 +10,25 @@
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.core.ParameterizedTypeReference;
-import org.springframework.http.*;
+
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
-import static org.junit.Assert.*;
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
-public class TaskControllerTest extends CommonTest {
+public class TaskControllerITCase extends CommonITCase {
private String baseURL;
diff --git a/kanban-app/src/integration-test/java/com.wkrzywiec.medium.kanban/service/KanbanServiceITCase.java b/kanban-app/src/integration-test/java/com.wkrzywiec.medium.kanban/service/KanbanServiceITCase.java
new file mode 100644
index 0000000..060ba67
--- /dev/null
+++ b/kanban-app/src/integration-test/java/com.wkrzywiec.medium.kanban/service/KanbanServiceITCase.java
@@ -0,0 +1,50 @@
+package com.wkrzywiec.medium.kanban.service;
+
+import com.wkrzywiec.medium.kanban.config.H2DatabaseConfig4Test;
+import com.wkrzywiec.medium.kanban.model.Kanban;
+import com.wkrzywiec.medium.kanban.model.KanbanDTO;
+import com.wkrzywiec.medium.kanban.repository.KanbanRepository;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(classes = { H2DatabaseConfig4Test.class })
+public class KanbanServiceITCase {
+
+ @Autowired
+ private KanbanRepository kanbanRepository;
+ private KanbanService kanbanService;
+
+
+ @Before
+ public void init() {
+ kanbanService = new KanbanServiceImpl(kanbanRepository);
+ }
+
+
+ @Test
+ public void whenNewKanbanCreated_thenKanbanIsSavedInDb() {
+ //given
+ KanbanDTO kanbanDTO = KanbanDTO.builder()
+ .title("Test Kanban")
+ .build();
+
+ //when
+ kanbanService.saveNewKanban(kanbanDTO);
+
+ //then
+ List kanbans = (List) kanbanRepository.findAll();
+
+ assertNotNull(kanbans.get(0));
+ assertEquals("Test Kanban", kanbans.get(0).getTitle());
+ }
+}
diff --git a/kanban-app/src/main/java/com/wkrzywiec/medium/kanban/model/Kanban.java b/kanban-app/src/main/java/com/wkrzywiec/medium/kanban/model/Kanban.java
index c7c89c7..c60308b 100644
--- a/kanban-app/src/main/java/com/wkrzywiec/medium/kanban/model/Kanban.java
+++ b/kanban-app/src/main/java/com/wkrzywiec/medium/kanban/model/Kanban.java
@@ -23,11 +23,9 @@ public class Kanban {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
- @Column(name = "id")
@ApiModelProperty(position = 1)
private Long id;
- @Column(name = "title")
@ApiModelProperty(position = 2)
private String title;
diff --git a/kanban-app/src/main/java/com/wkrzywiec/medium/kanban/model/Task.java b/kanban-app/src/main/java/com/wkrzywiec/medium/kanban/model/Task.java
index 979642a..893afac 100644
--- a/kanban-app/src/main/java/com/wkrzywiec/medium/kanban/model/Task.java
+++ b/kanban-app/src/main/java/com/wkrzywiec/medium/kanban/model/Task.java
@@ -19,19 +19,15 @@ public class Task {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
- @Column(name = "id")
@ApiModelProperty(position = 1)
private Long id;
- @Column(name = "title")
@ApiModelProperty(position = 2)
private String title;
- @Column(name = "description")
@ApiModelProperty(position = 3)
private String description;
- @Column(name = "color")
@ApiModelProperty(position = 4)
private String color;
diff --git a/kanban-app/src/main/resources/application.properties b/kanban-app/src/main/resources/application.properties
index ef3f21c..6d68284 100644
--- a/kanban-app/src/main/resources/application.properties
+++ b/kanban-app/src/main/resources/application.properties
@@ -1,5 +1,8 @@
-spring.datasource.url=jdbc:postgresql://${DB_SERVER}/kanban
-spring.datasource.username=kanban
-spring.datasource.password=kanban
+server.servlet.context-path=/api
+
+spring.datasource.url=jdbc:postgresql://${DB_SERVER}/${POSTGRES_DB}
+spring.datasource.username=${POSTGRES_USER}
+spring.datasource.password=${POSTGRES_PASSWORD}
spring.liquibase.change-log=classpath:/db/changelog/db.changelog-master.xml
+spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
diff --git a/kanban-app/src/test/java/com/wkrzywiec/medium/kanban/KanbanApplicationTests.java b/kanban-app/src/test/java/com/wkrzywiec/medium/kanban/KanbanApplicationTests.java
deleted file mode 100644
index b3f8987..0000000
--- a/kanban-app/src/test/java/com/wkrzywiec/medium/kanban/KanbanApplicationTests.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.wkrzywiec.medium.kanban;
-
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.junit4.SpringRunner;
-
-@RunWith(SpringRunner.class)
-@SpringBootTest
-public class KanbanApplicationTests {
-
- @Test
- @Ignore
- public void contextLoads() {
- }
-
-}
diff --git a/kanban-app/src/test/java/com/wkrzywiec/medium/kanban/service/KanbanServiceTest.java b/kanban-app/src/test/java/com/wkrzywiec/medium/kanban/service/KanbanServiceTest.java
new file mode 100644
index 0000000..365a49c
--- /dev/null
+++ b/kanban-app/src/test/java/com/wkrzywiec/medium/kanban/service/KanbanServiceTest.java
@@ -0,0 +1,59 @@
+package com.wkrzywiec.medium.kanban.service;
+
+import com.wkrzywiec.medium.kanban.model.Kanban;
+import com.wkrzywiec.medium.kanban.repository.KanbanRepository;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.IntStream;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class KanbanServiceTest {
+
+ KanbanService kanbanService;
+ @Mock
+ KanbanRepository kanbanRepository;
+
+ @Before
+ public void init() {
+ kanbanService = new KanbanServiceImpl(kanbanRepository);
+ }
+
+ @Test
+ public void when2KanbansInDatabase_thenGetListWithAllOfThem() {
+ //given
+ mockKanbanInDatabase(2);
+
+ //when
+ List kanbans = kanbanService.getAllKanbanBoards();
+
+ //then
+ assertEquals(2, kanbans.size());
+ }
+
+ private void mockKanbanInDatabase(int kanbanCount) {
+ when(kanbanRepository.findAll())
+ .thenReturn(createKanbanList(kanbanCount));
+ }
+
+ private List createKanbanList(int kanbanCount) {
+ List kanbans = new ArrayList<>();
+ IntStream.range(0, kanbanCount)
+ .forEach(number ->{
+ Kanban kanban = new Kanban();
+ kanban.setId(Long.valueOf(number));
+ kanban.setTitle("Kanban " + number);
+ kanban.setTasks(new ArrayList<>());
+ kanbans.add(kanban);
+ });
+ return kanbans;
+ }
+}
diff --git a/kanban-app/src/test/resources/application-integrationtest.properties b/kanban-app/src/test/resources/application-integrationtest.properties
deleted file mode 100644
index b7b5350..0000000
--- a/kanban-app/src/test/resources/application-integrationtest.properties
+++ /dev/null
@@ -1,3 +0,0 @@
-spring.datasource.url = jdbc:h2:mem:test
-spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.H2Dialect
-
diff --git a/kanban-postgres/Dockerfile b/kanban-postgres/Dockerfile
deleted file mode 100644
index adb898d..0000000
--- a/kanban-postgres/Dockerfile
+++ /dev/null
@@ -1,3 +0,0 @@
-FROM postgres:9.6-alpine
-ENV POSTGRES_PASSWORD postgres
-COPY init.sql /docker-entrypoint-initdb.d/
\ No newline at end of file
diff --git a/kanban-postgres/README.md b/kanban-postgres/README.md
deleted file mode 100644
index 928c033..0000000
--- a/kanban-postgres/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-### Kanban database
-
-PostgreSQL - Work in Progress
\ No newline at end of file
diff --git a/kanban-postgres/init.sql b/kanban-postgres/init.sql
deleted file mode 100644
index 29f58c7..0000000
--- a/kanban-postgres/init.sql
+++ /dev/null
@@ -1,5 +0,0 @@
-CREATE ROLE kanban WITH LOGIN PASSWORD 'kanban';
-ALTER USER kanban CREATEDB;
-
-\c postgres kanban
-CREATE DATABASE kanban;
diff --git a/kanban-ui/Dockerfile b/kanban-ui/Dockerfile
index 8aaa84b..33a1211 100644
--- a/kanban-ui/Dockerfile
+++ b/kanban-ui/Dockerfile
@@ -8,5 +8,6 @@ RUN npm run build
### STAGE 2: Run ###
FROM nginx:1.17.1-alpine
+COPY default.conf /etc/nginx/conf.d/default.conf
COPY --from=build /usr/src/app/dist/kanban-ui /usr/share/nginx/html
EXPOSE 80
\ No newline at end of file
diff --git a/kanban-ui/angular.json b/kanban-ui/angular.json
index 2b868de..2a21e5e 100644
--- a/kanban-ui/angular.json
+++ b/kanban-ui/angular.json
@@ -26,8 +26,7 @@
"./node_modules/@angular/material/prebuilt-themes/deeppurple-amber.css",
"src/styles.css"
],
- "scripts": [],
- "es5BrowserSupport": true
+ "scripts": []
},
"configurations": {
"production": {
diff --git a/kanban-ui/custom-webpack.config.js b/kanban-ui/custom-webpack.config.js
new file mode 100644
index 0000000..d6389c5
--- /dev/null
+++ b/kanban-ui/custom-webpack.config.js
@@ -0,0 +1,11 @@
+const webpack = require('webpack');
+
+module.exports = {
+ plugins: [
+ new webpack.DefinePlugin({
+ $ENV: {
+ KANBAN_APP_URL: JSON.stringify(process.env.KANBAN_APP_URL)
+ }
+ })
+ ]
+};
\ No newline at end of file
diff --git a/kanban-ui/default.conf b/kanban-ui/default.conf
new file mode 100644
index 0000000..533885e
--- /dev/null
+++ b/kanban-ui/default.conf
@@ -0,0 +1,20 @@
+server {
+ listen 80;
+ server_name kanban-ui;
+ root /usr/share/nginx/html;
+ index index.html index.html;
+
+ location /api/kanbans {
+ proxy_pass http://kanban-app:8080/api/kanbans;
+ }
+
+ location /api/tasks {
+ proxy_pass http://kanban-app:8080/api/tasks;
+ }
+
+ location / {
+ try_files $uri $uri/ /index.html;
+ }
+}
+
+
diff --git a/kanban-ui/package.json b/kanban-ui/package.json
index c6380cf..249d01f 100644
--- a/kanban-ui/package.json
+++ b/kanban-ui/package.json
@@ -4,7 +4,7 @@
"scripts": {
"ng": "ng",
"start": "ng serve",
- "build": "ng build",
+ "build": "ng build --prod",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
@@ -27,13 +27,14 @@
"zone.js": "~0.8.26"
},
"devDependencies": {
- "@angular-devkit/build-angular": "~0.13.0",
+ "@angular-builders/custom-webpack": "^7.2.0",
+ "@angular-devkit/build-angular": "^0.12.3",
"@angular/cli": "~7.3.9",
"@angular/compiler-cli": "~7.2.0",
"@angular/language-service": "~7.2.0",
- "@types/node": "~8.9.4",
"@types/jasmine": "~2.8.8",
"@types/jasminewd2": "~2.0.3",
+ "@types/node": "~8.9.4",
"codelyzer": "~4.5.0",
"jasmine-core": "~2.99.1",
"jasmine-spec-reporter": "~4.2.1",
diff --git a/kanban-ui/src/app/app.module.ts b/kanban-ui/src/app/app.module.ts
index 2f982f0..8192a6a 100644
--- a/kanban-ui/src/app/app.module.ts
+++ b/kanban-ui/src/app/app.module.ts
@@ -14,13 +14,15 @@ import { HomeComponent } from './home/home.component';
import { KanbanComponent } from './kanban/kanban.component';
import { TaskDialogComponent } from './task-dialog/task-dialog.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { KanbanDialogComponent } from './kanban-dialog/kanban-dialog.component';
@NgModule({
declarations: [
AppComponent,
HomeComponent,
KanbanComponent,
- TaskDialogComponent
+ TaskDialogComponent,
+ KanbanDialogComponent
],
imports: [
BrowserModule,
@@ -38,6 +40,6 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
],
providers: [],
bootstrap: [AppComponent],
- entryComponents: [TaskDialogComponent]
+ entryComponents: [TaskDialogComponent, KanbanDialogComponent]
})
export class AppModule { }
diff --git a/kanban-ui/src/app/home/home.component.css b/kanban-ui/src/app/home/home.component.css
index 2496546..f2c1bd3 100644
--- a/kanban-ui/src/app/home/home.component.css
+++ b/kanban-ui/src/app/home/home.component.css
@@ -1,4 +1,5 @@
.main-card {
+ margin-top: 20px;
background-color: #949AAC;
border-radius: 15px;
padding-bottom: 8px;
diff --git a/kanban-ui/src/app/home/home.component.html b/kanban-ui/src/app/home/home.component.html
index c7f7ae2..baa31c3 100644
--- a/kanban-ui/src/app/home/home.component.html
+++ b/kanban-ui/src/app/home/home.component.html
@@ -2,6 +2,9 @@
Kanban Boards
+
+
+
diff --git a/kanban-ui/src/app/home/home.component.ts b/kanban-ui/src/app/home/home.component.ts
index d8e4f56..fd5cfd1 100644
--- a/kanban-ui/src/app/home/home.component.ts
+++ b/kanban-ui/src/app/home/home.component.ts
@@ -1,6 +1,8 @@
import { Component, OnInit } from '@angular/core';
import { Kanban } from '../model/kanban/kanban';
import { KanbanService } from '../service/kanban-service.service';
+import { MatDialog, MatDialogConfig } from '@angular/material';
+import { KanbanDialogComponent } from '../kanban-dialog/kanban-dialog.component';
@Component({
selector: 'app-home',
@@ -12,13 +14,23 @@ export class HomeComponent implements OnInit {
kanbanList: Kanban[];
constructor(
- private kanbanService: KanbanService
+ private kanbanService: KanbanService,
+ private dialog: MatDialog
) { }
ngOnInit() {
this.retrieveAllKanbanBoards();
}
+ openDialogForNewKanban(): void {
+ const dialogConfig = new MatDialogConfig();
+ dialogConfig.autoFocus = true;
+ dialogConfig.data = {
+ kanban: new Kanban()
+ };
+ this.dialog.open(KanbanDialogComponent, dialogConfig)
+ }
+
private retrieveAllKanbanBoards(): void {
this.kanbanService.retrieveAllKanbanBoards().subscribe(
@@ -27,4 +39,5 @@ export class HomeComponent implements OnInit {
}
)
}
+
}
diff --git a/kanban-ui/src/app/kanban-dialog/kanban-dialog.component.css b/kanban-ui/src/app/kanban-dialog/kanban-dialog.component.css
new file mode 100644
index 0000000..98d92b4
--- /dev/null
+++ b/kanban-ui/src/app/kanban-dialog/kanban-dialog.component.css
@@ -0,0 +1,8 @@
+.dialog-content {
+ width: 450px;
+ min-height: 150px;
+}
+
+.kanban-title {
+ width: 400px;
+}
\ No newline at end of file
diff --git a/kanban-ui/src/app/kanban-dialog/kanban-dialog.component.html b/kanban-ui/src/app/kanban-dialog/kanban-dialog.component.html
new file mode 100644
index 0000000..7704e33
--- /dev/null
+++ b/kanban-ui/src/app/kanban-dialog/kanban-dialog.component.html
@@ -0,0 +1,17 @@
+
+
Create New Kanban
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/kanban-ui/src/app/kanban-dialog/kanban-dialog.component.spec.ts b/kanban-ui/src/app/kanban-dialog/kanban-dialog.component.spec.ts
new file mode 100644
index 0000000..1de50ef
--- /dev/null
+++ b/kanban-ui/src/app/kanban-dialog/kanban-dialog.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { KanbanDialogComponent } from './kanban-dialog.component';
+
+describe('KanbanDialogComponent', () => {
+ let component: KanbanDialogComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ KanbanDialogComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(KanbanDialogComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/kanban-ui/src/app/kanban-dialog/kanban-dialog.component.ts b/kanban-ui/src/app/kanban-dialog/kanban-dialog.component.ts
new file mode 100644
index 0000000..aa9609a
--- /dev/null
+++ b/kanban-ui/src/app/kanban-dialog/kanban-dialog.component.ts
@@ -0,0 +1,49 @@
+import { Component, OnInit, Inject } from '@angular/core';
+import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
+import { FormBuilder, Validators, FormGroup } from '@angular/forms';
+import { KanbanService } from '../service/kanban-service.service';
+import { Kanban } from '../model/kanban/kanban';
+
+@Component({
+ selector: 'app-kanban-dialog',
+ templateUrl: './kanban-dialog.component.html',
+ styleUrls: ['./kanban-dialog.component.css']
+})
+export class KanbanDialogComponent implements OnInit {
+
+ title : string;
+ form: FormGroup;
+
+ constructor(
+ private fb: FormBuilder,
+ private dialogRef: MatDialogRef,
+ @Inject(MAT_DIALOG_DATA) data,
+ private kanbanService: KanbanService) {
+
+ this.form = fb.group({
+ title: [this.title, Validators.required]
+ });
+ }
+
+ ngOnInit() {
+ }
+
+ close() {
+ this.dialogRef.close();
+ }
+
+ save() {
+ this.title = this.form.get('title').value;
+ if (this.title) {
+ this.kanbanService.saveNewKanban(this.title).subscribe(
+
+ response => {
+ console.log(response)
+ }
+ )
+ }
+ this.dialogRef.close();
+ window.location.reload();
+ }
+
+}
diff --git a/kanban-ui/src/app/service/kanban-service.service.ts b/kanban-ui/src/app/service/kanban-service.service.ts
index 877f6d5..60cb8d8 100644
--- a/kanban-ui/src/app/service/kanban-service.service.ts
+++ b/kanban-ui/src/app/service/kanban-service.service.ts
@@ -3,13 +3,14 @@ import { HttpClient, HttpHeaders} from '@angular/common/http';
import { Observable } from 'rxjs';
import { Kanban } from '../model/kanban/kanban';
import { Task } from '../model/task/task';
+import { environment } from 'src/environments/environment';
@Injectable({
providedIn: 'root'
})
export class KanbanService {
- private kanbanAppUrl = "http://localhost:8080"
+ private kanbanAppUrl = environment.kanbanAppUrl
constructor(private http: HttpClient) { }
@@ -21,6 +22,17 @@ export class KanbanService {
return this.http.get(this.kanbanAppUrl + '/kanbans/' + id);
}
+ saveNewKanban(title: string): Observable {
+ let headers = new HttpHeaders({'Content-Type': 'application/json' });
+ let options = { headers: headers };
+ let jsonObject = this.prepareTiTleJsonObject(title);
+ return this.http.post(
+ this.kanbanAppUrl + '/kanbans/',
+ jsonObject,
+ options
+ );
+ }
+
saveNewTaskInKanban(kanbanId: String, task: Task): Observable {
let headers = new HttpHeaders({'Content-Type': 'application/json' });
let options = { headers: headers };
@@ -29,4 +41,12 @@ export class KanbanService {
task,
options);
}
+
+ private prepareTiTleJsonObject(title: string) {
+ const object = {
+ title: title
+ }
+ return JSON.stringify(object);
+ }
+
}
diff --git a/kanban-ui/src/app/service/task.service.ts b/kanban-ui/src/app/service/task.service.ts
index 647acfd..140b1e2 100644
--- a/kanban-ui/src/app/service/task.service.ts
+++ b/kanban-ui/src/app/service/task.service.ts
@@ -2,13 +2,16 @@ import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { Task } from '../model/task/task';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
+import { environment } from 'src/environments/environment';
+
+
@Injectable({
providedIn: 'root'
})
export class TaskService {
- private kanbanAppUrl = "http://localhost:8080"
+ private kanbanAppUrl = environment.kanbanAppUrl
constructor(private http: HttpClient) { }
diff --git a/kanban-ui/src/app/task-dialog/task-dialog.component.ts b/kanban-ui/src/app/task-dialog/task-dialog.component.ts
index 7e97c0e..b1456ea 100644
--- a/kanban-ui/src/app/task-dialog/task-dialog.component.ts
+++ b/kanban-ui/src/app/task-dialog/task-dialog.component.ts
@@ -48,6 +48,7 @@ export class TaskDialogComponent implements OnInit {
this.taskService.updateTask(this.task).subscribe();
}
this.dialogRef.close();
+ window.location.reload();
}
close() {
diff --git a/kanban-ui/src/environments/environment.prod.ts b/kanban-ui/src/environments/environment.prod.ts
index 3612073..4728934 100644
--- a/kanban-ui/src/environments/environment.prod.ts
+++ b/kanban-ui/src/environments/environment.prod.ts
@@ -1,3 +1,4 @@
export const environment = {
- production: true
+ production: true,
+ kanbanAppUrl: '/api'
};
diff --git a/kanban-ui/src/environments/environment.ts b/kanban-ui/src/environments/environment.ts
index 7b4f817..71f4be6 100644
--- a/kanban-ui/src/environments/environment.ts
+++ b/kanban-ui/src/environments/environment.ts
@@ -3,7 +3,8 @@
// The list of file replacements can be found in `angular.json`.
export const environment = {
- production: false
+ production: false,
+ kanbanAppUrl: 'http://localhost:8080'
};
/*
diff --git a/kanban-ui/src/typing.d.ts b/kanban-ui/src/typing.d.ts
new file mode 100644
index 0000000..e69de29