WSL上で実行されているかどうかを判定する

スクリプトがWSL上のLinuxで動作しているかどうかを判定できるようにしておくと、
macOSやWSLといった複数環境をサポートするスクリプトを書いている時に少しだけ便利になります.

判定方法としては、ファイルの内容やコマンドの実行結果によって判断する方法などいくつか存在しますが、
今回は「/proc/sys/fs/binfmt_misc/WSLInteropファイルが存在しているかどうか」で判定する方法です.

/proc/sys/fs/binfmt_misc/WSLInteropファイルが存在しているかどうか」だけなので非常に簡単に判定することができます.

[ -e /proc/sys/fs/binfmt_misc/WSLInterop ]

実際にスクリプトで使う利用する場合には、関数として切り出しておき必要に応じて呼び出すようにしておくと良さそうです.

#!/usr/bin/env bash

os::is_wsl() {
  [ -e /proc/sys/fs/binfmt_misc/WSLInterop ]
}

# e.g. if-statement
if os::is_wsl; then
  # WSL
else
  # Others
fi

※ 個人で利用しているdotfilesではos::is_wsl()のように関数として定義しています

指定したプロパイダのバージョンを一括変更する

既視感がありますが、指定したproviderのバージョンを一括変更するためのスクリプトを業務終了間際に少し書いてみました.

今までそれなりの数のTerraformを触ってきましたが、
バージョンアップデートをする時に1ファイルずつを置換するのが正直なところかなり面倒臭いです.

全てのプロパイダのバージョンが統一されていればよいのですが、
単純なバージョン文字列の置換だけだは対応できないケースも存在しているため、
そういったケースでもバージョンを置換できるようなスクリプトを書きました.

#!/usr/bin/env bash

set -o pipefail
set -o errexit

if [ "$#" -lt 2 ]; then
    echo "置き換え対象のプロパイダと新しいバージョンを指定してください"
    exit 1
fi

provider="$1"
version="$2"

for fpath in $(find . -name "provider.tf");
do
    current_version="$(python3 -c "import hcl; obj = hcl.load(open('$fpath', 'r'), ); print(obj['provider']['$provider']['version'])" 2>/dev/null)"
    if [ "$(uname -s)" == "Darwin" ]; then
        sed -i '' -e "s/${current_version}/= ${version}/g" $fpath
    else
        sed -i -e "s/${current_version}/= ${version}/g" $fpath
    fi
done

./update_providers.sh aws 2.54.0を実行したと過程して、大まかな処理の流れとしては、

  1. provider.tfをカレントディレクトリ配下から再帰的に探索
  2. pyhclでprovider.tfにある指定したプロパイダのバージョンを抜き出す
  3. sedで引数に指定されたバージョンに置き換える

となります.
HCLの自前パースは時間的に面倒だったので、pyhclを使って指定したプロパイダのバージョンを抜き出しています.
単一のprovider.tfに同一プロパイダが複数存在するケースは考慮してないですけどね.

これとは別に利用するバージョンを統一するようなルールを機械的にさせる予定です.

シェルスクリプトにオプションを実装する

シェルスクリプトをよく書くのですが、そういった時によくオプションを指定して値を受け取りたいケースがあります.
その時の書き方をメモしておきます.

#!/usr/bin/env bash

verbose=false

while [ "$1" != "" ]; # "$1"が空文字列になるまで繰り返す
do
  case "$1" in
    # $1が-fまたは--fileなら、filepathに$2を代入する
    # その後、shift 2で引数を2つずらす
    -f|--file)
      filepath="$2"
      shift 2
      ;;

    # $1が-vか--verboseなら、verboseにtrueを代入する
    # その後、shiftで引数を1つずらす
    -v|--verbose)
      verbose=true
      shift
      ;;

    # それ以外の文字列ならエラー出力して終了する
    *)
      "Unknown option: $1" >&2
      exit 1
      ;;
  esac
done

echo "file: $filepath"
echo "verbose: $verbose"

whileの箇所は他にも書き方があり、「引数の個数が0を超えるなら繰り返す」という書き方もあります.

while [ "$#" -gt 0 ];
do
    ...
done

MkDocsのテンプレート

MkDocsを使ってプライベートでも仕事でもドキュメントをまとめることが増えてきました.
リアルタイム編集には向きませんが、やはり設計ドキュメントやオンボーディング資料には向いてる気がします.

そこで自分向けにMkDocsをリポジトリに導入するためのテンプレートを作って、リポジトリにコミットしておきました.

テンプレート

mkdocs-prj-templatesというリポジトリですが、このリポジトリユースケースに応じてテンプレートをディレクトリで分けて管理しています.
現時点では、2種類のテンプレートを作成しています.

simple

simpleは、ドキュメントだけ配置するリポジトリ向けのテンプレートです.

simple
├── Dockerfile
├── docker-compose.yaml
├── docs
│   └── index.md
└── mkdocs.yml

ドキュメントだけでなくても、docker-compose.yamlやDockerfileをリポジトリルートに設置しても気にならないなら、このテンプレートでも良さそうです.

in-docs

in-docsは、既にdocker-compose.yamlやDockerfileがリポジトリにあり、リポジトリルートを散らかしたくない時のテンプレートです.

in-docs
├── bin
│   └── docs-compose
├── docs
│   ├── Dockerfile
│   ├── docker-compose.yaml
│   └── index.md
└── mkdocs.yml

構造を見ると分かるように、Dockerfileとdocker-compose.yamldocsディレクトリに存在しています.
この状態だとリポジトリルートでdocker-compose upをすることができないため、ラッパースクリプトとしてbin/docs-composeを用意しています.
ドキュメント関連の操作はdocs-composeを使えば良いため、各々が独自のコマンドを実行しなくて良いですし、コマンド履歴も比較的キレイになります.

mkdocs.ymlもdocsに入れられる気がしなくもないんですが、今回は諦めました.

aptでnpmをインストールしたらlibmysqlclient-devが削除される

業務でubuntu:18.04イメージをベースとしたコンテナを使っていた時に、困った現象に出会いました.
それはlibmysqlclient-devをインストール後、nodejsnpmをaptでインストールするとlibmysqlclient-devが削除されるという現象です.

結論としては、

  • libmysqlclient-devlibssl-devに依存しているが、npmlibssl1.0-devに依存している
  • npmインストールする際にlibssl-devlibssl1.0-devが競合し、libssl-devと依存しているlibmysqlclient-devを削除する
  • Ubuntu公式リポジトリではなくnodesource/distributionsでaptリポジトリからNodeJSをインストールすれば良い

といった現象でした.

今のいままで問題なく動作していたんですが、昨日から動かなくなっていたので回避策があって良かったです.

挙動確認

挙動確認した時のコマンドを貼っておきます. ubuntu:18.04コンテナ上でaptの操作するだけで確認できます.

# Host
docker run --rm -it ubuntu:18.04 bash

# Ubuntu guest
apt update
apt install -y libmysqlclient-dev
apt install -s --no-remove nodejs npm # ERROR!

最後のapt install -s --no-remove nodejs npmのところのオプションについて軽く説明をしておきます.

-sオプションは、インストールやパッケージ削除などは行わないモードで、いわゆるDryRunです.
--no-removeオプションは、インストール時に他のパッケージが削除される場合にエラー終了させるためのオプションです.

コンテナイメージのビルドでaptを使う場合、--no-removeをつけておいた方が意図しないパッケージの削除が起こらないので、つけておいた方が良さそうです.

MacでネットワークのDNSサーバを変更する

新しいMacのセットアップや、ネットワークの追加した時にDNSサーバを変更する時があります.
その時のセットアップを楽にするためにスクリプトを書きました.

Gistにアップロードしてあるので、具体的なスクリプトをそちらを参照してください.

コマンド各種

dns-manager.sh [verbs] [optins] を意識して書いています.

Wi-Fiネットワークに設定されているDNSサーバを表示する

dns-manager.sh get -s Wi-Fi

Wi-FiネットワークのDNSサーバにCloudflareのPublic DNSを設定する

dns-manager.sh set -s Wi-Fi -d cloudflare

Wi-FiネットワークのDNSサーバにGoogleのPublic DNSを設定する

dns-manager.sh set -s Wi-Fi -d google

Wi-FiネットワークのDNSサーバに指定したIPアドレスを設定する

dns-manager.sh set -s Wi-Fi -d 192.168.1.1 192.168.1.2

Wi-FiネットワークのDNSサーバをクリアする

dns-manager.sh set -s Wi-Fi

conftestを使ってDeploymentにrequests,limitsが指定されているかをチェックする

最近は、プライベートでも仕事でもKubernetesマニフェストを書いていることが多くなってきました.
今回は「Deploymentのコンテナにrequests,limitsが指定されていること」をconftestというツールを使ってチェックする際のポリシーをメモしておきます.

# requestsが指定されていること
deny[msg] {
    input.kind = "Deployment"
    c := input.spec.template.spec.containers[_]
    not c.resources.requests

    msg = sprintf("%sコンテナにrequestsを指定してください", [c.name])
}

# limitsが指定されていること
deny[msg] {
    input.kind = "Deployment"
    c := input.spec.template.spec.containers[_]
    not c.resources.limits

    msg = sprintf("%sコンテナにlimitsを指定してください", [c.name])
}

OPAのIterationを参考に書いています.
OPAのIterationのArrayのサンプルでは、

# iterate over indices i
arr[i]

# iterate over values
val := arr[_]

# iterate over index/value pairs
val := arr[i]

と書かれており、今秋のようにDeployment内のコンテナにrequests,limitsが指定されていることを確認したければ、Iterationをポリシー内で使用してチェックすれば良いわけです.

おまけ

ちょっとしたおまけですが、どのコンテナがポリシー違反なのかを分かりやすくするためにsprintfを使ってコンテナ名を出力しています.
こういった細かい事でも出力するのと、しないのとで実際に利用するときのデバッグのしやすさが変わりますからね.