{"action":"created","comment":{"url":"https://api.github.com/repos/presslabs/mysql-operator/pulls/comments/218072166","pull_request_review_id":155909222,"id":218072166,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIxODA3MjE2Ng==","diff_hunk":"@@ -0,0 +1,474 @@\n+/*\n+Copyright 2018 Pressinfra SRL\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 orchestratormysql\n+\n+import (\n+\t\"errors\"\n+\t\"fmt\"\n+\t\"time\"\n+\n+\tcore \"k8s.io/api/core/v1\"\n+\t\"k8s.io/client-go/tools/record\"\n+\n+\tapi \"github.com/presslabs/mysql-operator/pkg/apis/mysql/v1alpha1\"\n+\twrapcluster \"github.com/presslabs/mysql-operator/pkg/controller/internal/mysqlcluster\"\n+\torc \"github.com/presslabs/mysql-operator/pkg/orchestrator\"\n+)\n+\n+const (\n+\thealtyMoreThanMinutes        = 10\n+\tdefaultMaxSlaveLatency int64 = 30\n+\tmysqlPort                    = 3306\n+)\n+\n+type orcUpdater struct {\n+\tcluster   *wrapcluster.MysqlCluster\n+\trecorder  record.EventRecorder\n+\torcClient orc.Interface\n+}\n+\n+// Syncer interface defines Sync method\n+type Syncer interface {\n+\tSync() error\n+}\n+\n+// NewOrcUpdater returns a syncer that updates cluster status from orchestrator.\n+func NewOrcUpdater(cluster *api.MysqlCluster, r record.EventRecorder, orcClient orc.Interface) Syncer {\n+\treturn &orcUpdater{\n+\t\tcluster:   wrapcluster.NewMysqlClusterWrapper(cluster),\n+\t\trecorder:  r,\n+\t\torcClient: orcClient,\n+\t}\n+}\n+\n+func (ou *orcUpdater) Sync() error {\n+\t// get instances from orchestrator\n+\tvar (\n+\t\tinstances  InstancesSet\n+\t\terr        error\n+\t\trecoveries []orc.TopologyRecovery\n+\t)\n+\n+\tif instances, err = ou.orcClient.Cluster(ou.cluster.GetClusterAlias()); err != nil {\n+\t\tlog.Error(err, \"Can't get instances from orchestrator\", \"alias\", ou.cluster.GetClusterAlias(), \"cluster\", ou.cluster)\n+\t}\n+\n+\tif len(instances) == 0 {\n+\t\tlog.Info(\"No instances in orchestrator!\", \"clusterAlias\", ou.cluster.GetClusterAlias(), \"cluster\", ou.cluster)\n+\t}\n+\n+\t// register nodes in orchestrator if needed, or remove nodes from status\n+\tif instances, err = ou.registerUnregisterNodesInOrc(instances); err != nil {\n+\t\tlog.Error(err, \"Failed registering nodes into orchestrator\", \"cluster\", ou.cluster)\n+\t}\n+\t// set readonly in orchestrator if needed\n+\tif err = ou.updateNodesReadOnlyFlagInOrc(instances); err != nil {\n+\t\tlog.Error(err, \"Error setting Master readOnly/writable\", \"instances\", instances, \"cluster\", ou.cluster)\n+\t}\n+\t// update cluster status accordingly with orchestrator\n+\tou.updateStatusFromOrc(instances)\n+\n+\t// get reecoveries for this cluster\n+\tif recoveries, err = ou.orcClient.AuditRecovery(ou.cluster.GetClusterAlias()); err != nil {\n+\t\tlog.Error(err, \"Can't get recoveries from orchestrator\", \"alias\", ou.cluster.GetClusterAlias(), \"cluster\", ou.cluster)\n+\t}\n+\t// update cluster status\n+\tou.updateStatusForRecoveries(recoveries)\n+\t// filter recoveries that can be acknowledged\n+\ttoAck := ou.getRecoveriesToAck(recoveries)\n+\t// acknowledge recoveries\n+\tif err = ou.acknowledgeRecoveries(toAck); err != nil {\n+\t\tlog.Error(err, \"Failed to acknowledge recoveries\", \"cluster\", ou.cluster, \"ack_recoveries\", toAck)\n+\t}\n+\treturn nil\n+}\n+\n+// nolint: gocyclo\n+func (ou *orcUpdater) updateStatusFromOrc(insts InstancesSet) {\n+\t// TODO: improve this code by computing differences between what\n+\t// orchestartor knows and what we know\n+\n+\t// we assume that cluster is in ReadOnly\n+\tisReadOnly := true\n+\n+\t// get maxSlaveLatency for this cluster\n+\tmaxSlaveLatency := defaultMaxSlaveLatency\n+\tif ou.cluster.Spec.MaxSlaveLatency != nil {\n+\t\tmaxSlaveLatency = *ou.cluster.Spec.MaxSlaveLatency\n+\t}\n+\n+\t// nodes that where updated\n+\tupdatedNodes := []string{}\n+\n+\t// update conditions for every node\n+\tfor _, node := range insts {\n+\t\thost := node.Key.Hostname\n+\t\tupdatedNodes = append(updatedNodes, host)\n+\n+\t\tif !node.IsUpToDate {\n+\t\t\tif !node.IsLastCheckValid {\n+\t\t\t\tou.updateNodeCondition(host, api.NodeConditionLagged, core.ConditionUnknown)\n+\t\t\t\tou.updateNodeCondition(host, api.NodeConditionReplicating, core.ConditionUnknown)\n+\t\t\t\tou.updateNodeCondition(host, api.NodeConditionMaster, core.ConditionUnknown)\n+\t\t\t}\n+\t\t\tcontinue\n+\t\t}\n+\n+\t\t// set node Lagged conditions\n+\t\tif !node.SlaveLagSeconds.Valid {\n+\t\t\tou.updateNodeCondition(host, api.NodeConditionLagged, core.ConditionUnknown)\n+\t\t} else if node.SlaveLagSeconds.Int64 <= maxSlaveLatency {\n+\t\t\tou.updateNodeCondition(host, api.NodeConditionLagged, core.ConditionFalse)\n+\t\t} else { // node is behind master\n+\t\t\tou.updateNodeCondition(host, api.NodeConditionLagged, core.ConditionTrue)\n+\t\t}\n+\n+\t\t// set node replicating condition\n+\t\tif node.Slave_SQL_Running && node.Slave_IO_Running {\n+\t\t\tou.updateNodeCondition(host, api.NodeConditionReplicating, core.ConditionTrue)\n+\t\t} else {\n+\t\t\tou.updateNodeCondition(host, api.NodeConditionReplicating, core.ConditionFalse)\n+\t\t}\n+\n+\t\t// set masters on false\n+\t\tou.updateNodeCondition(host, api.NodeConditionMaster, core.ConditionFalse)\n+\n+\t\t// set node read only\n+\t\tif node.ReadOnly {\n+\t\t\tou.updateNodeCondition(host, api.NodeConditionReadOnly, core.ConditionTrue)\n+\t\t} else {\n+\t\t\tou.updateNodeCondition(host, api.NodeConditionReadOnly, core.ConditionFalse)\n+\t\t}\n+\n+\t\t// check if cluster is read only\n+\t\tisReadOnly = isReadOnly && node.ReadOnly\n+\t}\n+\n+\t// set master node Master condition on True\n+\tmaster := insts.DetermineMaster()\n+\tif master == nil {\n+\t\tlog.Error(nil, \"Error acquiring master name\", \"instances\", insts)\n+\t} else {\n+\t\tou.updateNodeCondition(master.Key.Hostname, api.NodeConditionMaster, core.ConditionTrue)\n+\t}\n+\n+\t// set cluster ReadOnly condition\n+\tif isReadOnly {\n+\t\tou.cluster.UpdateStatusCondition(api.ClusterConditionReadOnly,\n+\t\t\tcore.ConditionTrue, \"initializedTrue\", \"settingReadOnlyTrue\")\n+\t} else {\n+\t\tou.cluster.UpdateStatusCondition(api.ClusterConditionReadOnly,\n+\t\t\tcore.ConditionFalse, \"initializedFalse\", \"settingReadOnlyFalse\")\n+\t}\n+\n+\tou.removeNodeConditionNotIn(updatedNodes)\n+}\n+\n+// registerUnregisterNodesInOrc is the functions that tries to register\n+// unregistered nodes and to remove nodes that does not exists.\n+func (ou *orcUpdater) registerUnregisterNodesInOrc(instSet InstancesSet) (InstancesSet, error) {\n+\tvar (\n+\t\terr       error\n+\t\terrMsg    string\n+\t\tinstances InstancesSet\n+\t)\n+\n+\tfor i := 0; i < ou.cluster.Status.ReadyNodes; i++ {\n+\t\thost := ou.cluster.GetPodHostname(i)\n+\t\tif inst := instSet.GetInstance(host); inst == nil {\n+\t\t\t// host is not present into orchestrator\n+\t\t\t// register new host into orchestrator\n+\t\t\tif err = ou.orcClient.Discover(host, mysqlPort); err != nil {\n+\t\t\t\tlog.V(2).Info(\"Failed to register %s with orchestrator\", \"host\", host, \"error\", err)\n+\t\t\t\terrMsg += fmt.Sprintf(\" failed to register %s\", host)\n+\t\t\t}\n+\t\t} else {\n+\t\t\tinstances = append(instances, *inst)\n+\t\t}\n+\t}\n+\n+\t// remove all instances from orchestrator that does not exists in k8s\n+\tfor _, inst := range instSet {\n+\t\tif i := instances.GetInstance(inst.Key.Hostname); i == nil {\n+\t\t\tif err = ou.orcClient.Forget(inst.Key.Hostname, inst.Key.Port); err != nil {\n+\t\t\t\tlog.V(2).Info(\"Failed to forget %s with orchestrator\", \"host\", i.Key.Hostname, \"error\", err)\n+\t\t\t\terrMsg += fmt.Sprintf(\" failed to forget %s\", inst.Key.Hostname)\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif len(errMsg) != 0 {\n+\t\terr = errors.New(errMsg)\n+\t}\n+\treturn instances, err\n+}\n+\n+func (ou *orcUpdater) getRecoveriesToAck(recoveries []orc.TopologyRecovery) []orc.TopologyRecovery {\n+\t// TODO: check for recoveries that need acknowledge, by excluding already acked recoveries\n+\ttoAck := []orc.TopologyRecovery{}\n+\n+\tif len(recoveries) == 0 {\n+\t\treturn toAck\n+\t}\n+\n+\ti, find := condIndexCluster(ou.cluster, api.ClusterConditionReady)\n+\tif !find || ou.cluster.Status.Conditions[i].Status != core.ConditionTrue {\n+\t\tlog.Info(\"[getRecoveriesToAck]: Cluster is not ready for ack.\")\n+\t\treturn toAck\n+\t}\n+\n+\tif time.Since(ou.cluster.Status.Conditions[i].LastTransitionTime.Time).Minutes() < healtyMoreThanMinutes {\n+\t\tlog.Info(\"Stateful set is not ready more then 10 minutes. Don't ack.\")\n+\t\treturn toAck\n+\t}\n+\n+\tfor _, recovery := range recoveries {\n+\t\tif !recovery.Acknowledged {\n+\t\t\t// skip if it's a new recovery, recovery should be older then <healtyMoreThanMinutes> minutes\n+\t\t\tstartTime, err := time.Parse(time.RFC3339, recovery.RecoveryStartTimestamp)\n+\t\t\tif err != nil {\n+\t\t\t\tlog.Error(err, \"Can't parse time: %s for audit recovery.\",\n+\t\t\t\t\t\"recovery\", recovery,\n+\t\t\t\t)\n+\t\t\t\tcontinue\n+\t\t\t}\n+\t\t\tif time.Since(startTime).Minutes() < healtyMoreThanMinutes {\n+\t\t\t\t// skip this recovery\n+\t\t\t\tlog.Error(nil, \"Tries to recover to soon.\", \"recovery\", recovery)\n+\t\t\t\tcontinue\n+\t\t\t}\n+\n+\t\t\ttoAck = append(toAck, recovery)\n+\t\t}\n+\t}\n+\treturn toAck\n+}\n+\n+func (ou *orcUpdater) acknowledgeRecoveries(toAck []orc.TopologyRecovery) error {\n+\tcomment := fmt.Sprintf(\"Statefulset '%s' is healty more then 10 minutes\",\n+\t\tou.cluster.GetNameForResource(api.StatefulSet),\n+\t)\n+\n+\t// acknowledge recoveries\n+\tfor _, recovery := range toAck {\n+\t\tif err := ou.orcClient.AckRecovery(recovery.Id, comment); err != nil {\n+\t\t\treturn err\n+\t\t}\n+\t\tou.recorder.Event(ou.cluster, eventNormal, \"RecoveryAcked\",\n+\t\t\tfmt.Sprintf(\"Recovery with id %d was acked.\", recovery.Id))\n+\t}\n+\n+\treturn nil\n+}\n+\n+func (ou *orcUpdater) updateStatusForRecoveries(recoveries []orc.TopologyRecovery) {\n+\tvar unack []orc.TopologyRecovery\n+\tfor _, recovery := range recoveries {\n+\t\tif !recovery.Acknowledged {\n+\t\t\tunack = append(unack, recovery)\n+\t\t}\n+\t}\n+\n+\tif len(unack) > 0 {\n+\t\tmsg := getRecoveryTextMsg(unack)\n+\t\tou.cluster.UpdateStatusCondition(api.ClusterConditionFailoverAck,\n+\t\t\tcore.ConditionTrue, \"pendingFailoverAckExists\", msg)\n+\t} else {\n+\t\tou.cluster.UpdateStatusCondition(api.ClusterConditionFailoverAck,\n+\t\t\tcore.ConditionFalse, \"noPendingFailoverAckExists\", \"no pending ack\")\n+\t}\n+}\n+\n+// nolint: unparam\n+func condIndexCluster(cluster *wrapcluster.MysqlCluster, ty api.ClusterConditionType) (int, bool) {\n+\tfor i, cond := range cluster.Status.Conditions {\n+\t\tif cond.Type == ty {\n+\t\t\treturn i, true\n+\t\t}\n+\t}\n+\n+\treturn 0, false\n+}\n+\n+func (ou *orcUpdater) updateNodeCondition(host string, cType api.NodeConditionType, status core.ConditionStatus) {\n+\tou.cluster.UpdateNodeConditionStatus(host, cType, status)\n+}\n+\n+func (ou *orcUpdater) removeNodeConditionNotIn(hosts []string) {\n+\tfor _, ns := range ou.cluster.Status.Nodes {\n+\t\tupdated := false\n+\t\tfor _, h := range hosts {\n+\t\t\tif h == ns.Name {\n+\t\t\t\tupdated = true\n+\t\t\t}\n+\t\t}\n+\n+\t\tif !updated {\n+\t\t\tou.updateNodeCondition(ns.Name, api.NodeConditionLagged, core.ConditionUnknown)\n+\t\t\tou.updateNodeCondition(ns.Name, api.NodeConditionReplicating, core.ConditionUnknown)\n+\t\t\tou.updateNodeCondition(ns.Name, api.NodeConditionMaster, core.ConditionUnknown)\n+\t\t}\n+\t}\n+}\n+\n+// set a host writable just if needed\n+func (ou *orcUpdater) setInstWritable(inst orc.Instance) error {\n+\tif inst.ReadOnly {\n+\t\tlog.V(2).Info(fmt.Sprintf(\"set instance %s writable\", inst.Key.Hostname))\n+\t\treturn ou.orcClient.SetHostWritable(inst.Key)\n+\t}\n+\treturn nil\n+}\n+\n+func (ou *orcUpdater) putNodeInMaintenance(inst orc.Instance) error {\n+\n+\tlog.V(2).Info(fmt.Sprintf(\"set instance %s in maintenance\", inst.Key.Hostname))\n+\treturn ou.orcClient.BeginMaintenance(inst.Key, \"mysqlcontroller\", \"clusterReadOnly\")\n+\n+}\n+\n+func (ou *orcUpdater) getNodeOutOfMaintenance(inst orc.Instance) error {\n+\n+\tlog.V(2).Info(fmt.Sprintf(\"set instance %s out of maintenance\", inst.Key.Hostname))\n+\treturn ou.orcClient.EndMaintenance(inst.Key)\n+\n+}\n+\n+// set a host read only just if needed\n+func (ou *orcUpdater) setInstReadOnly(inst orc.Instance) error {","path":"pkg/controller/orchestrator/orchestrator_reconcile.go","position":352,"original_position":352,"commit_id":"fe276939bb619768ef162d85f91ab7ff6c6b9e1b","original_commit_id":"fe276939bb619768ef162d85f91ab7ff6c6b9e1b","user":{"login":"calind","id":266375,"node_id":"MDQ6VXNlcjI2NjM3NQ==","avatar_url":"https://avatars2.githubusercontent.com/u/266375?v=4","gravatar_id":"","url":"https://api.github.com/users/calind","html_url":"https://github.com/calind","followers_url":"https://api.github.com/users/calind/followers","following_url":"https://api.github.com/users/calind/following{/other_user}","gists_url":"https://api.github.com/users/calind/gists{/gist_id}","starred_url":"https://api.github.com/users/calind/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/calind/subscriptions","organizations_url":"https://api.github.com/users/calind/orgs","repos_url":"https://api.github.com/users/calind/repos","events_url":"https://api.github.com/users/calind/events{/privacy}","received_events_url":"https://api.github.com/users/calind/received_events","type":"User","site_admin":false},"body":"`setReadOnlyNode`","created_at":"2018-09-17T13:41:20Z","updated_at":"2018-09-17T13:51:37Z","html_url":"https://github.com/presslabs/mysql-operator/pull/110#discussion_r218072166","pull_request_url":"https://api.github.com/repos/presslabs/mysql-operator/pulls/110","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/presslabs/mysql-operator/pulls/comments/218072166"},"html":{"href":"https://github.com/presslabs/mysql-operator/pull/110#discussion_r218072166"},"pull_request":{"href":"https://api.github.com/repos/presslabs/mysql-operator/pulls/110"}}},"pull_request":{"url":"https://api.github.com/repos/presslabs/mysql-operator/pulls/110","id":211725187,"node_id":"MDExOlB1bGxSZXF1ZXN0MjExNzI1MTg3","html_url":"https://github.com/presslabs/mysql-operator/pull/110","diff_url":"https://github.com/presslabs/mysql-operator/pull/110.diff","patch_url":"https://github.com/presslabs/mysql-operator/pull/110.patch","issue_url":"https://api.github.com/repos/presslabs/mysql-operator/issues/110","number":110,"state":"open","locked":false,"title":"[kubebuilder] Orchestrator to mysql controller","user":{"login":"AMecea","id":11756727,"node_id":"MDQ6VXNlcjExNzU2NzI3","avatar_url":"https://avatars3.githubusercontent.com/u/11756727?v=4","gravatar_id":"","url":"https://api.github.com/users/AMecea","html_url":"https://github.com/AMecea","followers_url":"https://api.github.com/users/AMecea/followers","following_url":"https://api.github.com/users/AMecea/following{/other_user}","gists_url":"https://api.github.com/users/AMecea/gists{/gist_id}","starred_url":"https://api.github.com/users/AMecea/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AMecea/subscriptions","organizations_url":"https://api.github.com/users/AMecea/orgs","repos_url":"https://api.github.com/users/AMecea/repos","events_url":"https://api.github.com/users/AMecea/events{/privacy}","received_events_url":"https://api.github.com/users/AMecea/received_events","type":"User","site_admin":false},"body":"","created_at":"2018-08-29T11:51:10Z","updated_at":"2018-09-17T13:51:36Z","closed_at":null,"merged_at":null,"merge_commit_sha":"17d744c9b9b66885a933213e09310527279e100b","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/presslabs/mysql-operator/pulls/110/commits","review_comments_url":"https://api.github.com/repos/presslabs/mysql-operator/pulls/110/comments","review_comment_url":"https://api.github.com/repos/presslabs/mysql-operator/pulls/comments{/number}","comments_url":"https://api.github.com/repos/presslabs/mysql-operator/issues/110/comments","statuses_url":"https://api.github.com/repos/presslabs/mysql-operator/statuses/fe276939bb619768ef162d85f91ab7ff6c6b9e1b","head":{"label":"presslabs:kb-mc-2-2","ref":"kb-mc-2-2","sha":"fe276939bb619768ef162d85f91ab7ff6c6b9e1b","user":{"login":"presslabs","id":1033395,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEwMzMzOTU=","avatar_url":"https://avatars0.githubusercontent.com/u/1033395?v=4","gravatar_id":"","url":"https://api.github.com/users/presslabs","html_url":"https://github.com/presslabs","followers_url":"https://api.github.com/users/presslabs/followers","following_url":"https://api.github.com/users/presslabs/following{/other_user}","gists_url":"https://api.github.com/users/presslabs/gists{/gist_id}","starred_url":"https://api.github.com/users/presslabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/presslabs/subscriptions","organizations_url":"https://api.github.com/users/presslabs/orgs","repos_url":"https://api.github.com/users/presslabs/repos","events_url":"https://api.github.com/users/presslabs/events{/privacy}","received_events_url":"https://api.github.com/users/presslabs/received_events","type":"Organization","site_admin":false},"repo":{"id":109712767,"node_id":"MDEwOlJlcG9zaXRvcnkxMDk3MTI3Njc=","name":"mysql-operator","full_name":"presslabs/mysql-operator","private":false,"owner":{"login":"presslabs","id":1033395,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEwMzMzOTU=","avatar_url":"https://avatars0.githubusercontent.com/u/1033395?v=4","gravatar_id":"","url":"https://api.github.com/users/presslabs","html_url":"https://github.com/presslabs","followers_url":"https://api.github.com/users/presslabs/followers","following_url":"https://api.github.com/users/presslabs/following{/other_user}","gists_url":"https://api.github.com/users/presslabs/gists{/gist_id}","starred_url":"https://api.github.com/users/presslabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/presslabs/subscriptions","organizations_url":"https://api.github.com/users/presslabs/orgs","repos_url":"https://api.github.com/users/presslabs/repos","events_url":"https://api.github.com/users/presslabs/events{/privacy}","received_events_url":"https://api.github.com/users/presslabs/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/presslabs/mysql-operator","description":"Bulletproof MySQL on Kubernetes","fork":false,"url":"https://api.github.com/repos/presslabs/mysql-operator","forks_url":"https://api.github.com/repos/presslabs/mysql-operator/forks","keys_url":"https://api.github.com/repos/presslabs/mysql-operator/keys{/key_id}","collaborators_url":"https://api.github.com/repos/presslabs/mysql-operator/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/presslabs/mysql-operator/teams","hooks_url":"https://api.github.com/repos/presslabs/mysql-operator/hooks","issue_events_url":"https://api.github.com/repos/presslabs/mysql-operator/issues/events{/number}","events_url":"https://api.github.com/repos/presslabs/mysql-operator/events","assignees_url":"https://api.github.com/repos/presslabs/mysql-operator/assignees{/user}","branches_url":"https://api.github.com/repos/presslabs/mysql-operator/branches{/branch}","tags_url":"https://api.github.com/repos/presslabs/mysql-operator/tags","blobs_url":"https://api.github.com/repos/presslabs/mysql-operator/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/presslabs/mysql-operator/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/presslabs/mysql-operator/git/refs{/sha}","trees_url":"https://api.github.com/repos/presslabs/mysql-operator/git/trees{/sha}","statuses_url":"https://api.github.com/repos/presslabs/mysql-operator/statuses/{sha}","languages_url":"https://api.github.com/repos/presslabs/mysql-operator/languages","stargazers_url":"https://api.github.com/repos/presslabs/mysql-operator/stargazers","contributors_url":"https://api.github.com/repos/presslabs/mysql-operator/contributors","subscribers_url":"https://api.github.com/repos/presslabs/mysql-operator/subscribers","subscription_url":"https://api.github.com/repos/presslabs/mysql-operator/subscription","commits_url":"https://api.github.com/repos/presslabs/mysql-operator/commits{/sha}","git_commits_url":"https://api.github.com/repos/presslabs/mysql-operator/git/commits{/sha}","comments_url":"https://api.github.com/repos/presslabs/mysql-operator/comments{/number}","issue_comment_url":"https://api.github.com/repos/presslabs/mysql-operator/issues/comments{/number}","contents_url":"https://api.github.com/repos/presslabs/mysql-operator/contents/{+path}","compare_url":"https://api.github.com/repos/presslabs/mysql-operator/compare/{base}...{head}","merges_url":"https://api.github.com/repos/presslabs/mysql-operator/merges","archive_url":"https://api.github.com/repos/presslabs/mysql-operator/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/presslabs/mysql-operator/downloads","issues_url":"https://api.github.com/repos/presslabs/mysql-operator/issues{/number}","pulls_url":"https://api.github.com/repos/presslabs/mysql-operator/pulls{/number}","milestones_url":"https://api.github.com/repos/presslabs/mysql-operator/milestones{/number}","notifications_url":"https://api.github.com/repos/presslabs/mysql-operator/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/presslabs/mysql-operator/labels{/name}","releases_url":"https://api.github.com/repos/presslabs/mysql-operator/releases{/id}","deployments_url":"https://api.github.com/repos/presslabs/mysql-operator/deployments","created_at":"2017-11-06T15:24:51Z","updated_at":"2018-09-17T01:32:18Z","pushed_at":"2018-09-17T12:57:59Z","git_url":"git://github.com/presslabs/mysql-operator.git","ssh_url":"git@github.com:presslabs/mysql-operator.git","clone_url":"https://github.com/presslabs/mysql-operator.git","svn_url":"https://github.com/presslabs/mysql-operator","homepage":"https://www.presslabs.com","size":42935,"stargazers_count":49,"watchers_count":49,"language":"Go","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":9,"mirror_url":null,"archived":false,"open_issues_count":29,"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":9,"open_issues":29,"watchers":49,"default_branch":"master"}},"base":{"label":"presslabs:kubebuilder","ref":"kubebuilder","sha":"1ba0d7d330a8a2c1fb57fa38b0aa21c6d994c9fe","user":{"login":"presslabs","id":1033395,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEwMzMzOTU=","avatar_url":"https://avatars0.githubusercontent.com/u/1033395?v=4","gravatar_id":"","url":"https://api.github.com/users/presslabs","html_url":"https://github.com/presslabs","followers_url":"https://api.github.com/users/presslabs/followers","following_url":"https://api.github.com/users/presslabs/following{/other_user}","gists_url":"https://api.github.com/users/presslabs/gists{/gist_id}","starred_url":"https://api.github.com/users/presslabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/presslabs/subscriptions","organizations_url":"https://api.github.com/users/presslabs/orgs","repos_url":"https://api.github.com/users/presslabs/repos","events_url":"https://api.github.com/users/presslabs/events{/privacy}","received_events_url":"https://api.github.com/users/presslabs/received_events","type":"Organization","site_admin":false},"repo":{"id":109712767,"node_id":"MDEwOlJlcG9zaXRvcnkxMDk3MTI3Njc=","name":"mysql-operator","full_name":"presslabs/mysql-operator","private":false,"owner":{"login":"presslabs","id":1033395,"node_id":"MDEyOk9yZ2FuaXphdGlvbjEwMzMzOTU=","avatar_url":"https://avatars0.githubusercontent.com/u/1033395?v=4","gravatar_id":"","url":"https://api.github.com/users/presslabs","html_url":"https://github.com/presslabs","followers_url":"https://api.github.com/users/presslabs/followers","following_url":"https://api.github.com/users/presslabs/following{/other_user}","gists_url":"https://api.github.com/users/presslabs/gists{/gist_id}","starred_url":"https://api.github.com/users/presslabs/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/presslabs/subscriptions","organizations_url":"https://api.github.com/users/presslabs/orgs","repos_url":"https://api.github.com/users/presslabs/repos","events_url":"https://api.github.com/users/presslabs/events{/privacy}","received_events_url":"https://api.github.com/users/presslabs/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/presslabs/mysql-operator","description":"Bulletproof MySQL on Kubernetes","fork":false,"url":"https://api.github.com/repos/presslabs/mysql-operator","forks_url":"https://api.github.com/repos/presslabs/mysql-operator/forks","keys_url":"https://api.github.com/repos/presslabs/mysql-operator/keys{/key_id}","collaborators_url":"https://api.github.com/repos/presslabs/mysql-operator/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/presslabs/mysql-operator/teams","hooks_url":"https://api.github.com/repos/presslabs/mysql-operator/hooks","issue_events_url":"https://api.github.com/repos/presslabs/mysql-operator/issues/events{/number}","events_url":"https://api.github.com/repos/presslabs/mysql-operator/events","assignees_url":"https://api.github.com/repos/presslabs/mysql-operator/assignees{/user}","branches_url":"https://api.github.com/repos/presslabs/mysql-operator/branches{/branch}","tags_url":"https://api.github.com/repos/presslabs/mysql-operator/tags","blobs_url":"https://api.github.com/repos/presslabs/mysql-operator/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/presslabs/mysql-operator/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/presslabs/mysql-operator/git/refs{/sha}","trees_url":"https://api.github.com/repos/presslabs/mysql-operator/git/trees{/sha}","statuses_url":"https://api.github.com/repos/presslabs/mysql-operator/statuses/{sha}","languages_url":"https://api.github.com/repos/presslabs/mysql-operator/languages","stargazers_url":"https://api.github.com/repos/presslabs/mysql-operator/stargazers","contributors_url":"https://api.github.com/repos/presslabs/mysql-operator/contributors","subscribers_url":"https://api.github.com/repos/presslabs/mysql-operator/subscribers","subscription_url":"https://api.github.com/repos/presslabs/mysql-operator/subscription","commits_url":"https://api.github.com/repos/presslabs/mysql-operator/commits{/sha}","git_commits_url":"https://api.github.com/repos/presslabs/mysql-operator/git/commits{/sha}","comments_url":"https://api.github.com/repos/presslabs/mysql-operator/comments{/number}","issue_comment_url":"https://api.github.com/repos/presslabs/mysql-operator/issues/comments{/number}","contents_url":"https://api.github.com/repos/presslabs/mysql-operator/contents/{+path}","compare_url":"https://api.github.com/repos/presslabs/mysql-operator/compare/{base}...{head}","merges_url":"https://api.github.com/repos/presslabs/mysql-operator/merges","archive_url":"https://api.github.com/repos/presslabs/mysql-operator/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/presslabs/mysql-operator/downloads","issues_url":"https://api.github.com/repos/presslabs/mysql-operator/issues{/number}","pulls_url":"https://api.github.com/repos/presslabs/mysql-operator/pulls{/number}","milestones_url":"https://api.github.com/repos/presslabs/mysql-operator/milestones{/number}","notifications_url":"https://api.github.com/repos/presslabs/mysql-operator/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/presslabs/mysql-operator/labels{/name}","releases_url":"https://api.github.com/repos/presslabs/mysql-operator/releases{/id}","deployments_url":"https://api.github.com/repos/presslabs/mysql-operator/deployments","created_at":"2017-11-06T15:24:51Z","updated_at":"2018-09-17T01:32:18Z","pushed_at":"2018-09-17T12:57:59Z","git_url":"git://github.com/presslabs/mysql-operator.git","ssh_url":"git@github.com:presslabs/mysql-operator.git","clone_url":"https://github.com/presslabs/mysql-operator.git","svn_url":"https://github.com/presslabs/mysql-operator","homepage":"https://www.presslabs.com","size":42935,"stargazers_count":49,"watchers_count":49,"language":"Go","has_issues":true,"has_projects":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":9,"mirror_url":null,"archived":false,"open_issues_count":29,"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":9,"open_issues":29,"watchers":49,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/presslabs/mysql-operator/pulls/110"},"html":{"href":"https://github.com/presslabs/mysql-operator/pull/110"},"issue":{"href":"https://api.github.com/repos/presslabs/mysql-operator/issues/110"},"comments":{"href":"https://api.github.com/repos/presslabs/mysql-operator/issues/110/comments"},"review_comments":{"href":"https://api.github.com/repos/presslabs/mysql-operator/pulls/110/comments"},"review_comment":{"href":"https://api.github.com/repos/presslabs/mysql-operator/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/presslabs/mysql-operator/pulls/110/commits"},"statuses":{"href":"https://api.github.com/repos/presslabs/mysql-operator/statuses/fe276939bb619768ef162d85f91ab7ff6c6b9e1b"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"calind"}}