├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── database.json ├── database ├── database_create_employees.go ├── database_delete_employee.go ├── database_get_employees.go ├── database_manager.go ├── database_test.go ├── database_update_employees.go ├── database_validate.go └── redis_cache_manger.go ├── deploy_kubernetes ├── go_cache_app │ ├── backup.txt │ ├── go-cache-poc-app.yaml │ ├── go-cache-poc-pvc.yaml │ └── go-cache-poc-svc.yaml ├── kafka │ ├── deploy │ │ ├── kafka-manager-deployment.yml │ │ ├── kafka-manager-service.yml │ │ ├── kafka-service.yml │ │ ├── kafka-statefulset.yml │ │ ├── namespace.yml │ │ ├── zookeeper-deployment.yml │ │ └── zookeeper-service.yml │ ├── kafka-repcon.yaml │ ├── kafka-service.yaml │ ├── zookeeper-deployment.yaml │ └── zookeeper-service.yaml ├── mongodb │ ├── mongodb-service.yaml │ └── mongodb-statefulset.yaml └── redis │ ├── redis-deployment.yaml │ └── redis-service.yaml ├── docker-compose.yml ├── handlers ├── handler_create_employee.go ├── handler_delete_employee.go ├── handler_get_employees.go ├── handler_manager.go ├── handler_response_validator.go └── handler_update_employee.go ├── kafka ├── kafka_consumer.go └── kafka_producer.go ├── main.go └── swagger.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | vendor.orig 3 | configs 4 | go-cache-kubernetes 5 | kompose 6 | dump.rdb 7 | go.sum 8 | go.mod -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:latest 2 | 3 | RUN mkdir -p $GOPATH/src/github.com/go-cache-kubernetes 4 | 5 | WORKDIR $GOPATH/src/github.com/go-cache-kubernetes 6 | 7 | COPY . $GOPATH/src/github.com/go-cache-kubernetes 8 | 9 | RUN go build -a -installsuffix cgo -o go-cache-kubernetes . 10 | 11 | CMD ["chmod +x go-cache-kubernetes"] 12 | 13 | CMD ["./go-cache-kubernetes"] 14 | 15 | EXPOSE 5000 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Deeptiman Pattnaik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-cache-kubernetes 2 |

3 | GitHub last commit 4 | GitHub language count 5 | GitHub top language 6 |

7 | The web application is a Data Caching service designed and implemented using microservices architecture. The cloud deployment environments are used Kubernetes, Docker, and written in Go programming language. The application also uses a MongoDB as NoSQL database with Redis in-memory database for the caching services. 8 | 9 | ## Features 10 | 11 | - **MongoDB** is implemented to perform several database operations. The installation can be done using the go dependency module. 12 | 13 | go get go.mongodb.org/mongo-driver/mongo 14 | https://github.com/mongodb/mongo-go-driver 15 | 16 | - **Redis Cache** is implemented to integrate the data caching in the application. So, the go-redis will cache the second GET request while reading the user details. 17 | 18 | go get github.com/go-redis/redis/v8 19 | https://github.com/go-redis/redis 20 | 21 | - **Kafka Message Broker**: The confluent-kafka-go is used as a Go client library for Kafka message broker. The library will provide **Producer** and **Consumer** architecture to stream messages to the user for a subscribed topic. So, there will be two REST APIs that the user can use for Producing the messages reading from MongoDB and Consuming or Reading messages from the message broker. 22 | 23 | go get github.com/confluentinc/confluent-kafka-go/kafka 24 | https://github.com/confluentinc/confluent-kafka-go 25 | 26 | Note: It's recommended to install **confluent-kafka-go v1.4.0**, as the **librdkafka** will come with the bundle and no need to install separately. 27 | 28 | ## Kubernetes tools 29 | Kubernetes provides several tools that can be useful to setup Kubernetes in the local environment. 30 | 31 | - **minikube** tool will run a single-node Kubernetes cluster running inside a Virtual Machine. Virtualization has to be supported in the computer and Hypervisor needed to be enabled. 32 | 33 | **Installation** 34 | follows with the Hypervisor installation and [Hyperkit](https://minikube.sigs.k8s.io/docs/drivers/hyperkit/) is the recommended virtualization toolkit. 35 | 36 | $ sudo install minikube 37 | 38 | $ minikube start 39 | https://kubernetes.io/docs/setup/learning-environment/minikube/ 40 | 41 | 42 | 43 | 44 | - **kubectl** command-line tool will work to manage a Kubernetes cluster. The tool will be used to deploy, create, analyze, inspect pods that are running under a Kubernetes cluster. 45 | 46 | 47 | **Installation** 48 | 49 | ```curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl" ``` 50 | 51 | https://kubernetes.io/docs/tasks/tools/install-kubectl/ 52 | 53 | 54 | ## Build the Docker images 55 | 56 | The application uses Docker for container-based development. The docker image gets published as a public repository at Docker Hub. 57 | 58 | - **Build the image** 59 | 60 | $ docker build -t go-cache-kubernetes-v1 . 61 | 62 | - **Tag the image** 63 | 64 | $ docker tag go-cache-kubernetes-v1 deeptiman1991/go-cache-kubernetes-v1:1.0.0 65 | 66 | - **Login to docker hub** 67 | 68 | $ docker login 69 | 70 | Type Username and Password to complete the authentication 71 | 72 | - **Push the image to docker hub** 73 | 74 | $ docker push deeptiman1991/go-cache-kubernetes-v1:1.0.0 75 | 76 | ## Kubernetes Deployment 77 | There will be several deployments, services that need to be running in the cluster as a Pod. The creation of a Pod requires a YAML file that will specify the kind, spec, containerPort, metadata, volume, and more. So, these parameters will be used to provide resources to the Kubernetes cluster. 78 | 79 | **Start minikube** to begin the deployment process start the minikube 80 | 81 | $ minikube start 82 | 83 |

Kubernetes Secret Management

84 | The application will be using few MongoDB credentials for database connection. So the username and password will be secure using HashiCorp Vault as static secrets. 85 |

86 |

More details explanation on HashiCorp Vault, please check my Medium article : Secrets in Kubernetes and HashiCorp Vault

87 |
88 |

Implement the Vault Envs in the pod deployment

89 | 90 | spec: 91 | serviceAccountName: vault 92 | containers: 93 | - name: go-cache-kubernetes-container-poc 94 | image: deeptiman1991/go-cache-kubernetes-v1:1.0.0 95 | env: 96 | - name: VAULT_ADDR 97 | value: "http://vault:8200" 98 | - name: JWT_PATH 99 | value: "/var/run/secrets/kubernetes.io/serviceaccount/token" 100 | - name: SERVICE_PORT 101 | value: "8080" 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 |
YAMLgo-cache-poc-app.yaml
110 |

111 |

So, now SECRET_USERNAME & SECRET_PASSWORD environment variables can be used to connect to the MongoDB database from the application.

112 | 113 |

Deploy PersistentVolumeClaim

114 | 115 | This will allocate a volume of 1GB storage in the cluster 116 |
117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 |
Namego-cache-poc-pvc
KindPersistentVolumeClaim
YAMLgo-cache-poc-pvc.yaml
133 | 134 | $ kubectl apply -f go-cache-poc-pvc.yaml 135 | 136 |

Deploy Go Web App

137 | 138 | This will load the web app Docker image in the cluster. 139 |
140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 |
Namego-cache-poc
KindDeployment
YAMLgo-cache-poc-app.yaml
156 | 157 | $ kubectl apply -f go-cache-poc-app.yaml 158 | 159 | **Verify** 160 | 161 | $ kubectl get deployments 162 | NAME READY UP-TO-DATE AVAILABLE AGE 163 | go-cache-kubernetes-app-poc 1/1 1 1 14s 164 | 165 | There is only one pod is running under this deployment. 166 | 167 |

Deploy Go Web App Service

168 | 169 | This service will create an external endpoint using a LoadBalancer. 170 |
171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 |
Namego-cache-poc-service
KindService
YAMLgo-cache-poc-svc.yaml
187 | 188 | 189 | $ kubectl apply -f go-cache-poc-svc.yaml 190 | 191 | **Verify** 192 | 193 | $ kubectl get services 194 | 195 |

Deploying MongoDB ReplicaSet as a Kubernetes StatefulSet

196 | 197 | 198 |

Kubernetes provides a feature that will allow us to create a stateful application in the cluster. There will be a storage class and services running under the cluster that will allow the databases to connect with services and store records in their persistent database.

199 |
200 |

More details explanation on MongoDB StatefulSet, please check my Medium article : MongoDB StatefulSet in Kubernetes

201 |
202 | MongoDB service will create the Mongo services in the cluster. 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 |
Namemongodb-service
KindService
YAMLmongodb-service.yaml
219 | 220 | $ kubectl apply -f mongodb-service.yaml 221 | 222 | **MongoDB StatefulSet** will create the StatefulSet app in the cluster. 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 |
Namemongod
KindStatefulSet
YAMLmongodb-statefulset.yaml
239 | 240 | $ kubectl apply -f mongodb-statefulset.yaml 241 | 242 | #### Define the Administrator 243 | There will be three mongo containers in the cluster. We need to connect to anyone of them to define the administrator. 244 | Command to exec 245 | 246 | $ kubectl exec -it mongod-0 -c mongod-container bash 247 | -it: mongo app name 248 | -c: mongo container name 249 | Bash 250 | 251 | $ hostname -f 252 | 253 | mongod-0.mongodb-service.default.svc.cluster.local 254 | 255 | Mongo Shell 256 | 257 | $ mongo 258 | 259 | Type to the following query to generate the replica set 260 | 261 | > rs.initiate({_id: "MainRepSet", version: 1, members: [ 262 | { _id: 0, host : "mongod-0.mongodb-service.default.svc.cluster.local:27017" } 263 | ]}); 264 | 265 | then verify 266 | 267 | > rs.status(); 268 | 269 | Now create the Admin user 270 | 271 | > db.getSiblingDB("admin").createUser({ 272 | user : "admin", 273 | pwd : "admin123", 274 | roles: [ { role: "root", db: "admin" } ] 275 | }); 276 | 277 | So, now the MongoDB is complete setup with ReplicaSet and with an Administrator for the database. 278 | 279 |

Deploy Redis in Kubernetes

280 | There will be several steps to follow for deploying Redis into the Kubernete cluster. 281 |

282 |

Download Docker images for Redis

283 |

284 | $ docker run -p 6379:6379 redislabs/redismod 285 |

286 |

Redis Deployment

287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 |
Nameredismod
KindDeployment
YAMLredis-deployment.yaml
303 |

$ kubectl apply -f redis-deployment.yaml

304 | 305 |

Redis Service

306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 |
Nameredis-service
KindService
YAMLredis-service.yaml
322 |

$ kubectl apply -f redis-service.yaml

323 |

Deploy the redismod image

324 |

325 | $ kubectl run redismod --image=redislabs/redismod --port=6379 326 |

327 |

Expose the deployment

328 |

329 | $ kubectl expose deployment redismod --type=NodePort 330 |

331 |

Now, check for the Redis Connection

332 |

333 | $ redis-cli -u $(minikube service --format "redis://{{.IP}}:{{.Port}}" --url redismod) 334 |

335 | You must be getting an ip address with a port that can be used as a connection string for Redis 336 | 337 |

Deploy Kafka in Kubernetes

338 | There will be a deployment of ZooKeeper, Kafka Service, and running kafka/zookeeper server script. Please install Apache Kafka in your local machine and gcloud. 339 |
340 |

Zookeeper

341 | There will be deployment and service similar to the other Pods running in the cluster. 342 |
zookeeper-deployment
343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 |
Namezookeeper-app
KindDeployment
YAMLzookeeper-deployment.yaml
359 | 360 | $ kubectl apply -f zookeeper-deployment.yaml 361 | 362 |
zookeeper-service
363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 |
Namezookeeper-service
KindService
YAMLzookeeper-service.yaml
379 | 380 | $ kubectl apply -f zookeeper-service.yaml 381 | 382 |

Kafka

383 |
kafka-service
384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 |
Namekafka-service
KindService
YAMLkafka-service.yaml
400 | 401 | 402 | 403 | $ kubectl apply -f kafka-service.yaml 404 | 405 |
kafka-replication-controller
406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 |
Namekafka-repcon
KindDeployment
YAMLkafka-repcon.yaml
422 | 423 | 424 | $ kubectl apply -f kafka-repcon.yaml 425 | 426 | 427 |

Start zookeeper/kafka server

428 |

zookeeper server

429 | 430 | $ cd kafka/ 431 | $~/kafka/ bin/zookeeper-server-start.sh config/zookeeper.properties 432 | 433 |

kafka server

434 | 435 | $ cd kafka/ 436 | $~/kafka/ bin/kafka-server-start.sh config/server.properties 437 | 438 | 439 | ## Troubleshoot with kubectl 440 | 441 | The kubectl is a very handy tool while troubleshooting application into the Kubernetes. 442 | 443 | **Few useful commands** 444 |
    445 |
  1. kubectl get pods
  2. 446 |
  3. kubectl describe pods
  4. 447 |
  5. kubectl logs
  6. 448 |
  7. kubectl exec -ti --bash
  8. 449 |
450 | 451 | ## Swagger API documentation 452 | 453 | The go-swagger toolkit is integrated for the REST APIs documentation. The API doc can be accessible via http://localhost:5000/docs 454 | 455 |

https://github.com/go-swagger/go-swagger

456 |

swagger.yaml

457 | 458 | ## More Info 459 | 466 | 467 |

License

468 |

This project is licensed under the MIT License

469 | -------------------------------------------------------------------------------- /database.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 100, 4 | "name": "Employee1", 5 | "email": "employee1@gmail.com", 6 | "company": "company1", 7 | "occupation": "engineer1", 8 | "salary": "$10,000" 9 | }, 10 | { 11 | "id": 101, 12 | "name": "Employee2", 13 | "email": "employee2@gmail.com", 14 | "company": "company2", 15 | "occupation": "engineer2", 16 | "salary": "$20,000" 17 | }, 18 | { 19 | "id": 102, 20 | "name": "Employee2", 21 | "email": "employee2@gmail.com", 22 | "company": "company2", 23 | "occupation": "engineer2", 24 | "salary": "$30,000" 25 | }, 26 | { 27 | "id": 103, 28 | "name": "Employee3", 29 | "email": "employee3@gmail.com", 30 | "company": "company3", 31 | "occupation": "engineer3", 32 | "salary": "$40,000" 33 | }, 34 | { 35 | "id": 104, 36 | "name": "Employee4", 37 | "email": "employee4@gmail.com", 38 | "company": "company4", 39 | "occupation": "engineer4", 40 | "salary": "$40,000" 41 | }, 42 | { 43 | "id": 105, 44 | "name": "Employee5", 45 | "email": "employee5@gmail.com", 46 | "company": "company5", 47 | "occupation": "engineer5", 48 | "salary": "$50,000" 49 | }, 50 | { 51 | "id": 106, 52 | "name": "Employee6", 53 | "email": "employee6@gmail.com", 54 | "company": "company6", 55 | "occupation": "engineer6", 56 | "salary": "$60,000" 57 | }, 58 | { 59 | "id": 107, 60 | "name": "Employee7", 61 | "email": "employee7@gmail.com", 62 | "company": "company7", 63 | "occupation": "engineer7", 64 | "salary": "$70,000" 65 | }, 66 | { 67 | "id": 108, 68 | "name": "Employee8", 69 | "email": "employee8@gmail.com", 70 | "company": "company8", 71 | "occupation": "engineer8", 72 | "salary": "$80,000" 73 | }, 74 | { 75 | "id": 109, 76 | "name": "Employee9", 77 | "email": "employee9@gmail.com", 78 | "company": "company9", 79 | "occupation": "engineer9", 80 | "salary": "$90,000" 81 | }, 82 | { 83 | "id": 110, 84 | "name": "Employee10", 85 | "email": "employee10@gmail.com", 86 | "company": "company10", 87 | "occupation": "engineer10", 88 | "salary": "$100,000" 89 | }, 90 | { 91 | "id": 111, 92 | "name": "Employee11", 93 | "email": "employee11@gmail.com", 94 | "company": "company11", 95 | "occupation": "engineer11", 96 | "salary": "$110,000" 97 | }, 98 | { 99 | "id": 112, 100 | "name": "Employee12", 101 | "email": "employee12@gmail.com", 102 | "company": "company12", 103 | "occupation": "engineer12", 104 | "salary": "$120,000" 105 | }, 106 | { 107 | "id": 113, 108 | "name": "Employee13", 109 | "email": "employee13@gmail.com", 110 | "company": "company13", 111 | "occupation": "engineer13", 112 | "salary": "$130,000" 113 | }, 114 | { 115 | "id": 114, 116 | "name": "Employee14", 117 | "email": "employee14@gmail.com", 118 | "company": "company14", 119 | "occupation": "engineer14", 120 | "salary": "$140,000" 121 | }, 122 | { 123 | "id": 115, 124 | "name": "Employee15", 125 | "email": "employee15@gmail.com", 126 | "company": "company15", 127 | "occupation": "engineer15", 128 | "salary": "$150,000" 129 | }, 130 | { 131 | "id": 116, 132 | "name": "Employee16", 133 | "email": "employee16@gmail.com", 134 | "company": "company16", 135 | "occupation": "engineer16", 136 | "salary": "$160,000" 137 | }, 138 | { 139 | "id": 117, 140 | "name": "Employee17", 141 | "email": "employee17@gmail.com", 142 | "company": "company17", 143 | "occupation": "engineer17", 144 | "salary": "$170,000" 145 | }, 146 | { 147 | "id": 118, 148 | "name": "Employee18", 149 | "email": "employee18@gmail.com", 150 | "company": "company18", 151 | "occupation": "engineer18", 152 | "salary": "$180,000" 153 | }, 154 | { 155 | "id": 119, 156 | "name": "Employee19", 157 | "email": "employee19@gmail.com", 158 | "company": "company19", 159 | "occupation": "engineer19", 160 | "salary": "$190,000" 161 | }, 162 | { 163 | "id": 120, 164 | "name": "Employee20", 165 | "email": "employee20@gmail.com", 166 | "company": "company20", 167 | "occupation": "engineer20", 168 | "salary": "$200,000" 169 | } 170 | ] -------------------------------------------------------------------------------- /database/database_create_employees.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | // CreateEmployee method 8 | func (e *EmployeeDB) CreateEmployee(employee *Employee) error { 9 | 10 | collection, err := e.GetCollection() 11 | if err != nil { 12 | e.log.Error("Unable to create collection", "error", err.Error()) 13 | return err 14 | } 15 | 16 | //check wheather employee exists in the db 17 | _, err = e.GetEmployeeByID(employee.ID) 18 | 19 | if err == ErrEmployeeNotFound { 20 | insertResult, err := collection.InsertOne(context.TODO(), employee) 21 | if err != nil { 22 | e.log.Error("Failed to create employee", "error", err.Error()) 23 | return err 24 | } 25 | e.log.Info("Employee Created", "success", insertResult) 26 | } else { 27 | return EmployeeAlreadyExists 28 | } 29 | 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /database/database_delete_employee.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "context" 5 | 6 | "go.mongodb.org/mongo-driver/bson" 7 | ) 8 | 9 | //DeleteEmployeeByID 10 | func (e *EmployeeDB) DeleteEmployeeByID(id int) error { 11 | 12 | collection, err := e.GetCollection() 13 | if err != nil { 14 | e.log.Error("Unable to create collection", "error", err.Error()) 15 | return err 16 | } 17 | 18 | _, err = collection.DeleteMany( 19 | context.TODO(), 20 | bson.M{MONGODB_COLLECTION_ID: id}) 21 | 22 | if err != nil { 23 | e.log.Error("Unable to delete employee", "error", err.Error()) 24 | return err 25 | } 26 | 27 | key := getKey(id) 28 | err = e.redisCache.Del(key) 29 | if err != nil { 30 | e.log.Error("Unable to delete from redis cache", "error", err.Error()) 31 | return err 32 | } 33 | 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /database/database_get_employees.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "context" 5 | 6 | "go.mongodb.org/mongo-driver/bson" 7 | "go.mongodb.org/mongo-driver/mongo/options" 8 | ) 9 | 10 | // GetAllEmployees method 11 | func (e *EmployeeDB) GetAllEmployees() ([]*Employee, error) { 12 | 13 | collection, err := e.GetCollection() 14 | if err != nil { 15 | e.log.Error("Unable to create collection", "error", err.Error()) 16 | return nil, err 17 | } 18 | 19 | findOptions := options.Find() 20 | findOptions.SetLimit(10) //currently supporting fetching 10 employee records 21 | 22 | records, err := collection.Find(context.TODO(), bson.D{{}}, findOptions) 23 | if err != nil { 24 | e.log.Error("Unable to fetch employee records", "error", err.Error()) 25 | return nil, err 26 | } 27 | 28 | var results []*Employee 29 | for records.Next(context.TODO()) { 30 | var employee Employee 31 | err := records.Decode(&employee) 32 | if err != nil { 33 | e.log.Error("Unable to read employee records", "error", err.Error()) 34 | return nil, err 35 | } 36 | results = append(results, &employee) 37 | } 38 | 39 | defer records.Close(context.TODO()) 40 | 41 | return results, nil 42 | } 43 | 44 | //GetEmployeeByID method 45 | func (e *EmployeeDB) GetEmployeeByID(id int) (*Employee, error) { 46 | 47 | var employee *Employee 48 | 49 | key := getKey(id) 50 | 51 | collection, err := e.GetCollection() 52 | if err != nil { 53 | e.log.Error("Unable to create collection", "error", err.Error()) 54 | return nil, err 55 | } 56 | 57 | filter := bson.D{{MONGODB_COLLECTION_ID, id}} 58 | cacheEmployee, err := e.redisCache.Get(key) 59 | 60 | if cacheEmployee == nil { 61 | e.log.Info("MongoDB ", "Employee", cacheEmployee) 62 | err = collection.FindOne(context.TODO(), filter).Decode(&employee) 63 | if err != nil { 64 | return nil, ErrEmployeeNotFound 65 | } 66 | e.redisCache.Set(key, employee) 67 | } else { 68 | e.log.Info("Redis Cache", "Employee", cacheEmployee) 69 | employee = cacheEmployee 70 | } 71 | 72 | if employee == nil { 73 | return nil, ErrEmployeeNotFound 74 | } 75 | 76 | return employee, nil 77 | } 78 | -------------------------------------------------------------------------------- /database/database_manager.go: -------------------------------------------------------------------------------- 1 | //Package classification of Employee API 2 | // 3 | //Documentation for Employee API 4 | // 5 | // 6 | // Schemes: http 7 | // BasePath: /api 8 | // Version: 1.0.0 9 | // 10 | // 11 | // Consumes: 12 | // - application/json 13 | // 14 | // Produces: 15 | // - application/json 16 | // 17 | //swagger:meta 18 | package database 19 | 20 | import ( 21 | "context" 22 | "encoding/json" 23 | "fmt" 24 | "io/ioutil" 25 | "net/http" 26 | "os" 27 | "strings" 28 | "time" 29 | 30 | "github.com/hashicorp/go-hclog" 31 | "go.mongodb.org/mongo-driver/mongo" 32 | "go.mongodb.org/mongo-driver/mongo/options" 33 | ) 34 | 35 | const ( 36 | VAULT_AUTH_LOGIN = "auth/kubernetes/login" 37 | VAULT_SECRET_DATA = "secret/data/webapp/config" 38 | VAULT_ROLE = "webapp" 39 | 40 | MONGODB_CONNECTION_STRING = "mongodb://mongod-0.mongodb-service.default.svc.cluster.local" 41 | MONGODB_DATABASE = "records" 42 | MONGODB_COLLECTION = "employees" 43 | 44 | MONGODB_COLLECTION_ID = "id" 45 | MONGODB_COLLECTION_NAME = "name" 46 | MONGODB_COLLECTION_EMAIL = "email" 47 | MONGODB_COLLECTION_COMPANY = "company" 48 | MONGODB_COLLECTION_OCCUPATION = "occupation" 49 | MONGODB_COLLECTION_SALARY = "salary" 50 | ) 51 | 52 | // Employee Schema structure 53 | type Employee struct { 54 | ID int `json:"id" validate:"required,gt=99"` 55 | Name string `json:"name" validate:"required"` 56 | Email string `json:"email" validate:"required,email"` 57 | Company string `json:"company" validate:"required"` 58 | Occupation string `json:"occupation" validate:"required"` 59 | Salary string `json:"salary" validate:"required"` 60 | } 61 | 62 | type EmployeeDB struct { 63 | redisCache *RedisCache 64 | log hclog.Logger 65 | } 66 | 67 | type Vault struct { 68 | Auth Auth 69 | } 70 | type Auth struct { 71 | Client_Token string 72 | } 73 | 74 | type VaultData struct { 75 | Data Data 76 | } 77 | 78 | type Data struct { 79 | Data SecretData 80 | } 81 | 82 | type SecretData struct { 83 | Username string 84 | Password string 85 | } 86 | 87 | var ErrEmployeeNotFound = fmt.Errorf("Employee not found") 88 | var EmployeeAlreadyExists = fmt.Errorf("Employee already exists") 89 | 90 | func InitializeDBManager(log hclog.Logger) *EmployeeDB { 91 | redisCache, _ := InitializeCacheClient() 92 | return &EmployeeDB{redisCache, log} 93 | } 94 | 95 | //GetMongoCredFromVault 96 | func (e *EmployeeDB) GetMongoCredFromVault() (*SecretData, error) { 97 | 98 | content_type := "application/json" 99 | VAULT_URL := os.Getenv("VAULT_ADDR") + "/v1/" 100 | JWT_TOKEN, err := ioutil.ReadFile(os.Getenv("JWT_PATH")) 101 | if err != nil { 102 | e.log.Error("Unable to read JWT File", "error", err.Error()) 103 | return nil, err 104 | } 105 | 106 | e.log.Info("VAULT_URL", "Prod", VAULT_URL) 107 | e.log.Info("Vault", "Auth URL", VAULT_URL+VAULT_AUTH_LOGIN) 108 | //Vault Request - 1: [Retrieve Client token from Vault] 109 | requestBody1 := strings.NewReader(` 110 | { 111 | "role": "` + VAULT_ROLE + `", 112 | "jwt": "` + string(JWT_TOKEN) + `" 113 | } 114 | `) 115 | res1, err := http.NewRequest("POST", VAULT_URL+VAULT_AUTH_LOGIN, requestBody1) 116 | if err != nil { 117 | e.log.Error("Unable to set Request", "error", err.Error()) 118 | return nil, err 119 | } 120 | res1.Header.Set("Content-Type", content_type) 121 | 122 | resp1, err := http.DefaultClient.Do(res1) 123 | if err != nil { 124 | e.log.Error("Unable to fetch Vault Client Token", "error", err.Error()) 125 | } 126 | defer resp1.Body.Close() 127 | data, _ := ioutil.ReadAll(resp1.Body) 128 | 129 | var vault Vault 130 | json.Unmarshal(data, &vault) 131 | 132 | e.log.Info("Vault", "Auth Response", string(data)) 133 | e.log.Info("Vault", "Secret URL", VAULT_URL+VAULT_SECRET_DATA) 134 | //Vault Request - 2: [Retrieve Secret Data from Vault] 135 | res2, err := http.NewRequest("GET", VAULT_URL+VAULT_SECRET_DATA, nil) 136 | if err != nil { 137 | e.log.Error("Unable to make Get request for Vault Data", "error", err.Error()) 138 | return nil, err 139 | } 140 | res2.Header.Set("Content-Type", content_type) 141 | res2.Header.Set("X-Vault-Token", vault.Auth.Client_Token) 142 | resp2, err := http.DefaultClient.Do(res2) 143 | if err != nil { 144 | e.log.Error("Unable to fetch Vault Data", "error", err.Error()) 145 | } 146 | defer resp2.Body.Close() 147 | vaultData, err := ioutil.ReadAll(resp2.Body) 148 | if err != nil { 149 | e.log.Error("Unable to read Vault Data", "error", err.Error()) 150 | return nil, err 151 | } 152 | 153 | var vData VaultData 154 | json.Unmarshal(vaultData, &vData) 155 | e.log.Info("Vault", "Response Secret Data", string(vaultData)) 156 | 157 | return &vData.Data.Data, nil 158 | } 159 | 160 | func (e *EmployeeDB) mongoClient() (*mongo.Client, error) { 161 | 162 | e.log.Info("MongoDB Server Connect") 163 | 164 | secretData, err := e.GetMongoCredFromVault() 165 | if err != nil { 166 | e.log.Error("Unable to read secret data from Vault", "error", err.Error()) 167 | return nil, err 168 | } 169 | 170 | client, err := mongo.NewClient(options.Client().ApplyURI(MONGODB_CONNECTION_STRING). 171 | SetAuth(options.Credential{ 172 | Username: secretData.Username, Password: secretData.Password, 173 | })) 174 | 175 | if err != nil { 176 | e.log.Error("Unable to create server mongo client", "error", err.Error()) 177 | return nil, err 178 | } 179 | 180 | return client, nil 181 | } 182 | 183 | func (e *EmployeeDB) ConnectDB() (*mongo.Client, error) { 184 | 185 | e.log.Info("Connect to MongoDB") 186 | 187 | client, err := e.mongoClient() 188 | if err != nil { 189 | e.log.Error("Unable to create mongo client", "error", err.Error()) 190 | return nil, err 191 | } 192 | 193 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 194 | defer cancel() 195 | err = client.Connect(ctx) 196 | if err != nil { 197 | e.log.Error("Unable to create mongo client", "error", err.Error()) 198 | return nil, err 199 | } 200 | 201 | e.log.Info("MongoDB Connected Successfully") 202 | 203 | return client, nil 204 | } 205 | 206 | func (e *EmployeeDB) GetCollection() (*mongo.Collection, error) { 207 | 208 | client, err := e.ConnectDB() 209 | if err != nil { 210 | e.log.Error("MongoDB", "Error", err.Error()) 211 | return nil, err 212 | } 213 | employeeCollection := client.Database(MONGODB_DATABASE).Collection(MONGODB_COLLECTION) 214 | 215 | return employeeCollection, nil 216 | } 217 | -------------------------------------------------------------------------------- /database/database_test.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestEmployeeIDReturnsErr(t *testing.T) { 10 | e := Employee{ 11 | ID: 55, 12 | } 13 | err := Validate(e) 14 | assert.Len(t, err, 1) 15 | } 16 | 17 | func TestEmployeeEmailReturnsErr(t *testing.T) { 18 | e := Employee{ 19 | Email: "abc.com", 20 | } 21 | err := Validate(e) 22 | assert.Len(t, err, 1) 23 | } 24 | 25 | func TestEmployeeDetailsReturnsErr(t *testing.T) { 26 | e := Employee{ 27 | ID: 345, 28 | Name: "Michale", 29 | Email: "abc@abc.com", 30 | } 31 | err := Validate(e) 32 | assert.Len(t, err, 1) 33 | } 34 | -------------------------------------------------------------------------------- /database/database_update_employees.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "context" 5 | 6 | "go.mongodb.org/mongo-driver/bson" 7 | ) 8 | 9 | //UpdateEmployee 10 | func (e *EmployeeDB) UpdateEmployee(employee *Employee) error { 11 | 12 | collection, err := e.GetCollection() 13 | if err != nil { 14 | e.log.Error("Unable to create collection", "error", err.Error()) 15 | return err 16 | } 17 | 18 | _, err = collection.UpdateMany( 19 | context.TODO(), 20 | bson.M{MONGODB_COLLECTION_ID: employee.Email}, 21 | bson.D{ 22 | {"$set", bson.D{ 23 | {MONGODB_COLLECTION_ID, employee.ID}, 24 | {MONGODB_COLLECTION_NAME, employee.Name}, 25 | {MONGODB_COLLECTION_EMAIL, employee.Email}, 26 | {MONGODB_COLLECTION_COMPANY, employee.Company}, 27 | {MONGODB_COLLECTION_OCCUPATION, employee.Occupation}, 28 | {MONGODB_COLLECTION_SALARY, employee.Salary}}}, 29 | }, 30 | ) 31 | if err != nil { 32 | e.log.Error("Unable to update employee", "error", err.Error()) 33 | return err 34 | } 35 | 36 | //Update Redis Cache 37 | key := getKey(employee.ID) 38 | e.redisCache.Set(key, employee) 39 | 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /database/database_validate.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-playground/validator/v10" 7 | ) 8 | 9 | type Validation struct { 10 | validate *validator.Validate 11 | } 12 | 13 | type ValidationError struct { 14 | validator.FieldError 15 | } 16 | 17 | type ValidationErrors []ValidationError 18 | 19 | func (v ValidationError) Error() string { 20 | return fmt.Sprintf( 21 | "validation_error: '%s' '%s'", 22 | v.Namespace(), 23 | v.Type(), 24 | ) 25 | } 26 | 27 | func (v ValidationErrors) Errors() []string { 28 | errs := []string{} 29 | for _, err := range v { 30 | errs = append(errs, err.Error()) 31 | } 32 | return errs 33 | } 34 | 35 | var validate *validator.Validate 36 | 37 | func (e *EmployeeDB) RequestValidator(data interface{}) ValidationErrors { 38 | return Validate(data) 39 | } 40 | 41 | func Validate(data interface{}) ValidationErrors { 42 | 43 | validate = validator.New() 44 | 45 | validateRefErr := validate.Struct(data) 46 | if validateRefErr != nil { 47 | validErrs := validateRefErr.(validator.ValidationErrors) 48 | 49 | if len(validErrs) == 0 { 50 | return nil 51 | } 52 | 53 | var validationErrs []ValidationError 54 | for _, err := range validErrs { 55 | 56 | ve := ValidationError{err.(validator.FieldError)} 57 | validationErrs = append(validationErrs, ve) 58 | } 59 | return validationErrs 60 | } 61 | 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /database/redis_cache_manger.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | 8 | "github.com/go-redis/redis/v8" 9 | "github.com/hashicorp/go-hclog" 10 | ) 11 | 12 | type RedisCache struct { 13 | redisClient *redis.Client 14 | log hclog.Logger 15 | } 16 | 17 | const ( 18 | LOCAL_ADDR = "172.17.0.2:31594" 19 | SERVER_ADDR = "redis.default.svc.cluster.local:6379" 20 | ) 21 | 22 | var ctx = context.Background() 23 | 24 | func InitializeCacheClient() (*RedisCache, error) { 25 | 26 | log := hclog.Default() 27 | 28 | log.Info("Initialize Redis", "Connect", LOCAL_ADDR) 29 | 30 | rdsClient := redis.NewClient(&redis.Options{ 31 | Addr: LOCAL_ADDR, 32 | Password: "", 33 | DB: 0, 34 | }) 35 | 36 | _, err := rdsClient.Ping(ctx).Result() 37 | if err != nil { 38 | log.Error("Failed to connect", "Redis", err.Error()) 39 | return nil, fmt.Errorf("Failed to connect to redis client - %s", err.Error()) 40 | } 41 | 42 | log.Info("Redis Connected", "Success", LOCAL_ADDR) 43 | 44 | return &RedisCache{ 45 | redisClient: rdsClient, 46 | log: log, 47 | }, nil 48 | } 49 | 50 | func (c *RedisCache) Set(key string, value *Employee) error { 51 | 52 | c.log.Info("Set Redis Cache", "Key", key) 53 | 54 | json, err := json.Marshal(value) 55 | if err != nil { 56 | c.log.Error("Failed to Marshal cache data", "Key", key, "Error", err.Error()) 57 | return err 58 | } 59 | 60 | err = c.redisClient.Set(ctx, key, json, 0).Err() 61 | if err != nil { 62 | c.log.Error("Set Redis Cache", "Key", key, "Error", err.Error()) 63 | return err 64 | } 65 | return nil 66 | } 67 | 68 | func (c *RedisCache) Get(key string) (*Employee, error) { 69 | 70 | c.log.Info("Get Redis Cache", "Key", key) 71 | 72 | value, err := c.redisClient.Get(ctx, key).Result() 73 | if err != nil { 74 | c.log.Error("Get Redis Cache", "Key", key, "Error", err.Error()) 75 | return nil, err 76 | } 77 | 78 | employee := Employee{} 79 | err = json.Unmarshal([]byte(value), &employee) 80 | 81 | if err != nil { 82 | c.log.Error("Unable to Unmarshal Redis cache", "Key", key, "Error", err.Error()) 83 | return nil, err 84 | } 85 | return &employee, nil 86 | } 87 | 88 | func (c *RedisCache) Del(key string) error { 89 | 90 | c.log.Info("Del Redis Cache", "Key", key) 91 | 92 | err := c.redisClient.Del(ctx, key).Err() 93 | if err != nil { 94 | c.log.Error("Unable to Del Redis Cache", "Key", key, "Error", err.Error()) 95 | return err 96 | } 97 | 98 | return nil 99 | } 100 | 101 | func getKey(id int) string { 102 | return fmt.Sprintf("%s%d", "Key-", id) 103 | } 104 | -------------------------------------------------------------------------------- /deploy_kubernetes/go_cache_app/backup.txt: -------------------------------------------------------------------------------- 1 | env: 2 | - name: SECRET_USERNAME 3 | valueFrom: 4 | secretKeyRef: 5 | name: mongosecret 6 | key: username 7 | - name: SECRET_PASSWORD 8 | valueFrom: 9 | secretKeyRef: 10 | name: mongosecret 11 | key: password -------------------------------------------------------------------------------- /deploy_kubernetes/go_cache_app/go-cache-poc-app.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: go-cache-kubernetes-app-poc 5 | labels: 6 | app: go-cache-kubernetes-app-poc 7 | spec: 8 | replicas: 1 9 | strategy: 10 | type: Recreate 11 | selector: 12 | matchLabels: 13 | app: go-cache-kubernetes-app-poc 14 | template: 15 | metadata: 16 | creationTimestamp: null 17 | labels: 18 | app: go-cache-kubernetes-app-poc 19 | spec: 20 | serviceAccountName: vault 21 | containers: 22 | - name: go-cache-kubernetes-container-poc 23 | image: deeptiman1991/go-cache-kubernetes-v1:1.0.0 24 | imagePullPolicy: Always 25 | env: 26 | - name: VAULT_ADDR 27 | value: "http://vault:8200" 28 | - name: JWT_PATH 29 | value: "/var/run/secrets/kubernetes.io/serviceaccount/token" 30 | - name: SERVICE_PORT 31 | value: "8080" 32 | ports: 33 | - containerPort: 5000 34 | hostname: go-cache-kubernetes-app-poc 35 | restartPolicy: Always 36 | volumes: 37 | - name: go-cache-kubernetes-pvc-v1-poc 38 | persistentVolumeClaim: 39 | claimName: go-cache-kubernetes-pvc-v1-poc 40 | status: {} -------------------------------------------------------------------------------- /deploy_kubernetes/go_cache_app/go-cache-poc-pvc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | app: go-cache-kubernetes-pvc-v1-poc 7 | name: go-cache-kubernetes-pvc-v1-poc 8 | spec: 9 | accessModes: 10 | - ReadWriteOnce 11 | resources: 12 | requests: 13 | storage: 1Gi 14 | status: {} 15 | -------------------------------------------------------------------------------- /deploy_kubernetes/go_cache_app/go-cache-poc-svc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: go-cache-kubernetes-svc-poc 5 | spec: 6 | ports: 7 | - name: "5000" 8 | port: 5000 9 | targetPort: 5000 10 | selector: 11 | app: go-cache-kubernetes-app-poc 12 | type: LoadBalancer 13 | status: 14 | loadBalancer: {} -------------------------------------------------------------------------------- /deploy_kubernetes/kafka/deploy/kafka-manager-deployment.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: kafka-manager 5 | namespace: kafka-ca1 6 | spec: 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | app: kafka-manager 11 | template: 12 | metadata: 13 | labels: 14 | app: kafka-manager 15 | spec: 16 | containers: 17 | - name: kafka-manager 18 | image: sheepkiller/kafka-manager@sha256:615f3b99d38aba2d5fdb3fb750a5990ba9260c8fb3fd29c7e776e8c150518b78 19 | ports: 20 | - containerPort: 9000 21 | env: 22 | - name: ZK_HOSTS 23 | value: "zookeeper-service:2181" -------------------------------------------------------------------------------- /deploy_kubernetes/kafka/deploy/kafka-manager-service.yml: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: kafka-manager 5 | namespace: kafka-ca1 6 | spec: 7 | type: NodePort 8 | selector: 9 | app: kafka-manager 10 | ports: 11 | - port: 9000 12 | protocol: TCP 13 | targetPort: 9000 -------------------------------------------------------------------------------- /deploy_kubernetes/kafka/deploy/kafka-service.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: kafka 6 | namespace: kafka-ca1 7 | labels: 8 | app: kafka 9 | spec: 10 | ports: 11 | - port: 9092 12 | name: plaintext 13 | - port: 9999 14 | name: jmx 15 | clusterIP: None 16 | selector: 17 | app: kafka -------------------------------------------------------------------------------- /deploy_kubernetes/kafka/deploy/kafka-statefulset.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: StatefulSet 4 | metadata: 5 | name: kafka 6 | namespace: kafka-ca1 7 | spec: 8 | selector: 9 | matchLabels: 10 | app: kafka 11 | serviceName: "kafka" 12 | replicas: 3 13 | podManagementPolicy: OrderedReady 14 | template: 15 | metadata: 16 | labels: 17 | app: kafka # has to match .spec.selector.matchLabels 18 | spec: 19 | containers: 20 | - name: kafka 21 | image: wurstmeister/kafka:2.11-2.0.0 22 | imagePullPolicy: IfNotPresent 23 | ports: 24 | - containerPort: 9092 25 | name: plaintext 26 | - containerPort: 9999 27 | name: jmx 28 | env: 29 | - name: KAFKA_ADVERTISED_PORT 30 | value: "9092" 31 | - name: BROKER_ID_COMMAND 32 | value: "hostname | cut -d'-' -f2" 33 | - name: KAFKA_ZOOKEEPER_CONNECT 34 | value: "zookeeper-service:2181" 35 | - name: KAFKA_LISTENERS 36 | value: "PLAINTEXT://:9092" 37 | - name: KAFKA_JMX_OPTS 38 | value: "-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.rmi.port=9999 -Djava.rmi.server.hostname=127.0.0.1" 39 | - name: JMX_PORT 40 | value: "9999" -------------------------------------------------------------------------------- /deploy_kubernetes/kafka/deploy/namespace.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: kafka-ca1 -------------------------------------------------------------------------------- /deploy_kubernetes/kafka/deploy/zookeeper-deployment.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | labels: 6 | app: zookeeper 7 | name: zookeeper 8 | namespace: kafka-ca1 9 | spec: 10 | replicas: 1 11 | selector: 12 | matchLabels: 13 | app: zookeeper 14 | template: 15 | metadata: 16 | labels: 17 | app: zookeeper 18 | spec: 19 | containers: 20 | - image: library/zookeeper:3.4.13 21 | imagePullPolicy: IfNotPresent 22 | name: zookeeper 23 | ports: 24 | - containerPort: 2181 25 | env: 26 | - name: ZOO_MY_ID 27 | value: "1" -------------------------------------------------------------------------------- /deploy_kubernetes/kafka/deploy/zookeeper-service.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | labels: 6 | app: zookeeper-service 7 | name: zookeeper-service 8 | namespace: kafka-ca1 9 | spec: 10 | type: NodePort 11 | ports: 12 | - name: zookeeper-port 13 | port: 2181 14 | # nodePort: 30181 15 | targetPort: 2181 16 | selector: 17 | app: zookeeper 18 | -------------------------------------------------------------------------------- /deploy_kubernetes/kafka/kafka-repcon.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ReplicationController 3 | metadata: 4 | name: kafka-repcon 5 | labels: 6 | app: kafka-repcon 7 | spec: 8 | replicas: 1 9 | selector: 10 | app: kafka-repcon 11 | template: 12 | metadata: 13 | labels: 14 | app: kafka-repcon 15 | spec: 16 | containers: 17 | - name: zookeeper-app-container 18 | image: wurstmeister/kafka 19 | command: 20 | - /bin/zookeeper-server-start.sh 21 | - /config/zookeeper.properties 22 | ports: 23 | - containerPort: 2181 24 | -------------------------------------------------------------------------------- /deploy_kubernetes/kafka/kafka-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: kafka-service 5 | labels: 6 | name: kafka-service 7 | spec: 8 | ports: 9 | - name: "9092" 10 | port: 9092 11 | targetPort: 9092 12 | protocol: TCP 13 | - name: "2181" 14 | port: 2181 15 | targetPort: 2181 16 | selector: 17 | app: kafka-service 18 | type: LoadBalancer 19 | -------------------------------------------------------------------------------- /deploy_kubernetes/kafka/zookeeper-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | name: zookeeper-app 5 | spec: 6 | replicas: 1 7 | template: 8 | metadata: 9 | labels: 10 | app: zookeeper-app 11 | role: master 12 | tier: backend 13 | spec: 14 | containers: 15 | - name: zookeeper-app-container 16 | image: bitnami/zookeeper 17 | ports: 18 | - containerPort: 2181 19 | env: 20 | - name: ZOOKEEPER_ID 21 | value: "1" 22 | - name: ZOOKEEPER_SERVER_1 23 | value: zookeeper-app 24 | -------------------------------------------------------------------------------- /deploy_kubernetes/kafka/zookeeper-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: zookeeper-service 5 | labels: 6 | app: zookeeper-service 7 | spec: 8 | ports: 9 | - name: client 10 | port: 2181 11 | protocol: TCP 12 | - name: follower 13 | port: 2888 14 | protocol: TCP 15 | - name: leader 16 | port: 3888 17 | protocol: TCP 18 | selector: 19 | app: zookeeper-service 20 | -------------------------------------------------------------------------------- /deploy_kubernetes/mongodb/mongodb-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: mongodb-service 5 | labels: 6 | name: mongo 7 | spec: 8 | ports: 9 | - port: 27017 10 | targetPort: 27017 11 | clusterIP: None 12 | selector: 13 | role: mongo -------------------------------------------------------------------------------- /deploy_kubernetes/mongodb/mongodb-statefulset.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: mongod 5 | spec: 6 | serviceName: mongodb-service 7 | replicas: 3 8 | selector: 9 | matchLabels: 10 | role: mongo 11 | template: 12 | metadata: 13 | labels: 14 | role: mongo 15 | environment: test 16 | replicaset: MainRepSet 17 | spec: 18 | terminationGracePeriodSeconds: 10 19 | containers: 20 | - name: mongod-container 21 | image: mongo 22 | command: 23 | - "mongod" 24 | - "--bind_ip" 25 | - "0.0.0.0" 26 | - "--replSet" 27 | - "MainRepSet" 28 | resources: 29 | requests: 30 | cpu: 0.2 31 | memory: 200Mi 32 | ports: 33 | - containerPort: 27017 34 | volumeMounts: 35 | - name: mongodb-persistent-storage-claim 36 | mountPath: /data/db 37 | volumeClaimTemplates: 38 | - metadata: 39 | name: mongodb-persistent-storage-claim 40 | annotations: 41 | volume.beta.kubernetes.io/storage-class: "standard" 42 | spec: 43 | accessModes: [ "ReadWriteOnce" ] 44 | resources: 45 | requests: 46 | storage: 1Gi 47 | -------------------------------------------------------------------------------- /deploy_kubernetes/redis/redis-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: redismod 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: redismod 10 | template: 11 | metadata: 12 | labels: 13 | app: redismod 14 | spec: 15 | containers: 16 | - name: redismod 17 | image: redislabs/redismod 18 | resources: 19 | requests: 20 | cpu: 100m 21 | memory: 100Mi 22 | ports: 23 | - containerPort: 6379 24 | securityContext: 25 | capabilities: 26 | add: 27 | - SYS_RESOURCE 28 | -------------------------------------------------------------------------------- /deploy_kubernetes/redis/redis-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: redis-service 5 | labels: 6 | app: redis-service 7 | spec: 8 | ports: 9 | - port: 6379 10 | targetPort: 6379 11 | selector: 12 | app: redismod -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '1' 2 | 3 | services: 4 | 5 | go-cache-poc: 6 | container_name: go-cache-kubernetes-container-poc 7 | image: deeptiman1991/go-cache-kubernetes-v1:1.0.0 8 | hostname: go-cache-kubernetes-container-poc 9 | build: 10 | context: . 11 | dockerfile: Dockerfile 12 | environment: 13 | GET_HOST_FROM: dns 14 | networks: 15 | - go-cache-poc 16 | volumes: 17 | - .:/go/src/go-cache-kubernetes 18 | ports: 19 | - 5000:5000 20 | labels: 21 | kompose.service.type: LoadBalancer 22 | 23 | networks: 24 | go-cache-poc: 25 | driver: bridge 26 | -------------------------------------------------------------------------------- /handlers/handler_create_employee.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | "go-cache-kubernetes/database" 7 | ) 8 | 9 | // swagger:route POST /employees create_employee 10 | // Request to create new employee 11 | // 12 | // responses: 13 | // 200: employeeResponse 14 | // 422: errorValidation 15 | // 501: errorResponse 16 | // POST request CreateEmployee 17 | func (h *Handlers) CreateEmployee(rw http.ResponseWriter, r *http.Request) { 18 | 19 | data := r.Context().Value(KeyEmp{}).(*database.Employee) 20 | 21 | h.log.Info("Insert Employee", "Create", data) 22 | 23 | err := h.database.CreateEmployee(data) 24 | if err != nil { 25 | h.respondJSON(rw, http.StatusBadRequest, &ServerError{Error: err.Error()}) 26 | return 27 | } 28 | 29 | h.respondJSON(rw, http.StatusCreated, map[string]string{"success": "Employee created successfully"}) 30 | } 31 | -------------------------------------------------------------------------------- /handlers/handler_delete_employee.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | "go-cache-kubernetes/database" 7 | ) 8 | 9 | // swagger:route DELETE /api/{id} employees delete_employee 10 | // Delete the employee details using the id 11 | // responses: 12 | // 201: noContentResponse 13 | // 404: errorResponse 14 | // GET request GetEmployeeByID 15 | func (h *Handlers) DeleteEmployeeByID(rw http.ResponseWriter, r *http.Request) { 16 | 17 | id := getEmployeeID(r) 18 | 19 | h.log.Info("DeleteEmployeeByID", "id", id) 20 | 21 | _, err := h.database.GetEmployeeByID(id) 22 | 23 | switch err { 24 | case nil: 25 | case database.ErrEmployeeNotFound: 26 | h.log.Error("Unable to find employee", "error", err) 27 | h.respondJSON(rw, http.StatusNotFound, &ServerError{Error: err.Error()}) 28 | return 29 | default: 30 | h.log.Error("Error while find employee", "error", err) 31 | h.respondJSON(rw, http.StatusInternalServerError, &ServerError{Error: err.Error()}) 32 | return 33 | } 34 | 35 | err = h.database.DeleteEmployeeByID(id) 36 | if err != nil { 37 | h.respondJSON(rw, http.StatusBadRequest, &ServerError{Error: err.Error()}) 38 | return 39 | } 40 | 41 | h.log.Info("DeleteEmployeeByID", "Deleted", id) 42 | 43 | h.respondJSON(rw, http.StatusCreated, map[string]string{"success": "Employee deleted successfully"}) 44 | } 45 | -------------------------------------------------------------------------------- /handlers/handler_get_employees.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | "go-cache-kubernetes/database" 7 | ) 8 | 9 | // swagger:route GET /api employees get_all_employees 10 | // Return the list of employees details 11 | // responses: 12 | // 200: employeeResponse 13 | // 404: errorResponse 14 | // GET request GetAllEmployees 15 | func (h *Handlers) GetAllEmployees(rw http.ResponseWriter, r *http.Request) { 16 | 17 | h.log.Info("GetAllEmployees Request") 18 | employees, err := h.database.GetAllEmployees() 19 | if err != nil { 20 | h.respondJSON(rw, http.StatusInternalServerError, &ServerError{Error: err.Error()}) 21 | return 22 | } 23 | h.respondJSON(rw, http.StatusOK, employees) 24 | } 25 | 26 | // swagger:route GET /api/{id} employees get_employee_by_id 27 | // Return the employee details for the id 28 | // responses: 29 | // 200: employeeResponse 30 | // 404: errorResponse 31 | // GET request GetEmployeeByID 32 | func (h *Handlers) GetEmployeeByID(rw http.ResponseWriter, r *http.Request) { 33 | 34 | id := getEmployeeID(r) 35 | 36 | h.log.Info("GetEmployeeByID", "id", id) 37 | 38 | employee, err := h.database.GetEmployeeByID(id) 39 | 40 | switch err { 41 | case nil: 42 | case database.ErrEmployeeNotFound: 43 | h.log.Error("Unable to find employee", "error", err) 44 | h.respondJSON(rw, http.StatusNotFound, &ServerError{Error: err.Error()}) 45 | return 46 | default: 47 | h.log.Error("Error while find employee", "error", err) 48 | h.respondJSON(rw, http.StatusInternalServerError, &ServerError{Error: err.Error()}) 49 | return 50 | } 51 | 52 | h.respondJSON(rw, http.StatusOK, employee) 53 | } 54 | -------------------------------------------------------------------------------- /handlers/handler_manager.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "go-cache-kubernetes/database" 5 | 6 | "github.com/hashicorp/go-hclog" 7 | ) 8 | 9 | type Handlers struct { 10 | database *database.EmployeeDB 11 | log hclog.Logger 12 | } 13 | 14 | type KeyEmp struct{} 15 | 16 | // The structure containing server error msgs 17 | type ServerError struct { 18 | Error string `json:"error"` 19 | } 20 | 21 | // The structure containing validation error msgs 22 | type ValidationErrorMsg struct { 23 | Error []string `json:"error"` 24 | } 25 | 26 | func InitializeHandlers(database *database.EmployeeDB, log hclog.Logger) *Handlers { 27 | return &Handlers{database, log} 28 | } 29 | -------------------------------------------------------------------------------- /handlers/handler_response_validator.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "io" 7 | "net/http" 8 | "strconv" 9 | 10 | "go-cache-kubernetes/database" 11 | 12 | "github.com/gorilla/mux" 13 | ) 14 | 15 | func (h *Handlers) ResponseValidator(request http.Handler) http.Handler { 16 | 17 | return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { 18 | 19 | var emp *database.Employee 20 | 21 | err := DecodeJSON(r.Body, &emp) 22 | if err != nil { 23 | h.respondJSON(rw, http.StatusBadRequest, &ServerError{Error: err.Error()}) 24 | return 25 | } 26 | 27 | //validate employee record 28 | validationErrors := h.database.RequestValidator(emp) 29 | if len(validationErrors) != 0 { 30 | h.respondJSON(rw, http.StatusUnprocessableEntity, &ValidationErrorMsg{Error: validationErrors.Errors()}) 31 | return 32 | } 33 | 34 | ctx := context.WithValue(r.Context(), KeyEmp{}, emp) 35 | r = r.WithContext(ctx) 36 | 37 | request.ServeHTTP(rw, r) 38 | }) 39 | } 40 | 41 | func (h *Handlers) respondJSON(rw http.ResponseWriter, status int, payload interface{}) { 42 | 43 | h.log.Info("respondJSON", "status", status) 44 | rw.Header().Add("Content-Type", "application/json") 45 | rw.WriteHeader(status) 46 | EncodeJSON(rw, payload) 47 | } 48 | 49 | func DecodeJSON(reader io.Reader, res interface{}) error { 50 | return json.NewDecoder(reader).Decode(res) 51 | } 52 | 53 | func EncodeJSON(writer io.Writer, res interface{}) error { 54 | return json.NewEncoder(writer).Encode(res) 55 | } 56 | 57 | func getEmployeeID(r *http.Request) int { 58 | vars := mux.Vars(r) 59 | id, _ := strconv.Atoi(vars["id"]) 60 | return id 61 | } 62 | -------------------------------------------------------------------------------- /handlers/handler_update_employee.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | "go-cache-kubernetes/database" 7 | ) 8 | 9 | // swagger:route PUT /api/{id} employees update_employee 10 | // Return the updated employee details for the id 11 | // responses: 12 | // 200: employeeResponse 13 | // 404: errorResponse 14 | // PUT request UpdateEmployee 15 | func (h *Handlers) UpdateEmployee(rw http.ResponseWriter, r *http.Request) { 16 | 17 | data := r.Context().Value(KeyEmp{}).(*database.Employee) 18 | 19 | h.log.Info("UpdateEmployee", "id", data.ID) 20 | 21 | _, err := h.database.GetEmployeeByID(data.ID) 22 | 23 | switch err { 24 | case nil: 25 | case database.ErrEmployeeNotFound: 26 | h.log.Error("Unable to find employee", "error", err) 27 | h.respondJSON(rw, http.StatusNotFound, &ServerError{Error: err.Error()}) 28 | return 29 | default: 30 | h.log.Error("Error while find employee", "error", err) 31 | h.respondJSON(rw, http.StatusInternalServerError, &ServerError{Error: err.Error()}) 32 | return 33 | } 34 | 35 | h.log.Info("UpdateEmployee", "Employee", data) 36 | 37 | err = h.database.UpdateEmployee(data) 38 | if err != nil { 39 | h.log.Error("Unable to update employee", "error", err) 40 | h.respondJSON(rw, http.StatusBadRequest, &ServerError{Error: err.Error()}) 41 | return 42 | } 43 | 44 | h.respondJSON(rw, http.StatusCreated, map[string]string{"success": "Employee record updated successfully"}) 45 | } 46 | -------------------------------------------------------------------------------- /kafka/kafka_consumer.go: -------------------------------------------------------------------------------- 1 | package kafka 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | 8 | "go-cache-kubernetes/database" 9 | 10 | "github.com/confluentinc/confluent-kafka-go/kafka" 11 | "github.com/hashicorp/go-hclog" 12 | ) 13 | 14 | const ( 15 | TOPIC = "EmployeesList" 16 | ) 17 | 18 | type KafkaConsumer struct { 19 | consumer *kafka.Consumer 20 | dbRef *database.EmployeeDB 21 | log hclog.Logger 22 | } 23 | 24 | func InitializeKafkaConsumer() *KafkaConsumer { 25 | 26 | c, _ := NewConsumer() 27 | log := hclog.Default() 28 | return &KafkaConsumer{ 29 | consumer: c, 30 | log: log, 31 | } 32 | } 33 | 34 | func NewConsumer() (*kafka.Consumer, error) { 35 | 36 | consumer, err := kafka.NewConsumer(&kafka.ConfigMap{ 37 | "bootstrap.servers": "localhost:9092", 38 | "broker.address.family": "v4", 39 | "group.id": "employees", 40 | "auto.offset.reset": "smallest", 41 | }) 42 | 43 | if err != nil { 44 | return nil, fmt.Errorf("Failed to initialize Kafka Consumer %s", err.Error()) 45 | } 46 | 47 | return consumer, nil 48 | } 49 | 50 | func (k *KafkaConsumer) ReadMessages(rw http.ResponseWriter, r *http.Request) { 51 | 52 | k.log.Info("Kafka Consumer", "Topic", TOPIC) 53 | 54 | k.consumer.SubscribeTopics([]string{TOPIC, "^aRegex.*[Tt]opic"}, nil) 55 | 56 | for { 57 | msg, err := k.consumer.ReadMessage(-1) 58 | if err == nil { 59 | var employee *database.Employee 60 | emp := []byte(string(msg.Value)) 61 | err = json.Unmarshal(emp, &employee) 62 | if err != nil { 63 | k.log.Error("Failed to read msg", "error", err.Error()) 64 | } 65 | k.log.Info("Kafka Consumer", "ReadMessage", string(msg.Value)) 66 | } else { 67 | // The client will automatically try to recover from all errors. 68 | fmt.Printf("Consumer error: %v (%v)\n", err, msg) 69 | k.log.Error("Kafka Consumer", "Error", err, "Msg", msg) 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /kafka/kafka_producer.go: -------------------------------------------------------------------------------- 1 | package kafka 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | 8 | "go-cache-kubernetes/database" 9 | 10 | "github.com/confluentinc/confluent-kafka-go/kafka" 11 | "github.com/hashicorp/go-hclog" 12 | ) 13 | 14 | type KafkaProducer struct { 15 | producer *kafka.Producer 16 | dbRef *database.EmployeeDB 17 | log hclog.Logger 18 | } 19 | 20 | func InitializeKafkaProducer(database *database.EmployeeDB) *KafkaProducer { 21 | 22 | p, _ := NewProducer() 23 | log := hclog.Default() 24 | return &KafkaProducer{ 25 | producer: p, 26 | dbRef: database, 27 | log: log, 28 | } 29 | } 30 | 31 | func NewProducer() (*kafka.Producer, error) { 32 | 33 | producer, err := kafka.NewProducer(&kafka.ConfigMap{"bootstrap.servers": "localhost:9092"}) 34 | if err != nil { 35 | return nil, fmt.Errorf("Failed to initialize Kafka Producer %s", err.Error()) 36 | } 37 | 38 | return producer, nil 39 | } 40 | 41 | func (k *KafkaProducer) eventDelivery() { 42 | 43 | go func() { 44 | for event := range k.producer.Events() { 45 | switch ev := event.(type) { 46 | case *kafka.Message: 47 | if ev.TopicPartition.Error != nil { 48 | k.log.Error("Kafka Producer", "Delivery failed", ev.TopicPartition, "Error", ev.TopicPartition.Error) 49 | } else { 50 | k.log.Info("Kafka Producer", "Delivery message", ev.TopicPartition) 51 | } 52 | } 53 | } 54 | }() 55 | } 56 | 57 | func (k *KafkaProducer) ProduceMessages(rw http.ResponseWriter, r *http.Request) { 58 | 59 | topic := TOPIC 60 | 61 | k.log.Info("Kafka Producer", "Topic", TOPIC) 62 | 63 | employeesList, err := k.dbRef.GetAllEmployees() 64 | if err != nil { 65 | k.log.Error("Kafka Producer", "error", err.Error()) 66 | return 67 | } 68 | 69 | for _, employee := range employeesList { 70 | 71 | k.log.Info("Kafka Producer", "Employee", employee) 72 | 73 | json, err := json.Marshal(employee) 74 | if err != nil { 75 | k.log.Error("Kafka Producer", "error", err.Error()) 76 | return 77 | } 78 | 79 | k.producer.Produce(&kafka.Message{ 80 | TopicPartition: kafka.TopicPartition{ 81 | Topic: &topic, 82 | Partition: kafka.PartitionAny, 83 | }, 84 | Value: []byte(json), 85 | }, nil) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | "time" 7 | 8 | "go-cache-kubernetes/database" 9 | "go-cache-kubernetes/handlers" 10 | "go-cache-kubernetes/kafka" 11 | 12 | "github.com/go-openapi/runtime/middleware" 13 | gohandlers "github.com/gorilla/handlers" 14 | "github.com/gorilla/mux" 15 | "github.com/hashicorp/go-hclog" 16 | ) 17 | 18 | var address *string 19 | 20 | func main() { 21 | 22 | PORT := ":5000" 23 | address = &PORT 24 | log := hclog.Default() 25 | 26 | db := database.InitializeDBManager(log) 27 | kafkaProducer := kafka.InitializeKafkaProducer(db) 28 | kafkaConsumer := kafka.InitializeKafkaConsumer() 29 | handlers := handlers.InitializeHandlers(db, log) 30 | 31 | router := mux.NewRouter() 32 | 33 | createRequest := router.Methods(http.MethodPost).Subrouter() 34 | createRequest.HandleFunc("/api/create_employee", handlers.CreateEmployee) 35 | createRequest.Use(handlers.ResponseValidator) 36 | 37 | getRequest := router.Methods(http.MethodGet).Subrouter() 38 | getRequest.HandleFunc("/api/get_employee_by_id/{id:[0-9]+}", handlers.GetEmployeeByID) 39 | getRequest.HandleFunc("/api/get_all_employees", handlers.GetAllEmployees) 40 | 41 | updateRequest := router.Methods(http.MethodPut).Subrouter() 42 | updateRequest.HandleFunc("/api/update_employee", handlers.UpdateEmployee) 43 | updateRequest.Use(handlers.ResponseValidator) 44 | 45 | deleteRequest := router.Methods(http.MethodDelete).Subrouter() 46 | deleteRequest.HandleFunc("/api/delete_employee/{id:[0-9]+}", handlers.DeleteEmployeeByID) 47 | 48 | getRequest.HandleFunc("/kafka/producer", kafkaProducer.ProduceMessages) 49 | getRequest.HandleFunc("/kafka/consumer", kafkaConsumer.ReadMessages) 50 | 51 | op := middleware.RedocOpts{SpecURL: "/swagger.yaml"} 52 | redoc := middleware.Redoc(op, nil) 53 | 54 | getRequest.Handle("/docs", redoc) 55 | getRequest.Handle("/swagger.yaml", http.FileServer(http.Dir("./"))) 56 | 57 | cors := gohandlers.CORS(gohandlers.AllowedOrigins([]string{"*"})) 58 | 59 | server := http.Server{ 60 | Addr: PORT, 61 | Handler: cors(router), 62 | ErrorLog: log.StandardLogger(&hclog.StandardLoggerOptions{}), 63 | ReadTimeout: 5 * time.Second, 64 | WriteTimeout: 10 * time.Second, 65 | IdleTimeout: 120 * time.Second, 66 | } 67 | 68 | log.Info("Starting server on port 5000") 69 | 70 | err := server.ListenAndServe() 71 | if err != nil { 72 | log.Error("Unable to start server", "error", err) 73 | os.Exit(1) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /swagger.yaml: -------------------------------------------------------------------------------- 1 | basePath: / 2 | consumes: 3 | - application/json 4 | definitions: 5 | ServerError: 6 | description: ServerError error message returned by a server 7 | properties: 8 | message: 9 | description: message 10 | type: string 11 | x-go-name: Message 12 | type: object 13 | x-go-package: github.com/Deeptiman/go-cache-kubernetes/database 14 | ValidationError: 15 | description: ValidationError is a collection of validation error messages 16 | properties: 17 | messages: 18 | description: messages 19 | items: 20 | type: string 21 | type: array 22 | x-go-name: Messages 23 | type: object 24 | x-go-package: github.com/Deeptiman/go-cache-kubernetes/database 25 | Employee: 26 | description: Employee defines the structure for an API employee 27 | properties: 28 | id: 29 | description: the id of an employee 30 | format: int 31 | minimum: 100 32 | type: integer 33 | x-go-name: ID 34 | name: 35 | description: the name of an employee 36 | maxLength: 255 37 | type: string 38 | x-go-name: Name 39 | email: 40 | description: the email of an employee 41 | pattern: 'abc@abc.abc' 42 | maxLength: 255 43 | type: string 44 | x-go-name: Email 45 | company: 46 | description: the company name where the employee is working 47 | maxLength: 255 48 | type: string 49 | x-go-name: Company 50 | occupation: 51 | description: the occupation of an employee 52 | maxLength: 255 53 | type: string 54 | x-go-name: Occupation 55 | salary: 56 | description: the salary of an employee 57 | minimum: 9999 58 | type: int 59 | x-go-name: Salary 60 | required: 61 | - id 62 | - name 63 | - email 64 | - company 65 | - occupation 66 | - salary 67 | type: object 68 | x-go-package: github.com/Deeptiman/go-cache-kubernetes/database 69 | Success: 70 | description: Employee created successfully 71 | properties: 72 | message: 73 | description: the employee created successfully 74 | maxLength: 255 75 | type: string 76 | x-go-name: Name 77 | type: object 78 | x-go-package: github.com/Deeptiman/go-cache-kubernetes/database 79 | info: 80 | description: Documentation for Employee API 81 | title: Employee API 82 | version: 1.0.0 83 | paths: 84 | /api: 85 | get: 86 | description: Return a list of employees from the database 87 | operationId: get_all_employees 88 | responses: 89 | "200": 90 | $ref: '#/responses/employeesResponse' 91 | "501": 92 | $ref: '#/responses/errorResponse' 93 | tags: 94 | - employees 95 | post: 96 | description: Create a new employee 97 | operationId: create_employee 98 | parameters: 99 | - description: |- 100 | Employee data structure to Update or Create. 101 | Note: the id field is ignored by update and create operations 102 | in: body 103 | name: Body 104 | required: true 105 | schema: 106 | $ref: '#/definitions/Employee' 107 | responses: 108 | "200": 109 | $ref: '#/responses/successResponse' 110 | "422": 111 | $ref: '#/responses/errorValidation' 112 | "501": 113 | $ref: '#/responses/errorResponse' 114 | tags: 115 | - employees 116 | put: 117 | description: Update a employee details 118 | operationId: update_employee 119 | parameters: 120 | - description: |- 121 | Employee data structure to Update or Create. 122 | Note: the id field is ignored by update and create operations 123 | in: body 124 | name: Body 125 | required: true 126 | schema: 127 | $ref: '#/definitions/Employee' 128 | responses: 129 | "201": 130 | $ref: '#/responses/noContentResponse' 131 | "404": 132 | $ref: '#/responses/errorResponse' 133 | "422": 134 | $ref: '#/responses/errorValidation' 135 | tags: 136 | - employees 137 | /employees/{id}: 138 | delete: 139 | description: Delete an employee details 140 | operationId: delete_employee 141 | parameters: 142 | - description: The id of the employee for which the operation relates 143 | format: int 144 | in: path 145 | name: id 146 | required: true 147 | type: integer 148 | x-go-name: ID 149 | responses: 150 | "201": 151 | $ref: '#/responses/noContentResponse' 152 | "404": 153 | $ref: '#/responses/errorResponse' 154 | "501": 155 | $ref: '#/responses/errorResponse' 156 | tags: 157 | - employees 158 | get: 159 | description: Return a list of employees from the database 160 | operationId: get_employee_by_email 161 | parameters: 162 | - description: Email id of the employee 163 | in: query 164 | name: Email 165 | type: string 166 | responses: 167 | "200": 168 | $ref: '#/responses/employeeResponse' 169 | "404": 170 | $ref: '#/responses/errorResponse' 171 | tags: 172 | - employees 173 | /kafka: 174 | get: 175 | description: Request to reload list of employees using a Kafka message broker 176 | operationId: producer 177 | responses: 178 | "200": 179 | $ref: '#/responses/noContentResponse' 180 | tags: 181 | - employees 182 | produces: 183 | - application/json 184 | responses: 185 | errorResponse: 186 | description: Server error message returned as a string 187 | schema: 188 | $ref: '#/definitions/ServerError' 189 | errorValidation: 190 | description: Validation errors defined as an array of strings 191 | schema: 192 | $ref: '#/definitions/ValidationError' 193 | noContentResponse: 194 | description: No content is returned by this API endpoint 195 | successResponse: 196 | description: Success response defined as strings 197 | schema: 198 | $ref: '#/definitions/Success' 199 | employeeResponse: 200 | description: Data structure representing a single employee 201 | schema: 202 | $ref: '#/definitions/Employee' 203 | employeesResponse: 204 | description: A list of employees 205 | schema: 206 | items: 207 | $ref: '#/definitions/Employee' 208 | type: array 209 | schemes: 210 | - http 211 | swagger: "2.0" 212 | --------------------------------------------------------------------------------