{"action":"created","comment":{"url":"https://api.github.com/repos/ubuntu-core/snappy/pulls/comments/44858364","id":44858364,"diff_hunk":"@@ -20,94 +20,302 @@\n package snappy\n \n import (\n-\t\"encoding/json\"\n+\t\"bufio\"\n+\t\"errors\"\n \t\"fmt\"\n \t\"io/ioutil\"\n \t\"os\"\n \t\"os/exec\"\n \t\"path/filepath\"\n+\t\"regexp\"\n \t\"strings\"\n \n-\t\"gopkg.in/yaml.v2\"\n-\n \t\"github.com/ubuntu-core/snappy/dirs\"\n+\t\"github.com/ubuntu-core/snappy/helpers\"\n \t\"github.com/ubuntu-core/snappy/logger\"\n \t\"github.com/ubuntu-core/snappy/pkg\"\n+\t\"github.com/ubuntu-core/snappy/policy\"\n+\t\"github.com/ubuntu-core/snappy/release\"\n )\n \n-type apparmorJSONTemplate struct {\n-\tTemplate      string   `json:\"template\"`\n-\tPolicyGroups  []string `json:\"policy_groups\"`\n-\tPolicyVendor  string   `json:\"policy_vendor\"`\n-\tPolicyVersion float64  `json:\"policy_version\"`\n+type errPolicyNotFound struct {\n+\t// type of policy, e.g. template or cap\n+\tPolType string\n+\t// apparmor or seccomp\n+\tPolKind *securityPolicyType\n+\t// name of the policy\n+\tPolName string\n }\n \n-type securitySeccompOverride struct {\n-\tTemplate      string   `yaml:\"security-template,omitempty\"`\n-\tPolicyGroups  []string `yaml:\"caps,omitempty\"`\n-\tSyscalls      []string `yaml:\"syscalls,omitempty\"`\n-\tPolicyVendor  string   `yaml:\"policy-vendor\"`\n-\tPolicyVersion float64  `yaml:\"policy-version\"`\n+func (e *errPolicyNotFound) Error() string {\n+\treturn fmt.Sprintf(\"could not find specified %s: %s (%s)\", e.PolType, e.PolName, e.PolKind)\n }\n \n-const defaultTemplate = \"default\"\n+var (\n+\t// Note: these are true for ubuntu-core but perhaps not other flavors\n+\tdefaultTemplateName = \"default\"\n+\tdefaultPolicyGroups = []string{\"network-client\"}\n \n-var defaultPolicyGroups = []string{\"network-client\"}\n+\t// AppArmor cache dir\n+\taaCacheDir = \"/var/cache/apparmor\"\n \n-// TODO: autodetect, this won't work for personal\n-const defaultPolicyVendor = \"ubuntu-core\"\n-const defaultPolicyVersion = 15.04\n+\t// ErrSystemVersionNotFound could not detect system version (eg, 15.04,\n+\t// 15.10, etc)\n+\terrSystemVersionNotFound = errors.New(\"could not detect system version\")\n \n-func (s *SecurityDefinitions) generateApparmorJSONContent() ([]byte, error) {\n-\tt := apparmorJSONTemplate{\n-\t\tTemplate:      s.SecurityTemplate,\n-\t\tPolicyGroups:  s.SecurityCaps,\n-\t\tPolicyVendor:  defaultPolicyVendor,\n-\t\tPolicyVersion: defaultPolicyVersion,\n-\t}\n+\terrOriginNotFound     = errors.New(\"could not detect origin\")\n+\terrPolicyTypeNotFound = errors.New(\"could not find specified policy type\")\n+\terrInvalidAppID       = errors.New(\"invalid APP_ID\")\n+\terrPolicyGen          = errors.New(\"errors found when generating policy\")\n \n-\t// FIXME: this is snappy specific, on other systems like the\n-\t//        phone we may want different defaults.\n-\tif t.Template == \"\" && t.PolicyGroups == nil {\n-\t\tt.PolicyGroups = defaultPolicyGroups\n+\t// snappyConfig is the default securityDefinition for a snappy\n+\t// config fragment\n+\tsnappyConfig = &SecurityDefinitions{\n+\t\tSecurityCaps: []string{},\n \t}\n \n-\t// never write a null value out into the json\n-\tif t.PolicyGroups == nil {\n-\t\tt.PolicyGroups = []string{}\n+\tlsbRelease        = \"/etc/lsb-release\"\n+\trunAppArmorParser = runAppArmorParserImpl\n+)\n+\n+func runAppArmorParserImpl(argv ...string) ([]byte, error) {\n+\tcmd := exec.Command(argv[0], argv[1:]...)\n+\treturn cmd.CombinedOutput()\n+}\n+\n+// SecurityOverrideDefinition is used to override apparmor or seccomp\n+// security defaults\n+type SecurityOverrideDefinition struct {\n+\tReadPaths    []string `yaml:\"read-paths,omitempty\" json:\"read-paths,omitempty\"`\n+\tWritePaths   []string `yaml:\"write-paths,omitempty\" json:\"write-paths,omitempty\"`\n+\tAbstractions []string `yaml:\"abstractions,omitempty\" json:\"abstractions,omitempty\"`\n+\tSyscalls     []string `yaml:\"syscalls,omitempty\" json:\"syscalls,omitempty\"`\n+\n+\t// deprecated keys, we warn when we see those\n+\tDeprecatedAppArmor interface{} `yaml:\"apparmor,omitempty\" json:\"apparmor,omitempty\"`\n+\tDeprecatedSeccomp  interface{} `yaml:\"seccomp,omitempty\" json:\"seccomp,omitempty\"`\n+}\n+\n+// SecurityPolicyDefinition is used to provide hand-crafted policy\n+type SecurityPolicyDefinition struct {\n+\tAppArmor string `yaml:\"apparmor\" json:\"apparmor\"`\n+\tSeccomp  string `yaml:\"seccomp\" json:\"seccomp\"`\n+}\n+\n+// SecurityDefinitions contains the common apparmor/seccomp definitions\n+type SecurityDefinitions struct {\n+\t// SecurityTemplate is a template name like \"default\"\n+\tSecurityTemplate string `yaml:\"security-template,omitempty\" json:\"security-template,omitempty\"`\n+\t// SecurityOverride is a override for the high level security json\n+\tSecurityOverride *SecurityOverrideDefinition `yaml:\"security-override,omitempty\" json:\"security-override,omitempty\"`\n+\t// SecurityPolicy is a hand-crafted low-level policy\n+\tSecurityPolicy *SecurityPolicyDefinition `yaml:\"security-policy,omitempty\" json:\"security-policy,omitempty\"`\n+\n+\t// SecurityCaps is are the apparmor/seccomp capabilities for an app\n+\tSecurityCaps []string `yaml:\"caps,omitempty\" json:\"caps,omitempty\"`\n+}\n+\n+// securityPolicyType is a kind of securityPolicy, we currently\n+// have \"apparmor\" and \"seccomp\"\n+type securityPolicyType struct {\n+\tname          string\n+\tbasePolicyDir string\n+}\n+\n+var securityPolicyTypeAppArmor = securityPolicyType{\n+\tname:          \"apparmor\",\n+\tbasePolicyDir: \"/usr/share/apparmor/easyprof\",\n+}\n+\n+var securityPolicyTypeSeccomp = securityPolicyType{\n+\tname:          \"seccomp\",\n+\tbasePolicyDir: \"/usr/share/seccomp\",\n+}\n+\n+func (sp *securityPolicyType) policyDir() string {\n+\treturn filepath.Join(dirs.GlobalRootDir, sp.basePolicyDir)\n+}\n+\n+func (sp *securityPolicyType) frameworkPolicyDir() string {\n+\tframeworkPolicyDir := filepath.Join(policy.SecBase, sp.name)\n+\treturn filepath.Join(dirs.GlobalRootDir, frameworkPolicyDir)\n+}\n+\n+// findTemplate returns the security template content from the template name.\n+func (sp *securityPolicyType) findTemplate(templateName string) (string, error) {\n+\tif templateName == \"\" {\n+\t\ttemplateName = defaultTemplateName\n \t}\n \n-\tif t.Template == \"\" {\n-\t\tt.Template = defaultTemplate\n+\tsubdir := filepath.Join(\"templates\", defaultPolicyVendor(), defaultPolicyVersion())\n+\tsystemTemplateDir := filepath.Join(sp.policyDir(), subdir, templateName)\n+\tfwTemplateDir := filepath.Join(sp.frameworkPolicyDir(), \"templates\", templateName)\n+\n+\t// Read system and framwork policy, but always prefer system policy\n+\tfns := []string{systemTemplateDir, fwTemplateDir}\n+\tfor _, fn := range fns {\n+\t\tcontent, err := ioutil.ReadFile(fn)\n+\t\t// it is ok if the file does not exists\n+\t\tif err != nil && os.IsNotExist(err) {\n+\t\t\tcontinue\n+\t\t}\n+\t\t// but any other error is a failure\n+\t\tif err != nil {\n+\t\t\treturn \"\", err\n+\t\t}\n+\n+\t\treturn string(content), nil\n \t}\n \n-\toutStr, err := json.MarshalIndent(t, \"\", \"  \")\n+\treturn \"\", &errPolicyNotFound{\"template\", sp, templateName}\n+}\n+\n+// helper for findSingleCap that implements readlines().\n+func readSingleCapFile(fn string) ([]string, error) {\n+\tp := []string{}\n+\n+\tr, err := os.Open(fn)\n \tif err != nil {\n \t\treturn nil, err\n \t}\n+\tdefer r.Close()\n+\n+\ts := bufio.NewScanner(r)\n+\tfor s.Scan() {\n+\t\tp = append(p, s.Text())\n+\t}\n+\tif err := s.Err(); err != nil {\n+\t\treturn nil, err\n+\t}\n \n-\treturn outStr, nil\n+\treturn p, nil\n }\n \n-func handleApparmor(buildDir string, m *packageYaml, hookName string, s *SecurityDefinitions) error {\n-\thasSecPol := s.SecurityPolicy != nil && s.SecurityPolicy.Apparmor != \"\"\n-\thasSecOvr := s.SecurityOverride != nil && s.SecurityOverride.Apparmor != \"\"\n+// findSingleCap returns the security template content for a single\n+// security-cap.\n+func (sp *securityPolicyType) findSingleCap(capName, systemPolicyDir, fwPolicyDir string) ([]string, error) {\n+\tfound := false\n+\tp := []string{}\n+\n+\tpolicyDirs := []string{systemPolicyDir, fwPolicyDir}\n+\tfor _, dir := range policyDirs {\n+\t\tfn := filepath.Join(dir, capName)\n+\t\tnewCaps, err := readSingleCapFile(fn)\n+\t\t// its ok if the file does not exist\n+\t\tif err != nil && os.IsNotExist(err) {\n+\t\t\tcontinue\n+\t\t}\n+\t\t// but any other error is not ok\n+\t\tif err != nil {\n+\t\t\treturn nil, err\n+\t\t}\n+\t\tp = append(p, newCaps...)\n+\t\tfound = true\n+\t\tbreak\n+\t}\n \n-\tif hasSecPol || hasSecOvr {\n-\t\treturn nil\n+\tif found == false {\n+\t\treturn nil, &errPolicyNotFound{\"cap\", sp, capName}\n+\t}\n+\n+\treturn p, nil\n+}\n+\n+// findCaps returns the security template content for the given list\n+// of security-caps.\n+func (sp *securityPolicyType) findCaps(caps []string, templateName string) ([]string, error) {\n+\t// XXX: this is snappy specific, on other systems like the phone we may\n+\t// want different defaults.\n+\tif templateName == \"\" && caps == nil {\n+\t\tcaps = defaultPolicyGroups\n+\t}\n+\n+\t// Nothing to find if caps is empty\n+\tif len(caps) == 0 {\n+\t\treturn nil, nil\n+\t}\n+\n+\tsubdir := filepath.Join(\"policygroups\", defaultPolicyVendor(), defaultPolicyVersion())\n+\tparentDir := filepath.Join(sp.policyDir(), subdir)\n+\tfwParentDir := filepath.Join(sp.frameworkPolicyDir(), \"policygroups\")\n+\n+\tvar p []string\n+\tfor _, c := range caps {\n+\t\tnewCap, err := sp.findSingleCap(c, parentDir, fwParentDir)\n+\t\tif err != nil {\n+\t\t\treturn nil, err\n+\t\t}\n+\t\tp = append(p, newCap...)\n \t}\n \n-\t// generate apparmor template\n-\tapparmorJSONFile := m.Integration[hookName][\"apparmor\"]\n-\tsecurityJSONContent, err := s.generateApparmorJSONContent()\n+\treturn p, nil\n+}\n+\n+func defaultPolicyVendor() string {\n+\t// FIXME: slightly ugly that we have to give a prefix here\n+\treturn fmt.Sprintf(\"ubuntu-%s\", release.Get().Flavor)\n+}\n+\n+// findUbuntuVersion determines the version (eg, 15.04, 15.10, etc) of the\n+// system, which is needed for determining the security policy\n+// policy-version\n+func findUbuntuVersion() (string, error) {\n+\tcontent, err := ioutil.ReadFile(lsbRelease)\n \tif err != nil {\n-\t\treturn err\n+\t\tlogger.Noticef(\"Failed to read %q: %v\", lsbRelease, err)\n+\t\treturn \"\", err\n \t}\n-\tif err := ioutil.WriteFile(filepath.Join(buildDir, apparmorJSONFile), securityJSONContent, 0644); err != nil {\n-\t\treturn err\n+\n+\tfor _, line := range strings.Split(string(content), \"\\n\") {\n+\t\tif strings.HasPrefix(line, \"DISTRIB_RELEASE=\") {\n+\t\t\ttmp := strings.Split(line, \"=\")\n+\t\t\tif len(tmp) != 2 {\n+\t\t\t\treturn \"\", errSystemVersionNotFound\n+\t\t\t}\n+\t\t\treturn tmp[1], nil\n+\t\t}\n \t}\n \n-\treturn nil\n+\treturn \"\", errSystemVersionNotFound\n+}\n+func defaultPolicyVersion() string {\n+\t// note that we can not use release.Get().Series here\n+\t// because that will return \"rolling\" for the development\n+\t// version but apparmor stores its templates under the\n+\t// version number (e.g. 16.04) instead\n+\tver, err := findUbuntuVersion()\n+\tif err != nil {\n+\t\t// when this happens we are in trouble\n+\t\tpanic(err)\n+\t}\n+\treturn ver\n+}\n+\n+const allowed = `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789`\n+\n+// Generate a string suitable for use in a DBus object\n+func dbusPath(s string) string {\n+\tdbusStr := \"\"\n+\n+\tfor _, c := range []byte(s) {\n+\t\tif strings.IndexByte(allowed, c) >= 0 {\n+\t\t\tdbusStr += fmt.Sprintf(\"%c\", c)","path":"snappy/security.go","position":332,"original_position":332,"commit_id":"1f4543c102bff82b53f80551c83ecdcf2f9abecb","original_commit_id":"1f4543c102bff82b53f80551c83ecdcf2f9abecb","user":{"login":"niemeyer","id":378683,"avatar_url":"https://avatars.githubusercontent.com/u/378683?v=3","gravatar_id":"","url":"https://api.github.com/users/niemeyer","html_url":"https://github.com/niemeyer","followers_url":"https://api.github.com/users/niemeyer/followers","following_url":"https://api.github.com/users/niemeyer/following{/other_user}","gists_url":"https://api.github.com/users/niemeyer/gists{/gist_id}","starred_url":"https://api.github.com/users/niemeyer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/niemeyer/subscriptions","organizations_url":"https://api.github.com/users/niemeyer/orgs","repos_url":"https://api.github.com/users/niemeyer/repos","events_url":"https://api.github.com/users/niemeyer/events{/privacy}","received_events_url":"https://api.github.com/users/niemeyer/received_events","type":"User","site_admin":false},"body":"Again FWIW, that might be string(c)","created_at":"2015-11-14T13:13:17Z","updated_at":"2015-11-14T13:13:17Z","html_url":"https://github.com/ubuntu-core/snappy/pull/55#discussion_r44858364","pull_request_url":"https://api.github.com/repos/ubuntu-core/snappy/pulls/55","_links":{"self":{"href":"https://api.github.com/repos/ubuntu-core/snappy/pulls/comments/44858364"},"html":{"href":"https://github.com/ubuntu-core/snappy/pull/55#discussion_r44858364"},"pull_request":{"href":"https://api.github.com/repos/ubuntu-core/snappy/pulls/55"}}},"pull_request":{"url":"https://api.github.com/repos/ubuntu-core/snappy/pulls/55","id":49689139,"html_url":"https://github.com/ubuntu-core/snappy/pull/55","diff_url":"https://github.com/ubuntu-core/snappy/pull/55.diff","patch_url":"https://github.com/ubuntu-core/snappy/pull/55.patch","issue_url":"https://api.github.com/repos/ubuntu-core/snappy/issues/55","number":55,"state":"open","locked":false,"title":"Feature/native security policygen regen","user":{"login":"mvo5","id":914418,"avatar_url":"https://avatars.githubusercontent.com/u/914418?v=3","gravatar_id":"","url":"https://api.github.com/users/mvo5","html_url":"https://github.com/mvo5","followers_url":"https://api.github.com/users/mvo5/followers","following_url":"https://api.github.com/users/mvo5/following{/other_user}","gists_url":"https://api.github.com/users/mvo5/gists{/gist_id}","starred_url":"https://api.github.com/users/mvo5/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mvo5/subscriptions","organizations_url":"https://api.github.com/users/mvo5/orgs","repos_url":"https://api.github.com/users/mvo5/repos","events_url":"https://api.github.com/users/mvo5/events{/privacy}","received_events_url":"https://api.github.com/users/mvo5/received_events","type":"User","site_admin":false},"body":"This branch implements native apparmor and seccomp generation from snappy itself. Once this branch lands we can remove aa-clickhook from the image. Its unfortunately huge. It is split up:\r\n  feature/native-security-policygen\r\n  feature/native-security-policygen-add-hwaccess\r\n  feature/native-security-policygen-compare\r\n* feature/native-security-policygen-regen\r\nso I could split it into smaller chunks, but I'm not sure this helps much as the first branch is already pretty big :/\r\n\r\nAnyway, early feedback would be great. I will also ask Jamie to see if there is anything missing. Please do not merge before Jamie had a chance to have a look.","created_at":"2015-11-04T13:39:11Z","updated_at":"2015-11-14T13:13:17Z","closed_at":null,"merged_at":null,"merge_commit_sha":"ce29d4f25c12924261a9410b35a09f6f9c6b3098","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/ubuntu-core/snappy/pulls/55/commits","review_comments_url":"https://api.github.com/repos/ubuntu-core/snappy/pulls/55/comments","review_comment_url":"https://api.github.com/repos/ubuntu-core/snappy/pulls/comments{/number}","comments_url":"https://api.github.com/repos/ubuntu-core/snappy/issues/55/comments","statuses_url":"https://api.github.com/repos/ubuntu-core/snappy/statuses/1f4543c102bff82b53f80551c83ecdcf2f9abecb","head":{"label":"mvo5:feature/native-security-policygen-regen","ref":"feature/native-security-policygen-regen","sha":"1f4543c102bff82b53f80551c83ecdcf2f9abecb","user":{"login":"mvo5","id":914418,"avatar_url":"https://avatars.githubusercontent.com/u/914418?v=3","gravatar_id":"","url":"https://api.github.com/users/mvo5","html_url":"https://github.com/mvo5","followers_url":"https://api.github.com/users/mvo5/followers","following_url":"https://api.github.com/users/mvo5/following{/other_user}","gists_url":"https://api.github.com/users/mvo5/gists{/gist_id}","starred_url":"https://api.github.com/users/mvo5/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mvo5/subscriptions","organizations_url":"https://api.github.com/users/mvo5/orgs","repos_url":"https://api.github.com/users/mvo5/repos","events_url":"https://api.github.com/users/mvo5/events{/privacy}","received_events_url":"https://api.github.com/users/mvo5/received_events","type":"User","site_admin":false},"repo":{"id":45067233,"name":"snappy","full_name":"mvo5/snappy","owner":{"login":"mvo5","id":914418,"avatar_url":"https://avatars.githubusercontent.com/u/914418?v=3","gravatar_id":"","url":"https://api.github.com/users/mvo5","html_url":"https://github.com/mvo5","followers_url":"https://api.github.com/users/mvo5/followers","following_url":"https://api.github.com/users/mvo5/following{/other_user}","gists_url":"https://api.github.com/users/mvo5/gists{/gist_id}","starred_url":"https://api.github.com/users/mvo5/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mvo5/subscriptions","organizations_url":"https://api.github.com/users/mvo5/orgs","repos_url":"https://api.github.com/users/mvo5/repos","events_url":"https://api.github.com/users/mvo5/events{/privacy}","received_events_url":"https://api.github.com/users/mvo5/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/mvo5/snappy","description":"Snappy is the software at the heart of Ubuntu Core.","fork":true,"url":"https://api.github.com/repos/mvo5/snappy","forks_url":"https://api.github.com/repos/mvo5/snappy/forks","keys_url":"https://api.github.com/repos/mvo5/snappy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mvo5/snappy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mvo5/snappy/teams","hooks_url":"https://api.github.com/repos/mvo5/snappy/hooks","issue_events_url":"https://api.github.com/repos/mvo5/snappy/issues/events{/number}","events_url":"https://api.github.com/repos/mvo5/snappy/events","assignees_url":"https://api.github.com/repos/mvo5/snappy/assignees{/user}","branches_url":"https://api.github.com/repos/mvo5/snappy/branches{/branch}","tags_url":"https://api.github.com/repos/mvo5/snappy/tags","blobs_url":"https://api.github.com/repos/mvo5/snappy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mvo5/snappy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mvo5/snappy/git/refs{/sha}","trees_url":"https://api.github.com/repos/mvo5/snappy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mvo5/snappy/statuses/{sha}","languages_url":"https://api.github.com/repos/mvo5/snappy/languages","stargazers_url":"https://api.github.com/repos/mvo5/snappy/stargazers","contributors_url":"https://api.github.com/repos/mvo5/snappy/contributors","subscribers_url":"https://api.github.com/repos/mvo5/snappy/subscribers","subscription_url":"https://api.github.com/repos/mvo5/snappy/subscription","commits_url":"https://api.github.com/repos/mvo5/snappy/commits{/sha}","git_commits_url":"https://api.github.com/repos/mvo5/snappy/git/commits{/sha}","comments_url":"https://api.github.com/repos/mvo5/snappy/comments{/number}","issue_comment_url":"https://api.github.com/repos/mvo5/snappy/issues/comments{/number}","contents_url":"https://api.github.com/repos/mvo5/snappy/contents/{+path}","compare_url":"https://api.github.com/repos/mvo5/snappy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mvo5/snappy/merges","archive_url":"https://api.github.com/repos/mvo5/snappy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mvo5/snappy/downloads","issues_url":"https://api.github.com/repos/mvo5/snappy/issues{/number}","pulls_url":"https://api.github.com/repos/mvo5/snappy/pulls{/number}","milestones_url":"https://api.github.com/repos/mvo5/snappy/milestones{/number}","notifications_url":"https://api.github.com/repos/mvo5/snappy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mvo5/snappy/labels{/name}","releases_url":"https://api.github.com/repos/mvo5/snappy/releases{/id}","created_at":"2015-10-27T20:12:18Z","updated_at":"2015-10-27T20:12:21Z","pushed_at":"2015-11-13T15:35:37Z","git_url":"git://github.com/mvo5/snappy.git","ssh_url":"git@github.com:mvo5/snappy.git","clone_url":"https://github.com/mvo5/snappy.git","svn_url":"https://github.com/mvo5/snappy","homepage":null,"size":24292,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":false,"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":0,"default_branch":"master"}},"base":{"label":"ubuntu-core:master","ref":"master","sha":"c7315fab637ff8f893b632c2a0e1492e2dbe2fcd","user":{"login":"ubuntu-core","id":15022473,"avatar_url":"https://avatars.githubusercontent.com/u/15022473?v=3","gravatar_id":"","url":"https://api.github.com/users/ubuntu-core","html_url":"https://github.com/ubuntu-core","followers_url":"https://api.github.com/users/ubuntu-core/followers","following_url":"https://api.github.com/users/ubuntu-core/following{/other_user}","gists_url":"https://api.github.com/users/ubuntu-core/gists{/gist_id}","starred_url":"https://api.github.com/users/ubuntu-core/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ubuntu-core/subscriptions","organizations_url":"https://api.github.com/users/ubuntu-core/orgs","repos_url":"https://api.github.com/users/ubuntu-core/repos","events_url":"https://api.github.com/users/ubuntu-core/events{/privacy}","received_events_url":"https://api.github.com/users/ubuntu-core/received_events","type":"Organization","site_admin":false},"repo":{"id":44672744,"name":"snappy","full_name":"ubuntu-core/snappy","owner":{"login":"ubuntu-core","id":15022473,"avatar_url":"https://avatars.githubusercontent.com/u/15022473?v=3","gravatar_id":"","url":"https://api.github.com/users/ubuntu-core","html_url":"https://github.com/ubuntu-core","followers_url":"https://api.github.com/users/ubuntu-core/followers","following_url":"https://api.github.com/users/ubuntu-core/following{/other_user}","gists_url":"https://api.github.com/users/ubuntu-core/gists{/gist_id}","starred_url":"https://api.github.com/users/ubuntu-core/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ubuntu-core/subscriptions","organizations_url":"https://api.github.com/users/ubuntu-core/orgs","repos_url":"https://api.github.com/users/ubuntu-core/repos","events_url":"https://api.github.com/users/ubuntu-core/events{/privacy}","received_events_url":"https://api.github.com/users/ubuntu-core/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/ubuntu-core/snappy","description":"Snappy is the software at the heart of Ubuntu Core.","fork":false,"url":"https://api.github.com/repos/ubuntu-core/snappy","forks_url":"https://api.github.com/repos/ubuntu-core/snappy/forks","keys_url":"https://api.github.com/repos/ubuntu-core/snappy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ubuntu-core/snappy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ubuntu-core/snappy/teams","hooks_url":"https://api.github.com/repos/ubuntu-core/snappy/hooks","issue_events_url":"https://api.github.com/repos/ubuntu-core/snappy/issues/events{/number}","events_url":"https://api.github.com/repos/ubuntu-core/snappy/events","assignees_url":"https://api.github.com/repos/ubuntu-core/snappy/assignees{/user}","branches_url":"https://api.github.com/repos/ubuntu-core/snappy/branches{/branch}","tags_url":"https://api.github.com/repos/ubuntu-core/snappy/tags","blobs_url":"https://api.github.com/repos/ubuntu-core/snappy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ubuntu-core/snappy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ubuntu-core/snappy/git/refs{/sha}","trees_url":"https://api.github.com/repos/ubuntu-core/snappy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ubuntu-core/snappy/statuses/{sha}","languages_url":"https://api.github.com/repos/ubuntu-core/snappy/languages","stargazers_url":"https://api.github.com/repos/ubuntu-core/snappy/stargazers","contributors_url":"https://api.github.com/repos/ubuntu-core/snappy/contributors","subscribers_url":"https://api.github.com/repos/ubuntu-core/snappy/subscribers","subscription_url":"https://api.github.com/repos/ubuntu-core/snappy/subscription","commits_url":"https://api.github.com/repos/ubuntu-core/snappy/commits{/sha}","git_commits_url":"https://api.github.com/repos/ubuntu-core/snappy/git/commits{/sha}","comments_url":"https://api.github.com/repos/ubuntu-core/snappy/comments{/number}","issue_comment_url":"https://api.github.com/repos/ubuntu-core/snappy/issues/comments{/number}","contents_url":"https://api.github.com/repos/ubuntu-core/snappy/contents/{+path}","compare_url":"https://api.github.com/repos/ubuntu-core/snappy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ubuntu-core/snappy/merges","archive_url":"https://api.github.com/repos/ubuntu-core/snappy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ubuntu-core/snappy/downloads","issues_url":"https://api.github.com/repos/ubuntu-core/snappy/issues{/number}","pulls_url":"https://api.github.com/repos/ubuntu-core/snappy/pulls{/number}","milestones_url":"https://api.github.com/repos/ubuntu-core/snappy/milestones{/number}","notifications_url":"https://api.github.com/repos/ubuntu-core/snappy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ubuntu-core/snappy/labels{/name}","releases_url":"https://api.github.com/repos/ubuntu-core/snappy/releases{/id}","created_at":"2015-10-21T11:51:46Z","updated_at":"2015-11-12T10:38:33Z","pushed_at":"2015-11-13T22:46:07Z","git_url":"git://github.com/ubuntu-core/snappy.git","ssh_url":"git@github.com:ubuntu-core/snappy.git","clone_url":"https://github.com/ubuntu-core/snappy.git","svn_url":"https://github.com/ubuntu-core/snappy","homepage":null,"size":24553,"stargazers_count":31,"watchers_count":31,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":14,"mirror_url":null,"open_issues_count":21,"forks":14,"open_issues":21,"watchers":31,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/ubuntu-core/snappy/pulls/55"},"html":{"href":"https://github.com/ubuntu-core/snappy/pull/55"},"issue":{"href":"https://api.github.com/repos/ubuntu-core/snappy/issues/55"},"comments":{"href":"https://api.github.com/repos/ubuntu-core/snappy/issues/55/comments"},"review_comments":{"href":"https://api.github.com/repos/ubuntu-core/snappy/pulls/55/comments"},"review_comment":{"href":"https://api.github.com/repos/ubuntu-core/snappy/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/ubuntu-core/snappy/pulls/55/commits"},"statuses":{"href":"https://api.github.com/repos/ubuntu-core/snappy/statuses/1f4543c102bff82b53f80551c83ecdcf2f9abecb"}}}}