DontShaveTheYak / cf2tf

Convert Cloudformation templates to Terraform.
GNU General Public License v3.0
493 stars 80 forks source link

ValueError: Fn::Split not allowed to be nested in None. #268

Closed mcrowley-axcess closed 6 months ago

mcrowley-axcess commented 8 months ago

Template: https://s3.amazonaws.com/aws-transfer-resources/custom-idp-templates/aws-transfer-custom-idp-secrets-manager-sourceip-protocol-support-apig.zip

Environment: Python 3.10.12

Traceback (most recent call last): File "/home/localuser1/.local/bin/cf2tf", line 8, in sys.exit(cli()) File "/usr/local/lib/python3.10/dist-packages/click/core.py", line 1157, in call return self.main(args, kwargs) File "/usr/local/lib/python3.10/dist-packages/click/core.py", line 1078, in main rv = self.invoke(ctx) File "/usr/local/lib/python3.10/dist-packages/click/core.py", line 1434, in invoke return ctx.invoke(self.callback, ctx.params) File "/usr/local/lib/python3.10/dist-packages/click/core.py", line 783, in invoke return __callback(args, **kwargs) File "/home/localuser1/.local/lib/python3.10/site-packages/cf2tf/app.py", line 44, in cli config = TemplateConverter(tmpl_path.stem, cf_template, search_manger).convert() File "/home/localuser1/.local/lib/python3.10/site-packages/cf2tf/convert.py", line 97, in convert tf_resources = self.convert_to_tf(self.manifest) File "/home/localuser1/.local/lib/python3.10/site-packages/cf2tf/convert.py", line 144, in convert_to_tf tf_resources.extend(converter(resources)) File "/home/localuser1/.local/lib/python3.10/site-packages/cf2tf/convert.py", line 344, in convert_resources resolved_values = self.resolve_values( File "/home/localuser1/.local/lib/python3.10/site-packages/cf2tf/convert.py", line 176, in resolve_values data[key] = self.resolve_values( File "/home/localuser1/.local/lib/python3.10/site-packages/cf2tf/convert.py", line 206, in resolve_values value = self.resolve_values( File "/home/localuser1/.local/lib/python3.10/site-packages/cf2tf/convert.py", line 217, in resolve_values resolved_list_values = [ File "/home/localuser1/.local/lib/python3.10/site-packages/cf2tf/convert.py", line 218, in self.resolve_values(item, allowed_func, prev_func) for item in data File "/home/localuser1/.local/lib/python3.10/site-packages/cf2tf/convert.py", line 176, in resolve_values data[key] = self.resolve_values( File "/home/localuser1/.local/lib/python3.10/site-packages/cf2tf/convert.py", line 204, in resolve_values raise ValueError(f"{key} not allowed to be nested in {prev_func}.") ValueError: Fn::Split not allowed to be nested in None.

shadycuz commented 6 months ago

This has been fixed in release v0.7.0. You can upgrade with pip install -U cf2tf

Here is your converted template:

data "aws_caller_identity" "current" {}

data "aws_partition" "current" {}

data "aws_region" "current" {}

locals {
  CreateServer = var.create_server == "true"
  NotCreateServer = !local.CreateServer
  SecretsManagerRegionProvided = !var.secrets_manager_region == ""
  TransferVPCEndpoint = var.transfer_endpoint_type == "VPC"
  stack_id = uuidv5("dns", "testing")
}

variable create_server {
  description = "Whether this stack creates an AWS Transfer endpoint or not. If the endpoint is created as part of the stack, the custom identity provider is automatically associated with it."
  type = string
  default = "true"
}

variable secrets_manager_region {
  description = "(Optional) The region the secrets are stored in. If this value is not provided, the region this stack is deployed in will be used. Use this field if you are deploying this stack in a region where SecretsManager is not available."
  type = string
}

variable transfer_endpoint_type {
  description = "Select PUBLIC if you want a public facing AWS Transfer endpoint or VPC if you want a VPC based endpoint. Note that only SFTP and FTPS are supported on public endpoints."
  type = string
  default = "PUBLIC"
}

variable transfer_subnet_i_ds {
  description = "Required if launching a VPC endpoint. Comma-separated list of subnets that you would like the AWS Transfer endpoint to be provisioned into."
  type = string
}

variable transfer_vpcid {
  description = "Required if launching a VPC endpoint. The VPC ID that you would like the AWS Transfer endpoint to be provisioned into."
  type = string
}

resource "aws_transfer_server" "transfer_server" {
  count = local.CreateServer ? 1 : 0
  endpoint_type = var.transfer_endpoint_type
  endpoint_details {
    // CF Property(InvocationRole) = aws_iam_role.transfer_identity_provider_role.arn
    // CF Property(Url) = join("", ["https://", aws_api_gateway_rest_api.custom_identity_provider_api.arn, ".execute-api.", data.aws_region.current.name, ".", data.aws_partition.current.dns_suffix, "/", aws_api_gateway_stage.api_stage.arn])
  }
  identity_provider_type = "API_GATEWAY"
  logging_role = aws_iam_role.transfer_cw_logging_role.arn
}

resource "aws_iam_role" "transfer_cw_logging_role" {
  count = local.CreateServer ? 1 : 0
  assume_role_policy = {
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Service = [
            "transfer.amazonaws.com"
          ]
        }
        Action = [
          "sts:AssumeRole"
        ]
      }
    ]
  }
  managed_policy_arns = [
    "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AWSTransferLoggingAccess"
  ]
}

resource "aws_api_gateway_rest_api" "custom_identity_provider_api" {
  name = "Transfer Family Secrets Manager Integration API"
  description = "API used for Transfer Family to access user information in Secrets Manager"
  fail_on_warnings = true
  endpoint_configuration {
    types = [
      "REGIONAL"
    ]
  }
}

resource "aws_iam_role" "lambda_execution_role" {
  assume_role_policy = {
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Service = [
            "lambda.amazonaws.com"
          ]
        }
        Action = [
          "sts:AssumeRole"
        ]
      }
    ]
  }
  managed_policy_arns = [
    "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
  ]
  force_detach_policies = [
    {
      PolicyName = "LambdaSecretsPolicy"
      PolicyDocument = {
        Version = "2012-10-17"
        Statement = [
          {
            Effect = "Allow"
            Action = [
              "secretsmanager:GetSecretValue"
            ]
            Resource = "arn:${data.aws_partition.current.partition}:secretsmanager:${local.SecretsManagerRegionProvided ? var.secrets_manager_region : data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:secret:aws/transfer/*"
          }
        ]
      }
    }
  ]
}

resource "aws_iam_role" "api_cloud_watch_logs_role" {
  assume_role_policy = {
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Service = [
            "apigateway.amazonaws.com"
          ]
        }
        Action = [
          "sts:AssumeRole"
        ]
      }
    ]
  }
  force_detach_policies = [
    {
      PolicyName = "ApiGatewayLogsPolicy"
      PolicyDocument = {
        Version = "2012-10-17"
        Statement = [
          {
            Effect = "Allow"
            Action = [
              "logs:CreateLogGroup",
              "logs:CreateLogStream",
              "logs:DescribeLogGroups",
              "logs:DescribeLogStreams",
              "logs:DescribeQueries",
              "logs:FilterLogEvents",
              "logs:GetLogEvents",
              "logs:GetLogGroupFields",
              "logs:GetLogRecord",
              "logs:GetQueryResults",
              "logs:PutLogEvents",
              "logs:StartQuery",
              "logs:StopQuery"
            ]
            Resource = "*"
          }
        ]
      }
    }
  ]
}

resource "aws_api_gateway_account" "api_logging_account" {
  cloudwatch_role_arn = aws_iam_role.api_cloud_watch_logs_role.arn
}

resource "aws_api_gateway_stage" "api_stage" {
  deployment_id = aws_api_gateway_deployment.api_deployment202008.id
  // CF Property(MethodSettings) = [
  //   {
  //     DataTraceEnabled = false
  //     HttpMethod = "*"
  //     LoggingLevel = "INFO"
  //     ResourcePath = "/*"
  //   }
  // ]
  rest_api_id = aws_api_gateway_rest_api.custom_identity_provider_api.arn
  stage_name = "prod"
}

resource "aws_api_gateway_deployment" "api_deployment202008" {
  rest_api_id = aws_api_gateway_rest_api.custom_identity_provider_api.arn
}

resource "aws_iam_role" "transfer_identity_provider_role" {
  assume_role_policy = {
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Service = "transfer.amazonaws.com"
        }
        Action = [
          "sts:AssumeRole"
        ]
      }
    ]
  }
  force_detach_policies = [
    {
      PolicyName = "TransferCanInvokeThisApi"
      PolicyDocument = {
        Version = "2012-10-17"
        Statement = [
          {
            Effect = "Allow"
            Action = [
              "execute-api:Invoke"
            ]
            Resource = "arn:${data.aws_partition.current.partition}:execute-api:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:${aws_api_gateway_rest_api.custom_identity_provider_api.arn}/prod/GET/*"
          }
        ]
      }
    },
    {
      PolicyName = "TransferCanReadThisApi"
      PolicyDocument = {
        Version = "2012-10-17"
        Statement = [
          {
            Effect = "Allow"
            Action = [
              "apigateway:GET"
            ]
            Resource = "*"
          }
        ]
      }
    }
  ]
}

resource "aws_emrserverless_application" "get_user_config_lambda" {
  // CF Property(CodeUri) = "src/"
  // CF Property(Description) = "A function to lookup and return user data from AWS Secrets Manager."
  // CF Property(Handler) = "index.lambda_handler"
  // CF Property(Role) = aws_iam_role.lambda_execution_role.arn
  // CF Property(Runtime) = "python3.11"
  // CF Property(Environment) = {
  //   Variables = {
  //     SecretsManagerRegion = local.SecretsManagerRegionProvided ? var.secrets_manager_region : data.aws_region.current.name
  //   }
  // }
}

resource "aws_lambda_permission" "get_user_config_lambda_permission" {
  action = "lambda:invokeFunction"
  function_name = aws_emrserverless_application.get_user_config_lambda.arn
  principal = "apigateway.amazonaws.com"
  source_arn = "arn:${data.aws_partition.current.partition}:execute-api:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:${aws_api_gateway_rest_api.custom_identity_provider_api.arn}/*"
}

resource "aws_api_gateway_resource" "servers_resource" {
  rest_api_id = aws_api_gateway_rest_api.custom_identity_provider_api.arn
  parent_id = aws_api_gateway_rest_api.custom_identity_provider_api.root_resource_id
  path_part = "servers"
}

resource "aws_api_gateway_resource" "server_id_resource" {
  rest_api_id = aws_api_gateway_rest_api.custom_identity_provider_api.arn
  parent_id = aws_api_gateway_resource.servers_resource.id
  path_part = "{serverId}"
}

resource "aws_api_gateway_resource" "users_resource" {
  rest_api_id = aws_api_gateway_rest_api.custom_identity_provider_api.arn
  parent_id = aws_api_gateway_resource.server_id_resource.id
  path_part = "users"
}

resource "aws_api_gateway_resource" "user_name_resource" {
  rest_api_id = aws_api_gateway_rest_api.custom_identity_provider_api.arn
  parent_id = aws_api_gateway_resource.users_resource.id
  path_part = "{username}"
}

resource "aws_api_gateway_resource" "get_user_config_resource" {
  rest_api_id = aws_api_gateway_rest_api.custom_identity_provider_api.arn
  parent_id = aws_api_gateway_resource.user_name_resource.id
  path_part = "config"
}

resource "aws_api_gateway_method" "get_user_config_request" {
  authorization = "AWS_IAM"
  http_method = "GET"
  // CF Property(Integration) = {
  //   Type = "AWS"
  //   IntegrationHttpMethod = "POST"
  //   Uri = join("", ["arn:", data.aws_partition.current.partition, ":apigateway:", data.aws_region.current.name, ":lambda:path/2015-03-31/functions/", aws_emrserverless_application.get_user_config_lambda.arn, "/invocations"])
  //   IntegrationResponses = [
  //     {
  //       StatusCode = 200
  //     }
  //   ]
  //   RequestTemplates = {
  //     application/json = "{
  //   "username": "$util.urlDecode($input.params('username'))",
  //   "password": "$util.escapeJavaScript($input.params('Password')).replaceAll("\\'","'")",
  //   "protocol": "$input.params('protocol')",
  //   "serverId": "$input.params('serverId')",
  //   "sourceIp": "$input.params('sourceIp')"
  // }
  // "
  //   }
  // }
  request_parameters = {
    method.request.header.Password = false
  }
  resource_id = aws_api_gateway_resource.get_user_config_resource.id
  rest_api_id = aws_api_gateway_rest_api.custom_identity_provider_api.arn
  // CF Property(MethodResponses) = [
  //   {
  //     StatusCode = 200
  //     ResponseModels = {
  //       application/json = "UserConfigResponseModel"
  //     }
  //   }
  // ]
}

resource "aws_api_gateway_model" "get_user_config_response_model" {
  rest_api_id = aws_api_gateway_rest_api.custom_identity_provider_api.arn
  content_type = "application/json"
  description = "API response for GetUserConfig"
  name = "UserConfigResponseModel"
  schema = {
    $schema = "http://json-schema.org/draft-04/schema#"
    title = "UserUserConfig"
    type = "object"
    properties = {
      HomeDirectory = {
        type = "string"
      }
      Role = {
        type = "string"
      }
      Policy = {
        type = "string"
      }
      PublicKeys = {
        type = "array"
        items = {
          type = "string"
        }
      }
    }
  }
}

output "server_id" {
  value = aws_transfer_server.transfer_server.id
}

output "stack_arn" {
  value = local.stack_id
}

output "transfer_identity_provider_url" {
  description = "URL to pass to AWS Transfer CreateServer call as part of optional IdentityProviderDetails"
  value = join("", ["https://", aws_api_gateway_rest_api.custom_identity_provider_api.arn, ".execute-api.", data.aws_region.current.name, ".", data.aws_partition.current.dns_suffix, "/", aws_api_gateway_stage.api_stage.arn])
}

output "transfer_identity_provider_invocation_role" {
  description = "IAM Role to pass to AWS Transfer CreateServer call as part of optional IdentityProviderDetails"
  value = aws_iam_role.transfer_identity_provider_role.arn
}
mcrowley-axcess commented 6 months ago

Thank you! :) I manually rewrote the cf to tf for this one, but I will definitely keep this in my back pocket for the next one.