diff --git a/.gitignore b/.gitignore index d8c7d65..917c9aa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ build/ tmp/ vendor/ + .DS_Store +Makefile.local diff --git a/Makefile b/Makefile index a5251e7..0e905cf 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,6 @@ GO_ENV ?= CGO_ENABLED=0 GO_PKGS ?= $(shell go list ./... | grep -v /vendor/) GO ?= go -GODEP ?= godep GOLINT ?= golint MOCKGEN ?= mockgen DOCKER ?= docker @@ -28,6 +27,10 @@ lint: test: $(GO_ENV) $(GO) test -v $(GO_PKGS) +.PHONY: vendor +vendor: + $(GO_ENV) $(GO) mod vendor + .PHONY: docker-build docker-build: DOCKER_TAG ?= latest docker-build: diff --git a/gke/gke.go b/gke/gke.go index 4e34cec..5641274 100644 --- a/gke/gke.go +++ b/gke/gke.go @@ -33,6 +33,7 @@ const ( NodeZoneLabel = "failure-domain.beta.kubernetes.io/zone" ResourceEvictionKind = "Eviction" ResourceEvictionName = "pods/eviction" + NodeNameMaxLength = 37 ) // @@ -383,6 +384,9 @@ func (cli *client) toNode(in coreV1.Node) *Node { return nil } nodeNamePrefix := "gke-" + cli.clusterName + "-" + pool + if len(nodeNamePrefix) > NodeNameMaxLength { + nodeNamePrefix = nodeNamePrefix[:NodeNameMaxLength] + } if cli.clusterName != "" && !strings.HasPrefix(in.Name, nodeNamePrefix) { // n.ClusterName is empty string log.Errorf("Ignore node because unexpected node name prefix: name=%s, prefix=%s", in.Name, nodeNamePrefix) return nil diff --git a/report/report.go b/report/report.go index 483d95f..1763411 100644 --- a/report/report.go +++ b/report/report.go @@ -82,7 +82,6 @@ func (r *Result) GetDetailLinks() string { } return "https://console.cloud.google.com/logs/query;query=resource.type%3D%22k8s_container%22%0A" + "resource.labels.cluster_name%3D%22" + r.Cluster.Name + "%22%0A" + - "resource.labels.namespace_name%3D%22ops%22%0A" + "resource.labels.pod_name%3D%22" + r.hostname + "%22%0A" + "timestamp%3E%3D%22" + r.startTime.Format(time.RFC3339) + "%22;summaryFields=:true:32:beginning?project=" + r.projectID } diff --git a/report/slack.go b/report/slack.go index 832c5ff..4489745 100644 --- a/report/slack.go +++ b/report/slack.go @@ -2,12 +2,15 @@ package report import ( "fmt" + "math" "strings" "time" "github.com/slack-go/slack" ) +const bulkMaxLength = 20 + // type slackReporter struct { cli *slack.Client @@ -115,37 +118,31 @@ func (s *slackReporter) Report(result *Result) error { Value: time.Now().In(timeZone).Format(time.RFC3339), Short: true, }, - { - Title: "Active node pools", - Value: s.WrapTextsInCodeBlock(activeNodePoolNameLinks), - }, - { - Title: "Active nodes", - Value: s.WrapTextsInCodeBlock(activeNodeNameLinks), - }, } // - if len(targetPreemptibleNode) > 0 { - fields = append(fields, slack.AttachmentField{ - Title: "Refresh target preemptible node", - Value: s.WrapTextsInCodeBlock(targetPreemptibleNode), - }) - } - if len(targetOndemandAutoscaleNode) > 0 { - fields = append(fields, slack.AttachmentField{ - Title: "Refresh target ondemand auto scale node", - Value: s.WrapTextsInCodeBlock(targetOndemandAutoscaleNode), - }) - } + detailFields := make([]slack.AttachmentField, len(fields)) + copy(detailFields, fields) + + // + detailFields = s.appendField(detailFields, "Active node pools", activeNodePoolNameLinks) + detailFields = s.appendField(detailFields, "Active nodes", activeNodeNameLinks) + detailFields = s.appendField(detailFields, "Refresh target preemptible node", targetPreemptibleNode) + detailFields = s.appendField(detailFields, "Refresh target ondemand auto scale node", targetOndemandAutoscaleNode) + + // if message != "" { fields = append(fields, slack.AttachmentField{ Title: "Message", Value: s.WrapTextInCodeBlock(message), }) + detailFields = append(detailFields, slack.AttachmentField{ + Title: "Message", + Value: s.WrapTextInCodeBlock(message), + }) } - // + // write summary opts := []slack.MsgOption{ slack.MsgOptionAsUser(true), slack.MsgOptionDisableLinkUnfurl(), @@ -155,10 +152,62 @@ func (s *slackReporter) Report(result *Result) error { Color: color, }), } - _, _, err := s.cli.PostMessage(s.channelID, opts...) + _, ts, err := s.cli.PostMessage(s.channelID, opts...) + if err != nil { + return err + } + + // write detail to thread + opts = []slack.MsgOption{ + slack.MsgOptionAsUser(true), + slack.MsgOptionDisableLinkUnfurl(), + slack.MsgOptionText(fmt.Sprintf("%s (detail)", title), false), + slack.MsgOptionTS(ts), + slack.MsgOptionAttachments(slack.Attachment{ + Fields: detailFields, + Color: color, + }), + } + _, _, err = s.cli.PostMessage(s.channelID, opts...) return err } +// +func (s *slackReporter) appendField(fields []slack.AttachmentField, title string, values []string) []slack.AttachmentField { + if len(values) == 0 { + return fields + } + if len(values) < bulkMaxLength { + fields = append(fields, slack.AttachmentField{ + Title: title, + Value: s.WrapTextsInCodeBlock(values), + }) + return fields + } + idx := 1 + pages := int(math.Ceil(float64(len(values)) / float64(bulkMaxLength))) + bulkMessages := make([]string, 0, bulkMaxLength) + for _, v := range values { + bulkMessages = append(bulkMessages, v) + // add field if bulk messages length is equals to bulk post max length. + if len(bulkMessages) == bulkMaxLength { + fields = append(fields, slack.AttachmentField{ + Title: fmt.Sprintf("%s (%d/%d)", title, idx, pages), + Value: s.WrapTextsInCodeBlock(bulkMessages), + }) + idx++ // increment + bulkMessages = make([]string, 0, bulkMaxLength) // clear + } + } + if len(bulkMessages) > 0 { + fields = append(fields, slack.AttachmentField{ + Title: fmt.Sprintf("%s (%d/%d)", title, idx, pages), + Value: s.WrapTextsInCodeBlock(bulkMessages), + }) + } + return fields +} + // WrapTextInCodeBlock wraps a string into a code-block formatted string func (s *slackReporter) WrapTextInCodeBlock(text string) string { return fmt.Sprintf("```\n%s\n```", text)