3年前に作った、DigitalOceanで毎月の請求額をシェルスクリプトでSlack通知(CircleCI/スケジュールトリガー)させていましたが、勉強がてらGoに移行してみました。かつ、今ChatGPTが流行りまくっていますが、コードのベースは作ってくれたとしても全く動かなかったので、デバッグして修正してみました。
ChatGPT使ってGo書いてるけど、全く動かなくて吹いた。
— adachin👾SRE (@adachin0817) April 30, 2023
add billing/main.go
- go version 1.20.3
- 流れ
- APIのHTTPリクエストを作成/送信
- レスポンスボディをJSONとしてパース
- 請求履歴を取得
- 取得した請求履歴を使用
- Slackに通知するメッセージを作成
- Slack Incoming Webhook URLを設定
- SlackにPOSTするJSONデータを作成
- SlackのHTTPリクエストを作成/Slackに通知。
- APIのHTTPリクエストを作成/送信
- 環境変数
- DO_TOKEN
- SLACK_URL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
package main import ( "encoding/json" "fmt" "io/ioutil" "net/http" "os" "strings" ) type Attachment struct { Color string `json:"color"` Text string `json:"text"` } func main() { endpoint := "https://api.digitalocean.com/v2/customers/my/billing_history" token := os.Getenv("DO_TOKEN") slack_url := os.Getenv("SLACK_URL") // HTTPリクエストを作成 req, err := http.NewRequest("GET", endpoint, nil) if err != nil { panic(err) } // アクセストークンをHTTPリクエストヘッダーに設定 req.Header.Set("Authorization", "Bearer "+token) // HTTPリクエストを送信 client := &http.Client{} res, err := client.Do(req) if err != nil { panic(err) } // レスポンスボディを読み取り defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { panic(err) } // レスポンスボディをJSONとしてパースして、請求履歴を取得 var data map[string]interface{} err = json.Unmarshal(body, &data) if err != nil { panic(err) } // 請求履歴を取得 billingHistory := data["billing_history"].([]interface{}) latestBill := billingHistory[0].(map[string]interface{}) date := latestBill["date"].(string) amount := latestBill["amount"].(string) // Slackに通知するメッセージを作成 message := fmt.Sprintf("https://hoge \n ・ %s \n ・ $%s ", date, amount) // debug //fmt.Println(message) // SlackにPOSTするJSONデータを作成 attachment := Attachment{ Color: "#240bde", Text: message, } attachments := []Attachment{attachment} payload := map[string]interface{}{ "text": "Check billing! :cloud:", "attachments": attachments, } jsonValue, err := json.Marshal(payload) if err != nil { panic(err) } reader := strings.NewReader(string(jsonValue)) // Slack HTTPリクエストを作成 req, err = http.NewRequest("POST", slack_url, reader) if err != nil { panic(err) } // Content-Typeを設定 req.Header.Set("Content-Type", "application/json") // Slack HTTPリクエストを送信 res, err = client.Do(req) if err != nil { panic(err) } } |
ちなみに latestBillを fmt.Println
すると以下のように出てきます。
1 |
map[amount:-73.66 date:2023-05-01T10:26:11Z description:Payment (Visa xxxxx) receipt_id:xxxxxxx type:Payment] |
CircleCI
- .circleci/config.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
version: 2.1 orbs: slack: circleci/slack@4.10.1 references: default_config: &default_config docker: - image: golang:1.20.3 working_directory: ~/project commands: slack-notify-fail: description: "Slack notify fail" steps: - slack/notify: channel: ci_cd event: fail template: basic_fail_1 slack-notify-success: description: "Slack notify success" steps: - slack/notify: channel: ci_cd event: pass template: basic_success_1 jobs: billing_run: <<: *default_config steps: - checkout - run: name : update and install curl jq command: | apt-get update && apt-get install curl jq -y - run: name : DigitalOcean billing_run command: | go run ~/project/billing/main.go - slack-notify-fail - slack-notify-success workflows: version: 2.1 cron_schedule: triggers: - schedule: cron: "0 0 2 * *" filters: branches: only: master jobs: - billing_run |
golangコンテナに変更してSlack Orbsもバージョン3だったので、4にバージョンアップしました。(通知方法はTemplates)ジョブのスケジュールトリガーは毎月2日0時のまま実行となります。
Test
- CircleCI
- Slack
まとめ
おっし、シェルからGoに移行できたからブログ書こう。久しぶりにGo書いたけどわからん。
— adachin👾SRE (@adachin0817) May 2, 2023
シェル芸からChatGPTを使ってGoに移行することができました。ChatGPTのメリットはプログラムを書く際に、学習の速度が向上します。やはり人のコードを真似して書くのが手っ取り早いので、ChatGPTはバシバシ活用していきましょう!
てか機能ごとに関数にしたほうが可読性が良いので、後でリファクタリングします。深夜3時のブログは眠いんだわ。
リファクタリング
そして時は2024/01/20!リファクタリングしたああああ!これでメンテナンス性上がった。
- 変更点
- 関数の分割とモジュール化
- エラーハンドリングの修正
- 型の明確化とJSONパースの改善
- フォーマット指定子の修正
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
package main import ( "encoding/json" "fmt" "io/ioutil" "net/http" "os" "strings" ) // Slackメッセージの構造を定義 type Attachment struct { Color string `json:"color"` Text string `json:"text"` } // billing historyの構造体を定義 type BillingHistoryItem struct { Date string `json:"date"` Amount string `json:"amount"` } func main() { token, err := getEnv("DO_TOKEN") if err != nil { fmt.Println(err) return } slackURL, err := getEnv("SLACK_URL") if err != nil { fmt.Println(err) return } billingHistory, err := fetchBillingHistory(token) if err != nil { fmt.Println("Error fetching billing history:", err) return } message := createSlackMessage(billingHistory) err = postToSlack(slackURL, message) if err != nil { fmt.Println("Error posting to Slack:", err) } } // エラーハンドリング func getEnv(key string) (string, error) { value := os.Getenv(key) if value == "" { return "", fmt.Errorf("environment variable %s not set", key) } return value, nil } // billing historyのAPIリクエストを作成する関数 func fetchBillingHistory(token string) ([]BillingHistoryItem, error) { client := &http.Client{} req, err := http.NewRequest("GET", "https://api.digitalocean.com/v2/customers/my/billing_history", nil) if err != nil { return nil, err } req.Header.Set("Authorization", "Bearer "+token) res, err := client.Do(req) if err != nil { return nil, err } defer res.Body.Close() body, err := ioutil.ReadAll(res.Body) if err != nil { return nil, err } var data struct { BillingHistory []BillingHistoryItem `json:"billing_history"` } err = json.Unmarshal(body, &data) if err != nil { return nil, err } return data.BillingHistory, nil } // billing historyのSlackメッセージを作成する関数 func createSlackMessage(billingHistory []BillingHistoryItem) string { if len(billingHistory) == 0 { return "No billing history available" } latestBill := billingHistory[0] message := fmt.Sprintf("https://hoge \n ・ %s \n ・ $%s", latestBill.Date, latestBill.Amount) return message } // Slack Webhookに送信するための関数 func postToSlack(slackURL, message string) error { attachment := Attachment{ Color: "#240bde", Text: message, } payload := map[string]interface{}{ "text": "Check billing! :cloud:", "attachments": []Attachment{attachment}, } jsonValue, err := json.Marshal(payload) if err != nil { return err } reader := strings.NewReader(string(jsonValue)) req, err := http.NewRequest("POST", slackURL, reader) if err != nil { return err } req.Header.Set("Content-Type", "application/json") client := &http.Client{} res, err := client.Do(req) if err != nil { return err } defer res.Body.Close() return nil } |
0件のコメント