{"action":"created","comment":{"url":"https://api.github.com/repos/crowbar/crowbar-core/pulls/comments/59222162","id":59222162,"diff_hunk":"@@ -0,0 +1,277 @@\n+#!/usr/bin/env ruby\n+#\n+# Copyright 2011-2013, Dell\n+# Copyright 2011-2016, Chef Software Inc.\n+# Copyright 2013-2016, SUSE Linux GmbH\n+#\n+# Licensed under the Apache License, Version 2.0 (the \"License\");\n+# you may not use this file except in compliance with the License.\n+# You may obtain a copy of the License at\n+#\n+#  http://www.apache.org/licenses/LICENSE-2.0\n+#\n+# Unless required by applicable law or agreed to in writing, software\n+# distributed under the License is distributed on an \"AS IS\" BASIS,\n+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+# See the License for the specific language governing permissions and\n+# limitations under the License.\n+#\n+\n+require \"chef/cookbook_uploader\"\n+require \"chef/knife/core/object_loader\"\n+\n+module Crowbar\n+  class Chef\n+    class Upload\n+      class << self\n+        def all\n+          cookbooks\n+          data_bags\n+          roles\n+        end\n+\n+        def cookbooks\n+          version_constraints_to_update = Hash.new\n+          cookbooks_for_upload = Array.new\n+\n+          cookbook_loader.each do |cookbook_name, cookbook|\n+            cookbooks_for_upload.push(cookbook)\n+            version_constraints_to_update[cookbook_name] = cookbook.version\n+          end\n+\n+          logger.info(\"Checking existing cookbooks...\")\n+          cookbooks_for_upload.reject! do |cookbook|\n+            next unless api_coobbooks.include?(cookbook.name.to_s)\n+\n+            local_md5sums = local_cookbook_md5_checksums(cookbook)\n+            remote_md5sums = api_cookbook_md5_checksums(cookbook.name)\n+            combined_md5sums = local_md5sums.deep_merge(remote_md5sums)\n+\n+            combined_md5sums.reject! { |cb, _| local_md5sums.include? cb }\n+            if combined_md5sums.empty?\n+              logger.info(\"Cookbook #{cookbook.name} already existis, skipping...\")\n+              true\n+            end\n+          end\n+\n+          unless cookbooks_for_upload.any?\n+            logger.info(\"All cookbooks are already existing on the server\")\n+            return true\n+          end\n+\n+          cookbooks_for_upload.each do |cookbook|\n+            logger.info(\"Checking cookbook #{cookbook.name}\")\n+            return false unless check_links(cookbook)\n+            return false unless check_dependencies(cookbook)\n+          end\n+\n+          cookbook_names = cookbooks_for_upload.map(&:name)\n+\n+          logger.info(\"Uploading cookbooks... #{cookbook_names.join(\", \")}\")\n+          ::Chef::CookbookUploader.new(\n+            cookbooks_for_upload,\n+            force: false,\n+            concurrency: cookbooks_for_upload.count\n+          ).upload_cookbooks\n+        end\n+\n+        def data_bags\n+          loader = ::Chef::Knife::Core::ObjectLoader.new(::Chef::DataBagItem, logger)\n+          data_bags = loader.find_all_object_dirs(chef_data_bags_path) || []\n+          data_bags.each do |data_bag|\n+            data_bag_items = loader.find_all_objects(chef_data_bags_path.join(data_bag))\n+            data_bag_item_paths = normalize_data_bag_item_paths(data_bag_items) || []\n+            data_bag_item_paths.each do |data_bag_item_path|\n+              # Workaround for a strange chef behavior\n+              relative_path = chef_data_bags_path.relative_path_from(Pathname.new(Dir.pwd))\n+              data_bag_item = loader.load_from(relative_path, data_bag, data_bag_item_path)\n+              bag = ::Chef::DataBagItem.new\n+              bag.data_bag(data_bag)\n+              bag.raw_data = data_bag_item\n+              logger.info(\"Uploading data_bag #{data_bag_item_path}\")\n+              bag.save\n+            end\n+          end\n+        end\n+\n+        def roles\n+          loader = ::Chef::Knife::Core::ObjectLoader.new(::Chef::Role, logger)\n+          chef_roles_path.each_child do |file|\n+            next unless file.file?\n+            role = loader.load_from(\"roles\", file)\n+            logger.info(\"Uploading role #{file.basename}\")\n+            role.save\n+          end\n+        end\n+\n+        protected\n+\n+        def logger\n+          return Rails.logger unless caller.grep(/rake/).present?\n+          Logger.new(STDOUT)\n+        end\n+\n+        def chef_dir_path\n+          Rails.root.join(\"..\", \"chef\")\n+        end\n+\n+        def chef_cookbooks_path\n+          chef_dir_path.join(\"cookbooks\")\n+        end\n+\n+        def chef_data_bags_path\n+          chef_dir_path.join(\"data_bags\")\n+        end\n+\n+        def chef_roles_path\n+          chef_dir_path.join(\"roles\")\n+        end\n+\n+        def version_constraint(version)\n+          ::Chef::VersionConstraint.new(version)\n+        end\n+\n+        def cookbook_loader\n+          @cookbook_loader ||= ::Chef::CookbookLoader.new(chef_cookbooks_path)\n+        end\n+\n+        def cookbooks_to_upload\n+          cookbook_loader.load_cookbooks\n+        end\n+\n+        def check_dependencies(cookbook)\n+          missing_dependencies = cookbook.metadata.dependencies.reject do |cookbook_name, version|\n+            check_server_side_cookbooks(cookbook_name, version) ||\n+              check_uploading_cookbooks(cookbook_name, version)\n+          end\n+\n+          unless missing_dependencies.empty?\n+            missing_cookbooks = missing_dependencies.map do |cookbook_name, version|\n+              \"\\\"#{cookbook_name}\\\" version \\\"#{version}\\\"\"\n+            end\n+            logger.error(\n+              \"Cookbook #{cookbook.name} dependencies are not resolable:\" \\\n+              \"#{missing_cookbooks.join(\", \")}\"\n+            )\n+            return false\n+          end\n+          true\n+        end\n+\n+        def check_links(cookbook)\n+          broken_files = cookbook.dup.manifest_records_by_path.select do |path, info|\n+            info[:checksum].nil? || info[:checksum] !~ /[0-9a-f]{32,}/\n+          end\n+\n+          unless broken_files.empty?\n+            broken_filenames = Array(broken_files).map { |path, info| path }\n+            logger.error(\n+              \"The cookbook #{cookbook.name} has the following broken files: \" \\\n+              \"#{broken_filenames.join(\", \")}\"\n+            )\n+            return false\n+          end\n+          true\n+        end\n+\n+        def check_server_side_cookbooks(cookbook_name, version)\n+          server_side_cookbooks = ::Chef::CookbookVersion.list_all_versions\n+          return false if server_side_cookbooks[cookbook_name].nil?\n+          server_side_cookbooks[cookbook_name][\"versions\"].each do |versions_hash|\n+            if version_constraint(version).include?(versions_hash[\"version\"])\n+              return true\n+            end\n+          end\n+          false\n+        end\n+\n+        def check_uploading_cookbooks(cookbook_name, version)\n+          unless cookbooks_to_upload[cookbook_name].nil?\n+            if version_constraint(version).include?(cookbooks_to_upload[cookbook_name].version)\n+              return true\n+            end\n+          end\n+          false\n+        end\n+\n+        def normalize_data_bag_item_paths(data_bag_items)\n+          paths = Array.new\n+          data_bag_items.each do |path|\n+            if File.directory?(path)\n+              paths.concat(Dir.glob(File.join(path, \"*.json\")))\n+            else\n+              paths.push(path)\n+            end\n+          end\n+          paths\n+        end\n+\n+        def api\n+          @rest ||= ::Chef::REST.new(\"http://localhost:4000\")","path":"crowbar_framework/lib/crowbar/chef/upload.rb","position":210,"original_position":210,"commit_id":"ba773ceb8a35a33a66a13a39a52258eac0abd53d","original_commit_id":"ba773ceb8a35a33a66a13a39a52258eac0abd53d","user":{"login":"MaximilianMeister","id":5364817,"avatar_url":"https://avatars.githubusercontent.com/u/5364817?v=3","gravatar_id":"","url":"https://api.github.com/users/MaximilianMeister","html_url":"https://github.com/MaximilianMeister","followers_url":"https://api.github.com/users/MaximilianMeister/followers","following_url":"https://api.github.com/users/MaximilianMeister/following{/other_user}","gists_url":"https://api.github.com/users/MaximilianMeister/gists{/gist_id}","starred_url":"https://api.github.com/users/MaximilianMeister/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/MaximilianMeister/subscriptions","organizations_url":"https://api.github.com/users/MaximilianMeister/orgs","repos_url":"https://api.github.com/users/MaximilianMeister/repos","events_url":"https://api.github.com/users/MaximilianMeister/events{/privacy}","received_events_url":"https://api.github.com/users/MaximilianMeister/received_events","type":"User","site_admin":false},"body":"can we call it `@api` like `@api_cookbooks` below?","created_at":"2016-04-11T15:13:27Z","updated_at":"2016-04-11T15:13:27Z","html_url":"https://github.com/crowbar/crowbar-core/pull/441#discussion_r59222162","pull_request_url":"https://api.github.com/repos/crowbar/crowbar-core/pulls/441","_links":{"self":{"href":"https://api.github.com/repos/crowbar/crowbar-core/pulls/comments/59222162"},"html":{"href":"https://github.com/crowbar/crowbar-core/pull/441#discussion_r59222162"},"pull_request":{"href":"https://api.github.com/repos/crowbar/crowbar-core/pulls/441"}}},"pull_request":{"url":"https://api.github.com/repos/crowbar/crowbar-core/pulls/441","id":63718810,"html_url":"https://github.com/crowbar/crowbar-core/pull/441","diff_url":"https://github.com/crowbar/crowbar-core/pull/441.diff","patch_url":"https://github.com/crowbar/crowbar-core/pull/441.patch","issue_url":"https://api.github.com/repos/crowbar/crowbar-core/issues/441","number":441,"state":"open","locked":false,"title":"Chef upload library","user":{"login":"rsalevsky","id":4128547,"avatar_url":"https://avatars.githubusercontent.com/u/4128547?v=3","gravatar_id":"","url":"https://api.github.com/users/rsalevsky","html_url":"https://github.com/rsalevsky","followers_url":"https://api.github.com/users/rsalevsky/followers","following_url":"https://api.github.com/users/rsalevsky/following{/other_user}","gists_url":"https://api.github.com/users/rsalevsky/gists{/gist_id}","starred_url":"https://api.github.com/users/rsalevsky/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rsalevsky/subscriptions","organizations_url":"https://api.github.com/users/rsalevsky/orgs","repos_url":"https://api.github.com/users/rsalevsky/repos","events_url":"https://api.github.com/users/rsalevsky/events{/privacy}","received_events_url":"https://api.github.com/users/rsalevsky/received_events","type":"User","site_admin":false},"body":"","created_at":"2016-03-22T13:26:13Z","updated_at":"2016-04-11T15:13:27Z","closed_at":null,"merged_at":null,"merge_commit_sha":"007501fcdad6a0f58742d27596af4adb52f40cd6","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/crowbar/crowbar-core/pulls/441/commits","review_comments_url":"https://api.github.com/repos/crowbar/crowbar-core/pulls/441/comments","review_comment_url":"https://api.github.com/repos/crowbar/crowbar-core/pulls/comments{/number}","comments_url":"https://api.github.com/repos/crowbar/crowbar-core/issues/441/comments","statuses_url":"https://api.github.com/repos/crowbar/crowbar-core/statuses/ba773ceb8a35a33a66a13a39a52258eac0abd53d","head":{"label":"rsalevsky:chef_upload","ref":"chef_upload","sha":"ba773ceb8a35a33a66a13a39a52258eac0abd53d","user":{"login":"rsalevsky","id":4128547,"avatar_url":"https://avatars.githubusercontent.com/u/4128547?v=3","gravatar_id":"","url":"https://api.github.com/users/rsalevsky","html_url":"https://github.com/rsalevsky","followers_url":"https://api.github.com/users/rsalevsky/followers","following_url":"https://api.github.com/users/rsalevsky/following{/other_user}","gists_url":"https://api.github.com/users/rsalevsky/gists{/gist_id}","starred_url":"https://api.github.com/users/rsalevsky/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rsalevsky/subscriptions","organizations_url":"https://api.github.com/users/rsalevsky/orgs","repos_url":"https://api.github.com/users/rsalevsky/repos","events_url":"https://api.github.com/users/rsalevsky/events{/privacy}","received_events_url":"https://api.github.com/users/rsalevsky/received_events","type":"User","site_admin":false},"repo":{"id":40906953,"name":"crowbar-core","full_name":"rsalevsky/crowbar-core","owner":{"login":"rsalevsky","id":4128547,"avatar_url":"https://avatars.githubusercontent.com/u/4128547?v=3","gravatar_id":"","url":"https://api.github.com/users/rsalevsky","html_url":"https://github.com/rsalevsky","followers_url":"https://api.github.com/users/rsalevsky/followers","following_url":"https://api.github.com/users/rsalevsky/following{/other_user}","gists_url":"https://api.github.com/users/rsalevsky/gists{/gist_id}","starred_url":"https://api.github.com/users/rsalevsky/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rsalevsky/subscriptions","organizations_url":"https://api.github.com/users/rsalevsky/orgs","repos_url":"https://api.github.com/users/rsalevsky/repos","events_url":"https://api.github.com/users/rsalevsky/events{/privacy}","received_events_url":"https://api.github.com/users/rsalevsky/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/rsalevsky/crowbar-core","description":"Core deployment for Crowbar","fork":true,"url":"https://api.github.com/repos/rsalevsky/crowbar-core","forks_url":"https://api.github.com/repos/rsalevsky/crowbar-core/forks","keys_url":"https://api.github.com/repos/rsalevsky/crowbar-core/keys{/key_id}","collaborators_url":"https://api.github.com/repos/rsalevsky/crowbar-core/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/rsalevsky/crowbar-core/teams","hooks_url":"https://api.github.com/repos/rsalevsky/crowbar-core/hooks","issue_events_url":"https://api.github.com/repos/rsalevsky/crowbar-core/issues/events{/number}","events_url":"https://api.github.com/repos/rsalevsky/crowbar-core/events","assignees_url":"https://api.github.com/repos/rsalevsky/crowbar-core/assignees{/user}","branches_url":"https://api.github.com/repos/rsalevsky/crowbar-core/branches{/branch}","tags_url":"https://api.github.com/repos/rsalevsky/crowbar-core/tags","blobs_url":"https://api.github.com/repos/rsalevsky/crowbar-core/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/rsalevsky/crowbar-core/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/rsalevsky/crowbar-core/git/refs{/sha}","trees_url":"https://api.github.com/repos/rsalevsky/crowbar-core/git/trees{/sha}","statuses_url":"https://api.github.com/repos/rsalevsky/crowbar-core/statuses/{sha}","languages_url":"https://api.github.com/repos/rsalevsky/crowbar-core/languages","stargazers_url":"https://api.github.com/repos/rsalevsky/crowbar-core/stargazers","contributors_url":"https://api.github.com/repos/rsalevsky/crowbar-core/contributors","subscribers_url":"https://api.github.com/repos/rsalevsky/crowbar-core/subscribers","subscription_url":"https://api.github.com/repos/rsalevsky/crowbar-core/subscription","commits_url":"https://api.github.com/repos/rsalevsky/crowbar-core/commits{/sha}","git_commits_url":"https://api.github.com/repos/rsalevsky/crowbar-core/git/commits{/sha}","comments_url":"https://api.github.com/repos/rsalevsky/crowbar-core/comments{/number}","issue_comment_url":"https://api.github.com/repos/rsalevsky/crowbar-core/issues/comments{/number}","contents_url":"https://api.github.com/repos/rsalevsky/crowbar-core/contents/{+path}","compare_url":"https://api.github.com/repos/rsalevsky/crowbar-core/compare/{base}...{head}","merges_url":"https://api.github.com/repos/rsalevsky/crowbar-core/merges","archive_url":"https://api.github.com/repos/rsalevsky/crowbar-core/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/rsalevsky/crowbar-core/downloads","issues_url":"https://api.github.com/repos/rsalevsky/crowbar-core/issues{/number}","pulls_url":"https://api.github.com/repos/rsalevsky/crowbar-core/pulls{/number}","milestones_url":"https://api.github.com/repos/rsalevsky/crowbar-core/milestones{/number}","notifications_url":"https://api.github.com/repos/rsalevsky/crowbar-core/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/rsalevsky/crowbar-core/labels{/name}","releases_url":"https://api.github.com/repos/rsalevsky/crowbar-core/releases{/id}","deployments_url":"https://api.github.com/repos/rsalevsky/crowbar-core/deployments","created_at":"2015-08-17T15:42:44Z","updated_at":"2016-02-25T14:15:13Z","pushed_at":"2016-04-11T11:18:47Z","git_url":"git://github.com/rsalevsky/crowbar-core.git","ssh_url":"git@github.com:rsalevsky/crowbar-core.git","clone_url":"https://github.com/rsalevsky/crowbar-core.git","svn_url":"https://github.com/rsalevsky/crowbar-core","homepage":null,"size":105961,"stargazers_count":0,"watchers_count":0,"language":"Ruby","has_issues":false,"has_downloads":true,"has_wiki":true,"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":"crowbar:master","ref":"master","sha":"99cdc0a921b24b8189bff9a83b3f768111a8d183","user":{"login":"crowbar","id":2910205,"avatar_url":"https://avatars.githubusercontent.com/u/2910205?v=3","gravatar_id":"","url":"https://api.github.com/users/crowbar","html_url":"https://github.com/crowbar","followers_url":"https://api.github.com/users/crowbar/followers","following_url":"https://api.github.com/users/crowbar/following{/other_user}","gists_url":"https://api.github.com/users/crowbar/gists{/gist_id}","starred_url":"https://api.github.com/users/crowbar/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/crowbar/subscriptions","organizations_url":"https://api.github.com/users/crowbar/orgs","repos_url":"https://api.github.com/users/crowbar/repos","events_url":"https://api.github.com/users/crowbar/events{/privacy}","received_events_url":"https://api.github.com/users/crowbar/received_events","type":"Organization","site_admin":false},"repo":{"id":39003227,"name":"crowbar-core","full_name":"crowbar/crowbar-core","owner":{"login":"crowbar","id":2910205,"avatar_url":"https://avatars.githubusercontent.com/u/2910205?v=3","gravatar_id":"","url":"https://api.github.com/users/crowbar","html_url":"https://github.com/crowbar","followers_url":"https://api.github.com/users/crowbar/followers","following_url":"https://api.github.com/users/crowbar/following{/other_user}","gists_url":"https://api.github.com/users/crowbar/gists{/gist_id}","starred_url":"https://api.github.com/users/crowbar/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/crowbar/subscriptions","organizations_url":"https://api.github.com/users/crowbar/orgs","repos_url":"https://api.github.com/users/crowbar/repos","events_url":"https://api.github.com/users/crowbar/events{/privacy}","received_events_url":"https://api.github.com/users/crowbar/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/crowbar/crowbar-core","description":"Core deployment for Crowbar","fork":false,"url":"https://api.github.com/repos/crowbar/crowbar-core","forks_url":"https://api.github.com/repos/crowbar/crowbar-core/forks","keys_url":"https://api.github.com/repos/crowbar/crowbar-core/keys{/key_id}","collaborators_url":"https://api.github.com/repos/crowbar/crowbar-core/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/crowbar/crowbar-core/teams","hooks_url":"https://api.github.com/repos/crowbar/crowbar-core/hooks","issue_events_url":"https://api.github.com/repos/crowbar/crowbar-core/issues/events{/number}","events_url":"https://api.github.com/repos/crowbar/crowbar-core/events","assignees_url":"https://api.github.com/repos/crowbar/crowbar-core/assignees{/user}","branches_url":"https://api.github.com/repos/crowbar/crowbar-core/branches{/branch}","tags_url":"https://api.github.com/repos/crowbar/crowbar-core/tags","blobs_url":"https://api.github.com/repos/crowbar/crowbar-core/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/crowbar/crowbar-core/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/crowbar/crowbar-core/git/refs{/sha}","trees_url":"https://api.github.com/repos/crowbar/crowbar-core/git/trees{/sha}","statuses_url":"https://api.github.com/repos/crowbar/crowbar-core/statuses/{sha}","languages_url":"https://api.github.com/repos/crowbar/crowbar-core/languages","stargazers_url":"https://api.github.com/repos/crowbar/crowbar-core/stargazers","contributors_url":"https://api.github.com/repos/crowbar/crowbar-core/contributors","subscribers_url":"https://api.github.com/repos/crowbar/crowbar-core/subscribers","subscription_url":"https://api.github.com/repos/crowbar/crowbar-core/subscription","commits_url":"https://api.github.com/repos/crowbar/crowbar-core/commits{/sha}","git_commits_url":"https://api.github.com/repos/crowbar/crowbar-core/git/commits{/sha}","comments_url":"https://api.github.com/repos/crowbar/crowbar-core/comments{/number}","issue_comment_url":"https://api.github.com/repos/crowbar/crowbar-core/issues/comments{/number}","contents_url":"https://api.github.com/repos/crowbar/crowbar-core/contents/{+path}","compare_url":"https://api.github.com/repos/crowbar/crowbar-core/compare/{base}...{head}","merges_url":"https://api.github.com/repos/crowbar/crowbar-core/merges","archive_url":"https://api.github.com/repos/crowbar/crowbar-core/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/crowbar/crowbar-core/downloads","issues_url":"https://api.github.com/repos/crowbar/crowbar-core/issues{/number}","pulls_url":"https://api.github.com/repos/crowbar/crowbar-core/pulls{/number}","milestones_url":"https://api.github.com/repos/crowbar/crowbar-core/milestones{/number}","notifications_url":"https://api.github.com/repos/crowbar/crowbar-core/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/crowbar/crowbar-core/labels{/name}","releases_url":"https://api.github.com/repos/crowbar/crowbar-core/releases{/id}","deployments_url":"https://api.github.com/repos/crowbar/crowbar-core/deployments","created_at":"2015-07-13T09:31:28Z","updated_at":"2016-04-10T12:47:20Z","pushed_at":"2016-04-11T11:18:49Z","git_url":"git://github.com/crowbar/crowbar-core.git","ssh_url":"git@github.com:crowbar/crowbar-core.git","clone_url":"https://github.com/crowbar/crowbar-core.git","svn_url":"https://github.com/crowbar/crowbar-core","homepage":null,"size":120360,"stargazers_count":6,"watchers_count":6,"language":"Ruby","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":23,"mirror_url":null,"open_issues_count":35,"forks":23,"open_issues":35,"watchers":6,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/crowbar/crowbar-core/pulls/441"},"html":{"href":"https://github.com/crowbar/crowbar-core/pull/441"},"issue":{"href":"https://api.github.com/repos/crowbar/crowbar-core/issues/441"},"comments":{"href":"https://api.github.com/repos/crowbar/crowbar-core/issues/441/comments"},"review_comments":{"href":"https://api.github.com/repos/crowbar/crowbar-core/pulls/441/comments"},"review_comment":{"href":"https://api.github.com/repos/crowbar/crowbar-core/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/crowbar/crowbar-core/pulls/441/commits"},"statuses":{"href":"https://api.github.com/repos/crowbar/crowbar-core/statuses/ba773ceb8a35a33a66a13a39a52258eac0abd53d"}}}}