Repository: aws-cloudformation/cfn-lint

Issue:
W1033 gets confused by complex Split-Join-Join
### CloudFormation Lint Version

cfn-lint 1.34.0

### What operating system are you using?

Linux

### Describe the bug

We have a complex split-join-join situation, in order to allow a comma-separated parameter to be optionally appended to a role's managed policies. It currently warns,

```console
W1033 'Join ["' does not match 'arn:(aws[a-zA-Z-]*)?:iam::(\\d{12}|aws):policy/[a-zA-Z_0-9+=,.@\\-_/]+' when 'Fn::Split' is resolved
/home/patrick/code/freckle/infra/templates/x.yaml:25:7

W1033 '"' does not match 'arn:(aws[a-zA-Z-]*)?:iam::(\\d{12}|aws):policy/[a-zA-Z_0-9+=,.@\\-_/]+' when 'Fn::Split' is resolved
/home/patrick/code/freckle/infra/templates/x.yaml:25:7

W1033 ' !Ref "ExtraManagedPolicyArns"]' does not match 'arn:(aws[a-zA-Z-]*)?:iam::(\\d{12}|aws):policy/[a-zA-Z_0-9+=,.@\\-_/]+' when 'Fn::Split' is resolved
/home/patrick/code/freckle/infra/templates/x.yaml:25:7
```

Now, I know that there are probably other ways to construct this (though we found this one to be the way to avoid duplicating the "all the time" Arns between cases), and I'm not surprised the validation breaks -- we will likely just ignore it.

Still, I wanted to report it in case you wanted a complex test case, or there was something else I was missing about the situation to avoid the warning.

### Expected behavior

As far as we know this construct works fine, there should be no warnings. The warning message also seems to indicate it is operating on still-unresolved intrinsic definitions, rather than resolved values, which could be a general problem worth solving.

### Reproduction template

```yaml
Parameters:
  ExtraManagedPolicyArns:
    Description: |
      Comma-separated list of extra ManagedPolicy Arns to attach to the
      ExecutionRole for ECS tasks

    Type: CommaDelimitedList
    Default: ""

Conditions:
  HasExtraManagedPolicies:
    !Not [!Equals [!Join ["", !Ref ExtraManagedPolicyArns], ""]]

Resources:
  ExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service: ecs-tasks.amazonaws.com
        Version: "2012-10-17"
      ManagedPolicyArns: !Split
        - ","
        - !Join
          - ","
          - - "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
            - !If
              - HasExtraManagedPolicies
              - Join [",", !Ref "ExtraManagedPolicyArns"]
              - !Ref "AWS::NoValue"
```

Discussion:
@kddejong: Is `- Join [",", !Ref "ExtraManagedPolicyArns"]` supposed to be `- !Join [",", !Ref "ExtraManagedPolicyArns"]`?@pbrisbin: Aha, good catch. That brings the warning down to,

```console
W1033 '' does not match 'arn:(aws[a-zA-Z-]*)?:iam::(\\d{12}|aws):policy/[a-zA-Z_0-9+=,.@\\-_/]+' when 'Fn::Split' is resolved
/home/patrick/code/freckle/infra/templates/platform-app-iam.yaml:25:7
```

Which is perhaps reasonable to report given the default of `""`, since the validation is not taking the `!If` into account?@kddejong: I'm looking into that now.  Even if the !If is `false` you still have one item in the list so it can't be empty.  This one doesn't look right.@pbrisbin: 👍 In the fuller template we have other examples that don't warn, but look the same (without the `Join` bug too):

Doesn't warn:

```yaml
      ManagedPolicyArns: !Split
        - ","
        - !Join
          - ","
          - - !Sub "arn:aws:iam::${AWS::AccountId}:policy/platform/CLI"
            - !Sub "arn:aws:iam::${AWS::AccountId}:policy/platform/${App}-assets"
            - !Sub "arn:aws:iam::${AWS::AccountId}:policy/platform/${App}-registry"
            - !Ref "DeployPolicy"
            - !If
              - HasCIRoleManagedPolicyArns
              - !Join [",", !Ref "CIRoleManagedPolicyArns"]
              - !Ref "AWS::NoValue"

```

Does warn:

```yaml
      ManagedPolicyArns: !Split
        - ","
        - !Join
          - ","
          - - "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
            - !If
              - HasExecutionRoleManagedPolicyArns
              - !Join [",", !Ref "ExecutionRoleManagedPolicyArns"]
              - !Ref "AWS::NoValue"
```

I'm staring hard, but just can't see the difference.@kddejong: This string is being sent to the split `'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy,'` which is resulting in an empty item.  Looks like this is because of ` - !Join [",", !Ref "ExtraManagedPolicyArns"]` so not the `!Ref "AWS::NoValue"`.  This shouldn't be happening because of the Condition so I need evaluate why that Condition isn't failing this scenario.  @kddejong: I have a fix in for the final portion of this issue. Basically, when `HasExecutionRoleManagedPolicyArns` is `True` the `!Join [",", !Ref "ExecutionRoleManagedPolicyArns"]` can't result in `""` which is getting evaluated.  We have the logic to determine if this is possible but it wasn't getting done property when we are using `Fn::If` inside a function. 

Solution patch:
diff --git a/src/cfnlint/context/__init__.py b/src/cfnlint/context/__init__.py
index 716204d27..390bfa15a 100644
--- a/src/cfnlint/context/__init__.py
+++ b/src/cfnlint/context/__init__.py
@@ -1,3 +1,4 @@
 __all__ = ["Context", "create_context_for_template"]
 
+from cfnlint.context.conditions.exceptions import Unsatisfiable
 from cfnlint.context.context import Context, Path, Resource, create_context_for_template
diff --git a/src/cfnlint/jsonschema/_resolvers_cfn.py b/src/cfnlint/jsonschema/_resolvers_cfn.py
index 58ea7da29..a862b2bb0 100644
--- a/src/cfnlint/jsonschema/_resolvers_cfn.py
+++ b/src/cfnlint/jsonschema/_resolvers_cfn.py
@@ -12,6 +12,7 @@ from typing import Any, Iterator
 
 import regex as re
 
+from cfnlint.context.conditions.exceptions import Unsatisfiable
 from cfnlint.helpers import (
     AVAILABILITY_ZONES,
     PSEUDOPARAMS,
@@ -479,7 +480,6 @@ def sub(validator: Validator, instance: Any) -> ResolutionResult:
             )
         )
         yield from _sub_string(resolved_validator, instance)
-        # yield from _sub_string(validator, instance)
 
 
 def if_(validator: Validator, instance: Any) -> ResolutionResult:
@@ -491,15 +491,21 @@ def if_(validator: Validator, instance: Any) -> ResolutionResult:
 
     for i in [1, 2]:
         for value, v, err in validator.resolve_value(instance[i]):
-            yield (
-                value,
-                v.evolve(
-                    context=v.context.evolve(
-                        path=v.context.path.evolve(value_path=deque([i])),
+            try:
+                yield (
+                    value,
+                    v.evolve(
+                        context=v.context.evolve(
+                            path=v.context.path.evolve(value_path=deque([i])),
+                            conditions=v.context.conditions.evolve(
+                                {instance[0]: True if i == 1 else False}
+                            ),
+                        ),
                     ),
-                ),
-                err,
-            )
+                    err,
+                )
+            except Unsatisfiable:
+                continue
 
 
 def to_json_string(validator: Validator, instance: Any) -> ResolutionResult:


Test patch:
diff --git a/test/unit/module/jsonschema/test_resolvers_cfn.py b/test/unit/module/jsonschema/test_resolvers_cfn.py
index a64ff6dd0..4d7788763 100644
--- a/test/unit/module/jsonschema/test_resolvers_cfn.py
+++ b/test/unit/module/jsonschema/test_resolvers_cfn.py
@@ -8,9 +8,16 @@ from collections import deque
 import pytest
 
 from cfnlint.context._mappings import Mappings
-from cfnlint.context.context import Context, Parameter, Path, Resource
+from cfnlint.context.context import (
+    Context,
+    Parameter,
+    Path,
+    Resource,
+    create_context_for_template,
+)
 from cfnlint.jsonschema import ValidationError
 from cfnlint.jsonschema.validators import CfnTemplateValidator
+from cfnlint.template import Template
 
 
 def _resolve(name, instance, expected_results, **kwargs):
@@ -877,3 +884,41 @@ def test_find_in_map_with_transform(name, instance, response):
         resources={},
     )
     _resolve(name, instance, response, context=context)
+
+
+def test_if():
+    cfn = Template(
+        None,
+        {
+            "Conditions": {
+                "Condition": {"Fn::Equals": [{"Ref": "AWS::StackName"}, "test"]}
+            },
+            "Resources": {},
+        },
+    )
+    context = create_context_for_template(cfn)
+    validator = CfnTemplateValidator().evolve(
+        context=context,
+        cfn=cfn,
+    )
+
+    instance = {"Fn::If": ["Condition", "Foo", "Bar"]}
+
+    results = list(validator.resolve_value(instance))
+
+    assert len(results) == 2
+    assert results[0][0] == "Foo"
+    assert results[1][0] == "Bar"
+
+    assert results[0][1].context.conditions.status == {"Condition": True}
+    assert results[1][1].context.conditions.status == {"Condition": False}
+
+    validator = validator.evolve(
+        context=validator.context.evolve(
+            conditions=validator.context.conditions.evolve({"Condition": False})
+        )
+    )
+
+    results = list(validator.resolve_value(instance))
+    assert len(results) == 1
+    assert results[0][0] == "Bar"