diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..5ede701 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,37 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + + build: + name: Build + runs-on: ubuntu-latest + steps: + + - name: Set up Go 1.x + uses: actions/setup-go@v2 + with: + go-version: ^1.13 + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Get dependencies + run: | + go get -v -t -d ./... + if [ -f Gopkg.toml ]; then + curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh + dep ensure + fi + + - name: Build + run: go build -v . + + - name: Test + run: go test ./test/registryapilayer_test.go ./test/schedulerapilayer_test.go ./test/caching_test.go diff --git a/Dockerfile b/Dockerfile index e37a714..7379c3e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ FROM alpine - +RUN apk add --no-cache tzdata COPY ukiyo . COPY dbs . RUN chmod 777 ./ukiyo diff --git a/README.md b/README.md index c2b2092..57d236b 100644 --- a/README.md +++ b/README.md @@ -2,18 +2,17 @@ ## Idea incubation -- ukiyo will act as a watcher for docker containers. It will run alongside with the other running containers and will be responsible for automatic updates. Updates will be based on push based model compared to existing solutions such as [watchtower](https://github.com/containrrr/watchtower) and [ouroboros](https://github.com/pyouroboros/ouroboros) +- ukiyo will act as a watcher for docker containers. It will run alongside with the other running containers and will be responsible for automatic updates and schedule the container deployment time. Updates will be based on push based model compared to existing solutions such as [watchtower](https://github.com/containrrr/watchtower) and [ouroboros](https://github.com/pyouroboros/ouroboros) - Push events will be recived from ukiyo via webhooks. Docker registries provide webhooks to subscribe and listen to image changes. Locally running images will change only after such an event is received by ukiyo. -- Pull based model can be implemented as an optional way of updating the running containers. +- ukiyo itself runs as a Docker container or else runs as a separate background process a daemon. ## Components - Container manager - Push manager (webhooks configuration) - Scheduler -- OPTIONAL - Pull based update implementation ## Execution modes @@ -22,14 +21,6 @@ Two modes of execution - As a container running alongside other containers (Should mount docker.sock to run docker commands inside the ukiyo docker container) - As a standalone executable -## Language - -- Go lang - -### Go style guide - -- https://github.com/rajikaimal/go-styleguide - ## Dev Setup Guide Setup docker @@ -47,33 +38,142 @@ $ go build -ldflags="-s -w" -o ukiyo main.go Docker build command ```sh -$ docker build -f Dockerfile -t agentukiyo/ukiyo . -$ docker push agentukiyo/ukiyo -$ docker run -p 8080:8080 \ +$ docker build -f Dockerfile -t agentukiyo/ukiyo:tag . +$ docker push agentukiyo/ukiyo:tag +``` + +The ukiyo-agent is available at https://hub.docker.com/r/agentukiyo/ukiyo + +Run ukiyo-agent +```sh +$ docker run -d \ + -p 8080:8080 \ + -- name ukiyo \ -v /var/run/docker.sock:/var/run/docker.sock \ - -v /home/reinventor/dbs:/dbs \ - agentukiyo/ukiyo + -v /home/ukiyo/dbs:/dbs \ + agentukiyo/ukiyo:tag ``` Add webhook to your dockerhub repository ``` http:{serverIP}:8080/ukiyo-web-hook ``` +For pull the containers, require the save Docker Hub authentications inside the ukiyo agent. Therefore you need to use curl or postman request to save the Docker keys. Add your own docker registy details ``` -http:{serverIP}:8080/add-container +http:{serverIP}:8080/save-container-access-keys { - "username":"agentukiyo", - "repoName":"Ukiyo Docker registry", - "accessToken":"f44e334e-1440-4166-a16f-d8fc9d0eb188", - "email":"hansika.16@itfac.mrt.ac.lk", + "username":"docker registry name" + "desc":"docker registry description" + "accessToken":"docker registry accesstoken" + "email":"your email" "serverAddress":"http://docker.io/v1" } +``` + +```sh +curl -X POST \ +-H "Accept: Application/json" \ +-H "Content-Type: application/json" http://{serverIP}:8080/save-container-access-keys \ +-d '{"username":"docker registry name","desc":"docker registry description", +"accessToken":"docker registry accesstoken","email":"your email", +"serverAddress":"http://docker.io/v1"}' +``` + +Update your own docker registy details +``` +http:{serverIP}:8080/edit-container-access-keys +{ + "username":"docker registry name" + "desc":"edited description" + "accessToken":"new docker registry accesstoken" + "email":"new email" + "serverAddress":"http://docker.io/v1" +} +``` + +```sh +curl -X POST \ +-H "Accept: Application/json" \ +-H "Content-Type: application/json" http://{serverIP}:8080/edit-container-access-keys \ +-d '{"username":"docker registry name","desc":"docker registry description", +"accessToken":"docker registry accesstoken","email":"your email", +"serverAddress":"http://docker.io/v1"}' +``` + +Delete your docker registy details +``` +http://{serverIP}:8080/delete-container-access-keys/{registryname} +``` + +```sh +curl -X DELETE \ +-H "Accept: Application/json" \ +-H "Content-Type: application/json" http://{serverIP}:8080/delete-container-access-keys/{registryname} +``` + +Ukiyo will do the deployment in order to configured deployment details. Using save-repository-scheduled-time API you can apply the deployment on the fly or scheduled time + +Add your deployment details and set schedule deployment time +``` +http://{serverIP}:8080/save-repository-scheduled-time +{ + "name": "repository name", + "bindingPort": + [{ + "exportPort": "8180", + "internalPort": "80" + }, + { + "exportPort": "443", + "internalPort": "443" + }], + "scheduledTime": "Aug 17 2020 00:40:50 AM", + "scheduledDowntime": false +} +``` -http:{serverIP}:8080/edit-container-token +```sh +curl -X POST \ +-H "Accept: Application/json" \ +-H "Content-Type: application/json" http://{serverIP}:8080/save-repository-scheduled-time \ +-d '{"name": "repository name","bindingPort": [{ "exportPort": "8180", "internalPort": "80" }, +{ "exportPort": "443", "internalPort": "443" }], +"scheduledTime": "Aug 17 2020 00:40:50 AM", "scheduledDowntime": false}' +``` + +Change the deployment schedule +``` +http://{serverIP}:8080/edit-repository-scheduled-time { - "username":"agentukiyo", - "accessToken":"f44e334e-1440-4166-a16f-d8fc9d0eb188" + "name": "repository name", + "bindingPort": + [{ + "exportPort": "8180", + "internalPort": "80" + }], + "scheduledTime": "Aug 17 2020 00:40:50 AM", + "scheduledDowntime": false } ``` + +```sh +curl -X POST \ +-H "Accept: Application/json" \ +-H "Content-Type: application/json" http://{serverIP}:8080/edit-repository-scheduled-time \ +-d '{"name": "repository name","bindingPort": [{ "exportPort": "8180", "internalPort": "80" }, +{ "exportPort": "443", "internalPort": "443" }], +"scheduledTime": "Aug 17 2020 00:40:50 AM", "scheduledDowntime": true}' +``` + +Delete your deployment details +``` +http://{serverIP}:8080/remove-repository-scheduled-time/{repositoryname} +``` + +```sh +curl -X DELETE \ +-H "Accept: Application/json" \ +-H "Content-Type: application/json" http://{serverIP}:8080/remove-repository-scheduled-time/{repositoryname} +``` diff --git a/api/registryapilayer/registryapilayer.go b/api/registryapilayer/registryapilayer.go new file mode 100644 index 0000000..493e053 --- /dev/null +++ b/api/registryapilayer/registryapilayer.go @@ -0,0 +1,101 @@ +package registryapilayer + +import ( + "encoding/json" + "github.com/gin-gonic/gin" + "github.com/patrickmn/go-cache" + "log" + "net/http" + "time" + "ukiyo/internal/containeraccess" + "ukiyo/pkg/jencoder" +) + +type History struct { + Time string + Action string + Status int + Comment string +} + +const ( + _TimeZone = "Asia/Kolkata" +) + +func SaveContainerAccessKeys(r *gin.Engine, cache *cache.Cache) { + r.POST("/save-container-access-keys", func(c *gin.Context) { + var containerKey containeraccess.ContainerKeys + var responseObj containeraccess.ResponseObj + c.ShouldBindJSON(&containerKey) + log.Println("save-container-access-keys | request : " + jencoder.PrintJson(containerKey)) + + if len(containerKey.UserName) > 0 && len(containerKey.AccessToken) > 0 && len(containerKey.ServerAddress) > 0 { + res := containeraccess.InsertDockerRegData(containerKey) + responseObj = res + } else { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Invalid Parameter : save-container key" + } + log.Println("save-container-access-keys | response : " + jencoder.PrintJson(responseObj)) + SetHistory(responseObj, cache) + c.JSON(http.StatusOK, responseObj) + }) +} + +func EditContainerAccessKeys(r *gin.Engine, cache *cache.Cache) { + r.POST("/edit-container-access-keys", func(c *gin.Context) { + var containerKey containeraccess.ContainerKeys + var responseObj containeraccess.ResponseObj + c.ShouldBindJSON(&containerKey) + log.Println("edit-container-access-keys | request : " + jencoder.PrintJson(containerKey)) + + if len(containerKey.UserName) > 0 && len(containerKey.AccessToken) > 0 { + res := containeraccess.UpdateDockerRegData(containerKey) + responseObj = res + } else { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Invalid Parameter edit-container key" + } + log.Println("edit-container-access-keys | response :" + jencoder.PrintJson(responseObj)) + SetHistory(responseObj, cache) + c.JSON(http.StatusOK, responseObj) + }) +} + +func DeleteContainerAccessKeys(r *gin.Engine, cache *cache.Cache) { + r.DELETE("/delete-container-access-keys/:userName", func(c *gin.Context) { + var responseObj containeraccess.ResponseObj + name := c.Param("userName") + log.Println("delete-container-access-keys | request : userName=" + name) + if len(name) > 0 { + res := containeraccess.DeleteDockerRegData(name) + responseObj = res + } else { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Invalid Parameter delete-container key" + } + log.Println("delete-container-access-keys | response : " + jencoder.PrintJson(responseObj)) + SetHistory(responseObj, cache) + c.JSON(http.StatusOK, responseObj) + }) +} + +func SetHistory(obj containeraccess.ResponseObj, cash *cache.Cache) { + var historyArray []History + var history History + loc, _ := time.LoadLocation(_TimeZone) + history.Time = time.Now().In(loc).Format("2006-01-02 15:04:05") + history.Status = obj.ResponseCode + history.Action = obj.ResponseDesc + history.Comment = obj.ResponseDesc + + if x, _, found := cash.GetWithExpiration("history"); found { + err := json.Unmarshal(jencoder.PassJson(x), &historyArray) + if err != nil { + log.Println(err) + } + } + + historyArray = append(historyArray, history) + cash.Set("history", historyArray, 5*time.Minute) +} diff --git a/api/schedulerapilayer/schedulerapilayer.go b/api/schedulerapilayer/schedulerapilayer.go new file mode 100644 index 0000000..010b028 --- /dev/null +++ b/api/schedulerapilayer/schedulerapilayer.go @@ -0,0 +1,169 @@ +package schedulerapilayer + +import ( + "encoding/json" + "github.com/gin-gonic/gin" + "github.com/patrickmn/go-cache" + "log" + "net/http" + "time" + "ukiyo/internal/containerscheduler" + "ukiyo/internal/util" + "ukiyo/pkg/jencoder" + "ukiyo/pkg/scheduler/eventsheduler" +) + +type History struct { + Time string + Action string + Status int + Comment string +} + +const ( + _TimeZone = "Asia/Kolkata" + _format = "Jan _2 2006 3:04:05 PM" +) + +type PodsDetailsObj struct { + Name string `json:"name"` + BindingPort []containerscheduler.BindingPort `json:"bindingPort"` + ScheduledTime string `json:"scheduledTime"` + ScheduledDowntime bool `json:"scheduledDowntime"` +} + +func SaveRepositoryScheduledTime(r *gin.Engine, cache *cache.Cache) { + r.POST("/save-repository-scheduled-time", func(c *gin.Context) { + var podsDetailsObj PodsDetailsObj + var responseObj containerscheduler.ResponseObj + c.ShouldBindJSON(&podsDetailsObj) + log.Println("save-repository-scheduled-time | request : " + jencoder.PrintJson(podsDetailsObj)) + + loc, _ := time.LoadLocation(_TimeZone) + if len(podsDetailsObj.Name) > 0 && util.BindPortValidator(podsDetailsObj.BindingPort, "") { + podsDetails, status := RequestDateConverter(podsDetailsObj) + if podsDetails.ScheduledDowntime && status == 0 { + if ((podsDetails.ScheduledAt - time.Now().In(loc).UnixNano()/int64(time.Millisecond)) / 60000) >= 5 { + res := containerscheduler.InsertPodsData(podsDetails) + if res.ResponseCode == 0 { + eventsheduler.ScheduledRunner("save", podsDetails.Name, podsDetails.ScheduledAt, cache) + } + responseObj = res + } else { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Invalid Scheduled Time. Minimum 5Mnt Required" + } + } else if status == 0 { + res := containerscheduler.InsertPodsData(podsDetails) + responseObj = res + } else { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Invalid Parameter : save-repository" + } + } else { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Invalid Parameter : save-repository" + } + log.Println("save-repository-scheduled-time | response : " + jencoder.PrintJson(responseObj)) + SetHistory(responseObj, cache, "save-repository") + c.JSON(http.StatusOK, responseObj) + }) +} + +func EditRepositoryScheduledTime(r *gin.Engine, cache *cache.Cache) { + r.POST("/edit-repository-scheduled-time", func(c *gin.Context) { + var podsDetailsObj PodsDetailsObj + var responseObj containerscheduler.ResponseObj + c.ShouldBindJSON(&podsDetailsObj) + log.Println("edit-repository-scheduled-time | request : " + jencoder.PrintJson(podsDetailsObj)) + + if len(podsDetailsObj.Name) > 0 && util.BindPortValidator(podsDetailsObj.BindingPort, podsDetailsObj.Name) { + podsDetails, status := RequestDateConverter(podsDetailsObj) + loc, _ := time.LoadLocation(_TimeZone) + if podsDetails.ScheduledDowntime && status == 0 { + log.Println((podsDetails.ScheduledAt - time.Now().In(loc).UnixNano()/int64(time.Millisecond)) / 60000) + if ((podsDetails.ScheduledAt - time.Now().In(loc).UnixNano()/int64(time.Millisecond)) / 60000) >= 5 { + res := containerscheduler.UpdatePodsData(podsDetails) + if res.ResponseCode == 0 { + eventsheduler.ScheduledRunner("edit", podsDetails.Name, podsDetails.ScheduledAt, cache) + } + responseObj = res + } else { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Invalid Scheduled Time. Minimum 5Mnt Required" + } + } else if !podsDetails.ScheduledDowntime && status == 0 { + res := containerscheduler.UpdatePodsData(podsDetails) + if res.ResponseCode == 0 { + eventsheduler.ScheduledRunner("delete", podsDetails.Name, 0, cache) + } + responseObj = res + } else { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Invalid Parameter : edit-repository" + } + } else { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Invalid Parameter : edit-repository" + } + log.Println("edit-repository-scheduled-time | response : " + jencoder.PrintJson(responseObj)) + SetHistory(responseObj, cache, "edit-repository") + c.JSON(http.StatusOK, responseObj) + }) +} + +func DeleteRepositoryScheduledTime(r *gin.Engine, cache *cache.Cache) { + r.DELETE("/remove-repository-scheduled-time/:name", func(c *gin.Context) { + var responseObj containerscheduler.ResponseObj + name := c.Param("name") + log.Println("remove-repository-scheduled-time | request : name=" + name) + if len(name) > 0 { + res := containerscheduler.DeleteDockerRegData(name) + if res.ResponseCode == 0 { + eventsheduler.ScheduledRunner("delete", name, 0, cache) + } + responseObj = res + } else { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Invalid Parameter : remove-repository" + } + log.Println("remove-repository-scheduled-time | response : " + jencoder.PrintJson(responseObj)) + SetHistory(responseObj, cache, "remove-repository") + c.JSON(http.StatusOK, responseObj) + }) +} + +func RequestDateConverter(podsDetailsObj PodsDetailsObj) (containerscheduler.PodsDetails, int) { + var podsDetails containerscheduler.PodsDetails + podsDetails.Name = podsDetailsObj.Name + podsDetails.BindingPort = podsDetailsObj.BindingPort + podsDetails.ScheduledDowntime = podsDetailsObj.ScheduledDowntime + loc, _ := time.LoadLocation(_TimeZone) + time1, err := time.ParseInLocation(_format, podsDetailsObj.ScheduledTime, loc) + if err != nil { + log.Println("Error while parsing date :", err) + return podsDetails, 1 + } + podsDetails.ScheduledAt = time.Time(time1).In(loc).UnixNano() / int64(time.Millisecond) + return podsDetails, 0 +} + +func SetHistory(obj containerscheduler.ResponseObj, cash *cache.Cache, desc string) { + var historyArray []History + var history History + loc, _ := time.LoadLocation(_TimeZone) + history.Time = time.Now().In(loc).Format("2006-01-02 15:04:05") + history.Status = obj.ResponseCode + history.Action = obj.ResponseDesc + history.Comment = desc + + if x, _, found := cash.GetWithExpiration("history"); found { + err := json.Unmarshal(jencoder.PassJson(x), &historyArray) + if err != nil { + log.Println(err) + } + } + + historyArray = append(historyArray, history) + cash.Set("history", historyArray, 5*time.Minute) +} diff --git a/cmd/ukiyo/main b/cmd/ukiyo/main new file mode 100644 index 0000000..4bcdceb Binary files /dev/null and b/cmd/ukiyo/main differ diff --git a/dbs/secret b/dbs/app.properties similarity index 100% rename from dbs/secret rename to dbs/app.properties diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index e69de29..0000000 diff --git a/go.mod b/go.mod index 1ef4462..934a0c4 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,8 @@ go 1.14 require ( github.com/fsouza/go-dockerclient v1.6.5 github.com/gin-gonic/gin v1.6.3 + github.com/olekukonko/tablewriter v0.0.4 + github.com/patrickmn/go-cache v2.1.0+incompatible github.com/sonyarouje/simdb v0.0.0-20181202125413-c2488dfc374a - github.com/stretchr/testify v1.6.1 + github.com/stretchr/testify v1.4.0 ) diff --git a/go.sum b/go.sum index 74c8076..3754fe2 100644 --- a/go.sum +++ b/go.sum @@ -87,12 +87,16 @@ github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -106,6 +110,8 @@ github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJ github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -126,8 +132,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= @@ -204,8 +208,6 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/caching/cacheupdate/cacheupdate.go b/internal/caching/cacheupdate/cacheupdate.go new file mode 100644 index 0000000..d9fbf36 --- /dev/null +++ b/internal/caching/cacheupdate/cacheupdate.go @@ -0,0 +1,94 @@ +package cacheupdate + +import ( + "encoding/json" + "github.com/patrickmn/go-cache" + "log" + "strconv" + "time" + "ukiyo/pkg/jencoder" +) + +const ( + _TimeZone = "Asia/Kolkata" +) + +type CashObj struct { + ImageName string `json:"imageName"` + ScheduledAt int64 `json:"scheduledAt"` +} + +type History struct { + Time string + Action string + Status int + Comment string +} + +var val int +var mnt int +var imageName string + +func CacheUpdate(name string, imageName string, c *cache.Cache) { + var cashObj CashObj + if x, y, found := c.GetWithExpiration(name); found { + err := json.Unmarshal(jencoder.PassJson(x), &cashObj) + if err != nil { + log.Println(err) + } + cashObj.ImageName = imageName + c.Set(name, cashObj, time.Duration(time.Time(y).Sub(time.Now()).Minutes()+1)*time.Minute) + log.Println("Successfully Cash update for the deployment") + } +} + +func TimeCalculator(name string, c *cache.Cache) (int, int, string) { + var cashObj CashObj + loc, _ := time.LoadLocation(_TimeZone) + if x, _, found := c.GetWithExpiration(name); found { + err := json.Unmarshal(jencoder.PassJson(x), &cashObj) + if err != nil { + log.Println(err) + } + val = int(cashObj.ScheduledAt-time.Now().In(loc).UnixNano()/int64(time.Millisecond)) + 60000 + if val > 0 { + mnt = val / 60000 + if len(cashObj.ImageName) > 0 { + imageName = cashObj.ImageName + } else { + imageName = "waiting" + } + return mnt, (val - mnt*60000) / 1000, imageName + } else { + return 0, 0, "-" + } + } else { + return 0, 0, "-" + } +} + +func HistoryFetch(c *cache.Cache) [][]string { + var historyArr []History + var historyData [][]string + if x, _, found := c.GetWithExpiration("history"); found { + err := json.Unmarshal(jencoder.PassJson(x), &historyArr) + if err != nil { + log.Println(err) + } + } + if len(historyArr) > 10 { + for x := 0; x < (len(historyArr) - 10); x++ { + historyArr = RemoveIndex(historyArr, 8) + } + } + if len(historyArr) > 0 { + for _, histor := range historyArr { + historyData = append(historyData, []string{histor.Time, histor.Action, strconv.Itoa(histor.Status), histor.Comment}) + } + } + return historyData +} + +func RemoveIndex(s []History, index int) []History { + return append(s[:index], s[index+1:]...) +} diff --git a/internal/caching/caching.go b/internal/caching/caching.go new file mode 100644 index 0000000..e92fbe8 --- /dev/null +++ b/internal/caching/caching.go @@ -0,0 +1,68 @@ +package caching + +import ( + "encoding/json" + "github.com/patrickmn/go-cache" + "log" + "time" + "ukiyo/pkg/jencoder" + "ukiyo/pkg/scheduler" +) + +var buildTimeMilSec int64 +var buildTimeMnt int64 + +const ( + _TimeZone = "Asia/Kolkata" +) + +type CashObj struct { + ImageName string `json:"imageName"` + ScheduledAt int64 `json:"scheduledAt"` +} + +func CacheRunner(name string, imageName string, scheduledAt int64, c *cache.Cache) { + var cashObj CashObj + cashObj.ImageName = imageName + cashObj.ScheduledAt = scheduledAt + + loc, _ := time.LoadLocation(_TimeZone) + buildTimeMilSec = scheduledAt - time.Now().In(loc).UnixNano()/int64(time.Millisecond) + buildTimeMnt = (buildTimeMilSec / 60000) + 1 + + c.Set(name, cashObj, time.Duration(buildTimeMnt+2)*time.Minute) + + log.Println(buildTimeMnt, buildTimeMnt+2) + log.Println("Applied AfterFunc : " + name) + log.Println(c.Items()) + + time.AfterFunc(time.Duration(buildTimeMnt)*time.Minute, func() { + ContainerRunFunc(name, scheduledAt, c) + }) +} + +func ContainerRunFunc(name string, scheduledAt int64, c *cache.Cache) { + loc, _ := time.LoadLocation(_TimeZone) + if ((scheduledAt - time.Now().In(loc).UnixNano()/int64(time.Millisecond)) > 60000) || + ((scheduledAt - time.Now().In(loc).UnixNano()/int64(time.Millisecond)) < 60000) { + + log.Println("Trigger Start Container @ : " + name) + + var cashObj CashObj + if x, found := c.Get(name); found { + err := json.Unmarshal(jencoder.PassJson(x), &cashObj) + if err != nil { + log.Println(err) + } + if len(cashObj.ImageName) > 0 { + log.Println("Starting to create scheduled container : " + cashObj.ImageName) + scheduler.DeploymentProcess(name, cashObj.ImageName, c) + } else { + log.Println("Failed to create scheduled container, No image in cache : null") + } + } + + } else { + log.Println("Not Valid Time for Start Container") + } +} diff --git a/internal/containeraccess/containeraccess.go b/internal/containeraccess/containeraccess.go new file mode 100644 index 0000000..dc3433d --- /dev/null +++ b/internal/containeraccess/containeraccess.go @@ -0,0 +1,164 @@ +package containeraccess + +import ( + "github.com/fsouza/go-dockerclient" + "log" + "strings" + "ukiyo/internal/dbconfig" +) + +type ContainerKeys struct { + UserName string `json:"userName"` + Desc string `json:"desc"` + AccessToken string `json:"accessToken"` + Email string `json:"email"` + ServerAddress string `json:"serverAddress"` +} + +type ResponseObj struct { + ResponseCode int + ResponseDesc string +} + +const ( + _dockerRegPk = "userName" +) + +func (c ContainerKeys) ID() (jsonField string, value interface{}) { + value = c.UserName + jsonField = _dockerRegPk + return +} + +var val int + +func RequestLoginKeys(userName string) docker.AuthConfiguration { + var registries = QueryRecodeInDB(userName) + + if len(registries.UserName) == 0 { + panic("User's Registry Credentials is not exist. : " + userName) + } + + return docker.AuthConfiguration{ + Username: registries.UserName, + Password: registries.AccessToken, + Email: registries.Email, + ServerAddress: registries.ServerAddress, + } +} + +func InsertDockerRegData(containerKeys ContainerKeys) ResponseObj { + var responseObj ResponseObj + var registries = QueryListRecodeInDB(containerKeys.UserName) + if len(registries) > 0 { + for _, registry := range registries { + if strings.EqualFold(registry.UserName, containerKeys.UserName) { + val++ + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Already Exist Container key :" + containerKeys.UserName + log.Println("Already Exist The Container key: " + registry.UserName) + } + } + } + if val == 0 { + responseObj = InsertDb(containerKeys) + } + return responseObj +} + +func UpdateDockerRegData(containerKeys ContainerKeys) ResponseObj { + var responseObj ResponseObj + + var registries = QueryRecodeInDB(containerKeys.UserName) + + log.Println(registries) + + if len(registries.UserName) != 0 { + registries.Desc = containerKeys.Desc + registries.AccessToken = containerKeys.AccessToken + registries.Email = containerKeys.Email + registries.ServerAddress = containerKeys.ServerAddress + responseObj = UpdateDb(registries) + } else { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Update Failed Container Key :" + containerKeys.UserName + } + + return responseObj +} + +func DeleteDockerRegData(userName string) ResponseObj { + var responseObj ResponseObj + + var registries = QueryRecodeInDB(userName) + + if len(registries.UserName) != 0 { + responseObj = DeleteDb(registries) + } else { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Delete Failed Container key :" + userName + } + return responseObj +} + +func QueryListRecodeInDB(Username string) []ContainerKeys { + var registries []ContainerKeys + err := dbconfig.DbConfig().Open(ContainerKeys{}).Where(_dockerRegPk, "=", Username).Get().AsEntity(®istries) + if err != nil { + log.Println(err) + return registries + } + return registries +} + +func QueryRecodeInDB(Username string) ContainerKeys { + var registries ContainerKeys + err := dbconfig.DbConfig().Open(ContainerKeys{}).Where(_dockerRegPk, "=", Username).First().AsEntity(®istries) + if err != nil { + log.Println(err) + return registries + } + return registries +} + +func InsertDb(containerKeys ContainerKeys) ResponseObj { + var responseObj ResponseObj + err := dbconfig.DbConfig().Insert(containerKeys) + if err != nil { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Insert Failed Container key : " + containerKeys.UserName + log.Println(err) + } else { + responseObj.ResponseCode = 0 + responseObj.ResponseDesc = "Successfully Added Container Key :" + containerKeys.UserName + } + return responseObj +} + +func UpdateDb(containerKeys ContainerKeys) ResponseObj { + var responseObj ResponseObj + err := dbconfig.DbConfig().Update(containerKeys) + if err != nil { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Update Failed Container key :" + containerKeys.UserName + log.Println(err) + } else { + responseObj.ResponseCode = 0 + responseObj.ResponseDesc = "Successfully Updated Container key :" + containerKeys.UserName + } + return responseObj +} + +func DeleteDb(containerKeys ContainerKeys) ResponseObj { + var responseObj ResponseObj + err := dbconfig.DbConfig().Delete(containerKeys) + if err != nil { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Delete Failed Container Key :" + containerKeys.UserName + log.Println(err) + } else { + responseObj.ResponseCode = 0 + responseObj.ResponseDesc = "Successfully Deleted Container Key :" + containerKeys.UserName + } + return responseObj +} diff --git a/internal/containerscheduler/containerscheduler.go b/internal/containerscheduler/containerscheduler.go new file mode 100644 index 0000000..c4b089c --- /dev/null +++ b/internal/containerscheduler/containerscheduler.go @@ -0,0 +1,167 @@ +package containerscheduler + +import ( + "log" + "strings" + "ukiyo/internal/dbconfig" +) + +type PodsDetails struct { + Name string `json:"name"` + BindingPort []BindingPort `json:"bindingPort"` + ScheduledAt int64 `json:"scheduledAt"` + ScheduledDowntime bool `json:"scheduledDowntime"` +} + +type BindingPort struct { + ExportPort string `json:"exportPort"` + InternalPort string `json:"internalPort"` +} + +type ResponseObj struct { + ResponseCode int + ResponseDesc string +} + +const ( + _podsPk = "name" +) + +var val int +var buildTimeMilSec int64 +var buildTimeMnt int64 + +type CashObj struct { + ImageName string `json:"imageName"` + ScheduledAt int64 `json:"scheduledAt"` +} + +func (c PodsDetails) ID() (jsonField string, value interface{}) { + value = c.Name + jsonField = _podsPk + return +} + +func GetBindingPortsForContainerCreate(name string) []string { + var data []string + pod := QueryRecodeInDB(name) + if len(pod.BindingPort) > 0 { + for _, port := range pod.BindingPort { + data = append(data, port.ExportPort+":"+port.InternalPort) + } + } + return data +} + +func InsertPodsData(podsDetails PodsDetails) ResponseObj { + var responseObj ResponseObj + var pods = QueryListRecodeInDB() + if len(pods) > 0 { + for _, pod := range pods { + if strings.EqualFold(pod.Name, podsDetails.Name) { + val++ + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Already Exist Pod details" + log.Println("Already Exist The Pods : " + pod.Name) + } + } + } + if val == 0 { + responseObj = InsertDb(podsDetails) + } + return responseObj +} + +func UpdatePodsData(podsDetails PodsDetails) ResponseObj { + var responseObj ResponseObj + + var pod = QueryRecodeInDB(podsDetails.Name) + + if len(pod.Name) != 0 { + pod.BindingPort = podsDetails.BindingPort + pod.ScheduledAt = podsDetails.ScheduledAt + pod.ScheduledDowntime = podsDetails.ScheduledDowntime + responseObj = UpdateDb(pod) + } else { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Update Failed pod." + } + + return responseObj +} + +func DeleteDockerRegData(name string) ResponseObj { + var responseObj ResponseObj + + var pod = QueryRecodeInDB(name) + + if len(pod.Name) != 0 { + responseObj = DeleteDb(pod) + } else { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Delete Failed pod." + } + return responseObj +} + +func QueryListRecodeInDB() []PodsDetails { + var pods []PodsDetails + err := dbconfig.DbConfig().Open(PodsDetails{}).Get().AsEntity(&pods) + if err != nil { + log.Println(err) + return pods + } + return pods +} + +func QueryRecodeInDB(Name string) PodsDetails { + var pod PodsDetails + err := dbconfig.DbConfig().Open(PodsDetails{}).Where(_podsPk, "=", Name).First().AsEntity(&pod) + if err != nil { + log.Println(err) + return pod + } + return pod +} + +func InsertDb(pods PodsDetails) ResponseObj { + var responseObj ResponseObj + err := dbconfig.DbConfig().Insert(pods) + if err != nil { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Insert Failed pod details" + log.Println(err) + } else { + responseObj.ResponseCode = 0 + responseObj.ResponseDesc = "Successfully Added pod details" + } + return responseObj +} + +func UpdateDb(pod PodsDetails) ResponseObj { + var responseObj ResponseObj + err := dbconfig.DbConfig().Update(pod) + if err != nil { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Update Failed pod details" + log.Println(err) + } else { + responseObj.ResponseCode = 0 + responseObj.ResponseDesc = "Successfully Updated pod details" + } + return responseObj +} + +func DeleteDb(pod PodsDetails) ResponseObj { + var responseObj ResponseObj + err := dbconfig.DbConfig().Delete(pod) + if err != nil { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Delete Failed pod details" + log.Println(err) + } else { + responseObj.ResponseCode = 0 + responseObj.ResponseDesc = "Successfully Deleted pod details" + } + return responseObj +} diff --git a/internal/dbconfig/dbconfig.go b/internal/dbconfig/dbconfig.go new file mode 100644 index 0000000..105a15a --- /dev/null +++ b/internal/dbconfig/dbconfig.go @@ -0,0 +1,13 @@ +package dbconfig + +import ( + "github.com/sonyarouje/simdb/db" +) + +func DbConfig() *db.Driver { + driver, err := db.New("dbs") + if err != nil { + panic(err) + } + return driver +} diff --git a/internal/util/util.go b/internal/util/util.go new file mode 100644 index 0000000..82792ba --- /dev/null +++ b/internal/util/util.go @@ -0,0 +1,44 @@ +package util + +import ( + "log" + "ukiyo/internal/containerscheduler" +) + +func BindPortValidator(bindingPort []containerscheduler.BindingPort, name string) bool { + + pods := containerscheduler.QueryListRecodeInDB() + var portArr []string + if len(pods) > 0 { + for _, pod := range pods { + if len(pod.BindingPort) > 0 && pod.Name != name { + for _, bindingPort := range pod.BindingPort { + portArr = append(portArr, bindingPort.ExportPort) + } + } + } + } + log.Println(portArr) + + if len(bindingPort) > 0 { + for _, ports := range bindingPort { + if len(ports.InternalPort) == 0 && len(ports.ExportPort) == 0 { + return false + } + if contains(portArr, ports.ExportPort) { + return false + } + } + return true + } + return false +} + +func contains(arr []string, str string) bool { + for _, a := range arr { + if a == str { + return true + } + } + return false +} diff --git a/internal/validator/validator.go b/internal/validator/validator.go deleted file mode 100644 index e69de29..0000000 diff --git a/internal/validator/validator_test.go b/internal/validator/validator_test.go deleted file mode 100644 index e69de29..0000000 diff --git a/main.go b/main.go index 2e6cef7..3f952f2 100644 --- a/main.go +++ b/main.go @@ -1,17 +1,38 @@ package main import ( - "ukiyo/pkg/apilayer" - "ukiyo/pkg/webhooks" - "github.com/gin-gonic/gin" + "github.com/patrickmn/go-cache" + "log" + "os" + "time" + "ukiyo/api/registryapilayer" + "ukiyo/api/schedulerapilayer" + "ukiyo/pkg/process" + "ukiyo/pkg/scheduler/eventsheduler" + "ukiyo/pkg/webhook-listener" ) func main() { + + file, err := os.OpenFile("dbs/info.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + log.Fatal(err) + } + defer file.Close() + log.SetOutput(file) + r := gin.Default() - webhooks.HooksListener(r) - webhooks.HealthCheck(r) - apilayer.AddContainer(r) - apilayer.EditContainerToken(r) + c := cache.New(1*time.Minute, 1*time.Minute) + webhook_listener.HealthCheck(r) + webhook_listener.HooksListener(r, c) + registryapilayer.SaveContainerAccessKeys(r, c) + registryapilayer.EditContainerAccessKeys(r, c) + registryapilayer.DeleteContainerAccessKeys(r, c) + schedulerapilayer.SaveRepositoryScheduledTime(r, c) + schedulerapilayer.EditRepositoryScheduledTime(r, c) + schedulerapilayer.DeleteRepositoryScheduledTime(r, c) + eventsheduler.ScheduledRunner("run", "", 0, c) + go process.Process(c) r.Run(":8080") } diff --git a/pkg/apilayer/apilayer.go b/pkg/apilayer/apilayer.go deleted file mode 100644 index 4fe3ed0..0000000 --- a/pkg/apilayer/apilayer.go +++ /dev/null @@ -1,59 +0,0 @@ -package apilayer - -import ( - "encoding/json" - "log" - "net/http" - "ukiyo/pkg/auth" - "ukiyo/pkg/util" - - "github.com/gin-gonic/gin" -) - -func AddContainer(r *gin.Engine) { - r.POST("/add-container", func(c *gin.Context) { - var dockerRegistry auth.DockerRegistry - var responseObj util.ResponseObj - c.ShouldBindJSON(&dockerRegistry) - - b, err := json.Marshal(dockerRegistry) - if err != nil { - log.Println(err) - return - } - log.Println("Requested container registry insert details" + string(b)) - - if len(dockerRegistry.Username) > 0 && len(dockerRegistry.AccessToken) > 0 && len(dockerRegistry.ServerAddress) > 0 { - res := auth.InsertDockerRegData(dockerRegistry) - responseObj = res - } else { - responseObj.ResponseCode = 1 - responseObj.ResponseDesc = "Invalid Parameter" - } - c.JSON(http.StatusOK, responseObj) - }) -} - -func EditContainerToken(r *gin.Engine) { - r.POST("/edit-container-token", func(c *gin.Context) { - var registryUpdate auth.RegistryUpdate - var responseObj util.ResponseObj - c.ShouldBindJSON(®istryUpdate) - - b, err := json.Marshal(registryUpdate) - if err != nil { - log.Println(err) - return - } - log.Println("Requested container registry update details" + string(b)) - - if len(registryUpdate.Username) > 0 && len(registryUpdate.AccessToken) > 0 { - res := auth.UpdateDockerRegData(registryUpdate) - responseObj = res - } else { - responseObj.ResponseCode = 1 - responseObj.ResponseDesc = "Invalid Parameter" - } - c.JSON(http.StatusOK, responseObj) - }) -} diff --git a/pkg/apilayer/apilayer_test.go b/pkg/apilayer/apilayer_test.go deleted file mode 100644 index 9731874..0000000 --- a/pkg/apilayer/apilayer_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package apilayer - -import ( - "encoding/json" - "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" - "log" - "net/http" - "net/http/httptest" - "strings" - "testing" -) - -func TestAddContainer(t *testing.T) { - r := gin.Default() - AddContainer(r) - - registry := `{ - "username":"agentukiyo", - "repoName":"Ukiyo Docker registry", - "accessToken":"f44e334e-1440-4166-a16f-d8fc9d0eb188", - "email":"hansika.16@itfac.mrt.ac.lk", - "serverAddress":"http://docker.io/v1" - }` - - w := httptest.NewRecorder() - req, _ := http.NewRequest("POST", "/add-container", strings.NewReader(registry)) - r.ServeHTTP(w, req) - - assert.Equal(t, http.StatusOK, w.Code) - - var response map[string]string - err := json.Unmarshal([]byte(w.Body.String()), &response) - log.Println(response) - value, exists := response["ResponseCode"] - - assert.Nil(t, err) - assert.True(t, exists) - assert.Equal(t, 0, value) -} - -func TestEditContainer(t *testing.T) { - r := gin.Default() - EditContainerToken(r) - - registry := `{ - "username":"agentukiyo", - "accessToken":"f44e334e-1440-4166-a16f-d8fc9d0eb188", - }` - - w := httptest.NewRecorder() - req, _ := http.NewRequest("POST", "/add-container", strings.NewReader(registry)) - r.ServeHTTP(w, req) - - assert.Equal(t, http.StatusOK, w.Code) - - var response map[string]string - err := json.Unmarshal([]byte(w.Body.String()), &response) - log.Println(response) - value, exists := response["ResponseCode"] - - assert.Nil(t, err) - assert.True(t, exists) - assert.Equal(t, 0, value) -} \ No newline at end of file diff --git a/pkg/auth/dockerauth.go b/pkg/auth/dockerauth.go deleted file mode 100644 index d2395bf..0000000 --- a/pkg/auth/dockerauth.go +++ /dev/null @@ -1,119 +0,0 @@ -package auth - -import ( - "log" - "strings" - "ukiyo/pkg/util" - - docker "github.com/fsouza/go-dockerclient" - "github.com/sonyarouje/simdb/db" -) - -type DockerRegistry struct { - Username string `json:"userName"` - RepoName string `json:"repoName"` - AccessToken string `json:"accessToken"` - Email string `json:"email"` - ServerAddress string `json:"serverAddress"` -} - -type RegistryUpdate struct { - Username string `json:"userName"` - AccessToken string `json:"accessToken"` -} - -var val int - -func DockerLogin(userName string) docker.AuthConfiguration { - - driver, err := db.New("dbs") - if err != nil { - panic(err) - } - - var registries DockerRegistry - err = driver.Open(DockerRegistry{}).Where("userName", "=", userName).First().AsEntity(®istries) - if err != nil { - panic(err) - } - - if len(registries.Username) == 0 { - panic("User's Registry Credentials is not exist. : " + userName) - } - - return docker.AuthConfiguration{ - Username: registries.Username, - Password: registries.AccessToken, - Email: registries.Email, - ServerAddress: registries.ServerAddress, - } -} - -func InsertDockerRegData(dockerRegistry DockerRegistry) util.ResponseObj { - var responseObj util.ResponseObj - - driver, err := db.New("dbs") - if err != nil { - panic(err) - } - - var registries []DockerRegistry - err = driver.Open(DockerRegistry{}).Where("userName", "=", dockerRegistry.Username).Get().AsEntity(®istries) - if err != nil { - panic(err) - } - - for _, registry := range registries { - if strings.EqualFold(registry.Username, dockerRegistry.Username) { - val = val + 1 - responseObj.ResponseCode = 1 - responseObj.ResponseDesc = "Already Exist" - log.Println("Already Exist The Registry : " + registry.Username) - } - } - - if val == 0 { - err = driver.Insert(dockerRegistry) - if err != nil { - panic(err) - } - responseObj.ResponseCode = 0 - responseObj.ResponseDesc = "Successfully Added" - } - - return responseObj -} - -func UpdateDockerRegData(registryUpdate RegistryUpdate) util.ResponseObj { - var responseObj util.ResponseObj - - driver, err := db.New("dbs") - if err != nil { - panic(err) - } - - var registries DockerRegistry - err = driver.Open(DockerRegistry{}).Where("userName", "=", registryUpdate.Username).First().AsEntity(®istries) - if err != nil { - panic(err) - } - - registries.AccessToken = registryUpdate.AccessToken - err = driver.Update(registries) - if err != nil { - responseObj.ResponseCode = 1 - responseObj.ResponseDesc = "Update Failed" - panic(err) - } else { - responseObj.ResponseCode = 0 - responseObj.ResponseDesc = "Successfully Updated" - } - - return responseObj -} - -func (c DockerRegistry) ID() (jsonField string, value interface{}) { - value = c.Username - jsonField = "userName" - return -} diff --git a/pkg/container/container.go b/pkg/container/container.go deleted file mode 100644 index e69de29..0000000 diff --git a/pkg/container/container_test.go b/pkg/container/container_test.go deleted file mode 100644 index e69de29..0000000 diff --git a/pkg/jencoder/jencoder.go b/pkg/jencoder/jencoder.go new file mode 100644 index 0000000..4dfdeb7 --- /dev/null +++ b/pkg/jencoder/jencoder.go @@ -0,0 +1,24 @@ +package jencoder + +import ( + "encoding/json" + "log" +) + +func PrintJson(obj interface{}) string { + b, err := json.Marshal(obj) + if err != nil { + log.Println(err) + return "json marshal error" + } + return string(b) +} + +func PassJson(obj interface{}) []byte { + b, err := json.Marshal(obj) + if err != nil { + log.Println(err) + return nil + } + return b +} diff --git a/pkg/manager/dockercreater/dockercreater.go b/pkg/manager/dockercreater/dockercreater.go new file mode 100644 index 0000000..dd053a9 --- /dev/null +++ b/pkg/manager/dockercreater/dockercreater.go @@ -0,0 +1,102 @@ +package dockercreater + +import ( + "encoding/json" + docker "github.com/fsouza/go-dockerclient" + "github.com/patrickmn/go-cache" + "log" + "strings" + "time" + "ukiyo/internal/containerscheduler" + "ukiyo/pkg/jencoder" +) + +type ResponseObj struct { + Name string + ContainerId string + ResponseCode int + ResponseDesc string +} + +type History struct { + Time string + Action string + Status int + Comment string +} + +const ( + _TimeZone = "Asia/Kolkata" +) + +func ContainerCreate(name string, imageName string, c *cache.Cache) (ResponseObj, error) { + var responseObj ResponseObj + client, err := docker.NewClientFromEnv() + if err != nil { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Error Creating Docker Client Env for : " + name + return responseObj, err + } + + ports := containerscheduler.GetBindingPortsForContainerCreate(name) + portBindings := make(map[docker.Port][]docker.PortBinding) + exposedPorts := map[docker.Port]struct{}{} + + if len(ports) > 0 { + for _, p := range ports { + s := strings.Split(p, ":") + extPort, intPort := s[0], s[1] + log.Println("extPort : " + extPort + " intPort : " + intPort) + dockerPort := docker.Port(intPort + "/tcp") + portBindings[dockerPort] = []docker.PortBinding{{HostIP: "0.0.0.0", HostPort: extPort}} + exposedPorts[dockerPort] = struct{}{} + } + } else { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "No valid Binding Ports for :" + name + return responseObj, err + } + + container, err := client.CreateContainer(docker.CreateContainerOptions{ + Name: name, + Config: &docker.Config{ + Image: imageName, + ExposedPorts: exposedPorts, + }, + HostConfig: &docker.HostConfig{ + PortBindings: portBindings, + }, + }) + if err != nil { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Error Creating Container for :" + name + return responseObj, err + } + log.Println(container) + + responseObj.ResponseCode = 0 + responseObj.ContainerId = container.ID + responseObj.ResponseDesc = "Successfully Create The Container :" + name + SetHistory(responseObj, c) + return responseObj, err +} + +func SetHistory(obj ResponseObj, cash *cache.Cache) { + var historyArray []History + var history History + loc, _ := time.LoadLocation(_TimeZone) + history.Time = time.Now().In(loc).Format("2006-01-02 15:04:05") + history.Status = obj.ResponseCode + history.Action = obj.ResponseDesc + history.Comment = obj.ResponseDesc + + if x, _, found := cash.GetWithExpiration("history"); found { + err := json.Unmarshal(jencoder.PassJson(x), &historyArray) + if err != nil { + log.Println(err) + } + } + + historyArray = append(historyArray, history) + cash.Set("history", historyArray, 5*time.Minute) +} diff --git a/pkg/manager/dockerpull/dockerpull.go b/pkg/manager/dockerpull/dockerpull.go new file mode 100644 index 0000000..b389821 --- /dev/null +++ b/pkg/manager/dockerpull/dockerpull.go @@ -0,0 +1,103 @@ +package dockerpull + +import ( + "encoding/json" + docker "github.com/fsouza/go-dockerclient" + "github.com/patrickmn/go-cache" + "log" + "strings" + "time" + "ukiyo/internal/containeraccess" + "ukiyo/pkg/jencoder" + "ukiyo/pkg/webhook" +) + +type ResponseObj struct { + Name string + ImageName string + ResponseCode int + ResponseDesc string +} + +type History struct { + Time string + Action string + Status int + Comment string +} + +const ( + _TimeZone = "Asia/Kolkata" +) + +var val int +var ImageName string + +func PullToDocker(str webhook.Response, cash *cache.Cache) (ResponseObj, error) { + var responseObj ResponseObj + log.Println("docker pull by repo name : " + jencoder.PrintJson(str)) + ImageName = str.RepoName + ":" + str.Tag + + client, err := docker.NewClientFromEnv() + if err != nil { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Error Creating Docker Client Env" + return responseObj, err + } + + err = client.PullImage(webhook.DockerPullImage(str), containeraccess.RequestLoginKeys(str.Namespace)) + if err != nil { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Error Pulling Images" + return responseObj, err + } + + imgs, err := client.ListImages(docker.ListImagesOptions{All: false}) + if err != nil { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Error List images" + return responseObj, err + } + + for _, img := range imgs { + for _, repo := range img.RepoTags { + if strings.EqualFold(repo, ImageName) { + val++ + log.Println("Successfully pull the images : " + repo) + } + } + } + + if val > 0 { + responseObj.ResponseCode = 0 + responseObj.Name = str.Name + responseObj.ImageName = ImageName + responseObj.ResponseDesc = "Successfully pull the images" + } else { + responseObj.ResponseCode = 0 + responseObj.ResponseDesc = "Failed pull the images" + } + + SetHistory(responseObj, cash) + return responseObj, err +} + +func SetHistory(obj ResponseObj, cash *cache.Cache) { + var historyArray []History + var history History + loc, _ := time.LoadLocation(_TimeZone) + history.Time = time.Now().In(loc).Format("2006-01-02 15:04:05") + history.Status = obj.ResponseCode + history.Action = obj.ResponseDesc + history.Comment = obj.ResponseDesc + + if x, _, found := cash.GetWithExpiration("history"); found { + err := json.Unmarshal(jencoder.PassJson(x), &historyArray) + if err != nil { + log.Println(err) + } + } + + historyArray = append(historyArray, history) + cash.Set("history", historyArray, 5*time.Minute) +} diff --git a/pkg/manager/dockerremove/dockerremove.go b/pkg/manager/dockerremove/dockerremove.go new file mode 100644 index 0000000..07063b6 --- /dev/null +++ b/pkg/manager/dockerremove/dockerremove.go @@ -0,0 +1,96 @@ +package dockerremove + +import ( + "encoding/json" + docker "github.com/fsouza/go-dockerclient" + "github.com/patrickmn/go-cache" + "log" + "strings" + "time" + "ukiyo/pkg/jencoder" +) + +type ResponseObj struct { + ResponseCode int + ResponseDesc string +} + +type History struct { + Time string + Action string + Status int + Comment string +} + +const ( + _TimeZone = "Asia/Kolkata" +) + +func RemoveRunningContainer(name string, cash *cache.Cache) (ResponseObj, error) { + var responseObj ResponseObj + log.Println("Starting Remove Running pods: " + name) + + client, err := docker.NewClientFromEnv() + if err != nil { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Error Creating Docker Client Env" + return responseObj, err + } + + imgs, err := client.ListContainers(docker.ListContainersOptions{All: false}) + if err != nil { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Error List Running Docker Services" + return responseObj, err + } + + if len(imgs) > 0 { + log.Println("List Running container ...." + jencoder.PrintJson(imgs)) + for _, img := range imgs { + if len(img.Names) > 0 { + for _, cName := range img.Names { + if strings.EqualFold(cName[1:len(cName)], name) { + err = client.StopContainer(img.ID, 0) + if err != nil { + responseObj.ResponseCode = 0 + responseObj.ResponseDesc = "Error getting Remove Running Container" + return responseObj, err + } else { + client.RemoveContainer(docker.RemoveContainerOptions{ID: img.ID}) + responseObj.ResponseCode = 0 + responseObj.ResponseDesc = "Successfully Remove Running Container" + return responseObj, err + } + } + } + } + } + } else { + responseObj.ResponseCode = 0 + responseObj.ResponseDesc = "No Running Container for Remove" + return responseObj, err + } + + SetHistory(responseObj, cash) + return responseObj, err +} + +func SetHistory(obj ResponseObj, cash *cache.Cache) { + var historyArray []History + var history History + loc, _ := time.LoadLocation(_TimeZone) + history.Time = time.Now().In(loc).Format("2006-01-02 15:04:05") + history.Status = obj.ResponseCode + history.Action = obj.ResponseDesc + history.Comment = obj.ResponseDesc + + if x, _, found := cash.GetWithExpiration("history"); found { + err := json.Unmarshal(jencoder.PassJson(x), &historyArray) + if err != nil { + log.Println(err) + } + } + + historyArray = append(historyArray, history) + cash.Set("history", historyArray, 5*time.Minute) +} diff --git a/pkg/manager/dockerrunner/dockerrunner.go b/pkg/manager/dockerrunner/dockerrunner.go new file mode 100644 index 0000000..53e4d1e --- /dev/null +++ b/pkg/manager/dockerrunner/dockerrunner.go @@ -0,0 +1,73 @@ +package dockerrunner + +import ( + "encoding/json" + docker "github.com/fsouza/go-dockerclient" + "github.com/patrickmn/go-cache" + "log" + "time" + "ukiyo/pkg/jencoder" +) + +type ResponseObj struct { + ResponseCode int + ResponseDesc string +} + +type History struct { + Time string + Action string + Status int + Comment string +} + +const ( + _TimeZone = "Asia/Kolkata" +) + +func ContainerRunner(containerId string, c *cache.Cache) (ResponseObj, error) { + var responseObj ResponseObj + log.Println("Starting container Runner : containerId: " + containerId) + client, err := docker.NewClientFromEnv() + if err != nil { + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Error Running Docker Container Id: " + containerId + return responseObj, err + } + + err = client.StartContainer(containerId, &docker.HostConfig{ + PublishAllPorts: true, + }) + if err != nil { + log.Println("Container Run failure :" + containerId) + responseObj.ResponseCode = 1 + responseObj.ResponseDesc = "Container Run failure" + } else { + log.Println("Successfully Run The Container :" + containerId) + responseObj.ResponseCode = 0 + responseObj.ResponseDesc = "Successfully Run The Container" + } + + SetHistory(responseObj, c) + return responseObj, err +} + +func SetHistory(obj ResponseObj, cash *cache.Cache) { + var historyArray []History + var history History + loc, _ := time.LoadLocation(_TimeZone) + history.Time = time.Now().In(loc).Format("2006-01-02 15:04:05") + history.Status = obj.ResponseCode + history.Action = obj.ResponseDesc + history.Comment = obj.ResponseDesc + + if x, _, found := cash.GetWithExpiration("history"); found { + err := json.Unmarshal(jencoder.PassJson(x), &historyArray) + if err != nil { + log.Println(err) + } + } + + historyArray = append(historyArray, history) + cash.Set("history", historyArray, 5*time.Minute) +} diff --git a/pkg/process/process.go b/pkg/process/process.go new file mode 100644 index 0000000..5a35871 --- /dev/null +++ b/pkg/process/process.go @@ -0,0 +1,86 @@ +package process + +import ( + "github.com/olekukonko/tablewriter" + "github.com/patrickmn/go-cache" + "os" + "os/exec" + "strconv" + "time" + "ukiyo/internal/caching/cacheupdate" + "ukiyo/internal/containerscheduler" +) + +type DataObj struct { + Name string + ImageName string + ScheduleType string + TimeMnt string + Ports string +} + +func Process(c *cache.Cache) string { + for { + var dataObjList []DataObj + var data [][]string + + pods := containerscheduler.QueryListRecodeInDB() + if len(pods) > 0 { + for _, pod := range pods { + var dataObj DataObj + dataObj.Name = pod.Name + if pod.ScheduledDowntime { + dataObj.ScheduleType = "Waiting For Scheduled" + a, b, c := cacheupdate.TimeCalculator(pod.Name, c) + dataObj.ImageName = c + if a > 0 || b > 0 { + dataObj.ScheduleType = "Scheduled" + } + dataObj.TimeMnt = strconv.Itoa(a) + " [mm]: " + strconv.Itoa(b) + " [SS]" + } else { + dataObj.ScheduleType = "ON THE FLY" + dataObj.ImageName = "-" + dataObj.TimeMnt = "00 [mm]: " + "00 [SS]" + } + + if len(pod.BindingPort) > 0 { + for _, port := range pod.BindingPort { + dataObj.Ports = dataObj.Ports + port.InternalPort + "->" + port.ExportPort + ", " + } + } + dataObjList = append(dataObjList, dataObj) + } + } + + if len(dataObjList) > 0 { + for _, dataObj := range dataObjList { + data = append(data, []string{dataObj.Name, dataObj.ImageName, dataObj.ScheduleType, dataObj.TimeMnt, dataObj.Ports}) + } + } + + table := tablewriter.NewWriter(os.Stdout) + table.SetRowLine(true) + table.SetHeader([]string{"Name", "Version", "Deployment Status", "Time Remaining to Deployment", "Ports"}) + table.SetCaption(true, "Caching Table in Ukiyo") + + for _, v := range data { + table.Append(v) + } + table.Render() + + table2 := tablewriter.NewWriter(os.Stdout) + table2.SetRowLine(true) + table2.SetHeader([]string{"Time", "Action", "Status", "Comment"}) + table2.SetCaption(true, "History Table") + + for _, v := range cacheupdate.HistoryFetch(c) { + table2.Append(v) + } + table2.Render() + + time.Sleep(1 * time.Second) + exec := exec.Command("clear") + exec.Stdout = os.Stdout + exec.Run() + } +} diff --git a/pkg/pullmanager/pullmanager.go b/pkg/pullmanager/pullmanager.go deleted file mode 100644 index 31f3960..0000000 --- a/pkg/pullmanager/pullmanager.go +++ /dev/null @@ -1,69 +0,0 @@ -package pullmanager - -import ( - "encoding/json" - "log" - "strings" - "ukiyo/pkg/auth" - "ukiyo/pkg/util" - - docker "github.com/fsouza/go-dockerclient" -) - -var val int -var imageName string - -func PullToDocker(str util.PullObj) (string, int, string, error) { - b, err := json.Marshal(str) - if err != nil { - log.Println(err) - return "", 1, "Invalid pull image Obj", err - } - imageName = str.RepoName + ":" + str.Tag - log.Println("pull images : " + string(b)) - - client, err := docker.NewClientFromEnv() - if err != nil { - panic(err) - } - - err = client.PullImage(DockerPullImage(str), auth.DockerLogin(str.Namespace)) - - if err != nil { - panic(err) - } - - imgs, err := client.ListImages(docker.ListImagesOptions{All: false}) - - if err != nil { - panic(err) - } - - for _, img := range imgs { - for _, repo := range img.RepoTags { - if strings.EqualFold(repo, imageName) { - val = val + 1 - log.Println("Successfully pull the images : " + repo) - } - } - } - - if val > 0 { - return imageName, 0, "Successfully pull the images", err - } else { - return "", 1, "Failed pull the images", err - } -} - -func DockerPullImage(str util.PullObj) docker.PullImageOptions { - return docker.PullImageOptions{ - Repository: str.RepoName, - Tag: str.Tag, - Platform: "", - Registry: "", - OutputStream: nil, - RawJSONStream: false, - InactivityTimeout: 0, - Context: nil, - } -} diff --git a/pkg/pullmanager/pullmanager_test.go b/pkg/pullmanager/pullmanager_test.go deleted file mode 100644 index b922dfb..0000000 --- a/pkg/pullmanager/pullmanager_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package pullmanager - -import ( - "testing" - "ukiyo/pkg/util" - - "github.com/stretchr/testify/assert" -) - -var responseCode int -var responseDesc string - -func TestAddContainer(t *testing.T) { - - var pullObj util.PullObj - pullObj.Namespace = "agentukiyo" - pullObj.RepoName = "agentukiyo/ukiyo" - pullObj.Tag = "02" - pullObj.PushedDate = 2020121234 - imageName, responseCode, responseDesc, err := PullToDocker(pullObj) - if err != nil { - panic(err) - } - - assert.Equal(t, "", imageName) - assert.Equal(t, 0, responseCode) - assert.Equal(t, "Successfully pull the images", responseDesc) -} diff --git a/pkg/pushmanager/pushmanager.go b/pkg/pushmanager/pushmanager.go deleted file mode 100644 index 3ec5513..0000000 --- a/pkg/pushmanager/pushmanager.go +++ /dev/null @@ -1,2 +0,0 @@ -package pushmanager - diff --git a/pkg/pushmanager/pushmanager_test.go b/pkg/pushmanager/pushmanager_test.go deleted file mode 100644 index bbee683..0000000 --- a/pkg/pushmanager/pushmanager_test.go +++ /dev/null @@ -1 +0,0 @@ -package pushmanager diff --git a/pkg/scheduler/eventsheduler/scheduler.go b/pkg/scheduler/eventsheduler/scheduler.go new file mode 100644 index 0000000..2ec8325 --- /dev/null +++ b/pkg/scheduler/eventsheduler/scheduler.go @@ -0,0 +1,80 @@ +package eventsheduler + +import ( + "github.com/patrickmn/go-cache" + "log" + "time" + "ukiyo/internal/caching" + "ukiyo/internal/containerscheduler" + "ukiyo/pkg/jencoder" +) + +const ( + _TimeZone = "Asia/Kolkata" +) + +func ScheduledRunner(event string, name string, ScheduledAt int64, c *cache.Cache) { + switch env := event; env { + case "run": + RunEvent(c) + case "save": + SaveEvent(name, ScheduledAt, c) + case "edit": + EditEvent(name, ScheduledAt, c) + case "delete": + DeleteEvent(name, c) + default: + log.Println("No Matching Event in ScheduledRunner") + } + +} + +func RunEvent(c *cache.Cache) { + log.Println("RunEvent") + pods := containerscheduler.QueryListRecodeInDB() + log.Println("ScheduledRunner trigger ...." + jencoder.PrintJson(pods)) + + if len(pods) > 0 { + for _, pod := range pods { + if len(pod.Name) > 0 && pod.ScheduledDowntime { + loc, _ := time.LoadLocation(_TimeZone) + log.Print(time.Now().In(loc).UnixNano() / int64(time.Millisecond)) + if (pod.ScheduledAt - time.Now().In(loc).UnixNano()/int64(time.Millisecond)) >= 0 { + log.Println("Activated scheduling :" + pod.Name) + caching.CacheRunner(pod.Name, "", pod.ScheduledAt, c) + } else { + log.Println("Time passed for the scheduling :" + pod.Name) + } + } else { + log.Println("Not activate scheduling :" + pod.Name) + } + } + } + log.Println(c.Items()) +} + +func SaveEvent(name string, scheduledAt int64, c *cache.Cache) { + log.Println("SaveEvent") + log.Println(c.Items()) + loc, _ := time.LoadLocation(_TimeZone) + if (scheduledAt - time.Now().In(loc).UnixNano()/int64(time.Millisecond)) >= 0 { + caching.CacheRunner(name, "", scheduledAt, c) + } else { + log.Println("Time passed for the scheduling :" + name) + } + log.Println(c.Items()) +} + +func EditEvent(name string, scheduledAt int64, c *cache.Cache) { + log.Println("EditEvent") + log.Println(c.Items()) + caching.CacheRunner(name, "", scheduledAt, c) + log.Println(c.Items()) +} + +func DeleteEvent(name string, c *cache.Cache) { + log.Println("DeleteEvent") + log.Println(c.Items()) + c.Delete(name) + log.Println(c.Items()) +} diff --git a/pkg/scheduler/scheduler.go b/pkg/scheduler/scheduler.go index 6990da0..c210bd5 100644 --- a/pkg/scheduler/scheduler.go +++ b/pkg/scheduler/scheduler.go @@ -1 +1,45 @@ package scheduler + +import ( + "github.com/patrickmn/go-cache" + "log" + "ukiyo/internal/caching/cacheupdate" + "ukiyo/internal/containerscheduler" + "ukiyo/pkg/jencoder" + "ukiyo/pkg/manager/dockercreater" + "ukiyo/pkg/manager/dockerremove" + "ukiyo/pkg/manager/dockerrunner" +) + +func WebHookScheduler(name string, imageName string, c *cache.Cache) { + pod := containerscheduler.QueryRecodeInDB(name) + log.Println("WebHookScheduler " + jencoder.PrintJson(pod)) + if len(pod.Name) > 0 && !pod.ScheduledDowntime { + log.Println("Starting Non scheduled deployment") + DeploymentProcess(name, imageName, c) + } else if len(pod.Name) > 0 && pod.ScheduledDowntime { + log.Println("Starting scheduled deployment") + cacheupdate.CacheUpdate(name, imageName, c) + } else { + log.Println("Error pod details to schedule images") + } +} + +func DeploymentProcess(name string, imageName string, c *cache.Cache) { + removeObj, _ := dockerremove.RemoveRunningContainer(name, c) + log.Println("Ending Container Remove - WebHookScheduler ...." + jencoder.PrintJson(removeObj)) + + if removeObj.ResponseCode == 0 { + + log.Println("WebHookScheduler trigger ContainerCreate...." + jencoder.PrintJson(name)) + res, _ := dockercreater.ContainerCreate(name, imageName, c) + log.Println("WebHookScheduler trigger ContainerCreate...." + jencoder.PrintJson(res)) + + log.Println("Starting Container runner - WebHookScheduler") + resObj, _ := dockerrunner.ContainerRunner(res.ContainerId, c) + log.Println("Ending Container runner - WebHookScheduler ...." + jencoder.PrintJson(resObj)) + + } else { + log.Println("Stop Container runner - WebHookScheduler - Failed Running Container remove process") + } +} diff --git a/pkg/scheduler/scheduler_test.go b/pkg/scheduler/scheduler_test.go deleted file mode 100644 index 6990da0..0000000 --- a/pkg/scheduler/scheduler_test.go +++ /dev/null @@ -1 +0,0 @@ -package scheduler diff --git a/pkg/util/util.go b/pkg/util/util.go deleted file mode 100644 index 8c5fe43..0000000 --- a/pkg/util/util.go +++ /dev/null @@ -1,13 +0,0 @@ -package util - -type PullObj struct { - RepoName string - Namespace string - Tag string - PushedDate int -} - -type ResponseObj struct { - ResponseCode int - ResponseDesc string -} diff --git a/pkg/webhooks/webhooks.go b/pkg/webhook-listener/webhook-listener.go similarity index 54% rename from pkg/webhooks/webhooks.go rename to pkg/webhook-listener/webhook-listener.go index 9746c92..59ef589 100644 --- a/pkg/webhooks/webhooks.go +++ b/pkg/webhook-listener/webhook-listener.go @@ -1,19 +1,19 @@ -package webhooks +package webhook_listener import ( - "encoding/json" + "github.com/gin-gonic/gin" + "github.com/patrickmn/go-cache" "log" "net/http" - "strconv" - "ukiyo/pkg/pullmanager" - "ukiyo/pkg/util" - - "github.com/gin-gonic/gin" + "ukiyo/pkg/jencoder" + "ukiyo/pkg/manager/dockerpull" + "ukiyo/pkg/scheduler" + "ukiyo/pkg/webhook" ) type PushData struct { PushedAt int `json:"pushed_at"` - images []string `json:"images"` + Images []string `json:"images"` Tag string `json:"tag"` Pusher string `json:"pusher"` } @@ -41,31 +41,23 @@ type DockerWebHook struct { Repository Repository `json:"repository"` } -var responseCode int -var responseDesc string -var imageName string - -func HooksListener(r *gin.Engine) { +func HooksListener(r *gin.Engine, cash *cache.Cache) { r.POST("/ukiyo-web-hook", func(c *gin.Context) { var dockerWebHook DockerWebHook c.ShouldBindJSON(&dockerWebHook) + log.Println("ukiyo web-hook trigger request" + jencoder.PrintJson(dockerWebHook)) - b, err := json.Marshal(dockerWebHook) - if err != nil { - log.Println(err) - return - } + res := SetWebHookResponse(dockerWebHook) + log.Println("ukiyo web-hook trigger response" + jencoder.PrintJson(res)) - var pullObj util.PullObj - pullObj.Namespace = dockerWebHook.Repository.Namespace - pullObj.RepoName = dockerWebHook.Repository.RepoName - pullObj.Tag = dockerWebHook.PushData.Tag - pullObj.PushedDate = dockerWebHook.PushData.PushedAt + resObj, _ := dockerpull.PullToDocker(res, cash) - log.Println("web-hook trigger" + string(b)) + if len(resObj.Name) > 0 { + scheduler.WebHookScheduler(resObj.Name, resObj.ImageName, cash) + } else { + log.Println("Error Creating Image .." + jencoder.PrintJson(resObj)) + } - imageName, responseCode, responseDesc, err = pullmanager.PullToDocker(pullObj) - log.Println("pull Manager responseCode :" + strconv.Itoa(responseCode) + " responseDesc : " + responseDesc) c.String(http.StatusOK, "OK") }) } @@ -75,3 +67,13 @@ func HealthCheck(r *gin.Engine) { c.String(http.StatusOK, "pong") }) } + +func SetWebHookResponse(dockerWebHook DockerWebHook) webhook.Response { + var response webhook.Response + response.Namespace = dockerWebHook.Repository.Namespace + response.RepoName = dockerWebHook.Repository.RepoName + response.Name = dockerWebHook.Repository.Name + response.Tag = dockerWebHook.PushData.Tag + response.PushedAt = dockerWebHook.PushData.PushedAt + return response +} diff --git a/pkg/webhook/webhook.go b/pkg/webhook/webhook.go new file mode 100644 index 0000000..59b2ca8 --- /dev/null +++ b/pkg/webhook/webhook.go @@ -0,0 +1,24 @@ +package webhook + +import docker "github.com/fsouza/go-dockerclient" + +type Response struct { + RepoName string + Namespace string + Name string + Tag string + PushedAt int +} + +func DockerPullImage(str Response) docker.PullImageOptions { + return docker.PullImageOptions{ + Repository: str.RepoName, + Tag: str.Tag, + Platform: "", + Registry: "", + OutputStream: nil, + RawJSONStream: false, + InactivityTimeout: 0, + Context: nil, + } +} diff --git a/test/caching_test.go b/test/caching_test.go new file mode 100644 index 0000000..f6ef058 --- /dev/null +++ b/test/caching_test.go @@ -0,0 +1,31 @@ +package test + +import ( + "github.com/patrickmn/go-cache" + "testing" + "time" + "ukiyo/internal/caching/cacheupdate" +) + +const ( + _TimeZone = "Asia/Kolkata" +) + +func TestHistoryFetch(t *testing.T) { + c := cache.New(1*time.Minute, 1*time.Minute) + var history cacheupdate.History + var historyArr []cacheupdate.History + loc, _ := time.LoadLocation(_TimeZone) + history.Action = "" + history.Comment = "" + history.Status = 0 + history.Time = time.Now().In(loc).Format("2006-01-02 15:04:05") + historyArr = append(historyArr, history) + c.Set("history", historyArr, 2*time.Minute) + val := cacheupdate.HistoryFetch(c) + if len(val) > 0 { + t.Logf("passed expected %v and got value %v", len(val), len(val)) + } else { + t.Errorf("failed expected %v but got value %v", 0, len(val)) + } +} \ No newline at end of file diff --git a/test/dockercreater_test.go b/test/dockercreater_test.go new file mode 100644 index 0000000..dd76b9e --- /dev/null +++ b/test/dockercreater_test.go @@ -0,0 +1,18 @@ +package test + +import ( + "github.com/patrickmn/go-cache" + "testing" + "time" + "ukiyo/pkg/manager/dockercreater" +) + +func TestContainerCreate(t *testing.T) { + c := cache.New(1*time.Minute, 1*time.Minute) + docker, _ := dockercreater.ContainerCreate("demo-nginx", "demo-nginx", c) + if docker.ResponseCode != 0 { + t.Errorf("failed expected %v but got value %v", 0, docker.ResponseCode) + } else { + t.Logf("passed expected %v and got value %v", 0, docker.ResponseCode) + } +} diff --git a/test/dockerpull_test.go b/test/dockerpull_test.go new file mode 100644 index 0000000..b1eb830 --- /dev/null +++ b/test/dockerpull_test.go @@ -0,0 +1,26 @@ +package test + +import ( + "github.com/patrickmn/go-cache" + "testing" + "time" + "ukiyo/pkg/manager/dockerpull" + "ukiyo/pkg/webhook" +) + +func TestPullToDocker(t *testing.T) { + c := cache.New(1*time.Minute, 1*time.Minute) + var webHook = webhook.Response{ + RepoName: "demo-nginx", + Namespace: "165090", + Name: "demo-nginx", + Tag: "02", + PushedAt: 0, + } + docker, _ := dockerpull.PullToDocker(webHook, c) + if docker.ResponseCode != 0 { + t.Errorf("failed expected %v but got value %v", 0, docker.ResponseCode) + } else { + t.Logf("passed expected %v and got value %v", 0, docker.ResponseCode) + } +} diff --git a/test/dockerrunner_test.go b/test/dockerrunner_test.go new file mode 100644 index 0000000..5f3de4e --- /dev/null +++ b/test/dockerrunner_test.go @@ -0,0 +1,18 @@ +package test + +import ( + "github.com/patrickmn/go-cache" + "testing" + "time" + "ukiyo/pkg/manager/dockerrunner" +) + +func TestContainerRunner(t *testing.T) { + c := cache.New(1*time.Minute, 1*time.Minute) + docker, _ := dockerrunner.ContainerRunner("6dc636d3c48dad91ee753440bd708888bf1b278040c6732e604b076ee44dd75f", c) + if docker.ResponseCode != 0 { + t.Errorf("failed expected %v but got value %v", 0, docker.ResponseCode) + } else { + t.Logf("passed expected %v and got value %v", 0, docker.ResponseCode) + } +} diff --git a/test/registryapilayer_test.go b/test/registryapilayer_test.go new file mode 100644 index 0000000..6f9f96e --- /dev/null +++ b/test/registryapilayer_test.go @@ -0,0 +1,60 @@ +package test + +import ( + "github.com/gin-gonic/gin" + "github.com/patrickmn/go-cache" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + "ukiyo/api/registryapilayer" +) + +func TestSaveContainerAccessKeys(t *testing.T) { + r := gin.Default() + c := cache.New(1*time.Minute, 1*time.Minute) + registryapilayer.SaveContainerAccessKeys(r, c) + pushJson := `{ + "username":"registry_name", + "desc":"Docker registry keys", + "accessToken":"290e587a-1790-41d7-a053-61a2ef377875", + "email":"username@gmail.com", + "serverAddress":"http://docker.io/v1" + }` + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/save-container-access-keys", strings.NewReader(pushJson)) + r.ServeHTTP(w, req) + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "{\"ResponseCode\":0,\"ResponseDesc\":\"Successfully Added Container Key :registry_name\"}", w.Body.String()) +} + +func TestEditContainerAccessKeys(t *testing.T) { + r := gin.Default() + c := cache.New(1*time.Minute, 1*time.Minute) + registryapilayer.EditContainerAccessKeys(r, c) + pushJson := `{ + "username":"registry_name", + "desc":"Docker registry keys", + "accessToken":"290e587a-1790-41d7-a053-61a2ef377875", + "email":"username@gmail.com", + "serverAddress":"http://docker.io/v1" + }` + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/edit-container-access-keys", strings.NewReader(pushJson)) + r.ServeHTTP(w, req) + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "{\"ResponseCode\":0,\"ResponseDesc\":\"Successfully Updated Container key :registry_name\"}", w.Body.String()) +} + +func TestDeleteContainerAccessKeys(t *testing.T) { + r := gin.Default() + c := cache.New(1*time.Minute, 1*time.Minute) + registryapilayer.DeleteContainerAccessKeys(r, c) + w := httptest.NewRecorder() + req, _ := http.NewRequest("DELETE", "/delete-container-access-keys/registry_name", nil) + r.ServeHTTP(w, req) + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "{\"ResponseCode\":0,\"ResponseDesc\":\"Successfully Deleted Container Key :registry_name\"}", w.Body.String()) +} \ No newline at end of file diff --git a/test/schedulerapilayer_test.go b/test/schedulerapilayer_test.go new file mode 100644 index 0000000..5cff6f2 --- /dev/null +++ b/test/schedulerapilayer_test.go @@ -0,0 +1,68 @@ +package test + +import ( + "github.com/gin-gonic/gin" + "github.com/patrickmn/go-cache" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + "ukiyo/api/schedulerapilayer" +) + +func TestSaveRepositoryScheduledTime(t *testing.T) { + r := gin.Default() + c := cache.New(1*time.Minute, 1*time.Minute) + schedulerapilayer.SaveRepositoryScheduledTime(r, c) + pushJson := `{ + "name": "demo-nginx", + "bindingPort": [{ + "exportPort": "8180", + "internalPort": "80" + }, + { + "exportPort": "443", + "internalPort": "443" + }], + "scheduledTime": "Aug 17 2020 00:19:50 AM", + "scheduledDowntime": false + }` + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/save-repository-scheduled-time", strings.NewReader(pushJson)) + r.ServeHTTP(w, req) + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "{\"ResponseCode\":0,\"ResponseDesc\":\"Successfully Added pod details\"}", w.Body.String()) +} + +func TestEditRepositoryScheduledTime(t *testing.T) { + r := gin.Default() + c := cache.New(1*time.Minute, 1*time.Minute) + schedulerapilayer.EditRepositoryScheduledTime(r, c) + pushJson := `{ + "name": "demo-nginx", + "bindingPort": [{ + "exportPort": "8180", + "internalPort": "80" + }], + "scheduledTime": "Aug 17 2020 00:19:50 AM", + "scheduledDowntime": false + }` + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/edit-repository-scheduled-time", strings.NewReader(pushJson)) + r.ServeHTTP(w, req) + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "{\"ResponseCode\":0,\"ResponseDesc\":\"Successfully Updated pod details\"}", w.Body.String()) +} + +func TestDeleteRepositoryScheduledTime(t *testing.T) { + r := gin.Default() + c := cache.New(1*time.Minute, 1*time.Minute) + schedulerapilayer.DeleteRepositoryScheduledTime(r, c) + w := httptest.NewRecorder() + req, _ := http.NewRequest("DELETE", "/remove-repository-scheduled-time/demo-nginx", nil) + r.ServeHTTP(w, req) + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "{\"ResponseCode\":0,\"ResponseDesc\":\"Successfully Deleted pod details\"}", w.Body.String()) +} \ No newline at end of file diff --git a/pkg/webhooks/webhooks_test.go b/test/webhook-listner_test.go similarity index 72% rename from pkg/webhooks/webhooks_test.go rename to test/webhook-listner_test.go index 66de495..2bb98a2 100644 --- a/pkg/webhooks/webhooks_test.go +++ b/test/webhook-listner_test.go @@ -1,10 +1,11 @@ -package webhooks +package test import ( "net/http" "net/http/httptest" "strings" "testing" + webHookListener "ukiyo/pkg/webhook-listener" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" @@ -12,7 +13,7 @@ import ( func TestPingRoute(t *testing.T) { r := gin.Default() - HealthCheck(r) + webHookListener.HealthCheck(r) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/ping", nil) @@ -24,34 +25,33 @@ func TestPingRoute(t *testing.T) { func TestHookRoute(t *testing.T) { r := gin.Default() - HooksListener(r) + webHookListener.HooksListener(r) pushJson := `{ "push_data": { "pushed_at": 1591175168, "images": [], "tag": "02", - "pusher": "dabare" + "pusher": "registryName" }, - "callback_url": "https://registry.hub.docker.com/u/dabare/testing/hook/22fbde3h00hi54a3jdf42j5cf44bebjf1/", + "callback_url": "https://registry.hub.docker.com/u/registryName/demo-nginx/hook/22fbde3h00hi54a3jdf42j5cf44bebjf1/", "repository": { "status": "Active", "description": "", "is_trusted": false, "full_description": "", - "repo_url": "https://hub.docker.com/r/dabare/testing", - "owner": "dabare", + "repo_url": "https://hub.docker.com/r/registryName/demo-nginx", + "owner": "registryName", "is_official": false, "is_private": false, - "name": "testing", - "namespace": "dabare", + "name": "demo-nginx", + "namespace": "registryName", "star_count": 0, "comment_count": 0, "date_created": 1555438658, - "repo_name": "dabare/testing" + "repo_name": "registryName/demo-nginx" } }` - w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/ukiyo-web-hook", strings.NewReader(pushJson)) r.ServeHTTP(w, req)