{"action":"created","comment":{"url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/pulls/comments/236770106","pull_request_review_id":178915294,"id":236770106,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIzNjc3MDEwNg==","diff_hunk":"@@ -0,0 +1,301 @@\n+// +build integration\n+\n+/*\n+Copyright 2017 DigitalOcean\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 e2e\n+\n+import (\n+\t\"fmt\"\n+\t\"log\"\n+\t\"net/http\"\n+\t\"os\"\n+\t\"path\"\n+\t\"strconv\"\n+\t\"testing\"\n+\t\"time\"\n+\n+\t\"github.com/davecgh/go-spew/spew\"\n+\tcorev1 \"k8s.io/api/core/v1\"\n+\tkerrors \"k8s.io/apimachinery/pkg/api/errors\"\n+\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n+\t\"k8s.io/apimachinery/pkg/util/wait\"\n+)\n+\n+const (\n+\tnumWantNodes          = 2\n+\tdoLabel               = \"beta.kubernetes.io/instance-type\"\n+\tkopsEnvVarClusterName = \"KOPS_CLUSTER_NAME\"\n+\tkopsEnvVarStateStore  = \"KOPS_STATE_STORE\"\n+)\n+\n+// TestE2E verifies that the node and service controller work as intended for\n+// all supported Kubernetes versions; that is, we expect nodes to become ready\n+// and requests to be routed through a DO-provisioned load balancer.\n+// The test creates various components and makes sure they get deleted prior to\n+// (to clean up any previous left-overs) and after testing.\n+func TestE2E(t *testing.T) {\n+\tvar missingEnvs []string\n+\tfor _, env := range []string{kopsEnvVarClusterName, kopsEnvVarStateStore} {\n+\t\tif _, ok := os.LookupEnv(env); !ok {\n+\t\t\tmissingEnvs = append(missingEnvs, env)\n+\t\t}\n+\t}\n+\tif len(missingEnvs) > 0 {\n+\t\tt.Fatalf(\"missing required environment variable(s): %s\", missingEnvs)\n+\t}\n+\n+\ts3Cl, err := createS3Client()\n+\tif err != nil {\n+\t\tt.Fatalf(\"failed to create S3 client: %s\", err)\n+\t}\n+\n+\ttests := []struct {\n+\t\tdesc    string\n+\t\tkubeVer string\n+\t}{\n+\t\t{\n+\t\t\tdesc:    \"latest release\",\n+\t\t\tkubeVer: \"1.12.0\",\n+\t\t},\n+\t\t{\n+\t\t\tdesc:    \"previous release\",\n+\t\t\tkubeVer: \"1.11.2\",\n+\t\t},\n+\t\t{\n+\t\t\tdesc:    \"previous previous release\",\n+\t\t\tkubeVer: \"1.10.6\",\n+\t\t},\n+\t}\n+\n+\tfor _, tt := range tests {\n+\t\ttt := tt\n+\t\tt.Run(tt.desc, func(t *testing.T) {\n+\t\t\tl := log.New(os.Stdout, fmt.Sprintf(\"[%s] \", t.Name()), 0)\n+\t\t\tdnsName := toDNSName(t.Name())\n+\n+\t\t\twd, err := os.Getwd()\n+\t\t\tif err != nil {\n+\t\t\t\tt.Fatalf(\"failed to get working directory: %s\", err)\n+\t\t\t}\n+\t\t\tkubeConfFile := path.Join(wd, \"kubeconfig-e2e.\"+dnsName)\n+\n+\t\t\t// Delete old kubeconfig\n+\t\t\tif err := os.Remove(kubeConfFile); err != nil && !os.IsNotExist(err) {\n+\t\t\t\tt.Fatalf(\"failed to delete kubeconfig %q: %s\", kubeConfFile, err)\n+\t\t\t}\n+\n+\t\t\t// Create space.\n+\t\t\tstoreName := toS3Name(fmt.Sprintf(\"%s-%s\", os.Getenv(kopsEnvVarClusterName), dnsName))\n+\t\t\tif err := s3Cl.deleteSpace(storeName); err != nil {\n+\t\t\t\tt.Fatalf(\"failed to delete space %q (pre-test): %s\", storeName, err)\n+\t\t\t}\n+\t\t\tif err := s3Cl.ensureSpace(storeName); err != nil {\n+\t\t\t\tt.Fatalf(\"failed to ensure space %q: %s\", storeName, err)\n+\t\t\t}\n+\t\t\tdefer func() {\n+\t\t\t\tif err := s3Cl.deleteSpace(storeName); err != nil {\n+\t\t\t\t\tt.Fatalf(\"failed to delete space %q (post-test): %s\", storeName, err)\n+\t\t\t\t}\n+\t\t\t}()\n+\n+\t\t\t// Create cluster.\n+\t\t\textraEnvs := []string{\n+\t\t\t\tfmt.Sprintf(\"%s=do://%s\", kopsEnvVarStateStore, storeName),\n+\t\t\t\t\"KUBECONFIG=\" + kubeConfFile,\n+\t\t\t}\n+\t\t\tif err := runScript(extraEnvs, \"destroy_cluster.sh\"); err != nil {\n+\t\t\t\tt.Fatalf(\"failed to destroy cluster (pre-test): %s\", err)\n+\t\t\t}\n+\t\t\tif err := runScript(extraEnvs, \"setup_cluster.sh\", tt.kubeVer, strconv.Itoa(numWantNodes)); err != nil {\n+\t\t\t\tt.Fatalf(\"failed to set up cluster: %s\", err)\n+\t\t\t}\n+\t\t\tdefer func() {\n+\t\t\t\tif err := runScript(extraEnvs, \"destroy_cluster.sh\"); err != nil {\n+\t\t\t\t\tt.Errorf(\"failed to destroy cluster (post-test): %s\", err)\n+\t\t\t\t}\n+\t\t\t}()\n+\n+\t\t\tcs, err := kubeClient(kubeConfFile)\n+\t\t\tif err != nil {\n+\t\t\t\tt.Fatalf(\"failed to create Kubernetes client: %s\", err)\n+\t\t\t}\n+\n+\t\t\t// Check that nodes become ready.\n+\t\t\tl.Println(\"Polling for node readiness\")\n+\t\t\tvar (\n+\t\t\t\tgotNodes      []corev1.Node\n+\t\t\t\tnumReadyNodes int\n+\t\t\t)\n+\t\t\tstart := time.Now()\n+\t\t\tif err := wait.Poll(5*time.Second, 6*time.Minute, func() (bool, error) {\n+\t\t\t\tnl, err := cs.Core().Nodes().List(metav1.ListOptions{LabelSelector: \"kubernetes.io/role=node\"})\n+\t\t\t\tif err != nil {\n+\t\t\t\t\treturn false, err\n+\t\t\t\t}\n+\n+\t\t\t\tgotNodes = nl.Items\n+\t\t\t\tnumReadyNodes = 0\n+\t\t\t\tfor _, node := range gotNodes {\n+\t\t\t\t\tfor _, cond := range node.Status.Conditions {\n+\t\t\t\t\t\tif cond.Type == corev1.NodeReady && cond.Status == corev1.ConditionTrue {\n+\t\t\t\t\t\t\tif _, ok := node.Labels[doLabel]; ok {\n+\t\t\t\t\t\t\t\tnumReadyNodes++\n+\t\t\t\t\t\t\t}\n+\t\t\t\t\t\t}\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\n+\t\t\t\tl.Printf(\"Found %d/%d ready node(s)\", numReadyNodes, numWantNodes)\n+\t\t\t\treturn numReadyNodes == numWantNodes, nil\n+\t\t\t}); err != nil {\n+\t\t\t\tt.Fatalf(\"got %d ready node(s), want %d: %s\\nnnodes: %v\", numReadyNodes, numWantNodes, err, spew.Sdump(gotNodes))\n+\t\t\t}\n+\t\t\tl.Printf(\"Took %v for nodes to become ready\\n\", time.Since(start))\n+\n+\t\t\t// Check that load balancer is working.\n+\n+\t\t\t// Install example pod to load-balance to.\n+\t\t\tappName := \"app\"\n+\t\t\tpod := corev1.Pod{\n+\t\t\t\tObjectMeta: metav1.ObjectMeta{\n+\t\t\t\t\tName: appName,\n+\t\t\t\t\tLabels: map[string]string{\n+\t\t\t\t\t\t\"app\": appName,\n+\t\t\t\t\t},\n+\t\t\t\t},\n+\t\t\t\tSpec: corev1.PodSpec{\n+\t\t\t\t\tContainers: []corev1.Container{\n+\t\t\t\t\t\t{\n+\t\t\t\t\t\t\tName:  \"nginx\",\n+\t\t\t\t\t\t\tImage: \"nginx\",\n+\t\t\t\t\t\t\tPorts: []corev1.ContainerPort{\n+\t\t\t\t\t\t\t\t{\n+\t\t\t\t\t\t\t\t\tContainerPort: 80,\n+\t\t\t\t\t\t\t\t},\n+\t\t\t\t\t\t\t},\n+\t\t\t\t\t\t},\n+\t\t\t\t\t},\n+\t\t\t\t},\n+\t\t\t}\n+\n+\t\t\tif _, err := cs.CoreV1().Pods(corev1.NamespaceDefault).Create(&pod); err != nil {\n+\t\t\t\tt.Fatalf(\"failed to create example pod: %s\", err)\n+\t\t\t}\n+\n+\t\t\t// Wait for example pod to become ready.\n+\t\t\tl.Println(\"Polling for pod readiness\")\n+\t\t\tstart = time.Now()\n+\t\t\tvar appPod *corev1.Pod\n+\t\t\tif err := wait.Poll(1*time.Second, 1*time.Minute, func() (bool, error) {\n+\t\t\t\tpod, err := cs.CoreV1().Pods(corev1.NamespaceDefault).Get(appName, metav1.GetOptions{})\n+\t\t\t\tif err != nil {\n+\t\t\t\t\tif kerrors.IsNotFound(err) {\n+\t\t\t\t\t\treturn false, nil\n+\t\t\t\t\t}\n+\t\t\t\t\treturn false, err\n+\t\t\t\t}\n+\t\t\t\tappPod = pod\n+\t\t\t\tfor _, cond := range appPod.Status.Conditions {\n+\t\t\t\t\tif cond.Type == corev1.PodReady && cond.Status == corev1.ConditionTrue {\n+\t\t\t\t\t\treturn true, nil\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t\treturn false, nil\n+\t\t\t}); err != nil {\n+\t\t\t\tt.Fatalf(\"failed to observe ready example pod %q in time: %s\\npod: %v\", appName, err, appPod)\n+\t\t\t}\n+\t\t\tl.Printf(\"Took %v for pod to become ready\\n\", time.Since(start))\n+\n+\t\t\t// Create service object.\n+\t\t\tsvcName := \"svc\"\n+\t\t\tsvc := corev1.Service{\n+\t\t\t\tObjectMeta: metav1.ObjectMeta{\n+\t\t\t\t\tName: svcName,\n+\t\t\t\t},\n+\t\t\t\tSpec: corev1.ServiceSpec{\n+\t\t\t\t\tSelector: map[string]string{\n+\t\t\t\t\t\t\"app\": appName,\n+\t\t\t\t\t},\n+\t\t\t\t\tType: corev1.ServiceTypeLoadBalancer,\n+\t\t\t\t\tPorts: []corev1.ServicePort{\n+\t\t\t\t\t\t{\n+\t\t\t\t\t\t\tPort: 80,\n+\t\t\t\t\t\t},\n+\t\t\t\t\t},\n+\t\t\t\t},\n+\t\t\t}\n+\n+\t\t\tif _, err := cs.CoreV1().Services(corev1.NamespaceDefault).Create(&svc); err != nil {\n+\t\t\t\tt.Fatalf(\"failed to create service: %s\", err)\n+\t\t\t}\n+\t\t\t// External LBs don't seem to get deleted when the kops cluster is\n+\t\t\t// removed, at least not on DO. Hence, we'll do it explicitly.\n+\t\t\tdefer func() {\n+\t\t\t\tif err := cs.CoreV1().Services(corev1.NamespaceDefault).Delete(svcName, &metav1.DeleteOptions{}); err != nil {\n+\t\t\t\t\tt.Fatalf(\"failed to delete service: %s\", err)\n+\t\t\t\t}\n+\t\t\t}()\n+\n+\t\t\t// Wait for service IP address to be assigned.\n+\t\t\tl.Println(\"Polling for service load balancer IP address assignment\")\n+\t\t\tstart = time.Now()\n+\t\t\tvar lbAddr string\n+\t\t\tif err := wait.Poll(5*time.Second, 10*time.Minute, func() (bool, error) {\n+\t\t\t\tsvc, err := cs.CoreV1().Services(corev1.NamespaceDefault).Get(svcName, metav1.GetOptions{})\n+\t\t\t\tif err != nil {\n+\t\t\t\t\treturn false, err\n+\t\t\t\t}\n+\t\t\t\tfor _, ing := range svc.Status.LoadBalancer.Ingress {","path":"e2e/e2e_test.go","position":null,"original_position":262,"commit_id":"164d9d1fb02ad362a701a339e44924a9b309add9","original_commit_id":"44c4363838b02b2ffa0d722d0e9c79236222db90","user":{"login":"timoreimann","id":694826,"node_id":"MDQ6VXNlcjY5NDgyNg==","avatar_url":"https://avatars1.githubusercontent.com/u/694826?v=4","gravatar_id":"","url":"https://api.github.com/users/timoreimann","html_url":"https://github.com/timoreimann","followers_url":"https://api.github.com/users/timoreimann/followers","following_url":"https://api.github.com/users/timoreimann/following{/other_user}","gists_url":"https://api.github.com/users/timoreimann/gists{/gist_id}","starred_url":"https://api.github.com/users/timoreimann/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/timoreimann/subscriptions","organizations_url":"https://api.github.com/users/timoreimann/orgs","repos_url":"https://api.github.com/users/timoreimann/repos","events_url":"https://api.github.com/users/timoreimann/events{/privacy}","received_events_url":"https://api.github.com/users/timoreimann/received_events","type":"User","site_admin":false},"body":"Sure, makes sense -- done!","created_at":"2018-11-27T17:32:56Z","updated_at":"2018-11-27T17:32:56Z","html_url":"https://github.com/digitalocean/digitalocean-cloud-controller-manager/pull/148#discussion_r236770106","pull_request_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/pulls/148","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/pulls/comments/236770106"},"html":{"href":"https://github.com/digitalocean/digitalocean-cloud-controller-manager/pull/148#discussion_r236770106"},"pull_request":{"href":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/pulls/148"}},"in_reply_to_id":236736044},"pull_request":{"url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/pulls/148","id":233639133,"node_id":"MDExOlB1bGxSZXF1ZXN0MjMzNjM5MTMz","html_url":"https://github.com/digitalocean/digitalocean-cloud-controller-manager/pull/148","diff_url":"https://github.com/digitalocean/digitalocean-cloud-controller-manager/pull/148.diff","patch_url":"https://github.com/digitalocean/digitalocean-cloud-controller-manager/pull/148.patch","issue_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/issues/148","number":148,"state":"open","locked":false,"title":"Add end-to-end test verifying Kubernetes compatibility.","user":{"login":"timoreimann","id":694826,"node_id":"MDQ6VXNlcjY5NDgyNg==","avatar_url":"https://avatars1.githubusercontent.com/u/694826?v=4","gravatar_id":"","url":"https://api.github.com/users/timoreimann","html_url":"https://github.com/timoreimann","followers_url":"https://api.github.com/users/timoreimann/followers","following_url":"https://api.github.com/users/timoreimann/following{/other_user}","gists_url":"https://api.github.com/users/timoreimann/gists{/gist_id}","starred_url":"https://api.github.com/users/timoreimann/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/timoreimann/subscriptions","organizations_url":"https://api.github.com/users/timoreimann/orgs","repos_url":"https://api.github.com/users/timoreimann/repos","events_url":"https://api.github.com/users/timoreimann/events{/privacy}","received_events_url":"https://api.github.com/users/timoreimann/received_events","type":"User","site_admin":false},"body":"This change adds an end-to-end test verifying that the CCM core functionalities (namely, node and service controllers) work as intended.\r\n\r\nIn a nutshell, it basically does the following for each Kubernetes version supported:\r\n\r\n- dynamically create a cluster via kops\r\n- wait for the cluster to become ready\r\n- wait for the nodes to be fully configured\r\n- deploy an example pod\r\n- expose the pod through a DO-managed LB\r\n- send a sample request over the LB\r\n- clean up all objects that were created along the way (LB, cluster, state store)\r\n\r\nThe tests should work both when invoked from the local machine as well as on Travis CI. The latter requires some extra tuning to the (Travis CI) configuration which I have done on [this development branch of mine](https://github.com/timoreimann/digitalocean-cloud-controller-manager/tree/verify-version-compat) resulting in [this build output](https://travis-ci.org/timoreimann/digitalocean-cloud-controller-manager/builds/459761520). I tried to explain all the needed environment variables in a separate README file.\r\n\r\nThe PR grew bigger than I initially thought it'd need to; a fair chunk of that resulted from the fact that cleaning up state manually during repeated test runs became tedious at some point, so I automated things as much as I could. The extra dependency on minio is a consequence of that so that I could talk to the Spaces/S3 API accordingly. I'm open to suggestions to reduce the overall code size.\r\n\r\nThere are a few minor tasks left to do if we wanted to integrate the test properly:\r\n\r\n1. pick some DigitalOcean account to set up a token in and push an SSH key to\r\n1. possibly make the CCM version configurable (and maybe even build it right before the test is invoked)\r\n1. (if we want to allow running end-to-end tests from multiple builds concurrently:) include a (sufficiently) unique identifier in the kops state name, e.g., the PR ID or branch name\r\n\r\nI didn't want to go too far (which I might have done already ;) ) though prior to getting some initial agreement on the direction taken. I'd be more than happy to close the final code gap in a follow-up PR if desirable.\r\n\r\nSome final notes:\r\n\r\n- For the longest time, I tested everything in the AMS3 region, which turned out to be fairly unreliable (machines not getting set up, load balancers not finishing, cluster cleanup failing, etc). I got a [hint](https://kubernetes.slack.com/archives/C3QUFP0QM/p1542892880619600?thread_ts=1542874467.611500&cid=C3QUFP0QM) on the `kops-users` Kubernetes Slack channel to switch to SFO2 which immediately made all my builds go green consistently. The person I talked to mentioned that the NYC regions seemed flaky to; I'm not sure if there's an issue with the other regions or if I (we) have been using them wrong, but I wanted to mention it.\r\n- I also have a variant of the implementation ready that parallelizes testing against the supported Kubernetes, which speeds up overall test execution quite significantly. The downsides are that it increases code complexity, complicates following test output, and aggravates debugging, which is why I did not include it in this PR. We are safely within the Travis execution deadline of 50 minutes with the sequential approach, so I'd only add the parallelized code if requested.\r\n\r\nThe PR references / fixes possibly a combination of #101 and #45.\r\n\r\nI'm very keen on hearing about what you think -- happy to discuss and adapt as needed!","created_at":"2018-11-26T17:31:06Z","updated_at":"2018-11-27T17:32:56Z","closed_at":null,"merged_at":null,"merge_commit_sha":"8332ef53a53c89f9243edb73dbd665a48172876a","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/pulls/148/commits","review_comments_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/pulls/148/comments","review_comment_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/pulls/comments{/number}","comments_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/issues/148/comments","statuses_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/statuses/164d9d1fb02ad362a701a339e44924a9b309add9","head":{"label":"timoreimann:verify-version-compat-upstream","ref":"verify-version-compat-upstream","sha":"164d9d1fb02ad362a701a339e44924a9b309add9","user":{"login":"timoreimann","id":694826,"node_id":"MDQ6VXNlcjY5NDgyNg==","avatar_url":"https://avatars1.githubusercontent.com/u/694826?v=4","gravatar_id":"","url":"https://api.github.com/users/timoreimann","html_url":"https://github.com/timoreimann","followers_url":"https://api.github.com/users/timoreimann/followers","following_url":"https://api.github.com/users/timoreimann/following{/other_user}","gists_url":"https://api.github.com/users/timoreimann/gists{/gist_id}","starred_url":"https://api.github.com/users/timoreimann/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/timoreimann/subscriptions","organizations_url":"https://api.github.com/users/timoreimann/orgs","repos_url":"https://api.github.com/users/timoreimann/repos","events_url":"https://api.github.com/users/timoreimann/events{/privacy}","received_events_url":"https://api.github.com/users/timoreimann/received_events","type":"User","site_admin":false},"repo":{"id":152814986,"node_id":"MDEwOlJlcG9zaXRvcnkxNTI4MTQ5ODY=","name":"digitalocean-cloud-controller-manager","full_name":"timoreimann/digitalocean-cloud-controller-manager","private":false,"owner":{"login":"timoreimann","id":694826,"node_id":"MDQ6VXNlcjY5NDgyNg==","avatar_url":"https://avatars1.githubusercontent.com/u/694826?v=4","gravatar_id":"","url":"https://api.github.com/users/timoreimann","html_url":"https://github.com/timoreimann","followers_url":"https://api.github.com/users/timoreimann/followers","following_url":"https://api.github.com/users/timoreimann/following{/other_user}","gists_url":"https://api.github.com/users/timoreimann/gists{/gist_id}","starred_url":"https://api.github.com/users/timoreimann/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/timoreimann/subscriptions","organizations_url":"https://api.github.com/users/timoreimann/orgs","repos_url":"https://api.github.com/users/timoreimann/repos","events_url":"https://api.github.com/users/timoreimann/events{/privacy}","received_events_url":"https://api.github.com/users/timoreimann/received_events","type":"User","site_admin":false},"html_url":"https://github.com/timoreimann/digitalocean-cloud-controller-manager","description":"Kubernetes cloud-controller-manager for DigitalOcean (beta)","fork":true,"url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager","forks_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/forks","keys_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/keys{/key_id}","collaborators_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/teams","hooks_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/hooks","issue_events_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/issues/events{/number}","events_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/events","assignees_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/assignees{/user}","branches_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/branches{/branch}","tags_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/tags","blobs_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/git/refs{/sha}","trees_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/git/trees{/sha}","statuses_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/statuses/{sha}","languages_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/languages","stargazers_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/stargazers","contributors_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/contributors","subscribers_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/subscribers","subscription_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/subscription","commits_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/commits{/sha}","git_commits_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/git/commits{/sha}","comments_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/comments{/number}","issue_comment_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/issues/comments{/number}","contents_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/contents/{+path}","compare_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/compare/{base}...{head}","merges_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/merges","archive_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/downloads","issues_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/issues{/number}","pulls_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/pulls{/number}","milestones_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/milestones{/number}","notifications_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/labels{/name}","releases_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/releases{/id}","deployments_url":"https://api.github.com/repos/timoreimann/digitalocean-cloud-controller-manager/deployments","created_at":"2018-10-12T22:47:28Z","updated_at":"2018-10-12T22:47:30Z","pushed_at":"2018-11-27T17:32:03Z","git_url":"git://github.com/timoreimann/digitalocean-cloud-controller-manager.git","ssh_url":"git@github.com:timoreimann/digitalocean-cloud-controller-manager.git","clone_url":"https://github.com/timoreimann/digitalocean-cloud-controller-manager.git","svn_url":"https://github.com/timoreimann/digitalocean-cloud-controller-manager","homepage":"","size":18722,"stargazers_count":0,"watchers_count":0,"language":"Go","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"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":0,"default_branch":"master"}},"base":{"label":"digitalocean:master","ref":"master","sha":"5ef072a20baa1ec3ceed1ea18be6452928eebfee","user":{"login":"digitalocean","id":4650108,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ2NTAxMDg=","avatar_url":"https://avatars0.githubusercontent.com/u/4650108?v=4","gravatar_id":"","url":"https://api.github.com/users/digitalocean","html_url":"https://github.com/digitalocean","followers_url":"https://api.github.com/users/digitalocean/followers","following_url":"https://api.github.com/users/digitalocean/following{/other_user}","gists_url":"https://api.github.com/users/digitalocean/gists{/gist_id}","starred_url":"https://api.github.com/users/digitalocean/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/digitalocean/subscriptions","organizations_url":"https://api.github.com/users/digitalocean/orgs","repos_url":"https://api.github.com/users/digitalocean/repos","events_url":"https://api.github.com/users/digitalocean/events{/privacy}","received_events_url":"https://api.github.com/users/digitalocean/received_events","type":"Organization","site_admin":false},"repo":{"id":94823705,"node_id":"MDEwOlJlcG9zaXRvcnk5NDgyMzcwNQ==","name":"digitalocean-cloud-controller-manager","full_name":"digitalocean/digitalocean-cloud-controller-manager","private":false,"owner":{"login":"digitalocean","id":4650108,"node_id":"MDEyOk9yZ2FuaXphdGlvbjQ2NTAxMDg=","avatar_url":"https://avatars0.githubusercontent.com/u/4650108?v=4","gravatar_id":"","url":"https://api.github.com/users/digitalocean","html_url":"https://github.com/digitalocean","followers_url":"https://api.github.com/users/digitalocean/followers","following_url":"https://api.github.com/users/digitalocean/following{/other_user}","gists_url":"https://api.github.com/users/digitalocean/gists{/gist_id}","starred_url":"https://api.github.com/users/digitalocean/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/digitalocean/subscriptions","organizations_url":"https://api.github.com/users/digitalocean/orgs","repos_url":"https://api.github.com/users/digitalocean/repos","events_url":"https://api.github.com/users/digitalocean/events{/privacy}","received_events_url":"https://api.github.com/users/digitalocean/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/digitalocean/digitalocean-cloud-controller-manager","description":"Kubernetes cloud-controller-manager for DigitalOcean (beta)","fork":false,"url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager","forks_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/forks","keys_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/keys{/key_id}","collaborators_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/teams","hooks_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/hooks","issue_events_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/issues/events{/number}","events_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/events","assignees_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/assignees{/user}","branches_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/branches{/branch}","tags_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/tags","blobs_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/git/refs{/sha}","trees_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/git/trees{/sha}","statuses_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/statuses/{sha}","languages_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/languages","stargazers_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/stargazers","contributors_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/contributors","subscribers_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/subscribers","subscription_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/subscription","commits_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/commits{/sha}","git_commits_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/git/commits{/sha}","comments_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/comments{/number}","issue_comment_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/issues/comments{/number}","contents_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/contents/{+path}","compare_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/compare/{base}...{head}","merges_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/merges","archive_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/downloads","issues_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/issues{/number}","pulls_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/pulls{/number}","milestones_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/milestones{/number}","notifications_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/labels{/name}","releases_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/releases{/id}","deployments_url":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/deployments","created_at":"2017-06-19T21:45:45Z","updated_at":"2018-11-25T07:53:03Z","pushed_at":"2018-11-27T17:32:05Z","git_url":"git://github.com/digitalocean/digitalocean-cloud-controller-manager.git","ssh_url":"git@github.com:digitalocean/digitalocean-cloud-controller-manager.git","clone_url":"https://github.com/digitalocean/digitalocean-cloud-controller-manager.git","svn_url":"https://github.com/digitalocean/digitalocean-cloud-controller-manager","homepage":"","size":18483,"stargazers_count":179,"watchers_count":179,"language":"Go","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":28,"mirror_url":null,"archived":false,"open_issues_count":18,"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":28,"open_issues":18,"watchers":179,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/pulls/148"},"html":{"href":"https://github.com/digitalocean/digitalocean-cloud-controller-manager/pull/148"},"issue":{"href":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/issues/148"},"comments":{"href":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/issues/148/comments"},"review_comments":{"href":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/pulls/148/comments"},"review_comment":{"href":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/pulls/148/commits"},"statuses":{"href":"https://api.github.com/repos/digitalocean/digitalocean-cloud-controller-manager/statuses/164d9d1fb02ad362a701a339e44924a9b309add9"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"timoreimann"}}