This object has no argument, nested block, or exported attribute named when using null_resource

Hi All,

Need your kind help around below issue, I am getting while using null_resource. I have created two buckets and just want to simply run a command for both the buckets with the help of null resource. However I am getting error " This object has no argument, nested block, or exported attribute named arn ". Could anyone please guide me what I am doing wrong here. Actually I am bit new in Terraform. :slight_smile:

Code:

provider "aws" {
region = "eu-west-1"
}
provider "null" {}
variable "bucketname" {
type = "list"
default = ["mytestbuckettfmodule1", "mytestbuckettfmodule2"]
}

below resource to create two buckets, working fine

resource "aws_s3_bucket" "mybucket" {
for_each = toset(var.bucketname)
bucket = each.value
acl = "private"
tags = {
Company = "ABC"
CostCenter = "21102353"
Environment = "NonProd"
Name = "TestInstane"
Role = "Test"
Service = "Test"
User = "[kapil.teotia@ABC.com](mailto:kapil.teotia@ABC.com)"
}
}

below null resource I am using to execute command under local-exec for both the buckets but unfortunately getting error

########################################

Error2

Hi @KapilTeotia1,

The direct cause of your error here is that self inside a provisioner block refers to the resource instance the provisioner is running for, which is null_resource.mybucket here. That resource doesn’t have an arn attribute.

It looks like your goal here is to generate a single JSON file containing, amongst other things, a comma-separated list of bucket ARNs. Here’s a different way to do that without using provisioners, which are considered to be a last resort.

locals {
  s3_rules = {
    "Rules": [
      {
        "DeleteMarkerReplication": {
          "Status": "Disabled"
        },
        "Status": "Enabled",
        "Priority": 1,
        "Filter": { "Prefix": "" },
        "Destination": { "Bucket": join(",", aws_s3_bucket.mybucket[*].id) },
      },
    ]
  }
}

The above declares a local value containing the data structure that we’ll write to the file as JSON. We can access this data structure elsewhere in the configuration using local.s3_rules.

Usually we don’t generate local files on disk using Terraform. If you were intending to use this result as an S3 bucket replication rule, please note that aws_s3_bucket has built-in support for replication configurations using a replication_configuration block, which I’d recommend using instead of this JSON string result if your goal is to manage your S3 buckets with Terraform.

However, if having it in a file on disk is important then you can use the local_file resource type to create a local file, like this:

resource "local_file" "example" {
  filename = "${path.cwd}/file.json"
  content  = jsonencode(local.s3_rules)
}

The above uses the jsonencode function to produce a valid JSON representation of the local.s3_rules value. That’s better that constructing the string yourself using a Terraform template because the jsonencode function will ensure that all string values are escaped properly, comma separators are used when needed, etc.

Thanks @apparentlymart for your prompt reply and kind help.

Let me give you a background. I actually want to implement the bi-directional replication on S3 bucket. I could find the below project where bi-directional is working fine when we have only single primary and secondary bucket. however when we pass multiple primary and secondary buckets with the help of list then we are facing issues.

Project Link which I found on google:


To achieve bi directional we are trying to save the required json with required replication details while creating the primary buckets. and then tried to created null_resource where we can run AWS CLI command for each bucket to add the bi directional replication on secondary buckets by passing the information which we saved in Json during primary bucket creation.

Could you please guide/adivse how I can achive to run AWS CLI command to add bi-directional on DR/secondary buckets as I am facing issues while implementing through null_resource. While going through the documentation I come to know that we can run CLI command by leveraging the null_resource and the provisioner local-exec only.

Please find below piece of code that I am trying…

**############################Providers############################**

provider "aws" {
  region = "eu-west-1"
}

provider "aws" {
  alias  = "dr"
  region = "eu-west-2"
  }

provider "null" {}

**############################variable############################**

variable "bucketname" {
  type = "list"
  default = ["mytestbuckettfmodule1", "mytestbuckettfmodule2"]
}

variable "counter" {
  default = "1"
}


**############################Null Resource to run AWS CLI to add bi directional############################**


resource "null_resource" "s3bucket" {
  depends_on = ["aws_s3_bucket.mybucket1", "aws_s3_bucket.dr_bucket"]
  for_each = toset(aws_s3_bucket.dr_bucket)

  triggers = {
    all_bucket = each.value
  }

  provisioner "local-exec" {
    command = "sleep 5 && aws s3api put-bucket-replication --bucket=${aws_s3_bucket.dr_bucket.bucket} --replication-configuration=file://${replication_details.json}"
  }
}


**############################Primary Region bucket############################**


resource "aws_s3_bucket" "mybucket1" {
  for_each = toset(var.bucketname)
  bucket = each.value
  acl = "private"
  tags = {
    Company = "IHSMarkit"
    CostCenter = "21102357"
    Environment = "NonProd"
    Name = "TestInstane"
    Role = "Test"
    Service = "Test"
    User = "kapil.teotia@ihsmarkit.com"
  }

  provisioner "local-exec" ######## Adding required replication details to a json while creating primary bucket which we can pass later in AWS CLI but facing an isse here as I need to keep seperate json for each bucket so that seperate bucket can be passed to run AWS CLI command.
  {
    command = <<CMD
  echo '{"Rules":[{"DeleteMarkerReplication": { "Status": "Disabled" },"Status":"Enabled","Priority":1,"Filter":{"Prefix":""},"Destination":{"Bucket":"${self.arn}"}}]}' > ./replication_details.json
  CMD
}
}

**############################Secondary bucket############################**

resource "aws_s3_bucket" "dr_bucket" {
  provider = aws.dr
  for_each = toset(var.bucketname)
  bucket = each.value-"dr"
  acl = "private"
  tags = {
    Company = "IHSMarkit"
    CostCenter = "21102357"
    Environment = "NonProd"
    Name = "TestInstane"
    Role = "Test"
    Service = "Test"
    User = "kapil.teotia@ihsmarkit.com"
  }

Hi @KapilTeotia1,

I’m afraid I’m not very familiar with S3 replication so I can’t give a full, working example. However, based on the example you shared I think you could extend what I wrote in my previous comment to include running the put-bucket-replication CLI command by adding a null_resource resource like this:

resource "null_resource" "s3_replication" {
  for_each = aws_s3_bucket.mybucket

  triggers = {
    # The provisioner command must re-run
    # whenever the rules are changed.
    config = local_file.example.content
    config_file = local_file.experiment.filename
  }

  provisioner "local-exec" {
    command = "sleep 5 && aws s3api put-bucket-replication --bucket=${each.value.bucket} --replication-configuration=file://${self.triggers.config_file}"
  }
}

The other thing I see in your new example is that you have two aws_s3_bucket resources because each one belongs to a different provider configuration. Again, I’m not at all familiar with S3 replication so I’m not sure what the requirements are, but if you need to run the aws s3api put-bucket-replication command for all of the mybucket1 instances and all of the dr_bucket instances then you can collect them all together into a single map like this:

locals {
  all_buckets = merge(
    { for k, b in aws_s3_bucket.mybucket1 : "${k}:primary" => b },
    { for k, b in aws_s3_bucket.dr_bucket : "${k}:replica" => b },
  )
}

The above declares a value local.all_buckets which includes both the primary and replica buckets. You could then use local.all_buckets in the for_each of another resource to create one instance of it per bucket across both types of bucket. For example, to run the AWS CLI command for all buckets:

resource "null_resource" "s3_replication" {
  for_each = local.all_buckets

  # ... and then the rest, unchanged
}

Unfortunately I can’t give a more specific or complete example than these with my lacking knowledge of this API. Hopefully someone else reading the forum who has more familiarity with S3 replication may be able to help with more specifics using the above as a starting point.