{"action":"created","comment":{"url":"https://api.github.com/repos/crowbar/barclamp-crowbar/pulls/comments/35191534","id":35191534,"diff_hunk":"@@ -0,0 +1,377 @@\n+#\n+# Copyright 2015, SUSE LINUX Products 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+module Crowbar\n+  class DeploymentQueue\n+    include CrowbarPacemakerProxy\n+\n+    attr_reader :logger, :proposal_queue\n+\n+    def initialize(logger: Rails.logger, queue: ProposalQueue.new)\n+      @logger         = logger\n+      @proposal_queue = queue\n+    end\n+\n+    # Receives proposal info (name, barclamp), list of nodes (elements), on which the proposal\n+    # should be applied, and list of dependencies - a list of {barclamp, name/inst} hashes.\n+    # It adds them to the queue, if possible.\n+    def queue_proposal(bc, inst, elements, deps)\n+      logger.debug(\"queue proposal: enter #{inst} #{bc}\")\n+      delay = []\n+      pre_cached_nodes = {}\n+      begin\n+        f = file_lock.acquire(\"queue\", logger: logger)\n+\n+        preexisting_queued_item = proposal_queue.proposals.find do |item|\n+          item[\"barclamp\"] == bc && item[\"inst\"] == inst\n+        end\n+\n+        # If queue_me is true, the delay contains all elements, otherwise, only\n+        # nodes that are not ready.\n+        queue_me = !dependencies_satisfied?(item)\n+\n+        # Delay is a list of nodes that are not in ready state. pre_cached_nodes\n+        # is an uninteresting optimization.\n+        delay, pre_cached_nodes = add_pending_elements(bc, inst, elements, queue_me)\n+\n+        # We have all nodes ready.\n+        if delay.empty?\n+          # There's a path: process_queue -> proposal_commit -> apply_role ->\n+          # queue_proposal, which seems to be used as a test if all dependencies\n+          # (queue_me = false) and nodes (delay.empty?) are still in that state\n+          # by the time we want to apply. So if that is the case, we just drop\n+          # proposal from the queue and exit.\n+          #\n+          # remove from queue if it was queued before; might not be in the queue\n+          # because the proposal got changed since it got added to the queue\n+          unless preexisting_queued_item.nil?\n+            logger.debug(\"queue proposal: dequeuing already queued #{inst} #{bc}\")\n+            dequeue_proposal_no_lock(bc, inst)\n+          end\n+\n+          return [ delay, pre_cached_nodes ]\n+        end\n+\n+        # Delay not empty, we're missing some nodes.\n+        # And proposal is not in queue\n+        if preexisting_queued_item.nil?\n+          proposal_queue << { \"barclamp\" => bc, \"inst\" => inst, \"elements\" => elements, \"deps\" => deps }\n+        else\n+          # Update (overwrite) item that is already in queue\n+          preexisting_queued_item[\"elements\"] = elements\n+          preexisting_queued_item[\"deps\"] = deps\n+          proposal_queue.save\n+        end\n+      rescue StandardError => e\n+        logger.error(\"Error queuing proposal for #{bc}:#{inst}: #{e.message}\")\n+      ensure\n+        file_lock.release(f)\n+      end\n+\n+      # Mark the proposal as in the queue\n+      prop = Proposal.where(barclamp: bc, name: inst).first\n+      prop[\"deployment\"][bc][\"crowbar-queued\"] = true\n+      prop.save\n+      logger.debug(\"queue proposal: exit #{inst} #{bc}\")\n+      [ delay, pre_cached_nodes ]\n+    end\n+\n+    # Locking wrapper around dequeue_proposal_no_lock\n+    def dequeue_proposal(bc, inst)\n+      logger.debug(\"dequeue proposal: enter #{inst} #{bc}\")\n+      dequeued = false\n+      begin\n+        f = file_lock.acquire(\"queue\", logger: logger)\n+\n+        if proposal_queue.empty?\n+          logger.debug(\"dequeue proposal: exit #{inst} #{bc}: no entry\")\n+          return [200, {}]\n+        end\n+\n+        dequeued = dequeue_proposal_no_lock(bc, inst)\n+      rescue StandardError => e\n+        logger.error(\"Error dequeuing proposal for #{bc}:#{inst}: #{e.message} #{e.backtrace.join(\"\\n\")}\")\n+        logger.debug(\"dequeue proposal: exit #{inst} #{bc}: error\")\n+        return [400, e.message]\n+      ensure\n+        file_lock.release(f)\n+      end\n+      logger.debug(\"dequeue proposal: exit #{inst} #{bc}\")\n+      return dequeued ? [200, {}] : [400, '']\n+    end\n+\n+    #\n+    # NOTE: If dependencies don't form a DAG (Directed Acyclic Graph) then we have a problem\n+    # with our dependency algorithm\n+    #\n+    def process_queue\n+      logger.debug(\"process queue: enter\")\n+      loop_again = true\n+      while loop_again\n+        loop_again = false\n+\n+        # List contains proposals in the queue that can be applied\n+        # Remove list contains proposals that were either deleted\n+        # or should be re-queued?\n+        # Proposals which reference non-ready nodes are also skipped.\n+        list = []\n+        begin\n+          f = file_lock.acquire(\"queue\", logger: logger)\n+\n+          if proposal_queue.empty?\n+            logger.debug(\"process queue: exit: empty queue\")\n+            return\n+          end\n+          logger.debug(\"process queue: queue: #{proposal_queue.proposals.inspect}\")\n+\n+          # Test for ready\n+          remove_list = []\n+          proposal_queue.proposals.each do |item|\n+            prop = Proposal.where(barclamp: item[\"barclamp\"], name: item[\"inst\"]).first\n+            if prop.nil?\n+              remove_list << item\n+              next\n+            end\n+\n+            next unless dependencies_satisfied?(item)\n+\n+            nodes_map = elements_to_nodes_to_roles_map(prop[\"deployment\"][item[\"barclamp\"]][\"elements\"])\n+            delay, pre_cached_nodes = elements_not_ready(nodes_map.keys)\n+            list << item if delay.empty?\n+          end\n+\n+          # Update the queue. Drop all proposals that we can process now (list) and those\n+          # that are deleted (remove_list). This leaves in the queue only proposals\n+          # which are still waiting for nodes (delay not empty), or for which deps are not\n+          # ready/created/deployed (queue_me = true).\n+          remove_list.each do |iii|\n+            dequeue_proposal_no_lock(iii[\"barclamp\"], iii[\"inst\"])\n+          end\n+\n+          list.each do |iii|\n+            dequeue_proposal_no_lock(iii[\"barclamp\"], iii[\"inst\"])\n+          end\n+        rescue StandardError => e\n+          logger.error(\"Error processing queue: #{e.message}\")\n+          logger.debug(\"process queue: exit: error\")\n+          return\n+        ensure\n+          file_lock.release(f)\n+        end\n+\n+        logger.debug(\"process queue: list: #{list.inspect}\")\n+\n+        # For each ready item, apply it.\n+        list.each do |item|\n+          logger.debug(\"process queue: item to do: #{item.inspect}\")\n+\n+          bc = item[\"barclamp\"]\n+          inst = item[\"inst\"]\n+          service = eval(\"#{bc.camelize}Service.new logger\")\n+\n+          # This will call apply_role and chef-client.\n+          # Params: (inst, in_queue, validate_after_save)\n+          answer = service.proposal_commit(inst, true, false)\n+\n+          logger.debug(\"process queue: item #{item.inspect}: results #{answer.inspect}\")\n+\n+          # 202 means some nodes are not ready, bail out in that case\n+          # We're re-running the whole apply continuously, until there\n+          # are no items left in the queue.\n+          # FIXME: This is lame, because from the user perspective, we're still\n+          # applying the first barclamp, while this part was in fact already\n+          # completed and we're applying next item(s) in the queue.\n+          loop_again = true if answer[0] != 202\n+\n+          $htdigest_reload = true\n+        end\n+        logger.debug(\"process queue: exit\")\n+      end\n+    end\n+\n+    private\n+\n+    # Deps are satisfied if all exist, have been deployed and are not in the queue ATM.\n+    def dependencies_satisfied?(item)\n+      item[\"deps\"].all? do |dep|\n+        depprop = Proposal.where(barclamp: dep[\"barclamp\"], name: dep[\"inst\"]).first\n+        depprop_queued   = depprop[\"deployment\"][dep[\"barclamp\"]][\"crowbar-queued\"] rescue false\n+        depprop_deployed = (depprop[\"deployment\"][dep[\"barclamp\"]][\"crowbar-status\"] == \"success\") rescue false\n+\n+        depprop && !depprop_queued && depprop_deployed\n+      end\n+    end\n+\n+","path":"crowbar_framework/lib/crowbar/deployment_queue.rb","position":217,"original_position":217,"commit_id":"dbd880de00c2c3592a2e6f82fdb0b25f3f037a4d","original_commit_id":"dbd880de00c2c3592a2e6f82fdb0b25f3f037a4d","user":{"login":"houndci","id":6697940,"avatar_url":"https://avatars.githubusercontent.com/u/6697940?v=3","gravatar_id":"","url":"https://api.github.com/users/houndci","html_url":"https://github.com/houndci","followers_url":"https://api.github.com/users/houndci/followers","following_url":"https://api.github.com/users/houndci/following{/other_user}","gists_url":"https://api.github.com/users/houndci/gists{/gist_id}","starred_url":"https://api.github.com/users/houndci/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/houndci/subscriptions","organizations_url":"https://api.github.com/users/houndci/orgs","repos_url":"https://api.github.com/users/houndci/repos","events_url":"https://api.github.com/users/houndci/events{/privacy}","received_events_url":"https://api.github.com/users/houndci/received_events","type":"User","site_admin":false},"body":"Style/EmptyLines: Extra blank line detected.","created_at":"2015-07-22T08:34:17Z","updated_at":"2015-07-22T08:34:17Z","html_url":"https://github.com/crowbar/barclamp-crowbar/pull/1314#discussion_r35191534","pull_request_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/pulls/1314","_links":{"self":{"href":"https://api.github.com/repos/crowbar/barclamp-crowbar/pulls/comments/35191534"},"html":{"href":"https://github.com/crowbar/barclamp-crowbar/pull/1314#discussion_r35191534"},"pull_request":{"href":"https://api.github.com/repos/crowbar/barclamp-crowbar/pulls/1314"}}},"pull_request":{"url":"https://api.github.com/repos/crowbar/barclamp-crowbar/pulls/1314","id":39786595,"html_url":"https://github.com/crowbar/barclamp-crowbar/pull/1314","diff_url":"https://github.com/crowbar/barclamp-crowbar/pull/1314.diff","patch_url":"https://github.com/crowbar/barclamp-crowbar/pull/1314.patch","issue_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/issues/1314","number":1314,"state":"open","locked":false,"title":"[WIP] Refactor deployment queue","user":{"login":"bkutil","id":696026,"avatar_url":"https://avatars.githubusercontent.com/u/696026?v=3","gravatar_id":"","url":"https://api.github.com/users/bkutil","html_url":"https://github.com/bkutil","followers_url":"https://api.github.com/users/bkutil/followers","following_url":"https://api.github.com/users/bkutil/following{/other_user}","gists_url":"https://api.github.com/users/bkutil/gists{/gist_id}","starred_url":"https://api.github.com/users/bkutil/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bkutil/subscriptions","organizations_url":"https://api.github.com/users/bkutil/orgs","repos_url":"https://api.github.com/users/bkutil/repos","events_url":"https://api.github.com/users/bkutil/events{/privacy}","received_events_url":"https://api.github.com/users/bkutil/received_events","type":"User","site_admin":false},"body":"Move queueing code to separate module & refactor.","created_at":"2015-07-13T07:54:29Z","updated_at":"2015-07-22T08:34:17Z","closed_at":null,"merged_at":null,"merge_commit_sha":"c2698ad5ee80477ca9bf979a0d5103e9bf2aac1d","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/pulls/1314/commits","review_comments_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/pulls/1314/comments","review_comment_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/pulls/comments{/number}","comments_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/issues/1314/comments","statuses_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/statuses/dbd880de00c2c3592a2e6f82fdb0b25f3f037a4d","head":{"label":"bkutil:deployment_queue","ref":"deployment_queue","sha":"dbd880de00c2c3592a2e6f82fdb0b25f3f037a4d","user":{"login":"bkutil","id":696026,"avatar_url":"https://avatars.githubusercontent.com/u/696026?v=3","gravatar_id":"","url":"https://api.github.com/users/bkutil","html_url":"https://github.com/bkutil","followers_url":"https://api.github.com/users/bkutil/followers","following_url":"https://api.github.com/users/bkutil/following{/other_user}","gists_url":"https://api.github.com/users/bkutil/gists{/gist_id}","starred_url":"https://api.github.com/users/bkutil/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bkutil/subscriptions","organizations_url":"https://api.github.com/users/bkutil/orgs","repos_url":"https://api.github.com/users/bkutil/repos","events_url":"https://api.github.com/users/bkutil/events{/privacy}","received_events_url":"https://api.github.com/users/bkutil/received_events","type":"User","site_admin":false},"repo":{"id":14868638,"name":"barclamp-crowbar","full_name":"bkutil/barclamp-crowbar","owner":{"login":"bkutil","id":696026,"avatar_url":"https://avatars.githubusercontent.com/u/696026?v=3","gravatar_id":"","url":"https://api.github.com/users/bkutil","html_url":"https://github.com/bkutil","followers_url":"https://api.github.com/users/bkutil/followers","following_url":"https://api.github.com/users/bkutil/following{/other_user}","gists_url":"https://api.github.com/users/bkutil/gists{/gist_id}","starred_url":"https://api.github.com/users/bkutil/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bkutil/subscriptions","organizations_url":"https://api.github.com/users/bkutil/orgs","repos_url":"https://api.github.com/users/bkutil/repos","events_url":"https://api.github.com/users/bkutil/events{/privacy}","received_events_url":"https://api.github.com/users/bkutil/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/bkutil/barclamp-crowbar","description":"Crowbar Core Barclamp","fork":true,"url":"https://api.github.com/repos/bkutil/barclamp-crowbar","forks_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/forks","keys_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/keys{/key_id}","collaborators_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/teams","hooks_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/hooks","issue_events_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/issues/events{/number}","events_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/events","assignees_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/assignees{/user}","branches_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/branches{/branch}","tags_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/tags","blobs_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/git/refs{/sha}","trees_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/git/trees{/sha}","statuses_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/statuses/{sha}","languages_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/languages","stargazers_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/stargazers","contributors_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/contributors","subscribers_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/subscribers","subscription_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/subscription","commits_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/commits{/sha}","git_commits_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/git/commits{/sha}","comments_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/comments{/number}","issue_comment_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/issues/comments{/number}","contents_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/contents/{+path}","compare_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/compare/{base}...{head}","merges_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/merges","archive_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/downloads","issues_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/issues{/number}","pulls_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/pulls{/number}","milestones_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/milestones{/number}","notifications_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/labels{/name}","releases_url":"https://api.github.com/repos/bkutil/barclamp-crowbar/releases{/id}","created_at":"2013-12-02T16:54:48Z","updated_at":"2015-05-06T08:28:30Z","pushed_at":"2015-07-22T08:34:05Z","git_url":"git://github.com/bkutil/barclamp-crowbar.git","ssh_url":"git@github.com:bkutil/barclamp-crowbar.git","clone_url":"https://github.com/bkutil/barclamp-crowbar.git","svn_url":"https://github.com/bkutil/barclamp-crowbar","homepage":"https://github.com/crowbar/crowbar","size":41747,"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":"989e08de3ef0268a54040fbb3a175aac77ba1954","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":2351384,"name":"barclamp-crowbar","full_name":"crowbar/barclamp-crowbar","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/barclamp-crowbar","description":"Crowbar Core: Core Application","fork":false,"url":"https://api.github.com/repos/crowbar/barclamp-crowbar","forks_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/forks","keys_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/keys{/key_id}","collaborators_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/teams","hooks_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/hooks","issue_events_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/issues/events{/number}","events_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/events","assignees_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/assignees{/user}","branches_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/branches{/branch}","tags_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/tags","blobs_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/git/refs{/sha}","trees_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/git/trees{/sha}","statuses_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/statuses/{sha}","languages_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/languages","stargazers_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/stargazers","contributors_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/contributors","subscribers_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/subscribers","subscription_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/subscription","commits_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/commits{/sha}","git_commits_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/git/commits{/sha}","comments_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/comments{/number}","issue_comment_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/issues/comments{/number}","contents_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/contents/{+path}","compare_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/compare/{base}...{head}","merges_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/merges","archive_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/downloads","issues_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/issues{/number}","pulls_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/pulls{/number}","milestones_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/milestones{/number}","notifications_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/labels{/name}","releases_url":"https://api.github.com/repos/crowbar/barclamp-crowbar/releases{/id}","created_at":"2011-09-08T20:47:56Z","updated_at":"2015-06-22T23:56:07Z","pushed_at":"2015-07-22T08:34:05Z","git_url":"git://github.com/crowbar/barclamp-crowbar.git","ssh_url":"git@github.com:crowbar/barclamp-crowbar.git","clone_url":"https://github.com/crowbar/barclamp-crowbar.git","svn_url":"https://github.com/crowbar/barclamp-crowbar","homepage":"https://github.com/crowbar/crowbar","size":40896,"stargazers_count":74,"watchers_count":74,"language":"Ruby","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":191,"mirror_url":null,"open_issues_count":16,"forks":191,"open_issues":16,"watchers":74,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/crowbar/barclamp-crowbar/pulls/1314"},"html":{"href":"https://github.com/crowbar/barclamp-crowbar/pull/1314"},"issue":{"href":"https://api.github.com/repos/crowbar/barclamp-crowbar/issues/1314"},"comments":{"href":"https://api.github.com/repos/crowbar/barclamp-crowbar/issues/1314/comments"},"review_comments":{"href":"https://api.github.com/repos/crowbar/barclamp-crowbar/pulls/1314/comments"},"review_comment":{"href":"https://api.github.com/repos/crowbar/barclamp-crowbar/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/crowbar/barclamp-crowbar/pulls/1314/commits"},"statuses":{"href":"https://api.github.com/repos/crowbar/barclamp-crowbar/statuses/dbd880de00c2c3592a2e6f82fdb0b25f3f037a4d"}}}}