hashicorp / terraform-provider-aws

The AWS Provider enables Terraform to manage AWS resources.
https://registry.terraform.io/providers/hashicorp/aws
Mozilla Public License 2.0
9.84k stars 9.19k forks source link

[Bug]: Perpetual Diff in `media_package_output_settings` in `aws_medialive_channel` resource #31555

Open RomanNess opened 1 year ago

RomanNess commented 1 year ago

Terraform Core Version

1.4.6

AWS Provider Version

4.67.0

Affected Resource(s)

resource aws_medialive_channel (encoder_settings[*].output_groups[*].outputs[*].output_settings[*].media_package_output_settings property)

Expected Behavior

No diffs should be detected by Terraform when the config is not changed in between applies.

Actual Behavior

When a MediaPackage output is configured for aws_medialive_channel, a perpetual diff is detected by terraform on every apply.

terraform plan output:

aws_medialive_channel.example: Refreshing state... [id=7455308]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # aws_medialive_channel.example will be updated in-place
  ~ resource "aws_medialive_channel" "example" {
        id            = "7455308"
        name          = "romans-test-bbb"
        tags          = {}
        # (7 unchanged attributes hidden)

      ~ encoder_settings {
          ~ output_groups {
                name = "livestream"

              ~ outputs {
                    # (4 unchanged attributes hidden)

                  ~ output_settings {
                      + media_package_output_settings {}
                    }
                }

                # (1 unchanged block hidden)
            }

            # (3 unchanged blocks hidden)
        }

        # (4 unchanged blocks hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

Relevant Error/Panic Output Snippet

No response

Terraform Configuration Files

locals {
  resource_prefix = "perpetual-drift-test"
}

terraform {
  required_version = "1.4.6"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "4.67"
    }
  }
}

resource "aws_iam_role" "example" {
  name                 = "${local.resource_prefix}-medialive-role"
  description          = ""
  max_session_duration = 3600
  path                 = "/"
  assume_role_policy   = <<EOF
{
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Effect": "Allow",
      "Principal": {
        "Service": "medialive.amazonaws.com"
      }
    }
  ],
  "Version": "2012-10-17"
}
EOF
}

resource "aws_iam_role_policy_attachment" "example_mediaPackage" {
  role       = aws_iam_role.example.name
  policy_arn = "arn:aws:iam::aws:policy/AWSElementalMediaPackageReadOnly"
}

resource "aws_iam_role_policy_attachment" "example_mediaLive" {
  role       = aws_iam_role.example.name
  policy_arn = "arn:aws:iam::aws:policy/AWSElementalMediaLiveFullAccess"
}

resource "aws_media_package_channel" "example" {
  channel_id = "${local.resource_prefix}-bbb"
}

resource "aws_medialive_input" "example" {
  name = "${local.resource_prefix}-bbb"
  type = "MP4_FILE"

  sources {
    url            = "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
    username       = ""
    password_param = ""
  }
}

resource "aws_medialive_channel" "example" {
  name          = "${local.resource_prefix}-bbb"
  channel_class = "SINGLE_PIPELINE"
  role_arn      = aws_iam_role.example.arn

  destinations {
    id = "mediapackage-output"
    media_package_settings {
      channel_id = aws_media_package_channel.example.id
    }
  }

  input_specification {
    codec            = "AVC"
    input_resolution = "HD"
    maximum_bitrate  = "MAX_20_MBPS"
  }

  input_attachments {
    input_id              = aws_medialive_input.example.id
    input_attachment_name = "BBB"

    input_settings {
      source_end_behavior = "LOOP"
    }
  }

  encoder_settings {

    audio_descriptions {
      name                = "example-audio"
      audio_selector_name = "Default"
    }

    video_descriptions {
      name   = "example-video"
      width  = 640
      height = 360

      codec_settings {
        h264_settings {
          framerate_control       = "SPECIFIED"
          par_control             = "SPECIFIED"
          framerate_numerator     = 50
          framerate_denominator   = 1
        }
      }
    }

    timecode_config {
      source = "EMBEDDED"
    }

    output_groups {
      name = "livestream"

      output_group_settings {
        media_package_group_settings {
          destination {
            destination_ref_id = "mediapackage-output"
          }
        }
      }

      outputs {
        output_name             = "output-name"
        video_description_name  = "example-video"
        audio_description_names = ["example-audio"]

        output_settings {
          media_package_output_settings {}
        }
      }
    }
  }
}

Steps to Reproduce

Apply the above terraform config multiple times.

Debug Output

No response

Panic Output

No response

Important Factoids

The media_package_output_settings field is special since it can only be an empty block and setting the block tells the provider that a MediaPackage output is used with the channel. CloudFormation docs: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-medialive-channel-mediapackageoutputdestinationsettings.html API docs: https://docs.aws.amazon.com/medialive/latest/apireference/channels-channelid.html#channels-channelid-model-mediapackageoutputsettings

The issue seems to be that media_package_output_settings are persisted as an empty json array and when the state is read, the provider cannot tell if the media_package_output_settings are set. Excerpt of the Terraform state:

"output_settings": [
  {
    "archive_output_settings": [],
    "frame_capture_output_settings": [],
    "hls_output_settings": [],
    "media_package_output_settings": [],
    "ms_smooth_output_settings": [],
    "multiplex_output_settings": [],
    "rtmp_output_settings": [],
    "udp_output_settings": []
  }
]

We have investigated the issue and a potential bug is that the anonymous function returns an empty interface in line 5069 even if MediaPackageOutputSettings are set. https://github.com/hashicorp/terraform-provider-aws/blob/5675259bb8a9c0c59c239dcb8e869ab4d49aea8d/internal/service/medialive/channel_encoder_settings_schema.go#L5056-L5087 As a quick fix we tried to return an empty map in the anonymous function which seems to resolve the bug at first glance.

func(inner *types.MediaPackageOutputSettings) []interface{} {
    if inner == nil {
        return nil
    }
    return []interface{}{map[string]interface{}{}}
}

With the fix, the media_package_output_settings are serialized as an array that contains an empty object ([{}]), which looks legit to me.

"output_settings": [
  {
    "archive_output_settings": [],
    "frame_capture_output_settings": [],
    "hls_output_settings": [],
    "media_package_output_settings": [{}],
    "ms_smooth_output_settings": [],
    "multiplex_output_settings": [],
    "rtmp_output_settings": [],
    "udp_output_settings": []
  }
]

I'm lacking the knowledge if the above fix would be considered dirty or even plain wrong, but I'm willing to open a PR and implement the fix when this is clarified. :)

References

No response

Would you like to implement a fix?

Yes

github-actions[bot] commented 1 year ago

Community Note

Voting for Prioritization

Volunteering to Work on This Issue