{"action":"created","comment":{"url":"https://api.github.com/repos/cloud-init/cloud-init/pulls/comments/23705038","id":23705038,"diff_hunk":"@@ -0,0 +1,580 @@\n+API design for the new distros namespace\n+========================================\n+\n+API convention\n+--------------\n+\n+Before dwelving into the details of the proposed API, some conventions\n+should be established, so that the API could be pythonic, easy to\n+comprehend and extend. We have the following examples of how an object\n+should look, depending on its state and behaviour:\n+\n+ - Use ``.attribute`` if it the attribute is not changeable\n+   throughout the life of the object.\n+   For instance, the name of a name of a device.\n+\n+ - Use ``.method()`` for obtaining a variant attribute, which can be\n+   different throughout the execution of the object and not modifiable\n+   through our object. This is the case for ``device.size()``, we can't\n+   set a new size and it can vary throughout the life of the device.\n+\n+ - For attributes which are modifiable by us and which aren't changing\n+   throughout the life of the object, we could use two approaches,\n+   a property-based approach or a method based approach.\n+\n+   The first one is more Pythonic, but can\n+   hide from the user of that object the fact that the property does\n+   more than it says, the second one is more in-your-face,\n+   but feels too much like Java's setters and getters and so on:\n+\n+       >>> device.mtu\n+       1500\n+       # actually changing the value, not the cached value.\n+       >>> device.mtu = 1400\n+       1400\n+\n+       vs\n+\n+       >>> device.mtu()\n+       1500\n+       >>> device.set_mtu(1400)\n+       >>>\n+\n+ - similar to the previous point, we could have ``is_something`` or\n+   ``is_something()``. We must choose one of this variants and use it\n+   consistently accross the project.\n+\n+\n+Proposed distro hierarchy\n+=========================\n+\n+Both frameworks have a concept of Distro, each different in its way:\n+\n+    - cloudinit has a ``distros`` location. There is a ``Distro`` base class,\n+      with abstract methods implemented by particular distros.\n+\n+        Problems:\n+\n+        * not DRY: many implementations have duplicate code with the base class\n+        * not properly encapsulated: distro-specific code executed outside the\n+          ``distros`` namespace.\n+        * lots of utilities, with low coherence between them.\n+\n+    - cloudbaseinit has a ``osutils`` location. There is a ``BaseOSUtils``\n+      base class, with a WindowsUtils implementation.\n+\n+       Problems:\n+\n+           * it's a pure utilities class, leading to low coherence\n+             between functions.\n+           * it is not a namespace of OS specific functionality.\n+             For this, there is also ``utils.windows``.\n+\n+As seen, both projects lack a namespaced location for all the OS related code.\n+\n+The following architecture proposal tries to solve this issue, by having one\n+namespace for both general utilies related to a distro, as well as distro\n+specific code, which doesn't have a counterpart on other distros.\n+\n+It can have the following advantages:\n+\n+    * one common place for all distro interaction, with standardized\n+      API for each subnamespace and increased coherence.\n+\n+    * avoids leaky abstractions. Distro specific code goes into ``distros``\n+      namespace.\n+\n+    * eases testability, it is easy to provide a mock with autospeccing\n+      for a namespace class, such as Route.\n+\n+    * Pythonic API, easy to understand and use.\n+\n+\n+The distros location is proposed, with the following structure and attributes:\n+\n+    - The base class for a distro is found in ``distros.base``.\n+\n+    - There are sub-namespaces for specific interaction with the OS,\n+      such as network, users.\n+\n+    - More namespaces can be added, if we identify a group of interactions that can\n+      be categorized in a namespace.\n+\n+    - There is a ``general`` namespace, which contains general utilities that can't be moved\n+      in another namespace.\n+\n+    - Each subnamespace has its own abstract base class, which must be implemented\n+      by different distros. Code reuse between distros is recommended.\n+\n+    - Each subnamespace exposes a way to obtain an instance of that namespace for\n+      the underlying distro. This can be exposed in namespace.factory.\n+      Thus, the following are equivalent:\n+\n+        >>> from cloudinit import distro\n+        >>> network = distro.get_distro().network\n+\n+        >>> from cloudinit.distro.network import factory\n+        >>> network = factory.get_network()\n+\n+    - Each subnamespace can expose additional behaviour that might not exist in\n+      the base class, if that behaviour does not make sense or if there is no\n+      equivalent on other platforms. But don't expose leaky abstraction, this\n+      specific code must be written in an abstract way, so that possible alternatives\n+      can be found for other distros in the mean time. This means that no ctypes\n+      interaction with the Windows should be exposed,\n+      but an object with a guaranteed interface.\n+\n+\n+      cloudinit/distros/__init__.py\n+                        base.py\n+                        factory.py\n+\n+                        network/\n+                          factory.py\n+                          base.py\n+                          windows.py\n+                          freebsd.py\n+                          debian.py\n+                          ....\n+                          __init__.py\n+\n+                        users\n+                          factory.py\n+                          base.py\n+                          freebsd.py\n+                          debian.py\n+                          ....\n+                          __init__.py\n+\n+                        filesystem\n+                          factory.py\n+                          base.py\n+                          freebsd.py\n+                          ...\n+                          __init__.py\n+\n+                        packaging\n+                          factory.py\n+                          base.py\n+                          freebsd.py\n+                          debian.py\n+                          ....\n+                          __init__.py\n+\n+                        general\n+                          base.py\n+                          windows.py\n+                          debian.py\n+                          ....\n+                          __init__.py\n+\n+\n+The base class for the Distro specific implementation must provide\n+an accessor member for each namespace, so that it will be sufficient\n+to obtain the distro in order to have each namespace.\n+\n+>>> from cloudinit.distros import factory\n+>>> distro = factory.get_distro()\n+>>> distro.network # the actual object, not the subpackage\n+<WindowsNetwork:/distro/network/windows>\n+>>> distro.users\n+<WindowsUsers:/distro/users/windows>\n+>>> distro.general\n+<WindowsGeneral:/distro/general/windows>\n+\n+\n+As an implementation detail, obtaining the distro object for the underlying\n+distro can use a combination of `platform.system`_ and `platform.linux_distribution`_.\n+\n+\n+In the following, I'll try to emphasize some possible APIs for each namespace.\n+\n+\n+Network subnamespace\n+----------------------\n+\n+    The abstract class can look like this:\n+\n+        class NetworkBase(ABCMeta):\n+\n+           def routes(self):\n+             \"\"\"Get the available routes, this can be the output of\n+             `netstat` on Posix and ``GetIpForwardTable`` on Windows.\n+             Each route should be an object encapsulating the inner workings\n+             of each variant.\n+\n+             So :meth:`routes` returns ``RouteContainer([Route(...), Route(...), Route(...))``\n+             See the description for :class:`RouteContainer` for more details,\n+             as well as the description of :class:`Route` for the API of the route object.\n+\n+             Using ``route in network.routes()`` and ``network.routes().add(route)\n+             removes the need for ``cloudbaseinit.osutils.check_static_route_exists``\n+             and ``cloudbaseinit.osutils.add_static_route``.\n+             \"\"\"\n+\n+          def default_gateway(self):\n+             \"\"\"Get the default gateway.\n+\n+             Can be implemented in the terms of :meth:`routes`.\n+             \"\"\"\n+\n+         def interfaces(self):\n+             \"\"\"Get the network interfaces\n+\n+             This can be implemented in the same vein as :meth:`routes`, e.g.\n+             ``InterfaceContainer([Interface(...), Interface(...), Interface(...)])``\n+             \"\"\"\n+\n+         def firewall_rules(self):\n+             \"\"\"Get a wrapper over the existing firewall rules.\n+\n+             Since this seems to be only used in Windows, it can be provided\n+             only in the Windows utils.\n+             The same behaviour as for :meth:`routes` can be used, that is:\n+\n+                 >>> rules = distro.network.firewall_rules()\n+                 >>> rule = distro.network.FirewallRule(name=..., port=..., protocol=...)\n+                 >>> rules.add(rule)\n+                 >>> rules.delete(rule)\n+                 >>> rule in rules\n+                 >>> for rule in rules: print(rules)\n+                 >>> del rules[i]\n+                 >>> rule = rules[0]\n+                 >>> rule.name, rule.port, rule.protocol, rule.allow\n+\n+             This gets rid of ``cloudbaseinit.osutils.firewall_add_rule`` and\n+             ``cloudbaseinit.osutils.firewall_remove_rule``.\n+             \"\"\"\n+\n+         def set_static_network_config(self, adapter_name, address, netmask,\n+                                       broadcast, gateway, dnsnameservers):\n+             \"\"\"Configure a new static network.\n+\n+             The :meth:``cloudinit.distros.Distro.apply_network`` should be\n+             removed in the favour of this method,\n+             which will be called by each network plugin.\n+             The method can be a template method, providing\n+             hooks for setting static DNS servers, setting static gateways or\n+             setting static IP addresses, which will be implemented by specific\n+             implementations of Distros.\n+             \"\"\"\n+\n+        def hosts(self):\n+             \"\"\"Get the content of /etc/hosts file in a more OO approach.\n+\n+\n+             >>> hosts = distro.network.hosts()\n+             >>> list(hosts) # support iteration and index access\n+             >>> hosts.add(ipaddress, hostname, alias)\n+             >>> hosts.delete(ipaddress, hostname, alias)\n+\n+             This gets rid of ``cloudinit.distros.Distro.update_etc_hosts``\n+             and can provide support for adding a new hostname for Windows, as well.\n+             \"\"\"\n+\n+        class Route(object):\n+             \"\"\"\n+             Encapsulate behaviour and state of a route.\n+             Something similar to Posix can be adopted, with the following API:\n+\n+                  route.destination\n+                  route.gateway\n+                  route.flags\n+                  route.refs\n+                  route.use\n+                  route.netif -> instance of :class:`Interface` object\n+                  route.expire\n+                  route.static -> 'S' in self.flags\n+                  route.usable -> 'U' in self.flag\n+             \"\"\"\n+\n+          @classmethod\n+          def from_route_item(self, item):\n+              \"\"\"\n+              Build a Route from a routing entry, either from\n+              the output of `netstat` or what will be used on Posix or\n+              from `GetIpForwardTable`.\n+              \"\"\"\n+\n+        class RouteContainer(object):","path":"specs/distros.rst","position":299,"original_position":299,"commit_id":"027478d8b6bb83c7bd369bf7d0055288ef839a5c","original_commit_id":"027478d8b6bb83c7bd369bf7d0055288ef839a5c","user":{"login":"harlowja","id":970458,"avatar_url":"https://avatars.githubusercontent.com/u/970458?v=3","gravatar_id":"","url":"https://api.github.com/users/harlowja","html_url":"https://github.com/harlowja","followers_url":"https://api.github.com/users/harlowja/followers","following_url":"https://api.github.com/users/harlowja/following{/other_user}","gists_url":"https://api.github.com/users/harlowja/gists{/gist_id}","starred_url":"https://api.github.com/users/harlowja/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/harlowja/subscriptions","organizations_url":"https://api.github.com/users/harlowja/orgs","repos_url":"https://api.github.com/users/harlowja/repos","events_url":"https://api.github.com/users/harlowja/events{/privacy}","received_events_url":"https://api.github.com/users/harlowja/received_events","type":"User","site_admin":false},"body":"Whats this offer over a list?","created_at":"2015-01-28T17:33:47Z","updated_at":"2015-01-28T17:33:47Z","html_url":"https://github.com/cloud-init/cloud-init/pull/1#discussion_r23705038","pull_request_url":"https://api.github.com/repos/cloud-init/cloud-init/pulls/1","_links":{"self":{"href":"https://api.github.com/repos/cloud-init/cloud-init/pulls/comments/23705038"},"html":{"href":"https://github.com/cloud-init/cloud-init/pull/1#discussion_r23705038"},"pull_request":{"href":"https://api.github.com/repos/cloud-init/cloud-init/pulls/1"}}},"pull_request":{"url":"https://api.github.com/repos/cloud-init/cloud-init/pulls/1","id":28188131,"html_url":"https://github.com/cloud-init/cloud-init/pull/1","diff_url":"https://github.com/cloud-init/cloud-init/pull/1.diff","patch_url":"https://github.com/cloud-init/cloud-init/pull/1.patch","issue_url":"https://api.github.com/repos/cloud-init/cloud-init/issues/1","number":1,"state":"open","locked":false,"title":"Add a spec draft for the new distro namespace.","user":{"login":"PCManticore","id":448166,"avatar_url":"https://avatars.githubusercontent.com/u/448166?v=3","gravatar_id":"","url":"https://api.github.com/users/PCManticore","html_url":"https://github.com/PCManticore","followers_url":"https://api.github.com/users/PCManticore/followers","following_url":"https://api.github.com/users/PCManticore/following{/other_user}","gists_url":"https://api.github.com/users/PCManticore/gists{/gist_id}","starred_url":"https://api.github.com/users/PCManticore/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/PCManticore/subscriptions","organizations_url":"https://api.github.com/users/PCManticore/orgs","repos_url":"https://api.github.com/users/PCManticore/repos","events_url":"https://api.github.com/users/PCManticore/events{/privacy}","received_events_url":"https://api.github.com/users/PCManticore/received_events","type":"User","site_admin":false},"body":"This initial draft targets a proposed API for the new distro submodule,\r\nwhich in turn is composed by other cohesive subnamespaces, that targets\r\none specialized are. A basic API is laid out, which describes how namespaces\r\ninteracts with objects from other namespaces and so on.\r\n\r\nI propose to have a spec folder in the repo, where we could track our design decissions and all that, a la Python's PEPs. A location such as this helps when we are in doubt why something was added as is.\r\nAnyway, this draft mostly targets a shallow description of the API. A more thorough document will follow it, if the proposed changes are accepted. I'll also have a pull request with the base classes and all the goodies, so that we could start putting actual code in the distros in the meanwhile.","created_at":"2015-01-28T12:01:38Z","updated_at":"2015-01-28T17:33:47Z","closed_at":null,"merged_at":null,"merge_commit_sha":"17645d395b6f1d4a546aebb65ada158d1dc2c75e","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/cloud-init/cloud-init/pulls/1/commits","review_comments_url":"https://api.github.com/repos/cloud-init/cloud-init/pulls/1/comments","review_comment_url":"https://api.github.com/repos/cloud-init/cloud-init/pulls/comments/{number}","comments_url":"https://api.github.com/repos/cloud-init/cloud-init/issues/1/comments","statuses_url":"https://api.github.com/repos/cloud-init/cloud-init/statuses/027478d8b6bb83c7bd369bf7d0055288ef839a5c","head":{"label":"PCManticore:specs","ref":"specs","sha":"027478d8b6bb83c7bd369bf7d0055288ef839a5c","user":{"login":"PCManticore","id":448166,"avatar_url":"https://avatars.githubusercontent.com/u/448166?v=3","gravatar_id":"","url":"https://api.github.com/users/PCManticore","html_url":"https://github.com/PCManticore","followers_url":"https://api.github.com/users/PCManticore/followers","following_url":"https://api.github.com/users/PCManticore/following{/other_user}","gists_url":"https://api.github.com/users/PCManticore/gists{/gist_id}","starred_url":"https://api.github.com/users/PCManticore/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/PCManticore/subscriptions","organizations_url":"https://api.github.com/users/PCManticore/orgs","repos_url":"https://api.github.com/users/PCManticore/repos","events_url":"https://api.github.com/users/PCManticore/events{/privacy}","received_events_url":"https://api.github.com/users/PCManticore/received_events","type":"User","site_admin":false},"repo":{"id":29961873,"name":"cloud-init-1","full_name":"PCManticore/cloud-init-1","owner":{"login":"PCManticore","id":448166,"avatar_url":"https://avatars.githubusercontent.com/u/448166?v=3","gravatar_id":"","url":"https://api.github.com/users/PCManticore","html_url":"https://github.com/PCManticore","followers_url":"https://api.github.com/users/PCManticore/followers","following_url":"https://api.github.com/users/PCManticore/following{/other_user}","gists_url":"https://api.github.com/users/PCManticore/gists{/gist_id}","starred_url":"https://api.github.com/users/PCManticore/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/PCManticore/subscriptions","organizations_url":"https://api.github.com/users/PCManticore/orgs","repos_url":"https://api.github.com/users/PCManticore/repos","events_url":"https://api.github.com/users/PCManticore/events{/privacy}","received_events_url":"https://api.github.com/users/PCManticore/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/PCManticore/cloud-init-1","description":"Cloud-init cross platform cross cloud customization of cloud instances","fork":true,"url":"https://api.github.com/repos/PCManticore/cloud-init-1","forks_url":"https://api.github.com/repos/PCManticore/cloud-init-1/forks","keys_url":"https://api.github.com/repos/PCManticore/cloud-init-1/keys{/key_id}","collaborators_url":"https://api.github.com/repos/PCManticore/cloud-init-1/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/PCManticore/cloud-init-1/teams","hooks_url":"https://api.github.com/repos/PCManticore/cloud-init-1/hooks","issue_events_url":"https://api.github.com/repos/PCManticore/cloud-init-1/issues/events{/number}","events_url":"https://api.github.com/repos/PCManticore/cloud-init-1/events","assignees_url":"https://api.github.com/repos/PCManticore/cloud-init-1/assignees{/user}","branches_url":"https://api.github.com/repos/PCManticore/cloud-init-1/branches{/branch}","tags_url":"https://api.github.com/repos/PCManticore/cloud-init-1/tags","blobs_url":"https://api.github.com/repos/PCManticore/cloud-init-1/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/PCManticore/cloud-init-1/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/PCManticore/cloud-init-1/git/refs{/sha}","trees_url":"https://api.github.com/repos/PCManticore/cloud-init-1/git/trees{/sha}","statuses_url":"https://api.github.com/repos/PCManticore/cloud-init-1/statuses/{sha}","languages_url":"https://api.github.com/repos/PCManticore/cloud-init-1/languages","stargazers_url":"https://api.github.com/repos/PCManticore/cloud-init-1/stargazers","contributors_url":"https://api.github.com/repos/PCManticore/cloud-init-1/contributors","subscribers_url":"https://api.github.com/repos/PCManticore/cloud-init-1/subscribers","subscription_url":"https://api.github.com/repos/PCManticore/cloud-init-1/subscription","commits_url":"https://api.github.com/repos/PCManticore/cloud-init-1/commits{/sha}","git_commits_url":"https://api.github.com/repos/PCManticore/cloud-init-1/git/commits{/sha}","comments_url":"https://api.github.com/repos/PCManticore/cloud-init-1/comments{/number}","issue_comment_url":"https://api.github.com/repos/PCManticore/cloud-init-1/issues/comments/{number}","contents_url":"https://api.github.com/repos/PCManticore/cloud-init-1/contents/{+path}","compare_url":"https://api.github.com/repos/PCManticore/cloud-init-1/compare/{base}...{head}","merges_url":"https://api.github.com/repos/PCManticore/cloud-init-1/merges","archive_url":"https://api.github.com/repos/PCManticore/cloud-init-1/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/PCManticore/cloud-init-1/downloads","issues_url":"https://api.github.com/repos/PCManticore/cloud-init-1/issues{/number}","pulls_url":"https://api.github.com/repos/PCManticore/cloud-init-1/pulls{/number}","milestones_url":"https://api.github.com/repos/PCManticore/cloud-init-1/milestones{/number}","notifications_url":"https://api.github.com/repos/PCManticore/cloud-init-1/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/PCManticore/cloud-init-1/labels{/name}","releases_url":"https://api.github.com/repos/PCManticore/cloud-init-1/releases{/id}","created_at":"2015-01-28T10:16:46Z","updated_at":"2015-01-28T10:16:47Z","pushed_at":"2015-01-28T11:57:29Z","git_url":"git://github.com/PCManticore/cloud-init-1.git","ssh_url":"git@github.com:PCManticore/cloud-init-1.git","clone_url":"https://github.com/PCManticore/cloud-init-1.git","svn_url":"https://github.com/PCManticore/cloud-init-1","homepage":null,"size":14118,"stargazers_count":0,"watchers_count":0,"language":"Shell","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":"cloud-init:master","ref":"master","sha":"404e1a81a998cc24dc15b317a031f2d35ce12e38","user":{"login":"cloud-init","id":9086115,"avatar_url":"https://avatars.githubusercontent.com/u/9086115?v=3","gravatar_id":"","url":"https://api.github.com/users/cloud-init","html_url":"https://github.com/cloud-init","followers_url":"https://api.github.com/users/cloud-init/followers","following_url":"https://api.github.com/users/cloud-init/following{/other_user}","gists_url":"https://api.github.com/users/cloud-init/gists{/gist_id}","starred_url":"https://api.github.com/users/cloud-init/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cloud-init/subscriptions","organizations_url":"https://api.github.com/users/cloud-init/orgs","repos_url":"https://api.github.com/users/cloud-init/repos","events_url":"https://api.github.com/users/cloud-init/events{/privacy}","received_events_url":"https://api.github.com/users/cloud-init/received_events","type":"Organization","site_admin":false},"repo":{"id":24948315,"name":"cloud-init","full_name":"cloud-init/cloud-init","owner":{"login":"cloud-init","id":9086115,"avatar_url":"https://avatars.githubusercontent.com/u/9086115?v=3","gravatar_id":"","url":"https://api.github.com/users/cloud-init","html_url":"https://github.com/cloud-init","followers_url":"https://api.github.com/users/cloud-init/followers","following_url":"https://api.github.com/users/cloud-init/following{/other_user}","gists_url":"https://api.github.com/users/cloud-init/gists{/gist_id}","starred_url":"https://api.github.com/users/cloud-init/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cloud-init/subscriptions","organizations_url":"https://api.github.com/users/cloud-init/orgs","repos_url":"https://api.github.com/users/cloud-init/repos","events_url":"https://api.github.com/users/cloud-init/events{/privacy}","received_events_url":"https://api.github.com/users/cloud-init/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/cloud-init/cloud-init","description":"Cloud-init cross platform cross cloud customization of cloud instances","fork":false,"url":"https://api.github.com/repos/cloud-init/cloud-init","forks_url":"https://api.github.com/repos/cloud-init/cloud-init/forks","keys_url":"https://api.github.com/repos/cloud-init/cloud-init/keys{/key_id}","collaborators_url":"https://api.github.com/repos/cloud-init/cloud-init/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/cloud-init/cloud-init/teams","hooks_url":"https://api.github.com/repos/cloud-init/cloud-init/hooks","issue_events_url":"https://api.github.com/repos/cloud-init/cloud-init/issues/events{/number}","events_url":"https://api.github.com/repos/cloud-init/cloud-init/events","assignees_url":"https://api.github.com/repos/cloud-init/cloud-init/assignees{/user}","branches_url":"https://api.github.com/repos/cloud-init/cloud-init/branches{/branch}","tags_url":"https://api.github.com/repos/cloud-init/cloud-init/tags","blobs_url":"https://api.github.com/repos/cloud-init/cloud-init/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/cloud-init/cloud-init/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/cloud-init/cloud-init/git/refs{/sha}","trees_url":"https://api.github.com/repos/cloud-init/cloud-init/git/trees{/sha}","statuses_url":"https://api.github.com/repos/cloud-init/cloud-init/statuses/{sha}","languages_url":"https://api.github.com/repos/cloud-init/cloud-init/languages","stargazers_url":"https://api.github.com/repos/cloud-init/cloud-init/stargazers","contributors_url":"https://api.github.com/repos/cloud-init/cloud-init/contributors","subscribers_url":"https://api.github.com/repos/cloud-init/cloud-init/subscribers","subscription_url":"https://api.github.com/repos/cloud-init/cloud-init/subscription","commits_url":"https://api.github.com/repos/cloud-init/cloud-init/commits{/sha}","git_commits_url":"https://api.github.com/repos/cloud-init/cloud-init/git/commits{/sha}","comments_url":"https://api.github.com/repos/cloud-init/cloud-init/comments{/number}","issue_comment_url":"https://api.github.com/repos/cloud-init/cloud-init/issues/comments/{number}","contents_url":"https://api.github.com/repos/cloud-init/cloud-init/contents/{+path}","compare_url":"https://api.github.com/repos/cloud-init/cloud-init/compare/{base}...{head}","merges_url":"https://api.github.com/repos/cloud-init/cloud-init/merges","archive_url":"https://api.github.com/repos/cloud-init/cloud-init/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/cloud-init/cloud-init/downloads","issues_url":"https://api.github.com/repos/cloud-init/cloud-init/issues{/number}","pulls_url":"https://api.github.com/repos/cloud-init/cloud-init/pulls{/number}","milestones_url":"https://api.github.com/repos/cloud-init/cloud-init/milestones{/number}","notifications_url":"https://api.github.com/repos/cloud-init/cloud-init/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/cloud-init/cloud-init/labels{/name}","releases_url":"https://api.github.com/repos/cloud-init/cloud-init/releases{/id}","created_at":"2014-10-08T15:54:41Z","updated_at":"2015-01-26T05:38:51Z","pushed_at":"2015-01-21T21:48:34Z","git_url":"git://github.com/cloud-init/cloud-init.git","ssh_url":"git@github.com:cloud-init/cloud-init.git","clone_url":"https://github.com/cloud-init/cloud-init.git","svn_url":"https://github.com/cloud-init/cloud-init","homepage":null,"size":14186,"stargazers_count":3,"watchers_count":3,"language":"Shell","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":1,"forks":1,"open_issues":1,"watchers":3,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/cloud-init/cloud-init/pulls/1"},"html":{"href":"https://github.com/cloud-init/cloud-init/pull/1"},"issue":{"href":"https://api.github.com/repos/cloud-init/cloud-init/issues/1"},"comments":{"href":"https://api.github.com/repos/cloud-init/cloud-init/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/cloud-init/cloud-init/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/cloud-init/cloud-init/pulls/comments/{number}"},"commits":{"href":"https://api.github.com/repos/cloud-init/cloud-init/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/cloud-init/cloud-init/statuses/027478d8b6bb83c7bd369bf7d0055288ef839a5c"}}}}