From 0056295f19d52a7d956b7c936303a8ac3715d832 Mon Sep 17 00:00:00 2001 From: Dominik Andruszak <39803943+dandruszak@users.noreply.github.com> Date: Wed, 29 Apr 2020 10:24:11 +0200 Subject: [PATCH] Initial commit (#1) --- .gitignore | 1 + .iceci.yaml | 90 +++++++++++++++++++++++++++++++++++++ Dockerfile | 5 +++ README.md | 24 +++++++++- cmd/main.go | 114 +++++++++++++++++++++++++++++++++++++++++++++++ cmd/main_test.go | 71 +++++++++++++++++++++++++++++ go.mod | 10 +++++ go.sum | 47 +++++++++++++++++++ 8 files changed, 361 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 .iceci.yaml create mode 100644 Dockerfile create mode 100644 cmd/main.go create mode 100644 cmd/main_test.go create mode 100644 go.mod create mode 100644 go.sum diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f11b75 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea/ diff --git a/.iceci.yaml b/.iceci.yaml new file mode 100644 index 0000000..f14e459 --- /dev/null +++ b/.iceci.yaml @@ -0,0 +1,90 @@ +globals: + onFailure: + # This failure handler will be called failed of any step in the pipeline + # comment this out if you do not want to integrate pipeline with slack + - handlerName: slack-notify-error + +failureHandlers: + - name: debug-env + image: busybox + script: | + echo "### PWD" + pwd + echo "### ENV" + env + echo "ls -la" + ls -la + + - name: slack-notify-error + image: iceci/utils + script: | + cat < slacknotify.json + { + "text" : "Build $ICE_BUILD_NUMBER on branch $ICE_GIT_BRANCH_NAME failed on step $ICE_FAILED_STEP_NAME commited by $ICE_GIT_AUTHOR_NAME" + } + EOF + curl -X POST -H 'Content-type: application/json' --data "@slacknotify.json" $SLACK_WEBHOOK + environment: + - name: SLACK_WEBHOOK + fromSecret: slack-webhook + +# Service with postgres will run during whole pipeline +services: + - name: db + image: postgres:11 + environment: + - name: POSTGRES_DB + value: testdb + - name: POSTGRES_PASSWORD + value: dbpass + - name: POSTGRES_USER + value: dbuser + +steps: + - name: test-and-build + containerRun: + image: golang:1.14 + script: | + go test ./cmd + go build -o example-go-app cmd/main.go + environment: + - name: CGO_ENABLED + value: 0 + - name: APP_PORT + value: 8000 + - name: DB_HOST + value: db + - name: DB_PORT + value: 5432 + - name: DB_USER + value: dbuser + - name: DB_PASS + value: dbpass + - name: DB_NAME + value: testdb + - name: DB_DIALECT + value: postgres + + - name: build-docker-image + containerBuild: + dockerSecret: dockerhub + user: iceci + imageName: example-go-webapp + tags: + - "{{ ICE_BUILD_NUMBER }}" + - latest + + # comment this step out if you do not want to integrate this pipeline with slack + - name: slack-notify + containerRun: + image: iceci/utils + script: | + cat < slacknotify.json + { + "text" : "Build $ICE_BUILD_NUMBER on branch $ICE_GIT_BRANCH_NAME finished successfully!" + } + EOF + curl -X POST -H 'Content-type: application/json' --data "@slacknotify.json" $SLACK_WEBHOOK + environment: + - name: SLACK_WEBHOOK + fromSecret: slack-webhook diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6b7cbe2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM scratch + +ADD example-go-app /example-go-app + +CMD ["/example-go-app"] diff --git a/README.md b/README.md index b531200..b19421e 100644 --- a/README.md +++ b/README.md @@ -1 +1,23 @@ -# example-go-gin-api \ No newline at end of file +# IceCI Go example - Gin API + +This repository is a small example to help you get started building **IceCI** pipelines for Go applications. + +The application itself is a very simplified example of a web API with tests. It's not by any means a guideline on building Go applications - its only purpose is showcasing how to build IceCI pipelines for Go applications. + +This repository is a *GitHub template repository*, so please feel free to create new repositories based on it and mess around with the code and the pipeline config. Please also check the information below for a list of prerequisites needed to run the pipeline in IceCI. + +# Setting up IceCI + + +To launch the pipeline in IceCI you have to create 2 secrets - both of which are explicitly referenced in the `.iceci.yaml` file. + +* `dockerhub` - docker hub credentials with `docker.io` set as registry. + +* `slack-webhook` - a generic secret with hook for Slack notifications - it can be ignored by commenting out the `slack-notify` step as well as the `slack-notify-error` failure handler in the pipeline definition file. + +You can also find some additional info in the comments of the `.iceci.yaml` file itself. + + +--- + +Kept cool 🧊 by [Icetek](https://icetek.io/) diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..8b01c94 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,114 @@ +package main + +import ( + "errors" + "fmt" + "github.com/gin-gonic/gin" + "github.com/jinzhu/gorm" + _ "github.com/jinzhu/gorm/dialects/postgres" + "github.com/kelseyhightower/envconfig" + "net/http" +) + +type AppConfig struct { + Port string `required:"true"` +} + +type DbConfig struct { + Host string `required:"true"` + Port string `required:"true"` + User string `required:"true"` + Pass string `required:"true"` + Name string `required:"true"` + Dialect string `required:"true"` + SSLMode string `default:"disable"` +} + +type Quote struct { + Id uint + Quote string + Author string +} + +func loadDbConfig() (*DbConfig, error) { + config := &DbConfig{} + err := envconfig.Process("DB", config) + if err != nil { + return nil, errors.New(fmt.Sprintf("cannot load db config, %s", err)) + } + + return config, nil +} + +func loadAppConfig() (*AppConfig, error) { + config := &AppConfig{} + err := envconfig.Process("APP", config) + if err != nil { + return nil, errors.New(fmt.Sprintf("cannot load app config, %s", err)) + } + + return config, nil +} + +func setupDb(dbConfig *DbConfig) (*gorm.DB, error) { + connString := fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s sslmode=%s", + dbConfig.Host, dbConfig.Port, dbConfig.User, dbConfig.Name, dbConfig.Pass, dbConfig.SSLMode) + + db, err := gorm.Open(dbConfig.Dialect, connString) + if err != nil { + return nil, err + } + + db.AutoMigrate(Quote{}) + + return db, nil +} + +func setupRouter(db *gorm.DB) (*gin.Engine, error) { + ginEngine := gin.Default() + ginEngine.GET("/health", func(c *gin.Context) { + c.String(http.StatusOK, "ok") + }) + + ginEngine.GET("/quote", func(c *gin.Context) { + var quotes []Quote + query := db.Find("es) + + if query.Error != nil { + c.JSON(http.StatusInternalServerError, gin.H{"message": "Error fetching rows"}) + return + } + + c.JSON(http.StatusOK, quotes) + }) + + return ginEngine, nil +} + +func main() { + appConfig, err := loadAppConfig() + if err != nil { + panic(err) + } + + dbConfig, err := loadDbConfig() + if err != nil { + panic(err) + } + + db, err := setupDb(dbConfig) + if err != nil { + panic(err) + } + + ginEngine, err := setupRouter(db) + if err != nil { + panic(err) + } + + err = ginEngine.Run(fmt.Sprintf(":%s", appConfig.Port)) + + if err != nil { + panic(err) + } +} diff --git a/cmd/main_test.go b/cmd/main_test.go new file mode 100644 index 0000000..9ff4680 --- /dev/null +++ b/cmd/main_test.go @@ -0,0 +1,71 @@ +package main + +import ( + "encoding/json" + "fmt" + "github.com/jinzhu/gorm" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "testing" +) + +func TestWebEndpoints(t *testing.T) { + a := assert.New(t) + + dbConfig, err := loadDbConfig() + if err != nil { + t.Fatal(err) + } + + db, err := setupDb(dbConfig) + if err != nil { + t.Fatal(err) + } + + router, err := setupRouter(db) + if err != nil { + t.Fatal(err) + } + + setupFixtures(db) + + t.Run("health endpoint should return status 200", func(t *testing.T) { + req, _ := http.NewRequest("GET", "/health", nil) + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + a.Equal(200, w.Code) + a.Equal("ok", w.Body.String()) + }) + + t.Run("quotes endpoint should return an element", func(t *testing.T) { + var result []map[string]interface{} + + req, _ := http.NewRequest("GET", "/quote", nil) + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + err := json.Unmarshal(w.Body.Bytes(), &result) + if err != nil { + t.Fatal(fmt.Sprintf("failed parsing endpoint response, %s", err)) + } + + a.Equal(200, w.Code) + a.Equal(1, len(result)) + }) + + teardownFixtures(db) +} + +func setupFixtures(db *gorm.DB) { + quote := Quote{ + Quote: "A day without sunshine is like, you know, night", + Author: "Steve Martin", + } + + db.Create("e) +} + +func teardownFixtures(db *gorm.DB) { + db.Delete(Quote{}, "author = ?", "Steve Martin") +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..749103e --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/iceCI/example-go-gin-api + +go 1.13 + +require ( + github.com/gin-gonic/gin v1.6.2 + github.com/jinzhu/gorm v1.9.12 + github.com/kelseyhightower/envconfig v1.4.0 + github.com/stretchr/testify v1.4.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0023aed --- /dev/null +++ b/go.sum @@ -0,0 +1,47 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=