{"action":"created","comment":{"url":"https://api.github.com/repos/weaveworks/flux/pulls/comments/179726227","pull_request_review_id":109993307,"id":179726227,"diff_hunk":"@@ -0,0 +1,349 @@\n+/*\n+When a Chart release is manually deleted/upgraded/created, the cluster gets out of sync\n+with the prescribed state defined by FluxHelmRelease custom resources. The releasesync package\n+attemps to bring the cluster back to the prescribed state.\n+*/\n+package releasesync\n+\n+import (\n+\t\"context\"\n+\t\"fmt\"\n+\t\"strings\"\n+\t\"sync\"\n+\t\"time\"\n+\n+\tprotobuf \"github.com/golang/protobuf/ptypes/timestamp\"\n+\t\"github.com/weaveworks/flux/integrations/helm/chartsync\"\n+\t\"github.com/weaveworks/flux/integrations/helm/release\"\n+\t\"k8s.io/api/core/v1\"\n+\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n+\t\"k8s.io/apimachinery/pkg/util/runtime\"\n+\t\"k8s.io/client-go/kubernetes\"\n+\thapi_release \"k8s.io/helm/pkg/proto/hapi/release\"\n+\n+\t\"github.com/go-kit/kit/log\"\n+\n+\tifv1 \"github.com/weaveworks/flux/apis/helm.integrations.flux.weave.works/v1alpha\"\n+\tifclientset \"github.com/weaveworks/flux/integrations/client/clientset/versioned\" // kubernetes 1.9\n+\thelmgit \"github.com/weaveworks/flux/integrations/helm/git\"\n+\tchartrelease \"github.com/weaveworks/flux/integrations/helm/release\"\n+)\n+\n+type action string\n+\n+const (\n+\tCustomResourceKind        = \"FluxHelmRelease\"\n+\treleaseLagTime            = 30\n+\tdeleteAction       action = \"DELETE\"\n+\tinstallAction      action = \"CREATE\"\n+\tupgradeAction      action = \"UPDATE\"\n+)\n+\n+type ReleaseFhr struct {\n+\tRelName string\n+\tFhrName string\n+\tFhr     ifv1.FluxHelmRelease\n+}\n+\n+type ReleaseChangeSync struct {\n+\tlogger log.Logger\n+\tchartsync.Polling\n+\tkubeClient kubernetes.Clientset\n+\tifClient   ifclientset.Clientset\n+\trelease    *chartrelease.Release\n+}\n+\n+func New(\n+\tlogger log.Logger, syncInterval time.Duration, syncTimeout time.Duration,\n+\tkubeClient kubernetes.Clientset, ifClient ifclientset.Clientset,\n+\trelease *chartrelease.Release) *ReleaseChangeSync {\n+\n+\treturn &ReleaseChangeSync{\n+\t\tlogger:     logger,\n+\t\tPolling:    chartsync.Polling{Interval: syncInterval, Timeout: syncTimeout},\n+\t\tkubeClient: kubeClient,\n+\t\tifClient:   ifClient,\n+\t\trelease:    release,\n+\t}\n+}\n+\n+type customResourceInfo struct {\n+\tname, releaseName string\n+\tresource          ifv1.FluxHelmRelease\n+\tlastUpdated       protobuf.Timestamp\n+}\n+\n+type chartRelease struct {\n+\treleaseName  string\n+\taction       action\n+\tdesiredState ifv1.FluxHelmRelease\n+}\n+\n+// Run ... creates a syncing loop monitoring repo chart changes\n+func (rs *ReleaseChangeSync) Run(stopCh <-chan struct{}, errc chan error, wg *sync.WaitGroup) {\n+\trs.logger.Log(\"info\", \"Starting repo charts sync loop\")\n+\n+\twg.Add(1)\n+\tgo func() {\n+\t\tdefer runtime.HandleCrash()\n+\t\tdefer wg.Done()\n+\t\tdefer rs.release.Repo.ReleasesSync.Cleanup()\n+\n+\t\ttime.Sleep(30 * time.Second)\n+\n+\t\tticker := time.NewTicker(rs.Polling.Interval)\n+\t\tdefer ticker.Stop()\n+\n+\t\tfor {\n+\t\t\tselect {\n+\t\t\t// ------------------------------------------------------------------------------------\n+\t\t\tcase <-ticker.C:\n+\t\t\t\trs.logger.Log(\"info\", fmt.Sprintf(\"Start of releasesync at %s\", time.Now().String()))\n+\t\t\t\tctx, cancel := context.WithTimeout(context.Background(), helmgit.DefaultCloneTimeout)\n+\t\t\t\trelsToSync, err := rs.releasesToSync(ctx)\n+\t\t\t\tcancel()\n+\t\t\t\tif err != nil {\n+\t\t\t\t\trs.logger.Log(\"error\", fmt.Sprintf(\"Failure to get info about manual chart release changes: %#v\", err))\n+\t\t\t\t\trs.logger.Log(\"info\", fmt.Sprintf(\"End of releasesync at %s\", time.Now().String()))\n+\t\t\t\t\tcontinue\n+\t\t\t\t}\n+\n+\t\t\t\t// manual chart release changes?\n+\t\t\t\tif len(relsToSync) == 0 {\n+\t\t\t\t\trs.logger.Log(\"info\", fmt.Sprint(\"No manual changes of Chart releases\"))\n+\t\t\t\t\trs.logger.Log(\"info\", fmt.Sprintf(\"End of releasesync at %s\", time.Now().String()))\n+\t\t\t\t\tcontinue\n+\t\t\t\t}\n+\n+\t\t\t\t// sync Chart releases\n+\t\t\t\tctx, cancel = context.WithTimeout(context.Background(), helmgit.DefaultCloneTimeout)\n+\t\t\t\terr = rs.sync(ctx, relsToSync)\n+\t\t\t\tcancel()\n+\t\t\t\tif err != nil {\n+\t\t\t\t\trs.logger.Log(\"error\", fmt.Sprintf(\"Failure to sync cluster after manual chart release changes: %#v\", err))\n+\t\t\t\t}\n+\t\t\t\trs.logger.Log(\"info\", fmt.Sprintf(\"End of releasesync at %s\", time.Now().String()))\n+\t\t\t// ------------------------------------------------------------------------------------\n+\t\t\tcase <-stopCh:\n+\t\t\t\trs.logger.Log(\"stopping\", \"true\")\n+\t\t\t\tbreak\n+\t\t\t}\n+\t\t}\n+\t}()\n+}\n+\n+func (rs *ReleaseChangeSync) getNSCustomResources(ns string) (*ifv1.FluxHelmReleaseList, error) {\n+\treturn rs.ifClient.HelmV1alpha().FluxHelmReleases(ns).List(metav1.ListOptions{})\n+}\n+\n+func (rs *ReleaseChangeSync) getNSEvents(ns string) (*v1.EventList, error) {\n+\treturn rs.kubeClient.CoreV1().Events(ns).List(metav1.ListOptions{})\n+}\n+\n+// getCustomResources retrieves FluxHelmRelease resources\n+//\t\tand outputs them organised by namespace and: Chart release name or Custom Resource name\n+//\t\t\t\t\t\tmap[namespace] = []ReleaseFhr\n+func (rs *ReleaseChangeSync) getCustomResources(namespaces []string) (map[string][]ReleaseFhr, error) {\n+\trelInfo := make(map[string][]ReleaseFhr)\n+\n+\tfor _, ns := range namespaces {\n+\t\tlist, err := rs.getNSCustomResources(ns)\n+\t\tif err != nil {\n+\t\t\trs.logger.Log(\"error\", fmt.Errorf(\"Failure while retrieving FluxHelmReleases in namespace %s: %v\", ns, err))\n+\t\t\treturn nil, err\n+\t\t}\n+\t\trf := []ReleaseFhr{}\n+\t\tfor _, fhr := range list.Items {\n+\t\t\trelName := release.GetReleaseName(fhr)\n+\t\t\trf = append(rf, ReleaseFhr{RelName: relName, Fhr: fhr})\n+\t\t}\n+\t\tif len(rf) > 0 {\n+\t\t\trelInfo[ns] = rf\n+\t\t}\n+\t}\n+\treturn relInfo, nil\n+}\n+\n+// GetCustomResourcesLastDeploy retrieves and stores last event timestamp for FluxHelmRelease resources (FHR)\n+// (Tamara: The method is not currently used. I am leaving it here as it will come handy for display on the UI)\n+//\t\toutput:\n+//             map[namespace][FHR name] = int64\n+func (rs *ReleaseChangeSync) GetCustomResourcesLastDeploy(namespaces []string) (map[string]map[string]int64, error) {\n+\trelEventsTime := make(map[string]map[string]int64)\n+\n+\tfor _, ns := range namespaces {\n+\t\teventList, err := rs.getNSEvents(ns)\n+\t\tif err != nil {\n+\t\t\treturn relEventsTime, err\n+\t\t}\n+\t\tfhrD := make(map[string]int64)\n+\t\tfor _, e := range eventList.Items {\n+\t\t\tif e.InvolvedObject.Kind == CustomResourceKind {\n+\t\t\t\tsecs := e.LastTimestamp.Unix()\n+\t\t\t\tfhrD[e.InvolvedObject.Name] = secs\n+\t\t\t}\n+\t\t}\n+\t\trelEventsTime[ns] = fhrD\n+\t}\n+\treturn relEventsTime, nil\n+}\n+\n+func (rs *ReleaseChangeSync) shouldUpgrade(currRel *hapi_release.Release, fhr ifv1.FluxHelmRelease) (bool, error) {\n+\tcurrVals := currRel.GetConfig().GetRaw()\n+\tcurrChart := currRel.GetChart().String()\n+\n+\t// Get the desired release state\n+\topts := chartrelease.InstallOptions{DryRun: true}\n+\ttempRelName := strings.Join([]string{currRel.GetName(), \"temp\"}, \"-\")\n+\tdesRel, err := rs.release.Install(rs.release.Repo.ReleasesSync, tempRelName, fhr, \"CREATE\", opts)\n+\tif err != nil {\n+\t\treturn false, err\n+\t}\n+\tdesVals := desRel.GetConfig().GetRaw()\n+\tdesChart := desRel.GetChart().String()\n+\n+\t// compare values && Chart\n+\tif strings.Compare(currVals, desVals) != 0 {\n+\t\trs.logger.Log(\"error\", fmt.Sprintf(\"Release %s: values have diverged due to manual Chart release\", currRel.GetName()))\n+\t\treturn true, nil\n+\t}\n+\tif strings.Compare(currChart, desChart) != 0 {\n+\t\trs.logger.Log(\"error\", fmt.Sprintf(\"Release %s: Chart has diverged due to manual Chart release\", currRel.GetName()))\n+\t\treturn true, nil\n+\t}\n+\n+\treturn false, nil\n+}\n+\n+// existingReleasesToSync determines which Chart releases need to be deleted/upgraded\n+// to bring the cluster to the desired state\n+func (rs *ReleaseChangeSync) existingReleasesToSync(\n+\tcurrentReleases map[string]map[string]int64,","path":"integrations/helm/releasesync/releasesync.go","position":221,"original_position":221,"commit_id":"d4413560ae39f9896d47ff447d8a41d7eee37a25","original_commit_id":"d4413560ae39f9896d47ff447d8a41d7eee37a25","user":{"login":"squaremo","id":62033,"avatar_url":"https://avatars3.githubusercontent.com/u/62033?v=4","gravatar_id":"","url":"https://api.github.com/users/squaremo","html_url":"https://github.com/squaremo","followers_url":"https://api.github.com/users/squaremo/followers","following_url":"https://api.github.com/users/squaremo/following{/other_user}","gists_url":"https://api.github.com/users/squaremo/gists{/gist_id}","starred_url":"https://api.github.com/users/squaremo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/squaremo/subscriptions","organizations_url":"https://api.github.com/users/squaremo/orgs","repos_url":"https://api.github.com/users/squaremo/repos","events_url":"https://api.github.com/users/squaremo/events{/privacy}","received_events_url":"https://api.github.com/users/squaremo/received_events","type":"User","site_admin":false},"body":"Is this `int64` ever examined?","created_at":"2018-04-06T11:09:14Z","updated_at":"2018-04-06T11:13:25Z","html_url":"https://github.com/weaveworks/flux/pull/1023#discussion_r179726227","pull_request_url":"https://api.github.com/repos/weaveworks/flux/pulls/1023","author_association":"OWNER","_links":{"self":{"href":"https://api.github.com/repos/weaveworks/flux/pulls/comments/179726227"},"html":{"href":"https://github.com/weaveworks/flux/pull/1023#discussion_r179726227"},"pull_request":{"href":"https://api.github.com/repos/weaveworks/flux/pulls/1023"}}},"pull_request":{"url":"https://api.github.com/repos/weaveworks/flux/pulls/1023","id":177399438,"html_url":"https://github.com/weaveworks/flux/pull/1023","diff_url":"https://github.com/weaveworks/flux/pull/1023.diff","patch_url":"https://github.com/weaveworks/flux/pull/1023.patch","issue_url":"https://api.github.com/repos/weaveworks/flux/issues/1023","number":1023,"state":"open","locked":false,"title":"Sync for manual Chart releases","user":{"login":"tamarakaufler","id":5575204,"avatar_url":"https://avatars1.githubusercontent.com/u/5575204?v=4","gravatar_id":"","url":"https://api.github.com/users/tamarakaufler","html_url":"https://github.com/tamarakaufler","followers_url":"https://api.github.com/users/tamarakaufler/followers","following_url":"https://api.github.com/users/tamarakaufler/following{/other_user}","gists_url":"https://api.github.com/users/tamarakaufler/gists{/gist_id}","starred_url":"https://api.github.com/users/tamarakaufler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tamarakaufler/subscriptions","organizations_url":"https://api.github.com/users/tamarakaufler/orgs","repos_url":"https://api.github.com/users/tamarakaufler/repos","events_url":"https://api.github.com/users/tamarakaufler/events{/privacy}","received_events_url":"https://api.github.com/users/tamarakaufler/received_events","type":"User","site_admin":false},"body":"When a Chart release is manually handled (upgraded/installed/deleted), the cluster gets out of sync with the desired state prescribed through Custom Resources.\r\n\r\nThe implementation regularly polls the cluster to check whether only expected Chart releases are running, all of them and no others. The existing Chart releases are checked for changes from the desired state described by their respective Custom Resources. Diversion from the expected state leads to an upgrade install of the changed release.\r\n\r\nFixes #980","created_at":"2018-03-26T10:12:26Z","updated_at":"2018-04-06T11:13:25Z","closed_at":null,"merged_at":null,"merge_commit_sha":"5c11dc4ef7e046a35139499dd4cb42384b8051d0","assignee":null,"assignees":[],"requested_reviewers":[{"login":"aaron7","id":903926,"avatar_url":"https://avatars2.githubusercontent.com/u/903926?v=4","gravatar_id":"","url":"https://api.github.com/users/aaron7","html_url":"https://github.com/aaron7","followers_url":"https://api.github.com/users/aaron7/followers","following_url":"https://api.github.com/users/aaron7/following{/other_user}","gists_url":"https://api.github.com/users/aaron7/gists{/gist_id}","starred_url":"https://api.github.com/users/aaron7/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aaron7/subscriptions","organizations_url":"https://api.github.com/users/aaron7/orgs","repos_url":"https://api.github.com/users/aaron7/repos","events_url":"https://api.github.com/users/aaron7/events{/privacy}","received_events_url":"https://api.github.com/users/aaron7/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/weaveworks/flux/pulls/1023/commits","review_comments_url":"https://api.github.com/repos/weaveworks/flux/pulls/1023/comments","review_comment_url":"https://api.github.com/repos/weaveworks/flux/pulls/comments{/number}","comments_url":"https://api.github.com/repos/weaveworks/flux/issues/1023/comments","statuses_url":"https://api.github.com/repos/weaveworks/flux/statuses/d4413560ae39f9896d47ff447d8a41d7eee37a25","head":{"label":"weaveworks:980-sync-for-manual-chart-releases","ref":"980-sync-for-manual-chart-releases","sha":"d4413560ae39f9896d47ff447d8a41d7eee37a25","user":{"login":"weaveworks","id":9976052,"avatar_url":"https://avatars1.githubusercontent.com/u/9976052?v=4","gravatar_id":"","url":"https://api.github.com/users/weaveworks","html_url":"https://github.com/weaveworks","followers_url":"https://api.github.com/users/weaveworks/followers","following_url":"https://api.github.com/users/weaveworks/following{/other_user}","gists_url":"https://api.github.com/users/weaveworks/gists{/gist_id}","starred_url":"https://api.github.com/users/weaveworks/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/weaveworks/subscriptions","organizations_url":"https://api.github.com/users/weaveworks/orgs","repos_url":"https://api.github.com/users/weaveworks/repos","events_url":"https://api.github.com/users/weaveworks/events{/privacy}","received_events_url":"https://api.github.com/users/weaveworks/received_events","type":"Organization","site_admin":false},"repo":{"id":62799996,"name":"flux","full_name":"weaveworks/flux","owner":{"login":"weaveworks","id":9976052,"avatar_url":"https://avatars1.githubusercontent.com/u/9976052?v=4","gravatar_id":"","url":"https://api.github.com/users/weaveworks","html_url":"https://github.com/weaveworks","followers_url":"https://api.github.com/users/weaveworks/followers","following_url":"https://api.github.com/users/weaveworks/following{/other_user}","gists_url":"https://api.github.com/users/weaveworks/gists{/gist_id}","starred_url":"https://api.github.com/users/weaveworks/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/weaveworks/subscriptions","organizations_url":"https://api.github.com/users/weaveworks/orgs","repos_url":"https://api.github.com/users/weaveworks/repos","events_url":"https://api.github.com/users/weaveworks/events{/privacy}","received_events_url":"https://api.github.com/users/weaveworks/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/weaveworks/flux","description":"A tool for deploying container images to Kubernetes services","fork":false,"url":"https://api.github.com/repos/weaveworks/flux","forks_url":"https://api.github.com/repos/weaveworks/flux/forks","keys_url":"https://api.github.com/repos/weaveworks/flux/keys{/key_id}","collaborators_url":"https://api.github.com/repos/weaveworks/flux/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/weaveworks/flux/teams","hooks_url":"https://api.github.com/repos/weaveworks/flux/hooks","issue_events_url":"https://api.github.com/repos/weaveworks/flux/issues/events{/number}","events_url":"https://api.github.com/repos/weaveworks/flux/events","assignees_url":"https://api.github.com/repos/weaveworks/flux/assignees{/user}","branches_url":"https://api.github.com/repos/weaveworks/flux/branches{/branch}","tags_url":"https://api.github.com/repos/weaveworks/flux/tags","blobs_url":"https://api.github.com/repos/weaveworks/flux/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/weaveworks/flux/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/weaveworks/flux/git/refs{/sha}","trees_url":"https://api.github.com/repos/weaveworks/flux/git/trees{/sha}","statuses_url":"https://api.github.com/repos/weaveworks/flux/statuses/{sha}","languages_url":"https://api.github.com/repos/weaveworks/flux/languages","stargazers_url":"https://api.github.com/repos/weaveworks/flux/stargazers","contributors_url":"https://api.github.com/repos/weaveworks/flux/contributors","subscribers_url":"https://api.github.com/repos/weaveworks/flux/subscribers","subscription_url":"https://api.github.com/repos/weaveworks/flux/subscription","commits_url":"https://api.github.com/repos/weaveworks/flux/commits{/sha}","git_commits_url":"https://api.github.com/repos/weaveworks/flux/git/commits{/sha}","comments_url":"https://api.github.com/repos/weaveworks/flux/comments{/number}","issue_comment_url":"https://api.github.com/repos/weaveworks/flux/issues/comments{/number}","contents_url":"https://api.github.com/repos/weaveworks/flux/contents/{+path}","compare_url":"https://api.github.com/repos/weaveworks/flux/compare/{base}...{head}","merges_url":"https://api.github.com/repos/weaveworks/flux/merges","archive_url":"https://api.github.com/repos/weaveworks/flux/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/weaveworks/flux/downloads","issues_url":"https://api.github.com/repos/weaveworks/flux/issues{/number}","pulls_url":"https://api.github.com/repos/weaveworks/flux/pulls{/number}","milestones_url":"https://api.github.com/repos/weaveworks/flux/milestones{/number}","notifications_url":"https://api.github.com/repos/weaveworks/flux/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/weaveworks/flux/labels{/name}","releases_url":"https://api.github.com/repos/weaveworks/flux/releases{/id}","deployments_url":"https://api.github.com/repos/weaveworks/flux/deployments","created_at":"2016-07-07T10:55:13Z","updated_at":"2018-04-05T10:18:29Z","pushed_at":"2018-04-05T10:18:33Z","git_url":"git://github.com/weaveworks/flux.git","ssh_url":"git@github.com:weaveworks/flux.git","clone_url":"https://github.com/weaveworks/flux.git","svn_url":"https://github.com/weaveworks/flux","homepage":"","size":6053,"stargazers_count":425,"watchers_count":425,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":48,"mirror_url":null,"archived":false,"open_issues_count":119,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0"},"forks":48,"open_issues":119,"watchers":425,"default_branch":"master"}},"base":{"label":"weaveworks:master","ref":"master","sha":"da1df26339d30a5e81be872c07bff7694db2007e","user":{"login":"weaveworks","id":9976052,"avatar_url":"https://avatars1.githubusercontent.com/u/9976052?v=4","gravatar_id":"","url":"https://api.github.com/users/weaveworks","html_url":"https://github.com/weaveworks","followers_url":"https://api.github.com/users/weaveworks/followers","following_url":"https://api.github.com/users/weaveworks/following{/other_user}","gists_url":"https://api.github.com/users/weaveworks/gists{/gist_id}","starred_url":"https://api.github.com/users/weaveworks/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/weaveworks/subscriptions","organizations_url":"https://api.github.com/users/weaveworks/orgs","repos_url":"https://api.github.com/users/weaveworks/repos","events_url":"https://api.github.com/users/weaveworks/events{/privacy}","received_events_url":"https://api.github.com/users/weaveworks/received_events","type":"Organization","site_admin":false},"repo":{"id":62799996,"name":"flux","full_name":"weaveworks/flux","owner":{"login":"weaveworks","id":9976052,"avatar_url":"https://avatars1.githubusercontent.com/u/9976052?v=4","gravatar_id":"","url":"https://api.github.com/users/weaveworks","html_url":"https://github.com/weaveworks","followers_url":"https://api.github.com/users/weaveworks/followers","following_url":"https://api.github.com/users/weaveworks/following{/other_user}","gists_url":"https://api.github.com/users/weaveworks/gists{/gist_id}","starred_url":"https://api.github.com/users/weaveworks/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/weaveworks/subscriptions","organizations_url":"https://api.github.com/users/weaveworks/orgs","repos_url":"https://api.github.com/users/weaveworks/repos","events_url":"https://api.github.com/users/weaveworks/events{/privacy}","received_events_url":"https://api.github.com/users/weaveworks/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/weaveworks/flux","description":"A tool for deploying container images to Kubernetes services","fork":false,"url":"https://api.github.com/repos/weaveworks/flux","forks_url":"https://api.github.com/repos/weaveworks/flux/forks","keys_url":"https://api.github.com/repos/weaveworks/flux/keys{/key_id}","collaborators_url":"https://api.github.com/repos/weaveworks/flux/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/weaveworks/flux/teams","hooks_url":"https://api.github.com/repos/weaveworks/flux/hooks","issue_events_url":"https://api.github.com/repos/weaveworks/flux/issues/events{/number}","events_url":"https://api.github.com/repos/weaveworks/flux/events","assignees_url":"https://api.github.com/repos/weaveworks/flux/assignees{/user}","branches_url":"https://api.github.com/repos/weaveworks/flux/branches{/branch}","tags_url":"https://api.github.com/repos/weaveworks/flux/tags","blobs_url":"https://api.github.com/repos/weaveworks/flux/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/weaveworks/flux/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/weaveworks/flux/git/refs{/sha}","trees_url":"https://api.github.com/repos/weaveworks/flux/git/trees{/sha}","statuses_url":"https://api.github.com/repos/weaveworks/flux/statuses/{sha}","languages_url":"https://api.github.com/repos/weaveworks/flux/languages","stargazers_url":"https://api.github.com/repos/weaveworks/flux/stargazers","contributors_url":"https://api.github.com/repos/weaveworks/flux/contributors","subscribers_url":"https://api.github.com/repos/weaveworks/flux/subscribers","subscription_url":"https://api.github.com/repos/weaveworks/flux/subscription","commits_url":"https://api.github.com/repos/weaveworks/flux/commits{/sha}","git_commits_url":"https://api.github.com/repos/weaveworks/flux/git/commits{/sha}","comments_url":"https://api.github.com/repos/weaveworks/flux/comments{/number}","issue_comment_url":"https://api.github.com/repos/weaveworks/flux/issues/comments{/number}","contents_url":"https://api.github.com/repos/weaveworks/flux/contents/{+path}","compare_url":"https://api.github.com/repos/weaveworks/flux/compare/{base}...{head}","merges_url":"https://api.github.com/repos/weaveworks/flux/merges","archive_url":"https://api.github.com/repos/weaveworks/flux/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/weaveworks/flux/downloads","issues_url":"https://api.github.com/repos/weaveworks/flux/issues{/number}","pulls_url":"https://api.github.com/repos/weaveworks/flux/pulls{/number}","milestones_url":"https://api.github.com/repos/weaveworks/flux/milestones{/number}","notifications_url":"https://api.github.com/repos/weaveworks/flux/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/weaveworks/flux/labels{/name}","releases_url":"https://api.github.com/repos/weaveworks/flux/releases{/id}","deployments_url":"https://api.github.com/repos/weaveworks/flux/deployments","created_at":"2016-07-07T10:55:13Z","updated_at":"2018-04-05T10:18:29Z","pushed_at":"2018-04-05T10:18:33Z","git_url":"git://github.com/weaveworks/flux.git","ssh_url":"git@github.com:weaveworks/flux.git","clone_url":"https://github.com/weaveworks/flux.git","svn_url":"https://github.com/weaveworks/flux","homepage":"","size":6053,"stargazers_count":425,"watchers_count":425,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":48,"mirror_url":null,"archived":false,"open_issues_count":119,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0"},"forks":48,"open_issues":119,"watchers":425,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/weaveworks/flux/pulls/1023"},"html":{"href":"https://github.com/weaveworks/flux/pull/1023"},"issue":{"href":"https://api.github.com/repos/weaveworks/flux/issues/1023"},"comments":{"href":"https://api.github.com/repos/weaveworks/flux/issues/1023/comments"},"review_comments":{"href":"https://api.github.com/repos/weaveworks/flux/pulls/1023/comments"},"review_comment":{"href":"https://api.github.com/repos/weaveworks/flux/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/weaveworks/flux/pulls/1023/commits"},"statuses":{"href":"https://api.github.com/repos/weaveworks/flux/statuses/d4413560ae39f9896d47ff447d8a41d7eee37a25"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"squaremo"}}