Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Terraform multi-state #472

Merged
merged 22 commits into from
Sep 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9901f46
added a simple example of a multi-state monorepo
psihachina Sep 26, 2022
296746b
added `depends_on` option for terraform struct
psihachina Sep 26, 2022
66c47bf
added `findDuplicates` function
psihachina Sep 27, 2022
cee1348
added `name` parameter for `GenerateTerraformFiles` function
psihachina Sep 27, 2022
9f9c7ea
added `GetStates` function
psihachina Sep 27, 2022
c56e7d4
added `ize up apps` command
psihachina Sep 27, 2022
ee7414c
added an exception for the `infra` state
psihachina Sep 27, 2022
66aa491
added support multiple terraform states & configurations
psihachina Sep 28, 2022
44c11ea
fixed terraform e2e test
psihachina Sep 28, 2022
dda6448
updated .gitignore
psihachina Sep 28, 2022
6487177
updated ize.toml in examples/multistate-monorepo
psihachina Sep 28, 2022
32be2ee
renamed `statePath` to `stackPath`
psihachina Sep 28, 2022
a996082
added `explain` flag
psihachina Sep 29, 2022
5e71166
added call `terraform init` before `terraform destroy` in `ize down`/…
psihachina Sep 29, 2022
01c8478
changed logic of generating terraform state key
psihachina Sep 30, 2022
33263d8
updated examples
psihachina Sep 30, 2022
3afa764
removed `infra` as default stack
psihachina Sep 30, 2022
4b28a58
updated sls monorepo example
psihachina Sep 30, 2022
ef786cb
Merge pull request #475 from hazelops/IZE-599-ize-infra-down-doesnt-r…
psihachina Sep 30, 2022
04a26c8
Merge branch 'IZE-582-IZE-361-IZE-446' into IZE-516-ize-up-infra-expl…
psihachina Sep 30, 2022
96cc544
Merge pull request #474 from hazelops/IZE-516-ize-up-infra-explain-ba…
psihachina Sep 30, 2022
fa1d3a9
updated ize.toml
psihachina Sep 30, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ ize
.terraform
examples/**/.ize/env/*
!examples/**/.ize/env/testnut
backend.tf
terraform.tfvars
Empty file.
11 changes: 11 additions & 0 deletions examples/multistate-monorepo/.ize/env/testnut/api/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
variable "env" {}
variable "namespace" {}
variable "aws_profile" {}
variable "aws_region" {}
variable "ssh_public_key" {}
variable "ec2_key_pair_name" {}

locals {
env = var.env
namespace = var.namespace
}
60 changes: 60 additions & 0 deletions examples/multistate-monorepo/.ize/env/testnut/ize.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
aws_region = "us-east-1" # (required) AWS Region of this environment should be specified here. Can be overriden by AWS_PROFILE env var or --aws-region flag.
namespace = "testnut" # (required) Namespace of the project can be specified here. It is used as a base for all naming. It can be overridden by NAMESPACE env var or --namespace flag.
terraform_version = "1.2.6" # (optional) Terraform version can be set here. 1.1.3 by default
# prefer_runtime = "" # (optional) Prefer a specific runtime. (native or docker) (default 'native')
# tag = "" # (optional) Tag can be set statically. Normally it is being constructed automatically based on the git revision.
# plain_text = false # (optional) Plain text output can be enabled here. Default is false. Can be overridden by IZE_PLAIN_TEXT env var or --plain-text flag.
# env = "dev" # (optional) Environment name can be specified here. Normally it should be passed via `ENV` variable or --env flag.
# env_dir = "" # (optional) Environment directory can be specified here. Normally it's calculated automatically based on the directory structure convention.
# docker_registry = "" # (optional) Docker registry can be set here. By default it uses ECR repo with the name of the service.
# tf_log_path = "" # (optional) TF_LOG_PATH can be set here.
# custom_prompt = false # (optional) Custom prompt can be enabled here for all console connections. Default: false.
# aws_profile = "" # (optional) AWS Profile can be specified here (but normally it's specified via AWS_PROFILE env var)
# log_level = "" # (optional) Log level can be specified here. Possible levels: info, debug, trace, panic, warn, error, fatal(default). Can be overridden via IZE_LOG_LEVEL env var or via --log-level flag.
# ize_dir = "" # (optional) Ize directory can be specified here. Normally it's assumed to be .infra or .ize in the current repo.
# apps_path = "" # (optional) Path to apps directory can be set. By default apps are searched in 'apps' and 'projects' directories. This is needed in case your repo structure is not purely ize-structured (let's say you have 'src' repo in your dotnet app, as an example)
# root_dir = "" # (optional) Project directory can be set here. By default it's the current directory, but in case you prefer to run ize from the outside of repo it may be useful (uncommon).
# tf_log = "" # (optional) Terraform TF_LOG can be set here. Can be TRACE, DEBUG, INFO, WARN or ERROR.
# config_file = "" # (optional) Path to ize.toml config file can be specified, but normally it's read from the environment's directory automatically.
# home = "" # (optional) User home directory can be specified here. Normally $HOME is used.

[terraform.infra]
aws_region = "us-east-1" # (optional) Terraform-specific AWS Region of this environment should be specified here. Normally global AWS_REGION is used.
# aws_profile = "" # (optional) Terraform-specific AWS profile (optional) can be specified here (but normally it should be inherited from a global AWS_PROFILE).
# version = "" # (optional) Terraform version can be set here. 1.1.3 by default.
# state_bucket_region = "" # (optional) Terraform state bucket region can be specified here. Normally AWS_REGION is used here. Can be overriden via env vars or flags.
# state_bucket_name = "" # (optional) Terraform state bucket name can be specified here. Normally it's generated and defaults to <NAMESPACE>-tf-state
# root_domain_name = "" # (optional) Root domain name can be set here. This is the main domain that will be passed to the terraform. Generally if your app lives at 'api.dev.nutcorp.net' the root domain is `nutcorp.net`

[terraform.api]
# aws_profile = "" # (optional) Terraform-specific AWS profile (optional) can be specified here (but normally it should be inherited from a global AWS_PROFILE).
# version = "" # (optional) Terraform version can be set here. 1.1.3 by default.
# state_bucket_region = "" # (optional) Terraform state bucket region can be specified here. Normally AWS_REGION is used here. Can be overriden via env vars or flags.
# state_bucket_name = "" # (optional) Terraform state bucket name can be specified here. Normally it's generated and defaults to <NAMESPACE>-tf-state
# root_domain_name = "" # (optional) Root domain name can be set here. This is the main domain that will be passed to the terraform. Generally if your app lives at 'api.dev.nutcorp.net' the root domain is `nutcorp.net`
depends_on = ["vpc"]

[terraform.vpc]
# aws_profile = "" # (optional) Terraform-specific AWS profile (optional) can be specified here (but normally it should be inherited from a global AWS_PROFILE).
# version = "" # (optional) Terraform version can be set here. 1.1.3 by default.
# state_bucket_region = "" # (optional) Terraform state bucket region can be specified here. Normally AWS_REGION is used here. Can be overriden via env vars or flags.
# state_bucket_name = "" # (optional) Terraform state bucket name can be specified here. Normally it's generated and defaults to <NAMESPACE>-tf-state
# root_domain_name = "" # (optional) Root domain name can be set here. This is the main domain that will be passed to the terraform. Generally if your app lives at 'api.dev.nutcorp.net' the root domain is `nutcorp.net`
depends_on = ["infra"]

# [ecs.<app>]
# timeout = "" # (optional) ECS deployment timeout can be specified here.
# docker_registry = "" # (optional) Docker registry can be set here. By default it uses ECR repo with the name of the service.
# skip_deploy = false # skip deploy app
# path = "" # (optional) Path to ecs app folder can be specified here. By default it's derived from apps path and app name.
# unsafe = false # (optional) Enables unsafe mode that increases deploy time on a cost of shorter healtchecks.
# image = "" # (optional) Docker image can be specified here. By default it's derived from the app name.
# cluster = "" # (optional) ECS cluster can be specified here. By default it's derived from env & namespace
# task_definition_revision = "" # (optional) Task definition revision can be specified here. By default latest revision is used to perform a deployment. Normally this parameter can be used via cli during specific deployment needs.

# [serverless.<name>]
# node_version = "16" # (optional) Node version that will be used by nvm can be specified here that. Default is v14.
# path = "" # (optional) Path to the serverless app directory can be specified here. Normally it's derived from app directory and app name.
# sls_node_modules_cache_mount = "" # (optional) SLS node_modules cache mount path can be specified here. It's used to store cache during CI/CD process.
# file = "" # (optional) Path to serverless file can be specified here. Normally it's serverless.yml in the app directory.
# create_domain = false # (optional) Create domain for the serverless domain manager during the deployment.
Empty file.
11 changes: 11 additions & 0 deletions examples/multistate-monorepo/.ize/env/testnut/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
variable "env" {}
variable "namespace" {}
variable "aws_profile" {}
variable "aws_region" {}
variable "ssh_public_key" {}
variable "ec2_key_pair_name" {}

locals {
env = var.env
namespace = var.namespace
}
Empty file.
11 changes: 11 additions & 0 deletions examples/multistate-monorepo/.ize/env/testnut/vpc/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
variable "env" {}
variable "namespace" {}
variable "aws_profile" {}
variable "aws_region" {}
variable "ssh_public_key" {}
variable "ec2_key_pair_name" {}

locals {
env = var.env
namespace = var.namespace
}
4 changes: 2 additions & 2 deletions examples/sls-apps-monorepo/.ize/env/testnut/ize.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ terraform_version = "1.2.6" # (optional) Terraform version can be se
# config_file = "" # (optional) Path to ize.toml config file can be specified, but normally it's read from the environment's directory automatically.
# home = "" # (optional) User home directory can be specified here. Normally $HOME is used.

# [terraform.infra]
# aws_region = "" # (optional) Terraform-specific AWS Region of this environment should be specified here. Normally global AWS_REGION is used.
[terraform.infra]
aws_region = "us-east-1" # (optional) Terraform-specific AWS Region of this environment should be specified here. Normally global AWS_REGION is used.
# aws_profile = "" # (optional) Terraform-specific AWS profile (optional) can be specified here (but normally it should be inherited from a global AWS_PROFILE).
# version = "" # (optional) Terraform version can be set here. 1.1.3 by default.
# state_bucket_region = "" # (optional) Terraform state bucket region can be specified here. Normally AWS_REGION is used here. Can be overriden via env vars or flags.
Expand Down
124 changes: 55 additions & 69 deletions internal/commands/down.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/pterm/pterm"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"time"
)

type DownOptions struct {
Expand Down Expand Up @@ -114,20 +115,21 @@ func (o *DownOptions) Complete(cmd *cobra.Command, args []string) error {
}

if o.Config.Terraform == nil {
o.Config.Terraform = map[string]*config.Terraform{}
o.Config.Terraform["infra"] = &config.Terraform{}
return fmt.Errorf("you must specify at least one terraform stack in ize.toml")
}

if len(o.Config.Terraform["infra"].AwsProfile) == 0 {
o.Config.Terraform["infra"].AwsProfile = o.Config.AwsProfile
}
if _, ok := o.Config.Terraform["infra"]; ok {
if len(o.Config.Terraform["infra"].AwsProfile) == 0 {
o.Config.Terraform["infra"].AwsProfile = o.Config.AwsProfile
}

if len(o.Config.Terraform["infra"].AwsRegion) == 0 {
o.Config.Terraform["infra"].AwsRegion = o.Config.AwsRegion
}
if len(o.Config.Terraform["infra"].AwsRegion) == 0 {
o.Config.Terraform["infra"].AwsRegion = o.Config.AwsRegion
}

if len(o.Config.Terraform["infra"].Version) == 0 {
o.Config.Terraform["infra"].Version = o.Config.TerraformVersion
if len(o.Config.Terraform["infra"].Version) == 0 {
o.Config.Terraform["infra"].Version = o.Config.TerraformVersion
}
}
} else {
if err := requirements.CheckRequirements(requirements.WithIzeStructure(), requirements.WithConfigFile()); err != nil {
Expand Down Expand Up @@ -172,7 +174,7 @@ func (o *DownOptions) Run() error {
return err
}
} else {
err := destroyApp(ui, o)
err := destroyApp(o.AppName, o.Config, ui)
if err != nil {
return err
}
Expand Down Expand Up @@ -218,58 +220,34 @@ func destroyAll(ui terminal.UI, o *DownOptions) error {
err := manager.InReversDependencyOrder(aws.BackgroundContext(), o.Config.GetApps(), func(c context.Context, name string) error {
o.Config.AwsProfile = o.Config.Terraform["infra"].AwsProfile

var m manager.Manager

if app, ok := o.Config.Serverless[name]; ok {
app.Name = name
m = &serverless.Manager{
Project: o.Config,
App: app,
}
}
if app, ok := o.Config.Alias[name]; ok {
app.Name = name
m = &alias.Manager{
Project: o.Config,
App: app,
}
}
if app, ok := o.Config.Ecs[name]; ok {
app.Name = name
m = &ecs.Manager{
Project: o.Config,
App: app,
}
}

// destroy
err := m.Destroy(ui)
if err != nil {
return fmt.Errorf("can't destroy app: %w", err)
}

return nil
return destroyApp(name, o.Config, ui)
})
if err != nil {
return err
}

err = destroyInfra(ui, o.Config, o.SkipGen)
if err != nil {
return err
if _, ok := o.Config.Terraform["infra"]; ok {
err = destroyInfra("infra", o.Config, o.SkipGen, ui)
if err != nil {
return err
}
}

err = manager.InReversDependencyOrder(aws.BackgroundContext(), o.Config.GetStates(), func(c context.Context, name string) error {
o.Config.AwsProfile = o.Config.Terraform["infra"].AwsProfile

return destroyInfra(name, o.Config, o.SkipGen, ui)
})

ui.Output("Destroy all completed!\n", terminal.WithSuccessStyle())
time.Sleep(time.Millisecond * 200)

return nil
}

func destroyInfra(ui terminal.UI, config *config.Project, skipGen bool) error {
func destroyInfra(state string, config *config.Project, skipGen bool, ui terminal.UI) error {
if !skipGen {
err := GenerateTerraformFiles(
config,
"",
)
err := GenerateTerraformFiles(state, "", config)
if err != nil {
return err
}
Expand All @@ -296,9 +274,9 @@ func destroyInfra(ui terminal.UI, config *config.Project, skipGen bool) error {

switch config.PreferRuntime {
case "docker":
tf = terraform.NewDockerTerraform(config.Terraform["infra"].Version, []string{"destroy", "-auto-approve"}, env, nil, config)
tf = terraform.NewDockerTerraform(state, []string{"init", "-input=true"}, env, nil, config)
case "native":
tf = terraform.NewLocalTerraform(config.Terraform["infra"].Version, []string{"destroy", "-auto-approve"}, env, nil, config)
tf = terraform.NewLocalTerraform(state, []string{"init", "-input=true"}, env, nil, config)
err = tf.Prepare()
if err != nil {
return fmt.Errorf("can't destroy infra: %w", err)
Expand All @@ -307,47 +285,57 @@ func destroyInfra(ui terminal.UI, config *config.Project, skipGen bool) error {
return fmt.Errorf("can't supported %s runtime", config.PreferRuntime)
}

ui.Output("Running terraform destroy...", terminal.WithHeaderStyle())
ui.Output("Execution terraform init...", terminal.WithHeaderStyle())

err = tf.RunUI(ui)
if err != nil {
return err
}

//terraform destroy run options
tf.NewCmd([]string{"destroy", "-auto-approve"})

ui.Output("Execution terraform destroy...", terminal.WithHeaderStyle())

err = tf.RunUI(ui)
if err != nil {
return fmt.Errorf("can't deploy infra: %w", err)
}

ui.Output("Terraform destroy completed!\n", terminal.WithSuccessStyle())

return nil
}

func destroyApp(ui terminal.UI, o *DownOptions) error {
func destroyApp(name string, cfg *config.Project, ui terminal.UI) error {
var m manager.Manager
var icon string

m = &ecs.Manager{
Project: o.Config,
App: &config.Ecs{Name: o.AppName},
Project: cfg,
App: &config.Ecs{Name: name},
}

if app, ok := o.Config.Serverless[o.AppName]; ok {
app.Name = o.AppName
if app, ok := cfg.Serverless[name]; ok {
app.Name = name
m = &serverless.Manager{
Project: o.Config,
Project: cfg,
App: app,
}
icon = app.Icon
}
if app, ok := o.Config.Alias[o.AppName]; ok {
app.Name = o.AppName
if app, ok := cfg.Alias[name]; ok {
app.Name = name
m = &alias.Manager{
Project: o.Config,
Project: cfg,
App: app,
}
icon = app.Icon
}
if app, ok := o.Config.Ecs[o.AppName]; ok {
app.Name = o.AppName
if app, ok := cfg.Ecs[name]; ok {
app.Name = name
m = &ecs.Manager{
Project: o.Config,
Project: cfg,
App: app,
}
icon = app.Icon
Expand All @@ -357,16 +345,14 @@ func destroyApp(ui terminal.UI, o *DownOptions) error {
icon += " "
}

ui.Output("Destroying %s%s app...\n", icon, o.AppName, terminal.WithHeaderStyle())
sg := ui.StepGroup()
defer sg.Wait()
ui.Output("Destroying %s%s app...\n", icon, name, terminal.WithHeaderStyle())

err := m.Destroy(ui)
if err != nil {
return fmt.Errorf("can't down: %w", err)
}

ui.Output("Destroy app %s%s completed\n", icon, o.AppName, terminal.WithSuccessStyle())
ui.Output("Destroy app %s%s completed\n", icon, name, terminal.WithSuccessStyle())

return nil
}
Loading