You've already forked golang-saas-starter-kit
mirror of
https://github.com/raseels-repos/golang-saas-starter-kit.git
synced 2025-07-15 01:34:32 +02:00
update devops
This commit is contained in:
@ -6,16 +6,16 @@ services:
|
||||
variables:
|
||||
DOCKER_TLS_CERTDIR: ""
|
||||
|
||||
before_script:
|
||||
- 'go install ./build/cicd'
|
||||
|
||||
stages:
|
||||
- setup:dev
|
||||
- build:dev
|
||||
- migrate:dev
|
||||
- deploy:dev
|
||||
- setup:stage
|
||||
- build:stage
|
||||
- migrate:stage
|
||||
- deploy:stage
|
||||
- setup:prod
|
||||
- build:prod
|
||||
- migrate:prod
|
||||
- deploy:prod
|
||||
@ -29,20 +29,43 @@ cache:
|
||||
only:
|
||||
- master
|
||||
|
||||
.build_tmpl: &build_tmpl
|
||||
.setup_tmpl: &setup_tmpl
|
||||
<<: *job_tmpl
|
||||
script:
|
||||
- 'cicd --env=${TARGET_ENV} build ${TARGET_TYPE} --name=${TARGET_REF}'
|
||||
|
||||
.deploy_tmpl: &deploy_tmpl
|
||||
<<: *job_tmpl
|
||||
script:
|
||||
- 'cicd --env=${TARGET_ENV} deploy ${TARGET_TYPE} --name=${TARGET_REF}'
|
||||
- 'go build -o cicd ./build/cicd'
|
||||
- './cicd --env=${TARGET_ENV} deploy infrastructure'
|
||||
artifacts:
|
||||
paths:
|
||||
- cicd
|
||||
expire_in: 1 day
|
||||
|
||||
.migrate_tmpl: &migrate_tmpl
|
||||
<<: *job_tmpl
|
||||
script:
|
||||
- 'cicd --env=${TARGET_ENV} schema migrate'
|
||||
- './cicd --env=${TARGET_ENV} schema migrate'
|
||||
|
||||
.build_tmpl: &build_tmpl
|
||||
<<: *job_tmpl
|
||||
script:
|
||||
- './cicd --env=${TARGET_ENV} build ${TARGET_TYPE} --name=${TARGET_REF}'
|
||||
|
||||
.deploy_tmpl: &deploy_tmpl
|
||||
<<: *job_tmpl
|
||||
script:
|
||||
- './cicd --env=${TARGET_ENV} deploy ${TARGET_TYPE} --name=${TARGET_REF}'
|
||||
|
||||
infra:setup:prod:
|
||||
<<: *setup_tmpl
|
||||
stage: setup:prod
|
||||
tags:
|
||||
- prod
|
||||
only:
|
||||
- master
|
||||
- prod
|
||||
- /^prod-.*$/
|
||||
variables:
|
||||
TARGET_ENV: 'prod'
|
||||
AWS_USE_ROLE: 'true'
|
||||
|
||||
db:migrate:prod:
|
||||
<<: *migrate_tmpl
|
||||
@ -56,6 +79,7 @@ db:migrate:prod:
|
||||
variables:
|
||||
TARGET_ENV: 'prod'
|
||||
AWS_USE_ROLE: 'true'
|
||||
#when: manual
|
||||
|
||||
webapp:build:prod:
|
||||
<<: *build_tmpl
|
||||
@ -81,6 +105,7 @@ webapp:deploy:prod:
|
||||
- prod
|
||||
- prod-web-app
|
||||
dependencies:
|
||||
- 'infra:setup:prod'
|
||||
- 'webapp:build:prod'
|
||||
- 'db:migrate:prod'
|
||||
variables:
|
||||
@ -114,6 +139,7 @@ webapi:deploy:prod:
|
||||
- prod
|
||||
- prod-web-api
|
||||
dependencies:
|
||||
- 'infra:setup:prod'
|
||||
- 'webapi:build:prod'
|
||||
- 'db:migrate:prod'
|
||||
variables:
|
||||
@ -147,6 +173,7 @@ ddlogscollector:deploy:prod:
|
||||
- prod
|
||||
- prod-ddlogs
|
||||
dependencies:
|
||||
- 'infra:setup:prod'
|
||||
- 'ddlogscollector:build:prod'
|
||||
- 'db:migrate:prod'
|
||||
variables:
|
||||
|
@ -2,6 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
@ -51,47 +52,26 @@ var EnvNames = []Env{
|
||||
EnvProd,
|
||||
}
|
||||
|
||||
// ConfigContext defines the flags for build env.
|
||||
type ConfigContext struct {
|
||||
// Env is the target environment used for the deployment.
|
||||
Env string `validate:"oneof=dev stage prod"`
|
||||
|
||||
// AwsCredentials defines the credentials used for deployment.
|
||||
AwsCredentials devdeploy.AwsCredentials `validate:"required,dive,required"`
|
||||
}
|
||||
|
||||
// NewConfigContext returns the ConfigContext.
|
||||
func NewConfigContext(targetEnv Env, awsCredentials devdeploy.AwsCredentials) (*ConfigContext, error) {
|
||||
ctx := &ConfigContext{
|
||||
// NewConfig defines the details to setup the target environment for the project to build services and functions.
|
||||
func NewConfig(log *log.Logger, targetEnv Env, awsCredentials devdeploy.AwsCredentials) (*devdeploy.Config, error) {
|
||||
cfg := &devdeploy.Config{
|
||||
Env: targetEnv,
|
||||
AwsCredentials: awsCredentials,
|
||||
}
|
||||
|
||||
// If AWS Credentials are not set and use role is not enabled, try to load the credentials from env vars.
|
||||
if ctx.AwsCredentials.UseRole == false && ctx.AwsCredentials.AccessKeyID == "" {
|
||||
if cfg.AwsCredentials.UseRole == false && cfg.AwsCredentials.AccessKeyID == "" {
|
||||
var err error
|
||||
ctx.AwsCredentials, err = devdeploy.GetAwsCredentialsFromEnv(ctx.Env)
|
||||
cfg.AwsCredentials, err = devdeploy.GetAwsCredentialsFromEnv(cfg.Env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if ctx.AwsCredentials.Region == "" {
|
||||
awsCreds, err := devdeploy.GetAwsCredentialsFromEnv(ctx.Env)
|
||||
} else if cfg.AwsCredentials.Region == "" {
|
||||
awsCreds, err := devdeploy.GetAwsCredentialsFromEnv(cfg.Env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ctx.AwsCredentials.Region = awsCreds.Region
|
||||
}
|
||||
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// Config defines the details to setup the target environment for the project to build services and functions.
|
||||
func (cfgCtx *ConfigContext) Config(log *log.Logger) (*devdeploy.Config, error) {
|
||||
|
||||
// Init a new build target environment for the project.
|
||||
cfg := &devdeploy.Config{
|
||||
Env: cfgCtx.Env,
|
||||
AwsCredentials: cfgCtx.AwsCredentials,
|
||||
cfg.AwsCredentials.Region = awsCreds.Region
|
||||
}
|
||||
|
||||
// Get the current working directory. This should be somewhere contained within the project.
|
||||
@ -517,6 +497,24 @@ func (cfgCtx *ConfigContext) Config(log *log.Logger) (*devdeploy.Config, error)
|
||||
}
|
||||
log.Printf("\t\tSet Task Policy Name to '%s'.", cfg.AwsIamPolicy.PolicyName)
|
||||
|
||||
// Append all the defined services to the config.
|
||||
for _, n := range ServiceNames {
|
||||
srv, err := NewService(n, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.ProjectServices = append(cfg.ProjectServices, srv)
|
||||
}
|
||||
|
||||
// Append all the defined functions to the config.
|
||||
for _, n := range FunctionNames {
|
||||
fn, err := NewFunction(n, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.ProjectFunctions = append(cfg.ProjectFunctions, fn)
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
@ -614,3 +612,30 @@ func gitRemoteUser(projectRoot string) string {
|
||||
|
||||
return remoteUser
|
||||
}
|
||||
|
||||
|
||||
// DeployInfrastructureForTargetEnv executes the deploy commands for a target function.
|
||||
func DeployInfrastructureForTargetEnv(log *log.Logger, awsCredentials devdeploy.AwsCredentials, targetEnv Env, dryRun bool) error {
|
||||
|
||||
cfg, err := NewConfig(log, targetEnv, awsCredentials)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if dryRun {
|
||||
cfgJSON, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("DeployFunctionForTargetEnv : Marshalling config to JSON : %+v", err)
|
||||
}
|
||||
log.Printf("DeployFunctionForTargetEnv : config : %v\n", string(cfgJSON))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = devdeploy.SetupInfrastructure(log, cfg, devdeploy.SetupOptionSkipCache)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -15,57 +15,35 @@ type Function = string
|
||||
|
||||
var (
|
||||
Function_Ddlogscollector = "ddlogscollector"
|
||||
Function_YourNewFunction = "your-new-function"
|
||||
)
|
||||
|
||||
// List of function names used by main.go for help.
|
||||
var FunctionNames = []Function{
|
||||
// Python Datadog Logs Collector
|
||||
Function_Ddlogscollector,
|
||||
Function_YourNewFunction,
|
||||
}
|
||||
|
||||
// FunctionContext defines the flags for deploying a function.
|
||||
type FunctionContext struct {
|
||||
// Required flags.
|
||||
Name string `validate:"required" example:"aws-lambda-go-func"`
|
||||
AwsLambdaFunction *devdeploy.AwsLambdaFunction `validate:"required"`
|
||||
AwsIamRole *devdeploy.AwsIamRole `validate:"required"`
|
||||
AwsIamPolicy *devdeploy.AwsIamPolicy `validate:"required"`
|
||||
|
||||
// Optional flags.
|
||||
FunctionDir string `validate:"omitempty"`
|
||||
BuildDir string `validate:"omitempty"`
|
||||
DockerBuildContext string `validate:"omitempty" example:"."`
|
||||
Dockerfile string `validate:"required" example:"./cmd/web-api/Dockerfile"`
|
||||
ReleaseTag string `validate:"required"`
|
||||
EnableVPC bool `validate:"omitempty" example:"false"`
|
||||
}
|
||||
|
||||
// NewFunctionContext returns the FunctionContext.
|
||||
func NewFunctionContext(funcName string, cfg *devdeploy.Config) (*FunctionContext, error) {
|
||||
|
||||
ctx := &FunctionContext{
|
||||
Name: funcName,
|
||||
|
||||
FunctionDir: filepath.Join(cfg.ProjectRoot, "examples", funcName),
|
||||
// NewFunction returns the *devdeploy.ProjectFunction.
|
||||
func NewFunction(funcName string, cfg *devdeploy.Config) (*devdeploy.ProjectFunction, error) {
|
||||
|
||||
ctx := &devdeploy.ProjectFunction{
|
||||
Name: funcName,
|
||||
CodeDir: filepath.Join(cfg.ProjectRoot, "cmd", funcName),
|
||||
DockerBuildDir: cfg.ProjectRoot,
|
||||
DockerBuildContext: ".",
|
||||
|
||||
// Set the release tag for the image to use include env + service name + commit hash/tag.
|
||||
// Set the release tag for the image to use include env + function name + commit hash/tag.
|
||||
ReleaseTag: devdeploy.GitLabCiReleaseTag(cfg.Env, funcName),
|
||||
}
|
||||
|
||||
switch funcName {
|
||||
case Function_YourNewFunction:
|
||||
// No additional settings for function.
|
||||
case Function_Ddlogscollector:
|
||||
|
||||
// Python Datadog Logs Collector is
|
||||
ctx.FunctionDir = filepath.Join(cfg.ProjectRoot, "deployments/ddlogscollector")
|
||||
ctx.CodeDir = filepath.Join(cfg.ProjectRoot, "deployments", funcName)
|
||||
|
||||
// Change the build directory to the function directory instead of project root.
|
||||
ctx.BuildDir = ctx.FunctionDir
|
||||
ctx.DockerBuildDir = ctx.CodeDir
|
||||
|
||||
// AwsLambdaFunction defines the details needed to create an lambda function.
|
||||
ctx.AwsLambdaFunction = &devdeploy.AwsLambdaFunction{
|
||||
@ -215,105 +193,54 @@ func NewFunctionContext(funcName string, cfg *devdeploy.Config) (*FunctionContex
|
||||
return nil
|
||||
}
|
||||
|
||||
ctx.CodeS3Bucket = cfg.AwsS3BucketPrivate.BucketName
|
||||
ctx.CodeS3Key = filepath.Join("src", "aws", "lambda", cfg.Env, ctx.Name, ctx.ReleaseTag+".zip")
|
||||
|
||||
// Set the docker file if no custom one has been defined for the service.
|
||||
if ctx.Dockerfile == "" {
|
||||
ctx.Dockerfile = filepath.Join(ctx.BuildDir, "Dockerfile")
|
||||
ctx.Dockerfile = filepath.Join(ctx.CodeDir, "Dockerfile")
|
||||
}
|
||||
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// Build handles defining all the information needed to deploy a service to AWS ECS.
|
||||
func (ctx *FunctionContext) Build(log *log.Logger, noCache, noPush bool) (*devdeploy.BuildLambda, error) {
|
||||
|
||||
log.Printf("Define build for function '%s'.", ctx.Name)
|
||||
log.Printf("\tUsing release tag %s.", ctx.ReleaseTag)
|
||||
|
||||
srv := &devdeploy.BuildLambda{
|
||||
FuncName: ctx.Name,
|
||||
ReleaseTag: ctx.ReleaseTag,
|
||||
BuildDir: ctx.BuildDir,
|
||||
Dockerfile: ctx.Dockerfile,
|
||||
DockerBuildContext: ctx.DockerBuildContext,
|
||||
NoCache: noCache,
|
||||
NoPush: noPush,
|
||||
}
|
||||
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
// Deploy handles defining all the information needed to deploy a service to AWS ECS.
|
||||
func (ctx *FunctionContext) Deploy(log *log.Logger) (*devdeploy.DeployLambda, error) {
|
||||
|
||||
log.Printf("Define build for function '%s'.", ctx.Name)
|
||||
log.Printf("\tUsing release tag %s.", ctx.ReleaseTag)
|
||||
|
||||
srv := &devdeploy.DeployLambda{
|
||||
FuncName: ctx.Name,
|
||||
EnableVPC: ctx.EnableVPC,
|
||||
AwsLambdaFunction: ctx.AwsLambdaFunction,
|
||||
AwsIamPolicy: ctx.AwsIamPolicy,
|
||||
AwsIamRole: ctx.AwsIamRole,
|
||||
}
|
||||
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
// S3Location returns the s3 bucket and key used to upload the code to.
|
||||
func (ctx *FunctionContext) S3Location(cfg *devdeploy.Config) (string, string) {
|
||||
s3Bucket := cfg.AwsS3BucketPrivate.BucketName
|
||||
s3Key := filepath.Join("src", "aws", "lambda", cfg.Env, ctx.Name, ctx.ReleaseTag+".zip")
|
||||
|
||||
return s3Bucket, s3Key
|
||||
}
|
||||
|
||||
// BuildFunctionForTargetEnv executes the build commands for a target function.
|
||||
func BuildFunctionForTargetEnv(log *log.Logger, awsCredentials devdeploy.AwsCredentials, targetEnv Env, functionName, releaseTag string, dryRun, noCache, noPush bool) error {
|
||||
|
||||
cfgCtx, err := NewConfigContext(targetEnv, awsCredentials)
|
||||
cfg, err := NewConfig(log, targetEnv, awsCredentials)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg, err := cfgCtx.Config(log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
funcCtx, err := NewFunctionContext(functionName, cfg)
|
||||
targetFunc, err := NewFunction(functionName, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Override the release tag if set.
|
||||
if releaseTag != "" {
|
||||
funcCtx.ReleaseTag = releaseTag
|
||||
targetFunc.ReleaseTag = releaseTag
|
||||
}
|
||||
|
||||
details, err := funcCtx.Build(log, noCache, noPush)
|
||||
if err != nil {
|
||||
return err
|
||||
// Append build args to be used for all functions.
|
||||
if targetFunc.DockerBuildArgs == nil {
|
||||
targetFunc.DockerBuildArgs = make(map[string]string)
|
||||
}
|
||||
|
||||
// Set the s3 bucket and s3 for uploading the zip file.
|
||||
details.CodeS3Bucket, details.CodeS3Key = funcCtx.S3Location(cfg)
|
||||
|
||||
// funcPath is used to copy the service specific code in the Dockerfile.
|
||||
funcPath, err := filepath.Rel(cfg.ProjectRoot, funcCtx.FunctionDir)
|
||||
codePath, err := filepath.Rel(cfg.ProjectRoot, targetFunc.CodeDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetFunc.DockerBuildArgs["code_path"] = codePath
|
||||
|
||||
// commitRef is used by main.go:build constant.
|
||||
commitRef := getCommitRef()
|
||||
if commitRef == "" {
|
||||
commitRef = funcCtx.ReleaseTag
|
||||
}
|
||||
|
||||
details.BuildArgs = map[string]string{
|
||||
"func_path": funcPath,
|
||||
"commit_ref": commitRef,
|
||||
commitRef = targetFunc.ReleaseTag
|
||||
}
|
||||
targetFunc.DockerBuildArgs["commit_ref"] = commitRef
|
||||
|
||||
if dryRun {
|
||||
cfgJSON, err := json.MarshalIndent(cfg, "", " ")
|
||||
@ -322,7 +249,7 @@ func BuildFunctionForTargetEnv(log *log.Logger, awsCredentials devdeploy.AwsCred
|
||||
}
|
||||
log.Printf("BuildFunctionForTargetEnv : config : %v\n", string(cfgJSON))
|
||||
|
||||
detailsJSON, err := json.MarshalIndent(details, "", " ")
|
||||
detailsJSON, err := json.MarshalIndent(targetFunc, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("BuildFunctionForTargetEnv : Marshalling details to JSON : %+v", err)
|
||||
}
|
||||
@ -331,40 +258,27 @@ func BuildFunctionForTargetEnv(log *log.Logger, awsCredentials devdeploy.AwsCred
|
||||
return nil
|
||||
}
|
||||
|
||||
return devdeploy.BuildLambdaForTargetEnv(log, cfg, details)
|
||||
return devdeploy.BuildLambdaForTargetEnv(log, cfg, targetFunc, noCache, noPush)
|
||||
}
|
||||
|
||||
// DeployFunctionForTargetEnv executes the deploy commands for a target function.
|
||||
func DeployFunctionForTargetEnv(log *log.Logger, awsCredentials devdeploy.AwsCredentials, targetEnv Env, functionName, releaseTag string, dryRun bool) error {
|
||||
|
||||
cfgCtx, err := NewConfigContext(targetEnv, awsCredentials)
|
||||
cfg, err := NewConfig(log, targetEnv, awsCredentials)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg, err := cfgCtx.Config(log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
funcCtx, err := NewFunctionContext(functionName, cfg)
|
||||
targetFunc, err := NewFunction(functionName, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Override the release tag if set.
|
||||
if releaseTag != "" {
|
||||
funcCtx.ReleaseTag = releaseTag
|
||||
targetFunc.ReleaseTag = releaseTag
|
||||
}
|
||||
|
||||
details, err := funcCtx.Deploy(log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the s3 bucket and s3 for uploading the zip file.
|
||||
details.CodeS3Bucket, details.CodeS3Key = funcCtx.S3Location(cfg)
|
||||
|
||||
if dryRun {
|
||||
cfgJSON, err := json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
@ -372,7 +286,7 @@ func DeployFunctionForTargetEnv(log *log.Logger, awsCredentials devdeploy.AwsCre
|
||||
}
|
||||
log.Printf("DeployFunctionForTargetEnv : config : %v\n", string(cfgJSON))
|
||||
|
||||
detailsJSON, err := json.MarshalIndent(details, "", " ")
|
||||
detailsJSON, err := json.MarshalIndent(targetFunc, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("DeployFunctionForTargetEnv : Marshalling details to JSON : %+v", err)
|
||||
}
|
||||
@ -381,5 +295,5 @@ func DeployFunctionForTargetEnv(log *log.Logger, awsCredentials devdeploy.AwsCre
|
||||
return nil
|
||||
}
|
||||
|
||||
return devdeploy.DeployLambdaToTargetEnv(log, cfg, details)
|
||||
return devdeploy.DeployLambdaToTargetEnv(log, cfg, targetFunc)
|
||||
}
|
||||
|
@ -13,22 +13,22 @@ import (
|
||||
// RunSchemaMigrationsForTargetEnv executes schema migrations for the target environment.
|
||||
func RunSchemaMigrationsForTargetEnv(log *log.Logger, awsCredentials devdeploy.AwsCredentials, targetEnv Env, isUnittest bool) error {
|
||||
|
||||
cfgCtx, err := NewConfigContext(targetEnv, awsCredentials)
|
||||
cfg, err := NewConfig(log, targetEnv, awsCredentials)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg, err := cfgCtx.Config(log)
|
||||
infra, err := devdeploy.SetupInfrastructure(log, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = devdeploy.SetupDeploymentEnv(log, cfg)
|
||||
connInfo, err := cfg.GetDBConnInfo(infra)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
masterDb, err := sqlx.Open(cfg.DBConnInfo.Driver, cfg.DBConnInfo.URL())
|
||||
masterDb, err := sqlx.Open(connInfo.Driver, connInfo.URL())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to connect to db for schema migration.")
|
||||
}
|
||||
|
@ -30,8 +30,8 @@ var ServiceNames = []Service{
|
||||
ServiceWebApp,
|
||||
}
|
||||
|
||||
// ServiceConfig defines the settings for a service.
|
||||
type ServiceConfig struct {
|
||||
// ServiceContext defines the settings for a service.
|
||||
type ServiceContext struct {
|
||||
// Required flags.
|
||||
Name string `validate:"required" example:"web-api"`
|
||||
ServiceHostPrimary string `validate:"required" example:"example-project.com"`
|
||||
@ -45,24 +45,16 @@ type ServiceConfig struct {
|
||||
EnableHTTPS bool `validate:"omitempty" example:"false"`
|
||||
EnableElb bool `validate:"omitempty" example:"false"`
|
||||
StaticFilesS3Enable bool `validate:"omitempty" example:"false"`
|
||||
BuildDir string `validate:"omitempty"`
|
||||
DockerBuildDir string `validate:"omitempty"`
|
||||
DockerBuildContext string `validate:"omitempty" example:"."`
|
||||
}
|
||||
|
||||
// ServiceContext includes the config and task definition for building and deploying a service.
|
||||
type ServiceContext struct {
|
||||
ServiceConfig
|
||||
|
||||
// AwsEcsTaskDefinition defines the ECS task definition based on the service configs.
|
||||
AwsEcsTaskDefinition func(cfg *devdeploy.Config, srv *devdeploy.DeployService) (*ecs.RegisterTaskDefinitionInput, error)
|
||||
}
|
||||
|
||||
// NewServiceConfig returns the Service for a service that is configured for the target deployment env.
|
||||
func NewServiceConfig(serviceName string, cfg *devdeploy.Config) (ServiceConfig, error) {
|
||||
// NewServiceContext returns the Service for a service that is configured for the target deployment env.
|
||||
func NewServiceContext(serviceName string, cfg *devdeploy.Config) (ServiceContext, error) {
|
||||
|
||||
// =========================================================================
|
||||
// New service context.
|
||||
srv := ServiceConfig{
|
||||
srv := ServiceContext{
|
||||
Name: serviceName,
|
||||
DesiredCount: 1,
|
||||
DockerBuildContext: ".",
|
||||
@ -110,7 +102,7 @@ func NewServiceConfig(serviceName string, cfg *devdeploy.Config) (ServiceConfig,
|
||||
}
|
||||
|
||||
default:
|
||||
return ServiceConfig{}, errors.Wrapf(devdeploy.ErrInvalidService,
|
||||
return ServiceContext{}, errors.Wrapf(devdeploy.ErrInvalidService,
|
||||
"No service config defined for service '%s'",
|
||||
serviceName)
|
||||
}
|
||||
@ -130,7 +122,7 @@ func NewServiceConfig(serviceName string, cfg *devdeploy.Config) (ServiceConfig,
|
||||
}
|
||||
|
||||
// BaseUrl returns the base url for a specific service.
|
||||
func (c ServiceConfig) BaseUrl() string {
|
||||
func (c ServiceContext) BaseUrl() string {
|
||||
var schema string
|
||||
if c.EnableHTTPS {
|
||||
schema = "https"
|
||||
@ -140,349 +132,40 @@ func (c ServiceConfig) BaseUrl() string {
|
||||
return fmt.Sprintf("%s://%s/", schema, c.ServiceHostPrimary)
|
||||
}
|
||||
|
||||
// NewServiceContext returns the ServiceContext for a service that is configured for the target deployment env.
|
||||
func NewServiceContext(serviceName Service, cfg *devdeploy.Config) (*ServiceContext, error) {
|
||||
// NewService returns the ProjectService for a service that is configured for the target deployment env.
|
||||
func NewService(serviceName string, cfg *devdeploy.Config) (*devdeploy.ProjectService, error) {
|
||||
|
||||
ctx, err := NewServiceContext(serviceName, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// New project service.
|
||||
srv := &devdeploy.ProjectService{
|
||||
Name: serviceName,
|
||||
CodeDir: filepath.Join(cfg.ProjectRoot, "cmd", serviceName),
|
||||
DockerBuildDir: ctx.DockerBuildDir,
|
||||
DockerBuildContext: ".",
|
||||
EnableHTTPS: ctx.EnableHTTPS,
|
||||
|
||||
ServiceHostPrimary: ctx.ServiceHostPrimary,
|
||||
ServiceHostNames: ctx.ServiceHostNames,
|
||||
ReleaseTag: ctx.ReleaseTag,
|
||||
}
|
||||
|
||||
if srv.DockerBuildDir == "" {
|
||||
srv.DockerBuildDir = cfg.ProjectRoot
|
||||
}
|
||||
|
||||
// Sync static files to S3 will be enabled when the S3 prefix is defined.
|
||||
if ctx.StaticFilesS3Enable {
|
||||
srv.StaticFilesS3Prefix = filepath.Join(cfg.AwsS3BucketPublicKeyPrefix, ctx.ReleaseTag, "static")
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Shared details that could be applied to all task definitions.
|
||||
|
||||
// Load the web-app config for the web-api can reference it's hostname.
|
||||
webAppCfg, err := NewServiceConfig(ServiceWebApp, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Load the web-api config for the web-app can reference it's hostname.
|
||||
webApiCfg, err := NewServiceConfig(ServiceWebApi, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Define a base set of environment variables that can be assigned to individual container definitions.
|
||||
baseEnvVals := func(cfg *devdeploy.Config, srv *devdeploy.DeployService) []*ecs.KeyValuePair {
|
||||
|
||||
var ciJobURL string
|
||||
if id := os.Getenv("CI_JOB_ID"); id != "" {
|
||||
ciJobURL = strings.TrimRight(GitLabProjectBaseUrl, "/") + "/-/jobs/" + os.Getenv("CI_JOB_ID")
|
||||
}
|
||||
|
||||
var ciPipelineURL string
|
||||
if id := os.Getenv("CI_PIPELINE_ID"); id != "" {
|
||||
ciPipelineURL = strings.TrimRight(GitLabProjectBaseUrl, "/") + "/pipelines/" + os.Getenv("CI_PIPELINE_ID")
|
||||
}
|
||||
|
||||
return []*ecs.KeyValuePair{
|
||||
ecsKeyValuePair(devdeploy.ENV_KEY_ECS_CLUSTER, srv.AwsEcsCluster.ClusterName),
|
||||
ecsKeyValuePair(devdeploy.ENV_KEY_ECS_SERVICE, srv.AwsEcsService.ServiceName),
|
||||
ecsKeyValuePair("AWS_DEFAULT_REGION", cfg.AwsCredentials.Region),
|
||||
ecsKeyValuePair("AWS_USE_ROLE", "true"),
|
||||
ecsKeyValuePair("AWSLOGS_GROUP", srv.AwsCloudWatchLogGroup.LogGroupName),
|
||||
ecsKeyValuePair("ECS_ENABLE_CONTAINER_METADATA", "true"),
|
||||
ecsKeyValuePair("CI_COMMIT_REF_NAME", os.Getenv("CI_COMMIT_REF_NAME")),
|
||||
ecsKeyValuePair("CI_COMMIT_SHORT_SHA", os.Getenv("CI_COMMIT_SHORT_SHA")),
|
||||
ecsKeyValuePair("CI_COMMIT_SHA", os.Getenv("CI_COMMIT_SHA")),
|
||||
ecsKeyValuePair("CI_COMMIT_TAG", os.Getenv("CI_COMMIT_TAG")),
|
||||
ecsKeyValuePair("CI_JOB_ID", os.Getenv("CI_JOB_ID")),
|
||||
ecsKeyValuePair("CI_PIPELINE_ID", os.Getenv("CI_PIPELINE_ID")),
|
||||
ecsKeyValuePair("CI_JOB_URL", ciJobURL),
|
||||
ecsKeyValuePair("CI_PIPELINE_URL", ciPipelineURL),
|
||||
ecsKeyValuePair("WEB_APP_BASE_URL", webAppCfg.BaseUrl()),
|
||||
ecsKeyValuePair("WEB_API_BASE_URL", webApiCfg.BaseUrl()),
|
||||
ecsKeyValuePair("EMAIL_SENDER", "lee+saas-starter-kit@geeksinthewoods.com"),
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Service dependant settings.
|
||||
|
||||
var ctx *ServiceContext
|
||||
switch serviceName {
|
||||
|
||||
// Define the ServiceContext for the web-app that will be used for build and deploy.
|
||||
case ServiceWebApp:
|
||||
|
||||
ctx = &ServiceContext{
|
||||
ServiceConfig: webAppCfg,
|
||||
}
|
||||
|
||||
// Define the service task definition with a function to enable use of config and deploy details.
|
||||
ctx.AwsEcsTaskDefinition = func(cfg *devdeploy.Config, srv *devdeploy.DeployService) (*ecs.RegisterTaskDefinitionInput, error) {
|
||||
|
||||
// Defined a container definition for the specific service.
|
||||
container1 := &ecs.ContainerDefinition{
|
||||
Name: aws.String(ctx.Name),
|
||||
Image: aws.String(srv.ReleaseImage),
|
||||
Essential: aws.Bool(true),
|
||||
LogConfiguration: &ecs.LogConfiguration{
|
||||
LogDriver: aws.String("awslogs"),
|
||||
Options: map[string]*string{
|
||||
"awslogs-group": aws.String(srv.AwsCloudWatchLogGroup.LogGroupName),
|
||||
"awslogs-region": aws.String(cfg.AwsCredentials.Region),
|
||||
"awslogs-stream-prefix": aws.String("ecs"),
|
||||
},
|
||||
},
|
||||
PortMappings: []*ecs.PortMapping{
|
||||
&ecs.PortMapping{
|
||||
HostPort: aws.Int64(80),
|
||||
Protocol: aws.String("tcp"),
|
||||
ContainerPort: aws.Int64(80),
|
||||
},
|
||||
},
|
||||
Cpu: aws.Int64(128),
|
||||
MemoryReservation: aws.Int64(128),
|
||||
Environment: baseEnvVals(cfg, srv),
|
||||
HealthCheck: &ecs.HealthCheck{
|
||||
Retries: aws.Int64(3),
|
||||
Command: aws.StringSlice([]string{
|
||||
"CMD-SHELL",
|
||||
"curl -f http://localhost/ping || exit 1",
|
||||
}),
|
||||
Timeout: aws.Int64(5),
|
||||
Interval: aws.Int64(60),
|
||||
StartPeriod: aws.Int64(60),
|
||||
},
|
||||
Ulimits: []*ecs.Ulimit{
|
||||
&ecs.Ulimit{
|
||||
Name: aws.String("nofile"),
|
||||
SoftLimit: aws.Int64(987654),
|
||||
HardLimit: aws.Int64(999999),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// If the service has HTTPS enabled with the use of an AWS Elastic Load Balancer, then need to enable
|
||||
// traffic for port 443 for SSL traffic to get terminated on the deployed tasks.
|
||||
if ctx.EnableHTTPS && !ctx.EnableElb {
|
||||
container1.PortMappings = append(container1.PortMappings, &ecs.PortMapping{
|
||||
HostPort: aws.Int64(443),
|
||||
Protocol: aws.String("tcp"),
|
||||
ContainerPort: aws.Int64(443),
|
||||
})
|
||||
}
|
||||
|
||||
// Append env vars for the service task.
|
||||
container1.Environment = append(container1.Environment,
|
||||
ecsKeyValuePair("SERVICE_NAME", srv.ServiceName),
|
||||
ecsKeyValuePair("PROJECT_NAME", cfg.ProjectName),
|
||||
|
||||
// Use placeholders for these environment variables that will be replaced with devdeploy.DeployServiceToTargetEnv
|
||||
ecsKeyValuePair("WEB_APP_HOST_HOST", "{HTTP_HOST}"),
|
||||
ecsKeyValuePair("WEB_APP_HTTPS_HOST", "{HTTPS_HOST}"),
|
||||
ecsKeyValuePair("WEB_APP_SERVICE_ENABLE_HTTPS", "{HTTPS_ENABLED}"),
|
||||
ecsKeyValuePair("WEB_APP_SERVICE_BASE_URL", "{APP_BASE_URL}"),
|
||||
ecsKeyValuePair("WEB_APP_SERVICE_HOST_NAMES", "{HOST_NAMES}"),
|
||||
ecsKeyValuePair("WEB_APP_SERVICE_STATICFILES_S3_ENABLED", "{STATIC_FILES_S3_ENABLED}"),
|
||||
ecsKeyValuePair("WEB_APP_SERVICE_STATICFILES_S3_PREFIX", "{STATIC_FILES_S3_PREFIX}"),
|
||||
ecsKeyValuePair("WEB_APP_SERVICE_STATICFILES_CLOUDFRONT_ENABLED", "{STATIC_FILES_CLOUDFRONT_ENABLED}"),
|
||||
ecsKeyValuePair("WEB_APP_REDIS_HOST", "{CACHE_HOST}"),
|
||||
ecsKeyValuePair("WEB_APP_DB_HOST", "{DB_HOST}"),
|
||||
ecsKeyValuePair("WEB_APP_DB_USERNAME", "{DB_USER}"),
|
||||
ecsKeyValuePair("WEB_APP_DB_PASSWORD", "{DB_PASS}"),
|
||||
ecsKeyValuePair("WEB_APP_DB_DATABASE", "{DB_DATABASE}"),
|
||||
ecsKeyValuePair("WEB_APP_DB_DRIVER", "{DB_DRIVER}"),
|
||||
ecsKeyValuePair("WEB_APP_DB_DISABLE_TLS", "{DB_DISABLE_TLS}"),
|
||||
ecsKeyValuePair("WEB_APP_AWS_S3_BUCKET_PRIVATE", "{AWS_S3_BUCKET_PRIVATE}"),
|
||||
ecsKeyValuePair("WEB_APP_AWS_S3_BUCKET_PUBLIC", "{AWS_S3_BUCKET_PUBLIC}"),
|
||||
ecsKeyValuePair(devdeploy.ENV_KEY_ROUTE53_UPDATE_TASK_IPS, "{ROUTE53_UPDATE_TASK_IPS}"),
|
||||
ecsKeyValuePair(devdeploy.ENV_KEY_ROUTE53_ZONES, "{ROUTE53_ZONES}"),
|
||||
)
|
||||
|
||||
// Define the full task definition for the service.
|
||||
def := &ecs.RegisterTaskDefinitionInput{
|
||||
Family: aws.String(srv.ServiceName),
|
||||
ExecutionRoleArn: aws.String(srv.AwsEcsExecutionRole.Arn()),
|
||||
TaskRoleArn: aws.String(srv.AwsEcsTaskRole.Arn()),
|
||||
NetworkMode: aws.String("awsvpc"),
|
||||
ContainerDefinitions: []*ecs.ContainerDefinition{
|
||||
// Include the single container definition for the service. Additional definitions could be added
|
||||
// here like one for datadog.
|
||||
container1,
|
||||
},
|
||||
RequiresCompatibilities: aws.StringSlice([]string{"FARGATE"}),
|
||||
}
|
||||
|
||||
return def, nil
|
||||
}
|
||||
|
||||
// Define the ServiceContext for the web-api that will be used for build and deploy.
|
||||
case ServiceWebApi:
|
||||
|
||||
ctx = &ServiceContext{
|
||||
ServiceConfig: webApiCfg,
|
||||
}
|
||||
|
||||
// Define the service task definition with a function to enable use of config and deploy details.
|
||||
ctx.AwsEcsTaskDefinition = func(cfg *devdeploy.Config, srv *devdeploy.DeployService) (*ecs.RegisterTaskDefinitionInput, error) {
|
||||
|
||||
// Defined a container definition for the specific service.
|
||||
container1 := &ecs.ContainerDefinition{
|
||||
Name: aws.String(ctx.Name),
|
||||
Image: aws.String(srv.ReleaseImage),
|
||||
Essential: aws.Bool(true),
|
||||
LogConfiguration: &ecs.LogConfiguration{
|
||||
LogDriver: aws.String("awslogs"),
|
||||
Options: map[string]*string{
|
||||
"awslogs-group": aws.String(srv.AwsCloudWatchLogGroup.LogGroupName),
|
||||
"awslogs-region": aws.String(cfg.AwsCredentials.Region),
|
||||
"awslogs-stream-prefix": aws.String("ecs"),
|
||||
},
|
||||
},
|
||||
PortMappings: []*ecs.PortMapping{
|
||||
&ecs.PortMapping{
|
||||
HostPort: aws.Int64(80),
|
||||
Protocol: aws.String("tcp"),
|
||||
ContainerPort: aws.Int64(80),
|
||||
},
|
||||
},
|
||||
Cpu: aws.Int64(128),
|
||||
MemoryReservation: aws.Int64(128),
|
||||
Environment: baseEnvVals(cfg, srv),
|
||||
HealthCheck: &ecs.HealthCheck{
|
||||
Retries: aws.Int64(3),
|
||||
Command: aws.StringSlice([]string{
|
||||
"CMD-SHELL",
|
||||
"curl -f http://localhost/ping || exit 1",
|
||||
}),
|
||||
Timeout: aws.Int64(5),
|
||||
Interval: aws.Int64(60),
|
||||
StartPeriod: aws.Int64(60),
|
||||
},
|
||||
Ulimits: []*ecs.Ulimit{
|
||||
&ecs.Ulimit{
|
||||
Name: aws.String("nofile"),
|
||||
SoftLimit: aws.Int64(987654),
|
||||
HardLimit: aws.Int64(999999),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// If the service has HTTPS enabled with the use of an AWS Elastic Load Balancer, then need to enable
|
||||
// traffic for port 443 for SSL traffic to get terminated on the deployed tasks.
|
||||
if ctx.EnableHTTPS && !ctx.EnableElb {
|
||||
container1.PortMappings = append(container1.PortMappings, &ecs.PortMapping{
|
||||
HostPort: aws.Int64(443),
|
||||
Protocol: aws.String("tcp"),
|
||||
ContainerPort: aws.Int64(443),
|
||||
})
|
||||
}
|
||||
|
||||
// Append env vars for the service task.
|
||||
container1.Environment = append(container1.Environment,
|
||||
ecsKeyValuePair("SERVICE_NAME", srv.ServiceName),
|
||||
ecsKeyValuePair("PROJECT_NAME", cfg.ProjectName),
|
||||
|
||||
// Use placeholders for these environment variables that will be replaced with devdeploy.DeployServiceToTargetEnv
|
||||
ecsKeyValuePair("WEB_API_HTTP_HOST", "{HTTP_HOST}"),
|
||||
ecsKeyValuePair("WEB_API_HTTPS_HOST", "{HTTPS_HOST}"),
|
||||
ecsKeyValuePair("WEB_API_SERVICE_ENABLE_HTTPS", "{HTTPS_ENABLED}"),
|
||||
ecsKeyValuePair("WEB_API_SERVICE_BASE_URL", "{APP_BASE_URL}"),
|
||||
ecsKeyValuePair("WEB_API_SERVICE_HOST_NAMES", "{HOST_NAMES}"),
|
||||
ecsKeyValuePair("WEB_API_SERVICE_STATICFILES_S3_ENABLED", "{STATIC_FILES_S3_ENABLED}"),
|
||||
ecsKeyValuePair("WEB_API_SERVICE_STATICFILES_S3_PREFIX", "{STATIC_FILES_S3_PREFIX}"),
|
||||
ecsKeyValuePair("WEB_API_SERVICE_STATICFILES_CLOUDFRONT_ENABLED", "{STATIC_FILES_CLOUDFRONT_ENABLED}"),
|
||||
ecsKeyValuePair("WEB_API_REDIS_HOST", "{CACHE_HOST}"),
|
||||
ecsKeyValuePair("WEB_API_DB_HOST", "{DB_HOST}"),
|
||||
ecsKeyValuePair("WEB_API_DB_USERNAME", "{DB_USER}"),
|
||||
ecsKeyValuePair("WEB_API_DB_PASSWORD", "{DB_PASS}"),
|
||||
ecsKeyValuePair("WEB_API_DB_DATABASE", "{DB_DATABASE}"),
|
||||
ecsKeyValuePair("WEB_API_DB_DRIVER", "{DB_DRIVER}"),
|
||||
ecsKeyValuePair("WEB_API_DB_DISABLE_TLS", "{DB_DISABLE_TLS}"),
|
||||
ecsKeyValuePair("WEB_API_AWS_S3_BUCKET_PRIVATE", "{AWS_S3_BUCKET_PRIVATE}"),
|
||||
ecsKeyValuePair("WEB_API_AWS_S3_BUCKET_PUBLIC", "{AWS_S3_BUCKET_PUBLIC}"),
|
||||
ecsKeyValuePair(devdeploy.ENV_KEY_ROUTE53_UPDATE_TASK_IPS, "{ROUTE53_UPDATE_TASK_IPS}"),
|
||||
ecsKeyValuePair(devdeploy.ENV_KEY_ROUTE53_ZONES, "{ROUTE53_ZONES}"),
|
||||
)
|
||||
|
||||
// Define the full task definition for the service.
|
||||
def := &ecs.RegisterTaskDefinitionInput{
|
||||
Family: aws.String(srv.ServiceName),
|
||||
ExecutionRoleArn: aws.String(srv.AwsEcsExecutionRole.Arn()),
|
||||
TaskRoleArn: aws.String(srv.AwsEcsTaskRole.Arn()),
|
||||
NetworkMode: aws.String("awsvpc"),
|
||||
ContainerDefinitions: []*ecs.ContainerDefinition{
|
||||
// Include the single container definition for the service. Additional definitions could be added
|
||||
// here like one for datadog.
|
||||
container1,
|
||||
},
|
||||
RequiresCompatibilities: aws.StringSlice([]string{"FARGATE"}),
|
||||
}
|
||||
|
||||
return def, nil
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, errors.Wrapf(devdeploy.ErrInvalidService,
|
||||
"No service context defined for service '%s'",
|
||||
serviceName)
|
||||
}
|
||||
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// BuildService handles defining all the information needed to a service with docker and push to AWS ECR.
|
||||
func (ctx *ServiceContext) Build(log *log.Logger, noCache, noPush bool) (*devdeploy.BuildService, error) {
|
||||
|
||||
log.Printf("Define build for service '%s'.", ctx.Name)
|
||||
log.Printf("\tUsing release tag %s.", ctx.ReleaseTag)
|
||||
|
||||
srv := &devdeploy.BuildService{
|
||||
ServiceName: ctx.Name,
|
||||
ReleaseTag: ctx.ReleaseTag,
|
||||
BuildDir: ctx.BuildDir,
|
||||
Dockerfile: ctx.Dockerfile,
|
||||
DockerBuildContext: ctx.DockerBuildContext,
|
||||
NoCache: noCache,
|
||||
NoPush: noPush,
|
||||
}
|
||||
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
// DeployService handles defining all the information needed to deploy a service to AWS ECS.
|
||||
func (ctx *ServiceContext) Deploy(log *log.Logger, cfg *devdeploy.Config) (*devdeploy.DeployService, error) {
|
||||
|
||||
log.Printf("Define deploy for service '%s'.", ctx.Name)
|
||||
log.Printf("\tUsing release tag %s.", ctx.ReleaseTag)
|
||||
|
||||
// Start to define all the information for the service from the service context.
|
||||
srv := &devdeploy.DeployService{
|
||||
ServiceName: ctx.Name,
|
||||
ReleaseTag: ctx.ReleaseTag,
|
||||
EnableHTTPS: ctx.EnableHTTPS,
|
||||
ServiceHostPrimary: ctx.ServiceHostPrimary,
|
||||
ServiceHostNames: ctx.ServiceHostNames,
|
||||
}
|
||||
|
||||
// When only service host names are set, choose the first item as the primary host.
|
||||
if srv.ServiceHostPrimary == "" && len(srv.ServiceHostNames) > 0 {
|
||||
srv.ServiceHostPrimary = srv.ServiceHostNames[0]
|
||||
log.Printf("\t\tSet Service Primary Host to '%s'.", srv.ServiceHostPrimary)
|
||||
}
|
||||
|
||||
// The S3 prefix used to upload static files served to public.
|
||||
if ctx.StaticFilesS3Enable {
|
||||
srv.StaticFilesS3Prefix = filepath.Join(cfg.AwsS3BucketPublicKeyPrefix, srv.ReleaseTag, "static")
|
||||
}
|
||||
|
||||
// Determine the Dockerfile for the service.
|
||||
if ctx.Dockerfile != "" {
|
||||
srv.Dockerfile = ctx.Dockerfile
|
||||
log.Printf("\t\tUsing docker file '%s'.", srv.Dockerfile)
|
||||
} else {
|
||||
var err error
|
||||
srv.Dockerfile, err = devdeploy.FindServiceDockerFile(cfg.ProjectRoot, srv.ServiceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("\t\tFound service docker file '%s'.", srv.Dockerfile)
|
||||
}
|
||||
|
||||
// Set the service directory.
|
||||
if ctx.ServiceDir == "" {
|
||||
ctx.ServiceDir = filepath.Dir(srv.Dockerfile)
|
||||
}
|
||||
srv.StaticFilesDir = filepath.Join(ctx.ServiceDir, "static")
|
||||
|
||||
// Define the ECS Cluster used to host the serverless fargate tasks.
|
||||
srv.AwsEcsCluster = &devdeploy.AwsEcsCluster{
|
||||
ClusterName: cfg.ProjectName + "-" + cfg.Env,
|
||||
@ -520,7 +203,7 @@ func (ctx *ServiceContext) Deploy(log *log.Logger, cfg *devdeploy.Config) (*devd
|
||||
|
||||
// AwsCloudWatchLogGroup defines the name of the cloudwatch log group that will be used to store logs for the ECS tasks.
|
||||
srv.AwsCloudWatchLogGroup = &devdeploy.AwsCloudWatchLogGroup{
|
||||
LogGroupName: fmt.Sprintf("logs/env_%s/aws/ecs/cluster_%s/service_%s", cfg.Env, srv.AwsEcsCluster.ClusterName, srv.ServiceName),
|
||||
LogGroupName: fmt.Sprintf("logs/env_%s/aws/ecs/cluster_%s/service_%s", cfg.Env, srv.AwsEcsCluster.ClusterName, srv.Name),
|
||||
Tags: []devdeploy.Tag{
|
||||
{Key: devdeploy.AwsTagNameProject, Value: cfg.ProjectName},
|
||||
{Key: devdeploy.AwsTagNameEnv, Value: cfg.Env},
|
||||
@ -545,7 +228,7 @@ func (ctx *ServiceContext) Deploy(log *log.Logger, cfg *devdeploy.Config) (*devd
|
||||
if ctx.EnableElb {
|
||||
// AwsElbLoadBalancer defines if the service should use an elastic load balancer.
|
||||
srv.AwsElbLoadBalancer = &devdeploy.AwsElbLoadBalancer{
|
||||
Name: fmt.Sprintf("%s-%s-%s", cfg.Env, srv.AwsEcsCluster.ClusterName, srv.ServiceName),
|
||||
Name: fmt.Sprintf("%s-%s-%s", cfg.Env, srv.AwsEcsCluster.ClusterName, ctx.Name),
|
||||
IpAddressType: "ipv4",
|
||||
Scheme: "internet-facing",
|
||||
Type: "application",
|
||||
@ -557,23 +240,25 @@ func (ctx *ServiceContext) Deploy(log *log.Logger, cfg *devdeploy.Config) (*devd
|
||||
log.Printf("\t\tSet ELB Name to '%s'.", srv.AwsElbLoadBalancer.Name)
|
||||
|
||||
// Define the target group for service to receive HTTP traffic from the load balancer.
|
||||
srv.AwsElbLoadBalancer.TargetGroup = &devdeploy.AwsElbTargetGroup{
|
||||
Name: fmt.Sprintf("%s-http", srv.ServiceName),
|
||||
Port: 80,
|
||||
Protocol: "HTTP",
|
||||
TargetType: "ip",
|
||||
HealthCheckEnabled: true,
|
||||
HealthCheckIntervalSeconds: 30,
|
||||
HealthCheckPath: "/ping",
|
||||
HealthCheckProtocol: "HTTP",
|
||||
HealthCheckTimeoutSeconds: 5,
|
||||
HealthyThresholdCount: 3,
|
||||
UnhealthyThresholdCount: 3,
|
||||
Matcher: "200",
|
||||
srv.AwsElbLoadBalancer.TargetGroups = []*devdeploy.AwsElbTargetGroup{
|
||||
&devdeploy.AwsElbTargetGroup{
|
||||
Name: fmt.Sprintf("%s-http", ctx.Name),
|
||||
Port: 80,
|
||||
Protocol: "HTTP",
|
||||
TargetType: "ip",
|
||||
HealthCheckEnabled: true,
|
||||
HealthCheckIntervalSeconds: 30,
|
||||
HealthCheckPath: "/ping",
|
||||
HealthCheckProtocol: "HTTP",
|
||||
HealthCheckTimeoutSeconds: 5,
|
||||
HealthyThresholdCount: 3,
|
||||
UnhealthyThresholdCount: 3,
|
||||
Matcher: "200",
|
||||
},
|
||||
}
|
||||
log.Printf("\t\t\tSet ELB Target Group Name for %s to '%s'.",
|
||||
srv.AwsElbLoadBalancer.TargetGroup.Protocol,
|
||||
srv.AwsElbLoadBalancer.TargetGroup.Name)
|
||||
srv.AwsElbLoadBalancer.TargetGroups[0].Protocol,
|
||||
srv.AwsElbLoadBalancer.TargetGroups[0].Name)
|
||||
|
||||
// Set ECS configs based on specified env.
|
||||
if cfg.Env == "prod" {
|
||||
@ -593,11 +278,6 @@ func (ctx *ServiceContext) Deploy(log *log.Logger, cfg *devdeploy.Config) (*devd
|
||||
LaunchType: "FARGATE",
|
||||
}
|
||||
|
||||
// Ensure when deploying a new service there is always at-least one running.
|
||||
if srv.AwsEcsService.DesiredCount == 0 {
|
||||
srv.AwsEcsService.DesiredCount = 1
|
||||
}
|
||||
|
||||
// Set ECS configs based on specified env.
|
||||
if cfg.Env == "prod" {
|
||||
srv.AwsEcsService.DeploymentMinimumHealthyPercent = 100
|
||||
@ -607,45 +287,328 @@ func (ctx *ServiceContext) Deploy(log *log.Logger, cfg *devdeploy.Config) (*devd
|
||||
srv.AwsEcsService.DeploymentMaximumPercent = 200
|
||||
}
|
||||
|
||||
// AwsEcsTaskDefinition defines the details for registering a new ECS task definition.
|
||||
taskDef, err := ctx.AwsEcsTaskDefinition(cfg, srv)
|
||||
// Load the web-app config for the web-api can reference it's hostname.
|
||||
webAppCtx, err := NewServiceContext(ServiceWebApp, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
srv.AwsEcsTaskDefinition = &devdeploy.AwsEcsTaskDefinition{
|
||||
RegisterInput: taskDef,
|
||||
UpdatePlaceholders: func(placeholders map[string]string) error {
|
||||
|
||||
// Try to find the Datadog API key, this value is optional.
|
||||
// If Datadog API key is not specified, then integration with Datadog for observability will not be active.
|
||||
{
|
||||
datadogApiKey, err := getDatadogApiKey(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if datadogApiKey != "" {
|
||||
log.Println("DATADOG API Key set.")
|
||||
} else {
|
||||
log.Printf("DATADOG API Key NOT set.")
|
||||
}
|
||||
|
||||
placeholders["{DATADOG_APIKEY}"] = datadogApiKey
|
||||
|
||||
// When the datadog API key is empty, don't force the container to be essential have have the whole task fail.
|
||||
if datadogApiKey != "" {
|
||||
placeholders["{DATADOG_ESSENTIAL}"] = "true"
|
||||
} else {
|
||||
placeholders["{DATADOG_ESSENTIAL}"] = "false"
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
// Load the web-api config for the web-app can reference it's hostname.
|
||||
webApiCtx, err := NewServiceContext(ServiceWebApi, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("\t\tDeploying task to '%s'.", ctx.ServiceHostPrimary)
|
||||
// Define a base set of environment variables that can be assigned to individual container definitions.
|
||||
baseEnvVals := func() []*ecs.KeyValuePair {
|
||||
|
||||
var ciJobURL string
|
||||
if id := os.Getenv("CI_JOB_ID"); id != "" {
|
||||
ciJobURL = strings.TrimRight(GitLabProjectBaseUrl, "/") + "/-/jobs/" + os.Getenv("CI_JOB_ID")
|
||||
}
|
||||
|
||||
var ciPipelineURL string
|
||||
if id := os.Getenv("CI_PIPELINE_ID"); id != "" {
|
||||
ciPipelineURL = strings.TrimRight(GitLabProjectBaseUrl, "/") + "/pipelines/" + os.Getenv("CI_PIPELINE_ID")
|
||||
}
|
||||
|
||||
return []*ecs.KeyValuePair{
|
||||
ecsKeyValuePair(devdeploy.ENV_KEY_ECS_CLUSTER, srv.AwsEcsCluster.ClusterName),
|
||||
ecsKeyValuePair(devdeploy.ENV_KEY_ECS_SERVICE, srv.AwsEcsService.ServiceName),
|
||||
ecsKeyValuePair("AWS_DEFAULT_REGION", cfg.AwsCredentials.Region),
|
||||
ecsKeyValuePair("AWS_USE_ROLE", "true"),
|
||||
ecsKeyValuePair("AWSLOGS_GROUP", srv.AwsCloudWatchLogGroup.LogGroupName),
|
||||
ecsKeyValuePair("ECS_ENABLE_CONTAINER_METADATA", "true"),
|
||||
ecsKeyValuePair("CI_COMMIT_REF_NAME", os.Getenv("CI_COMMIT_REF_NAME")),
|
||||
ecsKeyValuePair("CI_COMMIT_SHORT_SHA", os.Getenv("CI_COMMIT_SHORT_SHA")),
|
||||
ecsKeyValuePair("CI_COMMIT_SHA", os.Getenv("CI_COMMIT_SHA")),
|
||||
ecsKeyValuePair("CI_COMMIT_TAG", os.Getenv("CI_COMMIT_TAG")),
|
||||
ecsKeyValuePair("CI_JOB_ID", os.Getenv("CI_JOB_ID")),
|
||||
ecsKeyValuePair("CI_PIPELINE_ID", os.Getenv("CI_PIPELINE_ID")),
|
||||
ecsKeyValuePair("CI_JOB_URL", ciJobURL),
|
||||
ecsKeyValuePair("CI_PIPELINE_URL", ciPipelineURL),
|
||||
ecsKeyValuePair("WEB_APP_BASE_URL", webAppCtx.BaseUrl()),
|
||||
ecsKeyValuePair("WEB_API_BASE_URL", webApiCtx.BaseUrl()),
|
||||
ecsKeyValuePair("EMAIL_SENDER", "lee+saas-starter-kit@geeksinthewoods.com"),
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Service dependant settings.
|
||||
switch serviceName {
|
||||
|
||||
// Define the ServiceContext for the web-app that will be used for build and deploy.
|
||||
case ServiceWebApp:
|
||||
|
||||
// Defined a container definition for the specific service.
|
||||
container1 := &ecs.ContainerDefinition{
|
||||
Name: aws.String(ctx.Name),
|
||||
Image: aws.String(srv.ReleaseImage),
|
||||
Essential: aws.Bool(true),
|
||||
LogConfiguration: &ecs.LogConfiguration{
|
||||
LogDriver: aws.String("awslogs"),
|
||||
Options: map[string]*string{
|
||||
"awslogs-group": aws.String(srv.AwsCloudWatchLogGroup.LogGroupName),
|
||||
"awslogs-region": aws.String(cfg.AwsCredentials.Region),
|
||||
"awslogs-stream-prefix": aws.String("ecs"),
|
||||
},
|
||||
},
|
||||
PortMappings: []*ecs.PortMapping{
|
||||
&ecs.PortMapping{
|
||||
HostPort: aws.Int64(80),
|
||||
Protocol: aws.String("tcp"),
|
||||
ContainerPort: aws.Int64(80),
|
||||
},
|
||||
},
|
||||
Cpu: aws.Int64(128),
|
||||
MemoryReservation: aws.Int64(128),
|
||||
Environment: baseEnvVals(cfg, srv),
|
||||
HealthCheck: &ecs.HealthCheck{
|
||||
Retries: aws.Int64(3),
|
||||
Command: aws.StringSlice([]string{
|
||||
"CMD-SHELL",
|
||||
"curl -f http://localhost/ping || exit 1",
|
||||
}),
|
||||
Timeout: aws.Int64(5),
|
||||
Interval: aws.Int64(60),
|
||||
StartPeriod: aws.Int64(60),
|
||||
},
|
||||
Ulimits: []*ecs.Ulimit{
|
||||
&ecs.Ulimit{
|
||||
Name: aws.String("nofile"),
|
||||
SoftLimit: aws.Int64(987654),
|
||||
HardLimit: aws.Int64(999999),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// If the service has HTTPS enabled with the use of an AWS Elastic Load Balancer, then need to enable
|
||||
// traffic for port 443 for SSL traffic to get terminated on the deployed tasks.
|
||||
if ctx.EnableHTTPS && !ctx.EnableElb {
|
||||
container1.PortMappings = append(container1.PortMappings, &ecs.PortMapping{
|
||||
HostPort: aws.Int64(443),
|
||||
Protocol: aws.String("tcp"),
|
||||
ContainerPort: aws.Int64(443),
|
||||
})
|
||||
}
|
||||
|
||||
// Append env vars for the service task.
|
||||
container1.Environment = append(container1.Environment,
|
||||
ecsKeyValuePair("SERVICE_NAME", ctx.Name),
|
||||
ecsKeyValuePair("PROJECT_NAME", cfg.ProjectName),
|
||||
|
||||
// Use placeholders for these environment variables that will be replaced with devdeploy.DeployServiceToTargetEnv
|
||||
ecsKeyValuePair("WEB_APP_HOST_HOST", "{HTTP_HOST}"),
|
||||
ecsKeyValuePair("WEB_APP_HTTPS_HOST", "{HTTPS_HOST}"),
|
||||
ecsKeyValuePair("WEB_APP_SERVICE_ENABLE_HTTPS", "{HTTPS_ENABLED}"),
|
||||
ecsKeyValuePair("WEB_APP_SERVICE_BASE_URL", "{APP_BASE_URL}"),
|
||||
ecsKeyValuePair("WEB_APP_SERVICE_HOST_NAMES", "{HOST_NAMES}"),
|
||||
ecsKeyValuePair("WEB_APP_SERVICE_STATICFILES_S3_ENABLED", "{STATIC_FILES_S3_ENABLED}"),
|
||||
ecsKeyValuePair("WEB_APP_SERVICE_STATICFILES_S3_PREFIX", "{STATIC_FILES_S3_PREFIX}"),
|
||||
ecsKeyValuePair("WEB_APP_SERVICE_STATICFILES_CLOUDFRONT_ENABLED", "{STATIC_FILES_CLOUDFRONT_ENABLED}"),
|
||||
ecsKeyValuePair("WEB_APP_REDIS_HOST", "{CACHE_HOST}"),
|
||||
ecsKeyValuePair("WEB_APP_DB_HOST", "{DB_HOST}"),
|
||||
ecsKeyValuePair("WEB_APP_DB_USERNAME", "{DB_USER}"),
|
||||
ecsKeyValuePair("WEB_APP_DB_PASSWORD", "{DB_PASS}"),
|
||||
ecsKeyValuePair("WEB_APP_DB_DATABASE", "{DB_DATABASE}"),
|
||||
ecsKeyValuePair("WEB_APP_DB_DRIVER", "{DB_DRIVER}"),
|
||||
ecsKeyValuePair("WEB_APP_DB_DISABLE_TLS", "{DB_DISABLE_TLS}"),
|
||||
ecsKeyValuePair("WEB_APP_AWS_S3_BUCKET_PRIVATE", "{AWS_S3_BUCKET_PRIVATE}"),
|
||||
ecsKeyValuePair("WEB_APP_AWS_S3_BUCKET_PUBLIC", "{AWS_S3_BUCKET_PUBLIC}"),
|
||||
ecsKeyValuePair(devdeploy.ENV_KEY_ROUTE53_UPDATE_TASK_IPS, "{ROUTE53_UPDATE_TASK_IPS}"),
|
||||
ecsKeyValuePair(devdeploy.ENV_KEY_ROUTE53_ZONES, "{ROUTE53_ZONES}"),
|
||||
)
|
||||
|
||||
// Define the full task definition for the service.
|
||||
taskDef := &ecs.RegisterTaskDefinitionInput{
|
||||
Family: aws.String(fmt.Sprintf("%s-%s-%s", cfg.Env, srv.AwsEcsCluster.ClusterName, ctx.Name)),
|
||||
NetworkMode: aws.String("awsvpc"),
|
||||
ContainerDefinitions: []*ecs.ContainerDefinition{
|
||||
// Include the single container definition for the service. Additional definitions could be added
|
||||
// here like one for datadog.
|
||||
container1,
|
||||
},
|
||||
RequiresCompatibilities: aws.StringSlice([]string{"FARGATE"}),
|
||||
}
|
||||
|
||||
srv.AwsEcsTaskDefinition = &devdeploy.AwsEcsTaskDefinition{
|
||||
RegisterInput: taskDef,
|
||||
UpdatePlaceholders: func(placeholders map[string]string) error {
|
||||
|
||||
// Try to find the Datadog API key, this value is optional.
|
||||
// If Datadog API key is not specified, then integration with Datadog for observability will not be active.
|
||||
{
|
||||
datadogApiKey, err := getDatadogApiKey(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if datadogApiKey != "" {
|
||||
log.Println("DATADOG API Key set.")
|
||||
} else {
|
||||
log.Printf("DATADOG API Key NOT set.")
|
||||
}
|
||||
|
||||
placeholders["{DATADOG_APIKEY}"] = datadogApiKey
|
||||
|
||||
// When the datadog API key is empty, don't force the container to be essential have have the whole task fail.
|
||||
if datadogApiKey != "" {
|
||||
placeholders["{DATADOG_ESSENTIAL}"] = "true"
|
||||
} else {
|
||||
placeholders["{DATADOG_ESSENTIAL}"] = "false"
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// Define the ServiceContext for the web-api that will be used for build and deploy.
|
||||
case ServiceWebApi:
|
||||
|
||||
// Defined a container definition for the specific service.
|
||||
container1 := &ecs.ContainerDefinition{
|
||||
Name: aws.String(ctx.Name),
|
||||
Image: aws.String(srv.ReleaseImage),
|
||||
Essential: aws.Bool(true),
|
||||
LogConfiguration: &ecs.LogConfiguration{
|
||||
LogDriver: aws.String("awslogs"),
|
||||
Options: map[string]*string{
|
||||
"awslogs-group": aws.String(srv.AwsCloudWatchLogGroup.LogGroupName),
|
||||
"awslogs-region": aws.String(cfg.AwsCredentials.Region),
|
||||
"awslogs-stream-prefix": aws.String("ecs"),
|
||||
},
|
||||
},
|
||||
PortMappings: []*ecs.PortMapping{
|
||||
&ecs.PortMapping{
|
||||
HostPort: aws.Int64(80),
|
||||
Protocol: aws.String("tcp"),
|
||||
ContainerPort: aws.Int64(80),
|
||||
},
|
||||
},
|
||||
Cpu: aws.Int64(128),
|
||||
MemoryReservation: aws.Int64(128),
|
||||
Environment: baseEnvVals(cfg, srv),
|
||||
HealthCheck: &ecs.HealthCheck{
|
||||
Retries: aws.Int64(3),
|
||||
Command: aws.StringSlice([]string{
|
||||
"CMD-SHELL",
|
||||
"curl -f http://localhost/ping || exit 1",
|
||||
}),
|
||||
Timeout: aws.Int64(5),
|
||||
Interval: aws.Int64(60),
|
||||
StartPeriod: aws.Int64(60),
|
||||
},
|
||||
Ulimits: []*ecs.Ulimit{
|
||||
&ecs.Ulimit{
|
||||
Name: aws.String("nofile"),
|
||||
SoftLimit: aws.Int64(987654),
|
||||
HardLimit: aws.Int64(999999),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// If the service has HTTPS enabled with the use of an AWS Elastic Load Balancer, then need to enable
|
||||
// traffic for port 443 for SSL traffic to get terminated on the deployed tasks.
|
||||
if ctx.EnableHTTPS && !ctx.EnableElb {
|
||||
container1.PortMappings = append(container1.PortMappings, &ecs.PortMapping{
|
||||
HostPort: aws.Int64(443),
|
||||
Protocol: aws.String("tcp"),
|
||||
ContainerPort: aws.Int64(443),
|
||||
})
|
||||
}
|
||||
|
||||
// Append env vars for the service task.
|
||||
container1.Environment = append(container1.Environment,
|
||||
ecsKeyValuePair("SERVICE_NAME", ctx.Name),
|
||||
ecsKeyValuePair("PROJECT_NAME", cfg.ProjectName),
|
||||
|
||||
// Use placeholders for these environment variables that will be replaced with devdeploy.DeployServiceToTargetEnv
|
||||
ecsKeyValuePair("WEB_API_HTTP_HOST", "{HTTP_HOST}"),
|
||||
ecsKeyValuePair("WEB_API_HTTPS_HOST", "{HTTPS_HOST}"),
|
||||
ecsKeyValuePair("WEB_API_SERVICE_ENABLE_HTTPS", "{HTTPS_ENABLED}"),
|
||||
ecsKeyValuePair("WEB_API_SERVICE_BASE_URL", "{APP_BASE_URL}"),
|
||||
ecsKeyValuePair("WEB_API_SERVICE_HOST_NAMES", "{HOST_NAMES}"),
|
||||
ecsKeyValuePair("WEB_API_SERVICE_STATICFILES_S3_ENABLED", "{STATIC_FILES_S3_ENABLED}"),
|
||||
ecsKeyValuePair("WEB_API_SERVICE_STATICFILES_S3_PREFIX", "{STATIC_FILES_S3_PREFIX}"),
|
||||
ecsKeyValuePair("WEB_API_SERVICE_STATICFILES_CLOUDFRONT_ENABLED", "{STATIC_FILES_CLOUDFRONT_ENABLED}"),
|
||||
ecsKeyValuePair("WEB_API_REDIS_HOST", "{CACHE_HOST}"),
|
||||
ecsKeyValuePair("WEB_API_DB_HOST", "{DB_HOST}"),
|
||||
ecsKeyValuePair("WEB_API_DB_USERNAME", "{DB_USER}"),
|
||||
ecsKeyValuePair("WEB_API_DB_PASSWORD", "{DB_PASS}"),
|
||||
ecsKeyValuePair("WEB_API_DB_DATABASE", "{DB_DATABASE}"),
|
||||
ecsKeyValuePair("WEB_API_DB_DRIVER", "{DB_DRIVER}"),
|
||||
ecsKeyValuePair("WEB_API_DB_DISABLE_TLS", "{DB_DISABLE_TLS}"),
|
||||
ecsKeyValuePair("WEB_API_AWS_S3_BUCKET_PRIVATE", "{AWS_S3_BUCKET_PRIVATE}"),
|
||||
ecsKeyValuePair("WEB_API_AWS_S3_BUCKET_PUBLIC", "{AWS_S3_BUCKET_PUBLIC}"),
|
||||
ecsKeyValuePair(devdeploy.ENV_KEY_ROUTE53_UPDATE_TASK_IPS, "{ROUTE53_UPDATE_TASK_IPS}"),
|
||||
ecsKeyValuePair(devdeploy.ENV_KEY_ROUTE53_ZONES, "{ROUTE53_ZONES}"),
|
||||
)
|
||||
|
||||
// Define the full task definition for the service.
|
||||
taskDef := &ecs.RegisterTaskDefinitionInput{
|
||||
Family: aws.String(fmt.Sprintf("%s-%s-%s", cfg.Env, srv.AwsEcsCluster.ClusterName, ctx.Name)),
|
||||
NetworkMode: aws.String("awsvpc"),
|
||||
ContainerDefinitions: []*ecs.ContainerDefinition{
|
||||
// Include the single container definition for the service. Additional definitions could be added
|
||||
// here like one for datadog.
|
||||
container1,
|
||||
},
|
||||
RequiresCompatibilities: aws.StringSlice([]string{"FARGATE"}),
|
||||
}
|
||||
|
||||
srv.AwsEcsTaskDefinition = &devdeploy.AwsEcsTaskDefinition{
|
||||
RegisterInput: taskDef,
|
||||
UpdatePlaceholders: func(placeholders map[string]string) error {
|
||||
|
||||
// Try to find the Datadog API key, this value is optional.
|
||||
// If Datadog API key is not specified, then integration with Datadog for observability will not be active.
|
||||
{
|
||||
datadogApiKey, err := getDatadogApiKey(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if datadogApiKey != "" {
|
||||
log.Println("DATADOG API Key set.")
|
||||
} else {
|
||||
log.Printf("DATADOG API Key NOT set.")
|
||||
}
|
||||
|
||||
placeholders["{DATADOG_APIKEY}"] = datadogApiKey
|
||||
|
||||
// When the datadog API key is empty, don't force the container to be essential have have the whole task fail.
|
||||
if datadogApiKey != "" {
|
||||
placeholders["{DATADOG_ESSENTIAL}"] = "true"
|
||||
} else {
|
||||
placeholders["{DATADOG_ESSENTIAL}"] = "false"
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, errors.Wrapf(devdeploy.ErrInvalidService,
|
||||
"No service context defined for service '%s'",
|
||||
serviceName)
|
||||
}
|
||||
|
||||
// Set the docker file if no custom one has been defined for the service.
|
||||
if srv.Dockerfile == "" {
|
||||
srv.Dockerfile = filepath.Join(srv.CodeDir, "Dockerfile")
|
||||
}
|
||||
|
||||
if srv.StaticFilesDir == "" {
|
||||
srv.StaticFilesDir = filepath.Join(srv.CodeDir, "static")
|
||||
}
|
||||
|
||||
// When only service host names are set, choose the first item as the primary host.
|
||||
if srv.ServiceHostPrimary == "" && len(srv.ServiceHostNames) > 0 {
|
||||
srv.ServiceHostPrimary = srv.ServiceHostNames[0]
|
||||
log.Printf("\t\tSet Service Primary Host to '%s'.", srv.ServiceHostPrimary)
|
||||
}
|
||||
|
||||
return srv, nil
|
||||
}
|
||||
@ -653,47 +616,39 @@ func (ctx *ServiceContext) Deploy(log *log.Logger, cfg *devdeploy.Config) (*devd
|
||||
// BuildServiceForTargetEnv executes the build commands for a target service.
|
||||
func BuildServiceForTargetEnv(log *log.Logger, awsCredentials devdeploy.AwsCredentials, targetEnv Env, serviceName, releaseTag string, dryRun, noCache, noPush bool) error {
|
||||
|
||||
cfgCtx, err := NewConfigContext(targetEnv, awsCredentials)
|
||||
cfg, err := NewConfig(log, targetEnv, awsCredentials)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg, err := cfgCtx.Config(log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
srvCtx, err := NewServiceContext(serviceName, cfg)
|
||||
targetSvc, err := NewService(serviceName, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Override the release tag if set.
|
||||
if releaseTag != "" {
|
||||
srvCtx.ReleaseTag = releaseTag
|
||||
targetSvc.ReleaseTag = releaseTag
|
||||
}
|
||||
|
||||
details, err := srvCtx.Build(log, noCache, noPush)
|
||||
if err != nil {
|
||||
return err
|
||||
// Append build args to be used for all services.
|
||||
if targetSvc.DockerBuildArgs == nil {
|
||||
targetSvc.DockerBuildArgs = make(map[string]string)
|
||||
}
|
||||
|
||||
// servicePath is used to copy the service specific code in the Dockerfile.
|
||||
servicePath, err := filepath.Rel(cfg.ProjectRoot, srvCtx.ServiceDir)
|
||||
codePath, err := filepath.Rel(cfg.ProjectRoot, targetSvc.CodeDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetSvc.DockerBuildArgs["code_path"] = codePath
|
||||
|
||||
// commitRef is used by main.go:build constant.
|
||||
commitRef := getCommitRef()
|
||||
if commitRef == "" {
|
||||
commitRef = srvCtx.ReleaseTag
|
||||
}
|
||||
|
||||
details.BuildArgs = map[string]string{
|
||||
"service_path": servicePath,
|
||||
"commit_ref": commitRef,
|
||||
commitRef = targetSvc.ReleaseTag
|
||||
}
|
||||
targetSvc.DockerBuildArgs["commit_ref"] = commitRef
|
||||
|
||||
if dryRun {
|
||||
cfgJSON, err := json.MarshalIndent(cfg, "", " ")
|
||||
@ -702,7 +657,7 @@ func BuildServiceForTargetEnv(log *log.Logger, awsCredentials devdeploy.AwsCrede
|
||||
}
|
||||
log.Printf("BuildServiceForTargetEnv : config : %v\n", string(cfgJSON))
|
||||
|
||||
detailsJSON, err := json.MarshalIndent(details, "", " ")
|
||||
detailsJSON, err := json.MarshalIndent(targetSvc, "", " ")
|
||||
if err != nil {
|
||||
log.Fatalf("BuildServiceForTargetEnv : Marshalling details to JSON : %+v", err)
|
||||
}
|
||||
@ -711,38 +666,28 @@ func BuildServiceForTargetEnv(log *log.Logger, awsCredentials devdeploy.AwsCrede
|
||||
return nil
|
||||
}
|
||||
|
||||
return devdeploy.BuildServiceForTargetEnv(log, cfg, details)
|
||||
return devdeploy.BuildServiceForTargetEnv(log, cfg, targetSvc, noCache, noPush)
|
||||
}
|
||||
|
||||
// DeployServiceForTargetEnv executes the build commands for a target service.
|
||||
func DeployServiceForTargetEnv(log *log.Logger, awsCredentials devdeploy.AwsCredentials, targetEnv Env, serviceName, releaseTag string, dryRun bool) error {
|
||||
|
||||
cfgCtx, err := NewConfigContext(targetEnv, awsCredentials)
|
||||
cfg, err := NewConfig(log, targetEnv, awsCredentials)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg, err := cfgCtx.Config(log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
srvCtx, err := NewServiceContext(serviceName, cfg)
|
||||
targetSvc, err := NewService(serviceName, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Override the release tag if set.
|
||||
if releaseTag != "" {
|
||||
srvCtx.ReleaseTag = releaseTag
|
||||
targetSvc.ReleaseTag = releaseTag
|
||||
}
|
||||
|
||||
details, err := srvCtx.Deploy(log, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return devdeploy.DeployServiceToTargetEnv(log, cfg, details)
|
||||
return devdeploy.DeployServiceToTargetEnv(log, cfg, targetSvc)
|
||||
}
|
||||
|
||||
// ecsKeyValuePair returns an *ecs.KeyValuePair
|
||||
|
2
go.mod
2
go.mod
@ -41,7 +41,7 @@ require (
|
||||
github.com/tinylib/msgp v1.1.0 // indirect
|
||||
github.com/urfave/cli v1.21.0
|
||||
github.com/xwb1989/sqlparser v0.0.0-20180606152119-120387863bf2
|
||||
gitlab.com/geeks-accelerator/oss/devops v1.0.13
|
||||
gitlab.com/geeks-accelerator/oss/devops v1.0.14
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
|
||||
golang.org/x/tools v0.0.0-20190807223507-b346f7fd45de // indirect
|
||||
|
2
go.sum
2
go.sum
@ -219,6 +219,8 @@ gitlab.com/geeks-accelerator/oss/devops v1.0.11 h1:ojSvv4bSOZSyGjFMvpbJyREVfdN1A
|
||||
gitlab.com/geeks-accelerator/oss/devops v1.0.11/go.mod h1:xr+rhNSDXrEh0A6bkBPnfMiRIou3OiPZK0oD5h9GAAM=
|
||||
gitlab.com/geeks-accelerator/oss/devops v1.0.13 h1:Wnf+vXPP8Ps4tSVdbk/vgl1rHaAELIPE3OYBAzvroG8=
|
||||
gitlab.com/geeks-accelerator/oss/devops v1.0.13/go.mod h1:xr+rhNSDXrEh0A6bkBPnfMiRIou3OiPZK0oD5h9GAAM=
|
||||
gitlab.com/geeks-accelerator/oss/devops v1.0.14 h1:jNLi69UAH44+FkixN/rtS7qobsSFvxwQ+g8NgVOwFt0=
|
||||
gitlab.com/geeks-accelerator/oss/devops v1.0.14/go.mod h1:xr+rhNSDXrEh0A6bkBPnfMiRIou3OiPZK0oD5h9GAAM=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
|
Reference in New Issue
Block a user