├── .gitignore ├── README.md ├── k8s ├── cert-manager-vault-issuer-policy-pki.hcl ├── fruit-catalog-policy-dynamic.hcl ├── fruit-catalog-policy-static.hcl ├── fruits-catalog-dynamic-secret-deployment.yml ├── fruits-catalog-dynamic-secret-with-agent-deployment.yml ├── fruits-catalog-route-certificate.yml ├── fruits-catalog-static-secret-deployment.yml ├── fruits-catalog-static-secret-with-agent-deployment.yml ├── mongodb-deployment.yml ├── mongodb-istio-destinationrule.yml ├── mongodb-istio-virtualservice.yml ├── vault-agent-deployment.yml ├── vault-deployment.yml └── vault-issuer.yml ├── pom.xml └── src ├── main ├── fabric8 │ ├── deployment.yml │ └── service.yml ├── java │ └── com │ │ └── github │ │ └── lbroudoux │ │ └── fruits │ │ └── catalog │ │ ├── FruitsCatalogApplication.java │ │ ├── TimedConfiguration.java │ │ ├── TracerConfiguration.java │ │ ├── domain │ │ └── Fruit.java │ │ ├── repository │ │ └── FruitRepository.java │ │ └── service │ │ └── FruitController.java ├── resources │ └── application-local.yml └── webapp │ ├── angular.json │ ├── package-lock.json │ ├── package.json │ ├── proxy.conf.json │ ├── src │ ├── app │ │ ├── app-routing.module.ts │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── models │ │ │ └── fruit.model.ts │ │ ├── pages │ │ │ └── home │ │ │ │ ├── home.page.css │ │ │ │ ├── home.page.html │ │ │ │ ├── home.page.spec.ts │ │ │ │ └── home.page.ts │ │ └── services │ │ │ └── fruits.service.ts │ ├── assets │ │ ├── apple.png │ │ ├── banana.png │ │ ├── cherry.png │ │ ├── grape.png │ │ ├── lemon.png │ │ ├── orange.png │ │ ├── pear.png │ │ ├── pineapple.png │ │ └── strawberry.png │ ├── browserslist │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── karma.conf.js │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ ├── test.ts │ ├── tsconfig.app.json │ ├── tsconfig.spec.json │ └── tslint.json │ ├── tsconfig.json │ └── tslint.json └── test └── resources ├── application.yml ├── arquillian.xml ├── logback-test.xml └── test-configmap.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | /target 26 | *dist 27 | *node_modules 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # secured-fruits-catalog-k8s 2 | 3 | How to add security layer to your existing application with minimal touch to code ! 4 | 5 | This is the companion project of the blog series I started here: [Adding security layers to your App on OpenShift](https://medium.com/@lbroudoux/adding-security-layers-to-your-app-on-openshift-part-1-deployment-and-tls-ingress-9ef752835599). 6 | 7 | ## OpenShift versions 8 | 9 | This has been deployed successfully on OpenShift 3.11 and on OpenShift 4.1+ 10 | 11 | For OpenShift 3.11, make sure to change the Fabric8 Maven Plugin version in the `pom.xml` file: 12 | 13 | ``` 14 | 3.5.41 15 | ``` 16 | 17 | For OpenShift 4.2+ you have to use the latest version (the default now) 18 | 19 | ``` 20 | 4.3.1 21 | ``` 22 | 23 | ## Quick local deployment 24 | 25 | > You should have a MongoDB instance running locally on port 27017. 26 | 27 | ``` 28 | $ mvn spring-boot:run 29 | [...] 30 | ``` 31 | 32 | ## Quick deployment on OpenShift 33 | 34 | ``` 35 | $ oc new-project fruits-catalog --display-name="Fruits Catalog" 36 | [...] 37 | $ oc new-app mongodb-persistent --name=mongodb -p DATABASE_SERVICE_NAME=mongodb -p MONGODB_DATABASE=sampledb -l app=fruits-catalog -n fruits-catalog 38 | [...] 39 | $ mvn fabric8:deploy -Popenshift 40 | [...] 41 | ``` 42 | 43 | ## Quick deployment on Kubernetes 44 | 45 | Example running on Minikube: 46 | 47 | ``` 48 | $ eval $(minikube docker-env) 49 | [...] 50 | $ kubectl create namespace fruits-catalog 51 | [...] 52 | $ kubectl create -f k8s/mongodb-deployment.yml -n fruits-catalog 53 | [...] 54 | $ mvn fabric8:deploy -Popenshift -Dfabric8.mode=kubernetes -Dfabric8.namespace=fruits-catalog 55 | [...] 56 | ``` 57 | -------------------------------------------------------------------------------- /k8s/cert-manager-vault-issuer-policy-pki.hcl: -------------------------------------------------------------------------------- 1 | path "pki_int/sign/example-opentlc-com" { 2 | capabilities = ["read", "update", "list", "delete"] 3 | } 4 | path "pki_int/issue/example-opentlc-com" { 5 | capabilities = ["read", "update", "list", "delete"] 6 | } -------------------------------------------------------------------------------- /k8s/fruit-catalog-policy-dynamic.hcl: -------------------------------------------------------------------------------- 1 | path "database/creds/fruit-catalog-role" { 2 | capabilities = ["read"] 3 | } 4 | path "sys/leases/renew" { 5 | capabilities = ["create"] 6 | } 7 | path "sys/leases/revoke" { 8 | capabilities = ["update"] 9 | } -------------------------------------------------------------------------------- /k8s/fruit-catalog-policy-static.hcl: -------------------------------------------------------------------------------- 1 | path "secret/fruit-catalog-mongodb" { 2 | capabilities = ["read"] 3 | } -------------------------------------------------------------------------------- /k8s/fruits-catalog-dynamic-secret-deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps.openshift.io/v1 2 | kind: DeploymentConfig 3 | metadata: 4 | labels: 5 | app: fruits-catalog 6 | group: com.github.lbroudoux.msa 7 | provider: fabric8 8 | version: 1.0.0-SNAPSHOT 9 | name: fruits-catalog 10 | spec: 11 | replicas: 1 12 | selector: 13 | app: fruits-catalog 14 | group: com.github.lbroudoux.msa 15 | provider: fabric8 16 | strategy: 17 | activeDeadlineSeconds: 21600 18 | resources: {} 19 | rollingParams: 20 | intervalSeconds: 1 21 | maxSurge: 25% 22 | maxUnavailable: 25% 23 | timeoutSeconds: 3600 24 | updatePeriodSeconds: 1 25 | type: Rolling 26 | template: 27 | metadata: 28 | labels: 29 | app: fruits-catalog 30 | group: com.github.lbroudoux.msa 31 | provider: fabric8 32 | version: 1.0.0-SNAPSHOT 33 | spec: 34 | initContainers: 35 | - name: vault-init 36 | image: quay.io/lbroudoux/ubi8:latest 37 | command: 38 | - "sh" 39 | - "-c" 40 | - > 41 | OCP_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token); 42 | curl -k --request POST --data '{"jwt": "'"$OCP_TOKEN"'", "role": "fruits-catalog"}' https://vault-fruits-catalog.apps.144.76.24.92.nip.io/v1/auth/kubernetes/login | jq -j '.auth.client_token' > /etc/vault/token; 43 | X_VAULT_TOKEN=$(cat /etc/vault/token); 44 | curl -k --header "X-Vault-Token: $X_VAULT_TOKEN" https://vault-fruits-catalog.apps.144.76.24.92.nip.io/v1/database/creds/fruit-catalog-role > /etc/app/creds.json; 45 | echo "spring.data.mongodb.uri=mongodb://$(jq -j '.data.username' /etc/app/creds.json):$(jq -j '.data.password' /etc/app/creds.json)@mongodb/sampledb" > /etc/app/application.properties; 46 | cp /etc/app/application.properties /deployments/config/application.properties 47 | volumeMounts: 48 | - name: app-creds 49 | mountPath: /etc/app 50 | - name: vault-token 51 | mountPath: /etc/vault 52 | - name: app-config 53 | mountPath: /deployments/config 54 | containers: 55 | - name: vault-lease-refresher 56 | image: quay.io/lbroudoux/ubi8:latest 57 | command: 58 | - "sh" 59 | - "-c" 60 | - > 61 | X_VAULT_TOKEN=$(cat /etc/vault/token); 62 | VAULT_LEASE_ID=$(cat /etc/app/creds.json | jq -j '.lease_id'); 63 | while true; do 64 | curl -k -s --request PUT --header "X-Vault-Token: $X_VAULT_TOKEN" --data '{"lease_id": "'"$VAULT_LEASE_ID"'", "increment": 3600}' https://vault-fruits-catalog.apps.144.76.24.92.nip.io/v1/sys/leases/renew; 65 | echo 'Waiting for 360 sec before renewing lease' 66 | sleep 360; 67 | done 68 | lifecycle: 69 | preStop: 70 | exec: 71 | command: 72 | - "sh" 73 | - "-c" 74 | - > 75 | X_VAULT_TOKEN=$(cat /etc/vault/token); 76 | VAULT_LEASE_ID=$(cat /etc/app/creds.json | jq -j '.lease_id'); 77 | curl -k --request PUT --header "X-Vault-Token: $X_VAULT_TOKEN" --data '{"lease_id": "'"$VAULT_LEASE_ID"'"}' https://vault-fruits-catalog.apps.144.76.24.92.nip.io/v1/sys/leases/revoke; 78 | volumeMounts: 79 | - name: app-creds 80 | mountPath: /etc/app 81 | - name: vault-token 82 | mountPath: /etc/vault 83 | - env: 84 | - name: KEYCLOAK_URL 85 | value: 'https://keycloak-fruits-catalog.apps.144.76.24.92.nip.io/auth' 86 | - name: KUBERNETES_NAMESPACE 87 | valueFrom: 88 | fieldRef: 89 | apiVersion: v1 90 | fieldPath: metadata.namespace 91 | image: >- 92 | docker-registry.default.svc:5000/fruits-catalog/fruits-catalog:latest 93 | imagePullPolicy: IfNotPresent 94 | livenessProbe: 95 | failureThreshold: 3 96 | httpGet: 97 | path: /actuator/health 98 | port: 8080 99 | scheme: HTTP 100 | initialDelaySeconds: 15 101 | periodSeconds: 10 102 | successThreshold: 1 103 | timeoutSeconds: 3 104 | name: spring-boot 105 | ports: 106 | - containerPort: 8080 107 | name: http 108 | protocol: TCP 109 | - containerPort: 9779 110 | name: prometheus 111 | protocol: TCP 112 | - containerPort: 8778 113 | name: jolokia 114 | protocol: TCP 115 | readinessProbe: 116 | failureThreshold: 3 117 | httpGet: 118 | path: /actuator/health 119 | port: 8080 120 | scheme: HTTP 121 | initialDelaySeconds: 15 122 | periodSeconds: 10 123 | successThreshold: 1 124 | timeoutSeconds: 3 125 | resources: 126 | limits: 127 | cpu: '1' 128 | memory: 256Mi 129 | requests: 130 | cpu: 200m 131 | memory: 256Mi 132 | volumeMounts: 133 | - name: app-creds 134 | mountPath: /etc/app 135 | - name: vault-token 136 | mountPath: /etc/vault 137 | - name: app-config 138 | mountPath: /deployments/config 139 | securityContext: 140 | privileged: false 141 | terminationMessagePath: /dev/termination-log 142 | terminationMessagePolicy: File 143 | dnsPolicy: ClusterFirst 144 | restartPolicy: Always 145 | schedulerName: default-scheduler 146 | serviceAccount: fruits-catalog-vault 147 | serviceAccountName: fruits-catalog-vault 148 | volumes: 149 | - name: app-creds 150 | emptyDir: {} 151 | - name: app-config 152 | emptyDir: {} 153 | - name: vault-token 154 | emptyDir: {} 155 | securityContext: {} 156 | terminationGracePeriodSeconds: 30 157 | test: false 158 | triggers: 159 | - type: ConfigChange 160 | - imageChangeParams: 161 | automatic: true 162 | containerNames: 163 | - spring-boot 164 | from: 165 | kind: ImageStreamTag 166 | name: 'fruits-catalog:latest' 167 | namespace: fruits-catalog 168 | type: ImageChange 169 | 170 | -------------------------------------------------------------------------------- /k8s/fruits-catalog-dynamic-secret-with-agent-deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps.openshift.io/v1 2 | kind: DeploymentConfig 3 | metadata: 4 | labels: 5 | app: fruits-catalog 6 | group: com.github.lbroudoux.msa 7 | provider: fabric8 8 | version: 1.0.0-SNAPSHOT 9 | name: fruits-catalog 10 | spec: 11 | replicas: 1 12 | selector: 13 | app: fruits-catalog 14 | group: com.github.lbroudoux.msa 15 | provider: fabric8 16 | strategy: 17 | activeDeadlineSeconds: 21600 18 | resources: {} 19 | rollingParams: 20 | intervalSeconds: 1 21 | maxSurge: 25% 22 | maxUnavailable: 25% 23 | timeoutSeconds: 3600 24 | updatePeriodSeconds: 1 25 | type: Rolling 26 | template: 27 | metadata: 28 | labels: 29 | app: fruits-catalog 30 | group: com.github.lbroudoux.msa 31 | provider: fabric8 32 | version: 1.0.0-SNAPSHOT 33 | annotations: 34 | vault.hashicorp.com/agent-inject: "true" 35 | vault.hashicorp.com/agent-init-first: "true" 36 | vault.hashicorp.com/agent-inject-status: "update" 37 | vault.hashicorp.com/agent-inject-secret-application.properties: "database/creds/fruit-catalog-role" 38 | vault.hashicorp.com/agent-inject-template-application.properties: | 39 | {{- with secret "database/creds/fruit-catalog-role" -}} 40 | spring.data.mongodb.uri=mongodb://{{ .Data.username }}:{{ .Data.password }}@mongodb/sampledb 41 | {{- end }} 42 | vault.hashicorp.com/secret-volume-path-application.properties: "/deployments/config/" 43 | vault.hashicorp.com/role: "fruits-catalog" 44 | vault.hashicorp.com/tls-skip-verify : "true" 45 | spec: 46 | containers: 47 | - env: 48 | #- name: KEYCLOAK_URL 49 | # value: 'https://keycloak-fruits-catalog.apps.144.76.24.92.nip.io/auth' 50 | - name: KUBERNETES_NAMESPACE 51 | valueFrom: 52 | fieldRef: 53 | apiVersion: v1 54 | fieldPath: metadata.namespace 55 | image: >- 56 | image-registry.openshift-image-registry.svc:5000/fruits-catalog/fruits-catalog:latest 57 | # Use this notation on OpenShift 3 58 | #image: docker-registry.default.svc:5000/fruits-catalog/fruits-catalog:latest 59 | imagePullPolicy: IfNotPresent 60 | livenessProbe: 61 | failureThreshold: 3 62 | httpGet: 63 | path: /actuator/health 64 | port: 8080 65 | scheme: HTTP 66 | initialDelaySeconds: 15 67 | periodSeconds: 10 68 | successThreshold: 1 69 | timeoutSeconds: 3 70 | name: spring-boot 71 | ports: 72 | - containerPort: 8080 73 | name: http 74 | protocol: TCP 75 | - containerPort: 9779 76 | name: prometheus 77 | protocol: TCP 78 | - containerPort: 8778 79 | name: jolokia 80 | protocol: TCP 81 | readinessProbe: 82 | failureThreshold: 3 83 | httpGet: 84 | path: /actuator/health 85 | port: 8080 86 | scheme: HTTP 87 | initialDelaySeconds: 15 88 | periodSeconds: 10 89 | successThreshold: 1 90 | timeoutSeconds: 3 91 | resources: 92 | limits: 93 | cpu: '1' 94 | memory: 256Mi 95 | requests: 96 | cpu: 200m 97 | memory: 256Mi 98 | volumeMounts: 99 | - name: app-config 100 | mountPath: /deployments/config 101 | securityContext: 102 | privileged: false 103 | terminationMessagePath: /dev/termination-log 104 | terminationMessagePolicy: File 105 | dnsPolicy: ClusterFirst 106 | restartPolicy: Always 107 | schedulerName: default-scheduler 108 | serviceAccount: fruits-catalog-vault 109 | serviceAccountName: fruits-catalog-vault 110 | volumes: 111 | - name: app-config 112 | emptyDir: {} 113 | securityContext: {} 114 | terminationGracePeriodSeconds: 30 115 | test: false 116 | triggers: 117 | - type: ConfigChange 118 | - imageChangeParams: 119 | automatic: true 120 | containerNames: 121 | - spring-boot 122 | from: 123 | kind: ImageStreamTag 124 | name: 'fruits-catalog:latest' 125 | namespace: fruits-catalog 126 | type: ImageChange 127 | -------------------------------------------------------------------------------- /k8s/fruits-catalog-route-certificate.yml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1alpha2 2 | kind: Certificate 3 | metadata: 4 | name: fruits-catalog-route-certificate 5 | spec: 6 | secretName: fruits-catalog-route-secret 7 | commonName: FRUITS_CATALOG_ROUTE 8 | dnsNames: 9 | - FRUITS_CATALOG_ROUTE 10 | issuerRef: 11 | name: vault-issuer 12 | kind: ClusterIssuer -------------------------------------------------------------------------------- /k8s/fruits-catalog-static-secret-deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps.openshift.io/v1 2 | kind: DeploymentConfig 3 | metadata: 4 | labels: 5 | app: fruits-catalog 6 | group: com.github.lbroudoux.msa 7 | provider: fabric8 8 | version: 1.0.0-SNAPSHOT 9 | name: fruits-catalog 10 | spec: 11 | replicas: 1 12 | selector: 13 | app: fruits-catalog 14 | group: com.github.lbroudoux.msa 15 | provider: fabric8 16 | strategy: 17 | activeDeadlineSeconds: 21600 18 | resources: {} 19 | rollingParams: 20 | intervalSeconds: 1 21 | maxSurge: 25% 22 | maxUnavailable: 25% 23 | timeoutSeconds: 3600 24 | updatePeriodSeconds: 1 25 | type: Rolling 26 | template: 27 | metadata: 28 | labels: 29 | app: fruits-catalog 30 | group: com.github.lbroudoux.msa 31 | provider: fabric8 32 | version: 1.0.0-SNAPSHOT 33 | spec: 34 | initContainers: 35 | - name: vault-init 36 | image: quay.io/lbroudoux/ubi8:latest 37 | command: 38 | - "sh" 39 | - "-c" 40 | - > 41 | OCP_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token); 42 | curl -k --request POST --data '{"jwt": "'"$OCP_TOKEN"'", "role": "fruits-catalog"}' https://vault-fruits-catalog.apps.144.76.24.92.nip.io/v1/auth/kubernetes/login | jq -j '.auth.client_token' > /etc/vault/token; 43 | X_VAULT_TOKEN=$(cat /etc/vault/token); 44 | curl -k --header "X-Vault-Token: $X_VAULT_TOKEN" https://vault-fruits-catalog.apps.144.76.24.92.nip.io/v1/secret/fruit-catalog-mongodb > /etc/app/creds.json; 45 | echo "spring.data.mongodb.uri=mongodb://$(jq -j '.data.user' /etc/app/creds.json):$(jq -j '.data.password' /etc/app/creds.json)@mongodb/sampledb" > /etc/app/application.properties; 46 | cp /etc/app/application.properties /deployments/config/application.properties 47 | volumeMounts: 48 | - name: app-creds 49 | mountPath: /etc/app 50 | - name: vault-token 51 | mountPath: /etc/vault 52 | - name: app-config 53 | mountPath: /deployments/config 54 | containers: 55 | - env: 56 | - name: KEYCLOAK_URL 57 | value: 'https://keycloak-fruits-catalog.apps.144.76.24.92.nip.io/auth' 58 | - name: KUBERNETES_NAMESPACE 59 | valueFrom: 60 | fieldRef: 61 | apiVersion: v1 62 | fieldPath: metadata.namespace 63 | image: >- 64 | docker-registry.default.svc:5000/fruits-catalog/fruits-catalog:latest 65 | imagePullPolicy: IfNotPresent 66 | livenessProbe: 67 | failureThreshold: 3 68 | httpGet: 69 | path: /actuator/health 70 | port: 8080 71 | scheme: HTTP 72 | initialDelaySeconds: 15 73 | periodSeconds: 10 74 | successThreshold: 1 75 | timeoutSeconds: 3 76 | name: spring-boot 77 | ports: 78 | - containerPort: 8080 79 | name: http 80 | protocol: TCP 81 | - containerPort: 9779 82 | name: prometheus 83 | protocol: TCP 84 | - containerPort: 8778 85 | name: jolokia 86 | protocol: TCP 87 | readinessProbe: 88 | failureThreshold: 3 89 | httpGet: 90 | path: /actuator/health 91 | port: 8080 92 | scheme: HTTP 93 | initialDelaySeconds: 15 94 | periodSeconds: 10 95 | successThreshold: 1 96 | timeoutSeconds: 3 97 | resources: 98 | limits: 99 | cpu: '1' 100 | memory: 256Mi 101 | requests: 102 | cpu: 200m 103 | memory: 256Mi 104 | volumeMounts: 105 | - name: app-creds 106 | mountPath: /etc/app 107 | - name: vault-token 108 | mountPath: /etc/vault 109 | - name: app-config 110 | mountPath: /deployments/config 111 | securityContext: 112 | privileged: false 113 | terminationMessagePath: /dev/termination-log 114 | terminationMessagePolicy: File 115 | dnsPolicy: ClusterFirst 116 | restartPolicy: Always 117 | schedulerName: default-scheduler 118 | serviceAccount: fruits-catalog-vault 119 | serviceAccountName: fruits-catalog-vault 120 | volumes: 121 | - name: app-creds 122 | emptyDir: {} 123 | - name: app-config 124 | emptyDir: {} 125 | - name: vault-token 126 | emptyDir: {} 127 | securityContext: {} 128 | terminationGracePeriodSeconds: 30 129 | test: false 130 | triggers: 131 | - type: ConfigChange 132 | - imageChangeParams: 133 | automatic: true 134 | containerNames: 135 | - spring-boot 136 | from: 137 | kind: ImageStreamTag 138 | name: 'fruits-catalog:latest' 139 | namespace: fruits-catalog 140 | type: ImageChange 141 | 142 | -------------------------------------------------------------------------------- /k8s/fruits-catalog-static-secret-with-agent-deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps.openshift.io/v1 2 | kind: DeploymentConfig 3 | metadata: 4 | labels: 5 | app: fruits-catalog 6 | group: com.github.lbroudoux.msa 7 | provider: fabric8 8 | version: 1.0.0-SNAPSHOT 9 | name: fruits-catalog 10 | spec: 11 | replicas: 1 12 | selector: 13 | app: fruits-catalog 14 | group: com.github.lbroudoux.msa 15 | provider: fabric8 16 | strategy: 17 | activeDeadlineSeconds: 21600 18 | resources: {} 19 | rollingParams: 20 | intervalSeconds: 1 21 | maxSurge: 25% 22 | maxUnavailable: 25% 23 | timeoutSeconds: 3600 24 | updatePeriodSeconds: 1 25 | type: Rolling 26 | template: 27 | metadata: 28 | labels: 29 | app: fruits-catalog 30 | group: com.github.lbroudoux.msa 31 | provider: fabric8 32 | version: 1.0.0-SNAPSHOT 33 | annotations: 34 | vault.hashicorp.com/agent-inject: "true" 35 | vault.hashicorp.com/agent-init-first: "true" 36 | vault.hashicorp.com/agent-inject-status: "update" 37 | vault.hashicorp.com/agent-inject-secret-application.properties: "secret/fruit-catalog-mongodb" 38 | vault.hashicorp.com/agent-inject-template-application.properties: | 39 | {{- with secret "secret/fruit-catalog-mongodb" -}} 40 | spring.data.mongodb.uri=mongodb://{{ .Data.user }}:{{ .Data.password }}@mongodb/sampledb 41 | {{- end }} 42 | vault.hashicorp.com/secret-volume-path-application.properties: "/deployments/config/" 43 | vault.hashicorp.com/agent-pre-populate-only: "true" 44 | vault.hashicorp.com/role: "fruits-catalog" 45 | vault.hashicorp.com/tls-skip-verify : "true" 46 | spec: 47 | containers: 48 | - env: 49 | #- name: KEYCLOAK_URL 50 | # value: 'https://keycloak-fruits-catalog.apps.144.76.24.92.nip.io/auth' 51 | - name: KUBERNETES_NAMESPACE 52 | valueFrom: 53 | fieldRef: 54 | apiVersion: v1 55 | fieldPath: metadata.namespace 56 | image: >- 57 | image-registry.openshift-image-registry.svc:5000/fruits-catalog/fruits-catalog:latest 58 | # Use this notation on OpenShift 3 59 | #image: docker-registry.default.svc:5000/fruits-catalog/fruits-catalog:latest 60 | imagePullPolicy: IfNotPresent 61 | livenessProbe: 62 | failureThreshold: 3 63 | httpGet: 64 | path: /actuator/health 65 | port: 8080 66 | scheme: HTTP 67 | initialDelaySeconds: 15 68 | periodSeconds: 10 69 | successThreshold: 1 70 | timeoutSeconds: 3 71 | name: spring-boot 72 | ports: 73 | - containerPort: 8080 74 | name: http 75 | protocol: TCP 76 | - containerPort: 9779 77 | name: prometheus 78 | protocol: TCP 79 | - containerPort: 8778 80 | name: jolokia 81 | protocol: TCP 82 | readinessProbe: 83 | failureThreshold: 3 84 | httpGet: 85 | path: /actuator/health 86 | port: 8080 87 | scheme: HTTP 88 | initialDelaySeconds: 15 89 | periodSeconds: 10 90 | successThreshold: 1 91 | timeoutSeconds: 3 92 | resources: 93 | limits: 94 | cpu: '1' 95 | memory: 256Mi 96 | requests: 97 | cpu: 200m 98 | memory: 256Mi 99 | volumeMounts: 100 | - name: app-config 101 | mountPath: /deployments/config 102 | securityContext: 103 | privileged: false 104 | terminationMessagePath: /dev/termination-log 105 | terminationMessagePolicy: File 106 | dnsPolicy: ClusterFirst 107 | restartPolicy: Always 108 | schedulerName: default-scheduler 109 | serviceAccount: fruits-catalog-vault 110 | serviceAccountName: fruits-catalog-vault 111 | volumes: 112 | - name: app-config 113 | emptyDir: {} 114 | securityContext: {} 115 | terminationGracePeriodSeconds: 30 116 | test: false 117 | triggers: 118 | - type: ConfigChange 119 | - imageChangeParams: 120 | automatic: true 121 | containerNames: 122 | - spring-boot 123 | from: 124 | kind: ImageStreamTag 125 | name: 'fruits-catalog:latest' 126 | namespace: fruits-catalog 127 | type: ImageChange 128 | -------------------------------------------------------------------------------- /k8s/mongodb-deployment.yml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: Secret 3 | apiVersion: v1 4 | metadata: 5 | name: mongodb 6 | labels: 7 | app: fruits-catalog 8 | type: Opaque 9 | stringData: 10 | database-name: sampledb 11 | database-user: userEVY 12 | database-password: wwsPHR4gDorXgQ2a 13 | database-admin-password: YiwThWIcUsaIwymi 14 | --- 15 | kind: Service 16 | apiVersion: v1 17 | metadata: 18 | name: mongodb 19 | labels: 20 | app: fruits-catalog 21 | container: mongodb 22 | spec: 23 | ports: 24 | - name: mongodb 25 | protocol: TCP 26 | port: 27017 27 | targetPort: 27017 28 | selector: 29 | app: fruits-catalog 30 | container: mongodb 31 | type: ClusterIP 32 | sessionAffinity: None 33 | --- 34 | kind: PersistentVolumeClaim 35 | apiVersion: v1 36 | metadata: 37 | name: mongodb 38 | labels: 39 | app: fruits-catalog 40 | container: mongodb 41 | spec: 42 | accessModes: 43 | - ReadWriteOnce 44 | resources: 45 | requests: 46 | storage: 2Gi 47 | --- 48 | kind: Deployment 49 | apiVersion: apps/v1 50 | metadata: 51 | name: mongodb 52 | labels: 53 | app: fruits-catalog 54 | container: mongodb 55 | spec: 56 | strategy: 57 | type: Recreate 58 | replicas: 1 59 | selector: 60 | matchLabels: 61 | app: fruits-catalog 62 | deploymentconfig: mongodb 63 | container: mongodb 64 | template: 65 | metadata: 66 | labels: 67 | app: fruits-catalog 68 | deploymentconfig: mongodb 69 | container: mongodb 70 | spec: 71 | containers: 72 | - name: mongodb 73 | image: centos/mongodb-32-centos7:latest 74 | ports: 75 | - containerPort: 27017 76 | protocol: TCP 77 | readinessProbe: 78 | timeoutSeconds: 1 79 | initialDelaySeconds: 3 80 | exec: 81 | command: 82 | - "/bin/sh" 83 | - "-i" 84 | - "-c" 85 | - mongo 127.0.0.1:27017/$MONGODB_DATABASE -u $MONGODB_USER -p $MONGODB_PASSWORD 86 | --eval="quit()" 87 | livenessProbe: 88 | timeoutSeconds: 1 89 | initialDelaySeconds: 30 90 | tcpSocket: 91 | port: 27017 92 | env: 93 | - name: MONGODB_USER 94 | valueFrom: 95 | secretKeyRef: 96 | key: database-user 97 | name: mongodb 98 | - name: MONGODB_PASSWORD 99 | valueFrom: 100 | secretKeyRef: 101 | key: database-password 102 | name: mongodb 103 | - name: MONGODB_ADMIN_PASSWORD 104 | valueFrom: 105 | secretKeyRef: 106 | key: database-admin-password 107 | name: mongodb 108 | - name: MONGODB_DATABASE 109 | valueFrom: 110 | secretKeyRef: 111 | key: database-name 112 | name: mongodb 113 | resources: 114 | limits: 115 | memory: 512Mi 116 | volumeMounts: 117 | - name: "mongodb-data" 118 | mountPath: "/var/lib/mongodb/data" 119 | terminationMessagePath: "/dev/termination-log" 120 | imagePullPolicy: IfNotPresent 121 | securityContext: 122 | capabilities: {} 123 | privileged: false 124 | restartPolicy: Always 125 | dnsPolicy: ClusterFirst 126 | volumes: 127 | - name: "mongodb-data" 128 | persistentVolumeClaim: 129 | claimName: "mongodb" -------------------------------------------------------------------------------- /k8s/mongodb-istio-destinationrule.yml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: DestinationRule 3 | metadata: 4 | name: mongodb 5 | spec: 6 | host: mongodb 7 | trafficPolicy: 8 | tls: 9 | mode: ISTIO_MUTUAL 10 | subsets: 11 | - name: v1 12 | labels: 13 | version: v1 -------------------------------------------------------------------------------- /k8s/mongodb-istio-virtualservice.yml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: VirtualService 3 | metadata: 4 | name: mongodb 5 | spec: 6 | hosts: 7 | - mongodb 8 | http: 9 | - route: 10 | - destination: 11 | host: mongodb 12 | subset: v1 -------------------------------------------------------------------------------- /k8s/vault-agent-deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: vault-agent-injector-clusterrole 5 | labels: 6 | app.kubernetes.io/name: vault-agent-injector 7 | app.kubernetes.io/instance: vault-agent 8 | app.kubernetes.io/managed-by: vault-agent-injector-svc 9 | rules: 10 | - apiGroups: ["admissionregistration.k8s.io"] 11 | resources: ["mutatingwebhookconfigurations"] 12 | verbs: 13 | - "get" 14 | - "list" 15 | - "watch" 16 | - "patch" 17 | --- 18 | apiVersion: v1 19 | kind: ServiceAccount 20 | metadata: 21 | name: vault-agent-injector 22 | labels: 23 | app.kubernetes.io/name: vault-agent-injector 24 | app.kubernetes.io/instance: vault-agent 25 | app.kubernetes.io/managed-by: vault-agent-injector-svc 26 | --- 27 | apiVersion: rbac.authorization.k8s.io/v1 28 | kind: ClusterRoleBinding 29 | metadata: 30 | name: vault-agent-injector-binding 31 | namespace: fruits-catalog 32 | labels: 33 | app.kubernetes.io/name: vault-agent-injector 34 | app.kubernetes.io/instance: vault-agent 35 | app.kubernetes.io/managed-by: vault-agent-injector-svc 36 | roleRef: 37 | apiGroup: rbac.authorization.k8s.io 38 | kind: ClusterRole 39 | name: vault-agent-injector-clusterrole 40 | subjects: 41 | - kind: ServiceAccount 42 | name: vault-agent-injector 43 | namespace: fruits-catalog 44 | --- 45 | # Deployment for the injector 46 | apiVersion: apps/v1 47 | kind: Deployment 48 | metadata: 49 | name: vault-agent-injector 50 | labels: 51 | app.kubernetes.io/name: vault-agent-injector 52 | app.kubernetes.io/instance: vault-agent 53 | app.kubernetes.io/managed-by: vault-agent-injector-svc 54 | component: webhook 55 | spec: 56 | replicas: 1 57 | selector: 58 | matchLabels: 59 | app.kubernetes.io/name: vault-agent-injector 60 | app.kubernetes.io/instance: vault-agent 61 | component: webhook 62 | template: 63 | metadata: 64 | labels: 65 | app.kubernetes.io/name: vault-agent-injector 66 | app.kubernetes.io/instance: vault-agent 67 | component: webhook 68 | spec: 69 | serviceAccountName: "vault-agent-injector" 70 | securityContext: 71 | runAsNonRoot: true 72 | runAsGroup: 1000 73 | runAsUser: 100 74 | containers: 75 | - name: sidecar-injector 76 | image: "hashicorp/vault-k8s:0.3.0" 77 | imagePullPolicy: "IfNotPresent" 78 | env: 79 | - name: AGENT_INJECT_LISTEN 80 | value: ":8080" 81 | - name: AGENT_INJECT_LOG_LEVEL 82 | value: info 83 | - name: AGENT_INJECT_VAULT_ADDR 84 | value: https://vault.fruits-catalog.svc.cluster.local:8200 85 | - name: AGENT_INJECT_VAULT_AUTH_PATH 86 | value: auth/kubernetes 87 | - name: AGENT_INJECT_VAULT_IMAGE 88 | value: "vault:1.3.3" 89 | - name: AGENT_INJECT_TLS_AUTO 90 | value: vault-agent-injector-cfg 91 | - name: AGENT_INJECT_TLS_AUTO_HOSTS 92 | value: vault-agent-injector-svc,vault-agent-injector-svc.fruits-catalog,vault-agent-injector-svc.fruits-catalog.svc 93 | - name: AGENT_INJECT_LOG_FORMAT 94 | value: standard 95 | - name: AGENT_INJECT_REVOKE_ON_SHUTDOWN 96 | value: 'false' 97 | args: 98 | - agent-inject 99 | - 2>&1 100 | livenessProbe: 101 | httpGet: 102 | path: /health/ready 103 | port: 8080 104 | scheme: HTTPS 105 | failureThreshold: 2 106 | initialDelaySeconds: 1 107 | periodSeconds: 2 108 | successThreshold: 1 109 | timeoutSeconds: 5 110 | readinessProbe: 111 | httpGet: 112 | path: /health/ready 113 | port: 8080 114 | scheme: HTTPS 115 | failureThreshold: 2 116 | initialDelaySeconds: 2 117 | periodSeconds: 2 118 | successThreshold: 1 119 | timeoutSeconds: 5 120 | --- 121 | apiVersion: v1 122 | kind: Service 123 | metadata: 124 | name: vault-agent-injector-svc 125 | labels: 126 | app.kubernetes.io/name: vault-agent-injector 127 | app.kubernetes.io/instance: vault-agent 128 | app.kubernetes.io/managed-by: vault-agent-injector-svc 129 | spec: 130 | ports: 131 | - port: 443 132 | targetPort: 8080 133 | selector: 134 | app.kubernetes.io/name: vault-agent-injector 135 | app.kubernetes.io/instance: vault-agent 136 | component: webhook 137 | --- 138 | apiVersion: admissionregistration.k8s.io/v1beta1 139 | kind: MutatingWebhookConfiguration 140 | metadata: 141 | name: vault-agent-injector-cfg 142 | labels: 143 | app.kubernetes.io/name: vault-agent-injector 144 | app.kubernetes.io/instance: vault-agent 145 | app.kubernetes.io/managed-by: vault-agent-injector-svc 146 | webhooks: 147 | - name: vault.hashicorp.com 148 | clientConfig: 149 | service: 150 | name: vault-agent-injector-svc 151 | namespace: fruits-catalog 152 | path: "/mutate" 153 | caBundle: "" 154 | rules: 155 | - operations: ["CREATE", "UPDATE"] 156 | apiGroups: [""] 157 | apiVersions: ["v1"] 158 | resources: ["pods"] -------------------------------------------------------------------------------- /k8s/vault-deployment.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: vault-sa 6 | --- 7 | kind: ConfigMap 8 | apiVersion: v1 9 | metadata: 10 | name: vault-config 11 | labels: 12 | app: vault 13 | data: 14 | vault-config: |- 15 | { 16 | "backend": { 17 | "file": { 18 | "path": "/vault/file" 19 | } 20 | }, 21 | "default_lease_ttl": "168h", 22 | "max_lease_ttl": "720h" , 23 | "disable_mlock": true, 24 | "ui": true, 25 | "listener": { 26 | "tcp" : { 27 | "address" : "0.0.0.0:8200" , 28 | "tls_cert_file" : "/var/run/secrets/kubernetes.io/certs/tls.crt", 29 | "tls_key_file" : "/var/run/secrets/kubernetes.io/certs/tls.key" 30 | } 31 | } 32 | } 33 | --- 34 | kind: PersistentVolumeClaim 35 | apiVersion: v1 36 | metadata: 37 | name: vault-file-backend 38 | spec: 39 | accessModes: 40 | - ReadWriteOnce 41 | resources: 42 | requests: 43 | storage: 1Gi 44 | --- 45 | kind: Deployment 46 | apiVersion: apps/v1 47 | metadata: 48 | labels: 49 | app: vault 50 | name: vault 51 | spec: 52 | replicas: 1 53 | selector: 54 | matchLabels: 55 | app: vault 56 | template: 57 | metadata: 58 | labels: 59 | app: vault 60 | spec: 61 | serviceAccountName: vault-sa 62 | containers: 63 | - image: vault:1.3.3 64 | #image: vault:1.0.2 65 | name: vault 66 | ports: 67 | - containerPort: 8200 68 | name: vaultport 69 | protocol: TCP 70 | args: 71 | - server 72 | - -log-level=debug 73 | env: 74 | - name: SKIP_SETCAP 75 | value: 'true' 76 | - name: VAULT_LOCAL_CONFIG 77 | valueFrom: 78 | configMapKeyRef: 79 | name: vault-config 80 | key: vault-config 81 | volumeMounts: 82 | - name: vault-file-backend 83 | mountPath: /vault/file 84 | readOnly: false 85 | - name: vault-cert 86 | mountPath: /var/run/secrets/kubernetes.io/certs 87 | livenessProbe: 88 | httpGet: 89 | path: 'v1/sys/health?standbyok=true&standbycode=200&sealedcode=200&uninitcode=200' 90 | port: 8200 91 | scheme: HTTPS 92 | readinessProbe: 93 | httpGet: 94 | path: 'v1/sys/health?standbyok=true&standbycode=200&sealedcode=200&uninitcode=200' 95 | port: 8200 96 | scheme: HTTPS 97 | volumes: 98 | - name: vault-file-backend 99 | persistentVolumeClaim: 100 | claimName: vault-file-backend 101 | - name: vault-cert 102 | secret: 103 | secretName: vault-cert 104 | --- 105 | apiVersion: v1 106 | kind: Service 107 | metadata: 108 | name: vault 109 | annotations: 110 | service.alpha.openshift.io/serving-cert-secret-name: vault-cert 111 | labels: 112 | app: vault 113 | spec: 114 | ports: 115 | - name: vault 116 | port: 8200 117 | selector: 118 | app: vault -------------------------------------------------------------------------------- /k8s/vault-issuer.yml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1alpha2 2 | kind: ClusterIssuer 3 | metadata: 4 | name: vault-issuer 5 | spec: 6 | vault: 7 | path: pki_int/sign/example-opentlc-com 8 | server: https://vault.fruits-catalog.svc.cluster.local:8200 9 | caBundle: VAULT_LISTENER_CERT 10 | auth: 11 | kubernetes: 12 | role: cert-manager-vault-issuer 13 | mountPath: /v1/auth/kubernetes 14 | secretRef: 15 | name: CERT_MANAGER_ISSUER_TOKEN 16 | key: token -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 4.0.0 19 | com.github.lbroudoux.msa 20 | fruits-catalog 21 | 1.0.0-SNAPSHOT 22 | Fruits Catalog 23 | Fruits Catalog 24 | 25 | 1.4.0.Final 26 | 3.1.0 27 | 1.4 28 | 3.1.8 29 | 3.1.0 30 | 31 | 36 | openshift 37 | openshift/redhat-openjdk18-openshift:${openjdk18-openshift.version} 38 | istag 39 | 40 | 1.8 41 | UTF-8 42 | 2.1.2.RELEASE 43 | 1.15.3 44 | 1.0.1 45 | 0.1.5 46 | 47 | 48 | 49 | 4.3.1 50 | 51 | 52 | 53 | 54 | org.springframework.boot 55 | spring-boot-starter-parent 56 | ${spring-boot.version} 57 | pom 58 | import 59 | 60 | 61 | org.jboss.arquillian 62 | arquillian-bom 63 | ${arquillian.version} 64 | pom 65 | import 66 | 67 | 68 | io.rest-assured 69 | rest-assured 70 | ${rest-assured.version} 71 | 72 | 73 | org.awaitility 74 | awaitility 75 | ${awaitility.version} 76 | 77 | 78 | 79 | 80 | 81 | org.springframework.boot 82 | spring-boot-starter-web 83 | 84 | 85 | org.springframework.boot 86 | spring-boot-starter-data-mongodb 87 | 88 | 89 | 90 | 91 | org.springframework.boot 92 | spring-boot-starter-actuator 93 | 94 | 95 | 96 | io.micrometer 97 | micrometer-registry-prometheus 98 | 99 | 100 | 101 | org.aspectj 102 | aspectjweaver 103 | 104 | 105 | 106 | 107 | io.opentracing.contrib 108 | opentracing-spring-jaeger-cloud-starter 109 | ${opentracing-spring-jaeger-cloud.version} 110 | 111 | 113 | 120 | 121 | 122 | org.springframework.boot 123 | spring-boot-starter-test 124 | test 125 | 126 | 127 | org.jboss.arquillian.junit 128 | arquillian-junit-standalone 129 | test 130 | 131 | 132 | org.arquillian.cube 133 | arquillian-cube-openshift 134 | ${arquillian-cube-openshift.version} 135 | test 136 | 137 | 138 | io.undertow 139 | undertow-core 140 | 141 | 142 | 143 | 144 | io.rest-assured 145 | rest-assured 146 | test 147 | 148 | 149 | org.awaitility 150 | awaitility 151 | test 152 | 153 | 154 | org.assertj 155 | assertj-core 156 | test 157 | 158 | 159 | 160 | 161 | 162 | src/main/resources 163 | true 164 | 165 | 166 | 167 | 168 | src/test/resources 169 | true 170 | 171 | 172 | 173 | 174 | 175 | org.springframework.boot 176 | spring-boot-maven-plugin 177 | ${spring-boot.version} 178 | 179 | 180 | 181 | 182 | 183 | org.apache.maven.plugins 184 | maven-compiler-plugin 185 | 3.1 186 | 187 | ${java.version} 188 | ${java.version} 189 | ${project.build.sourceEncoding} 190 | 191 | 192 | 193 | org.springframework.boot 194 | spring-boot-maven-plugin 195 | 196 | 197 | local 198 | 199 | 200 | 201 | 202 | 203 | repackage 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | prod 213 | 214 | 215 | 216 | com.github.eirslett 217 | frontend-maven-plugin 218 | 1.6 219 | 220 | 221 | install node and npm 222 | 223 | install-node-and-npm 224 | 225 | generate-resources 226 | 227 | 228 | npm install 229 | 230 | npm 231 | 232 | 233 | install 234 | 235 | 236 | 237 | 238 | ng build 239 | 240 | npm 241 | 242 | generate-resources 243 | 244 | run-script build-prod 245 | 246 | 247 | 248 | 249 | v8.11.3 250 | 5.6.0 251 | ${basedir}/src/main/webapp 252 | target 253 | 254 | 255 | 256 | maven-resources-plugin 257 | 3.0.1 258 | 259 | 260 | copy-resources 261 | generate-resources 262 | 263 | copy-resources 264 | 265 | 266 | target/classes/public 267 | 268 | 269 | src/main/webapp/dist 270 | false 271 | 272 | 273 | ${basedir} 274 | false 275 | 276 | bower_components/bootstrap/** 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | openshift 290 | 291 | 292 | 293 | io.fabric8 294 | fabric8-maven-plugin 295 | ${fabric8-fmp.version} 296 | 297 | 298 | fmp 299 | 300 | resource 301 | build 302 | 303 | 304 | 305 | 306 | 307 | com.github.eirslett 308 | frontend-maven-plugin 309 | 1.6 310 | 311 | 312 | install node and npm 313 | 314 | install-node-and-npm 315 | 316 | generate-resources 317 | 318 | 319 | npm install 320 | 321 | npm 322 | 323 | 324 | install 325 | 326 | 327 | 328 | 329 | ng build 330 | 331 | npm 332 | 333 | generate-resources 334 | 335 | run-script build-prod 336 | 337 | 338 | 339 | 340 | v8.11.3 341 | 5.6.0 342 | ${basedir}/src/main/webapp 343 | target 344 | 345 | 346 | 347 | maven-resources-plugin 348 | 3.0.1 349 | 350 | 351 | copy-resources 352 | generate-resources 353 | 354 | copy-resources 355 | 356 | 357 | target/classes/public 358 | 359 | 360 | src/main/webapp/dist 361 | false 362 | 363 | 364 | ${basedir} 365 | false 366 | 367 | bower_components/bootstrap/** 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | openshift-it 381 | 382 | 383 | 384 | org.apache.maven.plugins 385 | maven-failsafe-plugin 386 | 387 | 388 | ${project.artifactId} 389 | 390 | ${project.build.directory}/${project.build.finalName}.${project.packaging}.original 391 | 392 | 393 | 394 | 395 | integration-test 396 | verify 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | -------------------------------------------------------------------------------- /src/main/fabric8/deployment.yml: -------------------------------------------------------------------------------- 1 | spec: 2 | template: 3 | spec: 4 | containers: 5 | - 6 | resources: 7 | requests: 8 | cpu: "0.2" 9 | memory: 256Mi 10 | limits: 11 | cpu: "1.0" 12 | memory: 256Mi 13 | livenessProbe: 14 | failureThreshold: 3 15 | httpGet: 16 | path: /actuator/health 17 | port: 8080 18 | scheme: HTTP 19 | initialDelaySeconds: 15 20 | periodSeconds: 10 21 | successThreshold: 1 22 | timeoutSeconds: 3 23 | readinessProbe: 24 | failureThreshold: 3 25 | httpGet: 26 | path: /actuator/health 27 | port: 8080 28 | scheme: HTTP 29 | initialDelaySeconds: 15 30 | periodSeconds: 10 31 | successThreshold: 1 32 | timeoutSeconds: 3 33 | env: 34 | - name: SPRING_DATA_MONGODB_URI 35 | value: mongodb://${SPRING_DATA_MONGODB_USER}:${SPRING_DATA_MONGODB_PASSWORD}@mongodb/sampledb 36 | - name: SPRING_DATA_MONGODB_USER 37 | valueFrom: 38 | secretKeyRef: 39 | key: database-user 40 | name: mongodb 41 | - name: SPRING_DATA_MONGODB_PASSWORD 42 | valueFrom: 43 | secretKeyRef: 44 | key: database-password 45 | name: mongodb 46 | 47 | -------------------------------------------------------------------------------- /src/main/fabric8/service.yml: -------------------------------------------------------------------------------- 1 | metadata: 2 | annotations: 3 | prometheus.io/path: /actuator/prometheus 4 | prometheus.io/port: '8080' 5 | prometheus.io/scrape: 'true' -------------------------------------------------------------------------------- /src/main/java/com/github/lbroudoux/fruits/catalog/FruitsCatalogApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package com.github.lbroudoux.fruits.catalog; 18 | 19 | import org.springframework.boot.SpringApplication; 20 | import org.springframework.boot.autoconfigure.SpringBootApplication; 21 | 22 | @SpringBootApplication 23 | public class FruitsCatalogApplication { 24 | 25 | public static void main(String[] args) { 26 | SpringApplication.run(FruitsCatalogApplication.class, args); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/github/lbroudoux/fruits/catalog/TimedConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package com.github.lbroudoux.fruits.catalog; 18 | 19 | import org.springframework.context.annotation.Bean; 20 | import org.springframework.context.annotation.Configuration; 21 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 22 | 23 | import io.micrometer.core.aop.TimedAspect; 24 | import io.micrometer.core.instrument.MeterRegistry; 25 | 26 | @Configuration 27 | @EnableAspectJAutoProxy 28 | public class TimedConfiguration { 29 | @Bean 30 | public TimedAspect timedAspect(MeterRegistry registry) { 31 | return new TimedAspect(registry); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/github/lbroudoux/fruits/catalog/TracerConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.fruits.catalog; 2 | 3 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | import io.jaegertracing.internal.JaegerTracer; 8 | import io.jaegertracing.internal.reporters.InMemoryReporter; 9 | import io.jaegertracing.internal.samplers.ConstSampler; 10 | import io.jaegertracing.spi.Reporter; 11 | import io.jaegertracing.spi.Sampler; 12 | import io.opentracing.Tracer; 13 | 14 | @ConditionalOnProperty(value = "opentracing.jaeger.enabled", havingValue = "false", matchIfMissing = false) 15 | @Configuration 16 | public class TracerConfiguration { 17 | 18 | @Bean 19 | public Tracer jaegerTracer() { 20 | final Reporter reporter = new InMemoryReporter(); 21 | final Sampler sampler = new ConstSampler(false); 22 | return new JaegerTracer.Builder("untraced-service") 23 | .withReporter(reporter) 24 | .withSampler(sampler) 25 | .build(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/lbroudoux/fruits/catalog/domain/Fruit.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.fruits.catalog.domain; 2 | 3 | import org.springframework.data.annotation.Id; 4 | 5 | public class Fruit { 6 | 7 | @Id 8 | private String id; 9 | 10 | private String name; 11 | private String origin; 12 | 13 | public Fruit() { 14 | } 15 | 16 | public String getId() { 17 | return this.id; 18 | } 19 | public void setId(String id) { 20 | this.id = id; 21 | } 22 | 23 | public String getName() { 24 | return this.name; 25 | } 26 | public void setName(String name) { 27 | this.name = name; 28 | } 29 | public String getOrigin() { 30 | return this.origin; 31 | } 32 | public void setOrigin(String origin) { 33 | this.origin = origin; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/github/lbroudoux/fruits/catalog/repository/FruitRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.fruits.catalog.repository; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.data.mongodb.repository.MongoRepository; 6 | 7 | import com.github.lbroudoux.fruits.catalog.domain.Fruit; 8 | 9 | public interface FruitRepository extends MongoRepository { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/github/lbroudoux/fruits/catalog/service/FruitController.java: -------------------------------------------------------------------------------- 1 | package com.github.lbroudoux.fruits.catalog.service; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.RequestBody; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestMethod; 11 | import org.springframework.web.bind.annotation.RequestParam; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | import io.micrometer.core.annotation.Timed; 15 | 16 | import com.github.lbroudoux.fruits.catalog.domain.Fruit; 17 | import com.github.lbroudoux.fruits.catalog.repository.FruitRepository; 18 | 19 | @RestController 20 | public class FruitController { 21 | 22 | @Autowired 23 | private FruitRepository repository; 24 | 25 | @RequestMapping(value = "/api/fruits", method = RequestMethod.GET) 26 | public List listFruits() { 27 | return repository.findAll(); 28 | } 29 | 30 | @RequestMapping(value = "/api/fruits", method = RequestMethod.POST) 31 | public ResponseEntity createFruit(@RequestBody Fruit fruit) { 32 | return new ResponseEntity<>(repository.save(fruit), HttpStatus.CREATED); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/resources/application-local.yml: -------------------------------------------------------------------------------- 1 | spring.data.mongodb.uri: mongodb://localhost:27017/sampledb 2 | 3 | # Metrics related configurations 4 | management: 5 | endpoints: 6 | web: 7 | exposure: 8 | include: "*" 9 | endpoint: 10 | metrics: 11 | enabled: true 12 | prometheus: 13 | enabled: true 14 | metrics: 15 | export: 16 | prometheus: 17 | enabled: true 18 | 19 | # OpenTracing related configurations. 20 | opentracing: 21 | jaeger: 22 | enabled: true 23 | log-spans: true 24 | enable-b3-propagation: false 25 | udp-sender: 26 | host: "localhost" 27 | port: 5775 28 | -------------------------------------------------------------------------------- /src/main/webapp/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "fruits-catalog": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": {}, 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "src/tsconfig.app.json", 21 | "assets": [ 22 | "src/favicon.ico", 23 | "src/assets" 24 | ], 25 | "styles": [ 26 | "./node_modules/bootstrap/dist/css/bootstrap.min.css", 27 | "src/styles.css" 28 | ], 29 | "scripts": [ 30 | "./node_modules/jquery/dist/jquery.min.js", 31 | "./node_modules/bootstrap/dist/js/bootstrap.min.js" 32 | ] 33 | }, 34 | "configurations": { 35 | "production": { 36 | "fileReplacements": [ 37 | { 38 | "replace": "src/environments/environment.ts", 39 | "with": "src/environments/environment.prod.ts" 40 | } 41 | ], 42 | "optimization": true, 43 | "outputHashing": "all", 44 | "sourceMap": false, 45 | "extractCss": true, 46 | "namedChunks": false, 47 | "aot": true, 48 | "extractLicenses": true, 49 | "vendorChunk": false, 50 | "buildOptimizer": true 51 | } 52 | } 53 | }, 54 | "serve": { 55 | "builder": "@angular-devkit/build-angular:dev-server", 56 | "options": { 57 | "browserTarget": "fruits-catalog:build", 58 | "proxyConfig": "proxy.conf.json" 59 | }, 60 | "configurations": { 61 | "production": { 62 | "browserTarget": "fruits-catalog:build:production" 63 | } 64 | } 65 | }, 66 | "extract-i18n": { 67 | "builder": "@angular-devkit/build-angular:extract-i18n", 68 | "options": { 69 | "browserTarget": "fruits-catalog:build" 70 | } 71 | }, 72 | "test": { 73 | "builder": "@angular-devkit/build-angular:karma", 74 | "options": { 75 | "main": "src/test.ts", 76 | "polyfills": "src/polyfills.ts", 77 | "tsConfig": "src/tsconfig.spec.json", 78 | "karmaConfig": "src/karma.conf.js", 79 | "styles": [ 80 | "src/styles.css" 81 | ], 82 | "scripts": [], 83 | "assets": [ 84 | "src/favicon.ico", 85 | "src/assets" 86 | ] 87 | } 88 | }, 89 | "lint": { 90 | "builder": "@angular-devkit/build-angular:tslint", 91 | "options": { 92 | "tsConfig": [ 93 | "src/tsconfig.app.json", 94 | "src/tsconfig.spec.json" 95 | ], 96 | "exclude": [ 97 | "**/node_modules/**" 98 | ] 99 | } 100 | } 101 | } 102 | }, 103 | "fruits-catalog-e2e": { 104 | "root": "e2e/", 105 | "projectType": "application", 106 | "architect": { 107 | "e2e": { 108 | "builder": "@angular-devkit/build-angular:protractor", 109 | "options": { 110 | "protractorConfig": "e2e/protractor.conf.js", 111 | "devServerTarget": "fruits-catalog:serve" 112 | }, 113 | "configurations": { 114 | "production": { 115 | "devServerTarget": "fruits-catalog:serve:production" 116 | } 117 | } 118 | }, 119 | "lint": { 120 | "builder": "@angular-devkit/build-angular:tslint", 121 | "options": { 122 | "tsConfig": "e2e/tsconfig.e2e.json", 123 | "exclude": [ 124 | "**/node_modules/**" 125 | ] 126 | } 127 | } 128 | } 129 | } 130 | }, 131 | "defaultProject": "fruits-catalog" 132 | } -------------------------------------------------------------------------------- /src/main/webapp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fruits-catalog", 3 | "version": "0.0.0", 4 | "author": "Laurent Broudoux", 5 | "license": "Apache-2.0", 6 | "scripts": { 7 | "ng": "ng", 8 | "start": "ng serve", 9 | "build": "ng build", 10 | "build-prod": "ng build --prod --build-optimizer", 11 | "test": "ng test", 12 | "lint": "ng lint", 13 | "e2e": "ng e2e" 14 | }, 15 | "private": true, 16 | "dependencies": { 17 | "@angular/animations": "^6.0.3", 18 | "@angular/common": "^6.0.3", 19 | "@angular/compiler": "^6.0.3", 20 | "@angular/core": "^6.0.3", 21 | "@angular/forms": "^6.0.3", 22 | "@angular/http": "^6.0.3", 23 | "@angular/platform-browser": "^6.0.3", 24 | "@angular/platform-browser-dynamic": "^6.0.3", 25 | "@angular/router": "^6.0.3", 26 | "bootstrap": "^3.3.7", 27 | "core-js": "^2.5.4", 28 | "jquery": "^3.3.1", 29 | "ngx-bootstrap": "^3.0.1", 30 | "ngx-highlightjs": "^2.1.4", 31 | "rxjs": "^6.0.0", 32 | "zone.js": "^0.8.26" 33 | }, 34 | "devDependencies": { 35 | "@angular/compiler-cli": "^6.0.3", 36 | "@angular-devkit/build-angular": "~0.6.8", 37 | "typescript": "~2.7.2", 38 | "@angular/cli": "~6.0.8", 39 | "@angular/language-service": "^6.0.3", 40 | "@types/jasmine": "~2.8.6", 41 | "@types/jasminewd2": "~2.0.3", 42 | "@types/node": "~8.9.4", 43 | "codelyzer": "~4.2.1", 44 | "jasmine-core": "~2.99.1", 45 | "jasmine-spec-reporter": "~4.2.1", 46 | "karma": "~1.7.1", 47 | "karma-chrome-launcher": "~2.2.0", 48 | "karma-coverage-istanbul-reporter": "~2.0.0", 49 | "karma-jasmine": "~1.1.1", 50 | "karma-jasmine-html-reporter": "^0.2.2", 51 | "protractor": "~5.3.0", 52 | "ts-node": "~5.0.1", 53 | "tslint": "~5.9.1" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/webapp/proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api": { 3 | "target": "http://localhost:8080", 4 | "secure": false 5 | } 6 | } -------------------------------------------------------------------------------- /src/main/webapp/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { HomePageComponent } from './pages/home/home.page'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: HomePageComponent 10 | } 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forRoot(routes, {useHash: true})], 15 | exports: [RouterModule], 16 | declarations: [] 17 | }) 18 | export class AppRoutingModule { 19 | } -------------------------------------------------------------------------------- /src/main/webapp/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/secured-fruits-catalog-k8s/a2274a8aa77ed40a50efa3587f35bab372245596/src/main/webapp/src/app/app.component.css -------------------------------------------------------------------------------- /src/main/webapp/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
-------------------------------------------------------------------------------- /src/main/webapp/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | describe('AppComponent', () => { 4 | beforeEach(async(() => { 5 | TestBed.configureTestingModule({ 6 | declarations: [ 7 | AppComponent 8 | ], 9 | }).compileComponents(); 10 | })); 11 | it('should create the app', async(() => { 12 | const fixture = TestBed.createComponent(AppComponent); 13 | const app = fixture.debugElement.componentInstance; 14 | expect(app).toBeTruthy(); 15 | })); 16 | it(`should have as title 'app'`, async(() => { 17 | const fixture = TestBed.createComponent(AppComponent); 18 | const app = fixture.debugElement.componentInstance; 19 | expect(app.title).toEqual('app'); 20 | })); 21 | it('should render title in a h1 tag', async(() => { 22 | const fixture = TestBed.createComponent(AppComponent); 23 | fixture.detectChanges(); 24 | const compiled = fixture.debugElement.nativeElement; 25 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to fruits-catalog!'); 26 | })); 27 | }); 28 | -------------------------------------------------------------------------------- /src/main/webapp/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent implements OnInit { 9 | title = 'Fruits Catalog'; 10 | 11 | constructor() { 12 | } 13 | 14 | ngOnInit() { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/webapp/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms'; 4 | 5 | import { HttpClientModule } from '@angular/common/http'; 6 | 7 | import { BsDropdownConfig, BsDropdownModule } from 'ngx-bootstrap/dropdown'; 8 | import { ModalModule } from 'ngx-bootstrap/modal'; 9 | import { TabsModule } from 'ngx-bootstrap/tabs'; 10 | import { TooltipModule } from 'ngx-bootstrap/tooltip'; 11 | import { HighlightModule } from 'ngx-highlightjs'; 12 | 13 | import { AppComponent } from './app.component'; 14 | import { AppRoutingModule } from './app-routing.module'; 15 | 16 | import { HomePageComponent } from './pages/home/home.page'; 17 | 18 | @NgModule({ 19 | imports: [ 20 | BrowserModule, FormsModule, BsDropdownModule.forRoot(), ModalModule.forRoot(), TabsModule.forRoot(), TooltipModule.forRoot(), 21 | HighlightModule.forRoot({ theme: 'github' }), AppRoutingModule, HttpClientModule 22 | ], 23 | declarations: [ 24 | AppComponent, HomePageComponent 25 | ], 26 | providers: [ 27 | ], 28 | entryComponents: [ 29 | ], 30 | bootstrap: [AppComponent] 31 | }) 32 | export class AppModule { } 33 | -------------------------------------------------------------------------------- /src/main/webapp/src/app/models/fruit.model.ts: -------------------------------------------------------------------------------- 1 | export class Fruit { 2 | id: string; 3 | name: string; 4 | origin: string; 5 | } -------------------------------------------------------------------------------- /src/main/webapp/src/app/pages/home/home.page.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/secured-fruits-catalog-k8s/a2274a8aa77ed40a50efa3587f35bab372245596/src/main/webapp/src/app/pages/home/home.page.css -------------------------------------------------------------------------------- /src/main/webapp/src/app/pages/home/home.page.html: -------------------------------------------------------------------------------- 1 |
2 |

Fruits Catalog

3 |

Browse and create fruits within your catalog

4 |
5 |
6 |
7 |
8 |
9 |
10 |

Fruits in catalog

11 |
12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
#NameOrigin
{{ fruit.id }}{{ fruit.name }}{{ fruit.origin }}
31 |
32 |
33 |
34 |
35 |
36 |
37 |

Add new fruit

38 |
39 |
40 |
41 |
42 | 43 |
44 | 45 |
46 |
47 |
48 | 49 |
50 | 51 |
52 |
53 |
54 |
55 |
56 |   57 | 58 |
59 |
60 |
61 |
62 |
63 |
64 |
-------------------------------------------------------------------------------- /src/main/webapp/src/app/pages/home/home.page.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HomePageComponent } from './home.page'; 4 | 5 | describe('HomePageComponent', () => { 6 | let component: HomePageComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ HomePageComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HomePageComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/main/webapp/src/app/pages/home/home.page.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { Fruit } from '../../models/fruit.model'; 4 | import { FruitsService } from '../../services/fruits.service'; 5 | 6 | @Component({ 7 | selector: 'home-page', 8 | templateUrl: './home.page.html', 9 | styleUrls: ['./home.page.css'] 10 | }) 11 | export class HomePageComponent implements OnInit { 12 | 13 | fruit: Fruit = new Fruit(); 14 | fruits: Fruit[]; 15 | 16 | constructor(private fruitsSvc: FruitsService) { } 17 | 18 | ngOnInit() { 19 | this.getFruits(); 20 | } 21 | 22 | getFruits(): void { 23 | this.fruitsSvc.getFruits().subscribe(results => this.fruits = results); 24 | } 25 | 26 | resetFruit(): void { 27 | this.fruit = new Fruit(); 28 | } 29 | addFruit(): void { 30 | this.fruitsSvc.createFruit(this.fruit).subscribe( 31 | { 32 | next: res => { 33 | this.fruit = new Fruit(); 34 | this.getFruits(); 35 | }, 36 | error: err => { 37 | alert("Fruit cannot be created (" + err.message + ")"); 38 | }, 39 | complete: () => console.log('Observer got a complete notification'), 40 | } 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/webapp/src/app/services/fruits.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | 5 | import { Fruit } from '../models/fruit.model'; 6 | 7 | @Injectable({ providedIn: 'root' }) 8 | export class FruitsService { 9 | 10 | private rootUrl: string = '/api'; 11 | 12 | constructor(private http: HttpClient) { } 13 | 14 | public getFruits(): Observable { 15 | return this.http.get(this.rootUrl + '/fruits'); 16 | } 17 | 18 | public createFruit(fruit: Fruit): Observable { 19 | return this.http.post(this.rootUrl + '/fruits', fruit); 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/webapp/src/assets/apple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/secured-fruits-catalog-k8s/a2274a8aa77ed40a50efa3587f35bab372245596/src/main/webapp/src/assets/apple.png -------------------------------------------------------------------------------- /src/main/webapp/src/assets/banana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/secured-fruits-catalog-k8s/a2274a8aa77ed40a50efa3587f35bab372245596/src/main/webapp/src/assets/banana.png -------------------------------------------------------------------------------- /src/main/webapp/src/assets/cherry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/secured-fruits-catalog-k8s/a2274a8aa77ed40a50efa3587f35bab372245596/src/main/webapp/src/assets/cherry.png -------------------------------------------------------------------------------- /src/main/webapp/src/assets/grape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/secured-fruits-catalog-k8s/a2274a8aa77ed40a50efa3587f35bab372245596/src/main/webapp/src/assets/grape.png -------------------------------------------------------------------------------- /src/main/webapp/src/assets/lemon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/secured-fruits-catalog-k8s/a2274a8aa77ed40a50efa3587f35bab372245596/src/main/webapp/src/assets/lemon.png -------------------------------------------------------------------------------- /src/main/webapp/src/assets/orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/secured-fruits-catalog-k8s/a2274a8aa77ed40a50efa3587f35bab372245596/src/main/webapp/src/assets/orange.png -------------------------------------------------------------------------------- /src/main/webapp/src/assets/pear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/secured-fruits-catalog-k8s/a2274a8aa77ed40a50efa3587f35bab372245596/src/main/webapp/src/assets/pear.png -------------------------------------------------------------------------------- /src/main/webapp/src/assets/pineapple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/secured-fruits-catalog-k8s/a2274a8aa77ed40a50efa3587f35bab372245596/src/main/webapp/src/assets/pineapple.png -------------------------------------------------------------------------------- /src/main/webapp/src/assets/strawberry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/secured-fruits-catalog-k8s/a2274a8aa77ed40a50efa3587f35bab372245596/src/main/webapp/src/assets/strawberry.png -------------------------------------------------------------------------------- /src/main/webapp/src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # For IE 9-11 support, please uncomment the last line of the file and adjust as needed 5 | > 0.5% 6 | last 2 versions 7 | Firefox ESR 8 | not dead 9 | # IE 9-11 -------------------------------------------------------------------------------- /src/main/webapp/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/main/webapp/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * In development mode, to ignore zone related error stack frames such as 11 | * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can 12 | * import the following file, but please comment it out in production mode 13 | * because it will have performance impact when throw error 14 | */ 15 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 16 | -------------------------------------------------------------------------------- /src/main/webapp/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lbroudoux/secured-fruits-catalog-k8s/a2274a8aa77ed40a50efa3587f35bab372245596/src/main/webapp/src/favicon.ico -------------------------------------------------------------------------------- /src/main/webapp/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Fruits-Catalog 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/webapp/src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; -------------------------------------------------------------------------------- /src/main/webapp/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | platformBrowserDynamic().bootstrapModule(AppModule) -------------------------------------------------------------------------------- /src/main/webapp/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/weak-map'; 35 | // import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** IE10 and IE11 requires the following for the Reflect API. */ 41 | // import 'core-js/es6/reflect'; 42 | 43 | 44 | /** Evergreen browsers require these. **/ 45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. 46 | import 'core-js/es7/reflect'; 47 | 48 | 49 | /** 50 | * Web Animations `@angular/platform-browser/animations` 51 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 52 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 53 | **/ 54 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 55 | 56 | /** 57 | * By default, zone.js will patch all possible macroTask and DomEvents 58 | * user can disable parts of macroTask/DomEvents patch by setting following flags 59 | */ 60 | 61 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 62 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 63 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 64 | 65 | /* 66 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 67 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 68 | */ 69 | // (window as any).__Zone_enable_cross_context_check = true; 70 | 71 | /*************************************************************************************************** 72 | * Zone JS is required by default for Angular itself. 73 | */ 74 | import 'zone.js/dist/zone'; // Included with Angular CLI. 75 | 76 | 77 | 78 | /*************************************************************************************************** 79 | * APPLICATION IMPORTS 80 | */ 81 | -------------------------------------------------------------------------------- /src/main/webapp/src/styles.css: -------------------------------------------------------------------------------- 1 | .breadcrumb-bar { 2 | height: 37px; 3 | border-bottom: 1px solid #ccc; 4 | margin-left: -20px; 5 | margin-right: -20px; 6 | margin-top: -20px; 7 | } 8 | .breadcrumb { 9 | padding: 8px 15px; 10 | margin-bottom: 20px; 11 | list-style: none; 12 | background-color: transparent; 13 | border-radius: 1px; 14 | } 15 | 16 | small { 17 | font-weight: 400; 18 | font-size: 0.9em; 19 | line-height: 1; 20 | color: #9c9c9c; 21 | } 22 | dl { 23 | margin-bottom: 2em; 24 | } 25 | dd, dt { 26 | line-height: 2; 27 | } 28 | .dl-horizontal.left dt { 29 | text-align: left; 30 | } 31 | 32 | [role=button] { 33 | cursor: pointer; 34 | } 35 | 36 | .control-group { 37 | padding-top: 0.8em; 38 | padding-bottom: 0.8em; 39 | } 40 | .form-actions { 41 | padding-top: 2em; 42 | } 43 | .form-actions>button { 44 | margin-left: 5px; 45 | } 46 | 47 | span.card-pf-footer-text { 48 | font-size: 0.7em !important; 49 | vertical-align: text-top; 50 | } 51 | 52 | .pf-icon-sm { 53 | line-height: 26px; 54 | border-radius: 50%; 55 | font-size: 1.4em; 56 | height: 30px; 57 | line-height: 30px; 58 | width: 30px; 59 | } 60 | .rest-icon-sm { 61 | border: 3px solid #89bf04 !important; 62 | } 63 | .soap-icon-sm { 64 | border: 3px solid #39a5dc !important; 65 | } 66 | .generic-icon-sm { 67 | border: 3px solid #9c27b0 !important; 68 | } 69 | 70 | .section-label { 71 | text-transform: uppercase; 72 | color: rgb(117, 117, 117); 73 | margin-top: 2em; 74 | margin-bottom: 1em; 75 | } 76 | 77 | .no-margin-top { 78 | margin-top: 0.2em !important; 79 | } 80 | 81 | .subsection-label { 82 | color: rgb(117, 117, 117); 83 | border-bottom: 1px solid rgb(220, 220, 220); 84 | padding-bottom: 10px; 85 | margin-top: 1em; 86 | } 87 | 88 | .learn-more-inline { 89 | display: inline; 90 | font-size: 11px; 91 | font-weight: 400; 92 | margin-left: 5px; 93 | } 94 | 95 | .bar { 96 | display: inline-block; 97 | border: 1px; 98 | border-bottom: 1px solid #CCC; 99 | } 100 | .bar-success { 101 | background: #7BB33D; 102 | } 103 | .bar-success:hover { 104 | background: #73A839; 105 | } 106 | .bar-failure { 107 | background: #dd1f26; 108 | } 109 | .bar-failure:hover { 110 | background: #d41e24; 111 | } 112 | 113 | .nav-tabs>li.second-tab:first-child>a { 114 | --padding-left: 0; 115 | } 116 | 117 | .nav-tabs>li.second-tab>a { 118 | border: 0; 119 | line-height: 1; 120 | margin-right: 0; 121 | padding-bottom: 10px; 122 | padding-top: 10px; 123 | } 124 | .nav-tabs>li.second-tab.active>a, .nav-tabs>li.second-tab.active>a:active, .nav-tabs>li.second-tab.active>a:focus, .nav-tabs>li.second-tab.active>a:hover { 125 | background-color: transparent; 126 | border: 0!important; 127 | color: #0088ce; 128 | } 129 | 130 | .nav-tabs>li.second-tab.active>a:before, .nav-tabs>li.second-tab.active>a:focus:before, .nav-tabs>li.second-tab.active>a:hover:before { 131 | background: #0088ce; 132 | bottom: -1px; 133 | content: ""; 134 | display: block; 135 | height: 2px; 136 | left: 15px; 137 | position: absolute; 138 | right: 15px; 139 | } 140 | .nav-tabs>li.second-tab>a:active:before, .nav-tabs>li.second-tab>a:focus:before, .nav-tabs>li.second-tab>a:hover:before { 141 | background: #bbb; 142 | bottom: -1px; 143 | content: ""; 144 | display: block; 145 | height: 2px; 146 | left: 15px; 147 | position: absolute; 148 | right: 15px; 149 | } -------------------------------------------------------------------------------- /src/main/webapp/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /src/main/webapp/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "es2015", 6 | "types": [], 7 | "paths": { 8 | "dragula": ["./node_modules/dragula/dist/dragula.min.js"] // https://github.com/valor-software/ng2-dragula/issues/849#issuecomment-385518621 9 | } 10 | }, 11 | "exclude": [ 12 | "src/test.ts", 13 | "**/*.spec.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/main/webapp/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "module": "commonjs", 6 | "types": [ 7 | "jasmine", 8 | "node" 9 | ] 10 | }, 11 | "files": [ 12 | "test.ts", 13 | "polyfills.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/main/webapp/src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/webapp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "lib": [ 16 | "es2017", 17 | "dom" 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/webapp/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "import-spacing": true, 24 | "indent": [ 25 | true, 26 | "spaces" 27 | ], 28 | "interface-over-type-literal": true, 29 | "label-position": true, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-arg": true, 47 | "no-bitwise": true, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-construct": true, 57 | "no-debugger": true, 58 | "no-duplicate-super": true, 59 | "no-empty": false, 60 | "no-empty-interface": true, 61 | "no-eval": true, 62 | "no-inferrable-types": [ 63 | true, 64 | "ignore-params" 65 | ], 66 | "no-misused-new": true, 67 | "no-non-null-assertion": true, 68 | "no-shadowed-variable": true, 69 | "no-string-literal": false, 70 | "no-string-throw": true, 71 | "no-switch-case-fall-through": true, 72 | "no-trailing-whitespace": true, 73 | "no-unnecessary-initializer": true, 74 | "no-unused-expression": true, 75 | "no-use-before-declare": true, 76 | "no-var-keyword": true, 77 | "object-literal-sort-keys": false, 78 | "one-line": [ 79 | true, 80 | "check-open-brace", 81 | "check-catch", 82 | "check-else", 83 | "check-whitespace" 84 | ], 85 | "prefer-const": true, 86 | "quotemark": [ 87 | true, 88 | "single" 89 | ], 90 | "radix": true, 91 | "semicolon": [ 92 | true, 93 | "always" 94 | ], 95 | "triple-equals": [ 96 | true, 97 | "allow-null-check" 98 | ], 99 | "typedef-whitespace": [ 100 | true, 101 | { 102 | "call-signature": "nospace", 103 | "index-signature": "nospace", 104 | "parameter": "nospace", 105 | "property-declaration": "nospace", 106 | "variable-declaration": "nospace" 107 | } 108 | ], 109 | "unified-signatures": true, 110 | "variable-name": false, 111 | "whitespace": [ 112 | true, 113 | "check-branch", 114 | "check-decl", 115 | "check-operator", 116 | "check-separator", 117 | "check-type" 118 | ], 119 | "no-output-on-prefix": true, 120 | "use-input-property-decorator": true, 121 | "use-output-property-decorator": true, 122 | "use-host-property-decorator": true, 123 | "no-input-rename": true, 124 | "no-output-rename": true, 125 | "use-life-cycle-interface": true, 126 | "use-pipe-transform-interface": true, 127 | "component-class-suffix": true, 128 | "directive-class-suffix": true 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | # Unit tests does not need config map 2 | # spring.application.name=${project.artifactId} 3 | greeting: 4 | message: "Hello, %s!" 5 | -------------------------------------------------------------------------------- /src/test/resources/arquillian.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | true 5 | false 6 | file://${basedir}/target/test-classes/test-configmap.yml 7 | microservice-b 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/test/resources/test-configmap.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: List 3 | items: 4 | # Setup the permission required to read the config map 5 | - apiVersion: v1 6 | kind: RoleBinding 7 | metadata: 8 | name: role-view-default 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | roleRef: 13 | name: view 14 | 15 | - apiVersion: v1 16 | kind: RoleBinding 17 | metadata: 18 | name: role-view-jenkins 19 | subjects: 20 | - kind: ServiceAccount 21 | name: jenkins 22 | roleRef: 23 | name: view 24 | 25 | # ConfigMap to be created to pass application.yml content to Spring Boot 26 | - apiVersion: v1 27 | kind: ConfigMap 28 | metadata: 29 | name: app-config 30 | data: 31 | application.yml: | 32 | # This properties file should be used to initialise a ConfigMap 33 | greeting: 34 | message: "Hello %s from a ConfigMap!" 35 | --------------------------------------------------------------------------------