{"action":"created","comment":{"url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/pulls/comments/168228890","pull_request_review_id":96551670,"id":168228890,"diff_hunk":"@@ -0,0 +1,193 @@\n+#!/usr/bin/env python3\n+# Receives docker owner and a list of changed files on the command line\n+# For a changed file isunittest.yml or Dockerfile\n+#   1) builds a docker image in the directory containing unittest.yml ( building Dockerfile )\n+#   2) runs tests specified in unittest.yml inside the docker image\n+#      a) with default docker settings\n+#      a) with WORKDIR and USER settings to check for singularity compatibility\n+# If both unittest.yml and Dockerfile are changed it only tests the image once\n+\n+import os\n+import sys\n+import subprocess\n+import yaml\n+import re\n+\n+UNITTEST_FILENAME = \"unittest.yml\"\n+TEST_WORKDIR = \"/tmp\"\n+TEST_USER = \"nobody\"\n+\n+\n+def run_bash_cmd(command, ignore_non_zero_exit_status=False):\n+    \"\"\"\n+    Run bash command and return output\n+    :param command: str bash command to run\n+    :param ignore_non_zero_exit_status: bool: when true returns output no matter the exit status\n+    :return: str: output from command\n+    \"\"\"\n+    try:\n+        return str(subprocess.check_output([\"bash\", \"-c\", command]))\n+    except subprocess.CalledProcessError as exc:\n+        if ignore_non_zero_exit_status:\n+            return str(exc.output)\n+        else:\n+            print(\"ERROR\", exc.returncode, exc.output, exc.stderr)\n+            raise\n+\n+\n+def get_test_list(filename):\n+    \"\"\"\n+    Read YAML test configuration from a file\n+    :param filename: str: path to YAML file with test settings for a single docker file\n+    :return: [(command, expected_text)]: array of command and expected output pairs\n+    \"\"\"\n+    with open(filename) as infile:\n+        testinfo_list = []\n+        unittest_config = yaml.load(infile)\n+        for testinfo in unittest_config['commands']:\n+            cmd = testinfo['cmd']\n+            expect_text = testinfo['expect_text']\n+            testinfo_list.append((cmd, expect_text))\n+        return testinfo_list\n+\n+\n+def build_docker_image(imagename, build_path):\n+    print(\"Building docker image for path {}\".format(build_path))\n+    run_bash_cmd(\"docker build -t {} {}\".format(imagename, build_path))\n+\n+\n+def run_docker_get_output(imagename, cmd, workdir=None, user=None):\n+    \"\"\"\n+    Launch docker process with run command passing the cmd.\n+    :param imagename: str: name of the image to run\n+    :param cmd: str: commmand to run inside the image\n+    :param workdir: str: optional flag to run in a particular directory\n+    :param user: str: optional flag to run as a particular user\n+    :return: str: output of the docker process\n+    \"\"\"\n+    options = \"\"\n+    if workdir:\n+        options += \"--workdir {} \".format(workdir)\n+    if user:\n+        options += \"--user {} \".format(user)\n+    options += \"-it \"\n+    print(\"Testing image {} with: docker run {} {}\".format(imagename, options, cmd))\n+    docker_cmd = \"docker run {}{} {}\".format(options, imagename, cmd)\n+    return run_bash_cmd(docker_cmd, ignore_non_zero_exit_status=True)\n+\n+\n+def run_tests(imagename, unittest_filepath):\n+    \"\"\"\n+    Run all tests contained in a unittest_filename against imagename\n+    :param imagename: str: name of the image to test\n+    :param unittest_filepath: str path to unittest.yml file\n+    :return: bool: true if we had an error\n+    \"\"\"\n+    had_error = False\n+    for cmd, expect_text in get_test_list(unittest_filepath):\n+        expect_pattern = re.compile(expect_text, re.DOTALL)\n+        docker_output = run_docker_get_output(imagename, cmd)\n+        if not re.match(expect_pattern, docker_output):\n+            print_test_error(cmd, expect_text, docker_output)\n+            had_error = True\n+        docker_output_with_options = run_docker_get_output(imagename, cmd, workdir=TEST_WORKDIR, user=TEST_USER)\n+        if not re.match(expect_pattern, docker_output_with_options):\n+            print_test_error(cmd + \" (with workdir and user options)\", expect_text, docker_output_with_options)\n+            had_error = True\n+    return had_error\n+\n+\n+def print_test_error(cmd, expect_text, cmd_output):\n+    print(\"Error running {}\".format(cmd))\n+    print(\"Expected Regex: {}\".format(expect_text))\n+    print(\"Actual: {}\".format(cmd_output))\n+\n+\n+def find_unittest_info():\n+    \"\"\"\n+    Recursively walk the current directory looking for tests.\n+    :return: [(test_dir, imagename, test_filename)]: list of testing data tuples\n+    \"\"\"\n+    test_info = []\n+    for root, dirs, files in os.walk(\".\"):\n+        for name in files:\n+            if name == UNITTEST_FILENAME:\n+                test_directory = root\n+                imagename = \"localhost:5000/test_{}\".format(test_directory.replace(\"./\", \"\").replace(\"/\", \":\").replace(\"+\",\"_\"))\n+                test_filename = os.path.join(root, name)\n+                test_info.append((test_directory, imagename, test_filename))\n+    return test_info\n+\n+\n+def get_test_file_path(file_path):\n+    \"\"\"\n+    Given a file path return a path to the associated unittest.yml file.\n+    This is either the file_path itself if it points to unittest.yml or if a Dockerfile it will\n+    point to the unittest.yml file(if it exists)\n+    :param file_path: str: path to a changed file that may have an associated unittest.yml file\n+    :return: str: a path to a unittest.yml file to test or None if there is no associated test file\n+    \"\"\"\n+    filename = os.path.basename(file_path)\n+    if filename in [\"Dockerfile\", UNITTEST_FILENAME]:\n+        parent_directory = os.path.dirname(file_path)\n+        unittest_filepath = \"{}/{}\".format(parent_directory, UNITTEST_FILENAME)\n+        if os.path.exists(unittest_filepath):\n+            return unittest_filepath\n+    return None\n+\n+\n+def get_unittest_file_paths(path_list):\n+    \"\"\"\n+    For a list of paths return any associated unittest.yml files.\n+    :param path_list: [str]: list of paths\n+    :return: [str]: list of unitttest.yml paths\n+    \"\"\"\n+    unittest_paths = set()\n+    for file_path in path_list:\n+        unittest_path = get_test_file_path(file_path)\n+        if unittest_path:\n+            unittest_paths.add(unittest_path)\n+    return unittest_paths\n+\n+\n+def find_and_run_tests(owner, changed_paths):\n+    \"\"\"\n+    Find an run tests based on a docker ownername (used to build image name) and a list of paths\n+    :param owner: str: prefix of docker imagename\n+    :param changed_paths: [str]: list of paths that were changed and may need to be tested\n+    :return: bool: true when we had errors\n+    \"\"\"\n+    had_errors = False\n+    tested_images = 0\n+    images_with_errors = 0\n+    for unittest_path in get_unittest_file_paths(changed_paths):\n+        parts = unittest_path.split(sep=\"/\")\n+        if len(parts):\n+            tool, tag, _ = parts\n+            imagename = \"{}/{}:{}\".format(owner, tool, tag).replace(\"+\",\"_\")\n+            build_docker_image(imagename, \"{}/{}\".format(tool, tag))","path":"tests/imagecheck.py","position":168,"original_position":168,"commit_id":"f4c7ef8e8ef75b28b179653502dc52891d864586","original_commit_id":"f4c7ef8e8ef75b28b179653502dc52891d864586","user":{"login":"dleehr","id":2752599,"avatar_url":"https://avatars3.githubusercontent.com/u/2752599?v=4","gravatar_id":"","url":"https://api.github.com/users/dleehr","html_url":"https://github.com/dleehr","followers_url":"https://api.github.com/users/dleehr/followers","following_url":"https://api.github.com/users/dleehr/following{/other_user}","gists_url":"https://api.github.com/users/dleehr/gists{/gist_id}","starred_url":"https://api.github.com/users/dleehr/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dleehr/subscriptions","organizations_url":"https://api.github.com/users/dleehr/orgs","repos_url":"https://api.github.com/users/dleehr/repos","events_url":"https://api.github.com/users/dleehr/events{/privacy}","received_events_url":"https://api.github.com/users/dleehr/received_events","type":"User","site_admin":false},"body":"Since `build-ci.sh` is designed to build images, it's unexpected that the test step has separate logic for determining what to build and how to build it.\r\n\r\nThere's also a risk if that logic is not maintained identically. Build and test can produce different images. Deploy, coming after test, would be pushing the images built by the test step, and not the ones from build.\r\n\r\nI recognize that test needs to detect changes to the unittest.yml files for re-running tests, but I'm not sure those need to trigger a rebuild.","created_at":"2018-02-14T16:22:16Z","updated_at":"2018-02-14T16:37:51Z","html_url":"https://github.com/Duke-GCB/GCB-Dockerfiles/pull/42#discussion_r168228890","pull_request_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/pulls/42","author_association":"OWNER","_links":{"self":{"href":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/pulls/comments/168228890"},"html":{"href":"https://github.com/Duke-GCB/GCB-Dockerfiles/pull/42#discussion_r168228890"},"pull_request":{"href":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/pulls/42"}}},"pull_request":{"url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/pulls/42","id":168660066,"html_url":"https://github.com/Duke-GCB/GCB-Dockerfiles/pull/42","diff_url":"https://github.com/Duke-GCB/GCB-Dockerfiles/pull/42.diff","patch_url":"https://github.com/Duke-GCB/GCB-Dockerfiles/pull/42.patch","issue_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/issues/42","number":42,"state":"open","locked":false,"title":"test docker images in circle","user":{"login":"johnbradley","id":1024463,"avatar_url":"https://avatars1.githubusercontent.com/u/1024463?v=4","gravatar_id":"","url":"https://api.github.com/users/johnbradley","html_url":"https://github.com/johnbradley","followers_url":"https://api.github.com/users/johnbradley/followers","following_url":"https://api.github.com/users/johnbradley/following{/other_user}","gists_url":"https://api.github.com/users/johnbradley/gists{/gist_id}","starred_url":"https://api.github.com/users/johnbradley/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/johnbradley/subscriptions","organizations_url":"https://api.github.com/users/johnbradley/orgs","repos_url":"https://api.github.com/users/johnbradley/repos","events_url":"https://api.github.com/users/johnbradley/events{/privacy}","received_events_url":"https://api.github.com/users/johnbradley/received_events","type":"User","site_admin":false},"body":"Adds `test-ci.sh` that will build/test all Docker images that have a `unittest.yml` file in their directory.\r\n\r\nExample `unittest.yml` file:\r\n```\r\ncommands:\r\n  - cmd: \"bedtools\"\r\n    expect_text: \".*usage:.*bedtool\r\n```\r\nFor each command in the unittest file the `cmd` will be run against the docker image.\r\nThe output will be compared against the `expect_text` regular expression.\r\nThe code is configured such that '.' will match any character including newlines.\r\n\r\n","created_at":"2018-02-12T18:55:58Z","updated_at":"2018-02-14T16:37:51Z","closed_at":null,"merged_at":null,"merge_commit_sha":"fd43d6ec91c681ee540fa0ce1645a47dccba3ec7","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/pulls/42/commits","review_comments_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/pulls/42/comments","review_comment_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/issues/42/comments","statuses_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/statuses/f4c7ef8e8ef75b28b179653502dc52891d864586","head":{"label":"Duke-GCB:docker-unittest","ref":"docker-unittest","sha":"f4c7ef8e8ef75b28b179653502dc52891d864586","user":{"login":"Duke-GCB","id":7875151,"avatar_url":"https://avatars2.githubusercontent.com/u/7875151?v=4","gravatar_id":"","url":"https://api.github.com/users/Duke-GCB","html_url":"https://github.com/Duke-GCB","followers_url":"https://api.github.com/users/Duke-GCB/followers","following_url":"https://api.github.com/users/Duke-GCB/following{/other_user}","gists_url":"https://api.github.com/users/Duke-GCB/gists{/gist_id}","starred_url":"https://api.github.com/users/Duke-GCB/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Duke-GCB/subscriptions","organizations_url":"https://api.github.com/users/Duke-GCB/orgs","repos_url":"https://api.github.com/users/Duke-GCB/repos","events_url":"https://api.github.com/users/Duke-GCB/events{/privacy}","received_events_url":"https://api.github.com/users/Duke-GCB/received_events","type":"Organization","site_admin":false},"repo":{"id":33499033,"name":"GCB-Dockerfiles","full_name":"Duke-GCB/GCB-Dockerfiles","owner":{"login":"Duke-GCB","id":7875151,"avatar_url":"https://avatars2.githubusercontent.com/u/7875151?v=4","gravatar_id":"","url":"https://api.github.com/users/Duke-GCB","html_url":"https://github.com/Duke-GCB","followers_url":"https://api.github.com/users/Duke-GCB/followers","following_url":"https://api.github.com/users/Duke-GCB/following{/other_user}","gists_url":"https://api.github.com/users/Duke-GCB/gists{/gist_id}","starred_url":"https://api.github.com/users/Duke-GCB/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Duke-GCB/subscriptions","organizations_url":"https://api.github.com/users/Duke-GCB/orgs","repos_url":"https://api.github.com/users/Duke-GCB/repos","events_url":"https://api.github.com/users/Duke-GCB/events{/privacy}","received_events_url":"https://api.github.com/users/Duke-GCB/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Duke-GCB/GCB-Dockerfiles","description":"Docker images for bioinformatic workflows that are built on Docker Hub under the DukeGCB organization.","fork":false,"url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles","forks_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/forks","keys_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/teams","hooks_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/hooks","issue_events_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/issues/events{/number}","events_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/events","assignees_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/assignees{/user}","branches_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/branches{/branch}","tags_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/tags","blobs_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/git/refs{/sha}","trees_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/statuses/{sha}","languages_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/languages","stargazers_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/stargazers","contributors_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/contributors","subscribers_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/subscribers","subscription_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/subscription","commits_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/commits{/sha}","git_commits_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/git/commits{/sha}","comments_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/comments{/number}","issue_comment_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/issues/comments{/number}","contents_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/contents/{+path}","compare_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/merges","archive_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/downloads","issues_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/issues{/number}","pulls_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/pulls{/number}","milestones_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/milestones{/number}","notifications_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/labels{/name}","releases_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/releases{/id}","deployments_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/deployments","created_at":"2015-04-06T18:48:42Z","updated_at":"2018-02-12T21:13:23Z","pushed_at":"2018-02-14T15:48:30Z","git_url":"git://github.com/Duke-GCB/GCB-Dockerfiles.git","ssh_url":"git@github.com:Duke-GCB/GCB-Dockerfiles.git","clone_url":"https://github.com/Duke-GCB/GCB-Dockerfiles.git","svn_url":"https://github.com/Duke-GCB/GCB-Dockerfiles","homepage":"https://hub.docker.com/u/dukegcb/","size":114,"stargazers_count":2,"watchers_count":2,"language":"Shell","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":3,"mirror_url":null,"archived":false,"open_issues_count":9,"license":null,"forks":3,"open_issues":9,"watchers":2,"default_branch":"master"}},"base":{"label":"Duke-GCB:master","ref":"master","sha":"41334058561d26f7804032ed1aff0c67644efcd5","user":{"login":"Duke-GCB","id":7875151,"avatar_url":"https://avatars2.githubusercontent.com/u/7875151?v=4","gravatar_id":"","url":"https://api.github.com/users/Duke-GCB","html_url":"https://github.com/Duke-GCB","followers_url":"https://api.github.com/users/Duke-GCB/followers","following_url":"https://api.github.com/users/Duke-GCB/following{/other_user}","gists_url":"https://api.github.com/users/Duke-GCB/gists{/gist_id}","starred_url":"https://api.github.com/users/Duke-GCB/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Duke-GCB/subscriptions","organizations_url":"https://api.github.com/users/Duke-GCB/orgs","repos_url":"https://api.github.com/users/Duke-GCB/repos","events_url":"https://api.github.com/users/Duke-GCB/events{/privacy}","received_events_url":"https://api.github.com/users/Duke-GCB/received_events","type":"Organization","site_admin":false},"repo":{"id":33499033,"name":"GCB-Dockerfiles","full_name":"Duke-GCB/GCB-Dockerfiles","owner":{"login":"Duke-GCB","id":7875151,"avatar_url":"https://avatars2.githubusercontent.com/u/7875151?v=4","gravatar_id":"","url":"https://api.github.com/users/Duke-GCB","html_url":"https://github.com/Duke-GCB","followers_url":"https://api.github.com/users/Duke-GCB/followers","following_url":"https://api.github.com/users/Duke-GCB/following{/other_user}","gists_url":"https://api.github.com/users/Duke-GCB/gists{/gist_id}","starred_url":"https://api.github.com/users/Duke-GCB/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Duke-GCB/subscriptions","organizations_url":"https://api.github.com/users/Duke-GCB/orgs","repos_url":"https://api.github.com/users/Duke-GCB/repos","events_url":"https://api.github.com/users/Duke-GCB/events{/privacy}","received_events_url":"https://api.github.com/users/Duke-GCB/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Duke-GCB/GCB-Dockerfiles","description":"Docker images for bioinformatic workflows that are built on Docker Hub under the DukeGCB organization.","fork":false,"url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles","forks_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/forks","keys_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/teams","hooks_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/hooks","issue_events_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/issues/events{/number}","events_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/events","assignees_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/assignees{/user}","branches_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/branches{/branch}","tags_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/tags","blobs_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/git/refs{/sha}","trees_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/statuses/{sha}","languages_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/languages","stargazers_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/stargazers","contributors_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/contributors","subscribers_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/subscribers","subscription_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/subscription","commits_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/commits{/sha}","git_commits_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/git/commits{/sha}","comments_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/comments{/number}","issue_comment_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/issues/comments{/number}","contents_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/contents/{+path}","compare_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/merges","archive_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/downloads","issues_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/issues{/number}","pulls_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/pulls{/number}","milestones_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/milestones{/number}","notifications_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/labels{/name}","releases_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/releases{/id}","deployments_url":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/deployments","created_at":"2015-04-06T18:48:42Z","updated_at":"2018-02-12T21:13:23Z","pushed_at":"2018-02-14T15:48:30Z","git_url":"git://github.com/Duke-GCB/GCB-Dockerfiles.git","ssh_url":"git@github.com:Duke-GCB/GCB-Dockerfiles.git","clone_url":"https://github.com/Duke-GCB/GCB-Dockerfiles.git","svn_url":"https://github.com/Duke-GCB/GCB-Dockerfiles","homepage":"https://hub.docker.com/u/dukegcb/","size":114,"stargazers_count":2,"watchers_count":2,"language":"Shell","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":3,"mirror_url":null,"archived":false,"open_issues_count":9,"license":null,"forks":3,"open_issues":9,"watchers":2,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/pulls/42"},"html":{"href":"https://github.com/Duke-GCB/GCB-Dockerfiles/pull/42"},"issue":{"href":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/issues/42"},"comments":{"href":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/issues/42/comments"},"review_comments":{"href":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/pulls/42/comments"},"review_comment":{"href":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/pulls/42/commits"},"statuses":{"href":"https://api.github.com/repos/Duke-GCB/GCB-Dockerfiles/statuses/f4c7ef8e8ef75b28b179653502dc52891d864586"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"dleehr"}}