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.
|
// Append all the defined services to the config.
|
||||||
for _, n := range ServiceNames {
|
for _, n := range ServiceNames {
|
||||||
srv, err := NewService(n, cfg)
|
srv, err := NewService(log, n, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// Append all the defined functions to the config.
|
||||||
for _, n := range FunctionNames {
|
for _, n := range FunctionNames {
|
||||||
fn, err := NewFunction(n, cfg)
|
fn, err := NewFunction(log, n, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ var FunctionNames = []Function{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewFunction returns the *devdeploy.ProjectFunction.
|
// 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{
|
ctx := &devdeploy.ProjectFunction{
|
||||||
Name: fmt.Sprintf("%s-%s-%s", cfg.Env, cfg.ProjectName, funcName),
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
targetFunc, err := NewFunction(functionName, cfg)
|
targetFunc, err := NewFunction(log, functionName, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -269,7 +269,7 @@ func DeployFunctionForTargetEnv(log *log.Logger, awsCredentials devdeploy.AwsCre
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
targetFunc, err := NewFunction(functionName, cfg)
|
targetFunc, err := NewFunction(log, functionName, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ var ImageNames = []Image{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewImage returns the *devdeploy.ProjectImage.
|
// 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{
|
ctx := &devdeploy.ProjectImage{
|
||||||
Name: fmt.Sprintf("%s-%s-%s", cfg.Env, cfg.ProjectName, imageName),
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
targetImage, err := NewImage(imageName, cfg)
|
targetImage, err := NewImage(log, imageName, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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.
|
// 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)
|
ctx, err := NewServiceContext(serviceName, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -774,7 +774,7 @@ func BuildServiceForTargetEnv(log *log.Logger, awsCredentials devdeploy.AwsCrede
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
targetSvc, err := NewService(serviceName, cfg)
|
targetSvc, err := NewService(log, serviceName, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -830,7 +830,7 @@ func DeployServiceForTargetEnv(log *log.Logger, awsCredentials devdeploy.AwsCred
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
targetSvc, err := NewService(serviceName, cfg)
|
targetSvc, err := NewService(log, serviceName, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,6 @@ import (
|
|||||||
"geeks-accelerator/oss/saas-starter-kit/internal/account/account_preference"
|
"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/mid"
|
||||||
"geeks-accelerator/oss/saas-starter-kit/internal/platform/auth"
|
"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/flag"
|
||||||
"geeks-accelerator/oss/saas-starter-kit/internal/platform/notify"
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/notify"
|
||||||
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext"
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext"
|
||||||
@ -45,6 +44,7 @@ import (
|
|||||||
"github.com/kelseyhightower/envconfig"
|
"github.com/kelseyhightower/envconfig"
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/geeks-accelerator/oss/devops/pkg/devdeploy"
|
||||||
"golang.org/x/crypto/acme"
|
"golang.org/x/crypto/acme"
|
||||||
"golang.org/x/crypto/acme/autocert"
|
"golang.org/x/crypto/acme/autocert"
|
||||||
awstrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/aws/aws-sdk-go/aws"
|
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 AWS is enabled, check the Secrets Manager for the session key.
|
||||||
if awsSession != nil {
|
if awsSession != nil {
|
||||||
cfg.Project.SharedSecretKey, err = devops.SecretManagerGetString(awsSession, secretID)
|
cfg.Project.SharedSecretKey, err = devdeploy.SecretManagerGetString(awsSession, secretID)
|
||||||
if err != nil && errors.Cause(err) != devops.ErrSecreteNotFound {
|
if err != nil && errors.Cause(err) != devdeploy.ErrSecreteNotFound {
|
||||||
log.Fatalf("main : Session : %+v", err)
|
log.Fatalf("main : Session : %+v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -314,7 +314,7 @@ func main() {
|
|||||||
cfg.Project.SharedSecretKey = string(securecookie.GenerateRandomKey(32))
|
cfg.Project.SharedSecretKey = string(securecookie.GenerateRandomKey(32))
|
||||||
|
|
||||||
if awsSession != nil {
|
if awsSession != nil {
|
||||||
err = devops.SecretManagerPutString(awsSession, secretID, cfg.Project.SharedSecretKey)
|
err = devdeploy.SecretManagerPutString(awsSession, secretID, cfg.Project.SharedSecretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("main : Session : %+v", err)
|
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.
|
// 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 {
|
if err != nil {
|
||||||
log.Fatalf("main : Ecs Service Task init : %+v", err)
|
log.Fatalf("main : Ecs Service Task init : %+v", err)
|
||||||
}
|
}
|
||||||
@ -588,7 +588,7 @@ func main() {
|
|||||||
// Local file cache to reduce requests hitting Secret Manager.
|
// Local file cache to reduce requests hitting Secret Manager.
|
||||||
localCache := autocert.DirCache(os.TempDir())
|
localCache := autocert.DirCache(os.TempDir())
|
||||||
|
|
||||||
cache, err := devops.NewSecretManagerAutocertCache(log, awsSession, secretPrefix, localCache)
|
cache, err := devdeploy.NewSecretManagerAutocertCache(log, awsSession, secretPrefix, localCache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("main : HTTPS : %+v", err)
|
log.Fatalf("main : HTTPS : %+v", err)
|
||||||
}
|
}
|
||||||
@ -621,7 +621,7 @@ func main() {
|
|||||||
log.Printf("main : %v : Start shutdown..", sig)
|
log.Printf("main : %v : Start shutdown..", sig)
|
||||||
|
|
||||||
// Ensure the public IP address for the task is removed from Route53.
|
// 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 {
|
if err != nil {
|
||||||
log.Fatalf("main : Ecs Service Task shutdown : %+v", err)
|
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/geonames"
|
||||||
"geeks-accelerator/oss/saas-starter-kit/internal/mid"
|
"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/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/flag"
|
||||||
img_resize "geeks-accelerator/oss/saas-starter-kit/internal/platform/img-resize"
|
img_resize "geeks-accelerator/oss/saas-starter-kit/internal/platform/img-resize"
|
||||||
"geeks-accelerator/oss/saas-starter-kit/internal/platform/notify"
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/notify"
|
||||||
@ -52,6 +51,7 @@ import (
|
|||||||
"github.com/kelseyhightower/envconfig"
|
"github.com/kelseyhightower/envconfig"
|
||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"gitlab.com/geeks-accelerator/oss/devops/pkg/devdeploy"
|
||||||
"golang.org/x/crypto/acme"
|
"golang.org/x/crypto/acme"
|
||||||
"golang.org/x/crypto/acme/autocert"
|
"golang.org/x/crypto/acme/autocert"
|
||||||
awstrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/aws/aws-sdk-go/aws"
|
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 AWS is enabled, check the Secrets Manager for the session key.
|
||||||
if awsSession != nil {
|
if awsSession != nil {
|
||||||
cfg.Project.SharedSecretKey, err = devops.SecretManagerGetString(awsSession, secretID)
|
cfg.Project.SharedSecretKey, err = devdeploy.SecretManagerGetString(awsSession, secretID)
|
||||||
if err != nil && errors.Cause(err) != devops.ErrSecreteNotFound {
|
if err != nil && errors.Cause(err) != devdeploy.ErrSecreteNotFound {
|
||||||
log.Fatalf("main : Session : %+v", err)
|
log.Fatalf("main : Session : %+v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -317,7 +317,7 @@ func main() {
|
|||||||
cfg.Project.SharedSecretKey = string(securecookie.GenerateRandomKey(32))
|
cfg.Project.SharedSecretKey = string(securecookie.GenerateRandomKey(32))
|
||||||
|
|
||||||
if awsSession != nil {
|
if awsSession != nil {
|
||||||
err = devops.SecretManagerPutString(awsSession, secretID, cfg.Project.SharedSecretKey)
|
err = devdeploy.SecretManagerPutString(awsSession, secretID, cfg.Project.SharedSecretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("main : Session : %+v", err)
|
log.Fatalf("main : Session : %+v", err)
|
||||||
}
|
}
|
||||||
@ -511,7 +511,7 @@ func main() {
|
|||||||
// a publicly available image URL.
|
// a publicly available image URL.
|
||||||
var staticS3UrlFormatter func(string) string
|
var staticS3UrlFormatter func(string) string
|
||||||
if cfg.Service.StaticFiles.S3Enabled || cfg.Service.StaticFiles.CloudFrontEnabled || cfg.Service.StaticFiles.ImgResizeEnabled {
|
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 {
|
if err != nil {
|
||||||
log.Fatalf("main : S3UrlFormatter failed : %+v", err)
|
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.
|
// 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 {
|
if err != nil {
|
||||||
log.Fatalf("main : Ecs Service Task init : %+v", err)
|
log.Fatalf("main : Ecs Service Task init : %+v", err)
|
||||||
}
|
}
|
||||||
@ -1058,7 +1058,7 @@ func main() {
|
|||||||
// Local file cache to reduce requests hitting Secret Manager.
|
// Local file cache to reduce requests hitting Secret Manager.
|
||||||
localCache := autocert.DirCache(os.TempDir())
|
localCache := autocert.DirCache(os.TempDir())
|
||||||
|
|
||||||
cache, err := devops.NewSecretManagerAutocertCache(log, awsSession, secretPrefix, localCache)
|
cache, err := devdeploy.NewSecretManagerAutocertCache(log, awsSession, secretPrefix, localCache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("main : HTTPS : %+v", err)
|
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