{"action":"created","comment":{"url":"https://api.github.com/repos/skydive-project/skydive/pulls/comments/231942920","pull_request_review_id":173026879,"id":231942920,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIzMTk0MjkyMA==","diff_hunk":"@@ -0,0 +1,309 @@\n+// +build libvirt,linux\n+\n+/*\n+ * Copyright (C) 2018 Orange.\n+ *\n+ * Licensed to the Apache Software Foundation (ASF) under one\n+ * or more contributor license agreements.  See the NOTICE file\n+ * distributed with this work for additional information\n+ * regarding copyright ownership.  The ASF licenses this file\n+ * to you under the Apache License, Version 2.0 (the\n+ * \"License\"); you may not use this file except in compliance\n+ * with the License.  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,\n+ * software distributed under the License is distributed on an\n+ * \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n+ * KIND, either express or implied.  See the License for the\n+ * specific language governing permissions and limitations\n+ * under the License.\n+ *\n+ */\n+\n+package libvirt\n+\n+import (\n+\t\"context\"\n+\t\"encoding/xml\"\n+\t\"fmt\"\n+\t\"sync\"\n+\n+\t\"github.com/skydive-project/skydive/config\"\n+\n+\tlibvirtgo \"github.com/libvirt/libvirt-go\"\n+\t\"github.com/skydive-project/skydive/logging\"\n+\t\"github.com/skydive-project/skydive/topology/graph\"\n+)\n+\n+// Probe is the libvirt probe\n+type Probe struct {\n+\tsync.Mutex\n+\tgraph.DefaultGraphListener                       // Probe is a listner\n+\tgraph                      *graph.Graph          // the graph\n+\tconn                       *libvirtgo.Connect    // libvirt conection\n+\tinterfaceMap               map[string]*Interface // Found interfaces not yet connected.\n+\tcidLifecycle               int                   // libvirt callback id of monitor to unregister\n+\tcidDevAdded                int                   // second monitor on devices added to domains\n+\turi                        string                // uri of the libvirt connection\n+\tcancel                     context.CancelFunc    // cancel function\n+\troot                       *graph.Node           // root node for ownership\n+\ttunProcessor               *graph.Processor      // metadata indexer\n+}\n+\n+// Address describes the XML coding of the pci addres of an interface in libvirt\n+type Address struct {\n+\tType     string `xml:\"type,attr\"`\n+\tDomain   string `xml:\"domain,attr\"`\n+\tBus      string `xml:\"bus,attr\"`\n+\tSlot     string `xml:\"slot,attr\"`\n+\tFunction string `xml:\"function,attr\"`\n+}\n+\n+// DomainStateMap stringifies the state of a domain\n+var DomainStateMap map[libvirtgo.DomainState]string = map[libvirtgo.DomainState]string{\n+\tlibvirtgo.DOMAIN_NOSTATE:     \"Undefined\",\n+\tlibvirtgo.DOMAIN_RUNNING:     \"UP\",\n+\tlibvirtgo.DOMAIN_BLOCKED:     \"BLOCKED\",\n+\tlibvirtgo.DOMAIN_PAUSED:      \"PAUSED\",\n+\tlibvirtgo.DOMAIN_SHUTDOWN:    \"DOWN\",\n+\tlibvirtgo.DOMAIN_CRASHED:     \"CRASHED\",\n+\tlibvirtgo.DOMAIN_PMSUSPENDED: \"PMSUSPENDED\",\n+\tlibvirtgo.DOMAIN_SHUTOFF:     \"DOWN\",\n+}\n+\n+// Interface is XML coding of an interface in libvirt\n+type Interface struct {\n+\tMac struct {\n+\t\tAddress string `xml:\"address,attr\"`\n+\t} `xml:\"mac\"`\n+\tTarget struct {\n+\t\tDevice string `xml:\"dev,attr\"`\n+\t} `xml:\"target\"`\n+\tAddress Address `xml:\"address\"`\n+\tAlias   struct {\n+\t\tName string `xml:\"name,attr\"`\n+\t} `xml:\"alias\"`\n+\tHost *graph.Node `xml:\"-\"`\n+}\n+\n+// Domain is the subset of XML coding of a domain in libvirt\n+type Domain struct {\n+\tInterfaces []Interface `xml:\"devices>interface\"`\n+}\n+\n+// queryDomain uses libvirt to get information on the interfaces of a domain.\n+// domain is the libvirt domain queried and bag is an accumulator for results\n+func (probe *Probe) queryDomain(\n+\tdomain *libvirtgo.Domain, // domain to query\n+\tdomainNode *graph.Node, // Node representing the domain\n+\tbag *[]*Interface, // to accumulate results\n+\tconstraint string, // to restrict the search to a single interface (by alias)\n+) {\n+\trawXML, err := domain.GetXMLDesc(0)\n+\tif err != nil {\n+\t\tlogging.GetLogger().Errorf(\"Cannot get XMLDesc: %s\", err)\n+\t\treturn\n+\t}\n+\td := Domain{}\n+\tif err = xml.Unmarshal([]byte(rawXML), &d); err != nil {\n+\t\tlogging.GetLogger().Errorf(\"XML parsing error: %s\", err)\n+\t\treturn\n+\t}\n+\tfor i := range d.Interfaces {\n+\t\titf := &d.Interfaces[i]\n+\t\tif constraint == \"\" || constraint == itf.Alias.Name {\n+\t\t\titf.Host = domainNode\n+\t\t\t*bag = append(*bag, itf)\n+\t\t}\n+\t}\n+}\n+\n+// registerInterfaces puts the information collected in the graph\n+// interfaces is an array of collected information.\n+func (probe *Probe) registerInterfaces(interfaces []*Interface) {\n+\tfor _, itf := range interfaces {\n+\t\tname := itf.Target.Device\n+\t\tif name == \"\" {\n+\t\t\tcontinue\n+\t\t}\n+\t\tlogging.GetLogger().Debugf(\n+\t\t\t\"Libvirt interface %s on %s\", name, itf.Host)\n+\t\tprobe.tunProcessor.DoAction(itf, name)\n+\t}\n+}\n+\n+// ProcessNode adds the libvirt interface information to a node of the graph\n+func (itf *Interface) ProcessNode(g *graph.Graph, node *graph.Node) bool {\n+\tlogging.GetLogger().Debugf(\"enrich %s\", itf.Alias.Name)\n+\ttr := g.StartMetadataTransaction(node)\n+\ttr.AddMetadata(\"Libvirt.MAC\", itf.Mac.Address)\n+\ttr.AddMetadata(\"Libvirt.Domain\", itf.Host)\n+\taddress := itf.Address\n+\tformatted := fmt.Sprintf(\n+\t\t\"%s:%s.%s.%s.%s\", address.Type, address.Domain, address.Bus,\n+\t\taddress.Slot, address.Function)\n+\ttr.AddMetadata(\"Libvirt.Address\", formatted)\n+\ttr.AddMetadata(\"Libvirt.Alias\", itf.Alias.Name)\n+\ttr.AddMetadata(\"PeerIntfMAC\", itf.Mac.Address)\n+\ttr.Commit()\n+\tgo func() {\n+\t\tg.Lock()\n+\t\tdefer g.Unlock()\n+\t\tg.Link(node, itf.Host, graph.Metadata{\"RelationType\": \"vlayer2\"})\n+\t}()\n+\treturn false\n+}\n+\n+// getDomain access the graph node representing a libvirt domain\n+func (probe *Probe) getDomain(d *libvirtgo.Domain) *graph.Node {\n+\tdomainName, _ := d.GetName()\n+\tprobe.graph.RLock()\n+\tdefer probe.graph.RUnlock()\n+\treturn probe.graph.LookupFirstNode(graph.Metadata{\"Name\": domainName, \"Type\": \"libvirt\"})\n+}\n+\n+// createOrUpdateDomain creates a new graph node representing a libvirt domain\n+// if necessary and updates its state.\n+func (probe *Probe) createOrUpdateDomain(d *libvirtgo.Domain) *graph.Node {\n+\tg := probe.graph\n+\tg.Lock()\n+\tdefer g.Unlock()\n+\tdomainName, _ := d.GetName()\n+\tmetadata := graph.Metadata{\n+\t\t\"Name\": domainName,\n+\t\t\"Type\": \"libvirt\",\n+\t}\n+\tdomainNode := g.LookupFirstNode(metadata)\n+\tif domainNode == nil {\n+\t\tdomainNode = g.NewNode(graph.GenID(), metadata)\n+\t\tg.Link(probe.root, domainNode, graph.Metadata{\"RelationType\": \"ownership\"})\n+\t}\n+\tstate, _, err := d.GetState()\n+\tif err != nil {\n+\t\tlogging.GetLogger().Errorf(\"Cannot update domain state for %s\", domainName)\n+\t} else {\n+\t\ttr := g.StartMetadataTransaction(domainNode)\n+\t\tdefer tr.Commit()\n+\t\ttr.AddMetadata(\"State\", DomainStateMap[state])\n+\t}\n+\treturn domainNode\n+}\n+\n+// deleteDomain deletes the graph node representing a libvirt domain\n+func (probe *Probe) deleteDomain(d *libvirtgo.Domain) {\n+\tdomainNode := probe.getDomain(d)\n+\tif domainNode != nil {\n+\t\tprobe.graph.Lock()\n+\t\tdefer probe.graph.Unlock()\n+\t\tprobe.graph.DelNode(domainNode)\n+\t}\n+}\n+\n+// Start get all domains attached to a libvirt connection\n+func (probe *Probe) Start() {\n+\t// The event loop must be registered WITH its poll loop active BEFORE the\n+\t// connection is opened. Otherwise it just does not work.\n+\tif err := libvirtgo.EventRegisterDefaultImpl(); err != nil {\n+\t\tlogging.GetLogger().Errorf(\"libvirt event handler:  %s\", err)\n+\t}\n+\tctx, cancel := context.WithCancel(context.Background())\n+\tprobe.cancel = cancel\n+\tgo func() {\n+\t\tfor ctx.Err() == nil {\n+\t\t\tif err := libvirtgo.EventRunDefaultImpl(); err != nil {\n+\t\t\t\tlogging.GetLogger().Errorf(\"libvirt poll loop problem: %s\", err)\n+\t\t\t}\n+\t\t}\n+\t}()\n+\n+\tconn, err := libvirtgo.NewConnectReadOnly(probe.uri)\n+\tif err != nil {\n+\t\tlogging.GetLogger().Errorf(\"Failed to create libvirt connect\")\n+\t\treturn\n+\t}\n+\tprobe.conn = conn\n+\tcallback := func(\n+\t\tc *libvirtgo.Connect, d *libvirtgo.Domain,\n+\t\tevent *libvirtgo.DomainEventLifecycle,\n+\t) {\n+\t\tswitch event.Event {\n+\t\tcase libvirtgo.DOMAIN_EVENT_UNDEFINED:\n+\t\t\tprobe.deleteDomain(d)\n+\t\tcase libvirtgo.DOMAIN_EVENT_STARTED:\n+\t\t\tdomainNode := probe.createOrUpdateDomain(d)\n+\t\t\tvar interfaces []*Interface\n+\t\t\tprobe.queryDomain(d, domainNode, &interfaces, \"\")\n+\t\t\tprobe.registerInterfaces(interfaces)\n+\t\tcase libvirtgo.DOMAIN_EVENT_DEFINED, libvirtgo.DOMAIN_EVENT_SUSPENDED,\n+\t\t\tlibvirtgo.DOMAIN_EVENT_RESUMED, libvirtgo.DOMAIN_EVENT_STOPPED,\n+\t\t\tlibvirtgo.DOMAIN_EVENT_SHUTDOWN, libvirtgo.DOMAIN_EVENT_PMSUSPENDED,\n+\t\t\tlibvirtgo.DOMAIN_EVENT_CRASHED:\n+\t\t\tprobe.createOrUpdateDomain(d)\n+\t\t}\n+\t}\n+\tprobe.cidLifecycle, err = conn.DomainEventLifecycleRegister(nil, callback)\n+\tif err != nil {\n+\t\tlogging.GetLogger().Errorf(\n+\t\t\t\"Could not register the lifecycle event handler %s\", err)\n+\t}\n+\tcallbackDeviceAdded := func(\n+\t\tc *libvirtgo.Connect, d *libvirtgo.Domain,\n+\t\tevent *libvirtgo.DomainEventDeviceAdded,\n+\t) {\n+\t\tvar interfaces []*Interface\n+\t\tdomainNode := probe.getDomain(d)\n+\t\tprobe.queryDomain(d, domainNode, &interfaces, event.DevAlias)\n+\t\tprobe.registerInterfaces(interfaces) // 0 or 1 device changed.\n+\t}\n+\tprobe.cidDevAdded, err = conn.DomainEventDeviceAddedRegister(nil, callbackDeviceAdded)\n+\tif err != nil {\n+\t\tlogging.GetLogger().Errorf(\n+\t\t\t\"Could not register the device added event handler %s\", err)\n+\t}\n+\tdomains, err := probe.conn.ListAllDomains(0)\n+\tif err != nil {\n+\t\tlogging.GetLogger().Error(err)\n+\t\treturn\n+\t}\n+\tvar interfaces []*Interface\n+\tfor _, domain := range domains {\n+\t\tdomainNode := probe.createOrUpdateDomain(&domain)\n+\t\tprobe.queryDomain(&domain, domainNode, &interfaces, \"\")\n+\t}\n+\tprobe.registerInterfaces(interfaces)\n+}\n+\n+// Stop stops the probe\n+func (probe *Probe) Stop() {\n+\tprobe.cancel()\n+\tprobe.tunProcessor.Stop()\n+\tif probe.cidLifecycle != -1 {\n+\t\tprobe.conn.DomainEventDeregister(probe.cidLifecycle)\n+\t\tprobe.conn.DomainEventDeregister(probe.cidDevAdded)\n+\t}\n+\tprobe.conn.Close()\n+}\n+\n+// NewProbe creates a libvirt topology probe\n+func NewProbe(g *graph.Graph, uri string, root *graph.Node) *Probe {\n+\ttunProcessor := graph.NewProcessor(g, g, graph.Metadata{\"Type\": \"tun\"}, \"Name\")\n+\tprobe := &Probe{\n+\t\tgraph:        g,\n+\t\tinterfaceMap: make(map[string]*Interface),\n+\t\tcidLifecycle: -1,\n+\t\turi:          uri,\n+\t\troot:         root,\n+\t\ttunProcessor: tunProcessor,\n+\t}\n+\ttunProcessor.Start()\n+\treturn probe\n+}\n+\n+// NewProbeFromConfig initializes the probe\n+func NewProbeFromConfig(g *graph.Graph, root *graph.Node) (*Probe, error) {\n+\turi := config.GetString(\"agent.topology.libvirt.url\")\n+\tprobe := NewProbe(g, uri, root)","path":"topology/probes/libvirt/libvirt.go","position":null,"original_position":307,"commit_id":"d0d38253fea1e65eea7d75b4b2e0a4830d17444c","original_commit_id":"c44233292d419411e2a2d4d8a74f6aac8b68da9b","user":{"login":"pierrecregut","id":7011215,"node_id":"MDQ6VXNlcjcwMTEyMTU=","avatar_url":"https://avatars1.githubusercontent.com/u/7011215?v=4","gravatar_id":"","url":"https://api.github.com/users/pierrecregut","html_url":"https://github.com/pierrecregut","followers_url":"https://api.github.com/users/pierrecregut/followers","following_url":"https://api.github.com/users/pierrecregut/following{/other_user}","gists_url":"https://api.github.com/users/pierrecregut/gists{/gist_id}","starred_url":"https://api.github.com/users/pierrecregut/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pierrecregut/subscriptions","organizations_url":"https://api.github.com/users/pierrecregut/orgs","repos_url":"https://api.github.com/users/pierrecregut/repos","events_url":"https://api.github.com/users/pierrecregut/events{/privacy}","received_events_url":"https://api.github.com/users/pierrecregut/received_events","type":"User","site_admin":false},"body":"ok...","created_at":"2018-11-08T15:49:26Z","updated_at":"2018-11-08T15:49:26Z","html_url":"https://github.com/skydive-project/skydive/pull/1423#discussion_r231942920","pull_request_url":"https://api.github.com/repos/skydive-project/skydive/pulls/1423","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/skydive-project/skydive/pulls/comments/231942920"},"html":{"href":"https://github.com/skydive-project/skydive/pull/1423#discussion_r231942920"},"pull_request":{"href":"https://api.github.com/repos/skydive-project/skydive/pulls/1423"}},"in_reply_to_id":231847586},"pull_request":{"url":"https://api.github.com/repos/skydive-project/skydive/pulls/1423","id":225826642,"node_id":"MDExOlB1bGxSZXF1ZXN0MjI1ODI2NjQy","html_url":"https://github.com/skydive-project/skydive/pull/1423","diff_url":"https://github.com/skydive-project/skydive/pull/1423.diff","patch_url":"https://github.com/skydive-project/skydive/pull/1423.patch","issue_url":"https://api.github.com/repos/skydive-project/skydive/issues/1423","number":1423,"state":"open","locked":false,"title":"libvirt: initial version of libvirt probe","user":{"login":"pierrecregut","id":7011215,"node_id":"MDQ6VXNlcjcwMTEyMTU=","avatar_url":"https://avatars1.githubusercontent.com/u/7011215?v=4","gravatar_id":"","url":"https://api.github.com/users/pierrecregut","html_url":"https://github.com/pierrecregut","followers_url":"https://api.github.com/users/pierrecregut/followers","following_url":"https://api.github.com/users/pierrecregut/following{/other_user}","gists_url":"https://api.github.com/users/pierrecregut/gists{/gist_id}","starred_url":"https://api.github.com/users/pierrecregut/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pierrecregut/subscriptions","organizations_url":"https://api.github.com/users/pierrecregut/orgs","repos_url":"https://api.github.com/users/pierrecregut/repos","events_url":"https://api.github.com/users/pierrecregut/events{/privacy}","received_events_url":"https://api.github.com/users/pierrecregut/received_events","type":"User","site_admin":false},"body":"Libvirt probe dynamically polls the status of libvirt domains and their network\r\ninterfaces. For each new interface, it tries to enrich the coresponding tun\r\ndevice with the name of the domain, the guest mac and the complete address of the\r\ndevice on the guest pci bus.\r\n\r\nThe probe relies on libvirt-go the official go binding for libvirt that requires\r\nthe c libvirt library.\r\n\r\ncloses #1410","created_at":"2018-10-25T16:32:33Z","updated_at":"2018-11-08T15:49:26Z","closed_at":null,"merged_at":null,"merge_commit_sha":"3d46fe2250daa72096c57861d9b32ea50014c50d","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/skydive-project/skydive/pulls/1423/commits","review_comments_url":"https://api.github.com/repos/skydive-project/skydive/pulls/1423/comments","review_comment_url":"https://api.github.com/repos/skydive-project/skydive/pulls/comments{/number}","comments_url":"https://api.github.com/repos/skydive-project/skydive/issues/1423/comments","statuses_url":"https://api.github.com/repos/skydive-project/skydive/statuses/d0d38253fea1e65eea7d75b4b2e0a4830d17444c","head":{"label":"Orange-OpenSource:libvirt_probe","ref":"libvirt_probe","sha":"d0d38253fea1e65eea7d75b4b2e0a4830d17444c","user":{"login":"Orange-OpenSource","id":1506386,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE1MDYzODY=","avatar_url":"https://avatars0.githubusercontent.com/u/1506386?v=4","gravatar_id":"","url":"https://api.github.com/users/Orange-OpenSource","html_url":"https://github.com/Orange-OpenSource","followers_url":"https://api.github.com/users/Orange-OpenSource/followers","following_url":"https://api.github.com/users/Orange-OpenSource/following{/other_user}","gists_url":"https://api.github.com/users/Orange-OpenSource/gists{/gist_id}","starred_url":"https://api.github.com/users/Orange-OpenSource/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Orange-OpenSource/subscriptions","organizations_url":"https://api.github.com/users/Orange-OpenSource/orgs","repos_url":"https://api.github.com/users/Orange-OpenSource/repos","events_url":"https://api.github.com/users/Orange-OpenSource/events{/privacy}","received_events_url":"https://api.github.com/users/Orange-OpenSource/received_events","type":"Organization","site_admin":false},"repo":{"id":96444708,"node_id":"MDEwOlJlcG9zaXRvcnk5NjQ0NDcwOA==","name":"skydive","full_name":"Orange-OpenSource/skydive","private":false,"owner":{"login":"Orange-OpenSource","id":1506386,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE1MDYzODY=","avatar_url":"https://avatars0.githubusercontent.com/u/1506386?v=4","gravatar_id":"","url":"https://api.github.com/users/Orange-OpenSource","html_url":"https://github.com/Orange-OpenSource","followers_url":"https://api.github.com/users/Orange-OpenSource/followers","following_url":"https://api.github.com/users/Orange-OpenSource/following{/other_user}","gists_url":"https://api.github.com/users/Orange-OpenSource/gists{/gist_id}","starred_url":"https://api.github.com/users/Orange-OpenSource/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Orange-OpenSource/subscriptions","organizations_url":"https://api.github.com/users/Orange-OpenSource/orgs","repos_url":"https://api.github.com/users/Orange-OpenSource/repos","events_url":"https://api.github.com/users/Orange-OpenSource/events{/privacy}","received_events_url":"https://api.github.com/users/Orange-OpenSource/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/Orange-OpenSource/skydive","description":"An open source real-time network topology and protocols analyzer","fork":true,"url":"https://api.github.com/repos/Orange-OpenSource/skydive","forks_url":"https://api.github.com/repos/Orange-OpenSource/skydive/forks","keys_url":"https://api.github.com/repos/Orange-OpenSource/skydive/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Orange-OpenSource/skydive/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Orange-OpenSource/skydive/teams","hooks_url":"https://api.github.com/repos/Orange-OpenSource/skydive/hooks","issue_events_url":"https://api.github.com/repos/Orange-OpenSource/skydive/issues/events{/number}","events_url":"https://api.github.com/repos/Orange-OpenSource/skydive/events","assignees_url":"https://api.github.com/repos/Orange-OpenSource/skydive/assignees{/user}","branches_url":"https://api.github.com/repos/Orange-OpenSource/skydive/branches{/branch}","tags_url":"https://api.github.com/repos/Orange-OpenSource/skydive/tags","blobs_url":"https://api.github.com/repos/Orange-OpenSource/skydive/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Orange-OpenSource/skydive/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Orange-OpenSource/skydive/git/refs{/sha}","trees_url":"https://api.github.com/repos/Orange-OpenSource/skydive/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Orange-OpenSource/skydive/statuses/{sha}","languages_url":"https://api.github.com/repos/Orange-OpenSource/skydive/languages","stargazers_url":"https://api.github.com/repos/Orange-OpenSource/skydive/stargazers","contributors_url":"https://api.github.com/repos/Orange-OpenSource/skydive/contributors","subscribers_url":"https://api.github.com/repos/Orange-OpenSource/skydive/subscribers","subscription_url":"https://api.github.com/repos/Orange-OpenSource/skydive/subscription","commits_url":"https://api.github.com/repos/Orange-OpenSource/skydive/commits{/sha}","git_commits_url":"https://api.github.com/repos/Orange-OpenSource/skydive/git/commits{/sha}","comments_url":"https://api.github.com/repos/Orange-OpenSource/skydive/comments{/number}","issue_comment_url":"https://api.github.com/repos/Orange-OpenSource/skydive/issues/comments{/number}","contents_url":"https://api.github.com/repos/Orange-OpenSource/skydive/contents/{+path}","compare_url":"https://api.github.com/repos/Orange-OpenSource/skydive/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Orange-OpenSource/skydive/merges","archive_url":"https://api.github.com/repos/Orange-OpenSource/skydive/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Orange-OpenSource/skydive/downloads","issues_url":"https://api.github.com/repos/Orange-OpenSource/skydive/issues{/number}","pulls_url":"https://api.github.com/repos/Orange-OpenSource/skydive/pulls{/number}","milestones_url":"https://api.github.com/repos/Orange-OpenSource/skydive/milestones{/number}","notifications_url":"https://api.github.com/repos/Orange-OpenSource/skydive/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Orange-OpenSource/skydive/labels{/name}","releases_url":"https://api.github.com/repos/Orange-OpenSource/skydive/releases{/id}","deployments_url":"https://api.github.com/repos/Orange-OpenSource/skydive/deployments","created_at":"2017-07-06T15:23:17Z","updated_at":"2018-11-06T07:48:46Z","pushed_at":"2018-11-08T15:46:27Z","git_url":"git://github.com/Orange-OpenSource/skydive.git","ssh_url":"git@github.com:Orange-OpenSource/skydive.git","clone_url":"https://github.com/Orange-OpenSource/skydive.git","svn_url":"https://github.com/Orange-OpenSource/skydive","homepage":"","size":38673,"stargazers_count":2,"watchers_count":2,"language":"Go","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":0,"open_issues":0,"watchers":2,"default_branch":"master"}},"base":{"label":"skydive-project:master","ref":"master","sha":"788ab9382c4b1ae2271fd2988ea290c8a42cb314","user":{"login":"skydive-project","id":19912486,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE5OTEyNDg2","avatar_url":"https://avatars0.githubusercontent.com/u/19912486?v=4","gravatar_id":"","url":"https://api.github.com/users/skydive-project","html_url":"https://github.com/skydive-project","followers_url":"https://api.github.com/users/skydive-project/followers","following_url":"https://api.github.com/users/skydive-project/following{/other_user}","gists_url":"https://api.github.com/users/skydive-project/gists{/gist_id}","starred_url":"https://api.github.com/users/skydive-project/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/skydive-project/subscriptions","organizations_url":"https://api.github.com/users/skydive-project/orgs","repos_url":"https://api.github.com/users/skydive-project/repos","events_url":"https://api.github.com/users/skydive-project/events{/privacy}","received_events_url":"https://api.github.com/users/skydive-project/received_events","type":"Organization","site_admin":false},"repo":{"id":44610195,"node_id":"MDEwOlJlcG9zaXRvcnk0NDYxMDE5NQ==","name":"skydive","full_name":"skydive-project/skydive","private":false,"owner":{"login":"skydive-project","id":19912486,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE5OTEyNDg2","avatar_url":"https://avatars0.githubusercontent.com/u/19912486?v=4","gravatar_id":"","url":"https://api.github.com/users/skydive-project","html_url":"https://github.com/skydive-project","followers_url":"https://api.github.com/users/skydive-project/followers","following_url":"https://api.github.com/users/skydive-project/following{/other_user}","gists_url":"https://api.github.com/users/skydive-project/gists{/gist_id}","starred_url":"https://api.github.com/users/skydive-project/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/skydive-project/subscriptions","organizations_url":"https://api.github.com/users/skydive-project/orgs","repos_url":"https://api.github.com/users/skydive-project/repos","events_url":"https://api.github.com/users/skydive-project/events{/privacy}","received_events_url":"https://api.github.com/users/skydive-project/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/skydive-project/skydive","description":"An open source real-time network topology and protocols analyzer","fork":false,"url":"https://api.github.com/repos/skydive-project/skydive","forks_url":"https://api.github.com/repos/skydive-project/skydive/forks","keys_url":"https://api.github.com/repos/skydive-project/skydive/keys{/key_id}","collaborators_url":"https://api.github.com/repos/skydive-project/skydive/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/skydive-project/skydive/teams","hooks_url":"https://api.github.com/repos/skydive-project/skydive/hooks","issue_events_url":"https://api.github.com/repos/skydive-project/skydive/issues/events{/number}","events_url":"https://api.github.com/repos/skydive-project/skydive/events","assignees_url":"https://api.github.com/repos/skydive-project/skydive/assignees{/user}","branches_url":"https://api.github.com/repos/skydive-project/skydive/branches{/branch}","tags_url":"https://api.github.com/repos/skydive-project/skydive/tags","blobs_url":"https://api.github.com/repos/skydive-project/skydive/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/skydive-project/skydive/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/skydive-project/skydive/git/refs{/sha}","trees_url":"https://api.github.com/repos/skydive-project/skydive/git/trees{/sha}","statuses_url":"https://api.github.com/repos/skydive-project/skydive/statuses/{sha}","languages_url":"https://api.github.com/repos/skydive-project/skydive/languages","stargazers_url":"https://api.github.com/repos/skydive-project/skydive/stargazers","contributors_url":"https://api.github.com/repos/skydive-project/skydive/contributors","subscribers_url":"https://api.github.com/repos/skydive-project/skydive/subscribers","subscription_url":"https://api.github.com/repos/skydive-project/skydive/subscription","commits_url":"https://api.github.com/repos/skydive-project/skydive/commits{/sha}","git_commits_url":"https://api.github.com/repos/skydive-project/skydive/git/commits{/sha}","comments_url":"https://api.github.com/repos/skydive-project/skydive/comments{/number}","issue_comment_url":"https://api.github.com/repos/skydive-project/skydive/issues/comments{/number}","contents_url":"https://api.github.com/repos/skydive-project/skydive/contents/{+path}","compare_url":"https://api.github.com/repos/skydive-project/skydive/compare/{base}...{head}","merges_url":"https://api.github.com/repos/skydive-project/skydive/merges","archive_url":"https://api.github.com/repos/skydive-project/skydive/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/skydive-project/skydive/downloads","issues_url":"https://api.github.com/repos/skydive-project/skydive/issues{/number}","pulls_url":"https://api.github.com/repos/skydive-project/skydive/pulls{/number}","milestones_url":"https://api.github.com/repos/skydive-project/skydive/milestones{/number}","notifications_url":"https://api.github.com/repos/skydive-project/skydive/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/skydive-project/skydive/labels{/name}","releases_url":"https://api.github.com/repos/skydive-project/skydive/releases{/id}","deployments_url":"https://api.github.com/repos/skydive-project/skydive/deployments","created_at":"2015-10-20T14:02:21Z","updated_at":"2018-11-08T14:36:50Z","pushed_at":"2018-11-08T15:46:29Z","git_url":"git://github.com/skydive-project/skydive.git","ssh_url":"git@github.com:skydive-project/skydive.git","clone_url":"https://github.com/skydive-project/skydive.git","svn_url":"https://github.com/skydive-project/skydive","homepage":"","size":38657,"stargazers_count":869,"watchers_count":869,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":189,"mirror_url":null,"archived":false,"open_issues_count":114,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":189,"open_issues":114,"watchers":869,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/skydive-project/skydive/pulls/1423"},"html":{"href":"https://github.com/skydive-project/skydive/pull/1423"},"issue":{"href":"https://api.github.com/repos/skydive-project/skydive/issues/1423"},"comments":{"href":"https://api.github.com/repos/skydive-project/skydive/issues/1423/comments"},"review_comments":{"href":"https://api.github.com/repos/skydive-project/skydive/pulls/1423/comments"},"review_comment":{"href":"https://api.github.com/repos/skydive-project/skydive/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/skydive-project/skydive/pulls/1423/commits"},"statuses":{"href":"https://api.github.com/repos/skydive-project/skydive/statuses/d0d38253fea1e65eea7d75b4b2e0a4830d17444c"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"pierrecregut"}}