リモートワークや副業で役立つ。複数の開発プロジェクトを効率的かつ安全に取り扱うための Tips 集

個人・本業・副業などで、認証アカウントや設定の異なる複数のプロジェクトを間違いを起こさず便利に使い分けるための Tips 集です。

使えそうなものをつまみ食いしてもらえると幸いです。

## （前提）

### 作業環境

普段使っている作業環境です。必須要件ではありません。

* Mac
* bash

### ディレクトリ構成

認証アカウントは大体案件毎に共通になり、その中に複数のプロジェクト（リポジトリ）があることも多いので、大体このような形にしています。

```
~/
    .bashrc
    .gitconfig
    Work/
        CompanyA/
            ProjectA-1/
                .git/
            ProjectA-2/
                .git/
        CompanyB/
            ProjectB-1/
                .git/
            ProjectB-2/
                .git/
```

## シェル操作関連の Tips

### プロジェクト毎に環境変数を切り替える。

[direnv](https://github.com/direnv/direnv) を使います。他の使い分け設定のベースとなるアプリケーションです。


```sh
# インストールして Hook を設定します。
$ brew install direnv
$ echo 'eval "$(direnv hook bash)"' >> $HOME/.bashrc
$ source $HOME/.bashrc

# git リポジトリ内で設定するかもしれないので、グローバルに無視されるようにしてあります。
# グローバルの .gitignore については後述します。
$ echo '.envrc' >> $HOME/.gitignore
```

インストールしたら、必要な環境変数を各ディレクト内の `.envrc` ファイルに書き込みます。

```sh
$ cd ~/Work/CompanyA
$ direnv edit .
# direnv に edit コマンドが用意されていますが、直接 .envrc ファイルを作成しても大丈夫です。
```

エディタが開くので以下のような内容に編集して保存します。

```sh:~/Work/CompanyA/.envrc
export FOO=foo
export BAR=bar
```

`direnv allow` しろという警告が表示されたら素直に従ってコマンドを実行して下さい。


デフォルトでは、遡って一番近い親ディレクトリの `.envrc` しか読み込んでくれないので、プロジェクト毎に定義しつつ親の案件毎のものも参照して欲しいような場合は、子の方に `source_up` と書いておきます。

```sh:~/Work/CompanyA/ProjectA-1/.envrc
source_up

export FOOBAR=foobar
```

重複した場合は、子の方で上書きされます。

これで、そのディレクトリ配下に移動するだけで、さまざまな作業環境が一度に切り替わることになります。（後述する設定を追加していくことで）

`~/.bashrc` などに移動コマンドの alias を登録しておくと Tab 補完も効いて便利です。

```sh:~/.bashrc
alias company_a='cd ~/Work/CompanyA'
```

##### (TIPS) Xcode をプロジェクト毎に使い分ける。

プロジェクト毎に使用している Xcode のバージョンが異なる場合に、コマンドライン上では `xcode-select -s` で適切なバージョンを使用するように変更することができますが、これを毎回行うのは大変です。

`man xcode-select` に記載されている内容になりますが、`DEVELOPER_DIR` 環境変数を用意することで上記コマンドの処理の代替とすることができます。

```sh
ENVIRONMENT
    DEVELOPER_DIR
        Overrides  the  active  developer directory. When DEVELOPER_DIR is set, its value will be
        used instead of the system-wide active developer directory.
```

なので、direnv で指定してあげれば、プロジェクト毎に使い分けることができます。

```sh:~/Work/CompanyA/.envrc
export DEVELOPER_DIR=/Applications/Xcode11_2_1.app/Contents/Developer
```

### .bashrc を分けて管理する。

案件固有のシェル設定は `.bashrc` を分けておいて、本体の方では以下のようにファイルを読むこむだけにしておけば、`~/.bashrc` が散らからずプロジェクト終了時にメンテナンスもしやすいです。

```sh:~/.bashrc
for rc in $(ls $HOME/work/*/.bashrc 2>/dev/null); do
  source $rc
done
```

自動的にファイルを探しにいくので、案件が追加された場合は（必要に応じて）ディレクトリ直下に `.bashrc` を作るだけで OK です。

## Git 関連の Tips

### いつも使う Git 設定をグローバル化する。

`~/.gitconfig` を用意しておけば、グローバルに設定が反映されます。

`git config --global` で設定した内容が書き込まれる場所なので、こちらのコマンドで設定しても OK です。

使い勝手の向上や、いつも付けておきたいコマンドオプションを書いておきます。

例えば以下のようなものです。


```ini:~/.gitconfig
[alias]
	st = status
	ci = commit
	co = checkout
	br = branch
[color]
	ui = true
[merge]
	tool = vimdiff
	ff = false
[pull]
	ff = only
[core]
	autocrlf = false
	quotepath = false
```

### いつもリポジトリに Commit しないファイルを、グローバルの設定で自動的に無視する。

自分の環境だけに生成されるファイルを Git から無視したいことはよくあります。しかし、プロジェクトの .gitignore に書き込むのは抵抗があったり、毎回 `.git/info/exclude` に追記するのが面倒だったりします。

**毎回無視しても大丈夫**という確信のあるものについては、グローバルに設定してしまいます。

```sh
# ホームにある .gitignore をグローバルの無視設定ファイルにします。
$ git config --global core.excludesfile ~/.gitignore
```

あとは無視したい内容を `~/.gitignore` に書いておけば以降は自動的に無視されます。direnv の設定ファイルもここに書いてあります。

```:~/.gitignore
.DS_Store
.envrc
```

確信がない場合は、リポジトリ毎に `.git/info/exclude` をメンテするようにします。

### Git のユーザー情報（など）を自動的に使い分ける。

よくあるのが Git ログに書き込まれる名前とメールアドレスの使い分けです。

仕事用の新しいリポジトリで、誤ってグローバルに設定してあるプライベートなメールアドレスを入れてしまったり、リポジトリ毎に毎回設定するのが面倒だったりします。

案件毎に作った親ディレクトリで一括管理し、配下のリポジトリは自動的に同じ設定が適用されるようにしたいと思います。

git config の [Conditional includes](https://git-scm.com/docs/git-config#_conditional_includes) を使います。

1. 案件毎のディレクトリ直下に専用の設定ファイル `~/Work/CompanyA/.gitconfig.inc` を用意します。

    ```ini:~/Work/CompanyA/.gitconfig.inc
    [user]
      name = name_on_work
      email = name_on_work@example.c0m
    ```
2. `~/.gitconfig` にディレクトリごとのファイルを読みに行くよう設定を追加します。

    ```ini:~/.gitconfig
    [user]
      name = name_in_private
      email = user_name_in_private@users.noreply.github.c0m

    [includeIf "gitdir:~/Work/CompanyA/"]
      path = ~/Work/CompanyA/.gitconfig.inc

    [includeIf "gitdir:~/Work/CompanyB/"]
      path = ~/Work/CompanyB/.gitconfig.inc
    ```

これで direnv のように、特定のディレクトリ配下では専用の設定が適用されるようになります。

ユーザー情報以外に、独自の Git 規約があるような場合にも使えます。

### GitHub の複数アカウントを使い分ける。

GitHub ではアカウント間で SSH Key を使い回すことができないので、複数の SSH Key を管理することになります。

この状態では少し手を加えないと SSH 認証での git コマンドが通らないアカウントが出てきます。

アカウントが異なるのは大体案件単位なので、前項で作った `~/Work/CompnayA/.gitconfig.inc` 内で設定を行います。

```ini:~/Work/CompanyA/.gitconfig.inc
[core]
  sshCommand = "ssh -i ~/.ssh/id_rsa_company_a
```

`~/.ssh/id_rsa` などの、 ssh がデフォルトで読みに行くキーを使っているアカウントは設定不要です。


## クラウドサービス関連の Tips

主に CLI ツールを使ったクラウドサービスの操作に関する手順です。

意図せず本番環境を書き換えてしまったりすることがないよう、設定切り替えをなるべく自動化したいと思います。

### GCP 環境を自動で切り替える。

`gcloud` や `bq` コマンドでの、操作を行うアカウントや対象のプロジェクトを切り替えます。

これらコマンドを含む Google Cloud SDK には、アカウントやプロジェクトなどのプロパティを保持する「構成」を複数作成して、適宜切り替え可能な仕組みが用意されています。

[SDK 構成の管理  |  Cloud SDK のドキュメント  |  Google Cloud](https://cloud.google.com/sdk/docs/configurations)

単純には以下のような流れになります。

```sh
# 好きな名前を付けて、新しい構成を作成します。
$ gcloud config configurations create [NAME]

# 構成のプロパティを設定していきます。（以下は例）
$ gcloud auth login
$ gcloud config set project [PROJECT_ID]

# 別の構成に切り替えるのは activate コマンドになります。
$ gcloud config configurations activate [NAME]

# 作成された全ての構成のリストは、以下で確認できます。
$ gcloud config configurations list
```

`gcloud config configurations activate` で切り替えていく仕組みも十分便利なのですが、全コンソールセッションで同じ構成が有効になるという（自分の使い方では）問題があります。

* コンソールセッション毎に有効な構成を変えたい。
* direnv と連携して、今いるディレクトリに応じて自動的に構成を変えたい。

という挙動にしたい場合は、上記の設定に加えて direnv の `.envrc` に設定を付け加えます。（[公式ドキュメント](https://cloud.google.com/sdk/docs/configurations#automating_configuration_switching) にも記載があります。）

```sh:~/Work/CompanyA/.envrc
export CLOUDSDK_ACTIVE_CONFIG_NAME=CompanyA-dev # gcloud config configurations create で付けた NAME を指定します。
```

### AWS 環境を自動で切り替える。

`aws` コマンドでの、アクセスキーを元にした認証情報や初期リージョン設定などを切り替えます。

aws コマンドをインストールした直後、`aws configure` コマンドでアクセスキーや初期リージョンを入力させられると思います。

この時 `default` の Profile が作成されるのですが、Profile は複数作成することができます。

以下のような流れになります。

```sh
# 好きな名前を付けて Profile を作成します。
$ aws configure --profile=[NAME]
# アクセスキーや初期リージョンの入力が求められ、Profile が作成されます。

# Profile を指定してコマンドを実行できます。
$ aws s3 ls --profile=[NAME]

# 作成された全ての Profile は、以下で確認できます。
$ cat ~/.aws/config

# 現在有効な Profile は以下で確認できます。
$ aws configure list
```

Profile は、環境変数 `AWS_DEFAULT_PROFILE` によって切り替えることができるので、これを direnv を使って自動設定されるようにします。

```sh:~/Work/CompanyA/.envrc
export AWS_DEFAULT_PROFILE=[NAME] # aws configire --profile= で付けた NAME を指定します。
```


### Kubenetes 環境を自動で切り替える。

`kubectl` コマンドでの、操作を行うユーザーや対象のクラスターを切り替えます。

kubectl ではユーザーとクラスターを組み合わせて context として管理し、適宜切り替え可能な仕組みが用意されています。

[Configure Access to Multiple Clusters - Kubernetes](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/)

ですが、gcloud の場合と同じくデフォルトの状態では、全コンソールセッションで同じ context が有効になってしまいます。

また、自動切り替えも実現したいため、 ここでも direnv を使って（context の保存ファイルを変更する）`KUBECONFIG` 環境変数が自動設定されるようにします。

*※ context を設定する前に行う必要があります*

```sh:~/Work/CompanyA/.envrc
export KUBECONFIG=$(pwd)/.kubeconfig
```

あとはディレクトリに入って context を作成し利用します。(以下は GKE の場合の例）

```sh
# 認証情報を取得して context を作成する。
$ gcloud container clusters get-credentials company_a_cluster_name --region us-west1

# 作成された全ての context は、以下で確認できます。
$ kubectl config get-contexts

# 自動で長い名前が付いているので、気になるようなら分かりやすい名前に付け替えます。
$ kubectl config rename-context [OLD_LONG_NAME] [NAME]

# context が 1 つの場合は、いつもそれが有効になります。
# 複数の context を作成した場合は、 適宜 use-context コマンドで切り替えます。
$ kubectl config use-context [NAME]
```

### Firebase 環境を自動で切り替える。

`firebase` コマンドでの、操作を行うアカウントを切り替えます。

firebase コマンドには、他のコマンドのような構成やプロファイルのような仕組みは見つけられていません。

ただし、CI用ということになっていますが、環境変数にトークンを設定してアカウント認証できるので、これを使います。

```sh
# CI 用のトークンを入手する。ブラウザに誘導されるので適切なユーザーを選択すると、コンソールにトークンを出力されます。
$ firebase login:ci

Visit this URL on this device to log in:
https://accounts.google.com/o/oauth2/auth?client_id=...

Waiting for authentication...

✔  Success! Use this token to login on a CI server:

# ここにトークン文字列が出力されます。
```

トークンを環境変数 FIREBASE_TOKEN に入れておくことで、firebase コマンドはそれを元に認証を行ってくれるので、direnv を使って自動設定されるようにします。

```sh:~/Work/CompanyA/.envrc
export FIREBASE_TOKEN=トークン文字列
```

### 現在の状態をターミナルに表示して注意を促す。

プロンプトにクラウドサービス毎の現在の設定名を表示しておくと、すぐに確認できて間違いも起きにくいです。

リスクのある環境になっている時だけ表示された方がアラートになるので、プライベートな環境には 'default' や 'private' といった設定名が付くようにしておいて、その場合は何も表示しないようにしています。

`~/.bashrc` に以下のような内容を追記しておくだけです。

```sh:~/.bashrc
function cloudps1() {
  if [ -x "`which gcloud`" ]; then
    __gcp=${CLOUDSDK_ACTIVE_CONFIG_NAME:-default}
  fi

  if [ -x "`which aws`" ]; then
    __aws=${AWS_DEFAULT_PROFILE:-default}
  fi

  if [ -x "`which kubectl`" ]; then
    __kube=`kubectl config current-context`
  fi

  if [ -n "${__gcp}" ] && [ "${__gcp,,}" != 'private' ] && [ "${__gcp,,}" != 'default' ];then
    echo -en "${__gcp}\U1F176 "
  fi

  if [ -n "${__aws}" ] && [ "${__aws,,}" != 'private' ] && [ "${__aws,,}" != 'default' ];then
    echo -en "${__aws}\U1F170 "
  fi

  if [ -n "${__kube}" ] && [ "${__kube,,}" != 'private' ] && [ "${__kube,,}" != 'default' ];then
    echo -en "${__kube}\U1F17A "
  fi
}

PS1='\[\e[01;30;43m\]$(cloudps1)\[\e[00m\]'$PS1
```