AWS CodePipeline does offer S3 source actions, which will trigger the pipeline when changes are made to a specific object in a bucket. At some point, I had a pipeline where there was an S3 source action, but the change detection was being handled by an Events rule watching for specific CloudTrails logs.

I don’t recall if the native S3 source action previously didn’t do change detection, or why I implemented that myself, but somehow I ended up there.

The only tricky bit was dealing with the fact that when the S3 source action ran as part of the pipeline execution, it would perform a CopyObject for the object in question. Because I was watching that object for both PutObject and CopyObject events in CloudTrail, things would get in a loop. The file would be updated (by some expected external means), the Events rule would trigger the pipeline, which would source the object and copy it, which would trigger the rule, which would trigger the pipelines, etc…

To get around that, the Events rule had to filter CloudTrail events where the principal that made the CopyObject call was the pipeline’s execution role. With that in place, all other changes would trigger the rule and the pipeline, but the CopyObject performed as part of the pipeline’s source action would not trigger another execution.

PipelineS3TriggerEventRule:
  Type: AWS::Events::Rule
  Properties:
    Description: >-
      Triggers a CodePipeline when CloudTrail sees changes in S3 on some object
    EventPattern:
      source:
        - aws.s3
      detail-type:
        - AWS API Call via CloudTrail
      detail:
        eventSource:
          - s3.amazonaws.com
        eventName:
          - PutObject
          - CopyObject
        userIdentity:
          sessionContext:
            sessionIssuer:
              arn:
                # CodePipeline does a CopyObject when it pulls in an S3
                # object as part of a Source action, using the pipeline's
                # role. To prevent an infinite loop, those events need to be
                # filtered out
                - anything-but: !GetAtt MyPipelineRole.Arn
        resources:
          ARN: arn:aws:s3:::my-bucket/my-source-object
    State: ENABLED
    Targets:
      - Arn: !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${MyPipeline}
        Id: my-target-id
        RoleArn: !GetAtt MyEventsRuleRole.Arn