Homepage of gin: https://gin-gonic.com/
Github page of gin: https://github.com/gin-gonic/gin

The code in this articel is based on and modified from Go Gin Framework Crash Course of Pragmatic Reviews: https://www.youtube.com/watch?v=qR0WnWL2o1Q&list=PL3eAkoh7fypr8zrkiygiY1e9osoqjoV9w

May. 13 - Start a video uploading and saving app

initialize the project:

  • For golang, we initialize a project by making a directory and move into that directory:

    1. [root@localhost src]# mkdir gin
    2. [root@localhost src]# cd gin/
  • create go.mod and go.sum:

    1. [root@localhost gin]# go mod init gin
    2. [root@localhost gin]# go get -u github.com/gin-gonic/gin
  • create a main server program ‘server.go’ in the same folder to initialize a server using the gin library: ```go package main

import “github.com/gin-gonic/gin”

func main() { server := gin.Default()

  1. //create an endpoint
  2. server.GET("/test", func(ctx *gin.Context) {
  3. //http code 200 means 'OK'
  4. ctx.JSON(200, gin.H{
  5. "message": "OK!!",
  6. })
  7. })
  8. server.Run(":9090")

}

  1. to test, use the terminal:
  2. ```bash
  3. [root@localhost gin]# go run server.go
  4. [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
  5. [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
  6. - using env: export GIN_MODE=release
  7. - using code: gin.SetMode(gin.ReleaseMode)
  8. [GIN-debug] GET /test --> main.main.func1 (3 handlers)
  9. [GIN-debug] Listening and serving HTTP on :9090

use the browser to access “localhost:9090/test”
image.png
Then we have some response on the terminal as server:

  1. [GIN] 2020/05/13 - 16:49:56 | 200 | 165.343µs | ::1 | GET "/test"

other packages:

  • Entity:
    • create an “entity” folder
    • in “entity”, create “video.go”: ```go package entity

type Video struct { //json:"XXX" after variable type define the name of the variable shown in JSON Title string json:"title" Description string json:"description" URL string json:"url" }

  1. - Service:
  2. - create a "service" folder
  3. - in "service", create "video-service.go":
  4. ```go
  5. package service
  6. type VideoService interface {
  7. Save(entity.Video) entity.Video // save the video
  8. FindAll() []entity.Video // return a slice of video
  9. }
  10. //define "videos" as the list of all videos
  11. type videoService struct {
  12. videos []entity.Video
  13. }
  14. //new instance
  15. func New() VideoService {
  16. return &videoService{}
  17. }
  18. //save a new video in the video list
  19. func (service *videoService) Save(video entity.Video) entity.Video {
  20. service.videos = append(service.videos, video)
  21. return video
  22. }
  23. //find all videos in the list
  24. func (service *videoService) FindAll() []entity.Video {
  25. return service.videos
  26. }
  • Controller:
    • create a “controller” folder
    • in “controller”, create “video-controller.go”: ```go package controller

import ( “github.com/gin-gonic/gin” “gin/entity” )

type VideoController interface { FindAll() []entity.Video Save(ctx *gin.Context) entity.Video }

type controller struct { service service.VideoService }

func New(service service.VideoService) VideoController { return &controller{ service: service, } }

//refer to service package func (c *controller) FindAll() []entity.Video { return c.service.FindAll() }

func (c controller) Save(ctx gin.Context) entity.Video { var video entity.Video ctx.BindJSON(&video) c.service.Save(video) return video }

  1. <a name="irH7l"></a>
  2. #### change server.go to handle GET and POST:
  3. ```go
  4. package main
  5. import (
  6. "github.com/gin-gonic/gin"
  7. "gin/controller"
  8. "gin/service"
  9. )
  10. var (
  11. videoService service.VideoService = service.New()
  12. videoController controller.VideoController = controller.New(videoService)
  13. )
  14. func main() {
  15. server := gin.Default()
  16. server.GET("/videos", func(ctx *gin.Context) {
  17. ctx.JSON(200, videoController.FindAll())
  18. })
  19. server.POST("/videos", func(ctx *gin.Context) {
  20. ctx.JSON(200, videoController.Save(ctx))
  21. })
  22. server.Run(":9090")
  23. }

to test, we use “Postman” (upper square is data, lower square is output)
we send 2 POST requests:
image.png
image.png
and a GET request:
image.png
now we see 2 video data information are sent and could be pulled out by POST and GET.

May. 14 - Middlewares 101: Authorization, Logging and Debugging

Customize the server by not using gin.Default():

  • we used gin.Default() for server, but we need to customize our server for more methods:
  • create a folder (for package) called /middlewares, and in /middlewares, create a “logger.go” to let server print the information about a specific request: ```go // /middlewares/logger.go

package middlewares

import ( “fmt” “time”

  1. "github.com/gin-gonic/gin"

)

func Logger() gin.HandlerFunc { return gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { return fmt.Sprintf(“%s - [%s] %s %s %d %s \n”, param.ClientIP, param.TimeStamp.Format(time.RFC822), param.Method, param.Path, param.StatusCode, param.Latency, ) }) }

  1. - apply middlewares package and logger on server.go:
  2. ```go
  3. package main
  4. import (
  5. "github.com/gin-gonic/gin"
  6. "gin/controller"
  7. "gin/middlewares" //new
  8. "gin/service"
  9. )
  10. var (
  11. videoService service.VideoService = service.New()
  12. videoController controller.VideoController = controller.New(videoService)
  13. )
  14. func main() {
  15. //server := gin.Default()
  16. server := gin.New() //new
  17. server.Use(gin.Recovery(), middlewares.Logger()) //new
  18. server.GET("/videos", func(ctx *gin.Context) {
  19. ctx.JSON(200, videoController.FindAll())
  20. })
  21. server.POST("/videos", func(ctx *gin.Context) {
  22. ctx.JSON(200, videoController.Save(ctx))
  23. })
  24. server.Run(":9090")
  25. }

to test, use the terminal as the server:

  1. [root@localhost gin]# go run server.go
  2. [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
  3. - using env: export GIN_MODE=release
  4. - using code: gin.SetMode(gin.ReleaseMode)
  5. [GIN-debug] GET /videos --> main.main.func1 (3 handlers)
  6. [GIN-debug] POST /videos --> main.main.func2 (3 handlers)
  7. [GIN-debug] Listening and serving HTTP on :9090

and send a GET request to http://localhost:9090/videos by using Postman, then server prints the information:

  1. ::1 - [14 May 20 13:18 EDT] GET /videos 200 167.528µs
  2. ::1 - [14 May 20 13:23 EDT] POST /videos 200 115.442µs
  • For the first request:
    • client ip: 1
    • time sent/received: 14 May 20 13:18 EDT
    • a GET request
    • path is /videos
    • status code 200
    • latency is 167.528µs
  • For the first request:

    • client ip: 2
    • time sent/received: 14 May 20 13:23 EDT
    • a POST request
    • path is /videos
    • status code 200
    • latency is 115.442µs

      gin.log:

  • for server.go: ```go package main

import ( “gin/controller” “gin/middlewares” “gin/service” “os” // new “io” // new

  1. "github.com/gin-gonic/gin"

)

func setupLogOutput() { f, _ := os.Create(“gin.log”) gin.DefaultWriter = io.MultiWriter(f, os.Stdout) }

func main() { setupLogOutput() /… }

  1. we can add those codes to create a gin.log, and put server terminal outputs to this file, until next time the server restarts:<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1589651004370-689765fd-042b-4936-b162-ab80fcda02bb.png#align=left&display=inline&height=311&margin=%5Bobject%20Object%5D&name=image.png&originHeight=621&originWidth=1191&size=99803&status=done&style=none&width=595.5)
  2. <a name="4icdr"></a>
  3. #### Authoization:
  4. - in /middlewares, add a "basic-auth.go":
  5. ```go
  6. package middlewares
  7. import "github.com/gin-gonic/gin"
  8. func BasicAuth() gin.HandlerFunc {
  9. return gin.BasicAuth(gin.Accounts{
  10. "pragmatic": "reviews",
  11. })
  12. }

and apply BasicAuth() on server.go:

  1. //...
  2. //server.Use(gin.Recovery(), middlewares.Logger())
  3. server.Use(gin.Recovery(), middlewares.Logger(), middlewares.BasicAuth())
  4. //...

if send requests by Postman as before, we will see that the status code is 401 (unauthorized):

  1. ::1 - [14 May 20 15:10 EDT] GET /videos 401 53.656µs
  2. ::1 - [14 May 20 15:35 EDT] POST /videos 401 4.729µs

to get 200 instead of 401, we will apply the username & password pair before to send the request as an authorized user (“pragmatic”: “reviews”):
image.png

  1. ::1 - [14 May 20 15:45 EDT] GET /videos 401 4.38µs
  • here we extract the code and make it into tester.go: ```go package main

import ( “fmt” “net/http” “io/ioutil” )

func main() {

url := “http://localhost:9090/videos“ method := “GET”

client := &http.Client { } req, err := http.NewRequest(method, url, nil)

if err != nil { fmt.Println(err) } req.Header.Add(“Authorization”, “Basic cHJhZ21hdGljOnJldmlld3M=”) //cHJhZ21hdGljOnJldmlld3M= can be encoded from base 64 format //then we get pragmatic:reviews - the pair of username and password for auth above

res, err := client.Do(req) defer res.Body.Close() body, err := ioutil.ReadAll(res.Body)

fmt.Println(string(body)) }

  1. we can run this tester.go when the server is online, and the result is same as we send request by Postman.
  2. <a name="37xUQ"></a>
  3. #### gindump:
  4. - simply, it's just a package that let server shows more detail about the request:
  5. - server.go:
  6. ```go
  7. import (
  8. "gin/controller"
  9. "gin/middlewares"
  10. "gin/service"
  11. "io"
  12. "os"
  13. gindump "github.com/tpkeeper/gin-dump" // new
  14. "github.com/gin-gonic/gin"
  15. )
  16. func main() {
  17. //...
  18. server.Use(gin.Recovery(), middlewares.Logger(), middlewares.BasicAuth(), gindump.Dump())
  19. //...
  20. }

to test, send a request as before, and the server terminal:

  1. Request-Header:
  2. {
  3. "Accept": [
  4. "*/*"
  5. ],
  6. "Accept-Encoding": [
  7. "gzip, deflate, br"
  8. ],
  9. "Authorization": [
  10. "Basic cHJhZ21hdGljOnJldmlld3M="
  11. ],
  12. "Connection": [
  13. "keep-alive"
  14. ],
  15. "Postman-Token": [
  16. "3b60ec66-1f54-441c-99e9-44c9b44fe97c"
  17. ],
  18. "User-Agent": [
  19. "PostmanRuntime/7.24.1"
  20. ]
  21. }
  22. Response-Header:
  23. {
  24. "Content-Type": [
  25. "application/json; charset=utf-8"
  26. ]
  27. }
  28. Response-Body:
  29. null
  30. ::1 - [16 May 20 15:57 EDT] GET /videos 200 387.07µs

May. 15 - Data Binding and Validation

BindJSON to ShouldBindJSON:

  • a slient change on video-controller.go in controller package:

    1. //...
    2. type VideoController interface {
    3. FindAll() []entity.Video
    4. Save(ctx *gin.Context) error
    5. }
    6. //...
    7. func (c *controller) Save(ctx *gin.Context) error { //entity.Video
    8. var video entity.Video
    9. //ctx.BindJSON(&video)
    10. err := ctx.ShouldBindJSON(&video) //will only return error, not return status code 400
    11. if err != nil {
    12. return err
    13. }
    14. c.service.Save(video)
    15. return nil
    16. }

    Entity binding:

  • a video should have an author

  • we add a Person struct in entity package, video.go and assign it to Video struct: ```go package entity

type Person struct { FirstName string json:"firstname" LastName string json:"lastname" Age uint8 json:"age" Email string json:"email"

}

type Video struct { Title string json:"title" Description string json:"description" URL string json:"url" Author Person json:"author" }

  1. - also, we can apply **binding** for variables inside those structs if necessary:
  2. ```go
  3. // /entity/video.go
  4. package entity
  5. type Person struct {
  6. FirstName string `json:"firstname" binding:"required"` // must have
  7. LastName string `json:"lastname" binding:"required"` // must have
  8. Age uint8 `json:"age" binding:"gte=0,lte=130"` // age >= 0, <= 130
  9. Email string `json:"email" binding:"required,email"` // must have, and format should be email
  10. }
  11. type Video struct {
  12. Title string `json:"title" binding:"min=2,max=10"` // 2-10 characters
  13. Description string `json:"description" binding:"max=20"` // at most 20 characters
  14. URL string `json:"url" binding:"required,url"` // must have, and format should be url
  15. Author Person `json:"author" binding:"required"` // must have
  16. }
  • modify server.go to return different JSON message: ```go package main

import ( “gin/controller” “gin/middlewares” “gin/service” “io” “net/http” //new “os”

  1. gindump "github.com/tpkeeper/gin-dump"
  2. "github.com/gin-gonic/gin"

)

//…

func main() {

  1. //...
  2. server.POST("/videos", func(ctx *gin.Context) {
  3. err := videoController.Save(ctx)
  4. if err != nil {
  5. ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  6. } else {
  7. ctx.JSON(http.StatusOK, gin.H{"message": "Video Input is valid"})
  8. }
  9. })
  10. server.Run(":9090")

}

  1. to test, send POST request (or course, from an authorized user):
  2. ```json
  3. {
  4. "title" : "title",
  5. "description" : "desc",
  6. "url" : "http://youtube.com/title2",
  7. "author" : {
  8. "firstname" : "K",
  9. "lastname" : "R",
  10. "age" : 22,
  11. "email" : "KR@gmail.com"
  12. }
  13. }
  1. # server terminal
  2. Response-Body:
  3. {
  4. "message": "Video Input is valid"
  5. }
  6. ::1 - [15 May 20 08:57 EDT] POST /videos 200 219.004µs

and we send some other invalid requests:

  1. {
  2. "title" : "title",
  3. "description" : "desc",
  4. "url" : "http://youtube.com/title2",
  5. "author" : {
  6. "firstname" : "K",
  7. "lastname" : "R",
  8. "age" : 200,
  9. "email" : "KR@gmail.com"
  10. }
  11. }
  12. {
  13. "title" : "title",
  14. "description" : "desc",
  15. "url" : "http://youtube.com/title2",
  16. "author" : {
  17. "firstname" : "K",
  18. "lastname" : "R",
  19. "age" : 22,
  20. "email" : "KRgmail.com"
  21. }
  22. }
  23. {
  24. "title" : "22222222222222222222222222222222",
  25. "description" : "desc",
  26. "url" : "http://youtube.com/title2",
  27. "author" : {
  28. "firstname" : "K",
  29. "lastname" : "R",
  30. "age" : 22,
  31. "email" : "KRgmail.com"
  32. }
  33. }
  1. # server terminal
  2. Response-Body:
  3. {
  4. "error": "Key: 'Video.Author.Age' Error:Field validation for 'Age' failed on the 'lte' tag"
  5. }
  6. ::1 - [15 May 20 09:04 EDT] POST /videos 400 332.862µs
  7. Response-Body:
  8. {
  9. "error": "Key: 'Video.Author.Email' Error:Field validation for 'Email' failed on the 'email' tag"
  10. }
  11. ::1 - [15 May 20 09:05 EDT] POST /videos 400 398.871µs
  12. Response-Body:
  13. {
  14. "error": "Key: 'Video.Title' Error:Field validation for 'Title' failed on the 'max' tag\nKey: 'Video.Author.Email' Error:Field validation for 'Email' failed on the 'email' tag"
  15. }
  16. ::1 - [15 May 20 09:07 EDT] POST /videos 400 763.254µs

Entity validation:

  • for this section, we need a /validators package and a validators.go: ```go package validators

import ( “strings”

  1. "gopkg.in/go-playground/validator.v9"

)

// ValidateCoolTitle - return true if the field value contains the word “cool”. func ValidateCoolTitle(fl validator.FieldLevel) bool { return strings.Contains(fl.Field().String(), “Cool”) }

  1. - apply it in video-controller:
  2. ```go
  3. package controller
  4. import (
  5. "gin/entity"
  6. "gin/service"
  7. "gin/validators" //new
  8. "github.com/gin-gonic/gin"
  9. "gopkg.in/go-playground/validator.v9" //new
  10. )
  11. //...
  12. var validate *validator.Validate //new
  13. func New(service service.VideoService) VideoController {
  14. validate = validator.New() //new
  15. validate.RegisterValidation("is-cool", validators.ValidateCoolTitle) //new
  16. return &controller{
  17. service: service,
  18. }
  19. }
  20. //...
  21. func (c *controller) Save(ctx *gin.Context) error {
  22. var video entity.Video
  23. err := ctx.ShouldBindJSON(&video)
  24. if err != nil {
  25. return err
  26. }
  27. //new
  28. err = validate.Struct(video)
  29. if err != nil {
  30. return err
  31. }
  32. c.service.Save(video)
  33. return nil
  34. }
  35. //...
  • and add is-cool validate after title in /entity/video.go: ```go package entity

type Person struct { FirstName string json:"firstname" binding:"required" // must have LastName string json:"lastname" binding:"required" // must have Age uint8 json:"age" binding:"gte=0,lte=130" // age >= 0, <= 130 Email string json:"email" binding:"required,email" // must have, and format should be email

}

type Video struct { Title string json:"title" binding:"min=2,max=10" validate:"is-cool" // 2-10 characters, includes the word “Cool” Description string json:"description" binding:"max=20" // at most 20 characters URL string json:"url" binding:"required,url" // must have, and format should be url Author Person json:"author" binding:"required" // must have }

  1. to test, again send different POST requests:<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1589740394347-8c29f5b2-41ba-4401-8d27-cc972a3fc248.png#align=left&display=inline&height=322&margin=%5Bobject%20Object%5D&name=image.png&originHeight=643&originWidth=1120&size=48674&status=done&style=none&width=560)
  2. ```json
  3. {
  4. "title" : "222222222",
  5. "description" : "desc",
  6. "url" : "http://youtube.com/title2",
  7. "author" : {
  8. "firstname" : "K",
  9. "lastname" : "R",
  10. "age" : 22,
  11. "email" : "KR@gmail.com"
  12. }
  13. }
  14. {
  15. "title" : "2222Cool",
  16. "description" : "desc",
  17. "url" : "http://youtube.com/title2",
  18. "author" : {
  19. "firstname" : "K",
  20. "lastname" : "R",
  21. "age" : 22,
  22. "email" : "KR@gmail.com"
  23. }
  24. }

get:

  1. # server terminal
  2. Response-Body:
  3. {
  4. "error": "Key: 'Video.Title' Error:Field validation for 'Title' failed on the 'is-cool' tag"
  5. }
  6. ::1 - [15 May 20 14:32 EDT] POST /videos 400 292.831µs
  7. Response-Body:
  8. {
  9. "message": "Video Input is valid"
  10. }
  11. ::1 - [15 May 20 14:33 EDT] POST /videos 200 231.704µs

May. 16-17 - HTML, Templates and Multi-Route Grouping

  • First, we need a folder for templates package, so create /templates
  • then create 3 separate parts:
    • header.html
    • footer.html
    • index.html ```html

<!DOCTYPE html>

html

  1. ```html
  2. <!--index.html-->
  3. {{ template "header.html" .}}
  4. {{ template "footer.html" .}}

and in /templates, create /css/index.css for formats later.

Separate apiRoutes and fieldRoutes:

  • add a ShowAll() function in /controller/video-controller.go: ```go package controller

import ( “gin/entity” “gin/service” “gin/validators” “net/http” // new

  1. "github.com/gin-gonic/gin"
  2. "gopkg.in/go-playground/validator.v9"

)

type VideoController interface { FindAll() []entity.Video Save(ctx gin.Context) error ShowAll(ctx gin.Context) // new } /… func (c controller) ShowAll(ctx gin.Context) { // new videos := c.service.FindAll() data := gin.H{ “title”: “Video Page”, “videos”: videos, } ctx.HTML(http.StatusOK, “index.html”, data) }

  1. - apply ShowAll(), css file and add apiRoutes/fieldRoutes in server.go:
  2. ```go
  3. package main
  4. import (
  5. "gin/controller"
  6. "gin/middlewares"
  7. "gin/service"
  8. "io"
  9. "net/http" //new
  10. "os"
  11. gindump "github.com/tpkeeper/gin-dump"
  12. "github.com/gin-gonic/gin"
  13. )
  14. //...
  15. func main() {
  16. setupLogOutput()
  17. server := gin.New()
  18. server.Static("/css", "./templates/css")
  19. server.LoadHTMLGlob("templates/*.html")
  20. apiRoutes := server.Group("/api")
  21. {
  22. server.Use(gin.Recovery(), middlewares.Logger(), middlewares.BasicAuth(), gindump.Dump())
  23. //server.GET("/videos", func(ctx *gin.Context) {
  24. apiRoutes.GET("/videos", func(ctx *gin.Context) {
  25. ctx.JSON(200, videoController.FindAll())
  26. })
  27. //server.POST("/videos", func(ctx *gin.Context) {
  28. apiRoutes.POST("/videos", func(ctx *gin.Context) {
  29. err := videoController.Save(ctx)
  30. if err != nil {
  31. ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  32. } else {
  33. ctx.JSON(http.StatusOK, gin.H{"message": "Video Input is valid"})
  34. }
  35. })
  36. }
  37. viewRoutes := server.Group("/view")
  38. {
  39. viewRoutes.GET("/videos", videoController.ShowAll)
  40. }
  41. server.Run(":9090")
  42. }

loop in index.html:

  • create a loop to show all contents of the video list (use range) ```html {{ template “header.html” .}}


{{range .videos }}


{{.Title}}

{{.Description}}


{{end}}

{{ template “footer.html” .}}

  1. - before testing, make the title and description can include more letters (/entity/video.go):
  2. ```go
  3. //...
  4. type Video struct {
  5. //Title string `json:"title" binding:"min=2,max=10" validate:"is-cool"`
  6. Title string `json:"title" binding:"min=2,max=200" validate:"is-cool"`
  7. //Description string `json:"description" binding:"max=20"`
  8. Description string `json:"description" binding:"max=200"`
  9. //...
  10. }
  11. //...

to test, use Postman to send GET/POST requests,
this time use the url localhost:9090/api/videos:
image.png
and we use localhost:9090/view/videos in a browser:
image.png
now we have a video collection browser, it even can play the video.

About embedded youtube videos:

Hint: to upload youtube video as an embedded version, the upload format is:
https://www.youtube.com/embed/[video_id]
**

May. 18-19 - Deployment on AWS with Elastic Beanstalk (bonus: IAM setup)

AWS Preparation:

About AWS Elastic Beanstalk:

  • a developer-friendly platform to platform to deploy and scale web applications
  • support golang, node.js, python, etc.

About IAM:

  • “AWS Identity and Access Management (IAM) is a web service that helps you securely control access to AWS resources.”

  • To use AWS, we first need to create an AWS account.

  • After that, sign into AWS Management Console:

image.png
Choose “IAM”:
image.png
Choose “Policies” and find “AWSElasticBeanstalkFullAccess”:
image.png
and we can find a JSON file:

  1. {
  2. "Version": "2012-10-17",
  3. "Statement": [
  4. {
  5. "Effect": "Allow",
  6. "Action": [
  7. "elasticbeanstalk:*",
  8. "ec2:*",
  9. "ecs:*",
  10. "ecr:*",
  11. "elasticloadbalancing:*",
  12. "autoscaling:*",
  13. "cloudwatch:*",
  14. "s3:*",
  15. "sns:*",
  16. "cloudformation:*",
  17. "dynamodb:*",
  18. "rds:*",
  19. "sqs:*",
  20. "logs:*",
  21. "iam:GetPolicyVersion",
  22. "iam:GetRole",
  23. "iam:PassRole",
  24. "iam:ListRolePolicies",
  25. "iam:ListAttachedRolePolicies",
  26. "iam:ListInstanceProfiles",
  27. "iam:ListRoles",
  28. "iam:ListServerCertificates",
  29. "acm:DescribeCertificate",
  30. "acm:ListCertificates",
  31. "codebuild:CreateProject",
  32. "codebuild:DeleteProject",
  33. "codebuild:BatchGetBuilds",
  34. "codebuild:StartBuild"
  35. ],
  36. "Resource": "*"
  37. },
  38. {
  39. "Effect": "Allow",
  40. "Action": [
  41. "iam:AddRoleToInstanceProfile",
  42. "iam:CreateInstanceProfile",
  43. "iam:CreateRole"
  44. ],
  45. "Resource": [
  46. "arn:aws:iam::*:role/aws-elasticbeanstalk*",
  47. "arn:aws:iam::*:instance-profile/aws-elasticbeanstalk*"
  48. ]
  49. },
  50. {
  51. "Effect": "Allow",
  52. "Action": [
  53. "iam:CreateServiceLinkedRole"
  54. ],
  55. "Resource": [
  56. "arn:aws:iam::*:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling*"
  57. ],
  58. "Condition": {
  59. "StringLike": {
  60. "iam:AWSServiceName": "autoscaling.amazonaws.com"
  61. }
  62. }
  63. },
  64. {
  65. "Effect": "Allow",
  66. "Action": [
  67. "iam:CreateServiceLinkedRole"
  68. ],
  69. "Resource": [
  70. "arn:aws:iam::*:role/aws-service-role/elasticbeanstalk.amazonaws.com/AWSServiceRoleForElasticBeanstalk*"
  71. ],
  72. "Condition": {
  73. "StringLike": {
  74. "iam:AWSServiceName": "elasticbeanstalk.amazonaws.com"
  75. }
  76. }
  77. },
  78. {
  79. "Effect": "Allow",
  80. "Action": [
  81. "iam:CreateServiceLinkedRole"
  82. ],
  83. "Resource": [
  84. "arn:aws:iam::*:role/aws-service-role/elasticloadbalancing.amazonaws.com/AWSServiceRoleForElasticLoadBalancing*"
  85. ],
  86. "Condition": {
  87. "StringLike": {
  88. "iam:AWSServiceName": "elasticloadbalancing.amazonaws.com"
  89. }
  90. }
  91. },
  92. {
  93. "Effect": "Allow",
  94. "Action": [
  95. "iam:AttachRolePolicy"
  96. ],
  97. "Resource": "arn:aws:iam::*:role/aws-elasticbeanstalk*",
  98. "Condition": {
  99. "StringLike": {
  100. "iam:PolicyArn": [
  101. "arn:aws:iam::aws:policy/AWSElasticBeanstalk*",
  102. "arn:aws:iam::aws:policy/service-role/AWSElasticBeanstalk*"
  103. ]
  104. }
  105. }
  106. }
  107. ]
  108. }

back to the previous page, click “Create Policy”:
image.png
we copy the previous JSON file into it but remove “dynamodb:“, “rds:“, “sqs:*”,
set the name as “eb-deploy”

  • Click “Create New Group” called eb-deploy and include that eb-deploy policy before:

image.png
image.png

  • Create a new user and include that user into the group before:

image.png

Default port:

  • For later requests might not add a port number, so we need to modify server.go for a default port:

    1. //...
    2. func main() {
    3. //...
    4. port := os.Getenv("PORT")
    5. if port == "" {
    6. port = "9090"
    7. }
    8. //server.Run(":9090")
    9. server.Run(":" + port)
    10. }

    Deploy golang app without using Docker:

  • Create a “build.sh” shell file in /gin: ```bash

    !/usr/bin/env bash

    set -xe

install packages and dependencies

go get github.com/gin-gonic/gin

go get gopkg.in/go-playground/validator.v9

build command

go build -o bin/application server.go

  1. - Also we need a "Buildfile" in /gin to apply build.sh:

make: ./build.sh

  1. - Finally, a "Procfile" in /gin to run web application:

web: bin/application

  1. <a name="3zc8B"></a>
  2. ### May. 20
  3. <a name="AaqA5"></a>
  4. #### install aws-elastic-beanstalk-cli:
  5. - aws-elastic-beanstalk-cli (a.k.a. "eb")
  6. - how to install it varies depends on your operating systems
  7. - [https://github.com/aws/aws-elastic-beanstalk-cli-setup](https://github.com/aws/aws-elastic-beanstalk-cli-setup) includes the process, I applied:
  8. ```bash
  9. git clone https://github.com/aws/aws-elastic-beanstalk-cli-setup.git
  10. # install dependencies
  11. yum group install "Development Tools"
  12. yum install \
  13. zlib-devel openssl-devel ncurses-devel libffi-devel \
  14. sqlite-devel.x86_64 readline-devel.x86_64 bzip2-devel.x86_64
  15. ./aws-elastic-beanstalk-cli-setup/scripts/bundled_installer
  16. # ensure `eb` is in PATH
  17. echo 'export PATH="/root/.ebcli-virtual-env/executables:$PATH"' >> ~/.bash_profile && source ~/.bash_profile
  18. # ensure `python` is in PATH
  19. echo 'export PATH=/root/.pyenv/versions/3.7.2/bin:$PATH' >> /root/.bash_profile && source /root/.bash_profile

Apply policies:

in users/security credenitals, “Create Access Key” and download the corresponding csv file for keypairs we need.
image.png
image.png
The .csv file looks like:

  1. Access key ID,Secret access key
  2. [Access key ID string],[Secret access key string]
  • in the terminal, use: ```bash [root@localhost Downloads]# eb init

Select a default region 1) us-east-1 : US East (N. Virginia) 2) us-west-1 : US West (N. California) 3) us-west-2 : US West (Oregon) 4) eu-west-1 : EU (Ireland) 5) eu-central-1 : EU (Frankfurt) 6) ap-south-1 : Asia Pacific (Mumbai) 7) ap-southeast-1 : Asia Pacific (Singapore) 8) ap-southeast-2 : Asia Pacific (Sydney) 9) ap-northeast-1 : Asia Pacific (Tokyo) 10) ap-northeast-2 : Asia Pacific (Seoul) 11) sa-east-1 : South America (Sao Paulo) 12) cn-north-1 : China (Beijing) 13) cn-northwest-1 : China (Ningxia) 14) us-east-2 : US East (Ohio) 15) ca-central-1 : Canada (Central) 16) eu-west-2 : EU (London) 17) eu-west-3 : EU (Paris) 18) eu-north-1 : EU (Stockholm) 19) eu-south-1 : EU (Milano) 20) ap-east-1 : Asia Pacific (Hong Kong) 21) me-south-1 : Middle East (Bahrain) 22) af-south-1 : Africa (Cape Town) (default is 3): 1

  1. The most suitable region can be found by using ping results
  2. Choose the one address with the least latency here: [https://www.cloudping.info/](https://www.cloudping.info/)
  3. Then, the terminal will ask for the keypair before:
  4. ```bash
  5. You have not yet set up your credentials or your credentials are incorrect
  6. You must provide your credentials.
  7. (aws-access-id): [Access key ID string]
  8. (aws-secret-key): [Secret access key string]

Then, language and other settings:

  1. Select an application to use
  2. 1) golang-api
  3. 2) [ Create new Application ]
  4. (default is 2): 1
  5. Select a platform.
  6. 1) .NET on Windows Server
  7. 2) Docker
  8. 3) GlassFish
  9. 4) Go
  10. 5) Java
  11. 6) Node.js
  12. 7) PHP
  13. 8) Packer
  14. 9) Python
  15. 10) Ruby
  16. 11) Tomcat
  17. (make a selection): 4
  18. Select a platform branch.
  19. 1) Go 1 running on 64bit Amazon Linux 2
  20. 2) Go 1 running on 64bit Amazon Linux
  21. 3) Preconfigured Docker - Go 1.4 running on 64bit Debian (Deprecated)
  22. 4) Preconfigured Docker - Go 1.3 running on 64bit Debian (Deprecated)
  23. (default is 1): 1
  24. Cannot setup CodeCommit because there is no Source Control setup, continuing with initialization
  25. Do you want to set up SSH for your instances?
  26. (Y/n): n

now we have a .elasticbeanstalk/config.yml:

  1. branch-defaults:
  2. default:
  3. environment: null
  4. global:
  5. application_name: golang-api
  6. branch: null
  7. default_ec2_keyname: null
  8. default_platform: Go 1 running on 64bit Amazon Linux 2
  9. default_region: us-east-1
  10. include_git_submodules: true
  11. instance_profile: null
  12. platform_name: null
  13. platform_version: null
  14. profile: eb-cli
  15. repository: null
  16. sc: null
  17. workspace_type: Application
  • and create the app with that setting
    1. eb create --single
    create in Linux met unexpected error, so I was trying to use browser:
    image.png
    unsuccessful, there is a hidden problem
    —————————————————————————————————————————————————