You've already forked golang-saas-starter-kit
mirror of
https://github.com/raseels-repos/golang-saas-starter-kit.git
synced 2025-06-25 00:46:51 +02:00
replace internal devops package
This commit is contained in:
@ -497,7 +497,7 @@ func NewConfig(log *log.Logger, targetEnv Env, awsCredentials devdeploy.AwsCrede
|
||||
|
||||
// Append all the defined services to the config.
|
||||
for _, n := range ServiceNames {
|
||||
srv, err := NewService(n, cfg)
|
||||
srv, err := NewService(log, n, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -506,7 +506,7 @@ func NewConfig(log *log.Logger, targetEnv Env, awsCredentials devdeploy.AwsCrede
|
||||
|
||||
// Append all the defined functions to the config.
|
||||
for _, n := range FunctionNames {
|
||||
fn, err := NewFunction(n, cfg)
|
||||
fn, err := NewFunction(log, n, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ var FunctionNames = []Function{
|
||||
}
|
||||
|
||||
// NewFunction returns the *devdeploy.ProjectFunction.
|
||||
func NewFunction(funcName string, cfg *devdeploy.Config) (*devdeploy.ProjectFunction, error) {
|
||||
func NewFunction(log *log.Logger, funcName string, cfg *devdeploy.Config) (*devdeploy.ProjectFunction, error) {
|
||||
|
||||
ctx := &devdeploy.ProjectFunction{
|
||||
Name: fmt.Sprintf("%s-%s-%s", cfg.Env, cfg.ProjectName, funcName),
|
||||
@ -213,7 +213,7 @@ func BuildFunctionForTargetEnv(log *log.Logger, awsCredentials devdeploy.AwsCred
|
||||
return err
|
||||
}
|
||||
|
||||
targetFunc, err := NewFunction(functionName, cfg)
|
||||
targetFunc, err := NewFunction(log, functionName, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -269,7 +269,7 @@ func DeployFunctionForTargetEnv(log *log.Logger, awsCredentials devdeploy.AwsCre
|
||||
return err
|
||||
}
|
||||
|
||||
targetFunc, err := NewFunction(functionName, cfg)
|
||||
targetFunc, err := NewFunction(log, functionName, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ var ImageNames = []Image{
|
||||
}
|
||||
|
||||
// NewImage returns the *devdeploy.ProjectImage.
|
||||
func NewImage(imageName string, cfg *devdeploy.Config) (*devdeploy.ProjectImage, error) {
|
||||
func NewImage(log *log.Logger, imageName string, cfg *devdeploy.Config) (*devdeploy.ProjectImage, error) {
|
||||
|
||||
ctx := &devdeploy.ProjectImage{
|
||||
Name: fmt.Sprintf("%s-%s-%s", cfg.Env, cfg.ProjectName, imageName),
|
||||
@ -61,7 +61,7 @@ func BuildImageForTargetEnv(log *log.Logger, awsCredentials devdeploy.AwsCredent
|
||||
return err
|
||||
}
|
||||
|
||||
targetImage, err := NewImage(imageName, cfg)
|
||||
targetImage, err := NewImage(log, imageName, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -154,7 +154,7 @@ func (c ServiceContext) BaseUrl() string {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
func NewService(log *log.Logger, serviceName string, cfg *devdeploy.Config) (*devdeploy.ProjectService, error) {
|
||||
|
||||
ctx, err := NewServiceContext(serviceName, cfg)
|
||||
if err != nil {
|
||||
@ -774,7 +774,7 @@ func BuildServiceForTargetEnv(log *log.Logger, awsCredentials devdeploy.AwsCrede
|
||||
return err
|
||||
}
|
||||
|
||||
targetSvc, err := NewService(serviceName, cfg)
|
||||
targetSvc, err := NewService(log, serviceName, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -830,7 +830,7 @@ func DeployServiceForTargetEnv(log *log.Logger, awsCredentials devdeploy.AwsCred
|
||||
return err
|
||||
}
|
||||
|
||||
targetSvc, err := NewService(serviceName, cfg)
|
||||
targetSvc, err := NewService(log, serviceName, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ import (
|
||||
"geeks-accelerator/oss/saas-starter-kit/internal/account/account_preference"
|
||||
"geeks-accelerator/oss/saas-starter-kit/internal/mid"
|
||||
"geeks-accelerator/oss/saas-starter-kit/internal/platform/auth"
|
||||
"geeks-accelerator/oss/saas-starter-kit/internal/platform/devops"
|
||||
"geeks-accelerator/oss/saas-starter-kit/internal/platform/flag"
|
||||
"geeks-accelerator/oss/saas-starter-kit/internal/platform/notify"
|
||||
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext"
|
||||
@ -45,6 +44,7 @@ import (
|
||||
"github.com/kelseyhightower/envconfig"
|
||||
"github.com/lib/pq"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/geeks-accelerator/oss/devops/pkg/devdeploy"
|
||||
"golang.org/x/crypto/acme"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
awstrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/aws/aws-sdk-go/aws"
|
||||
@ -303,8 +303,8 @@ func main() {
|
||||
|
||||
// If AWS is enabled, check the Secrets Manager for the session key.
|
||||
if awsSession != nil {
|
||||
cfg.Project.SharedSecretKey, err = devops.SecretManagerGetString(awsSession, secretID)
|
||||
if err != nil && errors.Cause(err) != devops.ErrSecreteNotFound {
|
||||
cfg.Project.SharedSecretKey, err = devdeploy.SecretManagerGetString(awsSession, secretID)
|
||||
if err != nil && errors.Cause(err) != devdeploy.ErrSecreteNotFound {
|
||||
log.Fatalf("main : Session : %+v", err)
|
||||
}
|
||||
}
|
||||
@ -314,7 +314,7 @@ func main() {
|
||||
cfg.Project.SharedSecretKey = string(securecookie.GenerateRandomKey(32))
|
||||
|
||||
if awsSession != nil {
|
||||
err = devops.SecretManagerPutString(awsSession, secretID, cfg.Project.SharedSecretKey)
|
||||
err = devdeploy.SecretManagerPutString(awsSession, secretID, cfg.Project.SharedSecretKey)
|
||||
if err != nil {
|
||||
log.Fatalf("main : Session : %+v", err)
|
||||
}
|
||||
@ -510,7 +510,7 @@ func main() {
|
||||
|
||||
// =========================================================================
|
||||
// ECS Task registration for services that don't use an AWS Elastic Load Balancer.
|
||||
err = devops.EcsServiceTaskInit(log, awsSession)
|
||||
err = devdeploy.EcsServiceTaskInit(log, awsSession)
|
||||
if err != nil {
|
||||
log.Fatalf("main : Ecs Service Task init : %+v", err)
|
||||
}
|
||||
@ -588,7 +588,7 @@ func main() {
|
||||
// Local file cache to reduce requests hitting Secret Manager.
|
||||
localCache := autocert.DirCache(os.TempDir())
|
||||
|
||||
cache, err := devops.NewSecretManagerAutocertCache(log, awsSession, secretPrefix, localCache)
|
||||
cache, err := devdeploy.NewSecretManagerAutocertCache(log, awsSession, secretPrefix, localCache)
|
||||
if err != nil {
|
||||
log.Fatalf("main : HTTPS : %+v", err)
|
||||
}
|
||||
@ -621,7 +621,7 @@ func main() {
|
||||
log.Printf("main : %v : Start shutdown..", sig)
|
||||
|
||||
// Ensure the public IP address for the task is removed from Route53.
|
||||
err = devops.EcsServiceTaskTaskShutdown(log, awsSession)
|
||||
err = devdeploy.EcsServiceTaskTaskShutdown(log, awsSession)
|
||||
if err != nil {
|
||||
log.Fatalf("main : Ecs Service Task shutdown : %+v", err)
|
||||
}
|
||||
|
@ -26,7 +26,6 @@ import (
|
||||
"geeks-accelerator/oss/saas-starter-kit/internal/geonames"
|
||||
"geeks-accelerator/oss/saas-starter-kit/internal/mid"
|
||||
"geeks-accelerator/oss/saas-starter-kit/internal/platform/auth"
|
||||
"geeks-accelerator/oss/saas-starter-kit/internal/platform/devops"
|
||||
"geeks-accelerator/oss/saas-starter-kit/internal/platform/flag"
|
||||
img_resize "geeks-accelerator/oss/saas-starter-kit/internal/platform/img-resize"
|
||||
"geeks-accelerator/oss/saas-starter-kit/internal/platform/notify"
|
||||
@ -52,6 +51,7 @@ import (
|
||||
"github.com/kelseyhightower/envconfig"
|
||||
"github.com/lib/pq"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/geeks-accelerator/oss/devops/pkg/devdeploy"
|
||||
"golang.org/x/crypto/acme"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
awstrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/aws/aws-sdk-go/aws"
|
||||
@ -306,8 +306,8 @@ func main() {
|
||||
|
||||
// If AWS is enabled, check the Secrets Manager for the session key.
|
||||
if awsSession != nil {
|
||||
cfg.Project.SharedSecretKey, err = devops.SecretManagerGetString(awsSession, secretID)
|
||||
if err != nil && errors.Cause(err) != devops.ErrSecreteNotFound {
|
||||
cfg.Project.SharedSecretKey, err = devdeploy.SecretManagerGetString(awsSession, secretID)
|
||||
if err != nil && errors.Cause(err) != devdeploy.ErrSecreteNotFound {
|
||||
log.Fatalf("main : Session : %+v", err)
|
||||
}
|
||||
}
|
||||
@ -317,7 +317,7 @@ func main() {
|
||||
cfg.Project.SharedSecretKey = string(securecookie.GenerateRandomKey(32))
|
||||
|
||||
if awsSession != nil {
|
||||
err = devops.SecretManagerPutString(awsSession, secretID, cfg.Project.SharedSecretKey)
|
||||
err = devdeploy.SecretManagerPutString(awsSession, secretID, cfg.Project.SharedSecretKey)
|
||||
if err != nil {
|
||||
log.Fatalf("main : Session : %+v", err)
|
||||
}
|
||||
@ -511,7 +511,7 @@ func main() {
|
||||
// a publicly available image URL.
|
||||
var staticS3UrlFormatter func(string) string
|
||||
if cfg.Service.StaticFiles.S3Enabled || cfg.Service.StaticFiles.CloudFrontEnabled || cfg.Service.StaticFiles.ImgResizeEnabled {
|
||||
s3UrlFormatter, err := devops.S3UrlFormatter(awsSession, cfg.Aws.S3BucketPublic, cfg.Service.StaticFiles.S3Prefix, cfg.Service.StaticFiles.CloudFrontEnabled)
|
||||
s3UrlFormatter, err := devdeploy.S3UrlFormatter(awsSession, cfg.Aws.S3BucketPublic, cfg.Service.StaticFiles.S3Prefix, cfg.Service.StaticFiles.CloudFrontEnabled)
|
||||
if err != nil {
|
||||
log.Fatalf("main : S3UrlFormatter failed : %+v", err)
|
||||
}
|
||||
@ -993,7 +993,7 @@ func main() {
|
||||
|
||||
// =========================================================================
|
||||
// ECS Task registration for services that don't use an AWS Elastic Load Balancer.
|
||||
err = devops.EcsServiceTaskInit(log, awsSession)
|
||||
err = devdeploy.EcsServiceTaskInit(log, awsSession)
|
||||
if err != nil {
|
||||
log.Fatalf("main : Ecs Service Task init : %+v", err)
|
||||
}
|
||||
@ -1058,7 +1058,7 @@ func main() {
|
||||
// Local file cache to reduce requests hitting Secret Manager.
|
||||
localCache := autocert.DirCache(os.TempDir())
|
||||
|
||||
cache, err := devops.NewSecretManagerAutocertCache(log, awsSession, secretPrefix, localCache)
|
||||
cache, err := devdeploy.NewSecretManagerAutocertCache(log, awsSession, secretPrefix, localCache)
|
||||
if err != nil {
|
||||
log.Fatalf("main : HTTPS : %+v", err)
|
||||
}
|
||||
|
@ -1,137 +0,0 @@
|
||||
package devops
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/secretsmanager"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
)
|
||||
|
||||
// SecretManagerAutocertCache implements the autocert.Cache interface for AWS Secrets Manager that is used by Manager
|
||||
// to store and retrieve previously obtained certificates and other account data as opaque blobs.
|
||||
type SecretManagerAutocertCache struct {
|
||||
awsSession *session.Session
|
||||
log *log.Logger
|
||||
secretPrefix string
|
||||
cache autocert.Cache
|
||||
}
|
||||
|
||||
// NewSecretManagerAutocertCache provides the functionality to keep config files sync'd between running tasks and across deployments.
|
||||
func NewSecretManagerAutocertCache(log *log.Logger, awsSession *session.Session, secretPrefix string, cache autocert.Cache) (*SecretManagerAutocertCache, error) {
|
||||
return &SecretManagerAutocertCache{
|
||||
awsSession,
|
||||
log,
|
||||
secretPrefix,
|
||||
cache,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Get returns a certificate data for the specified key.
|
||||
// If there's no such key, Get returns ErrCacheMiss.
|
||||
func (c *SecretManagerAutocertCache) Get(ctx context.Context, key string) ([]byte, error) {
|
||||
|
||||
// Check short term cache.
|
||||
if c.cache != nil {
|
||||
v, err := c.cache.Get(ctx, key)
|
||||
if err != nil && err != autocert.ErrCacheMiss {
|
||||
return nil, errors.WithStack(err)
|
||||
} else if len(v) > 0 {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
|
||||
secretID := filepath.Join(c.secretPrefix, key)
|
||||
|
||||
// Load the secret by ID from Secrets Manager.
|
||||
res, err := SecretManagerGetString(c.awsSession, secretID)
|
||||
if err != nil {
|
||||
if err == ErrSecreteNotFound {
|
||||
return nil, autocert.ErrCacheMiss
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("AWS Secrets Manager : Secret %s found", secretID)
|
||||
|
||||
return []byte(res), nil
|
||||
}
|
||||
|
||||
// Put stores the data in the cache under the specified key.
|
||||
// Underlying implementations may use any data storage format,
|
||||
// as long as the reverse operation, Get, results in the original data.
|
||||
func (c *SecretManagerAutocertCache) Put(ctx context.Context, key string, data []byte) error {
|
||||
|
||||
secretID := filepath.Join(c.secretPrefix, key)
|
||||
|
||||
err := SecretManagerPutString(c.awsSession, secretID, string(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("AWS Secrets Manager : Secret %s updated", secretID)
|
||||
|
||||
if c.cache != nil {
|
||||
err = c.cache.Put(ctx, key, data)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes a certificate data from the cache under the specified key.
|
||||
// If there's no such key in the cache, Delete returns nil.
|
||||
func (c *SecretManagerAutocertCache) Delete(ctx context.Context, key string) error {
|
||||
|
||||
svc := secretsmanager.New(c.awsSession)
|
||||
|
||||
secretID := filepath.Join(c.secretPrefix, key)
|
||||
|
||||
// Create the new entry in AWS Secret Manager for the file.
|
||||
_, err := svc.DeleteSecret(&secretsmanager.DeleteSecretInput{
|
||||
SecretId: aws.String(secretID),
|
||||
|
||||
// (Optional) Specifies that the secret is to be deleted without any recovery
|
||||
// window. You can't use both this parameter and the RecoveryWindowInDays parameter
|
||||
// in the same API call.
|
||||
//
|
||||
// An asynchronous background process performs the actual deletion, so there
|
||||
// can be a short delay before the operation completes. If you write code to
|
||||
// delete and then immediately recreate a secret with the same name, ensure
|
||||
// that your code includes appropriate back off and retry logic.
|
||||
//
|
||||
// Use this parameter with caution. This parameter causes the operation to skip
|
||||
// the normal waiting period before the permanent deletion that AWS would normally
|
||||
// impose with the RecoveryWindowInDays parameter. If you delete a secret with
|
||||
// the ForceDeleteWithouRecovery parameter, then you have no opportunity to
|
||||
// recover the secret. It is permanently lost.
|
||||
ForceDeleteWithoutRecovery: aws.Bool(false),
|
||||
|
||||
// (Optional) Specifies the number of days that Secrets Manager waits before
|
||||
// it can delete the secret. You can't use both this parameter and the ForceDeleteWithoutRecovery
|
||||
// parameter in the same API call.
|
||||
//
|
||||
// This value can range from 7 to 30 days.
|
||||
RecoveryWindowInDays: aws.Int64(30),
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "autocert failed to delete secret %s", secretID)
|
||||
}
|
||||
|
||||
log.Printf("AWS Secrets Manager : Secret %s deleted", secretID)
|
||||
|
||||
if c.cache != nil {
|
||||
err = c.cache.Delete(ctx, key)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,163 +0,0 @@
|
||||
package devops
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/cloudfront"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func CloudFrontDistribution(awsSession *session.Session, s3Bucket string) (*cloudfront.DistributionSummary, error) {
|
||||
// Init new CloudFront using provided AWS session.
|
||||
cloudFront := cloudfront.New(awsSession)
|
||||
|
||||
// Loop through all the cloudfront distributions and find the one that matches the
|
||||
// S3 Bucket name. AWS doesn't current support multiple distributions per bucket
|
||||
// so this should always be a one to one match.
|
||||
var distribution *cloudfront.DistributionSummary
|
||||
err := cloudFront.ListDistributionsPages(&cloudfront.ListDistributionsInput{},
|
||||
func(page *cloudfront.ListDistributionsOutput, lastPage bool) bool {
|
||||
if page.DistributionList != nil {
|
||||
for _, v := range page.DistributionList.Items {
|
||||
if v.DomainName == nil || v.Origins == nil || v.Origins.Items == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, o := range v.Origins.Items {
|
||||
if o.DomainName == nil || !strings.HasPrefix(*o.DomainName, s3Bucket+".") {
|
||||
continue
|
||||
}
|
||||
|
||||
distribution = v
|
||||
break
|
||||
}
|
||||
|
||||
if distribution != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if distribution != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return !lastPage
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if distribution == nil {
|
||||
return nil, errors.Errorf("aws cloud front deployment does not exist for s3 bucket %s.", s3Bucket)
|
||||
}
|
||||
|
||||
return distribution, nil
|
||||
}
|
||||
|
||||
// NewAuthenticator creates an *Authenticator for use.
|
||||
// key expiration is optional to filter out old keys
|
||||
// It will error if:
|
||||
// - The aws session is nil.
|
||||
// - The aws s3 bucket is blank.
|
||||
func S3UrlFormatter(awsSession *session.Session, s3Bucket, s3KeyPrefix string, enableCloudFront bool) (func(string) string, error) {
|
||||
if awsSession == nil {
|
||||
return nil, errors.New("aws session cannot be nil")
|
||||
}
|
||||
|
||||
if s3Bucket == "" {
|
||||
return nil, errors.New("aws s3 bucket cannot be empty")
|
||||
}
|
||||
|
||||
var (
|
||||
baseS3Url string
|
||||
baseS3Origin string
|
||||
)
|
||||
if enableCloudFront {
|
||||
dist, err := CloudFrontDistribution(awsSession, s3Bucket)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Format the domain as an HTTPS url, "dzuyel7n94hma.cloudfront.net"
|
||||
baseS3Url = fmt.Sprintf("https://%s/", *dist.DomainName)
|
||||
|
||||
// The origin used for the cloudfront needs to be striped from the path
|
||||
// provided, the URL shouldn't have one, but "/public"
|
||||
baseS3Origin = *dist.Origins.Items[0].OriginPath
|
||||
} else {
|
||||
// The static files are upload to a specific prefix, so need to ensure
|
||||
// the path reference includes this prefix
|
||||
s3Path := filepath.Join(s3Bucket, s3KeyPrefix)
|
||||
|
||||
if *awsSession.Config.Region == "us-east-1" {
|
||||
// US East (N.Virginia) region endpoint, http://s3.amazonaws.com/bucket or
|
||||
// http://s3-external-1.amazonaws.com/bucket/
|
||||
baseS3Url = fmt.Sprintf("https://s3.amazonaws.com/%s/", s3Path)
|
||||
} else {
|
||||
// Region-specific endpoint, http://s3-aws-region.amazonaws.com/bucket
|
||||
baseS3Url = fmt.Sprintf("https://s3-%s.amazonaws.com/%s/", *awsSession.Config.Region, s3Path)
|
||||
}
|
||||
|
||||
baseS3Origin = s3KeyPrefix
|
||||
}
|
||||
|
||||
f := func(p string) string {
|
||||
return S3Url(baseS3Url, baseS3Origin, p)
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// S3Url formats a path to include either the S3 URL or a CloudFront
|
||||
// URL instead of serving the file from local file system.
|
||||
func S3Url(baseS3Url, baseS3Origin, p string) string {
|
||||
// If its already a URL, then don't format it
|
||||
if strings.HasPrefix(p, "http") {
|
||||
return p
|
||||
}
|
||||
|
||||
// Drop the beginning forward slash
|
||||
p = strings.TrimLeft(p, "/")
|
||||
|
||||
// In the case of cloudfront, the base URL may not match S3,
|
||||
// removing the origin from the path provided
|
||||
// ie. The s3 bucket + path of
|
||||
// gitw-corp-web.s3.amazonaws.com/public
|
||||
// maps to dzuyel7n94hma.cloudfront.net
|
||||
// where the path prefix of '/public' needs to be dropped.
|
||||
org := strings.Trim(baseS3Origin, "/")
|
||||
if org != "" {
|
||||
p = strings.Replace(p, org+"/", "", 1)
|
||||
}
|
||||
|
||||
// Parse out the querystring from the path
|
||||
var pathQueryStr string
|
||||
if strings.Contains(p, "?") {
|
||||
pts := strings.Split(p, "?")
|
||||
p = pts[0]
|
||||
if len(pts) > 1 {
|
||||
pathQueryStr = pts[1]
|
||||
}
|
||||
}
|
||||
|
||||
u, err := url.Parse(baseS3Url)
|
||||
if err != nil {
|
||||
return "?"
|
||||
}
|
||||
ldir := filepath.Base(u.Path)
|
||||
|
||||
if strings.HasPrefix(p, ldir) {
|
||||
p = strings.Replace(p, ldir+"/", "", 1)
|
||||
}
|
||||
|
||||
u.Path = filepath.Join(u.Path, p)
|
||||
u.RawQuery = pathQueryStr
|
||||
|
||||
return u.String()
|
||||
}
|
@ -1,242 +0,0 @@
|
||||
package devops
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/aws/aws-sdk-go/service/ecs"
|
||||
"github.com/aws/aws-sdk-go/service/route53"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// EcsServiceTaskInit allows newly spun up ECS Service Tasks to register their public IP with Route 53.
|
||||
func EcsServiceTaskInit(log *log.Logger, awsSession *session.Session) error {
|
||||
if awsSession == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ecsClusterName := os.Getenv("ECS_CLUSTER")
|
||||
ecsServiceName := os.Getenv("ECS_SERVICE")
|
||||
|
||||
// If both env variables are empty, this instance of the services is not running on AWS ECS.
|
||||
if ecsClusterName == "" && ecsServiceName == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var zoneArecNames = map[string][]string{}
|
||||
if v := os.Getenv("ROUTE53_ZONES"); v != "" {
|
||||
dat, err := base64.RawURLEncoding.DecodeString(v)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to base64 URL decode zones")
|
||||
}
|
||||
|
||||
err = json.Unmarshal(dat, &zoneArecNames)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to json decode zones - %s", string(dat))
|
||||
}
|
||||
}
|
||||
|
||||
var registerServiceTasks bool
|
||||
if v := os.Getenv("ROUTE53_UPDATE_TASK_IPS"); v != "" {
|
||||
var err error
|
||||
registerServiceTasks, err = strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to parse ROUTE53_UPDATE_TASK_IPS value '%s' to bool", v)
|
||||
}
|
||||
}
|
||||
|
||||
if registerServiceTasks {
|
||||
if err := RegisterEcsServiceTasksRoute53(log, awsSession, ecsClusterName, ecsServiceName, zoneArecNames); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EcsServiceTaskTaskShutdown allows ECS Service Tasks that are spinning down to deregister their public IP with Route 53.
|
||||
func EcsServiceTaskTaskShutdown(log *log.Logger, awsSession *session.Session) error {
|
||||
// TODO: Should lookup the IP for the current running task and remove that specific IP.
|
||||
// For now just run the init since it removes all non-running tasks.
|
||||
return EcsServiceTaskInit(log, awsSession)
|
||||
}
|
||||
|
||||
// RegisterEcsServiceTasksRoute53 registers the public IPs for a ECS Service Task with Route 53.
|
||||
func RegisterEcsServiceTasksRoute53(log *log.Logger, awsSession *session.Session, ecsClusterName, ecsServiceName string, zoneArecNames map[string][]string) error {
|
||||
if awsSession == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var networkInterfaceIds []string
|
||||
for a := 0; a <= 3; a++ {
|
||||
svc := ecs.New(awsSession)
|
||||
|
||||
serviceRes, err := svc.DescribeServices(&ecs.DescribeServicesInput{
|
||||
Cluster: aws.String(ecsClusterName),
|
||||
Services: []*string{aws.String(ecsServiceName)},
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to describe service '%s'", ecsServiceName)
|
||||
}
|
||||
service := serviceRes.Services[0]
|
||||
|
||||
servceTaskRes, err := svc.ListTasks(&ecs.ListTasksInput{
|
||||
Cluster: aws.String(ecsClusterName),
|
||||
ServiceName: aws.String(ecsServiceName),
|
||||
DesiredStatus: aws.String("RUNNING"),
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to list tasks for cluster '%s' service '%s'", ecsClusterName, ecsServiceName)
|
||||
}
|
||||
|
||||
if len(servceTaskRes.TaskArns) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
taskRes, err := svc.DescribeTasks(&ecs.DescribeTasksInput{
|
||||
Cluster: aws.String(ecsClusterName),
|
||||
Tasks: servceTaskRes.TaskArns,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to describe %d tasks for cluster '%s'", len(servceTaskRes.TaskArns), ecsClusterName)
|
||||
}
|
||||
|
||||
for _, t := range taskRes.Tasks {
|
||||
if *t.TaskDefinitionArn != *service.TaskDefinition || *t.DesiredStatus != "RUNNING" {
|
||||
continue
|
||||
}
|
||||
|
||||
if t.Attachments == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, c := range t.Containers {
|
||||
if *c.Name != ecsServiceName {
|
||||
continue
|
||||
}
|
||||
|
||||
if c.NetworkInterfaces == nil || len(c.NetworkInterfaces) == 0 || c.NetworkInterfaces[0].AttachmentId == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, a := range t.Attachments {
|
||||
if a.Details == nil || *a.Id != *c.NetworkInterfaces[0].AttachmentId {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ad := range a.Details {
|
||||
if ad.Name != nil && *ad.Name == "networkInterfaceId" {
|
||||
networkInterfaceIds = append(networkInterfaceIds, *ad.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(networkInterfaceIds) > 0 {
|
||||
log.Printf("Found %d network interface IDs.\n", len(networkInterfaceIds))
|
||||
break
|
||||
}
|
||||
|
||||
// Found no network interfaces, try again.
|
||||
log.Println("Found no network interfaces.")
|
||||
time.Sleep((time.Duration(a) * time.Second * 10) * time.Duration(a))
|
||||
}
|
||||
|
||||
if len(networkInterfaceIds) == 0 {
|
||||
return errors.New("Unable to update public IPs. No network interfaces found.")
|
||||
}
|
||||
|
||||
log.Println("Get public IPs for network interface IDs.")
|
||||
var publicIps []string
|
||||
for a := 0; a <= 3; a++ {
|
||||
svc := ec2.New(awsSession)
|
||||
|
||||
log.Println("\t\tDescribe network interfaces.")
|
||||
res, err := svc.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{
|
||||
NetworkInterfaceIds: aws.StringSlice(networkInterfaceIds),
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to describe network interfaces")
|
||||
}
|
||||
|
||||
for _, ni := range res.NetworkInterfaces {
|
||||
if ni.Association == nil || ni.Association.PublicIp == nil {
|
||||
continue
|
||||
}
|
||||
publicIps = append(publicIps, *ni.Association.PublicIp)
|
||||
}
|
||||
|
||||
if len(publicIps) > 0 {
|
||||
log.Printf("Found %d public IPs.\n", len(publicIps))
|
||||
break
|
||||
}
|
||||
|
||||
// Found no public IPs, try again.
|
||||
log.Println("Found no public IPs.")
|
||||
time.Sleep((time.Duration(a) * time.Second * 10) * time.Duration(a))
|
||||
}
|
||||
|
||||
if len(publicIps) > 0 {
|
||||
log.Println("Update public IPs for hosted zones.")
|
||||
|
||||
svc := route53.New(awsSession)
|
||||
|
||||
// Public IPs to be served as round robin.
|
||||
log.Printf("\tPublic IPs:\n")
|
||||
rrs := []*route53.ResourceRecord{}
|
||||
for _, ip := range publicIps {
|
||||
log.Printf("\t\t%s\n", ip)
|
||||
rrs = append(rrs, &route53.ResourceRecord{Value: aws.String(ip)})
|
||||
}
|
||||
|
||||
for zoneId, aNames := range zoneArecNames {
|
||||
log.Printf("\tChange zone '%s'.\n", zoneId)
|
||||
|
||||
input := &route53.ChangeResourceRecordSetsInput{
|
||||
ChangeBatch: &route53.ChangeBatch{
|
||||
Changes: []*route53.Change{},
|
||||
},
|
||||
HostedZoneId: aws.String(zoneId),
|
||||
}
|
||||
|
||||
// Add all the A record names with the same set of public IPs.
|
||||
addedNames := make(map[string]bool)
|
||||
for _, aName := range aNames {
|
||||
if addedNames[aName] {
|
||||
continue
|
||||
}
|
||||
log.Printf("\t\tAdd A record for '%s'.\n", aName)
|
||||
|
||||
input.ChangeBatch.Changes = append(input.ChangeBatch.Changes, &route53.Change{
|
||||
Action: aws.String("UPSERT"),
|
||||
ResourceRecordSet: &route53.ResourceRecordSet{
|
||||
Name: aws.String(aName),
|
||||
ResourceRecords: rrs,
|
||||
TTL: aws.Int64(60),
|
||||
Type: aws.String("A"),
|
||||
},
|
||||
})
|
||||
addedNames[aName] = true
|
||||
}
|
||||
|
||||
_, err := svc.ChangeResourceRecordSets(input)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to update A records for zone '%s'", zoneId)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("DNS entries updated.\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
package devops
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/secretsmanager"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var ErrSecreteNotFound = errors.New("secret not found")
|
||||
|
||||
// SecretManagerGetString loads a key from AWS Secrets Manager.
|
||||
// when UnrecognizedClientException its likely the AWS IAM permissions are not correct.
|
||||
func SecretManagerGetString(awsSession *session.Session, secretID string) (string, error) {
|
||||
|
||||
svc := secretsmanager.New(awsSession)
|
||||
|
||||
// Load the secret by ID from Secrets Manager.
|
||||
res, err := svc.GetSecretValue(&secretsmanager.GetSecretValueInput{
|
||||
SecretId: aws.String(secretID),
|
||||
})
|
||||
if err != nil {
|
||||
if aerr, ok := err.(awserr.Error); ok && (aerr.Code() == secretsmanager.ErrCodeResourceNotFoundException || aerr.Code() == secretsmanager.ErrCodeInvalidRequestException) {
|
||||
return "", ErrSecreteNotFound
|
||||
}
|
||||
|
||||
return "", errors.Wrapf(err, "failed to get value for secret id %s", secretID)
|
||||
}
|
||||
|
||||
return *res.SecretString, nil
|
||||
}
|
||||
|
||||
// SecretManagerPutString saves a value to AWS Secrets Manager.
|
||||
// If the secret ID does not exist, it will create it.
|
||||
// If the secret ID was deleted, it will restore it and then update the value.
|
||||
func SecretManagerPutString(awsSession *session.Session, secretID, value string) error {
|
||||
|
||||
svc := secretsmanager.New(awsSession)
|
||||
|
||||
// Create the new entry in AWS Secret Manager for the file.
|
||||
_, err := svc.CreateSecret(&secretsmanager.CreateSecretInput{
|
||||
Name: aws.String(secretID),
|
||||
SecretString: aws.String(value),
|
||||
})
|
||||
if err != nil {
|
||||
aerr, ok := err.(awserr.Error)
|
||||
|
||||
if ok && aerr.Code() == secretsmanager.ErrCodeInvalidRequestException {
|
||||
// InvalidRequestException: You can't create this secret because a secret with this
|
||||
// name is already scheduled for deletion.
|
||||
|
||||
// Restore secret after it was already previously deleted.
|
||||
_, err = svc.RestoreSecret(&secretsmanager.RestoreSecretInput{
|
||||
SecretId: aws.String(secretID),
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to restore secret %s", secretID)
|
||||
}
|
||||
|
||||
} else if !ok || aerr.Code() != secretsmanager.ErrCodeResourceExistsException {
|
||||
return errors.Wrapf(err, "failed to create secret %s", secretID)
|
||||
}
|
||||
|
||||
// If where was a resource exists error for create, then need to update the secret instead.
|
||||
_, err = svc.UpdateSecret(&secretsmanager.UpdateSecretInput{
|
||||
SecretId: aws.String(secretID),
|
||||
SecretString: aws.String(value),
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to update secret %s", secretID)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user