{"action":"created","comment":{"url":"https://api.github.com/repos/kubernetes-incubator/external-storage/pulls/comments/225826371","pull_request_review_id":165509162,"id":225826371,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIyNTgyNjM3MQ==","diff_hunk":"@@ -0,0 +1,608 @@\n+/*\n+Copyright 2018 The Kubernetes Authors.\n+\n+Licensed under the Apache License, Version 2.0 (the \"License\");\n+you may not use this file except in compliance with the License.\n+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, software\n+distributed under the License is distributed on an \"AS IS\" BASIS,\n+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+See the License for the specific language governing permissions and\n+limitations under the License.\n+*/\n+\n+package main\n+\n+import (\n+\t\"flag\"\n+\t\"fmt\"\n+\t\"os\"\n+\t\"os/exec\"\n+\t\"strconv\"\n+\t\"strings\"\n+\t\"syscall\"\n+\n+\t\"github.com/containerd/continuity/fs\"\n+\t\"github.com/golang/glog\"\n+\t\"github.com/kubernetes-incubator/external-storage/lib/controller\"\n+\t\"github.com/kubernetes-incubator/external-storage/lib/gidallocator\"\n+\t\"github.com/kubernetes-incubator/external-storage/lib/util\"\n+\t\"k8s.io/api/core/v1\"\n+\t\"k8s.io/apimachinery/pkg/api/errors\"\n+\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n+\t\"k8s.io/apimachinery/pkg/types\"\n+\t\"k8s.io/apimachinery/pkg/util/wait\"\n+\t\"k8s.io/client-go/kubernetes\"\n+\t\"k8s.io/client-go/rest\"\n+\t\"k8s.io/client-go/tools/clientcmd\"\n+\t\"k8s.io/client-go/util/retry\"\n+)\n+\n+const (\n+\tprovisionerName    = \"gluster.org/gluster-subvol\"\n+\tprovisionerNameKey = \"PROVISIONER_NAME\"\n+\tdescAnn            = \"Gluster-subvol: Dynamically provisioned PV\"\n+\tdynamicEpSvcPrefix = \"gluster-subvol-dynamic-\"\n+\tglusterTypeAnn     = \"gluster.org/type\"\n+\tparentPVCAnn       = \"gluster.org/parentpvc\"\n+\tgidAnn             = \"pv.beta.kubernetes.io/gid\"\n+\tworkdir            = \"/var/run/gluster-subvol\"\n+\n+\t// CloneRequestAnn is an annotation to request that the PVC be provisioned as a clone of the referenced PVC\n+\tCloneRequestAnn = \"k8s.io/CloneRequest\"\n+\n+\t// CloneOfAnn is an annotation to indicate that a PVC is a clone of the referenced PVC\n+\tCloneOfAnn = \"k8s.io/CloneOf\"\n+)\n+\n+type glusterSubvolProvisioner struct {\n+\tclient    kubernetes.Interface\n+\tidentity  string\n+\tallocator gidallocator.Allocator\n+\tmtab      map[string]string\n+}\n+\n+var _ controller.Provisioner = &glusterSubvolProvisioner{}\n+\n+var accessModes = []v1.PersistentVolumeAccessMode{\n+\tv1.ReadWriteMany,\n+\tv1.ReadOnlyMany,\n+\tv1.ReadWriteOnce,\n+}\n+\n+func newGlusterSubvolProvisioner(client kubernetes.Interface, id string) (controller.Provisioner, error) {\n+\tp := &glusterSubvolProvisioner{\n+\t\tclient:    client,\n+\t\tidentity:  id,\n+\t\tallocator: gidallocator.New(client),\n+\t\tmtab:      make(map[string]string),\n+\t}\n+\n+\treturn p, nil\n+}\n+\n+func (p *glusterSubvolProvisioner) getPVC(ns string, name string) (*v1.PersistentVolumeClaim, error) {\n+\treturn p.client.CoreV1().PersistentVolumeClaims(ns).Get(name, metav1.GetOptions{})\n+}\n+\n+func (p *glusterSubvolProvisioner) annotatePVC(ns, pvc string, updates map[string]string) error {\n+\t// Retrieve the latest version of PVC before attempting update\n+\t// RetryOnConflict uses exponential backoff to avoid exhausting the apiserver\n+\tretryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {\n+\t\tresult, getErr := p.getPVC(ns, pvc)\n+\t\tif getErr != nil {\n+\t\t\tpanic(fmt.Errorf(\"Failed to get latest version of PVC %s/%s: %s\", ns, pvc, getErr))\n+\t\t}\n+\n+\t\tfor k, v := range updates {\n+\t\t\tresult.Annotations[k] = v\n+\t\t}\n+\t\t_, updateErr := p.client.CoreV1().PersistentVolumeClaims(ns).Update(result)\n+\t\treturn updateErr\n+\t})\n+\tif retryErr != nil {\n+\t\tglog.Errorf(\"Update of PVC %s/%s failed: %s\", ns, pvc, retryErr)\n+\t\treturn retryErr\n+\t}\n+\treturn nil\n+}\n+\n+// mount the given PV if not mounted yet, return the mountpoint or an error\n+func (p *glusterSubvolProvisioner) mountPV(ns string, pv *v1.PersistentVolume) (string, error) {\n+\t// check if not mounted yet\n+\tmountpoint, mounted := p.mtab[pv.Name]\n+\tif mounted {\n+\t\t// mounted already\n+\t\treturn mountpoint, nil\n+\t}\n+\n+\t// create the missing mountpoint\n+\tmountpoint = workdir + \"/\" + pv.Name\n+\tsb, err := os.Stat(mountpoint)\n+\tif err != nil {\n+\t\t// mountpoint does not exist yet, create it\n+\t\tglog.Infof(\"mountpoint %s for PV %s does not exist yet? Error: %s\", mountpoint, pv.Name, err)\n+\n+\t\terr = os.MkdirAll(mountpoint, 0775)\n+\t\tif err != nil {\n+\t\t\treturn \"\", fmt.Errorf(\"failed to create mountpoint %s for PV %s: %s\", mountpoint, pv.Name, err)\n+\t\t}\n+\t} else if !sb.Mode().IsDir() {\n+\t\t// mountpoint should be a directory, but it is not?!\n+\t\treturn \"\", fmt.Errorf(\"mountpoint %s for PV %s is not a directory?\", mountpoint, pv.Name)\n+\t}\n+\n+\t// get the name of the supervol to mount\n+\tsupervol := pv.Spec.Glusterfs.Path\n+\n+\t// get the mount options for the supervol\n+\tvar mountOpts string\n+\tif len(pv.Spec.MountOptions) != 0 {\n+\t\tmountOpts = strings.Join(pv.Spec.MountOptions, \",\")\n+\t}\n+\n+\t// Gluster Storage servers\n+\tep, err := p.client.CoreV1().Endpoints(ns).Get(pv.Spec.Glusterfs.EndpointsName, metav1.GetOptions{})\n+\tif err != nil {\n+\t\treturn \"\", fmt.Errorf(\"endpoint %s for PV %s count not be found: %s\", pv.Spec.Glusterfs.EndpointsName, pv.Name, err)\n+\t}\n+\n+\t// add the additional servers as backup-volfile-servers to the mountOpts\n+\tif len(ep.Subsets[0].Addresses) > 1 {\n+\t\tvar backupVolfileServers string\n+\t\tfor _, addr := range ep.Subsets[0].Addresses[1:] {\n+\t\t\tif backupVolfileServers != \"\" {\n+\t\t\t\tbackupVolfileServers = backupVolfileServers + \":\"\n+\t\t\t}\n+\t\t\tbackupVolfileServers = backupVolfileServers + addr.IP\n+\t\t}\n+\n+\t\tif mountOpts != \"\" {\n+\t\t\tmountOpts = mountOpts + \",\"\n+\t\t}\n+\t\tmountOpts = mountOpts + \"backup-volfile-servers=\" + backupVolfileServers\n+\t}\n+\n+\tmountCmd := make([]string, 0, 7)\n+\tmountCmd = append(mountCmd, \"/bin/mount\", \"-t\", \"glusterfs\")\n+\n+\t// don't forget the \"-o\" option before the mount options string\n+\tif mountOpts != \"\" {\n+\t\tmountOpts = \"-o \" + mountOpts\n+\t\tmountCmd = append(mountCmd, \"-o\", mountOpts)\n+\t}\n+\n+\tmountSource := fmt.Sprintf(\"%s:%s\", ep.Subsets[0].Addresses[0].IP, supervol)\n+\n+\tmountCmd = append(mountCmd, mountSource, mountpoint)\n+\tglog.Infof(\"going to mount supervol %s for PV %s: %s\", supervol, pv.Name, mountCmd)\n+\n+\t// os.StartProcess() does not allow deamons (fuse) to continue, using\n+\t// exec.Command instead.\n+\tcmd := exec.Command(mountCmd[0], mountCmd[1:]...)\n+\terr = cmd.Start()\n+\tif err != nil {\n+\t\tglog.Errorf(\"failed to mount supervol %s: %s\", supervol, err)\n+\t\treturn \"\", err\n+\t}\n+\n+\terr = cmd.Wait()\n+\tif err != nil {\n+\t\treturn \"\", fmt.Errorf(\"failed to mount supervol %s, process exited with %v\", supervol, err)\n+\t}\n+\n+\tp.mtab[pv.Name] = mountpoint\n+\n+\treturn mountpoint, nil\n+}\n+\n+func (p *glusterSubvolProvisioner) makeSubvolPath(mountpoint, pvcNS string, pvcUID types.UID) string {\n+\treturn fmt.Sprintf(\"%s/%s/pvc-%s\", mountpoint, pvcNS, pvcUID)\n+}\n+\n+func (p *glusterSubvolProvisioner) makeMountPath(supervolPV *v1.PersistentVolume, pvcNS string, pvcUID types.UID) string {\n+\treturn fmt.Sprintf(\"%s/%s/pvc-%s\", supervolPV.Spec.Glusterfs.Path, pvcNS, pvcUID)\n+}\n+\n+func (p *glusterSubvolProvisioner) copyEndpoints(sourceNS string, sourcePV *v1.PersistentVolume, destNS string, destPVCName string) (*v1.Endpoints, error) {\n+\t// Need to copy the endpoints from the supervol to the new PVC. A\n+\t// reference of the endpoints name is not sufficient, it can be in an\n+\t// other namespace.\n+\tsourceEP, err := p.client.CoreV1().Endpoints(sourceNS).Get(sourcePV.Spec.Glusterfs.EndpointsName, metav1.GetOptions{})\n+\tif err != nil {\n+\t\treturn nil, fmt.Errorf(\"endpoint %s for source PV %s can not be found: %s\", sourcePV.Spec.Glusterfs.EndpointsName, sourcePV.Name, err)\n+\t}\n+\n+\tep := sourceEP.DeepCopy()\n+\tep.ObjectMeta = metav1.ObjectMeta{\n+\t\tNamespace: destNS,\n+\t\tName:      dynamicEpSvcPrefix + destPVCName,\n+\t\tLabels: map[string]string{\n+\t\t\t\"gluster.org/provisioned-for-pvc\": destPVCName,\n+\t\t},\n+\t}\n+\n+\t_, err = p.client.CoreV1().Endpoints(destNS).Create(ep)\n+\tif err != nil && errors.IsAlreadyExists(err) {\n+\t\tglog.V(1).Infof(\"endpoint %s already exist in namespace %s, that is ok\", destPVCName, destNS)\n+\t\terr = nil\n+\t} else if err != nil {\n+\t\treturn nil, fmt.Errorf(\"failed to create endpoint %s: %s\", ep.ObjectMeta.Name, err)\n+\t}\n+\n+\treturn ep, nil\n+}\n+\n+// Pre-check for the requirements of the PVC-request and the supervol PVC.\n+// - certain features of the PVC-request may not be supported\n+// - the supervol PVC has some requirements that it needs to fulfil\n+func (p *glusterSubvolProvisioner) validateProvisionRequirements(pvcReq, supervolPVC *v1.PersistentVolumeClaim) error {\n+\tif pvcReq.Spec.Selector != nil {\n+\t\treturn fmt.Errorf(\"claim Selector is not supported\")\n+\t}\n+\n+\tif !util.AccessModesContainedInAll(accessModes, pvcReq.Spec.AccessModes) {\n+\t\treturn fmt.Errorf(\"invalid AccessModes %v: only AccessModes %v are supported\", pvcReq.Spec.AccessModes, accessModes)\n+\t}\n+\n+\t// check permission mode, the PV should be RWX as it is mounted here,\n+\t// and we'll create subdirs as new PVCs\n+\tif !util.AccessModesContains(supervolPVC.Spec.AccessModes, v1.ReadWriteMany) {\n+\t\treturn fmt.Errorf(\"this provisioner requires the PVC %s/%s to have ReadWriteMany permissions\", supervolPVC.Namespace, supervolPVC.Name)\n+\t}\n+\n+\treturn nil\n+}\n+\n+// Check if the PVC has a CloneRequest annotation and try to clone if it has.\n+// This returns the source namespace/PVC that got cloned, an error if something fails.\n+func (p *glusterSubvolProvisioner) tryClone(pvc *v1.PersistentVolumeClaim, mountpoint, destDir string) (string, error) {\n+\t// sourcePVCRef points to the PVC that should get cloned\n+\tsourcePVCRef, ok := pvc.Annotations[CloneRequestAnn]\n+\tif !ok || sourcePVCRef == \"\" {\n+\t\t// no CloneRequest, no need to try and clone\n+\t\treturn \"\", nil\n+\t}\n+\n+\tglog.Infof(\"got requested to clone %s for PVC %s/%s\", sourcePVCRef, pvc.Namespace, pvc.Name)\n+\n+\t// sourcePVCRef is like (namespace/)?pvc\n+\tvar sourceNS, sourcePVCName string\n+\n+\tparts := strings.Split(sourcePVCRef, \"/\")\n+\tif len(parts) == 1 {\n+\t\tsourceNS = pvc.Namespace\n+\t\tsourcePVCName = parts[0]\n+\t} else if len(parts) == 2 {\n+\t\tsourceNS = parts[0]\n+\t\tsourcePVCName = parts[1]\n+\t} else {\n+\t\treturn \"\", fmt.Errorf(\"failed to parse namespace/pvc from %s\", sourcePVCRef)\n+\t}\n+\n+\t// TODO: check if the size of the PVC >= source","path":"gluster/subvol/cmd/gluster-subvol-provisioner/gluster-subvol-provisioner.go","position":286,"original_position":286,"commit_id":"af4f86d09f13a29bbd99c891a39ec28101078ec8","original_commit_id":"af4f86d09f13a29bbd99c891a39ec28101078ec8","user":{"login":"nixpanic","id":400257,"node_id":"MDQ6VXNlcjQwMDI1Nw==","avatar_url":"https://avatars3.githubusercontent.com/u/400257?v=4","gravatar_id":"","url":"https://api.github.com/users/nixpanic","html_url":"https://github.com/nixpanic","followers_url":"https://api.github.com/users/nixpanic/followers","following_url":"https://api.github.com/users/nixpanic/following{/other_user}","gists_url":"https://api.github.com/users/nixpanic/gists{/gist_id}","starred_url":"https://api.github.com/users/nixpanic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nixpanic/subscriptions","organizations_url":"https://api.github.com/users/nixpanic/orgs","repos_url":"https://api.github.com/users/nixpanic/repos","events_url":"https://api.github.com/users/nixpanic/events{/privacy}","received_events_url":"https://api.github.com/users/nixpanic/received_events","type":"User","site_admin":false},"body":"Oh, this TODO is actually done. See https://github.com/kubernetes-incubator/external-storage/pull/1013/files#diff-066351a7c1b20ad6a21c9ad39e1257e4R375 line 375. I'll drop it for the next updated version of this PR.","created_at":"2018-10-17T08:28:50Z","updated_at":"2018-10-17T08:28:50Z","html_url":"https://github.com/kubernetes-incubator/external-storage/pull/1013#discussion_r225826371","pull_request_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/pulls/1013","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/kubernetes-incubator/external-storage/pulls/comments/225826371"},"html":{"href":"https://github.com/kubernetes-incubator/external-storage/pull/1013#discussion_r225826371"},"pull_request":{"href":"https://api.github.com/repos/kubernetes-incubator/external-storage/pulls/1013"}},"in_reply_to_id":225792678},"pull_request":{"url":"https://api.github.com/repos/kubernetes-incubator/external-storage/pulls/1013","id":220187449,"node_id":"MDExOlB1bGxSZXF1ZXN0MjIwMTg3NDQ5","html_url":"https://github.com/kubernetes-incubator/external-storage/pull/1013","diff_url":"https://github.com/kubernetes-incubator/external-storage/pull/1013.diff","patch_url":"https://github.com/kubernetes-incubator/external-storage/pull/1013.patch","issue_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/issues/1013","number":1013,"state":"open","locked":false,"title":"Add Gluster Subvol provisioner","user":{"login":"nixpanic","id":400257,"node_id":"MDQ6VXNlcjQwMDI1Nw==","avatar_url":"https://avatars3.githubusercontent.com/u/400257?v=4","gravatar_id":"","url":"https://api.github.com/users/nixpanic","html_url":"https://github.com/nixpanic","followers_url":"https://api.github.com/users/nixpanic/followers","following_url":"https://api.github.com/users/nixpanic/following{/other_user}","gists_url":"https://api.github.com/users/nixpanic/gists{/gist_id}","starred_url":"https://api.github.com/users/nixpanic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nixpanic/subscriptions","organizations_url":"https://api.github.com/users/nixpanic/orgs","repos_url":"https://api.github.com/users/nixpanic/repos","events_url":"https://api.github.com/users/nixpanic/events{/privacy}","received_events_url":"https://api.github.com/users/nixpanic/received_events","type":"User","site_admin":false},"body":"The Gluster Subvol provisioner creates 'light weight' Gluster PVCs that\r\nare located in subdirectories of an existing Gluster Volume. The design,\r\nadvantages and considerations are explained in the README.md, as well as\r\ninstallation and configuration instructions.\r\n\r\nA full example, including cloning support is described in the\r\nexamples/README.md.\r\n\r\nCloses: #1007 ","created_at":"2018-10-03T21:34:45Z","updated_at":"2018-10-17T08:28:50Z","closed_at":null,"merged_at":null,"merge_commit_sha":"c64614c1f0dad3216c2134565869e5a245b5454b","assignee":{"login":"wongma7","id":13111288,"node_id":"MDQ6VXNlcjEzMTExMjg4","avatar_url":"https://avatars0.githubusercontent.com/u/13111288?v=4","gravatar_id":"","url":"https://api.github.com/users/wongma7","html_url":"https://github.com/wongma7","followers_url":"https://api.github.com/users/wongma7/followers","following_url":"https://api.github.com/users/wongma7/following{/other_user}","gists_url":"https://api.github.com/users/wongma7/gists{/gist_id}","starred_url":"https://api.github.com/users/wongma7/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wongma7/subscriptions","organizations_url":"https://api.github.com/users/wongma7/orgs","repos_url":"https://api.github.com/users/wongma7/repos","events_url":"https://api.github.com/users/wongma7/events{/privacy}","received_events_url":"https://api.github.com/users/wongma7/received_events","type":"User","site_admin":false},"assignees":[{"login":"humblec","id":5001742,"node_id":"MDQ6VXNlcjUwMDE3NDI=","avatar_url":"https://avatars3.githubusercontent.com/u/5001742?v=4","gravatar_id":"","url":"https://api.github.com/users/humblec","html_url":"https://github.com/humblec","followers_url":"https://api.github.com/users/humblec/followers","following_url":"https://api.github.com/users/humblec/following{/other_user}","gists_url":"https://api.github.com/users/humblec/gists{/gist_id}","starred_url":"https://api.github.com/users/humblec/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/humblec/subscriptions","organizations_url":"https://api.github.com/users/humblec/orgs","repos_url":"https://api.github.com/users/humblec/repos","events_url":"https://api.github.com/users/humblec/events{/privacy}","received_events_url":"https://api.github.com/users/humblec/received_events","type":"User","site_admin":false},{"login":"wongma7","id":13111288,"node_id":"MDQ6VXNlcjEzMTExMjg4","avatar_url":"https://avatars0.githubusercontent.com/u/13111288?v=4","gravatar_id":"","url":"https://api.github.com/users/wongma7","html_url":"https://github.com/wongma7","followers_url":"https://api.github.com/users/wongma7/followers","following_url":"https://api.github.com/users/wongma7/following{/other_user}","gists_url":"https://api.github.com/users/wongma7/gists{/gist_id}","starred_url":"https://api.github.com/users/wongma7/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wongma7/subscriptions","organizations_url":"https://api.github.com/users/wongma7/orgs","repos_url":"https://api.github.com/users/wongma7/repos","events_url":"https://api.github.com/users/wongma7/events{/privacy}","received_events_url":"https://api.github.com/users/wongma7/received_events","type":"User","site_admin":false}],"requested_reviewers":[],"requested_teams":[],"labels":[{"id":1032087846,"node_id":"MDU6TGFiZWwxMDMyMDg3ODQ2","url":"https://api.github.com/repos/kubernetes-incubator/external-storage/labels/approved","name":"approved","color":"0ffa16","default":false},{"id":1078971174,"node_id":"MDU6TGFiZWwxMDc4OTcxMTc0","url":"https://api.github.com/repos/kubernetes-incubator/external-storage/labels/area/gluster/subvol","name":"area/gluster/subvol","color":"c198f9","default":false},{"id":547661380,"node_id":"MDU6TGFiZWw1NDc2NjEzODA=","url":"https://api.github.com/repos/kubernetes-incubator/external-storage/labels/cncf-cla:%20yes","name":"cncf-cla: yes","color":"bfe5bf","default":false},{"id":543732162,"node_id":"MDU6TGFiZWw1NDM3MzIxNjI=","url":"https://api.github.com/repos/kubernetes-incubator/external-storage/labels/kind/feature","name":"kind/feature","color":"c7def8","default":false},{"id":681405563,"node_id":"MDU6TGFiZWw2ODE0MDU1NjM=","url":"https://api.github.com/repos/kubernetes-incubator/external-storage/labels/size/XXL","name":"size/XXL","color":"ee0000","default":false}],"milestone":null,"commits_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/pulls/1013/commits","review_comments_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/pulls/1013/comments","review_comment_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/pulls/comments{/number}","comments_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/issues/1013/comments","statuses_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/statuses/af4f86d09f13a29bbd99c891a39ec28101078ec8","head":{"label":"nixpanic:gluster/subvol","ref":"gluster/subvol","sha":"af4f86d09f13a29bbd99c891a39ec28101078ec8","user":{"login":"nixpanic","id":400257,"node_id":"MDQ6VXNlcjQwMDI1Nw==","avatar_url":"https://avatars3.githubusercontent.com/u/400257?v=4","gravatar_id":"","url":"https://api.github.com/users/nixpanic","html_url":"https://github.com/nixpanic","followers_url":"https://api.github.com/users/nixpanic/followers","following_url":"https://api.github.com/users/nixpanic/following{/other_user}","gists_url":"https://api.github.com/users/nixpanic/gists{/gist_id}","starred_url":"https://api.github.com/users/nixpanic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nixpanic/subscriptions","organizations_url":"https://api.github.com/users/nixpanic/orgs","repos_url":"https://api.github.com/users/nixpanic/repos","events_url":"https://api.github.com/users/nixpanic/events{/privacy}","received_events_url":"https://api.github.com/users/nixpanic/received_events","type":"User","site_admin":false},"repo":{"id":119368930,"node_id":"MDEwOlJlcG9zaXRvcnkxMTkzNjg5MzA=","name":"external-storage","full_name":"nixpanic/external-storage","private":false,"owner":{"login":"nixpanic","id":400257,"node_id":"MDQ6VXNlcjQwMDI1Nw==","avatar_url":"https://avatars3.githubusercontent.com/u/400257?v=4","gravatar_id":"","url":"https://api.github.com/users/nixpanic","html_url":"https://github.com/nixpanic","followers_url":"https://api.github.com/users/nixpanic/followers","following_url":"https://api.github.com/users/nixpanic/following{/other_user}","gists_url":"https://api.github.com/users/nixpanic/gists{/gist_id}","starred_url":"https://api.github.com/users/nixpanic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nixpanic/subscriptions","organizations_url":"https://api.github.com/users/nixpanic/orgs","repos_url":"https://api.github.com/users/nixpanic/repos","events_url":"https://api.github.com/users/nixpanic/events{/privacy}","received_events_url":"https://api.github.com/users/nixpanic/received_events","type":"User","site_admin":false},"html_url":"https://github.com/nixpanic/external-storage","description":"External storage plugins, provisioners, and helper libraries","fork":true,"url":"https://api.github.com/repos/nixpanic/external-storage","forks_url":"https://api.github.com/repos/nixpanic/external-storage/forks","keys_url":"https://api.github.com/repos/nixpanic/external-storage/keys{/key_id}","collaborators_url":"https://api.github.com/repos/nixpanic/external-storage/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/nixpanic/external-storage/teams","hooks_url":"https://api.github.com/repos/nixpanic/external-storage/hooks","issue_events_url":"https://api.github.com/repos/nixpanic/external-storage/issues/events{/number}","events_url":"https://api.github.com/repos/nixpanic/external-storage/events","assignees_url":"https://api.github.com/repos/nixpanic/external-storage/assignees{/user}","branches_url":"https://api.github.com/repos/nixpanic/external-storage/branches{/branch}","tags_url":"https://api.github.com/repos/nixpanic/external-storage/tags","blobs_url":"https://api.github.com/repos/nixpanic/external-storage/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/nixpanic/external-storage/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/nixpanic/external-storage/git/refs{/sha}","trees_url":"https://api.github.com/repos/nixpanic/external-storage/git/trees{/sha}","statuses_url":"https://api.github.com/repos/nixpanic/external-storage/statuses/{sha}","languages_url":"https://api.github.com/repos/nixpanic/external-storage/languages","stargazers_url":"https://api.github.com/repos/nixpanic/external-storage/stargazers","contributors_url":"https://api.github.com/repos/nixpanic/external-storage/contributors","subscribers_url":"https://api.github.com/repos/nixpanic/external-storage/subscribers","subscription_url":"https://api.github.com/repos/nixpanic/external-storage/subscription","commits_url":"https://api.github.com/repos/nixpanic/external-storage/commits{/sha}","git_commits_url":"https://api.github.com/repos/nixpanic/external-storage/git/commits{/sha}","comments_url":"https://api.github.com/repos/nixpanic/external-storage/comments{/number}","issue_comment_url":"https://api.github.com/repos/nixpanic/external-storage/issues/comments{/number}","contents_url":"https://api.github.com/repos/nixpanic/external-storage/contents/{+path}","compare_url":"https://api.github.com/repos/nixpanic/external-storage/compare/{base}...{head}","merges_url":"https://api.github.com/repos/nixpanic/external-storage/merges","archive_url":"https://api.github.com/repos/nixpanic/external-storage/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/nixpanic/external-storage/downloads","issues_url":"https://api.github.com/repos/nixpanic/external-storage/issues{/number}","pulls_url":"https://api.github.com/repos/nixpanic/external-storage/pulls{/number}","milestones_url":"https://api.github.com/repos/nixpanic/external-storage/milestones{/number}","notifications_url":"https://api.github.com/repos/nixpanic/external-storage/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/nixpanic/external-storage/labels{/name}","releases_url":"https://api.github.com/repos/nixpanic/external-storage/releases{/id}","deployments_url":"https://api.github.com/repos/nixpanic/external-storage/deployments","created_at":"2018-01-29T10:37:26Z","updated_at":"2018-10-02T07:46:29Z","pushed_at":"2018-10-12T07:16:56Z","git_url":"git://github.com/nixpanic/external-storage.git","ssh_url":"git@github.com:nixpanic/external-storage.git","clone_url":"https://github.com/nixpanic/external-storage.git","svn_url":"https://github.com/nixpanic/external-storage","homepage":"","size":116223,"stargazers_count":1,"watchers_count":1,"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":1,"default_branch":"master"}},"base":{"label":"kubernetes-incubator:master","ref":"master","sha":"e3b73538bd9e63e1568ce98b803770fb33fb6e7d","user":{"login":"kubernetes-incubator","id":20862812,"node_id":"MDEyOk9yZ2FuaXphdGlvbjIwODYyODEy","avatar_url":"https://avatars2.githubusercontent.com/u/20862812?v=4","gravatar_id":"","url":"https://api.github.com/users/kubernetes-incubator","html_url":"https://github.com/kubernetes-incubator","followers_url":"https://api.github.com/users/kubernetes-incubator/followers","following_url":"https://api.github.com/users/kubernetes-incubator/following{/other_user}","gists_url":"https://api.github.com/users/kubernetes-incubator/gists{/gist_id}","starred_url":"https://api.github.com/users/kubernetes-incubator/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kubernetes-incubator/subscriptions","organizations_url":"https://api.github.com/users/kubernetes-incubator/orgs","repos_url":"https://api.github.com/users/kubernetes-incubator/repos","events_url":"https://api.github.com/users/kubernetes-incubator/events{/privacy}","received_events_url":"https://api.github.com/users/kubernetes-incubator/received_events","type":"Organization","site_admin":false},"repo":{"id":82572869,"node_id":"MDEwOlJlcG9zaXRvcnk4MjU3Mjg2OQ==","name":"external-storage","full_name":"kubernetes-incubator/external-storage","private":false,"owner":{"login":"kubernetes-incubator","id":20862812,"node_id":"MDEyOk9yZ2FuaXphdGlvbjIwODYyODEy","avatar_url":"https://avatars2.githubusercontent.com/u/20862812?v=4","gravatar_id":"","url":"https://api.github.com/users/kubernetes-incubator","html_url":"https://github.com/kubernetes-incubator","followers_url":"https://api.github.com/users/kubernetes-incubator/followers","following_url":"https://api.github.com/users/kubernetes-incubator/following{/other_user}","gists_url":"https://api.github.com/users/kubernetes-incubator/gists{/gist_id}","starred_url":"https://api.github.com/users/kubernetes-incubator/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kubernetes-incubator/subscriptions","organizations_url":"https://api.github.com/users/kubernetes-incubator/orgs","repos_url":"https://api.github.com/users/kubernetes-incubator/repos","events_url":"https://api.github.com/users/kubernetes-incubator/events{/privacy}","received_events_url":"https://api.github.com/users/kubernetes-incubator/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/kubernetes-incubator/external-storage","description":"External storage plugins, provisioners, and helper libraries","fork":false,"url":"https://api.github.com/repos/kubernetes-incubator/external-storage","forks_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/forks","keys_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/keys{/key_id}","collaborators_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/teams","hooks_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/hooks","issue_events_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/issues/events{/number}","events_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/events","assignees_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/assignees{/user}","branches_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/branches{/branch}","tags_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/tags","blobs_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/git/refs{/sha}","trees_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/git/trees{/sha}","statuses_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/statuses/{sha}","languages_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/languages","stargazers_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/stargazers","contributors_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/contributors","subscribers_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/subscribers","subscription_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/subscription","commits_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/commits{/sha}","git_commits_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/git/commits{/sha}","comments_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/comments{/number}","issue_comment_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/issues/comments{/number}","contents_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/contents/{+path}","compare_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/compare/{base}...{head}","merges_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/merges","archive_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/downloads","issues_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/issues{/number}","pulls_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/pulls{/number}","milestones_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/milestones{/number}","notifications_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/labels{/name}","releases_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/releases{/id}","deployments_url":"https://api.github.com/repos/kubernetes-incubator/external-storage/deployments","created_at":"2017-02-20T15:29:16Z","updated_at":"2018-10-17T05:54:03Z","pushed_at":"2018-10-16T22:49:18Z","git_url":"git://github.com/kubernetes-incubator/external-storage.git","ssh_url":"git@github.com:kubernetes-incubator/external-storage.git","clone_url":"https://github.com/kubernetes-incubator/external-storage.git","svn_url":"https://github.com/kubernetes-incubator/external-storage","homepage":"","size":116428,"stargazers_count":950,"watchers_count":950,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":516,"mirror_url":null,"archived":false,"open_issues_count":138,"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":516,"open_issues":138,"watchers":950,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/kubernetes-incubator/external-storage/pulls/1013"},"html":{"href":"https://github.com/kubernetes-incubator/external-storage/pull/1013"},"issue":{"href":"https://api.github.com/repos/kubernetes-incubator/external-storage/issues/1013"},"comments":{"href":"https://api.github.com/repos/kubernetes-incubator/external-storage/issues/1013/comments"},"review_comments":{"href":"https://api.github.com/repos/kubernetes-incubator/external-storage/pulls/1013/comments"},"review_comment":{"href":"https://api.github.com/repos/kubernetes-incubator/external-storage/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/kubernetes-incubator/external-storage/pulls/1013/commits"},"statuses":{"href":"https://api.github.com/repos/kubernetes-incubator/external-storage/statuses/af4f86d09f13a29bbd99c891a39ec28101078ec8"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"nixpanic"}}