{"action":"created","comment":{"url":"https://api.github.com/repos/openshift/test-pull-requests/pulls/comments/72101996","id":72101996,"diff_hunk":"@@ -935,6 +1023,103 @@ popd\n       end\n     end\n \n+    # has_valid_flake_comment? determines if a valid flake explanation comment exists,\n+    # if there exists no valid comment, a human-readable reason why to help users\n+    # understand the expected syntax\n+    #\n+    # A flake comment is valid iff:\n+    #  - the comment contains the failed build URL\n+    #  - at least one given issue link in the comment:\n+    #    - resides in the correct repository\n+    #    - points to issues with the correct label\n+    def has_valid_flake_comment?(comments, build_url, settings)\n+      general_issue_spec = /(https?:\\/\\/github.com\\/.*\\/issues\\/[0-9]*)/\n+      flake_issue_spec = /(https?:\\/\\/github.com\\/#{Regexp.quote(Properties['github_user'])}\\/#{Regexp.quote(settings['flake_identification']['repo'])}\\/issues\\/([0-9]*))/\n+\n+      valid_comment_exists = false\n+      flake_label = settings['flake_identification']['label']\n+      flake_repo = settings['flake_identification']['repo']\n+      org_repo = %{#{Properties['github_user']}/#{flake_repo}}\n+      flake_label_query = CGI.escape(\"label:#{flake_label}\")\n+      issue_link = %{https://github.com/#{org_repo}/issues?q=#{flake_label_query}}\n+      reason = %{No comment was found that meets the flake identification requirements. A correctly formatted comment should include a link to the failed Jenkins job and at least one link to a GitHub issue in the [`#{org_repo}` repository](#{issue_link}) with the `#{flake_label}` label.}\n+\n+      comments = sort_comments(comments)\n+      comments.each do |comment|\n+        comment_matches_syntax = comment['body'] =~ /#{Regexp.quote(build_url)}/ && comment['body'] =~ general_issue_spec\n+        # we can't allow the bot to be a valid author, or we would pick up\n+        # comments where the bot explains the flake syntax to GitHub users\n+        comment_author_valid = comment['user']['login'] != Properties['bot_github_user']\n+        if comment_matches_syntax && comment_author_valid\n+          # if we find a comment that matches the flake job syntax from someone\n+          # trusted, we need to validate the comment's flake issue list\n+          bad_issue_links = []\n+          comment['body'].scan(general_issue_spec) do |issue|\n+            if issue =~ flake_issue_spec\n+              issue_url = $1\n+              issue_id = $2.to_i\n+              if !issue_has_label?(issue_id, flake_repo, flake_label)\n+                # the issue does not have the labels necessary, so it is invalid\n+                bad_issue_links << issue_url\n+              else\n+                valid_comment_exists = true\n+              end\n+            else\n+              # we had a GitHub issue link, but it didn't\n+              # match the org or repo that we wanted\n+              bad_issue_links << issue\n+            end\n+          end\n+\n+          if valid_comment_exists\n+            break\n+          else\n+            # no valid issue links mean that the comment is invalid\n+            reason = %{A [comment](#{comment[\"html_url\"]}) was found that referenced the correct Jenkins job URL, but no listed issue links were valid. Ensure at least one linked issue is in the [`#{org_repo}` repository](#{issue_link}) and has the `#{flake_label}` label. Invalid links are:\n+ - #{bad_issue_links.join(\"\\n - \")}}\n+          end\n+        end\n+      end\n+\n+      [valid_comment_exists, reason]\n+    end\n+\n+    # issue_has_label determines if the issue at the\n+    # repo and issue ID has a label with the given name\n+    def issue_has_label?(issue_id, repo, label)\n+      get_labels(issue_id, repo).each do |label|\n+        if label[\"name\"] == label\n+          return true\n+        end\n+      end\n+      return false\n+    end\n+\n+    # format_teams builds a list of links to team rosters from a list of team IDs\n+    def format_teams(team_ids)\n+      links = []\n+      team_ids.each do |id|\n+        url, name = get_team_url_and_name(id)\n+        links << \"[#{name}](#{url})\"\n+      end\n+      if links.length == 1\n+        %{the #{links[0]} group}\n+      else\n+        %{the following groups: #{links.join(\", \")}}\n+      end\n+    end\n+\n+    # format_flake_comment builds a comment explaining why a re-build could not be triggered\n+    def format_flake_comment(repo, job, reason, admin_groups)","path":"test_pull_requests","position":293,"original_position":293,"commit_id":"95d9a0a88f749fc06c0058bfe4eba76012cfc1a6","original_commit_id":"95d9a0a88f749fc06c0058bfe4eba76012cfc1a6","user":{"login":"danmcp","id":492994,"avatar_url":"https://avatars.githubusercontent.com/u/492994?v=3","gravatar_id":"","url":"https://api.github.com/users/danmcp","html_url":"https://github.com/danmcp","followers_url":"https://api.github.com/users/danmcp/followers","following_url":"https://api.github.com/users/danmcp/following{/other_user}","gists_url":"https://api.github.com/users/danmcp/gists{/gist_id}","starred_url":"https://api.github.com/users/danmcp/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/danmcp/subscriptions","organizations_url":"https://api.github.com/users/danmcp/orgs","repos_url":"https://api.github.com/users/danmcp/repos","events_url":"https://api.github.com/users/danmcp/events{/privacy}","received_events_url":"https://api.github.com/users/danmcp/received_events","type":"User","site_admin":false},"body":"Shouldn't pass a formatted string to a format message.  It makes assumptions about what the final format should be.","created_at":"2016-07-25T16:58:20Z","updated_at":"2016-07-25T16:58:20Z","html_url":"https://github.com/openshift/test-pull-requests/pull/26#discussion_r72101996","pull_request_url":"https://api.github.com/repos/openshift/test-pull-requests/pulls/26","_links":{"self":{"href":"https://api.github.com/repos/openshift/test-pull-requests/pulls/comments/72101996"},"html":{"href":"https://github.com/openshift/test-pull-requests/pull/26#discussion_r72101996"},"pull_request":{"href":"https://api.github.com/repos/openshift/test-pull-requests/pulls/26"}}},"pull_request":{"url":"https://api.github.com/repos/openshift/test-pull-requests/pulls/26","id":77665808,"html_url":"https://github.com/openshift/test-pull-requests/pull/26","diff_url":"https://github.com/openshift/test-pull-requests/pull/26.diff","patch_url":"https://github.com/openshift/test-pull-requests/pull/26.patch","issue_url":"https://api.github.com/repos/openshift/test-pull-requests/issues/26","number":26,"state":"open","locked":false,"title":"Implemented failed job explanation logic","user":{"login":"stevekuznetsov","id":7328370,"avatar_url":"https://avatars.githubusercontent.com/u/7328370?v=3","gravatar_id":"","url":"https://api.github.com/users/stevekuznetsov","html_url":"https://github.com/stevekuznetsov","followers_url":"https://api.github.com/users/stevekuznetsov/followers","following_url":"https://api.github.com/users/stevekuznetsov/following{/other_user}","gists_url":"https://api.github.com/users/stevekuznetsov/gists{/gist_id}","starred_url":"https://api.github.com/users/stevekuznetsov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/stevekuznetsov/subscriptions","organizations_url":"https://api.github.com/users/stevekuznetsov/orgs","repos_url":"https://api.github.com/users/stevekuznetsov/repos","events_url":"https://api.github.com/users/stevekuznetsov/events{/privacy}","received_events_url":"https://api.github.com/users/stevekuznetsov/received_events","type":"User","site_admin":false},"body":"```\r\nIn order to ensure that flaky tests cannot be easily\r\nignored by GitHub contributors that have the power\r\nto re-trigger Jenkins jobs, this new logic forbids a\r\njob from being re-triggered unless:\r\n - new code is pushed to update the pull request's\r\n   base branch from the version we used to run the\r\n   failed tests\r\n - a trusted user posts a comment linking together\r\n   the failed job's URL and the URL of valid GitHub\r\n   issues with labels identifiying them as test\r\n   flakes\r\n - an administrator re-triggers the job, which will\r\n   override any flake identification logic\r\n\r\nIf no flake identification configuration is done in\r\nthe JSON properties file, the legacy behavior is\r\nunchaged and the job will be re-triggered always.\r\n\r\nSigned-off-by: Steve Kuznetsov <skuznets@redhat.com>\r\n```\r\n\r\n@danmcp PTAL first-pass, tests coming","created_at":"2016-07-15T19:28:17Z","updated_at":"2016-07-25T16:58:20Z","closed_at":null,"merged_at":null,"merge_commit_sha":"efe285e370e98f5fae0eba97a47c8da107c469d7","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/openshift/test-pull-requests/pulls/26/commits","review_comments_url":"https://api.github.com/repos/openshift/test-pull-requests/pulls/26/comments","review_comment_url":"https://api.github.com/repos/openshift/test-pull-requests/pulls/comments{/number}","comments_url":"https://api.github.com/repos/openshift/test-pull-requests/issues/26/comments","statuses_url":"https://api.github.com/repos/openshift/test-pull-requests/statuses/95d9a0a88f749fc06c0058bfe4eba76012cfc1a6","head":{"label":"stevekuznetsov:skuznets/enforce-flake-id","ref":"skuznets/enforce-flake-id","sha":"95d9a0a88f749fc06c0058bfe4eba76012cfc1a6","user":{"login":"stevekuznetsov","id":7328370,"avatar_url":"https://avatars.githubusercontent.com/u/7328370?v=3","gravatar_id":"","url":"https://api.github.com/users/stevekuznetsov","html_url":"https://github.com/stevekuznetsov","followers_url":"https://api.github.com/users/stevekuznetsov/followers","following_url":"https://api.github.com/users/stevekuznetsov/following{/other_user}","gists_url":"https://api.github.com/users/stevekuznetsov/gists{/gist_id}","starred_url":"https://api.github.com/users/stevekuznetsov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/stevekuznetsov/subscriptions","organizations_url":"https://api.github.com/users/stevekuznetsov/orgs","repos_url":"https://api.github.com/users/stevekuznetsov/repos","events_url":"https://api.github.com/users/stevekuznetsov/events{/privacy}","received_events_url":"https://api.github.com/users/stevekuznetsov/received_events","type":"User","site_admin":false},"repo":{"id":62829947,"name":"test-pull-requests","full_name":"stevekuznetsov/test-pull-requests","owner":{"login":"stevekuznetsov","id":7328370,"avatar_url":"https://avatars.githubusercontent.com/u/7328370?v=3","gravatar_id":"","url":"https://api.github.com/users/stevekuznetsov","html_url":"https://github.com/stevekuznetsov","followers_url":"https://api.github.com/users/stevekuznetsov/followers","following_url":"https://api.github.com/users/stevekuznetsov/following{/other_user}","gists_url":"https://api.github.com/users/stevekuznetsov/gists{/gist_id}","starred_url":"https://api.github.com/users/stevekuznetsov/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/stevekuznetsov/subscriptions","organizations_url":"https://api.github.com/users/stevekuznetsov/orgs","repos_url":"https://api.github.com/users/stevekuznetsov/repos","events_url":"https://api.github.com/users/stevekuznetsov/events{/privacy}","received_events_url":"https://api.github.com/users/stevekuznetsov/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/stevekuznetsov/test-pull-requests","description":"Utility for serially testing then merging pull requests in conjunction with Jenkins.","fork":true,"url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests","forks_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/forks","keys_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/keys{/key_id}","collaborators_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/teams","hooks_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/hooks","issue_events_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/issues/events{/number}","events_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/events","assignees_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/assignees{/user}","branches_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/branches{/branch}","tags_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/tags","blobs_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/git/refs{/sha}","trees_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/git/trees{/sha}","statuses_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/statuses/{sha}","languages_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/languages","stargazers_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/stargazers","contributors_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/contributors","subscribers_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/subscribers","subscription_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/subscription","commits_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/commits{/sha}","git_commits_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/git/commits{/sha}","comments_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/comments{/number}","issue_comment_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/issues/comments{/number}","contents_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/contents/{+path}","compare_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/compare/{base}...{head}","merges_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/merges","archive_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/downloads","issues_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/issues{/number}","pulls_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/pulls{/number}","milestones_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/milestones{/number}","notifications_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/labels{/name}","releases_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/releases{/id}","deployments_url":"https://api.github.com/repos/stevekuznetsov/test-pull-requests/deployments","created_at":"2016-07-07T18:36:42Z","updated_at":"2016-07-07T18:36:43Z","pushed_at":"2016-07-25T16:38:32Z","git_url":"git://github.com/stevekuznetsov/test-pull-requests.git","ssh_url":"git@github.com:stevekuznetsov/test-pull-requests.git","clone_url":"https://github.com/stevekuznetsov/test-pull-requests.git","svn_url":"https://github.com/stevekuznetsov/test-pull-requests","homepage":null,"size":209,"stargazers_count":0,"watchers_count":0,"language":"Ruby","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"openshift:master","ref":"master","sha":"6071b16ba2db4eb2cf70427a33db4fc992ecbe33","user":{"login":"openshift","id":792337,"avatar_url":"https://avatars.githubusercontent.com/u/792337?v=3","gravatar_id":"","url":"https://api.github.com/users/openshift","html_url":"https://github.com/openshift","followers_url":"https://api.github.com/users/openshift/followers","following_url":"https://api.github.com/users/openshift/following{/other_user}","gists_url":"https://api.github.com/users/openshift/gists{/gist_id}","starred_url":"https://api.github.com/users/openshift/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openshift/subscriptions","organizations_url":"https://api.github.com/users/openshift/orgs","repos_url":"https://api.github.com/users/openshift/repos","events_url":"https://api.github.com/users/openshift/events{/privacy}","received_events_url":"https://api.github.com/users/openshift/received_events","type":"Organization","site_admin":false},"repo":{"id":5604195,"name":"test-pull-requests","full_name":"openshift/test-pull-requests","owner":{"login":"openshift","id":792337,"avatar_url":"https://avatars.githubusercontent.com/u/792337?v=3","gravatar_id":"","url":"https://api.github.com/users/openshift","html_url":"https://github.com/openshift","followers_url":"https://api.github.com/users/openshift/followers","following_url":"https://api.github.com/users/openshift/following{/other_user}","gists_url":"https://api.github.com/users/openshift/gists{/gist_id}","starred_url":"https://api.github.com/users/openshift/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openshift/subscriptions","organizations_url":"https://api.github.com/users/openshift/orgs","repos_url":"https://api.github.com/users/openshift/repos","events_url":"https://api.github.com/users/openshift/events{/privacy}","received_events_url":"https://api.github.com/users/openshift/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/openshift/test-pull-requests","description":"Utility for serially testing then merging pull requests in conjunction with Jenkins.","fork":false,"url":"https://api.github.com/repos/openshift/test-pull-requests","forks_url":"https://api.github.com/repos/openshift/test-pull-requests/forks","keys_url":"https://api.github.com/repos/openshift/test-pull-requests/keys{/key_id}","collaborators_url":"https://api.github.com/repos/openshift/test-pull-requests/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/openshift/test-pull-requests/teams","hooks_url":"https://api.github.com/repos/openshift/test-pull-requests/hooks","issue_events_url":"https://api.github.com/repos/openshift/test-pull-requests/issues/events{/number}","events_url":"https://api.github.com/repos/openshift/test-pull-requests/events","assignees_url":"https://api.github.com/repos/openshift/test-pull-requests/assignees{/user}","branches_url":"https://api.github.com/repos/openshift/test-pull-requests/branches{/branch}","tags_url":"https://api.github.com/repos/openshift/test-pull-requests/tags","blobs_url":"https://api.github.com/repos/openshift/test-pull-requests/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/openshift/test-pull-requests/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/openshift/test-pull-requests/git/refs{/sha}","trees_url":"https://api.github.com/repos/openshift/test-pull-requests/git/trees{/sha}","statuses_url":"https://api.github.com/repos/openshift/test-pull-requests/statuses/{sha}","languages_url":"https://api.github.com/repos/openshift/test-pull-requests/languages","stargazers_url":"https://api.github.com/repos/openshift/test-pull-requests/stargazers","contributors_url":"https://api.github.com/repos/openshift/test-pull-requests/contributors","subscribers_url":"https://api.github.com/repos/openshift/test-pull-requests/subscribers","subscription_url":"https://api.github.com/repos/openshift/test-pull-requests/subscription","commits_url":"https://api.github.com/repos/openshift/test-pull-requests/commits{/sha}","git_commits_url":"https://api.github.com/repos/openshift/test-pull-requests/git/commits{/sha}","comments_url":"https://api.github.com/repos/openshift/test-pull-requests/comments{/number}","issue_comment_url":"https://api.github.com/repos/openshift/test-pull-requests/issues/comments{/number}","contents_url":"https://api.github.com/repos/openshift/test-pull-requests/contents/{+path}","compare_url":"https://api.github.com/repos/openshift/test-pull-requests/compare/{base}...{head}","merges_url":"https://api.github.com/repos/openshift/test-pull-requests/merges","archive_url":"https://api.github.com/repos/openshift/test-pull-requests/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/openshift/test-pull-requests/downloads","issues_url":"https://api.github.com/repos/openshift/test-pull-requests/issues{/number}","pulls_url":"https://api.github.com/repos/openshift/test-pull-requests/pulls{/number}","milestones_url":"https://api.github.com/repos/openshift/test-pull-requests/milestones{/number}","notifications_url":"https://api.github.com/repos/openshift/test-pull-requests/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/openshift/test-pull-requests/labels{/name}","releases_url":"https://api.github.com/repos/openshift/test-pull-requests/releases{/id}","deployments_url":"https://api.github.com/repos/openshift/test-pull-requests/deployments","created_at":"2012-08-29T17:45:58Z","updated_at":"2016-01-21T22:06:21Z","pushed_at":"2016-07-25T16:38:33Z","git_url":"git://github.com/openshift/test-pull-requests.git","ssh_url":"git@github.com:openshift/test-pull-requests.git","clone_url":"https://github.com/openshift/test-pull-requests.git","svn_url":"https://github.com/openshift/test-pull-requests","homepage":null,"size":297,"stargazers_count":12,"watchers_count":12,"language":"Ruby","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":21,"mirror_url":null,"open_issues_count":1,"forks":21,"open_issues":1,"watchers":12,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/openshift/test-pull-requests/pulls/26"},"html":{"href":"https://github.com/openshift/test-pull-requests/pull/26"},"issue":{"href":"https://api.github.com/repos/openshift/test-pull-requests/issues/26"},"comments":{"href":"https://api.github.com/repos/openshift/test-pull-requests/issues/26/comments"},"review_comments":{"href":"https://api.github.com/repos/openshift/test-pull-requests/pulls/26/comments"},"review_comment":{"href":"https://api.github.com/repos/openshift/test-pull-requests/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/openshift/test-pull-requests/pulls/26/commits"},"statuses":{"href":"https://api.github.com/repos/openshift/test-pull-requests/statuses/95d9a0a88f749fc06c0058bfe4eba76012cfc1a6"}}}}
{"actor":{"display_login":"danmcp"}}