I've used CloudFormation to maintain a large-scale web-application on AWS infrastructure since 2016. I've been continuously evaluating Terraform over the years (it is indeed maturing quickly), and my team has stuck with CloudFormation for now though we're considering a potential future migration. A year ago I would have recommended CloudFormation for anyone managing an AWS-exclusive (or mostly-AWS) deployment, but today I would say both are equally good choices (and Terraform wins handily if your use-case includes _anything_ non-AWS.) In addition to being able to manage non-AWS infrastructure, here are some more arguments for TF being the better option for a new infrastructure build today:
- CloudFormation is indeed AWS-supported, but it's also closed-source, which means that you never know exactly what is happening under the hood in resource implementations. I've spent days of back-and-forth communication with AWS support (weeks in one case when working with the AutoScalingRollingUpdate UpdatePolicy) trying to clarify/confirm implementation details for various resource-implementation edge-cases that were too time-consuming and/or risky to exhaustively test myself. With Terraform, I could just read the provider implementation code directly and confidently know exactly what it's going to do.
- CloudFormation has an inconsistent and opaque development roadmap. Though CloudFormation generally has more complete coverage of AWS-service resources than Terraform's AWS provider (at least as of a year ago, I haven't done a more recent comparison), you will still encounter situations where a new service, or new API properties (reflecting new/updated features) on existing services, do not yet have a CloudFormation resource to match. Unlike the AWS SDKs which have super-quick, mostly automated release cycles, CloudFormation resource updates are developed separately and can take months or years until AWS happens to update the resource. Sure, you can deploy a Custom Resource to fill in the gaps with the SDK, but since it's community-supported, Terraform development is both much more responsive to incremental updates to evolving services and since the development is done in an open-source project it's much easier to track and know when to expect upcoming feature support.
- CloudFormation doesn't support importing existing infrastructure. This adds a big extra barrier to evolving a complex system, as it prevents you from being able to incrementally create/modify resources through the console and then 'lock down' existing resources into your state/configuration after the fact. Instead, you need to re-create new resources within the stack and then migrate any dependencies over to the new resource. This migration effort can be substantial when dealing with live production systems- I once spent several weeks carefully migrating several old production CloudFront distributions to CloudFormation-managed resources, and still incurred several minutes of unexpected downtime along the way. This effort is completely eliminated using Terraform's import feature.