ã¯ããã« ããã«ã¡ã¯ããããã£æ ªåŒäŒç€Ÿã®æ·»éç¿å€ªã§ãã ä»å㯠Amazon RDS ã®ã¡ã³ããã³ã¹ã€ãã³ããSlackã«éç¥ããæ©æ§ãäœæãã話ãå
±æããŸãã èæ¯ å
æ¥ãAmazon RDSã®ã¡ã³ããã³ã¹ãåå ãšããŠããšããã¢ããªã±ãŒã·ã§ã³ãäžæçã«ããŠã³ããŸãããæ¯ãè¿ãäžã§ã¡ã³ããã³ã¹ã€ãã³ããèŠéããŠãããšãã課é¡ãäžãããåçºé²æ¢çã®äžç°ãšããŠãã¡ã³ããã³ã¹ã€ãã³ãã®éç¥ãè¡ãæ©æ§ãäœæããŸããã Amazon RDSã®ã¡ã³ããã³ã¹ã€ãã³ããSlackã«éç¥ãã ããŒã ã§æ
£ããŠãã人ãå€ãGoãå©çšããŠããã°ã©ã ãäœæããŸããããŸã AWS Lambda ãåºç€ãšãã宿çã«çºç«ãããããã« Amazon EventBridge ãå©çšããŸããã ã¢ãŒããã¯ãã£ã¯ã以äžã®éãã§ãã ãã£ã¬ã¯ããªæ§æã¯ä»¥äžã®éãã§ãããªãåäœãã¹ãçšã®ãã¡ã€ã«ãREADME.mdãªã©ã¯é€ããŠããŸãã src ââlib â ââget_events.go â ââlogger.go â ââslack.go ââgo.sum ââgo.mod ââmain.go 次ã«ãsrc/lib/logger.goãsrc/go.sum以å€ã®ã³ãŒãã以äžã«ç€ºããŸãã ãŸãã¯src/main.goã§ãããã¡ãã§ã¯ã»ãã·ã§ã³äœæãªã©å®æœããŠããŸãã package main import ( "context" "os" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/rds" lib "github.com/sample/lib" "go.uber.org/zap" ) func handler(ctx context.Context, event events.CloudWatchEvent) { // NOTE: ãã°ã®èšå® logger := lib.LoggerInit() defer logger.Sync() // NOTE: Slack WebHook URLãç°å¢å€æ°ããååŸ webhookURL := os.Getenv("SLACK_WEBHOOK_URL") if webhookURL == "" { logger.Warn("msg", zap.String("id", "DB-MAINTENANCE-NOTIFICATION-001"), zap.String("body", "Slack WebHook URLãèšå®ãããŠããªã"), ) return } // NOTE: ã»ãã·ã§ã³ãäœæ sess, err := session.NewSessionWithOptions(session.Options{ Config: aws.Config{ Region: aws.String("ap-northeast-1"), // NOTE: é©åãªãªãŒãžã§ã³ã«çœ®ãæããŠãã ãã }, }) if err != nil { logger.Fatal("msg", zap.String("id", "DB-MAINTENANCE-NOTIFICATION-002"), zap.String("body", "ã»ãã·ã§ã³äœæãšã©ãŒ"), zap.Error(err), ) return } rdsClient := &lib.RDSClient{ Client: rds.New(sess), } maintenanceActions, err := rdsClient.DescribePendingMaintenanceActions() if err != nil { logger.Fatal("msg", zap.String("id", "DB-MAINTENANCE-NOTIFICATION-003"), zap.String("body", "ã¡ã³ããã³ã¹æ
å ±ååŸãšã©ãŒ"), zap.Error(err), ) return } lib.NotifySlackWithWebhook(maintenanceActions, webhookURL) } func main() { lambda.Start(handler) } 次ã«src/lib/get_events.goã瀺ããŸãããã¡ãã§ã¯ DescribePendingMaintenanceActions ãåŒã³åºããããŒã¿åœ¢åŒãæŽããäžã§åŒã³åºãå
ã«ããŒã¿ãè¿åŽããŸãã package lib import ( "strconv" "strings" "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/rds" "github.com/aws/aws-sdk-go/service/rds/rdsiface" ) type RDSClient struct { Client rdsiface.RDSAPI } // NOTE: DescribePendingMaintenanceActionsãåŒã³åºããŠãã¡ã³ããã³ã¹æ
å ±ãååŸãã颿° func (c *RDSClient) DescribePendingMaintenanceActions() ([]*rds.ResourcePendingMaintenanceActions, error) { // NOTE: ãã¹ãŠã®ã€ãã³ãããŒã¿ãåã£ãŠãã input := &rds.DescribePendingMaintenanceActionsInput{} result, err := c.Client.DescribePendingMaintenanceActions(input) if err != nil { return nil, err } return result.PendingMaintenanceActions, nil } // NOTE: è€æ°ã¢ã€ãã ãæãå Žåã¯çµåããŠè¿åŽãã颿°ãããããã£ãæ¬ æããŠããå Žåã¯ç©ºçœãšãªã func getDescriptionAndTime(details []*rds.PendingMaintenanceAction) (string, string) { var descriptions []string var currentApplyDates []string for i, detail := range details { description := strconv.Itoa(i+1) + ": " currentApplyDate := strconv.Itoa(i+1) + ": " if detail.Description != nil { description += aws.StringValue(detail.Description) } if detail.CurrentApplyDate != nil { currentApplyDate += aws.TimeValue(detail.CurrentApplyDate).Format(time.RFC3339) } descriptions = append(descriptions, description) currentApplyDates = append(currentApplyDates, currentApplyDate) } return strings.Join(descriptions, "\n"), strings.Join(currentApplyDates, "\n") } 次ã«src/lib/slack.goã瀺ããŸãããã¡ãã§ã¯ååŸã§ããã¡ã³ããã³ã¹ã€ãã³ããããšã«Slackãžéç¥ãè¡ããŸãããªãæ
å ±ãç¬æã«ç¢ºèªã§ãããããDescriptionãšCurrentApplyDateã®ã¿ãè¿åŽããããã«ããŸããã package lib import ( "bytes" "encoding/json" "fmt" "net/http" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/rds" ) func NotifySlackWithWebhook(maintenanceActions []*rds.ResourcePendingMaintenanceActions, webhookURL string) error { // NOTE: ã€ãã³ãããªãå Žåã®ä»£æ¿ã¡ãã»ãŒãž if len(maintenanceActions) == 0 { message := map[string]interface{}{ "text": "No pending RDS maintenance actions at the moment.", } payload, err := json.Marshal(message) if err != nil { return err } resp, err := http.Post(webhookURL, "application/json", bytes.NewBuffer(payload)) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("Slack notification failed with status code %d", resp.StatusCode) } } // NOTE: ã€ãã³ããããå Žåãã¡ã³ããã³ã¹ã¢ã¯ã·ã§ã³ã®éç¥ãéä¿¡ for _, action := range maintenanceActions { description, currentApplyDate := getDescriptionAndTime(action.PendingMaintenanceActionDetails) message := map[string]interface{}{ "text": "Pending RDS Maintenance Action", "attachments": []map[string]interface{}{ { "title": "Pending RDS Maintenance Action", "text": "The following maintenance action is pending:", "fields": []map[string]interface{}{ { "title": "DB Instance ID", "value": aws.StringValue(action.ResourceIdentifier), "short": true, }, { "title": "Description", "value": description, "short": false, }, { "title": "Current Apply Date", "value": currentApplyDate, "short": false, }, }, }, }, } payload, err := json.Marshal(message) if err != nil { return err } resp, err := http.Post(webhookURL, "application/json", bytes.NewBuffer(payload)) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("Slack notification failed with status code %d", resp.StatusCode) } } return nil } æåŸã«src/go.modã瀺ããŸãã module github.com/sample go 1.19 require ( github.com/aws/aws-lambda-go v1.41.0 github.com/aws/aws-sdk-go v1.45.24 go.uber.org/zap v1.26.0 ) require ( github.com/jmespath/go-jmespath v0.4.0 // indirect go.uber.org/multierr v1.10.0 // indirect ) éç¥ã®èŠãç®ã¯ä»¥äžã®éãã§ãã ããã¡ã³ããã³ã¹ã€ãã³ããããå Žåã¯ã ãšãªããç¡ãå Žåã«ã¯ä»¥äžã®éãã§ãã ãããã« æ¬èšäºã§ã¯ãAmazon RDSã®ã¡ã³ããã³ã¹ã€ãã³ããSlackã«éç¥ããæ©æ§ãäœæãã話ã«ã€ããŠè¿°ã¹ãŸããããã®æ©æ§ã«ããã¡ã³ããã³ã¹ã€ãã³ããèŠéããªã¹ã¯ãæžããŸãã åèèšäº Amazon RDSã®ã¡ã³ããã³ã¹ã«ã€ããŠèª¿ã¹ãŠã¿ã We are hiring! ãããã£ã§ã¯ãããŸããŸãªãããã¯ããžææŠãããšã³ãžãã¢ãçµ¶è³åéäžã§ãïŒ ãèå³ã®ããæ¹ã¯ä»¥äžã®æ¡çšãµã€ããããæ°è»œã«ãé£çµ¡ãã ããïŒ ãããã£æ ªåŒäŒç€Ÿæ¡çšæ
å ± Tech TalkãMeetUpãéå¬ããŠãããŸãïŒ ãã¡ãããæ°è»œã«ãå¿åãã ããïŒ Event â NIFTY engineering