Technology Engineering

178inaba の技術ブログ

スプレッドシートのデータを取得する

slack botスプレッドシートからデータ取得する場面があったので調査して検証がてらgolangで実装してみた。
気をつける所とかメモ的に書いておく。

作ったのはスプレッドシートで重み付け抽選を行うアプリ。

github.com

認証

認証方法はAPIキー、OAuth、サービスアカウントの3つがある。

f:id:i178inaba:20190313020229p:plain

いずれもGCPの『APIとサービス』から取得できる。

APIキー

公開されたスプレッドシートしか読み込めないようだったので今回はパス。

OAuthクライアントID

一度ユーザがブラウザを開いて認証する必要がある。
bot用には使えないなーと思ったのでパス。

サービスアカウント

新しくGoogleアカウントを発行するイメージ。
スプレッドシートに招待して使う。

招待してしまえばずっと使えるためbotには合っていると思い、今回はサービスアカウントを使用することにした。

使用方法

発行したjsonキーファイルの中から client_email を取り出す。

$ cat /path/to/key.json | jq -r .client_email
test@quickstart-1551059800000.iam.gserviceaccount.com

データを取得したいスプレッドシートに移動して右上の共有ボタンをクリック。
『他のユーザと共有』ダイアログを開き、先程取得した client_email を追加する。

f:id:i178inaba:20190313022722p:plain

実装

実装側の認証には google.JWTConfigFromJSON を使う。
キーjsonファイルの中身とスコープ(今回は取得なのでreadonly)を渡す。

conf, err := google.JWTConfigFromJSON(
    jsonKeyBytes, 
    "https://www.googleapis.com/auth/spreadsheets.readonly"
)

返ってきた conf からhttpクライアントを取得し、 sheets.New に渡して Service を取得する。

srv, err := sheets.New(conf.Client(ctx))

あとは取得したサービスを使ってGetすればいい。
スプレッドシートIDと取得するデータのレンジ(下記の例では A2:B )を指定する。
( A2:B はA2セルからB列の最後のセルまでという意味)

resp, err := srv.Spreadsheets.Values.Get(spreadsheetID, "A2:B").Context(ctx).Do()

var items []lotteryItem
for _, row := range resp.Values {
    name := row[0].(string)
    weight, err := strconv.Atoi(row[1].(string))

    items = append(items, lotteryItem{name: name, weight: weight})
}

※エラーハンドリングは省略してあります。

列方向への取得

デフォルトでは行方向の取得になるが、列方向に取得したい場合は MajorDimensionCOLUMNS を指定すればよい。

resp, err := srv.Spreadsheets.Values.Get(spreadsheetID, "A2:B").Context(ctx).MajorDimension("COLUMNS").Do()

参考

developers.google.com godoc.org

Nuxt.js×Express使用時のhost指定でハマった

Nuxt.js×ExpressのDockerfileを書いている時に、コンテナ外からアクセスを受け付けようと思って server/index.jshost の値を 127.0.0.10.0.0.0 に書き換えた。

host = process.env.HOST || '0.0.0.0',

しかしコンテナ外からアクセスできなかった。

よく見ると nuxt.options.server で上書きされているようだった。

const {
  host = process.env.HOST || '0.0.0.0',
  port = process.env.PORT || 3000
} = nuxt.options.server

(最近のjsに慣れてなくて、この記法の意味調べるのに手間取った...)

nuxt.options.server のデフォルト値

github.com

環境変数 HOST0.0.0.0 を指定するとコンテナ外からのアクセスは通った。

まとめ

ENV HOST 0.0.0.0

追記

結局上書きされるなら server/index.js で代入してる所無駄じゃない?と思ってPR出してみた。

github.com

追記2

PRマージされてた😊

LinuxにNode.js入れる時はPermissionのためにnvm使ったほうがいい

前回の記事ではyumでNode.jsをインストールしたが、それはServerlessをインストールしようと思ったためであった。

しかし、Node.jsのインストール後、Serverless公式に書いてあったコマンド
npm install serverless -g
を実行したらPermissionで怒られた。

npm ERR! Error: EACCES: permission denied, access '/usr/local/lib/node_modules'
npm ERR!  { Error: EACCES: permission denied, access '/usr/local/lib/node_modules'
npm ERR!   errno: -13,
npm ERR!   code: 'EACCES',
npm ERR!   syscall: 'access',
npm ERR!   path: '/usr/local/lib/node_modules' }
npm ERR!
npm ERR! Please try running this command again as root/Administrator.

Permissionで怒られたのでsudoつけて実行したら以下のようなエラーが出た。

┌───────────────────────────────────────────────────┐
│          serverless update check failed           │
│        Try running with sudo or get access        │
│       to the local update config store via        │
│ sudo chown -R $USER:$(id -gn $USER) /root/.config │
└───────────────────────────────────────────────────┘

インストールはできていてコマンドは使えるようだったが、このようなエラーを放置するのはちょっと気持ち悪いのでnpmのPermission周りについて色々調べた。

すると以下のページを発見。

Resolving EACCES permissions errors when installing packages globally

ここの『Reinstall npm with a node version manager』に以下の記述がある。

This is the best way to avoid permissions issues.

node version managerでnpmをインストールする事がPermission問題を回避する最善の方法らしい。
そのままリンクを辿っていくとversion managerの紹介がされていた。

Using a Node version manager to install Node.js and npm

ここでnとnvmが紹介されていたのだが、nvmの方がスター数が多かったため今回はnvmを選択した。

nvmのインストール方法は割愛する。
以下nvmレポジトリの Installation を参照してください。

github.com

nvmのインストール後、 nvm install node で最新のNode.jsがインストールされる。
その後Serverlessをインストールするとエラーは出なかった🎉

まとめ

LinuxにNode.js入れる時はPermissionのためにnvm使ったほうがいい!

Amazon Linuxで古いNode.jsがインストールされる時の解決方法

Amazon Linuxで何度Node.js v11をインストールしようとしてもv6が入ってしまう現象が起こったのでその解決策をメモ。

現象

v11用RPMのセットアップをする。

$ curl -sL https://rpm.nodesource.com/setup_11.x | sudo bash -

その後、yum installしても 2:6.14.4-1nodesource がインストールされる。

$ sudo yum install -y nodejs
...
Installed:
  nodejs.x86_64 2:6.14.4-1nodesource

解決策

以下のコマンドでRPMのキャッシュを消す。

$ sudo yum remove -y nodesource-release* nodejs
$ yum clean all
$ sudo rm -rf /var/cache/yum/*
$ sudo rm /etc/yum.repos.d/nodesource-el.repo

その後、再度RPMセットアップコマンドを流してyum installすればよい。

$ curl -sL https://rpm.nodesource.com/setup_11.x | sudo bash -
...
$ sudo yum install -y nodejs
...
Installed:
  nodejs.x86_64 2:11.1.0-1nodesource

v11が入りました。

参考

github.com

削除済みリモートブランチを追跡しているローカルブランチを1発で全削除するコマンド

リモートブランチはマージ後GitHubが削除するか聞いてくれるのでその時点で削除するようにしている。

f:id:i178inaba:20181103173338p:plain

だが、そのリモートブランチを追跡しているローカルブランチの削除を忘れて溜まっていることがよくある。

$ git branch -v
* develop           fef1cdd Merge pull request #30 from abcdef/fix/bazbarfoo
  feature/foo       e57408f [gone] Fix deploy
  feature/bar       a9b73ad [gone] Upload image
  feature/baz       b833113 [gone] Fix test
  feature/foobar    703e2ad [gone] Add foobar.c
  feature/foobaz    1587581 [gone] Fix foobaz.rb
  feature/barfoo    37265cb [gone] Add barfoo.php
  feature/barbaz    3c121ca [gone] Fix barbaz.py
  feature/bazfoo    cb69767 [gone] Add bazfoo.go
  feature/bazbar    bb8575f [gone] Fix bazbar.pl
  feature/foobarbaz 66d2807 [gone] Fix README.md
  fix/foobazbar     84a781e [gone] Add gitignore
  fix/bazfoobar     cd36a6b [gone] Fix ci
  master            0a41ce8 Merge pull request #20 from abcdef/develop

[gone] が付いているのがリモートブランチへの追跡が切れているローカルブランチ。

いつも git branch -d の後ろに1つずつブランチ名をコピペして削除していた。

$ git branch -d feature/foo feature/bar feature/baz feature/foobar feature/foobaz feature/barfoo feature/barbaz feature/bazfoo feature/bazbar feature/foobarbaz fix/foobazbar fix/bazfoobar
Deleted branch feature/foo (was e57408f).
Deleted branch feature/bar (was a9b73ad).
Deleted branch feature/baz (was b833113).
Deleted branch feature/foobar (was 703e2ad).
Deleted branch feature/foobaz (was 1587581).
Deleted branch feature/barfoo (was 37265cb).
Deleted branch feature/barbaz (was 3c121ca).
Deleted branch feature/bazfoo (was cb69767).
Deleted branch feature/bazbar (was bb8575f).
Deleted branch feature/foobarbaz (was 66d2807).
Deleted branch fix/foobazbar (was 84a781e).
Deleted branch fix/bazfoobar (was cd36a6b).

面倒なので1発コマンド打つだけで全部消えるコマンドを組もうと思い調べた。

コマンド

git for-each-ref --format '%(if)%(upstream:track)%(then)%(refname:short)%(end)' | xargs git branch -d

解説

最初は git branch コマンドを使って作ろうとしていた。
だが、先の記事にも書いた通り、 git branch を使ってスクリプトを書かないほうがいいらしい。

inaba.hatenablog.com

なので git for-each-ref を使って書いた。
これはGitレポジトリの各refの情報を条件に従って出力していくコマンド。

Git - git-for-each-ref Documentation

何点か解説を書いてみる。

ブランチ名

ブランチ名は %(refname:short) で取得。
short をつけないと refs/heads がついてしまい、ブランチ名として認識されないので注意が必要。

# `refs/heads` がついてしまう。
$ git for-each-ref --format '%(refname)'
refs/heads/develop
refs/heads/feature/foo
refs/heads/feature/bar
refs/heads/feature/baz
refs/heads/feature/foobar
refs/heads/feature/foobaz
refs/heads/feature/barfoo
refs/heads/feature/barbaz
refs/heads/feature/bazfoo
refs/heads/feature/bazbar
refs/heads/feature/foobarbaz
refs/heads/fix/foobazbar
refs/heads/fix/bazfoobar
refs/heads/master
refs/remotes/origin/HEAD
refs/remotes/origin/develop
refs/remotes/origin/feature/aaa
refs/remotes/origin/feature/bbb
refs/remotes/origin/ccc
refs/remotes/origin/master
refs/remotes/origin/try/ddd
refs/remotes/origin/try/eee

# shortをつければ正しいブランチ名が取得できる。
$ git for-each-ref --format '%(refname:short)'
develop
feature/foo
feature/bar
feature/baz
feature/foobar
feature/foobaz
feature/barfoo
feature/barbaz
feature/bazfoo
feature/bazbar
feature/foobarbaz
fix/foobazbar
fix/bazfoobar
master
origin/HEAD
origin/develop
origin/feature/aaa
origin/feature/bbb
origin/ccc
origin/master
origin/try/ddd
origin/try/eee

gone

削除対象は [gone] が付いたブランチなのでこれを取得する。

upstream の項で以下のように説明されている。

:track also prints "[gone]" whenever unknown upstream ref is encountered.

試すと

$ git for-each-ref --format '%(upstream:track)'

[gone]
[gone]
[gone]
[gone]
[gone]
[gone]
[gone]
[gone]
[gone]
[gone]
[gone]
[gone]








[gone] のみでは分かりづらいと思うのでブランチ名をつけてみる。

$ git for-each-ref --format '%(upstream:track) %(refname:short)'
 develop
[gone] feature/foo
[gone] feature/bar
[gone] feature/baz
[gone] feature/foobar
[gone] feature/foobaz
[gone] feature/barfoo
[gone] feature/barbaz
[gone] feature/bazfoo
[gone] feature/bazbar
[gone] feature/foobarbaz
[gone] fix/foobazbar
[gone] fix/bazfoobar
 master
 origin/HEAD
 origin/develop
 origin/feature/aaa
 origin/feature/bbb
 origin/ccc
 origin/master
 origin/try/ddd
 origin/try/eee

リモートへの追跡が切れていない develop, master やリモートにのみ存在しているブランチには [gone] がついておらず、リモートへの追跡が切れたローカルブランチにのみ [gone] がついている事がわかる。

if

git for-each-ref ではif文( %(if)…​%(then)…​%(end) )が使える。
if文を使い、 [gone] があるときだけブランチ名を表示するようにする。

$ git for-each-ref --format '%(if)%(upstream:track)%(then)%(refname:short)%(end)'

feature/foo
feature/bar
feature/baz
feature/foobar
feature/foobaz
feature/barfoo
feature/barbaz
feature/bazfoo
feature/bazbar
feature/foobarbaz
fix/foobazbar
fix/bazfoobar








xargs

取得できたブランチ名をxargsを使って git branch -d に流す。 ちなみにxargsだけ実行すると git branch -d にどんな値が渡っているのかわかる。

$ git for-each-ref --format '%(if)%(upstream:track)%(then)%(refname:short)%(end)' | xargs
feature/foo feature/bar feature/baz feature/foobar feature/foobaz feature/barfoo feature/barbaz feature/bazfoo feature/bazbar feature/foobarbaz fix/foobazbar fix/bazfoobar

こんな感じ。

別解

サブコマンドとして実行してもよい。

git branch -d $(git for-each-ref --format '%(if)%(upstream:track)%(then)%(refname:short)%(end)')