Skip to content

Commit

Permalink
Merge branch 'main' into dependabot/go_modules/shared/github.com/aws/…
Browse files Browse the repository at this point in the history
…aws-sdk-go-v2/config-1.19.0
  • Loading branch information
alexlokshin-czi committed Oct 25, 2023
2 parents f42ee9d + f2065cd commit 40d5ab9
Show file tree
Hide file tree
Showing 29 changed files with 332 additions and 154 deletions.
16 changes: 8 additions & 8 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
{
"cli": "0.113.1",
"api": "0.113.1",
"shared": "0.113.1",
"terraform/provider": "0.113.1",
"cli": "0.114.0",
"api": "0.114.0",
"shared": "0.114.0",
"terraform/provider": "0.114.0",
"terraform/modules/happy-dns-ecs": "1.5.0",
"terraform/modules/happy-env-ecs": "3.3.0",
"terraform/modules/happy-env-eks": "4.12.2",
"terraform/modules/happy-github-ci-role": "1.5.0",
"terraform/modules/happy-route53": "1.3.0",
"terraform/modules/happy-service-ecs": "2.1.0",
"terraform/modules/happy-service-eks": "3.17.2",
"terraform/modules/happy-stack-ecs": "2.1.0",
"terraform/modules/happy-stack-eks": "4.20.0",
"terraform/modules/happy-service-eks": "3.18.0",
"terraform/modules/happy-stack-ecs": "2.2.0",
"terraform/modules/happy-stack-eks": "4.21.0",
"terraform/modules/happy-tfe-okta-app": "3.0.0",
"terraform/modules/happy-tfe-user": "1.3.0",
"terraform/modules/happy-ingress-eks": "2.9.0",
"hvm": "0.113.1",
"hvm": "0.114.0",
"hapi-proto": "0.1.0",
"terraform/modules/happy-cloudfront": "1.0.0",
"helm-charts/charts/stack": "0.1.0"
Expand Down
7 changes: 7 additions & 0 deletions api/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## [0.114.0](https://github.com/chanzuckerberg/happy/compare/api-v0.113.1...api-v0.114.0) (2023-10-24)


### Miscellaneous Chores

* **api:** Synchronize happy platform versions

## [0.113.1](https://github.com/chanzuckerberg/happy/compare/api-v0.113.0...api-v0.113.1) (2023-10-18)


Expand Down
12 changes: 12 additions & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog
<!-- bump -->
## [0.114.0](https://github.com/chanzuckerberg/happy/compare/cli-v0.113.1...cli-v0.114.0) (2023-10-24)


### Features

* fixed batch delete happy stack ([#2613](https://github.com/chanzuckerberg/happy/issues/2613)) ([21f927b](https://github.com/chanzuckerberg/happy/commit/21f927b9ac095bb2645b6e2a51d914cbfe1a265c))


### Bug Fixes

* sync go versions ([#2635](https://github.com/chanzuckerberg/happy/issues/2635)) ([e479c13](https://github.com/chanzuckerberg/happy/commit/e479c136a1f2cf83b4e6b430097e74d5512f31ee))

## [0.113.1](https://github.com/chanzuckerberg/happy/compare/cli-v0.113.0...cli-v0.113.1) (2023-10-18)


Expand Down
2 changes: 1 addition & 1 deletion cli/COVERAGE
Original file line number Diff line number Diff line change
@@ -1 +1 @@
22.97
22.65
192 changes: 99 additions & 93 deletions cli/cmd/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,16 @@ var deleteCmd = &cobra.Command{
Short: "Delete an existing stack",
Long: "Delete the stack with the given name.",
SilenceUsage: true,
RunE: runDelete,
RunE: func(cmd *cobra.Command, args []string) error {
for _, stackName := range args {
log.Infof("Deleting Happy Stack %s\n", stackName)
if err := runDelete(cmd, stackName); err != nil {
log.Errorf("Unable to delete Happy Stack %s: %s", stackName, err.Error())
continue
}
}
return nil
},
PreRunE: happyCmd.Validate(
cobra.MinimumNArgs(1),
happyCmd.IsStackNameDNSCharset,
Expand All @@ -46,115 +55,112 @@ var deleteCmd = &cobra.Command{
),
}

func runDelete(cmd *cobra.Command, args []string) error {
for _, stackName := range args {
happyClient, err := makeHappyClient(cmd, sliceName, stackName, []string{tag}, createTag)
if err != nil {
return errors.Wrap(err, "unable to initialize the happy client")
}
ctx := context.WithValue(cmd.Context(), options.DryRunKey, dryRun)
message := workspace_repo.Message(fmt.Sprintf("Happy %s Delete Stack [%s]", util.GetVersion().Version, stackName))
err = validate(
validateGitTree(happyClient.HappyConfig.GetProjectRoot()),
validateStackNameAvailable(ctx, happyClient.StackService, stackName, force),
validateStackExists(ctx, stackName, happyClient, message),
validateTFEBackLog(ctx, happyClient.AWSBackend),
)
if err != nil {
log.Warnf("failed one of the happy client validations %s", err.Error())
continue
}

stacks, err := happyClient.StackService.GetStacks(ctx)
if err != nil {
return err
}

stack, ok := stacks[stackName]
if !ok {
return errors.Wrapf(err, "stack %s not found", stackName)
}

// Run all necessary tasks before deletion
taskOrchestrator := orchestrator.
NewOrchestrator().
WithHappyConfig(happyClient.HappyConfig).
WithBackend(happyClient.AWSBackend)
err = taskOrchestrator.RunTasks(ctx, stack, backend.TaskTypeDelete)
if err != nil {
if !force {
if !diagnostics.IsInteractiveContext(ctx) {
return err
}
proceed := false
prompt := &survey.Confirm{
Message: fmt.Sprintf("Error running tasks while trying to delete %s (%s); Continue? ", stackName, err.Error()),
}
err = survey.AskOne(prompt, &proceed)
if err != nil {
return errors.Wrapf(err, "failed to ask for confirmation")
}
if !proceed {
return err
}
}
}

hasState, err := happyClient.StackService.HasState(ctx, stackName)
if err != nil {
return errors.Wrapf(err, "unable to determine whether the stack has state")
}

runopts := workspace_repo.Message(fmt.Sprintf("Happy %s Delete Stack [%s]", util.GetVersion().Version, stackName))
if !hasState {
log.Info("No state found for stack, workspace will be removed")
return happyClient.StackService.Remove(ctx, stackName, runopts)
}
func runDelete(cmd *cobra.Command, stackName string) error {
happyClient, err := makeHappyClient(cmd, sliceName, stackName, []string{tag}, createTag)
if err != nil {
return errors.Wrap(err, "unable to initialize the happy client")
}
ctx := context.WithValue(cmd.Context(), options.DryRunKey, dryRun)
message := workspace_repo.Message(fmt.Sprintf("Happy %s Delete Stack [%s]", util.GetVersion().Version, stackName))
err = validate(
validateGitTree(happyClient.HappyConfig.GetProjectRoot()),
validateStackNameAvailable(ctx, happyClient.StackService, stackName, force),
validateStackExists(ctx, stackName, happyClient, message),
validateTFEBackLog(ctx, happyClient.AWSBackend),
)
if err != nil {
log.Warnf("failed one of the happy client validations %s", err.Error())
return err
}

// Destroy the stack
destroySuccess := true
waitopts := options.WaitOptions{
StackName: stackName,
Orchestrator: taskOrchestrator,
Services: happyClient.HappyConfig.GetServices(),
}
stacks, err := happyClient.StackService.GetStacks(ctx)
if err != nil {
return err
}

tfDirPath := happyClient.HappyConfig.TerraformDirectory()
happyProjectRoot := happyClient.HappyConfig.GetProjectRoot()
srcDir := filepath.Join(happyProjectRoot, tfDirPath)
if err = stack.Destroy(ctx, srcDir, waitopts, runopts); err != nil {
// log error and set a flag, but do not return
log.Errorf("Failed to destroy stack: '%s'", err)
destroySuccess = false
}
stack, ok := stacks[stackName]
if !ok {
return errors.Wrapf(err, "stack %s not found", stackName)
}

doRemoveWorkspace := false
if !destroySuccess {
// Run all necessary tasks before deletion
taskOrchestrator := orchestrator.
NewOrchestrator().
WithHappyConfig(happyClient.HappyConfig).
WithBackend(happyClient.AWSBackend)
err = taskOrchestrator.RunTasks(ctx, stack, backend.TaskTypeDelete)
if err != nil {
if !force {
if !diagnostics.IsInteractiveContext(ctx) {
return errors.Errorf("Error while destroying %s; resources might remain, aborting workspace removal in non-interactive mode.", stackName)
return err
}

proceed := false
prompt := &survey.Confirm{Message: fmt.Sprintf("Error while destroying %s; resources might remain. Continue to remove workspace? ", stackName)}
prompt := &survey.Confirm{
Message: fmt.Sprintf("Error running tasks while trying to delete %s (%s); Continue? ", stackName, err.Error()),
}
err = survey.AskOne(prompt, &proceed)
if err != nil {
return errors.Wrapf(err, "failed to ask for confirmation")
}

if !proceed {
return err
}
}
}

hasState, err := happyClient.StackService.HasState(ctx, stackName)
if err != nil {
return errors.Wrapf(err, "unable to determine whether the stack has state")
}

runopts := workspace_repo.Message(fmt.Sprintf("Happy %s Delete Stack [%s]", util.GetVersion().Version, stackName))
if !hasState {
log.Info("No state found for stack, workspace will be removed")
return happyClient.StackService.Remove(ctx, stackName, runopts)
}

// Destroy the stack
destroySuccess := true
waitopts := options.WaitOptions{
StackName: stackName,
Orchestrator: taskOrchestrator,
Services: happyClient.HappyConfig.GetServices(),
}

tfDirPath := happyClient.HappyConfig.TerraformDirectory()
happyProjectRoot := happyClient.HappyConfig.GetProjectRoot()
srcDir := filepath.Join(happyProjectRoot, tfDirPath)
if err = stack.Destroy(ctx, srcDir, waitopts, runopts); err != nil {
// log error and set a flag, but do not return
log.Errorf("Failed to destroy stack: '%s'", err)
destroySuccess = false
}

doRemoveWorkspace := false
if !destroySuccess {
if !diagnostics.IsInteractiveContext(ctx) {
return errors.Errorf("Error while destroying %s; resources might remain, aborting workspace removal in non-interactive mode.", stackName)
}

doRemoveWorkspace = true
proceed := false
prompt := &survey.Confirm{Message: fmt.Sprintf("Error while destroying %s; resources might remain. Continue to remove workspace? ", stackName)}
err = survey.AskOne(prompt, &proceed)
if err != nil {
return errors.Wrapf(err, "failed to ask for confirmation")
}

// Remove the stack from state
// TODO: are these the right error messages?
if destroySuccess || doRemoveWorkspace {
return happyClient.StackService.Remove(ctx, stackName, runopts)
} else {
log.Warnf("Stack %s was not deleted fully", stackName)
if !proceed {
return err
}

doRemoveWorkspace = true
}

// Remove the stack from state
// TODO: are these the right error messages?
if destroySuccess || doRemoveWorkspace {
return happyClient.StackService.Remove(ctx, stackName, runopts)
}
log.Warnf("Stack %s was not deleted fully", stackName)
return nil
}
51 changes: 26 additions & 25 deletions cli/cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,49 +301,50 @@ func validateConfigurationIntegirty(ctx context.Context, slice string, happyClie
logrus.Debug("Scheduling validateConfigurationIntegirty()")
return func() error {
logrus.Debug("Running validateConfigurationIntegirty()")
// These services are configured in docker-compose.yml, and have their containers built
availableServices, err := happyClient.ArtifactBuilder.GetServices(ctx)

// Happy configuration is spread across these files:
// * .happy/config.json defines environments, specifies services, slices, features and tasks
// * docker-compose.yml defines services and their build configuration
// * terraform code from .happy/terraform/envs/<ENVNAME>/*.tf references services and their settings

// All services referenced through TF code must be present in config.json. All services listed in config.json
// must be declared in docker-compose.yml and have a build section.

// These services are configured in docker-compose.yml
composeServices, err := happyClient.ArtifactBuilder.GetAllServices(ctx)
if err != nil {
return errors.Wrap(err, "unable to get available services")
}

// NOTE: availableServices will only contain the services that are a part of the slice.
// That means we have to iterate over those first to make sure they are all in the config.json and
// not the other way around.
// ConfigServices are configured in config.json, and are a subset of the services in docker-compose.yml.
// Every service from config.json must be present in docker-compose.yml and must have a build section.
configServices := happyClient.HappyConfig.GetServices()
ss := sets.NewStringSet().Add(configServices...)
for serviceName, service := range availableServices {
// ignore services that don't have a build section
// as these are not deployable services
if service.Build == nil {
continue
}
ok := ss.ContainsElement(serviceName)
for _, serviceName := range configServices {
service, ok := composeServices[serviceName]
if !ok {
return errors.Errorf("service %s was referenced in docker-compose.yml, but not referenced in .happy/config.json services array", serviceName)
return errors.Errorf("service '%s' is not configured in docker-compose.yml, but referenced in your .happy/config.json services array", serviceName)
}
if service.Build == nil {
return errors.Errorf("service '%s' is not configured to be built in docker-compose.yml, but referenced in your .happy/config.json services array", serviceName)
}
}

// These services are referenced in terraform code for the environment
// These services are referenced in terraform code for the environment, and must be present in config.json
// (and docker-compose.yml as well -- see the check above).
srcDir := filepath.Join(happyClient.HappyConfig.GetProjectRoot(), happyClient.HappyConfig.TerraformDirectory())
deployedServices, err := tf.NewTfParser().ParseServices(srcDir)
if err != nil {
return errors.Wrap(err, "unable to parse terraform code")
}
for service := range deployedServices {
if _, ok := availableServices[service]; !ok {
return errors.Errorf("service %s is not configured in docker-compose.yml, but referenced in your terraform code", service)
}
found := false
for _, configuredService := range happyClient.HappyConfig.GetServices() {
if service == configuredService {
found = true
break
}
for serviceName := range deployedServices {
if _, ok := composeServices[serviceName]; !ok {
return errors.Errorf("service '%s' is not configured in docker-compose.yml, but referenced in your terraform code", serviceName)
}
found := ss.ContainsElement(serviceName)

if !found {
return errors.Errorf("service %s is not configured in ./happy/config.json, but referenced in your terraform code", service)
return errors.Errorf("service %s is not configured in ./happy/config.json, but referenced in your terraform code", serviceName)
}
}

Expand Down
2 changes: 1 addition & 1 deletion cli/pkg/artifact_builder/COVERAGE
Original file line number Diff line number Diff line change
@@ -1 +1 @@
58.8
56.7
11 changes: 11 additions & 0 deletions cli/pkg/artifact_builder/artifact_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -595,3 +595,14 @@ func (ab ArtifactBuilder) GetServices(ctx context.Context) (map[string]ServiceCo
}
return config.Services, nil
}

func (ab ArtifactBuilder) GetAllServices(ctx context.Context) (map[string]ServiceConfig, error) {
bc := ab.config.Clone()
bc.configData = nil
bc.Profile = nil
config, err := bc.GetConfigData(ctx)
if err != nil {
return nil, errors.Wrap(err, "unable to get config data")
}
return config.Services, nil
}
1 change: 1 addition & 0 deletions cli/pkg/artifact_builder/artifact_builder_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type ArtifactBuilderIface interface {
Pull(ctx context.Context, stackName, tag string) (map[string]string, error)
BuildAndPush(ctx context.Context) error
GetServices(ctx context.Context) (map[string]ServiceConfig, error)
GetAllServices(ctx context.Context) (map[string]ServiceConfig, error)
}

func CreateArtifactBuilder(ctx context.Context) ArtifactBuilderIface {
Expand Down
Loading

0 comments on commit 40d5ab9

Please sign in to comment.