diff --git a/_magefile/ask.go b/_magefile/ask.go new file mode 100644 index 0000000000000000000000000000000000000000..515bc3f2573fccefe23e8d414748c16124c163d7 --- /dev/null +++ b/_magefile/ask.go @@ -0,0 +1,34 @@ +package _magefile + +import ( + "fmt" + + "github.com/AlecAivazis/survey/v2" + "github.com/thoas/go-funk" +) + +func Select(message string, options []string) int { + var question = []*survey.Question{ + { + Name: "name", + Prompt: &survey.Select{ + Message: message, + Options: options, + }, + Validate: survey.Required, + }, + } + + answer := struct { + Name string + }{} + + err := survey.Ask(question, &answer, survey.WithPageSize(20)) + if err != nil { + fmt.Println(err.Error()) + return 0 + } + + index := funk.IndexOf(options, answer.Name) + return index +} diff --git a/_magefile/build.go b/_magefile/build.go new file mode 100644 index 0000000000000000000000000000000000000000..0eb335d53336892b38eed02dc05235121de74827 --- /dev/null +++ b/_magefile/build.go @@ -0,0 +1,134 @@ +package _magefile + +import ( + "fmt" + "os" + "os/exec" + "path" + "runtime" + "strings" + + "github.com/magefile/mage/sh" +) + +func Clean(path string) error { + + cmd := exec.Command("rm", "-rf", "build") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Dir = path + err := cmd.Run() + if err != nil { + return err + } + + cmd = exec.Command("mkdir", "-p", "build") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Dir = path + err = cmd.Run() + if err != nil { + return err + } + return nil +} + +func Build(dir string, isDebug bool) error { + currentDir, _ := os.Getwd() + fullPath := currentDir + "/core/" + dir + + if err := Clean(fullPath); err != nil { + return err + } + + handler := path.Base(fullPath) + fmt.Println(fmt.Sprintf("Building %v", handler)) + + outputDir := fmt.Sprintf(currentDir+`/core/build/handlers/%v`, handler) + outputFile := fmt.Sprintf(`%v/%v`, outputDir, handler) + appPath := fmt.Sprintf(`gitlab.com/ship-logic/backends/backend/core/%v`, handler) + err := BuildGolangApp(outputFile, appPath, isDebug) + if err != nil { + return err + } + + // Copy files in handler folder + err = copyFilesToBuildDir(fullPath, outputDir) + if err != nil { + return err + } + + // Copy shared files + err = copyFilesToBuildDir(currentDir+"/core/shared", outputDir) + if err != nil { + return err + } + + if isDebug { + // copy env file + err = copyFilesToBuildDir(currentDir+"/.env.debug", outputDir) + if err != nil { + return err + } + } + + return nil +} + +func BuildGolangApp(outputDir string, appPath string, isDebug bool) error { + commandArgs := []string{ + `build`, + fmt.Sprintf(`-ldflags=-X gitlab.com/ship-logic/backends/backend/globals.BuildVersion=%v -X gitlab.com/ship-logic/backends/backend/globals.OSType=%v -X gitlab.com/ship-logic/backends/backend/globals.IsDebugBuild=%v`, CurrentCommit(), runtime.GOOS, isDebug), + } + + if isDebug { + commandArgs = append(commandArgs, "-gcflags", "all=-N -l") + } + + commandArgs = append(commandArgs, `-o`, + outputDir, + appPath) + + env := map[string]string{ + "GOOS": "linux", + } + + if !isDebug { + if runtime.GOOS == "darwin" && runtime.GOARCH == "arm64" { // M1 mac + // Change to intel architecture (used when deploying locally) + env["GOARCH"] = "amd64" + } + } + + err := sh.RunWith(env, "go", commandArgs...) + if err != nil { + return err + } + return nil +} + +func BuildMJML(filename string) error { + // Get the full path to the file + currentDir, _ := os.Getwd() + fullFilePath := currentDir + "/" + filename + fmt.Println(fullFilePath) + + // Trim the extension from the filename + if strings.HasSuffix(fullFilePath, ".mjml") { + fullFilePath = strings.TrimSuffix(fullFilePath, ".mjml") + } + + commandArgs := []string{ + "-l strict", + "--config.beautify=true", + fmt.Sprintf("-r %v.mjml", fullFilePath), + fmt.Sprintf("-o%v.html", fullFilePath), + } + + err := sh.RunWith(nil, "mjml", commandArgs...) + if err != nil { + return err + } + return nil + +} diff --git a/_magefile/cdk.go b/_magefile/cdk.go new file mode 100644 index 0000000000000000000000000000000000000000..3ce59b8c974393923bab1adbc4c33527a6f98f94 --- /dev/null +++ b/_magefile/cdk.go @@ -0,0 +1,260 @@ +package _magefile + +import ( + _ "embed" + "fmt" + "io/ioutil" + "os" + "os/exec" + "strconv" + "strings" + + "gopkg.in/yaml.v2" +) + +func CDKCompileTypeScript(cdkDir string) error { + fmt.Println("Compiling Typescript") + + cmd := exec.Command("tsc") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Dir = cdkDir + err := cmd.Run() + + if err != nil { + return err + } + return nil +} + +func CDKDiff(cdkDir string, env string, stack string, local bool) error { + fmt.Println("Getting Diff for: " + stack) + + commandArgs := []string{ + `diff`, + stack, + `-c`, + fmt.Sprintf(`config=%v`, env), + `-c`, + fmt.Sprintf(`exclusively=%v`, stack), + `-c`, + `aws-cdk:enableDiffNoFail=true`, + } + + if local { + commandArgs = append(commandArgs, fmt.Sprintf(`--profile=shiplogic-%v`, env)) + } + + commandArgs = append(commandArgs, + `--require-approval=never`, + `--exclusively=true`, + `--progress=events`) + + fmt.Println(fmt.Sprintf("Running command: cdk %s", strings.Join(commandArgs, " "))) + cmd := exec.Command("cdk", commandArgs...) + cmd.Dir = cdkDir + + output, err := cmd.CombinedOutput() + if err != nil { + fmt.Println(string(output)) + return err + } + fmt.Println(string(output)) + return nil +} + +func CDKDeploy(cdkDir string, env string, stack string, exclusively bool, local bool) error { + commandArgs := []string{ + `deploy`, + fmt.Sprintf(`"%s"`, stack), + `-c`, + fmt.Sprintf(`config=%v`, env), + } + + if exclusively { + commandArgs = append(commandArgs, `-c`, + fmt.Sprintf(`exclusively="%v"`, stack)) + } + + if local { + commandArgs = append(commandArgs, fmt.Sprintf(`--profile=shiplogic-%v`, env)) + commandArgs = append(commandArgs, `--hotswap`) // make it go fast + } + + commandArgs = append(commandArgs, + `--require-approval=never`, + fmt.Sprintf(`--exclusively=%v`, strconv.FormatBool(exclusively)), + `--progress=events`, + `--app=./cdk.out`) + + fmt.Println(fmt.Sprintf("Running command: cdk %s", strings.Join(commandArgs, " "))) + + cmd := exec.Command("cdk", commandArgs...) + cmd.Dir = cdkDir + + output, err := cmd.CombinedOutput() + if err != nil { + fmt.Println(string(output)) + return err + } + fmt.Printf("==================%v==================\n", stack) + fmt.Println(string(output)) + fmt.Printf("==================%v==================\n\n\n\n", stack) + return nil +} + +func CDKSynthAll(cdkDir string, env string, local bool) error { + commandArgs := []string{ + `synth`, + `"*"`, + `-c`, + fmt.Sprintf(`config=%v`, env), + `-c`, + `aws-cdk:enableDiffNoFail=true`, + } + + if local { + commandArgs = append(commandArgs, fmt.Sprintf(`--profile=shiplogic-%v`, env)) + } + + commandArgs = append(commandArgs, + `--progress=events`) + + fmt.Println(fmt.Sprintf("Running command: cdk %s", strings.Join(commandArgs, " "))) + cmd := exec.Command("cdk", commandArgs...) + cmd.Dir = cdkDir + + output, err := cmd.CombinedOutput() + if err != nil { + fmt.Println("Failed to run CDK Synth") + fmt.Println(err) + fmt.Println(string(output)) + return err + } + fmt.Println(string(output)) + return nil +} + +func CDKSynthDebug(cdkDir string, env string, handler string) error { + // Check if sandbox environment is active and get the sandbox name from the config file + sandboxFilePath := "cdk/sandbox/config.dev.sandbox.yaml" + sandboxName := "" + if _, err := os.Stat(sandboxFilePath); err == nil { + sandboxYamlFile, err := ioutil.ReadFile(sandboxFilePath) + if err != nil { + return err + } + + var sandboxData = make(map[string]interface{}) + err = yaml.Unmarshal(sandboxYamlFile, &sandboxData) + if err != nil { + return err + } + + overrides := sandboxData["Overrides"].(map[interface{}]interface{}) + sandboxName = overrides["SandboxName"].(string) + } + + var stack string + if len(sandboxName) == 0 { + stack = fmt.Sprintf("shiplogic-backend-%v-core", env) + } else { + stack = fmt.Sprintf("sl-%v-%v-core", env, sandboxName) + } + + commandArgs := []string{ + `synth`, + stack, + `-c`, + fmt.Sprintf(`config=%v`, env), + fmt.Sprintf("--profile=shiplogic-%v", env), + `--require-approval=never`, + `--exclusively=true`, + `--no-staging`, + } + + fmt.Println(fmt.Sprintf("Running command: cdk %s", strings.Join(commandArgs, " "))) + + cmd := exec.Command("cdk", commandArgs...) + cmd.Dir = cdkDir + + output, err := cmd.CombinedOutput() + if err != nil { + fmt.Println(string(output)) + return err + } + + file, err := os.Create("core/" + handler + "/template.yml") + if err != nil { + return err + } + defer file.Close() + + _, err = file.WriteString(string(output)) + if err != nil { + return err + } + + return nil +} + +var ( + //go:embed clear-cdk-cache.sh + script []byte +) + +func ClearCDKCache(cdkDir string) error { + cmd := exec.Command("rm", "-rf", "./cdk.out") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Dir = cdkDir + err := cmd.Run() + if err != nil { + return err + } + return nil + + // runScript := fmt.Sprintf("%s", string(script)) + // + // var out bytes.Buffer + // cmd := exec.Command("/bin/bash", "-s") + // cmd.Stdin = bytes.NewBuffer([]byte(runScript)) + // cmd.Dir = cdkDir + // cmd.Stderr = os.Stderr + // cmd.Stdout = os.Stdout + // err := cmd.Run() + // fmt.Println(string(out.Bytes())) + // if err != nil { + // return err + // } + // return nil +} + +func FunctionFromTemplate(path string) ([]string, error) { + yamlFile, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + template := map[string]interface{}{} + err = yaml.Unmarshal(yamlFile, template) + if err != nil { + return nil, err + } + + resources := template["Resources"].(map[interface{}]interface{}) + + functions := []string{} + for _, function := range resources { + function := function.(map[interface{}]interface{}) + if function["Type"] == "AWS::Lambda::Function" { + properties := function["Properties"].(map[interface{}]interface{}) + name, exists := properties["FunctionName"] + if exists { + functions = append(functions, name.(string)) + } + + } + } + + return functions, nil +} diff --git a/_magefile/clear-cdk-cache.sh b/_magefile/clear-cdk-cache.sh new file mode 100755 index 0000000000000000000000000000000000000000..5b675c616699e48339feb09765f5a7882abc56c9 --- /dev/null +++ b/_magefile/clear-cdk-cache.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +# Check what will be deleted: +#(cd ../cdk; find . -wholename "*/cdk.out/asset.*" -type d -prune -print | xargs du -chs) +#(cd ../cdk; find . -wholename "*/cdk.out/.cache" -type d -prune -print | xargs du -chs) + +# Actually delete them: +(cd ../cdk; find . -wholename "*/cdk.out/asset.*" -type d -prune -print -exec rm -rf '{}' \;) +(cd ../cdk; find . -wholename "*/cdk.out/.cache" -type d -prune -print -exec rm -rf '{}' \;) \ No newline at end of file diff --git a/_magefile/database.go b/_magefile/database.go new file mode 100644 index 0000000000000000000000000000000000000000..195faf252115f5625fb926ce73455552022bfd5e --- /dev/null +++ b/_magefile/database.go @@ -0,0 +1 @@ +package _magefile diff --git a/_magefile/debug.go b/_magefile/debug.go new file mode 100644 index 0000000000000000000000000000000000000000..7ba21068a63353c8175e7e48632e009888e900d6 --- /dev/null +++ b/_magefile/debug.go @@ -0,0 +1,135 @@ +package _magefile + +import ( + "context" + "fmt" + "log" + "os" + "os/exec" + "os/signal" + "os/user" + "syscall" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/client" +) + +func SamStartApi(ctx context.Context, env string, handler string, debug bool) error { + stopRunningSamDocker() + + usr, _ := user.Current() + homeDir := usr.HomeDir + delveDir := homeDir + "/go/delve/" + + commandArgs := []string{ + `local`, + `start-api`, + } + + if debug { + commandArgs = append(commandArgs, `--debug-port=5986`, + fmt.Sprintf(`--debugger-path=%s`, delveDir), + `--debug-args=-delveAPI=2`) + } else { + commandArgs = append(commandArgs, `--warm-containers`, `EAGER`) + } + + commandArgs = append(commandArgs, + fmt.Sprintf(`--profile=shiplogic-%v`, env), + fmt.Sprintf(`--template=core/%v/template.yml`, handler), + `--region=af-south-1`) + + err := runLongRunningSubProcess(ctx, "sam", commandArgs...) + if err != nil { + return err + } + + return nil +} + +func SamInvokeSQS(ctx context.Context, env string, handler string, function string) error { + stopRunningSamDocker() + + usr, _ := user.Current() + homeDir := usr.HomeDir + delveDir := homeDir + "/go/delve/" + + commandArgs := []string{ + `local`, + `invoke`, + `-e`, + fmt.Sprintf(`core/%v/sqs.input.json`, handler), + function, + `--debug-port=5986`, + fmt.Sprintf(`--debugger-path=%s`, delveDir), + `--debug-args=-delveAPI=2`, + fmt.Sprintf(`--profile=shiplogic-%v`, env), + fmt.Sprintf(`--template=core/%v/template.yml`, handler), + `--region=af-south-1`, + } + + err := runLongRunningSubProcess(ctx, "sam", commandArgs...) + if err != nil { + return err + } + + return nil +} + +func runLongRunningSubProcess(ctx context.Context, name string, arg ...string) error { + cancelContext, cancel := context.WithCancel(ctx) + signalChan := make(chan os.Signal, 1) + signal.Notify(signalChan, os.Interrupt) + defer func() { + signal.Stop(signalChan) + cancel() + }() + + cmd := exec.CommandContext(cancelContext, name, arg...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + go func() { + select { + case <-signalChan: // first signal, cancel context + fmt.Println("Stopping process") + if err := cmd.Process.Signal(syscall.SIGINT); err != nil { + log.Fatal("failed to kill process: ", err) + } + cancel() + case <-cancelContext.Done(): + } + <-signalChan // second signal, hard exit + os.Exit(1) + }() + + err := cmd.Run() + if err != nil { + return err + } + return nil +} + +func stopRunningSamDocker() { + cli, err := client.NewEnvClient() + if err != nil { + panic(err) + } + + containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{}) + if err != nil { + panic(err) + } + + for _, container := range containers { + for _, port := range container.Ports { + if port.PrivatePort == 5986 { + err = cli.ContainerStop(context.Background(), container.ID, nil) + if err != nil { + panic(err) + } + } + } + } + +} diff --git a/_magefile/file_system.go b/_magefile/file_system.go new file mode 100644 index 0000000000000000000000000000000000000000..0bce74e6f9373e2bac9f8ebe6c01d356a9f02648 --- /dev/null +++ b/_magefile/file_system.go @@ -0,0 +1,170 @@ +package _magefile + +import ( + "context" + "io" + "io/ioutil" + "log" + "os" + "os/signal" + "path" + "path/filepath" + + "github.com/thoas/go-funk" + + "github.com/magefile/mage/sh" +) + +func GetDirs(path string) []os.FileInfo { + filesDir, err := ioutil.ReadDir(path) + // filesDir, err := ioutil.ReadDir("/Users/johan/src/Shiplogic/backends/backend/billing") + if err != nil { + log.Fatal(err) + } + + dirs := funk.Filter(filesDir, func(info os.FileInfo) bool { + if info.IsDir() && info.Name() == "build" { + return false + } + + return info.IsDir() + }).([]os.FileInfo) + + return dirs +} + +func copyFilesToBuildDir(currentDir string, buildDir string) error { + files, err := AllFiles(currentDir) + if err != nil { + return err + } + for _, file := range files { + fileExtension := filepath.Ext(file) + if fileExtension == ".go" || fileExtension == ".md" || fileExtension == ".html" || fileExtension == ".mjml" || fileExtension == ".txt" { + continue + } + + // Copy files to build DIR + err := Copy(file, buildDir+"/"+path.Base(file)) + if err != nil { + return err + } + } + return nil +} + +func AllFiles(dir string) ([]string, error) { + files := []string{} + err := filepath.Walk(dir, + func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + + files = append(files, path) + return nil + }) + + if err != nil { + return files, err + } + return files, nil +} + +func Copy(src, dst string) error { + fileInfo, err := os.Stat(src) + if err != nil { + return err + } + + if fileInfo.Mode() == os.ModeSymlink { + src, err = os.Readlink(src) + if err != nil { + return err + } + } + + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + + out, err := os.Create(dst) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, in) + if err != nil { + return err + } + + err = os.Chmod(dst, fileInfo.Mode()) + if err != nil { + return err + } + + return out.Close() +} + +func CurrentBasePath() string { + currentDir, _ := os.Getwd() + base := path.Base(currentDir) + return base +} + +func CurrentEnv() string { + + // Get env from CI commit branch + env := os.Getenv("CI_COMMIT_BRANCH") + + // Get env from current git branch + if env == "" { + commandArgs := []string{ + `rev-parse`, + `--abbrev-ref`, + `HEAD`, + } + + var err error + env, err = sh.Output("git", commandArgs...) + if err != nil { + return "" + } + } + + if env != "dev" && env != "stage" && env != "prod" { + env = "dev" + } + return env +} + +const ( + exitCodeErr = 1 + exitCodeInterrupt = 2 +) + +func killWithContext(ctx context.Context) { + cancelContext, cancel := context.WithCancel(ctx) + signalChan := make(chan os.Signal, 1) + signal.Notify(signalChan, os.Interrupt) + defer func() { + signal.Stop(signalChan) + cancel() + }() + go func() { + select { + case <-signalChan: // first signal, cancel context + cancel() + os.Exit(exitCodeInterrupt) + case <-cancelContext.Done(): + } + <-signalChan // second signal, hard exit + os.Exit(exitCodeInterrupt) + }() +} diff --git a/_magefile/git.go b/_magefile/git.go new file mode 100644 index 0000000000000000000000000000000000000000..57dc782cb1a9f6a050306341faef62ef139758a1 --- /dev/null +++ b/_magefile/git.go @@ -0,0 +1,45 @@ +package _magefile + +import ( + "fmt" + "os/exec" + "strings" +) + +func HasChanges(previousCommit string, folder string) bool { + fmt.Println(fmt.Sprintf("Comparing changes from: %v for %v", previousCommit, folder)) + commandArgs := []string{ + `diff`, + `--name-only`, + previousCommit + `..HEAD`, + folder, + } + + cmd := exec.Command("git", commandArgs...) + output, err := cmd.CombinedOutput() + if err != nil { + fmt.Println(err) + } + changes := strings.Fields(string(output)) + if len(changes) > 0 { + fmt.Println(fmt.Sprintf("Has %v changes", folder)) + return true + } + fmt.Println("No changes") + return false +} + +func CurrentCommit() string { + + commandArgs := []string{ + `rev-parse`, + `--short`, + `HEAD`, + } + cmd := exec.Command("git", commandArgs...) + output, err := cmd.CombinedOutput() + if err != nil { + fmt.Println(err) + } + return string(output) +} diff --git a/_magefile/s3.go b/_magefile/s3.go new file mode 100644 index 0000000000000000000000000000000000000000..e7e4aaf479058d308e536f11e276e35f93d8d9e3 --- /dev/null +++ b/_magefile/s3.go @@ -0,0 +1,63 @@ +package _magefile + +import ( + "bytes" + "fmt" + "net/http" + "os" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" +) + +func UploadToS3(env string, bucket string, filePath string, name string, local bool) error { + fmt.Println(fmt.Sprintf("Uploading %s to %s", filePath, bucket)) + + options := session.Options{ + Config: aws.Config{ + Region: aws.String("af-south-1"), + }, + } + + if local { + options.Profile = fmt.Sprintf("shiplogic-%s", env) + } + + sess, err := session.NewSessionWithOptions(options) + if err != nil { + return err + } + + // Open the file for use + file, err := os.Open(filePath) + if err != nil { + return err + } + defer file.Close() + + // Get file size and read the file content into a buffer + fileInfo, _ := file.Stat() + var size = fileInfo.Size() + buffer := make([]byte, size) + _, err = file.Read(buffer) + if err != nil { + return err + } + + // Upload to s3 + putInput := &s3.PutObjectInput{ + Bucket: aws.String(bucket), + Key: aws.String(name), + Body: bytes.NewReader(buffer), + ContentLength: aws.Int64(size), + ContentType: aws.String(http.DetectContentType(buffer)), + } + svc := s3.New(sess) + _, err = svc.PutObject(putInput) + if err != nil { + return err + } + + return nil +} diff --git a/mage_ci.go b/mage_ci.go new file mode 100644 index 0000000000000000000000000000000000000000..f047595f3bc67f2bb06a580ebf5b4d8ca205da7c --- /dev/null +++ b/mage_ci.go @@ -0,0 +1,13 @@ +package main + +import ( + "os" + + "github.com/magefile/mage/mage" +) + +func main() { os.Exit(mage.Main()) } + +// func main() { +// DebugSQS(nil) +// } diff --git a/mage_debug.go b/mage_debug.go new file mode 100644 index 0000000000000000000000000000000000000000..efe4cb60f5591ddd950a2851574d6a03550a2ee9 --- /dev/null +++ b/mage_debug.go @@ -0,0 +1,112 @@ +// +build mage + +package main + +import ( + "context" + "sync" + + "github.com/magefile/mage/mg" + + "gitlab.com/ship-logic/backends/backend/_magefile" +) + +// ==================================================================================== +// region Debug +// ==================================================================================== + +// build a debug build and start sam +func Debug(ctx context.Context) error { + mg.Deps(clearCDKCache) + mg.Deps(compileCDKTypeScript) + + // Build + api := "handler_api" + if err := _magefile.Build(api, true); err != nil { + return err + } + + // Create template file + env := _magefile.CurrentEnv() + cdkDir := "./cdk" + if err := _magefile.CDKSynthDebug(cdkDir, env, api); err != nil { + return err + } + + err := _magefile.SamStartApi(ctx, env, api, true) + return err +} + +// build a debug build and invoke the sqs queue +func DebugSQS(ctx context.Context) error { + mg.Deps(clearCDKCache) + mg.Deps(compileCDKTypeScript) + + // Build + sqs := "handler_sqs" + if err := _magefile.Build(sqs, true); err != nil { + return err + } + + // Create template file + cdkDir := "./cdk" + env := _magefile.CurrentEnv() + if err := _magefile.CDKSynthDebug(cdkDir, env, sqs); err != nil { + return err + } + + functions, err := _magefile.FunctionFromTemplate("core/" + sqs + "/template.yml") + if err != nil { + return err + } + + index := _magefile.Select("Choose a function:", functions) + functionName := functions[index] + + err = _magefile.SamInvokeSQS(ctx, env, sqs, functionName) + return err +} + +// used from golang to build a debug function +func BuildDebugFunction() error { + var waitGroup sync.WaitGroup + for _, handler := range handlers { + waitGroup.Add(1) + go func(handler string) { + if err := _magefile.Build(handler, true); err != nil { + panic(err) + } + waitGroup.Done() + }(handler) + } + waitGroup.Wait() + return nil +} + +// endregion + +// ==================================================================================== +// region Run +// ==================================================================================== + +func Run(ctx context.Context) error { + mg.Deps(clearCDKCache) + mg.Deps(compileCDKTypeScript) + // Build + api := "handler_api" + if err := _magefile.Build(api, true); err != nil { + return err + } + + // Create template file + env := _magefile.CurrentEnv() + cdkDir := "./cdk" + if err := _magefile.CDKSynthDebug(cdkDir, env, api); err != nil { + return err + } + + err := _magefile.SamStartApi(ctx, env, api, false) + return err +} + +// endregion diff --git a/magefile.go b/magefile.go new file mode 100644 index 0000000000000000000000000000000000000000..f7f49cb0c941b7b92a4bc87cf6b48c792f7eb273 --- /dev/null +++ b/magefile.go @@ -0,0 +1,277 @@ +// +build mage + +package main + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "sync" + + "github.com/magefile/mage/mg" + "gitlab.com/ship-logic/backends/backend/_magefile" +) + +var handlers = []string{ + "handler_adhoc", + "handler_api", + "handler_cron", + "handler_sqs", +} + +// ==================================================================================== +// region Build +// ==================================================================================== + +// Builds all the microservices +func Build() error { + var waitGroup sync.WaitGroup + for _, handler := range handlers { + waitGroup.Add(1) + go func(handler string) { + if err := _magefile.Build(handler, false); err != nil { + panic(err) + } + waitGroup.Done() + }(handler) + } + waitGroup.Wait() + return nil +} + +func BuildWorkers() error { + if err := _magefile.BuildGolangApp("workers-ec2/high-priority", "./workers-ec2/high-priority", false); err != nil { + return err + } + if err := _magefile.BuildGolangApp("workers-ec2/medium-priority", "./workers-ec2/medium-priority", false); err != nil { + return err + } + if err := _magefile.BuildGolangApp("workers-ec2/low-priority", "./workers-ec2/low-priority", false); err != nil { + return err + } + return nil +} + +// endregion + +// ==================================================================================== +// region CDK +// ==================================================================================== + +func compileCDKTypeScript() error { + cdkDir := "./cdk" + return _magefile.CDKCompileTypeScript(cdkDir) +} + +func clearCDKCache() error { + cdkDir := "./cdk" + fmt.Println("Clean CDK Cache") + if err := _magefile.ClearCDKCache(cdkDir); err != nil { + return err + } + return nil +} + +func SetupCDK(local bool) { + env := _magefile.CurrentEnv() + cdkDir := "./cdk" + + mg.Deps(Build) + mg.Deps(clearCDKCache) + mg.Deps(compileCDKTypeScript) + mg.Deps(mg.F(_magefile.CDKSynthAll, cdkDir, env, local)) +} + +// Run a stack diff +func Diff(local bool) error { + mg.Deps(mg.F(SetupCDK, local)) + + env := _magefile.CurrentEnv() + stack := fmt.Sprintf("shiplogic-backend-%v-core", env) + + // CDK Diff + cdkDir := "./cdk" + return _magefile.CDKDiff(cdkDir, env, stack, local) +} + +// Use CDK to deploy the base infrastructure to AWS. +func DeployBase(local bool) error { + mg.Deps(mg.F(SetupCDK, local)) + + cdkDir := "./cdk" + env := _magefile.CurrentEnv() + baseStacks := []string{ + "infra", + "infra-us-east", + "cognito", + "policies", + } + + for _, stack := range baseStacks { + stackName := fmt.Sprintf("shiplogic-backend-%v-%v", env, stack) + fmt.Println("Deploying: " + stack) + if err := _magefile.CDKDeploy(cdkDir, env, stackName, false, local); err != nil { + return err + } + } + + // Frontend + stack := fmt.Sprintf("shiplogic-frontend-%v", env) + fmt.Println("Deploying: " + stack) + if err := _magefile.CDKDeploy(cdkDir, env, stack, false, local); err != nil { + return err + } + + return nil +} + +// Use CDK to deploy all the service monitoring stacks +func DeployServiceMonitoringStack(local bool) error { + mg.Deps(mg.F(SetupCDK, local)) + + env := _magefile.CurrentEnv() + stack := fmt.Sprintf("shiplogic-backend-%v-service-monitoring", _magefile.CurrentEnv()) + if err := _magefile.CDKDeploy("./cdk", env, stack, false, local); err != nil { + return err + } + + return nil +} + +// Use CDK to deploy a specific microservice to AWS. Needs to be in the format `mage deploy billing` +func DeployCore(local bool) error { + mg.Deps(mg.F(SetupCDK, local)) + + // CDK deploy + env := _magefile.CurrentEnv() + stack := fmt.Sprintf("shiplogic-backend-%v-core", env) + + // CDK deploy + fmt.Println("Deploying: " + stack) + return _magefile.CDKDeploy("./cdk", env, stack, true, local) +} + +// endregion + +// ==================================================================================== +// region CI +// ==================================================================================== + +func RunCI(previousCommit string, local bool) error { + mg.Deps(mg.F(SetupCDK, local)) + + // Deploy base + hasCDKChanges := _magefile.HasChanges(previousCommit, "cdk") + if hasCDKChanges { + fmt.Println("Deploying base") + if err := DeployBase(local); err != nil { + return err + } + } + + // Deploy core + utilsChanged := _magefile.HasChanges(previousCommit, "utils") + coreChanged := _magefile.HasChanges(previousCommit, "core") + if coreChanged || utilsChanged || hasCDKChanges { + err := DeployCore(local) + if err != nil { + return err + } + } + + return nil +} + +func DeployWorkers(previousCommit string, local bool) error { + env := _magefile.CurrentEnv() + + hasWorkerChanges := _magefile.HasChanges(previousCommit, "workers-ec2") + if !hasWorkerChanges && env == "dev" { + return nil + } + + mg.Deps(mg.F(BuildWorkers)) + + bucket := fmt.Sprintf("shiplogic-backend-%s-infra-worker-update", env) + + if err := _magefile.UploadToS3(env, bucket, "./workers-ec2/high-priority/high-priority", "high-priority", local); err != nil { + return err + } + if err := _magefile.UploadToS3(env, bucket, "./workers-ec2/medium-priority/medium-priority", "medium-priority", local); err != nil { + return err + } + if err := _magefile.UploadToS3(env, bucket, "./workers-ec2/low-priority/low-priority", "low-priority", local); err != nil { + return err + } + + // Create version file + file, err := ioutil.TempFile(os.TempDir(), "version") + if err != nil { + return err + } + defer os.Remove(file.Name()) + + // Save current commit in version file + commitHash := _magefile.CurrentCommit() + _, err = file.Write([]byte(strings.TrimSuffix(commitHash, "\n"))) + if err != nil { + return err + } + + // Upload to s3 + if err := _magefile.UploadToS3(env, bucket, file.Name(), "version", local); err != nil { + return err + } + return nil +} + +// endregion + +// ==================================================================================== +// region MJML +// ==================================================================================== + +// Builds all the microservices +func BuildMJML() error { + // Install mjml with `npm install -g mjml` + + // Recursively traverse all folders in the microservice to find and build MJML files. + err := filepath.Walk("core", + func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.Mode().IsDir() { + // Ignore "build" directories + if info.Name() == "build" { + return nil + } + + // Get all the subdirectories + files, err := ioutil.ReadDir(path) + if err != nil { + return err + } + + // Build all MJML files + for _, f := range files { + if strings.HasSuffix(f.Name(), ".mjml") { + mjmlFilePath := path + "/" + f.Name() + err = _magefile.BuildMJML(mjmlFilePath) + if err != nil { + return err + } + } + } + } + return nil + }) + if err != nil { + return err + } + return nil +} + +// endregion