You've already forked golang-saas-starter-kit
mirror of
https://github.com/raseels-repos/golang-saas-starter-kit.git
synced 2025-08-10 22:41:25 +02:00
Added build stage caching
This commit is contained in:
@@ -14,7 +14,9 @@ RUN GO111MODULE=off go get gopkg.in/go-playground/validator.v9 && \
|
|||||||
GO111MODULE=off go get github.com/tinylib/msgp/msgp && \
|
GO111MODULE=off go get github.com/tinylib/msgp/msgp && \
|
||||||
GO111MODULE=off go get gopkg.in/DataDog/dd-trace-go.v1/ddtrace && \
|
GO111MODULE=off go get gopkg.in/DataDog/dd-trace-go.v1/ddtrace && \
|
||||||
GO111MODULE=off go get github.com/xwb1989/sqlparser && \
|
GO111MODULE=off go get github.com/xwb1989/sqlparser && \
|
||||||
GO111MODULE=off go get golang.org/x/xerrors
|
GO111MODULE=off go get golang.org/x/xerrors && \
|
||||||
|
GO111MODULE=off go get github.com/pkg/errors && \
|
||||||
|
GO111MODULE=off go get golang.org/x/crypto/nacl/secretbox
|
||||||
|
|
||||||
# Install swag with go modules enabled.
|
# Install swag with go modules enabled.
|
||||||
RUN GO111MODULE=on go get -u github.com/swaggo/swag/cmd/swag
|
RUN GO111MODULE=on go get -u github.com/swaggo/swag/cmd/swag
|
||||||
|
@@ -1,8 +1,13 @@
|
|||||||
package cicd
|
package cicd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"crypto/md5"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -188,13 +193,111 @@ func ServiceBuild(log *log.Logger, req *serviceBuildRequest) error {
|
|||||||
|
|
||||||
// Once we can access the repository in ECR, do the docker build.
|
// Once we can access the repository in ECR, do the docker build.
|
||||||
{
|
{
|
||||||
log.Println("Starting docker build")
|
log.Printf("Starting docker build %s\n", req.ReleaseImage)
|
||||||
|
|
||||||
dockerFile, err := filepath.Rel(req.ProjectRoot, req.DockerFile)
|
dockerFile, err := filepath.Rel(req.ProjectRoot, req.DockerFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "Failed parse relative path for %s from %s", req.DockerFile, req.ProjectRoot)
|
return errors.Wrapf(err, "Failed parse relative path for %s from %s", req.DockerFile, req.ProjectRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When the dockerFile is multistage, caching can be applied. Scan the dockerFile for the first stage.
|
||||||
|
// FROM golang:1.12.6-alpine3.9 AS build_base
|
||||||
|
var buildBaseImageTag string
|
||||||
|
{
|
||||||
|
file, err := os.Open(req.DockerFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// List of lines in the dockerfile for the first stage. This will be used to tag the image to help ensure
|
||||||
|
// any changes to the lines associated with the first stage force cache to be reset.
|
||||||
|
var stageLines []string
|
||||||
|
|
||||||
|
// Name of the first build stage declared in the docckerFile.
|
||||||
|
var buildStageName string
|
||||||
|
|
||||||
|
// Loop through all the lines in the Dockerfile searching for the lines associated with the first build stage.
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
|
||||||
|
lineLower := strings.ToLower(line)
|
||||||
|
|
||||||
|
if strings.HasPrefix(lineLower, "from ") {
|
||||||
|
if buildStageName != "" {
|
||||||
|
// Only need to scan all the lines for the first build stage. Break when reach next FROM.
|
||||||
|
break
|
||||||
|
} else if !strings.Contains(lineLower, " as ") {
|
||||||
|
// Caching is only supported if the first FROM has a name.
|
||||||
|
log.Printf("\t\t\tSkipping stage cache, build stage not detected.\n")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
buildStageName = strings.TrimSpace(strings.Split(lineLower, " as ")[1])
|
||||||
|
stageLines = append(stageLines, line )
|
||||||
|
} else if buildStageName != "" {
|
||||||
|
stageLines = append(stageLines, line )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have detected a build stage, then generate the appropriate tag.
|
||||||
|
if buildStageName != "" {
|
||||||
|
log.Printf("\t\tFound build stage %s for caching.\n", buildStageName)
|
||||||
|
|
||||||
|
// Generate a checksum for the lines associated with the build stage.
|
||||||
|
stageChecksum := fmt.Sprintf("%x", md5.Sum([]byte(strings.Join(stageLines, "\n"))))
|
||||||
|
|
||||||
|
// Making the assumption that the first stage always imports go.sum. Compute a checksum for the file.
|
||||||
|
goSumPath := filepath.Join(req.ProjectRoot, "go.sum")
|
||||||
|
goSumDat, err := ioutil.ReadFile(goSumPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "Failed parse relative path for %s from %s", req.DockerFile, req.ProjectRoot)
|
||||||
|
}
|
||||||
|
goModChecksum := fmt.Sprintf("%x", md5.Sum(goSumDat))
|
||||||
|
|
||||||
|
// Combine both checksums used to tag the target build stage.
|
||||||
|
buildBaseHash := fmt.Sprintf("%x", md5.Sum([]byte(stageChecksum+goModChecksum)))
|
||||||
|
|
||||||
|
buildBaseImageTag = buildStageName + "-" + buildBaseHash[0:8]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmds [][]string
|
||||||
|
|
||||||
|
// Enabling caching of the first build stage defined in the dockerFile.
|
||||||
|
var buildBaseImage string
|
||||||
|
if !req.NoCache && buildBaseImageTag != "" {
|
||||||
|
var pushTargetImg bool
|
||||||
|
if ciReg := os.Getenv("CI_REGISTRY"); ciReg != "" {
|
||||||
|
cmds = append(cmds, []string{"docker", "login", "-u", "gitlab-ci-token", "-p", os.Getenv("CI_JOB_TOKEN"), ciReg})
|
||||||
|
|
||||||
|
buildBaseImage = ciReg + "/" + buildBaseImageTag
|
||||||
|
pushTargetImg = true
|
||||||
|
} else {
|
||||||
|
buildBaseImageTag = req.ProjectName + ":" + req.Env + "-" +req.ServiceName + "-" + buildBaseImageTag
|
||||||
|
}
|
||||||
|
|
||||||
|
cmds = append(cmds, []string{"docker", "pull", buildBaseImageTag})
|
||||||
|
|
||||||
|
cmds = append(cmds, []string{
|
||||||
|
"docker", "build",
|
||||||
|
"--file=" + dockerFile,
|
||||||
|
"--build-arg", "service=" + req.ServiceName,
|
||||||
|
"--build-arg", "env=" + req.Env,
|
||||||
|
"-t", buildBaseImageTag,
|
||||||
|
"--target", "build_base",
|
||||||
|
".",
|
||||||
|
})
|
||||||
|
|
||||||
|
if pushTargetImg {
|
||||||
|
cmds = append(cmds, []string{"docker", "push", buildBaseImageTag})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The initial build command slice.
|
// The initial build command slice.
|
||||||
buildCmd := []string{
|
buildCmd := []string{
|
||||||
"docker", "build",
|
"docker", "build",
|
||||||
@@ -207,30 +310,31 @@ func ServiceBuild(log *log.Logger, req *serviceBuildRequest) error {
|
|||||||
// Append additional build flags.
|
// Append additional build flags.
|
||||||
if req.NoCache {
|
if req.NoCache {
|
||||||
buildCmd = append(buildCmd, "--no-cache")
|
buildCmd = append(buildCmd, "--no-cache")
|
||||||
|
} else if buildBaseImage != "" {
|
||||||
|
buildCmd = append(buildCmd, "--cache-from", buildBaseImage)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally append the build context as the current directory since os.Exec will use the project root as
|
// Finally append the build context as the current directory since os.Exec will use the project root as
|
||||||
// the working directory.
|
// the working directory.
|
||||||
buildCmd = append(buildCmd, ".")
|
buildCmd = append(buildCmd, ".")
|
||||||
|
|
||||||
err = execCmds(log, req.ProjectRoot, buildCmd)
|
cmds = append(cmds, buildCmd)
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "Failed to build docker image")
|
if req.NoPush == false {
|
||||||
|
cmds = append(cmds, dockerLoginCmd)
|
||||||
|
cmds = append(cmds, []string{"docker", "push", req.ReleaseImage})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push the newly built image of the Docker container to the registry.
|
for _, cmd := range cmds {
|
||||||
if req.NoPush == false {
|
log.Printf("\t\t%s\n", strings.Join(cmd, " "))
|
||||||
|
|
||||||
log.Printf("\t\tDocker Login")
|
err = execCmds(log, req.ProjectRoot, cmd)
|
||||||
err = execCmds(log, req.ProjectRoot, dockerLoginCmd)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "Failed to login to AWS ECR")
|
if len(cmd) > 2 && cmd[1] == "pull" {
|
||||||
}
|
log.Printf("\t\t\tSkipping pull - %s\n", err.Error())
|
||||||
|
} else {
|
||||||
log.Printf("\t\tPush release image %s", req.ReleaseImage)
|
return errors.Wrapf(err, "Failed to exec %s", strings.Join(cmd, " "))
|
||||||
err = execCmds(log, req.ProjectRoot, []string{"docker", "push", req.ReleaseImage})
|
}
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "Failed to push docker image %s", req.ReleaseImage)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user