mirror of
https://github.com/raseels-repos/golang-saas-starter-kit.git
synced 2025-06-06 23:46:29 +02:00
263 lines
9.4 KiB
Go
263 lines
9.4 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"expvar"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"geeks-accelerator/oss/saas-starter-kit/example-project/tools/truss/cmd/dbtable2crud"
|
|
"geeks-accelerator/oss/saas-starter-kit/example-project/tools/truss/cmd/devops"
|
|
"github.com/kelseyhightower/envconfig"
|
|
"github.com/lib/pq"
|
|
_ "github.com/lib/pq"
|
|
"github.com/pkg/errors"
|
|
"github.com/urfave/cli"
|
|
sqltrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/database/sql"
|
|
sqlxtrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/jmoiron/sqlx"
|
|
)
|
|
|
|
// build is the git version of this program. It is set using build flags in the makefile.
|
|
var build = "develop"
|
|
|
|
// service is the name of the program used for logging, tracing and the
|
|
// the prefix used for loading env variables
|
|
// ie: export TRUSS_ENV=dev
|
|
var service = "TRUSS"
|
|
|
|
func main() {
|
|
// =========================================================================
|
|
// Logging
|
|
|
|
log := log.New(os.Stdout, service+" : ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile)
|
|
|
|
// =========================================================================
|
|
// Configuration
|
|
var cfg struct {
|
|
DB struct {
|
|
Host string `default:"127.0.0.1:5433" envconfig:"HOST"`
|
|
User string `default:"postgres" envconfig:"USER"`
|
|
Pass string `default:"postgres" envconfig:"PASS" json:"-"` // don't print
|
|
Database string `default:"shared" envconfig:"DATABASE"`
|
|
Driver string `default:"postgres" envconfig:"DRIVER"`
|
|
Timezone string `default:"utc" envconfig:"TIMEZONE"`
|
|
DisableTLS bool `default:"false" envconfig:"DISABLE_TLS"`
|
|
}
|
|
}
|
|
|
|
// For additional details refer to https://github.com/kelseyhightower/envconfig
|
|
if err := envconfig.Process(service, &cfg); err != nil {
|
|
log.Fatalf("main : Parsing Config : %v", err)
|
|
}
|
|
|
|
// TODO: can't use flag.Process here since it doesn't support nested arg options
|
|
//if err := flag.Process(&cfg); err != nil {
|
|
/// if err != flag.ErrHelp {
|
|
// log.Fatalf("main : Parsing Command Line : %v", err)
|
|
// }
|
|
// return // We displayed help.
|
|
//}
|
|
|
|
// =========================================================================
|
|
// Log App Info
|
|
|
|
// Print the build version for our logs. Also expose it under /debug/vars.
|
|
expvar.NewString("build").Set(build)
|
|
log.Printf("main : Started : Application Initializing version %q", build)
|
|
defer log.Println("main : Completed")
|
|
|
|
// Print the config for our logs. It's important to any credentials in the config
|
|
// that could expose a security risk are excluded from being json encoded by
|
|
// applying the tag `json:"-"` to the struct var.
|
|
{
|
|
cfgJSON, err := json.MarshalIndent(cfg, "", " ")
|
|
if err != nil {
|
|
log.Fatalf("main : Marshalling Config to JSON : %v", err)
|
|
}
|
|
log.Printf("main : Config : %v\n", string(cfgJSON))
|
|
}
|
|
|
|
// =========================================================================
|
|
// Start Database
|
|
var dbUrl url.URL
|
|
{
|
|
// Query parameters.
|
|
var q url.Values = make(map[string][]string)
|
|
|
|
// Handle SSL Mode
|
|
if cfg.DB.DisableTLS {
|
|
q.Set("sslmode", "disable")
|
|
} else {
|
|
q.Set("sslmode", "require")
|
|
}
|
|
|
|
q.Set("timezone", cfg.DB.Timezone)
|
|
|
|
// Construct url.
|
|
dbUrl = url.URL{
|
|
Scheme: cfg.DB.Driver,
|
|
User: url.UserPassword(cfg.DB.User, cfg.DB.Pass),
|
|
Host: cfg.DB.Host,
|
|
Path: cfg.DB.Database,
|
|
RawQuery: q.Encode(),
|
|
}
|
|
}
|
|
|
|
// Register informs the sqlxtrace package of the driver that we will be using in our program.
|
|
// It uses a default service name, in the below case "postgres.db". To use a custom service
|
|
// name use RegisterWithServiceName.
|
|
sqltrace.Register(cfg.DB.Driver, &pq.Driver{}, sqltrace.WithServiceName(service))
|
|
masterDb, err := sqlxtrace.Open(cfg.DB.Driver, dbUrl.String())
|
|
if err != nil {
|
|
log.Fatalf("main : Register DB : %s : %v", cfg.DB.Driver, err)
|
|
}
|
|
defer masterDb.Close()
|
|
|
|
// =========================================================================
|
|
// Start Truss
|
|
|
|
var deployFlags devops.ServiceDeployFlags
|
|
|
|
app := cli.NewApp()
|
|
app.Commands = []cli.Command{
|
|
{
|
|
Name: "dbtable2crud",
|
|
Aliases: []string{},
|
|
Usage: "-table=projects -file=../../internal/project/models.go -model=Project [-dbtable=TABLE] [-templateDir=DIR] [-projectPath=DIR] [-saveChanges=false] ",
|
|
Flags: []cli.Flag{
|
|
cli.StringFlag{Name: "dbtable, table"},
|
|
cli.StringFlag{Name: "modelFile, modelfile, file"},
|
|
cli.StringFlag{Name: "modelName, modelname, model"},
|
|
cli.StringFlag{Name: "templateDir, templates", Value: "./templates/dbtable2crud"},
|
|
cli.StringFlag{Name: "projectPath"},
|
|
cli.BoolFlag{Name: "saveChanges, save"},
|
|
},
|
|
Action: func(c *cli.Context) error {
|
|
dbTable := strings.TrimSpace(c.String("dbtable"))
|
|
modelFile := strings.TrimSpace(c.String("modelFile"))
|
|
modelName := strings.TrimSpace(c.String("modelName"))
|
|
templateDir := strings.TrimSpace(c.String("templateDir"))
|
|
projectPath := strings.TrimSpace(c.String("projectPath"))
|
|
|
|
pwd, err := os.Getwd()
|
|
if err != nil {
|
|
return errors.WithMessage(err, "Failed to get current working directory")
|
|
}
|
|
|
|
if !path.IsAbs(templateDir) {
|
|
templateDir = filepath.Join(pwd, templateDir)
|
|
}
|
|
ok, err := exists(templateDir)
|
|
if err != nil {
|
|
return errors.WithMessage(err, "Failed to load template directory")
|
|
} else if !ok {
|
|
return errors.Errorf("Template directory %s does not exist", templateDir)
|
|
}
|
|
|
|
if modelFile == "" {
|
|
return errors.Errorf("Model file path is required")
|
|
}
|
|
|
|
if !path.IsAbs(modelFile) {
|
|
modelFile = filepath.Join(pwd, modelFile)
|
|
}
|
|
ok, err = exists(modelFile)
|
|
if err != nil {
|
|
return errors.WithMessage(err, "Failed to load model file")
|
|
} else if !ok {
|
|
return errors.Errorf("Model file %s does not exist", modelFile)
|
|
}
|
|
|
|
// Load the project path from go.mod if not set.
|
|
if projectPath == "" {
|
|
goModFile := filepath.Join(pwd, "../../go.mod")
|
|
ok, err = exists(goModFile)
|
|
if err != nil {
|
|
return errors.WithMessage(err, "Failed to load go.mod for project")
|
|
} else if !ok {
|
|
return errors.Errorf("Failed to locate project go.mod at %s", goModFile)
|
|
}
|
|
|
|
b, err := ioutil.ReadFile(goModFile)
|
|
if err != nil {
|
|
return errors.WithMessagef(err, "Failed to read go.mod at %s", goModFile)
|
|
}
|
|
|
|
lines := strings.Split(string(b), "\n")
|
|
for _, l := range lines {
|
|
if strings.HasPrefix(l, "module ") {
|
|
projectPath = strings.TrimSpace(strings.Split(l, " ")[1])
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if modelName == "" {
|
|
modelName = strings.Split(filepath.Base(modelFile), ".")[0]
|
|
modelName = strings.Replace(modelName, "_", " ", -1)
|
|
modelName = strings.Replace(modelName, "-", " ", -1)
|
|
modelName = strings.Title(modelName)
|
|
modelName = strings.Replace(modelName, " ", "", -1)
|
|
}
|
|
|
|
return dbtable2crud.Run(masterDb, log, cfg.DB.Database, dbTable, modelFile, modelName, templateDir, projectPath, c.Bool("saveChanges"))
|
|
},
|
|
},
|
|
{
|
|
Name: "deploy",
|
|
Aliases: []string{"serviceDeploy"},
|
|
Usage: "-service=web-api -env=dev",
|
|
Flags: []cli.Flag{
|
|
cli.StringFlag{Name: "service", Usage: "name of cmd", Destination: &deployFlags.ServiceName},
|
|
cli.StringFlag{Name: "env", Usage: "dev, stage, or prod", Destination: &deployFlags.Env},
|
|
cli.BoolFlag{Name: "enable_https", Usage: "enable HTTPS", Destination: &deployFlags.EnableHTTPS},
|
|
cli.StringFlag{Name: "domain_name", Usage: "dev, stage, or prod", Destination: &deployFlags.ServiceDomainName},
|
|
cli.StringSliceFlag{Name: "domain_name_aliases", Usage: "dev, stage, or prod", Value: &deployFlags.ServiceDomainNameAliases},
|
|
cli.StringFlag{Name: "private_bucket", Usage: "dev, stage, or prod", Destination: &deployFlags.S3BucketPrivateName},
|
|
cli.StringFlag{Name: "public_bucket", Usage: "dev, stage, or prod", Destination: &deployFlags.S3BucketPublicName},
|
|
cli.StringFlag{Name: "dockerfile", Usage: "DockerFile for service", Destination: &deployFlags.DockerFile},
|
|
cli.StringFlag{Name: "root", Usage: "project root directory", Destination: &deployFlags.ProjectRoot},
|
|
cli.StringFlag{Name: "project", Usage: "name of project", Destination: &deployFlags.ProjectName},
|
|
cli.BoolFlag{Name: "elb", Usage: "enable deployed to use Elastic Load Balancer", Destination: &deployFlags.EnableEcsElb},
|
|
cli.BoolTFlag{Name: "lambda_vpc", Usage: "deploy lambda behind VPC", Destination: &deployFlags.EnableLambdaVPC},
|
|
cli.BoolFlag{Name: "no_build", Usage: "skip build and continue directly to deploy", Destination: &deployFlags.NoBuild},
|
|
cli.BoolFlag{Name: "no_deploy", Usage: "skip deploy after build", Destination: &deployFlags.NoDeploy},
|
|
cli.BoolFlag{Name: "no_cache", Usage: "skip docker cache", Destination: &deployFlags.NoCache},
|
|
cli.BoolFlag{Name: "no_push", Usage: "skip docker push after build", Destination: &deployFlags.NoPush},
|
|
cli.BoolFlag{Name: "recreate_service", Usage: "skip docker push after build", Destination: &deployFlags.RecreateService},
|
|
},
|
|
Action: func(c *cli.Context) error {
|
|
req, err := devops.NewServiceDeployRequest(log, deployFlags)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return devops.ServiceDeploy(log, req)
|
|
},
|
|
},
|
|
}
|
|
|
|
err = app.Run(os.Args)
|
|
if err != nil {
|
|
log.Fatalf("main : Truss : %+v", err)
|
|
}
|
|
|
|
log.Printf("main : Truss : Completed")
|
|
}
|
|
|
|
// exists returns a bool as to whether a file path exists.
|
|
func exists(path string) (bool, error) {
|
|
_, err := os.Stat(path)
|
|
if err == nil {
|
|
return true, nil
|
|
}
|
|
if os.IsNotExist(err) {
|
|
return false, nil
|
|
}
|
|
return true, err
|
|
}
|