diff --git a/.golangci.yml b/.golangci.yml
index 65edc18f70..e4222ff797 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -66,6 +66,7 @@ linters:
- "!**/pkg/auth/factory/**"
- "!**/pkg/auth/types/aws_credentials.go"
- "!**/pkg/auth/types/github_oidc_credentials.go"
+ - "!**/internal/aws_utils/**"
- "$test"
deny:
# AWS: Identity and auth-related SDKs
diff --git a/errors/errors.go b/errors/errors.go
index cfabf10c97..07ac4a0632 100644
--- a/errors/errors.go
+++ b/errors/errors.go
@@ -90,6 +90,7 @@ var (
ErrInvalidTerraformSingleComponentAndMultiComponentFlags = errors.New("the single-component flags (`--from-plan`, `--planfile`) can't be used with the multi-component (bulk operations) flags (`--affected`, `--all`, `--query`, `--components`)")
ErrYamlFuncInvalidArguments = errors.New("invalid number of arguments in the Atmos YAML function")
+ ErrAwsGetCallerIdentity = errors.New("failed to get AWS caller identity")
ErrDescribeComponent = errors.New("failed to describe component")
ErrReadTerraformState = errors.New("failed to read Terraform state")
ErrEvaluateTerraformBackendVariable = errors.New("failed to evaluate terraform backend variable")
diff --git a/internal/aws_utils/aws_utils.go b/internal/aws_utils/aws_utils.go
index 0b3526399b..f6dfec612f 100644
--- a/internal/aws_utils/aws_utils.go
+++ b/internal/aws_utils/aws_utils.go
@@ -96,7 +96,7 @@ func LoadAWSConfigWithAuth(
baseCfg, err := config.LoadDefaultConfig(ctx, cfgOpts...)
if err != nil {
log.Debug("Failed to load AWS config", "error", err)
- return aws.Config{}, fmt.Errorf("%w: %v", errUtils.ErrLoadAwsConfig, err)
+ return aws.Config{}, fmt.Errorf("%w: %w", errUtils.ErrLoadAwsConfig, err)
}
log.Debug("Successfully loaded AWS SDK config", "region", baseCfg.Region)
@@ -126,3 +126,54 @@ func LoadAWSConfig(ctx context.Context, region string, roleArn string, assumeRol
return LoadAWSConfigWithAuth(ctx, region, roleArn, assumeRoleDuration, nil)
}
+
+// AWSCallerIdentityResult holds the result of GetAWSCallerIdentity.
+type AWSCallerIdentityResult struct {
+ Account string
+ Arn string
+ UserID string
+ Region string
+}
+
+// GetAWSCallerIdentity retrieves AWS caller identity using STS GetCallerIdentity API.
+// Returns account ID, ARN, user ID, and region.
+// This function keeps AWS SDK STS imports contained within aws_utils package.
+func GetAWSCallerIdentity(
+ ctx context.Context,
+ region string,
+ roleArn string,
+ assumeRoleDuration time.Duration,
+ authContext *schema.AWSAuthContext,
+) (*AWSCallerIdentityResult, error) {
+ defer perf.Track(nil, "aws_utils.GetAWSCallerIdentity")()
+
+ // Load AWS config.
+ cfg, err := LoadAWSConfigWithAuth(ctx, region, roleArn, assumeRoleDuration, authContext)
+ if err != nil {
+ return nil, err
+ }
+
+ // Create STS client and get caller identity.
+ stsClient := sts.NewFromConfig(cfg)
+ output, err := stsClient.GetCallerIdentity(ctx, &sts.GetCallerIdentityInput{})
+ if err != nil {
+ return nil, fmt.Errorf("%w: %w", errUtils.ErrAwsGetCallerIdentity, err)
+ }
+
+ result := &AWSCallerIdentityResult{
+ Region: cfg.Region,
+ }
+
+ // Extract values from pointers.
+ if output.Account != nil {
+ result.Account = *output.Account
+ }
+ if output.Arn != nil {
+ result.Arn = *output.Arn
+ }
+ if output.UserId != nil {
+ result.UserID = *output.UserId
+ }
+
+ return result, nil
+}
diff --git a/internal/exec/aws_getter.go b/internal/exec/aws_getter.go
new file mode 100644
index 0000000000..27109f8ca8
--- /dev/null
+++ b/internal/exec/aws_getter.go
@@ -0,0 +1,164 @@
+package exec
+
+import (
+ "context"
+ "fmt"
+ "sync"
+
+ awsUtils "github.com/cloudposse/atmos/internal/aws_utils"
+ log "github.com/cloudposse/atmos/pkg/logger"
+ "github.com/cloudposse/atmos/pkg/perf"
+ "github.com/cloudposse/atmos/pkg/schema"
+)
+
+// AWSCallerIdentity holds the information returned by AWS STS GetCallerIdentity.
+type AWSCallerIdentity struct {
+ Account string
+ Arn string
+ UserID string
+ Region string // The AWS region from the loaded config.
+}
+
+// AWSGetter provides an interface for retrieving AWS caller identity information.
+// This interface enables dependency injection and testability.
+//
+//go:generate go run go.uber.org/mock/mockgen@v0.6.0 -source=$GOFILE -destination=mock_aws_getter_test.go -package=exec
+type AWSGetter interface {
+ // GetCallerIdentity retrieves the AWS caller identity for the current credentials.
+ // Returns the account ID, ARN, and user ID of the calling identity.
+ GetCallerIdentity(
+ ctx context.Context,
+ atmosConfig *schema.AtmosConfiguration,
+ authContext *schema.AWSAuthContext,
+ ) (*AWSCallerIdentity, error)
+}
+
+// defaultAWSGetter is the production implementation that uses real AWS SDK calls.
+type defaultAWSGetter struct{}
+
+// GetCallerIdentity retrieves the AWS caller identity using the STS GetCallerIdentity API.
+func (d *defaultAWSGetter) GetCallerIdentity(
+ ctx context.Context,
+ atmosConfig *schema.AtmosConfiguration,
+ authContext *schema.AWSAuthContext,
+) (*AWSCallerIdentity, error) {
+ defer perf.Track(atmosConfig, "exec.AWSGetter.GetCallerIdentity")()
+
+ log.Debug("Getting AWS caller identity")
+
+ // Use the aws_utils helper to get caller identity (keeps AWS SDK imports in aws_utils).
+ result, err := awsUtils.GetAWSCallerIdentity(ctx, "", "", 0, authContext)
+ if err != nil {
+ return nil, err // Error already wrapped by aws_utils.
+ }
+
+ identity := &AWSCallerIdentity{
+ Account: result.Account,
+ Arn: result.Arn,
+ UserID: result.UserID,
+ Region: result.Region,
+ }
+
+ log.Debug("Retrieved AWS caller identity",
+ "account", identity.Account,
+ "arn", identity.Arn,
+ "user_id", identity.UserID,
+ "region", identity.Region,
+ )
+
+ return identity, nil
+}
+
+// awsGetter is the global instance used by YAML functions.
+// This allows test code to replace it with a mock.
+var awsGetter AWSGetter = &defaultAWSGetter{}
+
+// SetAWSGetter allows tests to inject a mock AWSGetter.
+// Returns a function to restore the original getter.
+func SetAWSGetter(getter AWSGetter) func() {
+ defer perf.Track(nil, "exec.SetAWSGetter")()
+
+ original := awsGetter
+ awsGetter = getter
+ return func() {
+ awsGetter = original
+ }
+}
+
+// cachedAWSIdentity holds the cached AWS caller identity.
+// The cache is per-CLI-invocation (stored in memory) to avoid repeated STS calls.
+type cachedAWSIdentity struct {
+ identity *AWSCallerIdentity
+ err error
+}
+
+var (
+ awsIdentityCache map[string]*cachedAWSIdentity
+ awsIdentityCacheMu sync.RWMutex
+)
+
+func init() {
+ awsIdentityCache = make(map[string]*cachedAWSIdentity)
+}
+
+// getCacheKey generates a cache key based on the auth context.
+// Different auth contexts (different credentials) get different cache entries.
+// Includes Profile, CredentialsFile, and ConfigFile since all three affect AWS config loading.
+func getCacheKey(authContext *schema.AWSAuthContext) string {
+ if authContext == nil {
+ return "default"
+ }
+ return fmt.Sprintf("%s:%s:%s", authContext.Profile, authContext.CredentialsFile, authContext.ConfigFile)
+}
+
+// getAWSCallerIdentityCached retrieves the AWS caller identity with caching.
+// Results are cached per auth context to avoid repeated STS calls within the same CLI invocation.
+func getAWSCallerIdentityCached(
+ ctx context.Context,
+ atmosConfig *schema.AtmosConfiguration,
+ authContext *schema.AWSAuthContext,
+) (*AWSCallerIdentity, error) {
+ defer perf.Track(atmosConfig, "exec.getAWSCallerIdentityCached")()
+
+ cacheKey := getCacheKey(authContext)
+
+ // Check cache first (read lock).
+ awsIdentityCacheMu.RLock()
+ if cached, ok := awsIdentityCache[cacheKey]; ok {
+ awsIdentityCacheMu.RUnlock()
+ log.Debug("Using cached AWS caller identity", "cache_key", cacheKey)
+ return cached.identity, cached.err
+ }
+ awsIdentityCacheMu.RUnlock()
+
+ // Cache miss - acquire write lock and fetch.
+ awsIdentityCacheMu.Lock()
+ defer awsIdentityCacheMu.Unlock()
+
+ // Double-check after acquiring write lock.
+ if cached, ok := awsIdentityCache[cacheKey]; ok {
+ log.Debug("Using cached AWS caller identity (double-check)", "cache_key", cacheKey)
+ return cached.identity, cached.err
+ }
+
+ // Fetch from AWS.
+ identity, err := awsGetter.GetCallerIdentity(ctx, atmosConfig, authContext)
+
+ // Cache the result (including errors to avoid repeated failed calls).
+ awsIdentityCache[cacheKey] = &cachedAWSIdentity{
+ identity: identity,
+ err: err,
+ }
+
+ return identity, err
+}
+
+// ClearAWSIdentityCache clears the AWS identity cache.
+// This is useful in tests or when credentials change during execution.
+func ClearAWSIdentityCache() {
+ defer perf.Track(nil, "exec.ClearAWSIdentityCache")()
+
+ awsIdentityCacheMu.Lock()
+ defer awsIdentityCacheMu.Unlock()
+ awsIdentityCache = make(map[string]*cachedAWSIdentity)
+}
diff --git a/internal/exec/yaml_func_aws.go b/internal/exec/yaml_func_aws.go
new file mode 100644
index 0000000000..7b8efb7963
--- /dev/null
+++ b/internal/exec/yaml_func_aws.go
@@ -0,0 +1,151 @@
+package exec
+
+import (
+ "context"
+
+ errUtils "github.com/cloudposse/atmos/errors"
+ log "github.com/cloudposse/atmos/pkg/logger"
+ "github.com/cloudposse/atmos/pkg/perf"
+ "github.com/cloudposse/atmos/pkg/schema"
+ u "github.com/cloudposse/atmos/pkg/utils"
+)
+
+const (
+ execAWSYAMLFunction = "Executing Atmos YAML function"
+ invalidYAMLFunction = "Invalid YAML function"
+ failedGetIdentity = "Failed to get AWS caller identity"
+ functionKey = "function"
+)
+
+// processTagAwsValue is a shared helper for AWS YAML functions.
+// It validates the input tag, retrieves AWS caller identity, and returns the requested value.
+func processTagAwsValue(
+ atmosConfig *schema.AtmosConfiguration,
+ input string,
+ expectedTag string,
+ stackInfo *schema.ConfigAndStacksInfo,
+ extractor func(*AWSCallerIdentity) string,
+) any {
+ log.Debug(execAWSYAMLFunction, functionKey, input)
+
+ // Validate the tag matches expected.
+ if input != expectedTag {
+ log.Error(invalidYAMLFunction, functionKey, input, "expected", expectedTag)
+ errUtils.CheckErrorPrintAndExit(errUtils.ErrYamlFuncInvalidArguments, "", "")
+ return nil
+ }
+
+ // Get auth context from stack info if available.
+ var authContext *schema.AWSAuthContext
+ if stackInfo != nil && stackInfo.AuthContext != nil && stackInfo.AuthContext.AWS != nil {
+ authContext = stackInfo.AuthContext.AWS
+ }
+
+ // Get the AWS caller identity (cached).
+ ctx := context.Background()
+ identity, err := getAWSCallerIdentityCached(ctx, atmosConfig, authContext)
+ if err != nil {
+ log.Error(failedGetIdentity, "error", err)
+ errUtils.CheckErrorPrintAndExit(err, "", "")
+ return nil
+ }
+
+ // Extract the requested value.
+ return extractor(identity)
+}
+
+// processTagAwsAccountID processes the !aws.account_id YAML function.
+// It returns the AWS account ID of the current caller identity.
+// The function takes no parameters.
+//
+// Usage in YAML:
+//
+// account_id: !aws.account_id
+func processTagAwsAccountID(
+ atmosConfig *schema.AtmosConfiguration,
+ input string,
+ stackInfo *schema.ConfigAndStacksInfo,
+) any {
+ defer perf.Track(atmosConfig, "exec.processTagAwsAccountID")()
+
+ result := processTagAwsValue(atmosConfig, input, u.AtmosYamlFuncAwsAccountID, stackInfo, func(id *AWSCallerIdentity) string {
+ return id.Account
+ })
+
+ if result != nil {
+ log.Debug("Resolved !aws.account_id", "account_id", result)
+ }
+ return result
+}
+
+// processTagAwsCallerIdentityArn processes the !aws.caller_identity_arn YAML function.
+// It returns the ARN of the current AWS caller identity.
+// The function takes no parameters.
+//
+// Usage in YAML:
+//
+// caller_arn: !aws.caller_identity_arn
+func processTagAwsCallerIdentityArn(
+ atmosConfig *schema.AtmosConfiguration,
+ input string,
+ stackInfo *schema.ConfigAndStacksInfo,
+) any {
+ defer perf.Track(atmosConfig, "exec.processTagAwsCallerIdentityArn")()
+
+ result := processTagAwsValue(atmosConfig, input, u.AtmosYamlFuncAwsCallerIdentityArn, stackInfo, func(id *AWSCallerIdentity) string {
+ return id.Arn
+ })
+
+ if result != nil {
+ log.Debug("Resolved !aws.caller_identity_arn", "arn", result)
+ }
+ return result
+}
+
+// processTagAwsCallerIdentityUserID processes the !aws.caller_identity_user_id YAML function.
+// It returns the unique user ID of the current AWS caller identity.
+// The function takes no parameters.
+//
+// Usage in YAML:
+//
+// user_id: !aws.caller_identity_user_id
+func processTagAwsCallerIdentityUserID(
+ atmosConfig *schema.AtmosConfiguration,
+ input string,
+ stackInfo *schema.ConfigAndStacksInfo,
+) any {
+ defer perf.Track(atmosConfig, "exec.processTagAwsCallerIdentityUserID")()
+
+ result := processTagAwsValue(atmosConfig, input, u.AtmosYamlFuncAwsCallerIdentityUserID, stackInfo, func(id *AWSCallerIdentity) string {
+ return id.UserID
+ })
+
+ if result != nil {
+ log.Debug("Resolved !aws.caller_identity_user_id", "user_id", result)
+ }
+ return result
+}
+
+// processTagAwsRegion processes the !aws.region YAML function.
+// It returns the AWS region from the current configuration.
+// The function takes no parameters.
+//
+// Usage in YAML:
+//
+// region: !aws.region
+func processTagAwsRegion(
+ atmosConfig *schema.AtmosConfiguration,
+ input string,
+ stackInfo *schema.ConfigAndStacksInfo,
+) any {
+ defer perf.Track(atmosConfig, "exec.processTagAwsRegion")()
+
+ result := processTagAwsValue(atmosConfig, input, u.AtmosYamlFuncAwsRegion, stackInfo, func(id *AWSCallerIdentity) string {
+ return id.Region
+ })
+
+ if result != nil {
+ log.Debug("Resolved !aws.region", "region", result)
+ }
+ return result
+}
diff --git a/internal/exec/yaml_func_aws_test.go b/internal/exec/yaml_func_aws_test.go
new file mode 100644
index 0000000000..67b955c47c
--- /dev/null
+++ b/internal/exec/yaml_func_aws_test.go
@@ -0,0 +1,828 @@
+package exec
+
+import (
+ "context"
+ "errors"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ errUtils "github.com/cloudposse/atmos/errors"
+ "github.com/cloudposse/atmos/pkg/schema"
+ u "github.com/cloudposse/atmos/pkg/utils"
+)
+
+// mockAWSGetter is a mock implementation of AWSGetter for testing.
+type mockAWSGetter struct {
+ identity *AWSCallerIdentity
+ err error
+}
+
+func (m *mockAWSGetter) GetCallerIdentity(
+ ctx context.Context,
+ atmosConfig *schema.AtmosConfiguration,
+ authContext *schema.AWSAuthContext,
+) (*AWSCallerIdentity, error) {
+ return m.identity, m.err
+}
+
+// runAWSYamlFuncTest is a helper that reduces duplication in AWS YAML function tests.
+func runAWSYamlFuncTest(
+ input string,
+ mockIdentity *AWSCallerIdentity,
+ mockErr error,
+ testFunc func(*schema.AtmosConfiguration, string, *schema.ConfigAndStacksInfo) any,
+) any {
+ // Clear cache before each test.
+ ClearAWSIdentityCache()
+
+ // Set up mock.
+ restore := SetAWSGetter(&mockAWSGetter{
+ identity: mockIdentity,
+ err: mockErr,
+ })
+ defer restore()
+
+ atmosConfig := &schema.AtmosConfiguration{}
+ stackInfo := &schema.ConfigAndStacksInfo{}
+
+ return testFunc(atmosConfig, input, stackInfo)
+}
+
+func TestProcessTagAwsAccountID(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ mockIdentity *AWSCallerIdentity
+ mockErr error
+ expectedResult string
+ shouldReturnNil bool
+ }{
+ {
+ name: "valid account ID",
+ input: u.AtmosYamlFuncAwsAccountID,
+ mockIdentity: &AWSCallerIdentity{
+ Account: "123456789012",
+ Arn: "arn:aws:iam::123456789012:user/testuser",
+ UserID: "AIDAEXAMPLE",
+ },
+ mockErr: nil,
+ expectedResult: "123456789012",
+ },
+ {
+ name: "different account ID",
+ input: u.AtmosYamlFuncAwsAccountID,
+ mockIdentity: &AWSCallerIdentity{
+ Account: "987654321098",
+ Arn: "arn:aws:sts::987654321098:assumed-role/TestRole/session",
+ UserID: "AROAEXAMPLE:session",
+ },
+ mockErr: nil,
+ expectedResult: "987654321098",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := runAWSYamlFuncTest(tt.input, tt.mockIdentity, tt.mockErr, processTagAwsAccountID)
+
+ if tt.shouldReturnNil {
+ assert.Nil(t, result)
+ } else {
+ assert.Equal(t, tt.expectedResult, result)
+ }
+ })
+ }
+}
+
+func TestProcessTagAwsCallerIdentityArn(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ mockIdentity *AWSCallerIdentity
+ mockErr error
+ expectedResult string
+ }{
+ {
+ name: "valid IAM user ARN",
+ input: u.AtmosYamlFuncAwsCallerIdentityArn,
+ mockIdentity: &AWSCallerIdentity{
+ Account: "123456789012",
+ Arn: "arn:aws:iam::123456789012:user/testuser",
+ UserID: "AIDAEXAMPLE",
+ },
+ mockErr: nil,
+ expectedResult: "arn:aws:iam::123456789012:user/testuser",
+ },
+ {
+ name: "valid assumed role ARN",
+ input: u.AtmosYamlFuncAwsCallerIdentityArn,
+ mockIdentity: &AWSCallerIdentity{
+ Account: "987654321098",
+ Arn: "arn:aws:sts::987654321098:assumed-role/AdminRole/session-name",
+ UserID: "AROAEXAMPLE:session-name",
+ },
+ mockErr: nil,
+ expectedResult: "arn:aws:sts::987654321098:assumed-role/AdminRole/session-name",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := runAWSYamlFuncTest(tt.input, tt.mockIdentity, tt.mockErr, processTagAwsCallerIdentityArn)
+ assert.Equal(t, tt.expectedResult, result)
+ })
+ }
+}
+
+func TestProcessTagAwsCallerIdentityUserID(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ mockIdentity *AWSCallerIdentity
+ mockErr error
+ expectedResult string
+ }{
+ {
+ name: "valid IAM user ID",
+ input: u.AtmosYamlFuncAwsCallerIdentityUserID,
+ mockIdentity: &AWSCallerIdentity{
+ Account: "123456789012",
+ Arn: "arn:aws:iam::123456789012:user/testuser",
+ UserID: "AIDAEXAMPLE123456789",
+ },
+ mockErr: nil,
+ expectedResult: "AIDAEXAMPLE123456789",
+ },
+ {
+ name: "valid assumed role user ID",
+ input: u.AtmosYamlFuncAwsCallerIdentityUserID,
+ mockIdentity: &AWSCallerIdentity{
+ Account: "987654321098",
+ Arn: "arn:aws:sts::987654321098:assumed-role/AdminRole/session-name",
+ UserID: "AROAEXAMPLE:session-name",
+ },
+ mockErr: nil,
+ expectedResult: "AROAEXAMPLE:session-name",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := runAWSYamlFuncTest(tt.input, tt.mockIdentity, tt.mockErr, processTagAwsCallerIdentityUserID)
+ assert.Equal(t, tt.expectedResult, result)
+ })
+ }
+}
+
+func TestProcessTagAwsRegion(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ mockIdentity *AWSCallerIdentity
+ mockErr error
+ expectedResult string
+ }{
+ {
+ name: "us-east-1 region",
+ input: u.AtmosYamlFuncAwsRegion,
+ mockIdentity: &AWSCallerIdentity{
+ Account: "123456789012",
+ Arn: "arn:aws:iam::123456789012:user/testuser",
+ UserID: "AIDAEXAMPLE",
+ Region: "us-east-1",
+ },
+ mockErr: nil,
+ expectedResult: "us-east-1",
+ },
+ {
+ name: "eu-west-1 region",
+ input: u.AtmosYamlFuncAwsRegion,
+ mockIdentity: &AWSCallerIdentity{
+ Account: "987654321098",
+ Arn: "arn:aws:sts::987654321098:assumed-role/AdminRole/session",
+ UserID: "AROAEXAMPLE:session",
+ Region: "eu-west-1",
+ },
+ mockErr: nil,
+ expectedResult: "eu-west-1",
+ },
+ {
+ name: "ap-northeast-1 region",
+ input: u.AtmosYamlFuncAwsRegion,
+ mockIdentity: &AWSCallerIdentity{
+ Account: "111111111111",
+ Arn: "arn:aws:iam::111111111111:root",
+ UserID: "111111111111",
+ Region: "ap-northeast-1",
+ },
+ mockErr: nil,
+ expectedResult: "ap-northeast-1",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := runAWSYamlFuncTest(tt.input, tt.mockIdentity, tt.mockErr, processTagAwsRegion)
+ assert.Equal(t, tt.expectedResult, result)
+ })
+ }
+}
+
+func TestAWSIdentityCache(t *testing.T) {
+ // Clear cache before test.
+ ClearAWSIdentityCache()
+
+ callCount := 0
+ mockGetter := &mockAWSGetter{
+ identity: &AWSCallerIdentity{
+ Account: "111111111111",
+ Arn: "arn:aws:iam::111111111111:user/cachetest",
+ UserID: "AIDACACHETEST",
+ },
+ err: nil,
+ }
+
+ // Wrap to count calls.
+ countingGetter := &countingAWSGetter{
+ wrapped: mockGetter,
+ callCount: &callCount,
+ }
+
+ restore := SetAWSGetter(countingGetter)
+ defer restore()
+
+ atmosConfig := &schema.AtmosConfiguration{}
+ ctx := context.Background()
+
+ // First call should hit the mock.
+ identity1, err := getAWSCallerIdentityCached(ctx, atmosConfig, nil)
+ require.NoError(t, err)
+ assert.Equal(t, "111111111111", identity1.Account)
+ assert.Equal(t, 1, callCount, "First call should invoke the getter")
+
+ // Second call with same auth context should use cache.
+ identity2, err := getAWSCallerIdentityCached(ctx, atmosConfig, nil)
+ require.NoError(t, err)
+ assert.Equal(t, "111111111111", identity2.Account)
+ assert.Equal(t, 1, callCount, "Second call should use cache, not invoke getter")
+
+ // Call with different auth context should hit mock again.
+ differentAuth := &schema.AWSAuthContext{
+ Profile: "different-profile",
+ CredentialsFile: "/different/path",
+ }
+ identity3, err := getAWSCallerIdentityCached(ctx, atmosConfig, differentAuth)
+ require.NoError(t, err)
+ assert.Equal(t, "111111111111", identity3.Account)
+ assert.Equal(t, 2, callCount, "Different auth context should invoke getter")
+
+ // Clear cache and verify next call hits mock.
+ ClearAWSIdentityCache()
+ identity4, err := getAWSCallerIdentityCached(ctx, atmosConfig, nil)
+ require.NoError(t, err)
+ assert.Equal(t, "111111111111", identity4.Account)
+ assert.Equal(t, 3, callCount, "After cache clear, should invoke getter")
+}
+
+// countingAWSGetter wraps another getter and counts calls.
+type countingAWSGetter struct {
+ wrapped AWSGetter
+ callCount *int
+}
+
+func (c *countingAWSGetter) GetCallerIdentity(
+ ctx context.Context,
+ atmosConfig *schema.AtmosConfiguration,
+ authContext *schema.AWSAuthContext,
+) (*AWSCallerIdentity, error) {
+ *c.callCount++
+ return c.wrapped.GetCallerIdentity(ctx, atmosConfig, authContext)
+}
+
+func TestAWSCacheWithErrors(t *testing.T) {
+ // Clear cache before test.
+ ClearAWSIdentityCache()
+
+ callCount := 0
+ expectedErr := errors.New("mock AWS error")
+ mockGetter := &mockAWSGetter{
+ identity: nil,
+ err: expectedErr,
+ }
+
+ countingGetter := &countingAWSGetter{
+ wrapped: mockGetter,
+ callCount: &callCount,
+ }
+
+ restore := SetAWSGetter(countingGetter)
+ defer restore()
+
+ atmosConfig := &schema.AtmosConfiguration{}
+ ctx := context.Background()
+
+ // First call should return error and cache it.
+ _, err := getAWSCallerIdentityCached(ctx, atmosConfig, nil)
+ require.Error(t, err)
+ assert.Equal(t, 1, callCount)
+
+ // Second call should return cached error.
+ _, err = getAWSCallerIdentityCached(ctx, atmosConfig, nil)
+ require.Error(t, err)
+ assert.Equal(t, 1, callCount, "Errors should be cached too")
+}
+
+func TestGetCacheKey(t *testing.T) {
+ tests := []struct {
+ name string
+ authContext *schema.AWSAuthContext
+ expected string
+ }{
+ {
+ name: "nil auth context",
+ authContext: nil,
+ expected: "default",
+ },
+ {
+ name: "with profile credentials and config file",
+ authContext: &schema.AWSAuthContext{
+ Profile: "my-profile",
+ CredentialsFile: "/home/user/.aws/credentials",
+ ConfigFile: "/home/user/.aws/config",
+ },
+ expected: "my-profile:/home/user/.aws/credentials:/home/user/.aws/config",
+ },
+ {
+ name: "empty profile",
+ authContext: &schema.AWSAuthContext{
+ Profile: "",
+ CredentialsFile: "/some/path",
+ ConfigFile: "/some/config",
+ },
+ expected: ":/some/path:/some/config",
+ },
+ {
+ name: "empty config file",
+ authContext: &schema.AWSAuthContext{
+ Profile: "prod",
+ CredentialsFile: "/creds",
+ ConfigFile: "",
+ },
+ expected: "prod:/creds:",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := getCacheKey(tt.authContext)
+ assert.Equal(t, tt.expected, result)
+ })
+ }
+}
+
+func TestAWSGetterInterface(t *testing.T) {
+ // Ensure defaultAWSGetter implements AWSGetter.
+ var _ AWSGetter = &defaultAWSGetter{}
+}
+
+func TestProcessTagAwsWithAuthContext(t *testing.T) {
+ // Clear cache before test.
+ ClearAWSIdentityCache()
+
+ // Set up mock with specific identity.
+ restore := SetAWSGetter(&mockAWSGetter{
+ identity: &AWSCallerIdentity{
+ Account: "222222222222",
+ Arn: "arn:aws:sts::222222222222:assumed-role/MyRole/session",
+ UserID: "AROAEXAMPLE:session",
+ },
+ err: nil,
+ })
+ defer restore()
+
+ atmosConfig := &schema.AtmosConfiguration{}
+
+ // Test with auth context in stackInfo.
+ stackInfo := &schema.ConfigAndStacksInfo{
+ AuthContext: &schema.AuthContext{
+ AWS: &schema.AWSAuthContext{
+ Profile: "test-profile",
+ CredentialsFile: "/test/credentials",
+ },
+ },
+ }
+
+ result := processTagAwsAccountID(atmosConfig, u.AtmosYamlFuncAwsAccountID, stackInfo)
+ assert.Equal(t, "222222222222", result)
+
+ // Clear cache for next test.
+ ClearAWSIdentityCache()
+
+ result = processTagAwsCallerIdentityArn(atmosConfig, u.AtmosYamlFuncAwsCallerIdentityArn, stackInfo)
+ assert.Equal(t, "arn:aws:sts::222222222222:assumed-role/MyRole/session", result)
+}
+
+func TestProcessSimpleTagsWithAWSFunctions(t *testing.T) {
+ // Clear cache before test.
+ ClearAWSIdentityCache()
+
+ // Set up mock.
+ restore := SetAWSGetter(&mockAWSGetter{
+ identity: &AWSCallerIdentity{
+ Account: "333333333333",
+ Arn: "arn:aws:iam::333333333333:user/integration-test",
+ UserID: "AIDAINTEGRATION",
+ Region: "us-west-2",
+ },
+ err: nil,
+ })
+ defer restore()
+
+ atmosConfig := &schema.AtmosConfiguration{}
+ stackInfo := &schema.ConfigAndStacksInfo{}
+
+ // Test !aws.account_id through processSimpleTags.
+ result, handled := processSimpleTags(atmosConfig, u.AtmosYamlFuncAwsAccountID, "", nil, stackInfo)
+ assert.True(t, handled)
+ assert.Equal(t, "333333333333", result)
+
+ // Clear cache for next test.
+ ClearAWSIdentityCache()
+
+ // Test !aws.caller_identity_arn through processSimpleTags.
+ result, handled = processSimpleTags(atmosConfig, u.AtmosYamlFuncAwsCallerIdentityArn, "", nil, stackInfo)
+ assert.True(t, handled)
+ assert.Equal(t, "arn:aws:iam::333333333333:user/integration-test", result)
+
+ // Clear cache for next test.
+ ClearAWSIdentityCache()
+
+ // Test !aws.caller_identity_user_id through processSimpleTags.
+ result, handled = processSimpleTags(atmosConfig, u.AtmosYamlFuncAwsCallerIdentityUserID, "", nil, stackInfo)
+ assert.True(t, handled)
+ assert.Equal(t, "AIDAINTEGRATION", result)
+
+ // Clear cache for next test.
+ ClearAWSIdentityCache()
+
+ // Test !aws.region through processSimpleTags.
+ result, handled = processSimpleTags(atmosConfig, u.AtmosYamlFuncAwsRegion, "", nil, stackInfo)
+ assert.True(t, handled)
+ assert.Equal(t, "us-west-2", result)
+}
+
+func TestProcessSimpleTagsSkipsAWSFunctions(t *testing.T) {
+ atmosConfig := &schema.AtmosConfiguration{}
+ stackInfo := &schema.ConfigAndStacksInfo{}
+
+ // Test that skipping works for aws.account_id.
+ skip := []string{"aws.account_id"}
+ result, handled := processSimpleTags(atmosConfig, u.AtmosYamlFuncAwsAccountID, "", skip, stackInfo)
+ assert.False(t, handled)
+ assert.Nil(t, result)
+
+ // Test that skipping works for aws.caller_identity_arn.
+ skip = []string{"aws.caller_identity_arn"}
+ result, handled = processSimpleTags(atmosConfig, u.AtmosYamlFuncAwsCallerIdentityArn, "", skip, stackInfo)
+ assert.False(t, handled)
+ assert.Nil(t, result)
+
+ // Test that skipping works for aws.caller_identity_user_id.
+ skip = []string{"aws.caller_identity_user_id"}
+ result, handled = processSimpleTags(atmosConfig, u.AtmosYamlFuncAwsCallerIdentityUserID, "", skip, stackInfo)
+ assert.False(t, handled)
+ assert.Nil(t, result)
+
+ // Test that skipping works for aws.region.
+ skip = []string{"aws.region"}
+ result, handled = processSimpleTags(atmosConfig, u.AtmosYamlFuncAwsRegion, "", skip, stackInfo)
+ assert.False(t, handled)
+ assert.Nil(t, result)
+}
+
+// TestAWSYamlFunctionConstants verifies the constants are defined correctly.
+func TestAWSYamlFunctionConstants(t *testing.T) {
+ assert.Equal(t, "!aws.account_id", u.AtmosYamlFuncAwsAccountID)
+ assert.Equal(t, "!aws.caller_identity_arn", u.AtmosYamlFuncAwsCallerIdentityArn)
+ assert.Equal(t, "!aws.caller_identity_user_id", u.AtmosYamlFuncAwsCallerIdentityUserID)
+ assert.Equal(t, "!aws.region", u.AtmosYamlFuncAwsRegion)
+}
+
+// TestErrorWrapping verifies that AWS errors are properly wrapped.
+func TestErrorWrapping(t *testing.T) {
+ // Clear cache before test.
+ ClearAWSIdentityCache()
+
+ underlyingErr := errors.New("network timeout")
+ restore := SetAWSGetter(&mockAWSGetter{
+ identity: nil,
+ err: underlyingErr,
+ })
+ defer restore()
+
+ atmosConfig := &schema.AtmosConfiguration{}
+ ctx := context.Background()
+
+ _, err := getAWSCallerIdentityCached(ctx, atmosConfig, nil)
+ require.Error(t, err)
+
+ // The error should be wrapped with the underlying error accessible.
+ assert.ErrorIs(t, err, underlyingErr)
+}
+
+// TestDefaultAWSGetterExists verifies the default getter exists.
+func TestDefaultAWSGetterExists(t *testing.T) {
+ // The awsGetter variable should be initialized.
+ assert.NotNil(t, awsGetter)
+
+ // It should be a *defaultAWSGetter.
+ _, ok := awsGetter.(*defaultAWSGetter)
+ assert.True(t, ok, "Default awsGetter should be *defaultAWSGetter")
+}
+
+// TestSetAWSGetterRestore verifies the restore function works.
+func TestSetAWSGetterRestore(t *testing.T) {
+ originalGetter := awsGetter
+
+ mockGetter := &mockAWSGetter{
+ identity: &AWSCallerIdentity{Account: "444444444444"},
+ }
+
+ restore := SetAWSGetter(mockGetter)
+
+ // Verify getter was replaced.
+ assert.Equal(t, mockGetter, awsGetter)
+
+ // Restore original.
+ restore()
+
+ // Verify original was restored.
+ assert.Equal(t, originalGetter, awsGetter)
+}
+
+// TestErrAwsGetCallerIdentity verifies the error constant exists.
+func TestErrAwsGetCallerIdentity(t *testing.T) {
+ assert.NotNil(t, errUtils.ErrAwsGetCallerIdentity)
+}
+
+// TestProcessTagAwsWithNilStackInfo verifies functions work with nil stackInfo.
+func TestProcessTagAwsWithNilStackInfo(t *testing.T) {
+ ClearAWSIdentityCache()
+
+ restore := SetAWSGetter(&mockAWSGetter{
+ identity: &AWSCallerIdentity{
+ Account: "555555555555",
+ Arn: "arn:aws:iam::555555555555:user/nil-test",
+ UserID: "AIDANILTEST",
+ Region: "us-west-1",
+ },
+ err: nil,
+ })
+ defer restore()
+
+ atmosConfig := &schema.AtmosConfiguration{}
+
+ // Test with nil stackInfo - should still work using default auth context.
+ result := processTagAwsAccountID(atmosConfig, u.AtmosYamlFuncAwsAccountID, nil)
+ assert.Equal(t, "555555555555", result)
+
+ ClearAWSIdentityCache()
+
+ result = processTagAwsRegion(atmosConfig, u.AtmosYamlFuncAwsRegion, nil)
+ assert.Equal(t, "us-west-1", result)
+}
+
+// TestProcessTagAwsWithPartialAuthContext verifies functions work with partial auth context.
+func TestProcessTagAwsWithPartialAuthContext(t *testing.T) {
+ ClearAWSIdentityCache()
+
+ restore := SetAWSGetter(&mockAWSGetter{
+ identity: &AWSCallerIdentity{
+ Account: "666666666666",
+ Arn: "arn:aws:iam::666666666666:user/partial-test",
+ UserID: "AIDAPARTIAL",
+ Region: "eu-central-1",
+ },
+ err: nil,
+ })
+ defer restore()
+
+ atmosConfig := &schema.AtmosConfiguration{}
+
+ // Test with stackInfo that has AuthContext but nil AWS.
+ stackInfo := &schema.ConfigAndStacksInfo{
+ AuthContext: &schema.AuthContext{
+ AWS: nil, // AWS is nil but AuthContext exists.
+ },
+ }
+
+ result := processTagAwsAccountID(atmosConfig, u.AtmosYamlFuncAwsAccountID, stackInfo)
+ assert.Equal(t, "666666666666", result)
+
+ ClearAWSIdentityCache()
+
+ // Test with stackInfo that has nil AuthContext.
+ stackInfo2 := &schema.ConfigAndStacksInfo{
+ AuthContext: nil,
+ }
+
+ result = processTagAwsCallerIdentityArn(atmosConfig, u.AtmosYamlFuncAwsCallerIdentityArn, stackInfo2)
+ assert.Equal(t, "arn:aws:iam::666666666666:user/partial-test", result)
+}
+
+// TestProcessTagAwsWithEmptyIdentityFields verifies handling of empty identity fields.
+func TestProcessTagAwsWithEmptyIdentityFields(t *testing.T) {
+ ClearAWSIdentityCache()
+
+ restore := SetAWSGetter(&mockAWSGetter{
+ identity: &AWSCallerIdentity{
+ Account: "",
+ Arn: "",
+ UserID: "",
+ Region: "",
+ },
+ err: nil,
+ })
+ defer restore()
+
+ atmosConfig := &schema.AtmosConfiguration{}
+ stackInfo := &schema.ConfigAndStacksInfo{}
+
+ // Empty values should still be returned (not nil).
+ result := processTagAwsAccountID(atmosConfig, u.AtmosYamlFuncAwsAccountID, stackInfo)
+ assert.Equal(t, "", result)
+
+ ClearAWSIdentityCache()
+
+ result = processTagAwsRegion(atmosConfig, u.AtmosYamlFuncAwsRegion, stackInfo)
+ assert.Equal(t, "", result)
+}
+
+// TestCacheConcurrency verifies cache is thread-safe under concurrent access.
+func TestCacheConcurrency(t *testing.T) {
+ ClearAWSIdentityCache()
+
+ callCount := 0
+ restore := SetAWSGetter(&countingAWSGetter{
+ wrapped: &mockAWSGetter{
+ identity: &AWSCallerIdentity{
+ Account: "777777777777",
+ Arn: "arn:aws:iam::777777777777:user/concurrent",
+ UserID: "AIDACONCURRENT",
+ Region: "ap-southeast-1",
+ },
+ err: nil,
+ },
+ callCount: &callCount,
+ })
+ defer restore()
+
+ atmosConfig := &schema.AtmosConfiguration{}
+ ctx := context.Background()
+
+ // Run multiple goroutines concurrently accessing the cache.
+ const numGoroutines = 50
+ done := make(chan bool, numGoroutines)
+
+ for i := 0; i < numGoroutines; i++ {
+ go func() {
+ identity, err := getAWSCallerIdentityCached(ctx, atmosConfig, nil)
+ assert.NoError(t, err)
+ assert.Equal(t, "777777777777", identity.Account)
+ done <- true
+ }()
+ }
+
+ // Wait for all goroutines to complete.
+ for i := 0; i < numGoroutines; i++ {
+ <-done
+ }
+
+ // Despite concurrent access, should only call getter once due to caching.
+ assert.Equal(t, 1, callCount, "Concurrent access should result in only one getter call")
+}
+
+// TestCacheKeyWithRegion verifies cache key includes all relevant auth context fields.
+func TestCacheKeyWithRegion(t *testing.T) {
+ tests := []struct {
+ name string
+ authContext *schema.AWSAuthContext
+ expected string
+ }{
+ {
+ name: "full auth context with region",
+ authContext: &schema.AWSAuthContext{
+ Profile: "prod",
+ CredentialsFile: "/prod/creds",
+ ConfigFile: "/prod/config",
+ Region: "us-east-1", // Region is in auth context but not in cache key.
+ },
+ expected: "prod:/prod/creds:/prod/config",
+ },
+ {
+ name: "same profile different region should have same cache key",
+ authContext: &schema.AWSAuthContext{
+ Profile: "prod",
+ CredentialsFile: "/prod/creds",
+ ConfigFile: "/prod/config",
+ Region: "eu-west-1", // Different region, same cache key.
+ },
+ expected: "prod:/prod/creds:/prod/config",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := getCacheKey(tt.authContext)
+ assert.Equal(t, tt.expected, result)
+ })
+ }
+}
+
+// TestAllAWSFunctionsShareCache verifies all four functions share the same cache.
+func TestAllAWSFunctionsShareCache(t *testing.T) {
+ ClearAWSIdentityCache()
+
+ callCount := 0
+ restore := SetAWSGetter(&countingAWSGetter{
+ wrapped: &mockAWSGetter{
+ identity: &AWSCallerIdentity{
+ Account: "888888888888",
+ Arn: "arn:aws:iam::888888888888:user/shared-cache",
+ UserID: "AIDASHARED",
+ Region: "sa-east-1",
+ },
+ err: nil,
+ },
+ callCount: &callCount,
+ })
+ defer restore()
+
+ atmosConfig := &schema.AtmosConfiguration{}
+ stackInfo := &schema.ConfigAndStacksInfo{}
+
+ // Call all four functions.
+ result1 := processTagAwsAccountID(atmosConfig, u.AtmosYamlFuncAwsAccountID, stackInfo)
+ result2 := processTagAwsCallerIdentityArn(atmosConfig, u.AtmosYamlFuncAwsCallerIdentityArn, stackInfo)
+ result3 := processTagAwsCallerIdentityUserID(atmosConfig, u.AtmosYamlFuncAwsCallerIdentityUserID, stackInfo)
+ result4 := processTagAwsRegion(atmosConfig, u.AtmosYamlFuncAwsRegion, stackInfo)
+
+ // Verify all results are correct.
+ assert.Equal(t, "888888888888", result1)
+ assert.Equal(t, "arn:aws:iam::888888888888:user/shared-cache", result2)
+ assert.Equal(t, "AIDASHARED", result3)
+ assert.Equal(t, "sa-east-1", result4)
+
+ // All functions should share the same cached result - only one getter call.
+ assert.Equal(t, 1, callCount, "All AWS functions should share the same cache")
+}
+
+// TestCacheWithDifferentConfigFiles verifies different config files get different cache entries.
+func TestCacheWithDifferentConfigFiles(t *testing.T) {
+ ClearAWSIdentityCache()
+
+ callCount := 0
+ restore := SetAWSGetter(&countingAWSGetter{
+ wrapped: &mockAWSGetter{
+ identity: &AWSCallerIdentity{
+ Account: "999999999999",
+ Arn: "arn:aws:iam::999999999999:user/config-test",
+ UserID: "AIDACONFIG",
+ Region: "me-south-1",
+ },
+ err: nil,
+ },
+ callCount: &callCount,
+ })
+ defer restore()
+
+ atmosConfig := &schema.AtmosConfiguration{}
+ ctx := context.Background()
+
+ // First call with config file A.
+ auth1 := &schema.AWSAuthContext{
+ Profile: "test",
+ CredentialsFile: "/creds",
+ ConfigFile: "/config-a",
+ }
+ _, err := getAWSCallerIdentityCached(ctx, atmosConfig, auth1)
+ require.NoError(t, err)
+ assert.Equal(t, 1, callCount)
+
+ // Second call with same config file A - should use cache.
+ _, err = getAWSCallerIdentityCached(ctx, atmosConfig, auth1)
+ require.NoError(t, err)
+ assert.Equal(t, 1, callCount, "Same config file should use cache")
+
+ // Third call with config file B - should call getter again.
+ auth2 := &schema.AWSAuthContext{
+ Profile: "test",
+ CredentialsFile: "/creds",
+ ConfigFile: "/config-b", // Different config file.
+ }
+ _, err = getAWSCallerIdentityCached(ctx, atmosConfig, auth2)
+ require.NoError(t, err)
+ assert.Equal(t, 2, callCount, "Different config file should result in new getter call")
+}
diff --git a/internal/exec/yaml_func_utils.go b/internal/exec/yaml_func_utils.go
index f238be35a9..d8d9ac877e 100644
--- a/internal/exec/yaml_func_utils.go
+++ b/internal/exec/yaml_func_utils.go
@@ -154,6 +154,19 @@ func processSimpleTags(
errUtils.CheckErrorPrintAndExit(err, "", "")
return res, true
}
+ // AWS YAML functions - note these check for exact match since they take no arguments.
+ if input == u.AtmosYamlFuncAwsAccountID && !skipFunc(skip, u.AtmosYamlFuncAwsAccountID) {
+ return processTagAwsAccountID(atmosConfig, input, stackInfo), true
+ }
+ if input == u.AtmosYamlFuncAwsCallerIdentityArn && !skipFunc(skip, u.AtmosYamlFuncAwsCallerIdentityArn) {
+ return processTagAwsCallerIdentityArn(atmosConfig, input, stackInfo), true
+ }
+ if input == u.AtmosYamlFuncAwsCallerIdentityUserID && !skipFunc(skip, u.AtmosYamlFuncAwsCallerIdentityUserID) {
+ return processTagAwsCallerIdentityUserID(atmosConfig, input, stackInfo), true
+ }
+ if input == u.AtmosYamlFuncAwsRegion && !skipFunc(skip, u.AtmosYamlFuncAwsRegion) {
+ return processTagAwsRegion(atmosConfig, input, stackInfo), true
+ }
return nil, false
}
diff --git a/pkg/utils/yaml_utils.go b/pkg/utils/yaml_utils.go
index 445fe0f56c..33173defd1 100644
--- a/pkg/utils/yaml_utils.go
+++ b/pkg/utils/yaml_utils.go
@@ -19,17 +19,21 @@ import (
const (
// Atmos YAML functions.
- AtmosYamlFuncExec = "!exec"
- AtmosYamlFuncStore = "!store"
- AtmosYamlFuncStoreGet = "!store.get"
- AtmosYamlFuncTemplate = "!template"
- AtmosYamlFuncTerraformOutput = "!terraform.output"
- AtmosYamlFuncTerraformState = "!terraform.state"
- AtmosYamlFuncEnv = "!env"
- AtmosYamlFuncInclude = "!include"
- AtmosYamlFuncIncludeRaw = "!include.raw"
- AtmosYamlFuncGitRoot = "!repo-root"
- AtmosYamlFuncRandom = "!random"
+ AtmosYamlFuncExec = "!exec"
+ AtmosYamlFuncStore = "!store"
+ AtmosYamlFuncStoreGet = "!store.get"
+ AtmosYamlFuncTemplate = "!template"
+ AtmosYamlFuncTerraformOutput = "!terraform.output"
+ AtmosYamlFuncTerraformState = "!terraform.state"
+ AtmosYamlFuncEnv = "!env"
+ AtmosYamlFuncInclude = "!include"
+ AtmosYamlFuncIncludeRaw = "!include.raw"
+ AtmosYamlFuncGitRoot = "!repo-root"
+ AtmosYamlFuncRandom = "!random"
+ AtmosYamlFuncAwsAccountID = "!aws.account_id"
+ AtmosYamlFuncAwsCallerIdentityArn = "!aws.caller_identity_arn"
+ AtmosYamlFuncAwsCallerIdentityUserID = "!aws.caller_identity_user_id"
+ AtmosYamlFuncAwsRegion = "!aws.region"
DefaultYAMLIndent = 2
@@ -48,20 +52,28 @@ var (
AtmosYamlFuncTerraformState,
AtmosYamlFuncEnv,
AtmosYamlFuncRandom,
+ AtmosYamlFuncAwsAccountID,
+ AtmosYamlFuncAwsCallerIdentityArn,
+ AtmosYamlFuncAwsCallerIdentityUserID,
+ AtmosYamlFuncAwsRegion,
}
// AtmosYamlTagsMap provides O(1) lookup for custom tag checking.
// This optimization replaces the O(n) SliceContainsString calls that were previously
// called 75M+ times, causing significant performance overhead.
atmosYamlTagsMap = map[string]bool{
- AtmosYamlFuncExec: true,
- AtmosYamlFuncStore: true,
- AtmosYamlFuncStoreGet: true,
- AtmosYamlFuncTemplate: true,
- AtmosYamlFuncTerraformOutput: true,
- AtmosYamlFuncTerraformState: true,
- AtmosYamlFuncEnv: true,
- AtmosYamlFuncRandom: true,
+ AtmosYamlFuncExec: true,
+ AtmosYamlFuncStore: true,
+ AtmosYamlFuncStoreGet: true,
+ AtmosYamlFuncTemplate: true,
+ AtmosYamlFuncTerraformOutput: true,
+ AtmosYamlFuncTerraformState: true,
+ AtmosYamlFuncEnv: true,
+ AtmosYamlFuncRandom: true,
+ AtmosYamlFuncAwsAccountID: true,
+ AtmosYamlFuncAwsCallerIdentityArn: true,
+ AtmosYamlFuncAwsCallerIdentityUserID: true,
+ AtmosYamlFuncAwsRegion: true,
}
// ParsedYAMLCache stores parsed yaml.Node objects and their position information
diff --git a/pkg/utils/yaml_utils_test.go b/pkg/utils/yaml_utils_test.go
index 27e2048daf..e5fc5631ec 100644
--- a/pkg/utils/yaml_utils_test.go
+++ b/pkg/utils/yaml_utils_test.go
@@ -763,6 +763,10 @@ func TestAtmosYamlTagsMap_ContainsAllTags(t *testing.T) {
AtmosYamlFuncTerraformState,
AtmosYamlFuncEnv,
AtmosYamlFuncRandom,
+ AtmosYamlFuncAwsAccountID,
+ AtmosYamlFuncAwsCallerIdentityArn,
+ AtmosYamlFuncAwsCallerIdentityUserID,
+ AtmosYamlFuncAwsRegion,
}
for _, tag := range expectedTags {
diff --git a/website/blog/2025-12-05-aws-yaml-functions.mdx b/website/blog/2025-12-05-aws-yaml-functions.mdx
new file mode 100644
index 0000000000..f7f5cfd67e
--- /dev/null
+++ b/website/blog/2025-12-05-aws-yaml-functions.mdx
@@ -0,0 +1,76 @@
+---
+slug: aws-yaml-functions
+title: "AWS YAML Functions for Identity and Region"
+authors: [osterman]
+tags: [feature, terraform, cloud-architecture]
+date: 2025-12-05
+---
+
+Atmos now includes four AWS YAML functions that retrieve identity and region information directly in stack configurations: `!aws.account_id`, `!aws.caller_identity_arn`, `!aws.caller_identity_user_id`, and `!aws.region`.
+
+
+
+## What's New
+
+These functions use the AWS STS GetCallerIdentity API to retrieve information about the current AWS credentials:
+
+| Function | Returns | Example Output |
+|----------|---------|----------------|
+| `!aws.account_id` | AWS account ID | `123456789012` |
+| `!aws.caller_identity_arn` | Full ARN of caller | `arn:aws:iam::123456789012:user/deploy` |
+| `!aws.caller_identity_user_id` | Unique user identifier | `AIDAEXAMPLE123456789` |
+| `!aws.region` | Current AWS region | `us-east-1` |
+
+## Usage
+
+```yaml
+components:
+ terraform:
+ s3-bucket:
+ vars:
+ # Pass account ID and region for bucket naming in Terraform
+ aws_account_id: !aws.account_id
+ aws_region: !aws.region
+
+ iam-policy:
+ vars:
+ # Reference caller identity in policies
+ deployer_arn: !aws.caller_identity_arn
+```
+
+## Use Cases
+
+**Dynamic Resource Naming**: Include account IDs in S3 bucket names, DynamoDB tables, or other resources that require globally unique names.
+
+**Audit and Logging**: Capture the ARN or user ID of the identity running deployments for audit trails.
+
+**Cross-Account References**: Build ARNs dynamically when referencing resources across accounts.
+
+**Region-Aware Configuration**: Configure resources based on the current AWS region without hardcoding values.
+
+## Caching and Performance
+
+All four functions share a single cached STS API call per CLI invocation. The first function call fetches the identity; subsequent calls use the cached result. This means using multiple AWS functions in the same configuration adds no extra API overhead.
+
+## Authentication Integration
+
+When using [Atmos Authentication](/cli/commands/auth/usage), these functions automatically use the credentials from the configured auth context. This works with AWS SSO, IAM roles, and other credential sources supported by the AWS SDK.
+
+## Comparison with Terragrunt
+
+These functions provide equivalent functionality to Terragrunt's built-in helpers:
+
+| Atmos | Terragrunt |
+|-------|------------|
+| `!aws.account_id` | `get_aws_account_id()` |
+| `!aws.caller_identity_arn` | `get_aws_caller_identity_arn()` |
+| `!aws.caller_identity_user_id` | `get_aws_caller_identity_user_id()` |
+| `!aws.region` | Similar to region from `get_aws_caller_identity()` |
+
+## Learn More
+
+- [`!aws.account_id`](/functions/yaml/aws.account-id) - Full documentation
+- [`!aws.caller_identity_arn`](/functions/yaml/aws.caller-identity-arn) - Full documentation
+- [`!aws.caller_identity_user_id`](/functions/yaml/aws.caller-identity-user-id) - Full documentation
+- [`!aws.region`](/functions/yaml/aws.region) - Full documentation
+- [YAML Functions Overview](/functions/yaml/) - All available YAML functions
diff --git a/website/docs/functions/yaml/aws.account-id.mdx b/website/docs/functions/yaml/aws.account-id.mdx
new file mode 100644
index 0000000000..9314ab370c
--- /dev/null
+++ b/website/docs/functions/yaml/aws.account-id.mdx
@@ -0,0 +1,166 @@
+---
+title: "!aws.account_id"
+sidebar_position: 11
+sidebar_label: "!aws.account_id"
+sidebar_class_name: command
+description: Retrieve the AWS account ID of the current caller identity
+---
+
+import File from '@site/src/components/File'
+import Intro from '@site/src/components/Intro'
+
+
+The `!aws.account_id` YAML function retrieves the AWS account ID of the current caller identity
+by calling the AWS STS `GetCallerIdentity` API.
+
+
+## Usage
+
+The `!aws.account_id` function takes no parameters:
+
+```yaml
+ # Get the AWS account ID of the current caller identity
+ account_id: !aws.account_id
+```
+
+## Arguments
+
+This function takes no arguments. It uses the AWS credentials from the environment or the
+Atmos authentication context if configured.
+
+## How It Works
+
+When processing the `!aws.account_id` YAML function, Atmos:
+
+1. **Loads AWS Configuration** - Uses the standard AWS SDK credential resolution chain:
+ - Environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`)
+ - Shared credentials file (`~/.aws/credentials`)
+ - Shared config file (`~/.aws/config`)
+ - EC2 Instance Metadata Service (IMDS)
+ - ECS Task credentials
+ - Web Identity Token credentials
+
+2. **Calls STS GetCallerIdentity** - Makes an API call to retrieve the caller identity
+
+3. **Returns Account ID** - Extracts and returns the 12-digit AWS account ID as a string
+
+:::note Atmos Auth Integration
+When using [Atmos Authentication](/cli/commands/auth/usage), the function automatically uses credentials
+from the active identity. This enables seamless integration with SSO, assume role chains, and other
+authentication methods configured in your `atmos.yaml`.
+:::
+
+## Caching
+
+The `!aws.account_id` function caches its results in memory for the duration of the CLI invocation.
+This means:
+
+- Multiple uses of `!aws.account_id` in the same command only make one STS API call
+- Different authentication contexts (e.g., different profiles) get separate cache entries
+- Each new CLI command starts with a fresh cache
+
+This caching significantly improves performance when the function is used in multiple places
+across your stack manifests.
+
+:::note Type-Aware Merging
+Atmos supports type-aware merging of YAML functions and concrete values, allowing them to coexist in the inheritance chain without type conflicts.
+See the full explanation: [YAML Function Merging](/reference/yaml-function-merging)
+:::
+
+## Examples
+
+### Basic Usage
+
+
+```yaml
+components:
+ terraform:
+ my-component:
+ vars:
+ # Inject the AWS account ID into Terraform variables
+ aws_account_id: !aws.account_id
+```
+
+
+### Use in Backend Configuration
+
+
+```yaml
+terraform:
+ backend:
+ s3:
+ # Pass account ID to Terraform for constructing bucket names
+ account_id: !aws.account_id
+```
+
+
+### Conditional Logic with Account ID
+
+
+```yaml
+components:
+ terraform:
+ security-baseline:
+ vars:
+ # Pass account ID for resource naming
+ account_id: !aws.account_id
+
+ # Use in tags
+ tags:
+ AccountId: !aws.account_id
+ ManagedBy: "atmos"
+```
+
+
+### Multiple Components Using Account ID
+
+
+```yaml
+components:
+ terraform:
+ # Account ID is fetched once and cached
+ vpc:
+ vars:
+ account_id: !aws.account_id
+
+ eks:
+ vars:
+ account_id: !aws.account_id # Uses cached value
+
+ rds:
+ vars:
+ account_id: !aws.account_id # Uses cached value
+```
+
+
+## Comparison with Terragrunt
+
+This function is equivalent to Terragrunt's `get_aws_account_id()` function:
+
+| Terragrunt | Atmos |
+|------------|-------|
+| `get_aws_account_id()` | `!aws.account_id` |
+
+## Error Handling
+
+If the function fails to retrieve the AWS caller identity (e.g., no credentials available,
+network issues, or insufficient permissions), Atmos will log an error and exit.
+
+Common error scenarios:
+- No AWS credentials configured
+- Expired credentials
+- Network connectivity issues
+- Missing STS permissions
+
+## Considerations
+
+- **Requires valid AWS credentials** - The function will fail if no valid credentials are available
+- **Network dependency** - Requires connectivity to AWS STS endpoint
+- **Performance** - Results are cached per CLI invocation, so there's minimal overhead when used multiple times
+- **IAM permissions** - Requires `sts:GetCallerIdentity` permission (usually available to all authenticated principals)
+
+## Related Functions
+
+- [!aws.caller_identity_arn](/functions/yaml/aws.caller-identity-arn) - Get the full ARN of the caller identity
+- [!aws.caller_identity_user_id](/functions/yaml/aws.caller-identity-user-id) - Get the unique user ID
+- [!aws.region](/functions/yaml/aws.region) - Get the AWS region
diff --git a/website/docs/functions/yaml/aws.caller-identity-arn.mdx b/website/docs/functions/yaml/aws.caller-identity-arn.mdx
new file mode 100644
index 0000000000..882abeba96
--- /dev/null
+++ b/website/docs/functions/yaml/aws.caller-identity-arn.mdx
@@ -0,0 +1,187 @@
+---
+title: "!aws.caller_identity_arn"
+sidebar_position: 12
+sidebar_label: "!aws.caller_identity_arn"
+sidebar_class_name: command
+description: Retrieve the ARN of the current AWS caller identity
+---
+
+import File from '@site/src/components/File'
+import Intro from '@site/src/components/Intro'
+
+
+The `!aws.caller_identity_arn` YAML function retrieves the Amazon Resource Name (ARN) of the current
+caller identity by calling the AWS STS `GetCallerIdentity` API.
+
+
+## Usage
+
+The `!aws.caller_identity_arn` function takes no parameters:
+
+```yaml
+ # Get the ARN of the current AWS caller identity
+ caller_arn: !aws.caller_identity_arn
+```
+
+## Arguments
+
+This function takes no arguments. It uses the AWS credentials from the environment or the
+Atmos authentication context if configured.
+
+## How It Works
+
+When processing the `!aws.caller_identity_arn` YAML function, Atmos:
+
+1. **Loads AWS Configuration** - Uses the standard AWS SDK credential resolution chain:
+ - Environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`)
+ - Shared credentials file (`~/.aws/credentials`)
+ - Shared config file (`~/.aws/config`)
+ - EC2 Instance Metadata Service (IMDS)
+ - ECS Task credentials
+ - Web Identity Token credentials
+
+2. **Calls STS GetCallerIdentity** - Makes an API call to retrieve the caller identity
+
+3. **Returns ARN** - Extracts and returns the full ARN of the calling identity
+
+The returned ARN format depends on the type of identity:
+
+| Identity Type | ARN Format |
+|--------------|------------|
+| IAM User | `arn:aws:iam::123456789012:user/username` |
+| IAM Role (assumed) | `arn:aws:sts::123456789012:assumed-role/RoleName/session-name` |
+| Root Account | `arn:aws:iam::123456789012:root` |
+| Federated User | `arn:aws:sts::123456789012:federated-user/username` |
+
+:::note Atmos Auth Integration
+When using [Atmos Authentication](/cli/commands/auth/usage), the function automatically uses credentials
+from the active identity. This enables seamless integration with SSO, assume role chains, and other
+authentication methods configured in your `atmos.yaml`.
+:::
+
+## Caching
+
+The `!aws.caller_identity_arn` function caches its results in memory for the duration of the CLI invocation.
+This means:
+
+- Multiple uses of `!aws.caller_identity_arn` in the same command only make one STS API call
+- Different authentication contexts (e.g., different profiles) get separate cache entries
+- Each new CLI command starts with a fresh cache
+
+The cache is shared with `!aws.account_id`, so using both functions only makes one STS API call.
+
+:::note Type-Aware Merging
+Atmos supports type-aware merging of YAML functions and concrete values, allowing them to coexist in the inheritance chain without type conflicts.
+See the full explanation: [YAML Function Merging](/reference/yaml-function-merging)
+:::
+
+## Examples
+
+### Basic Usage
+
+
+```yaml
+components:
+ terraform:
+ my-component:
+ vars:
+ # Inject the caller ARN into Terraform variables
+ caller_arn: !aws.caller_identity_arn
+```
+
+
+### Audit and Tagging
+
+
+```yaml
+components:
+ terraform:
+ infrastructure:
+ vars:
+ tags:
+ # Track who provisioned the resources
+ ProvisionedBy: !aws.caller_identity_arn
+ ManagedBy: "atmos"
+```
+
+
+### IAM Policy Configuration
+
+
+```yaml
+components:
+ terraform:
+ s3-bucket:
+ vars:
+ # Allow the current identity to access the bucket
+ allowed_principals:
+ - !aws.caller_identity_arn
+```
+
+
+### Combined with Account ID
+
+
+```yaml
+components:
+ terraform:
+ security-config:
+ vars:
+ # Both functions use the same cached STS call
+ aws_account_id: !aws.account_id
+ caller_arn: !aws.caller_identity_arn
+
+ # Useful for logging and auditing
+ deployment_context:
+ account: !aws.account_id
+ identity: !aws.caller_identity_arn
+```
+
+
+### Debugging and Troubleshooting
+
+
+```yaml
+components:
+ terraform:
+ debug-component:
+ vars:
+ # Verify which identity Atmos is using
+ debug_info:
+ current_identity_arn: !aws.caller_identity_arn
+ current_account_id: !aws.account_id
+```
+
+
+## Comparison with Terragrunt
+
+This function is equivalent to Terragrunt's `get_aws_caller_identity_arn()` function:
+
+| Terragrunt | Atmos |
+|------------|-------|
+| `get_aws_caller_identity_arn()` | `!aws.caller_identity_arn` |
+
+## Error Handling
+
+If the function fails to retrieve the AWS caller identity (e.g., no credentials available,
+network issues, or insufficient permissions), Atmos will log an error and exit.
+
+Common error scenarios:
+- No AWS credentials configured
+- Expired credentials
+- Network connectivity issues
+- Missing STS permissions
+
+## Considerations
+
+- **Requires valid AWS credentials** - The function will fail if no valid credentials are available
+- **Network dependency** - Requires connectivity to AWS STS endpoint
+- **Performance** - Results are cached per CLI invocation, so there's minimal overhead when used multiple times
+- **IAM permissions** - Requires `sts:GetCallerIdentity` permission (usually available to all authenticated principals)
+- **ARN format varies** - The format differs based on identity type (user, assumed role, etc.)
+
+## Related Functions
+
+- [!aws.account_id](/functions/yaml/aws.account-id) - Get the AWS account ID
+- [!aws.caller_identity_user_id](/functions/yaml/aws.caller-identity-user-id) - Get the unique user ID
+- [!aws.region](/functions/yaml/aws.region) - Get the AWS region
diff --git a/website/docs/functions/yaml/aws.caller-identity-user-id.mdx b/website/docs/functions/yaml/aws.caller-identity-user-id.mdx
new file mode 100644
index 0000000000..6bdb7993a0
--- /dev/null
+++ b/website/docs/functions/yaml/aws.caller-identity-user-id.mdx
@@ -0,0 +1,146 @@
+---
+title: "!aws.caller_identity_user_id"
+sidebar_position: 13
+sidebar_label: "!aws.caller_identity_user_id"
+sidebar_class_name: command
+description: Retrieve the unique user ID of the current AWS caller identity
+---
+
+import File from '@site/src/components/File'
+import Intro from '@site/src/components/Intro'
+
+
+The `!aws.caller_identity_user_id` YAML function retrieves the unique user ID of the current
+caller identity by calling the AWS STS `GetCallerIdentity` API.
+
+
+## Usage
+
+The `!aws.caller_identity_user_id` function takes no parameters:
+
+```yaml
+ # Get the user ID of the current AWS caller identity
+ user_id: !aws.caller_identity_user_id
+```
+
+## Arguments
+
+This function takes no arguments. It uses the AWS credentials from the environment or the
+Atmos authentication context if configured.
+
+## How It Works
+
+When processing the `!aws.caller_identity_user_id` YAML function, Atmos:
+
+1. **Loads AWS Configuration** - Uses the standard AWS SDK credential resolution chain:
+ - Environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`)
+ - Shared credentials file (`~/.aws/credentials`)
+ - Shared config file (`~/.aws/config`)
+ - EC2 Instance Metadata Service (IMDS)
+ - ECS Task credentials
+ - Web Identity Token credentials
+
+2. **Calls STS GetCallerIdentity** - Makes an API call to retrieve the caller identity
+
+3. **Returns User ID** - Extracts and returns the unique user ID
+
+The returned user ID format depends on the type of identity:
+
+| Identity Type | User ID Format |
+|--------------|----------------|
+| IAM User | `AIDAXXXXXXXXXXEXAMPLE` (21 character unique ID) |
+| IAM Role (assumed) | `AROAXXXXXXXXXXEXAMPLE:session-name` |
+| Root Account | The account ID (e.g., `123456789012`) |
+| Federated User | `account-id:caller-specified-name` |
+
+:::note Atmos Auth Integration
+When using [Atmos Authentication](/cli/commands/auth/usage), the function automatically uses credentials
+from the active identity. This enables seamless integration with SSO, assume role chains, and other
+authentication methods configured in your `atmos.yaml`.
+:::
+
+## Caching
+
+The `!aws.caller_identity_user_id` function shares its cache with other AWS identity functions
+(`!aws.account_id`, `!aws.caller_identity_arn`, `!aws.region`). This means:
+
+- All AWS identity functions share a single STS API call
+- Results are cached per CLI invocation
+- Different authentication contexts get separate cache entries
+
+:::note Type-Aware Merging
+Atmos supports type-aware merging of YAML functions and concrete values, allowing them to coexist in the inheritance chain without type conflicts.
+See the full explanation: [YAML Function Merging](/reference/yaml-function-merging)
+:::
+
+## Examples
+
+### Basic Usage
+
+
+```yaml
+components:
+ terraform:
+ my-component:
+ vars:
+ # Inject the caller user ID into Terraform variables
+ caller_user_id: !aws.caller_identity_user_id
+```
+
+
+### Audit Trail
+
+
+```yaml
+components:
+ terraform:
+ infrastructure:
+ vars:
+ tags:
+ # Track unique user ID for audit purposes
+ ProvisionedByUserID: !aws.caller_identity_user_id
+ ManagedBy: "atmos"
+```
+
+
+### Combined with Other AWS Functions
+
+
+```yaml
+components:
+ terraform:
+ audit-config:
+ vars:
+ # All AWS functions share the same cached STS call
+ aws_account_id: !aws.account_id
+ caller_arn: !aws.caller_identity_arn
+ caller_user_id: !aws.caller_identity_user_id
+ aws_region: !aws.region
+```
+
+
+## Comparison with Terragrunt
+
+This function is equivalent to Terragrunt's `get_aws_caller_identity_user_id()` function:
+
+| Terragrunt | Atmos |
+|------------|-------|
+| `get_aws_caller_identity_user_id()` | `!aws.caller_identity_user_id` |
+
+## Error Handling
+
+If the function fails to retrieve the AWS caller identity (e.g., no credentials available,
+network issues, or insufficient permissions), Atmos will log an error and exit.
+
+## Considerations
+
+- **Requires valid AWS credentials** - The function will fail if no valid credentials are available
+- **Network dependency** - Requires connectivity to AWS STS endpoint
+- **Performance** - Results are cached and shared with other AWS identity functions
+- **IAM permissions** - Requires `sts:GetCallerIdentity` permission
+
+## Related Functions
+
+- [!aws.account_id](/functions/yaml/aws.account-id) - Get the AWS account ID
+- [!aws.caller_identity_arn](/functions/yaml/aws.caller-identity-arn) - Get the full ARN
+- [!aws.region](/functions/yaml/aws.region) - Get the AWS region
diff --git a/website/docs/functions/yaml/aws.region.mdx b/website/docs/functions/yaml/aws.region.mdx
new file mode 100644
index 0000000000..f6212beac6
--- /dev/null
+++ b/website/docs/functions/yaml/aws.region.mdx
@@ -0,0 +1,178 @@
+---
+title: "!aws.region"
+sidebar_position: 14
+sidebar_label: "!aws.region"
+sidebar_class_name: command
+description: Retrieve the current AWS region from the SDK configuration
+---
+
+import File from '@site/src/components/File'
+import Intro from '@site/src/components/Intro'
+
+
+The `!aws.region` YAML function retrieves the AWS region from the current SDK configuration.
+This is the region that would be used for AWS API calls.
+
+
+## Usage
+
+The `!aws.region` function takes no parameters:
+
+```yaml
+ # Get the current AWS region
+ region: !aws.region
+```
+
+## Arguments
+
+This function takes no arguments. It uses the AWS credentials and configuration from the environment
+or the Atmos authentication context if configured.
+
+## How It Works
+
+When processing the `!aws.region` YAML function, Atmos:
+
+1. **Loads AWS Configuration** - Uses the standard AWS SDK credential resolution chain:
+ - Environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`)
+ - Shared credentials file (`~/.aws/credentials`)
+ - Shared config file (`~/.aws/config`)
+ - EC2 Instance Metadata Service (IMDS)
+ - ECS Task credentials
+ - Web Identity Token credentials
+
+2. **Calls STS GetCallerIdentity** - Makes an API call to retrieve the caller identity
+
+3. **Returns Region** - Extracts and returns the region from the loaded AWS configuration
+
+:::note Atmos Auth Integration
+When using [Atmos Authentication](/cli/commands/auth/usage), the function automatically uses credentials
+from the active identity. This enables seamless integration with SSO, assume role chains, and other
+authentication methods configured in your `atmos.yaml`.
+:::
+
+## Caching
+
+The `!aws.region` function shares its cache with other AWS identity functions
+(`!aws.account_id`, `!aws.caller_identity_arn`, `!aws.caller_identity_user_id`). This means:
+
+- All AWS identity functions share a single STS API call
+- Results are cached per CLI invocation
+- Different authentication contexts get separate cache entries
+
+:::note Type-Aware Merging
+Atmos supports type-aware merging of YAML functions and concrete values, allowing them to coexist in the inheritance chain without type conflicts.
+See the full explanation: [YAML Function Merging](/reference/yaml-function-merging)
+:::
+
+## Examples
+
+### Basic Usage
+
+
+```yaml
+components:
+ terraform:
+ my-component:
+ vars:
+ # Inject the AWS region into Terraform variables
+ aws_region: !aws.region
+```
+
+
+### Provider Configuration
+
+
+```yaml
+components:
+ terraform:
+ vpc:
+ vars:
+ # Use the current region for the VPC
+ region: !aws.region
+
+ providers:
+ aws:
+ region: !aws.region
+```
+
+
+### Resource Naming with Region
+
+
+```yaml
+components:
+ terraform:
+ s3-bucket:
+ vars:
+ # Pass region as separate var for Terraform to construct names
+ aws_region: !aws.region
+
+ tags:
+ Region: !aws.region
+ Environment: "production"
+```
+
+
+### Combined with Other AWS Functions
+
+
+```yaml
+components:
+ terraform:
+ infrastructure:
+ vars:
+ # All AWS functions share the same cached STS call
+ aws_account_id: !aws.account_id
+ aws_region: !aws.region
+ caller_arn: !aws.caller_identity_arn
+```
+
+
+### Cross-Region Configuration
+
+
+```yaml
+components:
+ terraform:
+ replication-config:
+ vars:
+ # Use the current region as the source
+ source_region: !aws.region
+
+ # Replicate to a different region (hardcoded destination)
+ destination_region: "eu-west-1"
+```
+
+
+## Region Resolution Order
+
+The region is resolved in the following order:
+
+1. **Atmos Auth Context** - If using Atmos authentication with a region specified
+2. **AWS_REGION environment variable**
+3. **AWS_DEFAULT_REGION environment variable**
+4. **Shared config file** (`~/.aws/config`) - The `region` setting for the active profile
+5. **Instance metadata** - For EC2 instances or ECS tasks
+
+## Error Handling
+
+If the function fails to determine the AWS region (e.g., no credentials available,
+no region configured), Atmos will log an error and exit.
+
+Common error scenarios:
+- No AWS credentials configured
+- No region specified in credentials, config, or environment
+- Network connectivity issues (for STS call)
+
+## Considerations
+
+- **Requires valid AWS credentials** - The function needs credentials to make the STS call
+- **Region must be configured** - Either via environment, config file, or Atmos auth
+- **Performance** - Results are cached and shared with other AWS identity functions
+- **IAM permissions** - Requires `sts:GetCallerIdentity` permission
+
+## Related Functions
+
+- [!aws.account_id](/functions/yaml/aws.account-id) - Get the AWS account ID
+- [!aws.caller_identity_arn](/functions/yaml/aws.caller-identity-arn) - Get the full ARN
+- [!aws.caller_identity_user_id](/functions/yaml/aws.caller-identity-user-id) - Get the user ID
diff --git a/website/docs/functions/yaml/index.mdx b/website/docs/functions/yaml/index.mdx
index 8464536e70..2874917b7d 100644
--- a/website/docs/functions/yaml/index.mdx
+++ b/website/docs/functions/yaml/index.mdx
@@ -78,6 +78,18 @@ YAML supports three types of data: core, defined, and user-defined.
- The [__`!random`__](/functions/yaml/random) YAML function generates a cryptographically secure random integer
within a specified range, useful for generating random port numbers or IDs
+ - The [__`!aws.account_id`__](/functions/yaml/aws.account-id) YAML function retrieves the AWS account ID
+ of the current caller identity using STS GetCallerIdentity
+
+ - The [__`!aws.caller_identity_arn`__](/functions/yaml/aws.caller-identity-arn) YAML function retrieves the full ARN
+ of the current AWS caller identity using STS GetCallerIdentity
+
+ - The [__`!aws.caller_identity_user_id`__](/functions/yaml/aws.caller-identity-user-id) YAML function retrieves the unique user ID
+ of the current AWS caller identity using STS GetCallerIdentity
+
+ - The [__`!aws.region`__](/functions/yaml/aws.region) YAML function retrieves the AWS region
+ from the current SDK configuration
+
:::tip
You can combine [Atmos Stack Manifest Templating](/templates) with Atmos YAML functions within the same stack configuration.
Atmos processes templates first, followed by YAML functions, enabling you to dynamically provide parameters to the YAML functions.
@@ -154,6 +166,18 @@ components:
# Generate a random port number between 1024 and 65535
app_port: !random 1024 65535
+
+ # Get the current AWS account ID
+ aws_account_id: !aws.account_id
+
+ # Get the ARN of the current AWS caller identity
+ aws_caller_arn: !aws.caller_identity_arn
+
+ # Get the unique user ID of the current AWS caller identity
+ aws_user_id: !aws.caller_identity_user_id
+
+ # Get the current AWS region
+ aws_region: !aws.region
```