Terraspace integration with localstack

I am integrating terraspace with localstack, but getting below errors. I have mentioned main.tf and error. Can someone please suggest on it.

Localstack docker : docker run --rm -p 4566:4566 localstack/localstack

main.tf

provider "aws" {

  access_key                  = "mock_access_key"
  secret_key                  = "mock_secret_key"
  region                      = "us-east-1"
  s3_use_path_style         = true
  skip_credentials_validation = true
  skip_metadata_api_check     = true
  skip_requesting_account_id  = true

  endpoints {
     
    ec2            = "http://localhost:4566"
    s3             = "http://s3.localhost.localstack.cloud:4566"

  }
}

locals {
  user_data = <<EOF
#!/bin/bash
echo "Hello Terraform!"
EOF
}

##################################################################
# Data sources to get VPC, subnet, security group and AMI details
##################################################################
data "aws_vpc" "default" {
  default = true
}

data "aws_subnet_ids" "all" {
  vpc_id = data.aws_vpc.default.id
}

data "aws_ami" "amazon_linux" {
  most_recent = true

  owners = ["amazon"]

  filter {
    name = "name"

    values = [
      "amzn-ami-hvm-*-x86_64-gp2",
    ]
  }

  filter {
    name = "owner-alias"

    values = [
      "amazon",
    ]
  }
}

module "security_group" {
  source  = "terraform-aws-modules/security-group/aws"
  version = "~> 3.0"

  name        = "example"
  description = "Security group for example usage with EC2 instance"
  vpc_id      = data.aws_vpc.default.id

  ingress_cidr_blocks = ["0.0.0.0/0"]
  ingress_rules       = ["http-80-tcp", "all-icmp"]
  egress_rules        = ["all-all"]
}

resource "aws_eip" "this" {
  vpc      = true
  instance = module.ec2.id[0]
}

resource "aws_placement_group" "web" {
  name     = "hunky-dory-pg"
  strategy = "cluster"
}

resource "aws_kms_key" "this" {
}

resource "aws_network_interface" "this" {
  count = 1

  subnet_id = tolist(data.aws_subnet_ids.all.ids)[count.index]
}

module "ec2" {
  source = "../../modules/ec2"

  instance_count = 1

  name          = "example-normal"
  ami           = data.aws_ami.amazon_linux.id
  instance_type = "c5.large"
  subnet_id     = tolist(data.aws_subnet_ids.all.ids)[0]
  //  private_ips                 = ["172.31.32.5", "172.31.46.20"]
  vpc_security_group_ids      = [module.security_group.this_security_group_id]
  associate_public_ip_address = true
  placement_group             = aws_placement_group.web.id

  user_data_base64 = base64encode(local.user_data)

  root_block_device = [
    {
      volume_type = "gp2"
      volume_size = 10
    },
  ]

  ebs_block_device = [
    {
      device_name = "/dev/sdf"
      volume_type = "gp2"
      volume_size = 5
      encrypted   = true
      kms_key_id  = aws_kms_key.this.arn
    }
  ]

  tags = {
    "Env"      = "Private"
    "Location" = "Secret"
  }
}

module "ec2_with_t2_unlimited" {
  source = "../../modules/ec2"

  instance_count = 1

  name          = "example-t2-unlimited"
  ami           = data.aws_ami.amazon_linux.id
  instance_type = "t2.micro"
  cpu_credits   = "unlimited"
  subnet_id     = tolist(data.aws_subnet_ids.all.ids)[0]
  //  private_ip = "172.31.32.10"
  vpc_security_group_ids      = [module.security_group.this_security_group_id]
  associate_public_ip_address = true
}

module "ec2_with_t3_unlimited" {
  source = "../../modules/ec2"

  instance_count = 1

  name                        = "example-t3-unlimited"
  ami                         = data.aws_ami.amazon_linux.id
  instance_type               = "t3.large"
  cpu_credits                 = "unlimited"
  subnet_id                   = tolist(data.aws_subnet_ids.all.ids)[0]
  vpc_security_group_ids      = [module.security_group.this_security_group_id]
  associate_public_ip_address = true
}

module "ec2_with_network_interface" {
  source = "../../modules/ec2"

  instance_count = 1

  name            = "example-network"
  ami             = data.aws_ami.amazon_linux.id
  instance_type   = "c5.large"
  placement_group = aws_placement_group.web.id

  network_interface = [
    {
      device_index          = 0
      network_interface_id  = aws_network_interface.this[0].id
      delete_on_termination = false
    }
  ]
}

# This instance won't be created
module "ec2_zero" {
  source = "../../modules/ec2"

  instance_count = 0

  name                   = "example-zero"
  ami                    = data.aws_ami.amazon_linux.id
  instance_type          = "c5.large"
  subnet_id              = tolist(data.aws_subnet_ids.all.ids)[0]
  vpc_security_group_ids = [module.security_group.this_security_group_id]
}

Error

terraspace up basic

You can also get rid of this message by setting AWS_REGION or configuring ~/.aws/config with the region

You can also get rid of this message by setting AWS_REGION or configuring ~/.aws/config with the region
C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/aws-sdk-core-3.131.2/lib/seahorse/client/plugins/raise_response_errors.rb:17:in `call': The security token included in the request is invalid. (Aws::STS::Errors::InvalidClientTokenId)
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/aws-sdk-core-3.131.2/lib/aws-sdk-core/plugins/checksum_algorithm.rb:111:in `call'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/aws-sdk-core-3.131.2/lib/aws-sdk-core/plugins/jsonvalue_converter.rb:22:in `call'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/aws-sdk-core-3.131.2/lib/aws-sdk-core/plugins/idempotency_token.rb:19:in `call'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/aws-sdk-core-3.131.2/lib/aws-sdk-core/plugins/param_converter.rb:26:in `call'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/aws-sdk-core-3.131.2/lib/seahorse/client/plugins/request_callback.rb:71:in `call'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/aws-sdk-core-3.131.2/lib/aws-sdk-core/plugins/response_paging.rb:12:in `call'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/aws-sdk-core-3.131.2/lib/seahorse/client/plugins/response_target.rb:24:in `call'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/aws-sdk-core-3.131.2/lib/seahorse/client/request.rb:72:in `send_request'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/aws-sdk-core-3.131.2/lib/aws-sdk-sts/client.rb:1784:in `get_caller_identity'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/aws_data-0.1.1/lib/aws_data.rb:59:in `account'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/memoist-0.16.2/lib/memoist.rb:169:in `account'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/terraspace_plugin_aws-0.4.1/lib/terraspace_plugin_aws/interfaces/layer.rb:10:in `namespace'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/terraspace-2.0.2/lib/terraspace/compiler/strategy/tfvar/layer.rb:112:in `block in plugins'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/terraspace-2.0.2/lib/terraspace/compiler/strategy/tfvar/layer.rb:106:in `each'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/terraspace-2.0.2/lib/terraspace/compiler/strategy/tfvar/layer.rb:106:in `plugins'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/terraspace-2.0.2/lib/terraspace/layering.rb:11:in `main_layers'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/terraspace-2.0.2/lib/terraspace/layering.rb:6:in `layers'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/terraspace-2.0.2/lib/terraspace/cli/up.rb:9:in `run'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/terraspace-2.0.2/lib/terraspace/cli.rb:230:in `up'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/thor-1.2.1/lib/thor/command.rb:27:in `run'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/thor-1.2.1/lib/thor/invocation.rb:127:in `invoke_command'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/thor-1.2.1/lib/thor.rb:392:in `dispatch'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/terraspace-2.0.2/lib/terraspace/command.rb:76:in `dispatch'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/thor-1.2.1/lib/thor/base.rb:485:in `start'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/terraspace-2.0.2/lib/terraspace/cli/concern.rb:65:in `start'
        from C:/Ruby30-x64/lib/ruby/gems/3.0.0/gems/terraspace-2.0.2/exe/terraspace:7:in `<top (required)>'
        from C:/Ruby30-x64/bin/terraspace:25:in `load'
        from C:/Ruby30-x64/bin/terraspace:25:in `<main>'

Haven’t played with localstack yet so unsure :face_with_monocle:

Looked into the stack trace though. So terraspace up basic calls terraspace build internally. In the stack trace it looks like at terraspace-2.0.2/lib/terraspace/cli/up.rb:9 which leads to

Unsure exactly why the stack trace does not show the lead-up to the layering.rb:6. But traced it anyway and basically, it is the result of calling @mod.cache_dir in builder.rb

The @mod.cache can actually be configured with a config setting. The default is ":REGION/:APP/:ROLE/:ENV/:BUILD_DIR" Docs:

https://terraspace.cloud/docs/config/reference/

So the :REGION is a cloud plugin-specific value, which is why it’s calling out to the AWS API and trying to get the value.

As a test, maybe try overriding the setting without :REGION:

config/app.rb

Terraspace.configure do |config|
  config.build.cache_dir	= ":APP/:ROLE/:ENV/:BUILD_DIR" # without :REGION
end

That might work. Unsure if other AWS API calls might still occur, though.

@satya were you ever able to get terraspace working with localstack?
interested on how you ended up passing the endpoint urls to TF/localstack, did you end up just hooking them directly into the provider.tf/backend.tf ? (getting errors on my side)
or did you figure out a different way?
I had a look through the aws sdk ruby gem, and while it looks that it provides a endpoint_url interface for a url connection string, it doesn’t seem to be consumed anywhere.

Following up on this

I am trying to get terraspace to connect to my localstack instance, but getting the error from terraspace as it attempts to connect to AWS (not terraform)

 AWS_DEFAULT_PROFILE=localstack TS_ENV=localstack bundle exec terraspace up vpc
Exception Aws::STS::Errors::InvalidClientTokenId: The security token included in the request is invalid.
/home/gdeuser/.gem/gems/aws-sdk-core-3.174.0/lib/seahorse/client/plugins/raise_response_errors.rb:17:in `call'
/home/gdeuser/.gem/gems/aws-sdk-core-3.174.0/lib/aws-sdk-core/plugins/checksum_algorithm.rb:111:in `call'
/home/gdeuser/.gem/gems/aws-sdk-core-3.174.0/lib/aws-sdk-core/plugins/jsonvalue_converter.rb:16:in `call'
/home/gdeuser/.gem/gems/aws-sdk-core-3.174.0/lib/aws-sdk-core/plugins/idempotency_token.rb:19:in `call'
/home/gdeuser/.gem/gems/aws-sdk-core-3.174.0/lib/aws-sdk-core/plugins/param_converter.rb:26:in `call'
/home/gdeuser/.gem/gems/aws-sdk-core-3.174.0/lib/seahorse/client/plugins/request_callback.rb:71:in `call'
/home/gdeuser/.gem/gems/aws-sdk-core-3.174.0/lib/aws-sdk-core/plugins/response_paging.rb:12:in `call'
/home/gdeuser/.gem/gems/aws-sdk-core-3.174.0/lib/seahorse/client/plugins/response_target.rb:24:in `call'
/home/gdeuser/.gem/gems/aws-sdk-core-3.174.0/lib/seahorse/client/request.rb:72:in `send_request'
/home/gdeuser/.gem/gems/aws-sdk-core-3.174.0/lib/aws-sdk-sts/client.rb:1801:in `get_caller_identity'
/home/gdeuser/.gem/gems/aws_data-0.1.1/lib/aws_data.rb:59:in `account'
/home/gdeuser/.gem/gems/memoist-0.16.2/lib/memoist.rb:169:in `account'
/home/gdeuser/.gem/gems/terraspace_plugin_aws-0.6.0/lib/terraspace_plugin_aws/interfaces/layer.rb:10:in `namespace'
/home/gdeuser/.gem/gems/terraspace-2.2.7/lib/terraspace/compiler/strategy/tfvar/layer.rb:113:in `block in plugins'
/home/gdeuser/.gem/gems/terraspace-2.2.7/lib/terraspace/compiler/strategy/tfvar/layer.rb:107:in `each'
/home/gdeuser/.gem/gems/terraspace-2.2.7/lib/terraspace/compiler/strategy/tfvar/layer.rb:107:in `plugins'
/home/gdeuser/.gem/gems/terraspace-2.2.7/lib/terraspace/layering.rb:11:in `main_layers'
/home/gdeuser/.gem/gems/terraspace-2.2.7/lib/terraspace/layering.rb:6:in `layers'
/home/gdeuser/.gem/gems/terraspace-2.2.7/lib/terraspace/compiler/strategy/tfvar/layer.rb:68:in `full_layering'
/home/gdeuser/.gem/gems/terraspace-2.2.7/lib/terraspace/compiler/strategy/tfvar/layer.rb:53:in `full_paths'
/home/gdeuser/.gem/gems/terraspace-2.2.7/lib/terraspace/compiler/strategy/tfvar/layer.rb:42:in `paths'
/home/gdeuser/.gem/gems/memoist-0.16.2/lib/memoist.rb:169:in `paths'
/home/gdeuser/.gem/gems/terraspace-2.2.7/lib/terraspace/compiler/strategy/tfvar.rb:27:in `layer_paths'
/home/gdeuser/.gem/gems/memoist-0.16.2/lib/memoist.rb:169:in `layer_paths'
/home/gdeuser/.gem/gems/terraspace-2.2.7/lib/terraspace/compiler/strategy/tfvar.rb:11:in `run'
/home/gdeuser/.gem/gems/terraspace-2.2.7/lib/terraspace/compiler/perform.rb:35:in `compile_tfvars'
/home/gdeuser/.gem/gems/terraspace-2.2.7/lib/terraspace/dependency/resolver.rb:12:in `block in resolve'
/home/gdeuser/.gem/gems/terraspace-2.2.7/lib/terraspace/compiler/dirs_concern.rb:18:in `block in with_each_mod'
/home/gdeuser/.gem/gems/terraspace-2.2.7/lib/terraspace/compiler/dirs_concern.rb:15:in `each'
/home/gdeuser/.gem/gems/terraspace-2.2.7/lib/terraspace/compiler/dirs_concern.rb:15:in `with_each_mod'
/home/gdeuser/.gem/gems/terraspace-2.2.7/lib/terraspace/dependency/resolver.rb:10:in `resolve'
/home/gdeuser/.gem/gems/terraspace-2.2.7/lib/terraspace/builder.rb:36:in `resolve_dependencies'
/home/gdeuser/.gem/gems/terraspace-2.2.7/lib/terraspace/builder.rb:30:in `run'
/home/gdeuser/.gem/gems/terraspace-2.2.7/lib/terraspace/cli/up.rb:80:in `build'
/home/gdeuser/.gem/gems/terraspace-2.2.7/lib/terraspace/cli/up.rb:19:in `perform'
/home/gdeuser/.gem/gems/terraspace-2.2.7/lib/terraspace/cli/up.rb:11:in `run'
/home/gdeuser/.gem/gems/terraspace-2.2.7/lib/terraspace/cli.rb:236:in `up'
/home/gdeuser/.gem/gems/thor-1.2.2/lib/thor/command.rb:27:in `run'
/home/gdeuser/.gem/gems/thor-1.2.2/lib/thor/invocation.rb:127:in `invoke_command'
/home/gdeuser/.gem/gems/thor-1.2.2/lib/thor.rb:392:in `dispatch'
/home/gdeuser/.gem/gems/terraspace-2.2.7/lib/terraspace/command.rb:76:in `dispatch'
/home/gdeuser/.gem/gems/thor-1.2.2/lib/thor/base.rb:485:in `start'
/home/gdeuser/.gem/gems/terraspace-2.2.7/lib/terraspace/cli/concern.rb:65:in `start'
/home/gdeuser/.gem/gems/terraspace-2.2.7/exe/terraspace:7:in `<top (required)>'
/home/gdeuser/.gem/bin/terraspace:25:in `load'
/home/gdeuser/.gem/bin/terraspace:25:in `<top (required)>'
/snap/ruby/317/lib/ruby/3.2.0/bundler/cli/exec.rb:58:in `load'
/snap/ruby/317/lib/ruby/3.2.0/bundler/cli/exec.rb:58:in `kernel_load'
/snap/ruby/317/lib/ruby/3.2.0/bundler/cli/exec.rb:23:in `run'
/snap/ruby/317/lib/ruby/3.2.0/bundler/cli.rb:492:in `exec'
/snap/ruby/317/lib/ruby/3.2.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
/snap/ruby/317/lib/ruby/3.2.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'
/snap/ruby/317/lib/ruby/3.2.0/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'
/snap/ruby/317/lib/ruby/3.2.0/bundler/cli.rb:34:in `dispatch'
/snap/ruby/317/lib/ruby/3.2.0/bundler/vendor/thor/lib/thor/base.rb:485:in `start'
/snap/ruby/317/lib/ruby/3.2.0/bundler/cli.rb:28:in `start'
/snap/ruby/317/lib/ruby/gems/3.2.0/gems/bundler-2.4.10/libexec/bundle:45:in `block in <top (required)>'
/snap/ruby/317/lib/ruby/3.2.0/bundler/friendly_errors.rb:117:in `with_friendly_errors'
/snap/ruby/317/lib/ruby/gems/3.2.0/gems/bundler-2.4.10/libexec/bundle:33:in `<top (required)>'
/snap/ruby/317/bin/bundle:25:in `load'
/snap/ruby/317/bin/bundle:25:in `<main>'

I believe I might need to pass :endpoint around here:

similar to: https://github.com/localstack/localstack/issues/472#issuecomment-501034431

thoughts?
is there a better place to inject a :endpoint from a ENV[‘AWS_ENDPOINT’] var?