{"action":"created","comment":{"url":"https://api.github.com/repos/juju/gocharm/pulls/comments/36060697","id":36060697,"diff_hunk":"@@ -0,0 +1,439 @@\n+package httpservice\n+\n+import (\n+\t\"bytes\"\n+\t\"crypto/tls\"\n+\t\"encoding/json\"\n+\t\"fmt\"\n+\t\"io/ioutil\"\n+\t\"log\"\n+\t\"net\"\n+\t\"net/http\"\n+\t\"os\"\n+\t\"path/filepath\"\n+\t\"reflect\"\n+\t\"strconv\"\n+\t\"sync\"\n+\t\"time\"\n+\n+\t\"gopkg.in/errgo.v1\"\n+\t\"gopkg.in/tomb.v2\"\n+\n+\t\"github.com/juju/gocharm/charmbits/service\"\n+\t\"github.com/juju/gocharm/hook\"\n+)\n+\n+// server represents a running HTTP server.\n+type server struct {\n+\ttomb tomb.Tomb\n+\n+\thandlerInfo *handlerInfo\n+\thandler     Handler\n+\tstateDir    string\n+\n+\tmu    sync.Mutex\n+\tstate ServerState\n+\t// relationState holds the struct value with fields\n+\t// set by the setFieldValue argument to RegisterRelation\n+\t// calls.\n+\trelationState reflect.Value\n+\thttpListener  *handlerListener\n+\thttpsListener *handlerListener\n+}\n+\n+// ServerState holds the state of a server - all the\n+// parameters that might affect it.\n+//\n+// It is an implementation detail and only exposed\n+// because net/rpc requires it.\n+type ServerState struct {\n+\tHTTPPort  int\n+\tHTTPSPort int\n+\tCertPEM   string\n+\n+\t// Arg holds the value passed to Service.Start, marshaled as JSON.\n+\tArgData []byte\n+\n+\t// RelationValues holds an entry for each relation\n+\t// defined in the second argument of the handler function\n+\t// passed to Register.\n+\tRelationValues map[string][]byte\n+}\n+\n+func startServer(ctxt *service.Context, args []string, h *handlerInfo) (hook.Command, error) {\n+\tc, err := startServer0(ctxt, args, h)\n+\tif err != nil {\n+\t\treturn c, err\n+\t}\n+\treturn c, err\n+}\n+\n+// start starts the actual HTTP server running. It is called\n+// in service context, not hook context.\n+func startServer0(ctxt *service.Context, args []string, h *handlerInfo) (hook.Command, error) {\n+\tif len(args) != 1 {\n+\t\treturn nil, fmt.Errorf(\"need exactly one argument, got %q\", args)\n+\t}\n+\tsrv := &server{\n+\t\thandlerInfo: h,\n+\t\tstateDir:    args[0],\n+\t}\n+\tstate, err := srv.loadState()\n+\tif err != nil {\n+\t\treturn nil, fmt.Errorf(\"cannot load state: %v\", err)\n+\t}\n+\tvar fb Feedback\n+\tsrv.set(state, &fb)\n+\tfb.show()\n+\t// Fool net/rpc into thinking the type is exported, because\n+\t// we don't want to export the srv type from this package.\n+\ttype Srv struct {\n+\t\t*server\n+\t}\n+\trpcCmd, err := ctxt.ServeLocalRPC(Srv{srv})\n+\tif err != nil {\n+\t\treturn nil, errgo.Notef(err, \"serve local RPC failed\")\n+\t}\n+\tsrv.tomb.Go(func() error {\n+\t\tsrv.tomb.Kill(rpcCmd.Wait())\n+\t\treturn nil\n+\t})\n+\tsrv.tomb.Go(func() error {\n+\t\t<-srv.tomb.Dying()\n+\t\t// The command has been killed. Stop the RPC listener\n+\t\t// and close any current listeners to cause the handlers\n+\t\t// to terminate.\n+\t\trpcCmd.Kill()\n+\t\terr := rpcCmd.Wait()\n+\t\tif err != nil {\n+\t\t\terr = errgo.Notef(err, \"local RPC server\")\n+\t\t}\n+\n+\t\tsrv.mu.Lock()\n+\t\tdefer srv.mu.Unlock()\n+\t\tsrv.closeResources()\n+\t\treturn err\n+\t})\n+\treturn srv, nil\n+}\n+\n+// Kill implements gocharm.Command.Kill.\n+func (srv *server) Kill() {\n+\tsrv.tomb.Kill(nil)\n+}\n+\n+// Wait implements gocharm.Command.Wait.\n+func (srv *server) Wait() error {\n+\treturn srv.tomb.Wait()\n+}\n+\n+// Feedback is an implementation detail, exposed only because net/rpc requires it.\n+type Feedback struct {\n+\tWarnings []string\n+}\n+\n+// show shows all the warnings.\n+func (fb *Feedback) show() {\n+\tfor _, w := range fb.Warnings {\n+\t\tfmt.Fprintf(os.Stderr, \"warning: %v\\n\", w)\n+\t}\n+}\n+\n+// addError adds the given error to the list of warnings\n+// that will be reported as the result of an RPC call.\n+func (fb *Feedback) addError(err error) {\n+\tfb.Warnings = append(fb.Warnings, err.Error())\n+}\n+\n+// Changed implements an RPC server method that is called\n+// when a value changes in the charm.\n+func (srv *server) Set(p *ServerState, response *Feedback) error {\n+\tsrv.mu.Lock()\n+\tdefer srv.mu.Unlock()\n+\tsrv.set(*p, response)\n+\tsrv.state = *p\n+\t// Save the server state so that if it's started or the\n+\t// instance is rebooted, we can carry on as before.\n+\tif err := srv.saveState(srv.state); err != nil {\n+\t\treturn errgo.Notef(err, \"cannot save server state\")\n+\t}\n+\treturn nil\n+}\n+\n+func (srv *server) serveHTTP(port int, h http.Handler) error {\n+\taddr := \":\" + strconv.Itoa(port)\n+\tlistener, err := net.Listen(\"tcp\", addr)\n+\tif err != nil {\n+\t\treturn errgo.Newf(\"cannot listen on %s: %v\", addr, err)\n+\t}\n+\tserver := &http.Server{\n+\t\tAddr:    addr,\n+\t\tHandler: h,\n+\t}\n+\tsrv.httpListener = newHandlerListener(server, listener)\n+\treturn nil\n+}\n+\n+func (srv *server) serveHTTPS(port int, certPEM string, h http.Handler) error {\n+\tcertPEMBytes := []byte(certPEM)\n+\tcert, err := tls.X509KeyPair(certPEMBytes, certPEMBytes)\n+\tif err != nil {\n+\t\treturn errgo.Newf(\"cannot parse certificate: %v\", err)\n+\t}\n+\tconfig := &tls.Config{\n+\t\tNextProtos:   []string{\"http/1.1\"},\n+\t\tCertificates: []tls.Certificate{cert},\n+\t}\n+\taddr := \":\" + strconv.Itoa(port)\n+\tlistener, err := net.Listen(\"tcp\", addr)\n+\tif err != nil {\n+\t\treturn errgo.Newf(\"cannot listen on %s: %v\", addr, err)\n+\t}\n+\ttlsListener := tls.NewListener(\n+\t\ttcpKeepAliveListener{listener.(*net.TCPListener)},\n+\t\tconfig,\n+\t)\n+\tserver := &http.Server{\n+\t\tAddr:    addr,\n+\t\tHandler: h,\n+\t}\n+\tsrv.httpsListener = newHandlerListener(server, tlsListener)\n+\treturn nil\n+}\n+\n+func (srv *server) set(state ServerState, fb *Feedback) {\n+\tsrv.set0(state, fb)\n+\tlog.Printf(\"httpservice set -> %q\", fb.Warnings)","path":"charmbits/httpservice/server.go","position":206,"original_position":206,"commit_id":"8a44ab9adb51628298a8580762f5a3073124f004","original_commit_id":"8a44ab9adb51628298a8580762f5a3073124f004","user":{"login":"rogpeppe","id":66491,"avatar_url":"https://avatars.githubusercontent.com/u/66491?v=3","gravatar_id":"","url":"https://api.github.com/users/rogpeppe","html_url":"https://github.com/rogpeppe","followers_url":"https://api.github.com/users/rogpeppe/followers","following_url":"https://api.github.com/users/rogpeppe/following{/other_user}","gists_url":"https://api.github.com/users/rogpeppe/gists{/gist_id}","starred_url":"https://api.github.com/users/rogpeppe/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rogpeppe/subscriptions","organizations_url":"https://api.github.com/users/rogpeppe/orgs","repos_url":"https://api.github.com/users/rogpeppe/repos","events_url":"https://api.github.com/users/rogpeppe/events{/privacy}","received_events_url":"https://api.github.com/users/rogpeppe/received_events","type":"User","site_admin":false},"body":"So that I can show the warnings regardless of when it returns. I've added the show-warnings logic to the caller instead and ditched set0.","created_at":"2015-08-03T07:14:18Z","updated_at":"2015-08-03T07:14:18Z","html_url":"https://github.com/juju/gocharm/pull/49#discussion_r36060697","pull_request_url":"https://api.github.com/repos/juju/gocharm/pulls/49","_links":{"self":{"href":"https://api.github.com/repos/juju/gocharm/pulls/comments/36060697"},"html":{"href":"https://github.com/juju/gocharm/pull/49#discussion_r36060697"},"pull_request":{"href":"https://api.github.com/repos/juju/gocharm/pulls/49"}}},"pull_request":{"url":"https://api.github.com/repos/juju/gocharm/pulls/49","id":41263502,"html_url":"https://github.com/juju/gocharm/pull/49","diff_url":"https://github.com/juju/gocharm/pull/49.diff","patch_url":"https://github.com/juju/gocharm/pull/49.patch","issue_url":"https://api.github.com/repos/juju/gocharm/issues/49","number":49,"state":"open","locked":false,"title":"charmbits/httpservice: dependency injection","user":{"login":"rogpeppe","id":66491,"avatar_url":"https://avatars.githubusercontent.com/u/66491?v=3","gravatar_id":"","url":"https://api.github.com/users/rogpeppe","html_url":"https://github.com/rogpeppe","followers_url":"https://api.github.com/users/rogpeppe/followers","following_url":"https://api.github.com/users/rogpeppe/following{/other_user}","gists_url":"https://api.github.com/users/rogpeppe/gists{/gist_id}","starred_url":"https://api.github.com/users/rogpeppe/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rogpeppe/subscriptions","organizations_url":"https://api.github.com/users/rogpeppe/orgs","repos_url":"https://api.github.com/users/rogpeppe/repos","events_url":"https://api.github.com/users/rogpeppe/events{/privacy}","received_events_url":"https://api.github.com/users/rogpeppe/received_events","type":"User","site_admin":false},"body":"This makes it possible to write HTTP servers where all\r\nthe juju relation logic for the required relations is implied\r\nby the type of a field in the argument to the handler.\r\n\r\nSo, for instance, if I want a charm that implements an HTTP\r\nhandler that requires a MongoDB connection, I can define\r\na handler creation function like:\r\n\r\n     type relations struct {\r\n         Session *mgo.Session\r\n     }\r\n     func newHandler(args struct{}, rel *relations) (httpservice.Handler, error) {\r\n          ... I can use rel.Session how I like here.\r\n     }\r\n\r\nand register it as an HTTP server with:\r\n\r\n      var svc httpservice.Service\r\n      svc.Register(r.Clone(\"svc\"), \"httpservicename\", \"http\", newHandler)\r\n\r\nThe handler will be created and destroyed as necessary when HTTP ports,\r\netc change, and when the relations change too.\r\n\r\nThis PR also fixes httpservice so that its server is long running - listeners\r\nare started and stopped within the same process rather than (as previously)\r\nrewriting the upstart file each time a service's parameters change.\r\n\r\nIt also adds mongodbrelation as an example and adds tests that it actually\r\nworks as advertised.","created_at":"2015-07-30T18:04:44Z","updated_at":"2015-08-03T07:14:18Z","closed_at":null,"merged_at":null,"merge_commit_sha":"586a8f2484603f391e32bc7d25b698d3e70385a7","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/juju/gocharm/pulls/49/commits","review_comments_url":"https://api.github.com/repos/juju/gocharm/pulls/49/comments","review_comment_url":"https://api.github.com/repos/juju/gocharm/pulls/comments{/number}","comments_url":"https://api.github.com/repos/juju/gocharm/issues/49/comments","statuses_url":"https://api.github.com/repos/juju/gocharm/statuses/8a44ab9adb51628298a8580762f5a3073124f004","head":{"label":"rogpeppe:039-dependency-injection","ref":"039-dependency-injection","sha":"8a44ab9adb51628298a8580762f5a3073124f004","user":{"login":"rogpeppe","id":66491,"avatar_url":"https://avatars.githubusercontent.com/u/66491?v=3","gravatar_id":"","url":"https://api.github.com/users/rogpeppe","html_url":"https://github.com/rogpeppe","followers_url":"https://api.github.com/users/rogpeppe/followers","following_url":"https://api.github.com/users/rogpeppe/following{/other_user}","gists_url":"https://api.github.com/users/rogpeppe/gists{/gist_id}","starred_url":"https://api.github.com/users/rogpeppe/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rogpeppe/subscriptions","organizations_url":"https://api.github.com/users/rogpeppe/orgs","repos_url":"https://api.github.com/users/rogpeppe/repos","events_url":"https://api.github.com/users/rogpeppe/events{/privacy}","received_events_url":"https://api.github.com/users/rogpeppe/received_events","type":"User","site_admin":false},"repo":{"id":26325710,"name":"gocharm","full_name":"rogpeppe/gocharm","owner":{"login":"rogpeppe","id":66491,"avatar_url":"https://avatars.githubusercontent.com/u/66491?v=3","gravatar_id":"","url":"https://api.github.com/users/rogpeppe","html_url":"https://github.com/rogpeppe","followers_url":"https://api.github.com/users/rogpeppe/followers","following_url":"https://api.github.com/users/rogpeppe/following{/other_user}","gists_url":"https://api.github.com/users/rogpeppe/gists{/gist_id}","starred_url":"https://api.github.com/users/rogpeppe/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rogpeppe/subscriptions","organizations_url":"https://api.github.com/users/rogpeppe/orgs","repos_url":"https://api.github.com/users/rogpeppe/repos","events_url":"https://api.github.com/users/rogpeppe/events{/privacy}","received_events_url":"https://api.github.com/users/rogpeppe/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/rogpeppe/gocharm","description":"Write your charms in Go!","fork":true,"url":"https://api.github.com/repos/rogpeppe/gocharm","forks_url":"https://api.github.com/repos/rogpeppe/gocharm/forks","keys_url":"https://api.github.com/repos/rogpeppe/gocharm/keys{/key_id}","collaborators_url":"https://api.github.com/repos/rogpeppe/gocharm/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/rogpeppe/gocharm/teams","hooks_url":"https://api.github.com/repos/rogpeppe/gocharm/hooks","issue_events_url":"https://api.github.com/repos/rogpeppe/gocharm/issues/events{/number}","events_url":"https://api.github.com/repos/rogpeppe/gocharm/events","assignees_url":"https://api.github.com/repos/rogpeppe/gocharm/assignees{/user}","branches_url":"https://api.github.com/repos/rogpeppe/gocharm/branches{/branch}","tags_url":"https://api.github.com/repos/rogpeppe/gocharm/tags","blobs_url":"https://api.github.com/repos/rogpeppe/gocharm/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/rogpeppe/gocharm/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/rogpeppe/gocharm/git/refs{/sha}","trees_url":"https://api.github.com/repos/rogpeppe/gocharm/git/trees{/sha}","statuses_url":"https://api.github.com/repos/rogpeppe/gocharm/statuses/{sha}","languages_url":"https://api.github.com/repos/rogpeppe/gocharm/languages","stargazers_url":"https://api.github.com/repos/rogpeppe/gocharm/stargazers","contributors_url":"https://api.github.com/repos/rogpeppe/gocharm/contributors","subscribers_url":"https://api.github.com/repos/rogpeppe/gocharm/subscribers","subscription_url":"https://api.github.com/repos/rogpeppe/gocharm/subscription","commits_url":"https://api.github.com/repos/rogpeppe/gocharm/commits{/sha}","git_commits_url":"https://api.github.com/repos/rogpeppe/gocharm/git/commits{/sha}","comments_url":"https://api.github.com/repos/rogpeppe/gocharm/comments{/number}","issue_comment_url":"https://api.github.com/repos/rogpeppe/gocharm/issues/comments{/number}","contents_url":"https://api.github.com/repos/rogpeppe/gocharm/contents/{+path}","compare_url":"https://api.github.com/repos/rogpeppe/gocharm/compare/{base}...{head}","merges_url":"https://api.github.com/repos/rogpeppe/gocharm/merges","archive_url":"https://api.github.com/repos/rogpeppe/gocharm/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/rogpeppe/gocharm/downloads","issues_url":"https://api.github.com/repos/rogpeppe/gocharm/issues{/number}","pulls_url":"https://api.github.com/repos/rogpeppe/gocharm/pulls{/number}","milestones_url":"https://api.github.com/repos/rogpeppe/gocharm/milestones{/number}","notifications_url":"https://api.github.com/repos/rogpeppe/gocharm/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/rogpeppe/gocharm/labels{/name}","releases_url":"https://api.github.com/repos/rogpeppe/gocharm/releases{/id}","created_at":"2014-11-07T15:37:22Z","updated_at":"2014-11-07T13:56:22Z","pushed_at":"2015-07-30T17:55:38Z","git_url":"git://github.com/rogpeppe/gocharm.git","ssh_url":"git@github.com:rogpeppe/gocharm.git","clone_url":"https://github.com/rogpeppe/gocharm.git","svn_url":"https://github.com/rogpeppe/gocharm","homepage":null,"size":3280,"stargazers_count":0,"watchers_count":0,"language":null,"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":"juju:master","ref":"master","sha":"1ae190ed6c414d55d3313cef18a64127632d0fca","user":{"login":"juju","id":4604548,"avatar_url":"https://avatars.githubusercontent.com/u/4604548?v=3","gravatar_id":"","url":"https://api.github.com/users/juju","html_url":"https://github.com/juju","followers_url":"https://api.github.com/users/juju/followers","following_url":"https://api.github.com/users/juju/following{/other_user}","gists_url":"https://api.github.com/users/juju/gists{/gist_id}","starred_url":"https://api.github.com/users/juju/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/juju/subscriptions","organizations_url":"https://api.github.com/users/juju/orgs","repos_url":"https://api.github.com/users/juju/repos","events_url":"https://api.github.com/users/juju/events{/privacy}","received_events_url":"https://api.github.com/users/juju/received_events","type":"Organization","site_admin":false},"repo":{"id":26321263,"name":"gocharm","full_name":"juju/gocharm","owner":{"login":"juju","id":4604548,"avatar_url":"https://avatars.githubusercontent.com/u/4604548?v=3","gravatar_id":"","url":"https://api.github.com/users/juju","html_url":"https://github.com/juju","followers_url":"https://api.github.com/users/juju/followers","following_url":"https://api.github.com/users/juju/following{/other_user}","gists_url":"https://api.github.com/users/juju/gists{/gist_id}","starred_url":"https://api.github.com/users/juju/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/juju/subscriptions","organizations_url":"https://api.github.com/users/juju/orgs","repos_url":"https://api.github.com/users/juju/repos","events_url":"https://api.github.com/users/juju/events{/privacy}","received_events_url":"https://api.github.com/users/juju/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/juju/gocharm","description":"Write your charms in Go!","fork":false,"url":"https://api.github.com/repos/juju/gocharm","forks_url":"https://api.github.com/repos/juju/gocharm/forks","keys_url":"https://api.github.com/repos/juju/gocharm/keys{/key_id}","collaborators_url":"https://api.github.com/repos/juju/gocharm/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/juju/gocharm/teams","hooks_url":"https://api.github.com/repos/juju/gocharm/hooks","issue_events_url":"https://api.github.com/repos/juju/gocharm/issues/events{/number}","events_url":"https://api.github.com/repos/juju/gocharm/events","assignees_url":"https://api.github.com/repos/juju/gocharm/assignees{/user}","branches_url":"https://api.github.com/repos/juju/gocharm/branches{/branch}","tags_url":"https://api.github.com/repos/juju/gocharm/tags","blobs_url":"https://api.github.com/repos/juju/gocharm/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/juju/gocharm/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/juju/gocharm/git/refs{/sha}","trees_url":"https://api.github.com/repos/juju/gocharm/git/trees{/sha}","statuses_url":"https://api.github.com/repos/juju/gocharm/statuses/{sha}","languages_url":"https://api.github.com/repos/juju/gocharm/languages","stargazers_url":"https://api.github.com/repos/juju/gocharm/stargazers","contributors_url":"https://api.github.com/repos/juju/gocharm/contributors","subscribers_url":"https://api.github.com/repos/juju/gocharm/subscribers","subscription_url":"https://api.github.com/repos/juju/gocharm/subscription","commits_url":"https://api.github.com/repos/juju/gocharm/commits{/sha}","git_commits_url":"https://api.github.com/repos/juju/gocharm/git/commits{/sha}","comments_url":"https://api.github.com/repos/juju/gocharm/comments{/number}","issue_comment_url":"https://api.github.com/repos/juju/gocharm/issues/comments{/number}","contents_url":"https://api.github.com/repos/juju/gocharm/contents/{+path}","compare_url":"https://api.github.com/repos/juju/gocharm/compare/{base}...{head}","merges_url":"https://api.github.com/repos/juju/gocharm/merges","archive_url":"https://api.github.com/repos/juju/gocharm/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/juju/gocharm/downloads","issues_url":"https://api.github.com/repos/juju/gocharm/issues{/number}","pulls_url":"https://api.github.com/repos/juju/gocharm/pulls{/number}","milestones_url":"https://api.github.com/repos/juju/gocharm/milestones{/number}","notifications_url":"https://api.github.com/repos/juju/gocharm/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/juju/gocharm/labels{/name}","releases_url":"https://api.github.com/repos/juju/gocharm/releases{/id}","created_at":"2014-11-07T13:40:48Z","updated_at":"2015-04-29T13:14:03Z","pushed_at":"2015-07-30T18:04:44Z","git_url":"git://github.com/juju/gocharm.git","ssh_url":"git@github.com:juju/gocharm.git","clone_url":"https://github.com/juju/gocharm.git","svn_url":"https://github.com/juju/gocharm","homepage":null,"size":3273,"stargazers_count":8,"watchers_count":8,"language":"Go","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":7,"mirror_url":null,"open_issues_count":4,"forks":7,"open_issues":4,"watchers":8,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/juju/gocharm/pulls/49"},"html":{"href":"https://github.com/juju/gocharm/pull/49"},"issue":{"href":"https://api.github.com/repos/juju/gocharm/issues/49"},"comments":{"href":"https://api.github.com/repos/juju/gocharm/issues/49/comments"},"review_comments":{"href":"https://api.github.com/repos/juju/gocharm/pulls/49/comments"},"review_comment":{"href":"https://api.github.com/repos/juju/gocharm/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/juju/gocharm/pulls/49/commits"},"statuses":{"href":"https://api.github.com/repos/juju/gocharm/statuses/8a44ab9adb51628298a8580762f5a3073124f004"}}}}