Repository: aws-cloudformation/cfn-lint

Issue:
E2541 is not working properly
*cfn-lint version: 0.8.1*

*Description of issue.*

E2541 For all currently supported action types, the only valid version string is "1".

But

> Version: "1" 


Discussion:
I looked into this quick and wasn't able to reproduce.  I was hoping to not see more of your template but I may need to do this justice.

In our testing (and testing manually) this template https://github.com/awslabs/cfn-python-lint/blob/master/test/fixtures/templates/good/resources_codepipeline.yaml is coming back clean which does have the `Version: "1"` set.  I flipped it to "2" just to check and it did trigger.  The only other thing I see here is that we are making sure its a string and not an integer.  Your example shouldn't parse as an integer though.
I am sorry, can you please double-check?

Here the Codepipeline resource code:


    Pipeline:
      Type: AWS::CodePipeline::Pipeline
      Properties:
        Name: !Sub Payments-${Environment}
        RoleArn: !GetAtt CodePipelineServiceRole.Arn
        ArtifactStore:
          Type: S3
          Location: !Ref ArtifactBucket
        Stages:
          - Name: Source
            Actions:
              - Name: Payments
                ActionTypeId:
                  Category: Source
                  Provider: S3
                  Owner: AWS
                  Version: "1"
                Configuration:
                  S3Bucket: wallapop-trigger-link-cicd
                  S3ObjectKey: !Sub payment/payment-${Tag}.zip
                OutputArtifacts:
                  - Name: App
          - Name: Infrastructure
            Actions:
              - Name: RDS
                ActionTypeId:
                  Category: Deploy
                  Owner: AWS
                  Version: "1"
                  Provider: CloudFormation
                Configuration:
                  ActionMode: CREATE_UPDATE
                  Capabilities: CAPABILITY_IAM
                  StackName: !Sub PaymentsRDS-${Environment}
                  TemplateConfiguration: !Sub App::infrastructure/parameters/payments/rds/${Environment}.json
                  TemplatePath: App::infrastructure/templates/payments/payments-rds.yml
                  RoleArn: !GetAtt CloudFormationExecutionRole.Arn
                InputArtifacts:
                  - Name: App
              - Name: Resources
                ActionTypeId:
                  Category: Deploy
                  Owner: AWS
                  Version: "1"
                  Provider: CloudFormation
                Configuration:
                  ActionMode: CREATE_UPDATE
                  Capabilities: CAPABILITY_IAM
                  StackName: !Sub Payments-${Environment}
                  TemplateConfiguration: !Sub App::infrastructure/parameters/payments/resources/${Environment}.json
                  TemplatePath: App::infrastructure/templates/payments/payments-resources.yml
                  RoleArn: !GetAtt CloudFormationExecutionRole.Arn
                InputArtifacts:
                  - Name: App
              - Name: Service
                ActionTypeId:
                  Category: Deploy
                  Owner: AWS
                  Version: "1"
                  Provider: CloudFormation
                Configuration:
                  ActionMode: CREATE_UPDATE
                  Capabilities: CAPABILITY_IAM
                  StackName: !Sub payments-service-${Environment}
                  TemplateConfiguration: !Sub App::infrastructure/parameters/payments/service/${Environment}.json
                  TemplatePath: App::infrastructure/templates/payments/payments-service.yml
                  RoleArn: !GetAtt CloudFormationExecutionRole.Arn
                InputArtifacts:
                  - Name: App
              - Name: Pipeline
                ActionTypeId:
                  Category: Deploy
                  Owner: AWS
                  Version: "1"
                  Provider: CloudFormation
                Configuration:
                  ActionMode: CREATE_UPDATE
                  Capabilities: CAPABILITY_IAM
                  StackName: !Sub payments-pipeline-${Environment}
                  TemplateConfiguration: !Sub App::infrastructure/parameters/payments/pipeline/${Environment}.json
                  TemplatePath: App::infrastructure/templates/payments/payments-pipeline.yml
                  RoleArn: !GetAtt CloudFormationExecutionRole.Arn
                InputArtifacts:
                  - Name: App
              - Name: Alarms
                ActionTypeId:
                  Category: Deploy
                  Owner: AWS
                  Version: "1"
                  Provider: CloudFormation
                Configuration:
                  ActionMode: CREATE_UPDATE
                  Capabilities: CAPABILITY_IAM
                  StackName: !Sub payments-alarms-${Environment}
                  TemplateConfiguration: !Sub App::infrastructure/parameters/payments/alarms/${Environment}.json
                  TemplatePath: App::infrastructure/templates/payments/payments-alarms.yml
                  RoleArn: !GetAtt CloudFormationExecutionRole.Arn
                InputArtifacts:
                  - Name: App
              - !If
                - CreateProdResources
                - Name: Dashboard
                  ActionTypeId:
                    Category: Deploy
                    Owner: AWS
                    Version: "1"
                    Provider: CloudFormation
                  Configuration:
                    ActionMode: CREATE_UPDATE
                    Capabilities: CAPABILITY_IAM
                    StackName: !Sub payments-dashboard-${Environment}
                    TemplateConfiguration: !Sub App::infrastructure/parameters/payments/dashboard/${Environment}.json
                    TemplatePath: App::infrastructure/templates/payments/payments-dashboard.yml
                    RoleArn: !GetAtt CloudFormationExecutionRole.Arn
                  InputArtifacts:
                    - Name: App
                - !Ref AWS::NoValue
          - Name: Build
            Actions:
            - Name: Flyway
              ActionTypeId:
                Category: Build
                Owner: AWS
                Version: "1"
                Provider: CodeBuild
              Configuration:
                ProjectName: !Ref CodeBuildDBScripts
              InputArtifacts:
              - Name: App
          - Name: Deploy
            Actions:
              - Name: Deploy
                ActionTypeId:
                  Category: Deploy
                  Owner: AWS
                  Version: "1"
                  Provider: ECS
                Configuration:
                  ClusterName: !Ref Environment
                  ServiceName:
                      Fn::ImportValue:
                        !Sub "payments-${Environment}-name"
                  FileName: images.json
                InputArtifacts:
                  - Name: App
                RunOrder: 1`

If I run the last version linter:
```
$ sudo pip install -U cfn-lint
$ cfn-lint -t infrastructure/templates/delivery/delivery-pipeline.yml
E2541 For all currently supported action types, the only valid version string is "1".
infrastructure/templates/delivery/delivery-pipeline.yml:391:13
```

If I run an old version:

```
$ sudo pip install -U cfn-lint==0.6.1
$ cfn-lint -t infrastructure/templates/delivery/delivery-pipeline.yml
$
```
ok I was able to replicate this now.  I'm looking into it.

Its the condition that is causing us issues.  Should be able to get this fixed shortly. 

Resolving patch:
diff --git a/src/cfnlint/rules/resources/codepipeline/CodepipelineStageActions.py b/src/cfnlint/rules/resources/codepipeline/CodepipelineStageActions.py
--- a/src/cfnlint/rules/resources/codepipeline/CodepipelineStageActions.py
+++ b/src/cfnlint/rules/resources/codepipeline/CodepipelineStageActions.py
@@ -212,30 +212,29 @@ def match(self, cfn):
             path = resource['Path']
             properties = resource['Value']
 
-            stages = properties.get('Stages')
-            if not isinstance(stages, list):
-                self.logger.debug('Stages not list. Should have been caught by generic linting.')
-                return matches
-
-            for sidx, stage in enumerate(stages):
-                stage_path = path + ['Stages', sidx]
-                action_names = set()
-                actions = stage.get('Actions')
-                if not isinstance(actions, list):
-                    self.logger.debug('Actions not list. Should have been caught by generic linting.')
+            s_stages = properties.get_safe('Stages', path)
+            for s_stage_v, s_stage_p in s_stages:
+                if not isinstance(s_stage_v, list):
+                    self.logger.debug('Stages not list. Should have been caught by generic linting.')
                     return matches
 
-                for aidx, action in enumerate(actions):
-                    action_path = stage_path + ['Actions', aidx]
-
-                    try:
-                        matches.extend(self.check_names_unique(action, action_path, action_names))
-                        matches.extend(self.check_version(action, action_path))
-                        matches.extend(self.check_owner(action, action_path))
-                        matches.extend(self.check_artifact_counts(action, 'InputArtifacts', action_path))
-                        matches.extend(self.check_artifact_counts(action, 'OutputArtifacts', action_path))
-                    except AttributeError as err:
-                        self.logger.debug('Got AttributeError. Should have been caught by generic linting. '
-                                          'Ignoring the error here: %s', str(err))
+                for l_i_stage, l_i_path in s_stage_v.items_safe(s_stage_p):
+                    action_names = set()
+                    s_actions = l_i_stage.get_safe('Actions', l_i_path)
+                    for s_action_v, s_action_p in s_actions:
+                        if not isinstance(s_action_v, list):
+                            self.logger.debug('Actions not list. Should have been caught by generic linting.')
+                            return matches
+
+                        for l_i_a_action, l_i_a_path in s_action_v.items_safe(s_action_p):
+                            try:
+                                matches.extend(self.check_names_unique(l_i_a_action, l_i_a_path, action_names))
+                                matches.extend(self.check_version(l_i_a_action, l_i_a_path))
+                                matches.extend(self.check_owner(l_i_a_action, l_i_a_path))
+                                matches.extend(self.check_artifact_counts(l_i_a_action, 'InputArtifacts', l_i_a_path))
+                                matches.extend(self.check_artifact_counts(l_i_a_action, 'OutputArtifacts', l_i_a_path))
+                            except AttributeError as err:
+                                self.logger.debug('Got AttributeError. Should have been caught by generic linting. '
+                                                  'Ignoring the error here: %s', str(err))
 
         return matches
diff --git a/src/cfnlint/rules/resources/codepipeline/CodepipelineStages.py b/src/cfnlint/rules/resources/codepipeline/CodepipelineStages.py
--- a/src/cfnlint/rules/resources/codepipeline/CodepipelineStages.py
+++ b/src/cfnlint/rules/resources/codepipeline/CodepipelineStages.py
@@ -106,27 +106,27 @@ def match(self, cfn):
             path = resource['Path']
             properties = resource['Value']
 
-            stages = properties.get('Stages')
-            if not isinstance(stages, list):
-                self.logger.debug('Stages not list. Should have been caught by generic linting.')
-                return matches
-
-            try:
-                matches.extend(
-                    self.check_stage_count(stages, path + ['Stages'])
-                )
-                matches.extend(
-                    self.check_first_stage(stages, path + ['Stages'])
-                )
-                matches.extend(
-                    self.check_source_actions(stages, path + ['Stages'])
-                )
-                matches.extend(
-                    self.check_names_unique(stages, path + ['Stages'])
-                )
-            except AttributeError as err:
-                self.logger.debug('Got AttributeError. Should have been caught by generic linting. '
-                                  'Ignoring the error here: %s', str(err))
-
+            s_stages = properties.get_safe('Stages', path)
+            for s_stage_v, s_stage_p in s_stages:
+                if not isinstance(s_stage_v, list):
+                    self.logger.debug('Stages not list. Should have been caught by generic linting.')
+                    return matches
+
+                try:
+                    matches.extend(
+                        self.check_stage_count(s_stage_v, s_stage_p)
+                    )
+                    matches.extend(
+                        self.check_first_stage(s_stage_v, s_stage_p)
+                    )
+                    matches.extend(
+                        self.check_source_actions(s_stage_v, s_stage_p)
+                    )
+                    matches.extend(
+                        self.check_names_unique(s_stage_v, s_stage_p)
+                    )
+                except AttributeError as err:
+                    self.logger.debug('Got AttributeError. Should have been caught by generic linting. '
+                                      'Ignoring the error here: %s', str(err))
 
         return matches


Test patch:
diff --git a/test/fixtures/templates/good/resources_codepipeline.yaml b/test/fixtures/templates/good/resources_codepipeline.yaml
--- a/test/fixtures/templates/good/resources_codepipeline.yaml
+++ b/test/fixtures/templates/good/resources_codepipeline.yaml
@@ -1,4 +1,6 @@
 AWSTemplateFormatVersion: 2010-09-09
+Conditions:
+  myCondition: !Equals [!Ref "AWS::Region", 'us-east-1']
 Resources:
   TestPipeline:
     Type: AWS::CodePipeline::Pipeline
@@ -39,6 +41,8 @@ Resources:
               - Name: MyApp
         - Name: !ImportValue TestImport
           Actions:
+          - Fn::If:
+            - myCondition
             - Name: CodeBuild
               ActionTypeId:
                 Category: Build
@@ -49,3 +53,4 @@ Resources:
                 ProjectName: cfn-python-lint
               InputArtifacts:
               - Name: MyApp
+            - !Ref AWS::NoValue