20210509のGoに関する記事は8件です。

Firestoreのセキュリティルール以外でPermissionDenied になった原因と解決策

前提 GCP内に作成されたProjectAとProjectBという2つのプロジェクトが登場します。 Cloud Firestoreへのアクセスは、ProjectAに「Firebase Admin SDK Service Agent」というサービスアカウントを作成し、ProjectBからもこのサービスアカウントを使用してFirestoreにアクセスします。 今回のサービスでは、Go言語で作成したAPIをApp Engineにデプロイした例を用いていますが、Cloud Functionsやその他のリソースを使用していても問題ないと思います。ざっくりとした構成図は下記のとおりです。 発生した現象 ProjectA内に作成されたFirestore内のデータにProjectBからアクセスしようとすると、rpc error: code = PermissionDenied desc = Missing or insufficient permissions.というエラーが表示され、データを参照することができません。 考えられる原因と試したこと ①セキュリティルールをオープンアクセスに設定する。 Firestoreのセキュリティルールを全てのユーザーが読み取りと書き込みをできるアクセス権を付与しました。 Firestore関連でPermissionDeniedと検索するとこちらの解決方法が多くヒットしますが、この方法では解決しませんでした。 そもそもサーバーサイドからAdmin権限でアクセスしているため、セキュリティールールは無視されるので、セキュリティルールが原因ではないことはすぐにわかることでしたが、ここに数時間も時間をかけてしまいました。 // Allow read/write access to all users under any conditions // Warning: **NEVER** use this rule set in production; it allows // anyone to overwrite your entire database. service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read, write: if true; } } } ②ProjectBでも同様のサービスアカウントを作成する こちらの方法は実現不可能でした。 理由はProjectBにはFirestoreが存在しないので物理的に作成することができません。 原因 ①、②から原因はセキュリティルールではなく、ProjectBのApp EngineからProjectAのFirestoreにアクセスする際に使用しているサービスアカウントの権限が不足していることが原因ではないかとの予測が立ちました。 結論を述べるとこちらの予測は当たっていって、ProjectBのサービスアカウントに必要な権限を与えたところ無事アクセスできるようになりました。 解決方法については下記で説明します。 解決方法 解決方法から説明すると、ProjectAに作成した「Firebase Admin SDK Service Agent」というサービスアカウントに、ProjectBで使用しているサービスアカウントのアクセスを許可するという方法です。 ProjectBではApp Engineを使用しているため、デフォルトで作成されるProjectB@appspot.gserviceaccount.comというサービスアカウントを使用して通信を行っています。 上記の作業を行うことでプロジェクトをまたいで、Firestore内のデータにアクセスできるようになります。 具体的な手順は、ProjectAの管理コンソールのメニューからIAMと管理 > サービスアカウントと進むと下記のような画面が開き、アクセスと許可というボタンをクリックすると、右側に「新しいメンバー」を追加するフォームが表示されます。 このフォームにProjectBのApp Engineで使用しているサービスアカウント(デフォルトではProjectB@appspot.gserviceaccount.com)を指定すると、ProjectAに設置されたFirestoreへのアクセス権が付与されるようになります。 まとめ 今回はかなり初歩的な設定ミスにより、エラーの解決までかなり時間を要してしまいました。 Firestoreはエラーメッセージから具体的な原因を特定しにくいのがネックではありますが、この記事が一人でも多くの方の参考になれると幸いです。 今後は初心に戻り、再度IAM周りについて勉強し直してみます。 参照 セキュリティルール https://firebase.google.com/docs/firestore/security/insecure-rules#open_access Firebase - Admin SDK https://firebase.google.com/docs/admin/setup
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Golang のテストカバレッジを効率的に確認する

Golang のテストカバレッジを効率的に確認する 前回の記事でテストカバレッジをシェルスクリプトにしてそれを実行することで確認していました。 https://qiita.com/k_yuda/items/86f40bdf3e040560ade4 今回これをDockerfileとMakefileにまとめてよりテストカバレッジを確認しやすいようにしたメモ。 流れ マルチステージビルドのstage1においてテストカバレッジを確認するためのcover.outなどを生成する stage2において、stage1のファイルをコピーする Dockerコンテナーないにあるファイル、cover.outをhtmlにコンバートしたファイルをローカルにコピーする Makefileで各種コマンドをまとめる マルチステージビルドを実現しているDockerfileを記述し直す。 Dockerfile FROM golang:latest AS stage1-buildphase WORKDIR /go/src COPY . . RUN go test main_test.go main.go -v RUN go build -o tail main.go RUN go test main_test.go main.go -coverprofile=cover.out RUN go tool cover -html=cover.out -o convert.html FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root COPY --from=stage1-buildphase /go/src/ . CMD ["./start.sh"] ここで生成されるconvert.htmlはグラフィカルにテストカバレッジを確認することができます。 Makefileで各種コマンドをまとめる Makefile NAME := gotail NAME-C := gotailcheck .PHONY: all all: docker-build docker-run .PHONY: docker-build docker-build: docker build -t $(NAME) . .PHONY: docker-run docker-run: docker run -t $(NAME) check: docker-build docker-run-covercheck open clean docker-run-covercheck: docker run --rm --name $(NAME-C) -itd $(NAME) /bin/sh docker cp $(NAME-C):/root/convert.html $(shell pwd) docker stop $(NAME-C) cp convert.html $(shell pwd)/coverchecklog/$(shell date +"%Y%m%d%I%M%S").html .PHONY: open open: open convert.html .PHONY: clean clean: sleep 10 rm -f cover.out convert.html このように記述すると/coverchecklogにテストカバレッジを確認できるhtmlファイルを保存し、自動でブラウザを立ち上げて確認できます。 docker-run-covercheck: docker run --rm --name $(NAME-C) -itd $(NAME) /bin/sh docker cp $(NAME-C):/root/convert.html $(shell pwd) docker stop $(NAME-C) cp convert.html $(shell pwd)/coverchecklog/$(shell date +"%Y%m%d%I%M%S").html docker run --rmでdockerイメージを処理が終わった後に自動で削除してくれます。 また、--nameオプションを使用することで、IDではなくてnameでdocker cpを実行することができます。 dockerのcpが終わったらイメージを停止します。 最後に現在の時間をファイル名として、coverchecklogにコンバートされたhtmlを記録します。 cp convert.html $(shell pwd)/coverchecklog/$(shell date +"%Y%m%d%I%M%S").html 全ての設定が終わったら、次のコマンドでテストカバレッジを瞬時に確認でき、記録も残してくれます。 make check これでテストカバレッジを意識した開発を手軽に行うことができそうです。 最後に 今回はdocker cpの使い方に詰まっておりました、dockerコマンドは結構覚えることが多いのでたまに混乱してしまうのですが、Makefileにまとめておくと安心です。 まだまだgolangは始めたばかりで分からないことばかりですが、golangにおけるMakefileの記述のベストプラクティスを追求していきたいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語入門 --Hello Worldまで--

はじめに 最近Go言語へ入門したので学んだことを記憶の整理を兼ねて書く。この記事ではGoでHello Worldができるまでを書く。windows、linuxなどのMacOS以外の環境での動作は確認していないが、同様の方法で進められるように書いた。 環境 MacOS BigSur 11.2.3で行った。 使用方法 Goは他にも幾つかあるかもしれないが、Playgroundを使う方法とGoをインストールしてを使う方法がある。Playgroundはプログラムの作成と実行ができるwebアプリケーションであり、簡単なコードを試したいときなどに使うのが便利である。この記事では後者の方法で導入を行い、Hello Worldを行う(PlayGroundは開いてrunするとできる)。 インストール GoはMacを使っているのであればbrew install goでインストールできるが、今回はwindowsやlinuxでも同様の手法で行えるように公式サイトからダウンロードしてインストールを行う。バージョンに拘りがなければFeatured downloadsから自分の環境に合わせたものをクリックするとダウンロードが行える。インストーラーではインストール先などが聞かれるが、通常はそのまま進めてインストールを行なって良い。インストールができたら、ターミナルなどからgo versionと行いインストールしたGOのバージョンが出たら成功(既にターミナルを開いていたのであれば再起動する必要がある)。 goコマンドがわからないと出たら、zshを使っている場合は echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.zshrc source ~/.zhsrc bashを使っている場合は echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc source ~/.bashrc とターミナルから実行することで問題なく使える(GOROOTと言うものを設定している)。linuxでは$HOME/.profileまたは/etc/profileに export PATH=$PATH:/usr/local/go/bin を追加すると良いらしい。インストールについてわからないことがあればここを参照すると良い。 Hello Worldするコードを書く。 適当なテキストエディタで任意の場所に以下のようなソースコードを記述する。 hello.go package main import ( "fmt" ) func main() { fmt.Prinrln("Hello World") } 記述したら、コードを保存した場所で以下のコマンドを打つ go run hello.go そうすると、Hello Worldと出力される。インタプリタ言語のように動いているように感じるが、go runはコンパイルと実行を連続して行うコマンドであり、インタプリタのように感じるのはコンパイルの高速さゆえの錯覚のためである。もちろんコンパイルだけを行うこともできて、その場合はgo build hello.goと入力することでネイティブコードのプログラムファイルを生成することができる。このファイルはMacOSではhelloというコマンドプログラム、windowsであればhello.exeである。これらのプログラムの実行は./helloやhelloと入力することでできる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Goを書くときの個人用メモ

よく参照する公式ドキュメント Package fmt - fmt.Printf()で出力する時に型に応じて、表記方法が違うため確認したり Testの事前処理 test.go //テストの事前処理を書く func TestMain(m *testing.M) { println("before all...") code := m.Run() println("after all...") os.Exit(code) } 構造体の宣言いろいろ Go言語(golang) 構造体の定義と使い方 test.go func TestValidation(t *testing.T) { t.Run("テストケース", func(t *testing.T) { // structを定義及び代入、スライスにする test_case := []struct { from string to string expected error }{ {"20200101", "20200102", nil}, {"20200101", "20191229", errors.New(fmt.Sprintf("date from(%s) is not before than to (%s).", "20200529", "20200601"))}, } for _, v := range test_case { actual := Validation(v.from, v.to) if actual != v.expected { if actual != nil { // error同士だと比較できないので(error).Error()->stringで比較する if actual.Error() != v.expected.Error() { t.Errorf("There is unexpected result: actual(%s), expected(%s) startdate(%s) endDate(%s)", actual, v.expected, v.from, v.to) } } else { t.Errorf("There is unexpected result: actual(%s), expected(%s) startdate(%s) endDate(%s)", actual, v.expected, v.from, v.to) } } } }) } index.go f, err := os.Create("write.txt") if err != nil { fmt.Println("cannot open the file") } defer f.Close() defer func(){ err := f.Close() if err != nil { fmt.Println(err) } } // 以下write処理等を書く
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Go】構造体のスライスからCSV生成

はじめに GoでCSVの生成をしたいときに縛りが緩い汎用的な処理が欲しかったので、構造体のスライスを突っ込めば良いだけの処理を作成しました。 成果物 file.go package file import ( "encoding/csv" "fmt" "os" "reflect" "strconv" ) func GenerateCSV(i interface{}, fileName string) { reflectType := reflect.TypeOf(i) reflectTypeElem := reflectType.Elem() if reflectType.Kind() != reflect.Slice || reflectTypeElem.Kind() != reflect.Struct { fmt.Println("構造体のスライスのみ使えます") return } name := fmt.Sprintf("/tmp/%s.csv", fileName) f, err := os.Create(name) if err != nil { panic(err) } defer f.Close() writer := csv.NewWriter(f) writer.Write(buildHeader(reflectTypeElem)) reflectValue := reflect.Indirect(reflect.ValueOf(i)) for index := 0; index < reflectValue.Len(); index++ { record := reflectValue.Index(index) writer.Write(buildBody(record)) } writer.Flush() fmt.Printf("CSVが生成されました。 %s\n", name) } func buildHeader(rt reflect.Type) []string { header := []string{} for i := 0; i < rt.NumField(); i++ { rs := rt.Field(i) header = append(header, rs.Tag.Get("json")) } return header } func buildBody(rv reflect.Value) []string { body := []string{} for i := 0; i < rv.Type().NumField(); i++ { v := rv.Field(i) switch v.Kind() { case reflect.Int: body = append(body, strconv.FormatInt(v.Int(), 10)) case reflect.String: body = append(body, v.String()) default: body = append(body, "") // 例外でもOK。お好みで。 } } return body } 使用例 main.go type Hoge struct { ID int `json:"id"` Name string `json:"name"` } type HogeList []Hoge func test() { hoges := HogeList{ Hoge{ ID: 1, Name: "name1", }, Hoge{ ID: 2, Name: "name2", }, } fileName := "hoge" file.GenerateCSV(hoges, fileName) } 実行結果 hoge.csv id,name 1,name1 2,name2 感想 理想はGenerateCSVの引数型をinterfaceではなく縛ることで確認処理を辞めたかったのですが、気軽に使えることを優先してこのような形に落ち着きました。 reflect周りがもう少しスッキリできるかも?と思いつつ今回は一旦ここまでにしようかと思います。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Golang】time.Timeのスライスをソート

概要 timestamp.Timestampの型を持ったstructのスライスをソートする方法 sort interfaceを満たすメソッド(Len, Less, Swapメソッド)を用意する方法よりこちらの方が簡単なので、メモ 実装 package main import ( "fmt" "time" "sort" ) type User struct { Name string CreatedAt time.Time } func main() { us := []User{ { Name: "john", CreatedAt: time.Now().Add(time.Minute), }, { Name: "kenta", CreatedAt: time.Now(), }, } fmt.Println(us) sort.Slice(us, func(i, j int) bool { return us[i].CreatedAt.Before(us[j].CreatedAt) }) fmt.Println(us) } // Sort前 // [{john 2009-11-10 23:01:00 +0000 UTC m=+60.000000001} {kenta 2009-11-10 23:00:00 +0000 UTC m=+0.000000001}] // Sort後 // [{kenta 2009-11-10 23:00:00 +0000 UTC m=+0.000000001} {john 2009-11-10 23:01:00 +0000 UTC m=+60.000000001}]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コミットやSlack上の発言から日報を自動で生成するツールを作った

作ったもの こんな感じで、指定したリポジトリに積んだコミットやSlack上の発言を収集して、日報を自動で生成できます。 毎日日報を書くのがめんどくさい方、自分がその日に積んだコミットをモニタリングしたい方、ぜひご利用ください。 インストール方法 Homebrew $ brew install masatoraatarashi/nippo/nippo go get $ go get -u github.com/MasatoraAtarashi/nippo-generator 使い方 1. SlackのAPIトークンを取得 こちらの手順を参考にSlackのAPIトークンを取得してください。 User Token Scopesでsearch:readを指定してください。 2. 初期化する $ nippo init 上記のコマンドで$HOME/.nippo.yamlという設定ファイルが生成されます。 3. 設定ファイルを編集する 生成された設定ファイルを編集してください。 $HOME/.nippo.yaml template: #日報に含めたい見出しを自由に設定してください。 - 今日やったこと - 明日の予定 - 所感・連絡事項 - git - slack git: heading: "git" repositories: #コミットを取得したいディレクトリの絶対パスを記入してください。 - "Users/MasatoraAtarashi/workspace/hogehoge" - "Users/MasatoraAtarashi/workspace/hogehoge2" slack: token: "" #Slack APIトークンを記入してください。 username: "" #Slackのユーザ名を記入してください。 4. 日報を生成する $ nippot generate 出力例 # 2021-05-06 ## 今日やったこと ## 明日の予定 ## 所感・連絡事項 ## git ### hogehoge(3 commits) - 7ae0175 Add count option - 7335722 Add slack to default config - 3bcd230 Add slackname option ### hogehoge2(1 commits) - 3bcd230 Update README ## slack - `よろしくお願いいたします!` (random) - `こんにちは` (テスト) オプション templateを編集すれば日報に含める内容を自由に変更できます。 repositoriesを編集すれば日報に含めたいリポジトリを自由に指定できます。 使った技術 golangとcobraというライブラリを使って作りました。 https://github.com/spf13/cobra
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【育児負荷低減の為のエンジニアリングその2 ポケモンしりとり編 AWS S3 lambda api-gateway terraform】

何を作ったか ポケモンの名前を入力すると、自動的にしりとりをしてくれる物を作りました。 AIとかそんな大それたもの入ってないです。ランダムで対象のポケモンを繋いでいくものです。 しりとりモード ふつうにしりとり 最初と最後のポケモンを入力すると勝手にしりとりやってくれます にゅうりょくもじからはじまるポケモン 入力文字から始まるポケモンを返してくれます どこかにもじがはいるポケモン(尻じゃないですが) 入力文字が含まれているポケモンを返してくれます にゅうりょくもじでおわるポケモン 入力文字で終わるポケモンを返してくれます 何故作ろうと思ったか https://qiita.com/zukaishi/items/7bcb16fb57fdcd762914 - 前回のこちらと同じ理由になりますが、あまりにもしりとりをする事を要求されることに疲れました - ポケモンにハマっている息子(小学1年生)がたくさんポケモンの名前を覚えたのでそれをアピールしたいんでしょうね。そのためしりとり、毎日のように勝負を挑んでくるが面倒になってしまったので、ポケモンしりとりを勝手にしてくれるものをつくりました。 - そんなにポケモン知らないし、そもそも赤・緑で知識が止まっている状態ですし、、、。 どのように実現したか アーキテクチャ AWSとTerraformの勉強がてらこんなアーキテクチャで作ってみました。 AWSでホスティングさせるための実際にブラウザでアクセスするindexとしりとりの対象にするリストをあらかじめs3 bucketにあらかじめ格納しておく しりとりの核となる処理は、API-Gateway → LambdaでAPIリクエストすることによって処理される 取得された結果をブラウザが表示する AWS準備 variable "bucket_name" { default = "website.shiritori.com" } data "aws_iam_policy_document" "s3_bucket_policy" { statement { actions = ["s3:GetObject"] effect = "Allow" principals { type = "AWS" identifiers = ["*"] } resources = ["arn:aws:s3:::${var.bucket_name}/*"] sid = "PublicReadGetObject" } } resource "aws_s3_bucket" "website_shiritori_com" { bucket = var.bucket_name policy = data.aws_iam_policy_document.s3_bucket_policy.json website { index_document = "index.html" error_document = "error.html" } } resource "aws_s3_bucket_public_access_block" "website_shiritori" { bucket = var.bucket_name block_public_acls = true block_public_policy = false ignore_public_acls = true restrict_public_buckets = false } output "url" { value = aws_s3_bucket.website_shiritori_com.website_endpoint } データ取得 func comprised(word string) string { url := "https://s3-ap-northeast-1.amazonaws.com/website.shiritori.com/data/pokemon_list.csv" resp, err := http.Get(url) if err != nil { fmt.Println(err) } defer resp.Body.Close() reader := csv.NewReader(resp.Body) pokemonList := map[int]string{} for { record, err := reader.Read() if err == io.EOF { break } if err != nil { log.Fatal(err) } var i int i, _ = strconv.Atoi(record[0]) pokemonList[i] = record[1] } result := "" for key, value := range pokemonList { if strings.Contains(value, word) { result += fmt.Sprintf("%d:%s", key, value) result += "," } } return result } メイン部 func shiritori(name1 string, name2 string) string { url := "https://s3-ap-northeast-1.amazonaws.com/website.shiritori.com/data/pokemon_list.csv" resp, err := http.Get(url) if err != nil { fmt.Println(err) } defer resp.Body.Close() reader := csv.NewReader(resp.Body) pokemonList := map[int]string{} for { record, err := reader.Read() if err == io.EOF { break } if err != nil { log.Fatal(err) } if name2 == record[1] || getLastString(record[1]) != "ン" { var i int i, _ = strconv.Atoi(record[0]) pokemonList[i] = record[1] } } startNo := contains(pokemonList, name1) endNo := contains(pokemonList, name2) if startNo == 0 || endNo == 0 { fmt.Printf("input name failed ¥n") } word := "" no := startNo result := "" for { result += fmt.Sprintf("%d:%s", no, pokemonList[no]) word = pokemonList[no] delete(pokemonList, no) lastString := getLastString(word) list := containsList(pokemonList, lastString) if len(list) == 0 || endNo == no { break } result += "," if lastString == getRuneAt(name2, 0) { result += fmt.Sprintf("%d:%s", endNo, name2) break } randMap := []int{} for key := range list { randMap = append(randMap, key) } rand.Seed(time.Now().UnixNano()) randNo := rand.Intn(len(randMap)) no = randMap[randNo] } return result } 文字変換周り func getLastString(word string) string { // 対象文字の最後の文字を取得utf-8のため、/3している lastString := getRuneAt(word, len(word)/3-1) // 最後の文字が伸ばし棒の場合 if lastString == "ー" { lastString = getRuneAt(word, len(word)/3-2) } // 特殊文字、捨て仮名を扱いやすい形へ変換する r := strings.NewReplacer("♂", "ス", "♀", "ス", "ァ", "ア", "ィ", "イ", "ゥ", "ウ", "ェ", "エ", "ォ", "オ", "ュ", "ユ", "ャ", "ヤ", "ョ", "ヨ") resStr := r.Replace(lastString) return resStr } コード コードはこちらにアップしてあります。 https://github.com/zukaishi/aws_shiritori 使い方 https://s3-ap-northeast-1.amazonaws.com/website.shiritori.com/index.html 結果どうなったか その度にiPadをせがまれるようになって助かったのはいいのですが、僕がその時間iPad使えないのでちょっと不便、、、。 子供はキーボード叩いたりできないのでちょいちょい打ち込むのが面倒 あと、ポケモンのブームが過ぎ去って、今度はLEGOブームが到来です。 LEGOを自動化するものは、、、。ハードルが高いなぁと思う今日この頃です。 今後やりたいこと 最短、最長の経路を選択できるようにしたい イラストとか引っ張ってこれるようにしたい terraformでapi-gateway周りも実現できるようにしたい ポケモン名前を選択できるようにする or 音声入力を可能にするか
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む