vimの補完プラグインasyncomplete-around.vimを作りました

asyncomplete-around.vimとは

asyncomplete-around.vimprabirshrestha/asyncomplete.vimのSourceプラグインです。

github.com

現在のカーソル位置を中心として上下20行(デフォルト値)に存在する単語を補完対象として表示します。

asyncomplete.vimは補完ポップアップを表示するプラグインで、これに適した補完対象(Source)を渡すプラグインと組み合わせることでポップアップに単語が表示され補完できるようになります。

asyncomplete.vimのソースプラグインとして代表的なものとして、LSPプラグインprabirshrestha/vim-lspprabirshrestha/asyncomplete-lsp.vimがあります。

作った経緯

もともと補完プラグインとしてShougo/deoplete.nvimを使っていました。
使用感に不満はありませんでしたが、Python環境を用意するのが面倒だと感じていました。

LSPとしてprabirshrestha/vim-lspを使っていたため同じ作者のprabirshrestha/asyncomplete.vimに乗り換えたところ、Buffer関連の補完が少し弱く、LSPで補完できないちょっとした単語を繰り返し入力することを面倒に感じていました。
少し調べると独自の補完ソースを追加できるとの記載があったため、asyncomplete-around.vimを作成することにしました。

オススメ設定

ここで私の設定を紹介します。
コードの詳細はGitHubにて公開しているため、適宜そちらを参照してください。

github.com

補完範囲の設定

私は画面に表示されている単語が補完できれば良いかなと思っているため50行にしています。

let g:asyncomplete_around_range = 50

Priorityの設定

asyncomplete.vimpreprocessorという仕組みがあり、補完の表示内容を編集することができます。
今回はそれを利用して補完ソースの表示順を使いやすいように制御します。

各ソースにPriorityを設定し、その値をもとにソートする関数をg:asyncomplete_preprocessorに設定します。

function! s:sort_by_priority_preprocessor(options, matches) abort
    ...
endfunction

let g:asyncomplete_preprocessor = [function('s:sort_by_priority_preprocessor')]

call asyncomplete#register_source(asyncomplete#sources#around#get_source_options({
      \ 'name': 'around',
      \ 'allowlist': ['*'],
      \ 'priority': 10,
      \ 'completor': function('asyncomplete#sources#around#completor'),
      \ }))

私はasyncomplete-lsp.vim, asyncomplete-file.vim, asyncomplete-around.vimという順番で表示させています。
asyncomplete-around.vimは指定範囲内のマッチした単語が全て候補として表示されるため、LSPで候補となっている関数や変数よりも上に表示されてしまうと、とても使いづらくなってしまうためこの順番にしています。

asyncomplete-around.vimの今後

しばらく使用してみて私の要望を満たすものだと確信できたので、上位互換のプラグインが登場しない限り今後も使用、メンテナンスをしていきます。

asyncomplete-around.vimは非同期化されていないため場合によってはカクつくことがあるかもしれません。
そのため、まずは非同期化を進めていきたいと思います。

Pull RequestのコメントからGitHub Actionsを実行する

GitHubのPull Requestのコメントから任意のGitHub Actionsを実行する必要があり、やり方に少々癖があったので紹介します。

issue_comment

ドキュメントを眺めているとPull Requestに関連するイベントはいくつかありますが、Pull Requestのコメントをトリガーとしたイベントが無いように見えます。

どうにかPull Requestのコメントをトリガーに起動できないか調べていたらCommunity Forumで以下の投稿を見つけました。

github.community

この投稿によるとissue_commentイベントでPull Requestのコメントもトリガーとなるようです。
Issueへのコメントもトリガーの対象となるので、適切にフィルタリングする必要があると書かれています。

実装

今回は/planとコメントに入力することでterraform planが実行されるGitHub Actionsを作ってみたいと思います。

stepsまでのコードが以下になります。

name: "/plan"
on:
  issue_comment:
    types: [created, edited]

jobs:
  manual_plan:
    name: "manual plan"
    if: contains(github.event.comment.html_url, '/pull/') && startsWith(github.event.comment.body, '/plan')
    runs-on: [ubuntu-latest]
    steps:

ポイントはジョブ全体でifを設定して起動する条件を設定しているところです。

contains()でPull Requestのコメントのみに反応するようにし、startsWith()/planとコメントに入力された場合のみにしています。

実行されるブランチ

上記の設定のままstepsで処理を書いていけば意図した通りに動作しそうですが、1つ問題があります。
issue_commentをトリガーとした場合、actions/checkoutでpullできるブランチがmasterブランチになってしまいます。(正確にはリポジトリのデフォルトブランチ)

Pull Requestのコメントで実行するので、そのPull Requestのブランチでジョブを実行したいです。

actions/checkout@v2からはブランチを指定してpullできるようになりました。
Actionsをトリガーした内容が入った変数、つまり${{ github }}をもとにGitHubAPIを使ってPull Requestのブランチ名を取得します。
それをactions/checkout@v2にセットし、Pull Requestのブランチでジョブを実行できるようにします。

    steps:
      - name: get upstream branch
        id: upstreambranch
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          echo "::set-output name=branchname::$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.ref' | sed 's/\"//g')"

      - name: Checkout upstream repo
        uses: actions/checkout@v2
        with:
          ref: ${{ steps.upstreambranch.outputs.branchname }}

Pull Requestに結果を書き込み

せっかくなのでplanの結果をPull Requestのコメントに書き込みたいと思います。

GitHubに関連する操作はactions/github-scriptを使えば大体の事ができます。

github.com

      - id: plan
        run: terraform plan -no-color

      - name: plan success message
        uses: actions/github-script@0.9.0
        if: steps.plan.outputs.exitcode == 0
        env:
          STDOUT: "```${{ steps.plan.outputs.stdout }}```"
        with:
          github-token: ${{secrets.GITHUB_TOKEN}}
          script: |
            github.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: "#### `terraform plan` Success\n" + process.env.STDOUT
            })

LFSをやってみたので振り返る

長期休暇を利用してLFSをやってみたので振り返ってみたいと思います。

きっかけ

日頃から業務でLinuxを使ってサービスを運用していますが、最近はクラウドやコンテナなどを使うことがほとんどでLinuxのほんの一部だけを知っていれば運用できています。
しかし、何らかの障害が発生したときの調査は平常時に使う一部の知識だけでは難しいことが多いです。
そんな状態から脱出すべく、以下の書籍を読みました。

gihyo.jp

www.sbcr.jp

これらを読んでいるとLinuxの仕組みがわかってきますが、ただテキストを読んでいるだけだとどうしても印象が薄くなってしまい忘れてしまうことが目立ってきました。
そこで何か手を動かしてできることがないか調べてみるとLFSLinux From Scratch)を見つけたので、やってみることにしました。

期待したこと

LFSをやることで以下の内容を取得できることを期待して手を動かしてみました。

  • Linuxの基盤となる部分を知る
  • サービスを運用するときの障害対応力を上げる

構築中に感じたこと

Linuxで使われるファイルやディレクトリを手作業で作成する

LinuxをインストールするとFHSに即したディレクトリが構築され、/etc/passwd/etc/groupには初期のユーザー情報が記述されています。
LFSで1から構築する場合はこれらのファイルやディレクトリも自分で作成することになります。
当たり前だと言えばそうかもしれませんが、なかなか新鮮な感覚でした。

ソフトウェアのバージョンを合わせることの重要性

LFSは構築に使うパッケージを全て自前でビルドします。
その関係上、まずはホストシステムでLFSの構築に使う環境の構築を行います。この環境の構築はホストにインストールされているソフトウェアを使うため、このバージョンが異なっていると予期せぬエラーに遭遇することもあります。

Linux KernelやGlibcなど、変更するのが大変なものもありますが、ホストのOSを変えるなどして、できる限り合わせる必要があります。
最低でもマイナーバージョンまでは揃えるべきです。

ホスト環境をチェックするスクリプトが載っているので、よくチェックすることをオススメします。

lfsbookja.osdn.jp

クラウドでは使用頻度が少ないコマンドを実行する

LFSを始めると最初に専用のパーティションを作成することになります。
パーティションを作成するためにfdiskコマンドを実行したり、それをマウントするためのmountコマンド、/ets/fstabへの記述、そして構築が進んでいくと作り途中のLFS環境に入るためのchrootコマンド、デバイスノードを作成するmknodコマンドなど、クラウドでサービスを運用しているとあまり使わないコマンドを実行します。

クラウド以外で運用する場合やLinuxに関連する開発を行う場合、これらのコマンドは定期的に使うことになるので久しぶりに実行して思い出すことができたのは良い機会だったと思います。

知らないこと、忘れていることはその場で調べる

これが今回の構築で一番良かったところです。

LFSのドキュメントはとてもよくできており、ほぼ全ての作業をコピペするだけで構築できます。
そのコピペするコマンドの中には上記の本で触れられているがあまり使わないコマンドや、そのそも出てこないものなどもあります。
それらのコマンドを知らないままでもコピペで実行すれば構築できてしまいますが、コマンドのオプションも含め、知らないものは全て調べます。
時間はかかりますが勉強になります。

また、LFSでは全てのパッケージをビルドするため、インストールする全てのライブラリに触れます。
その中には知らないパッケージや生成されるコマンドがあると思います。
LFSのドキュメントに簡単には説明が書いてありますが、これも調べるととても勉強になります。

期待したものは得られたか

構築が完了した時点で期待した成果が得られたのか振り返ります。

Linuxの基盤となる部分を知る

これに関しては十分に達成できたと思います。
ただ、ライブラリやコマンドはビルドしただけなので、ふつうのLinuxプログラミングで書かれている内容をもう一度やってみるとさらなる理解が得られそうな気がしています。

サービスを運用するときの障害対応力を上げる

障害対応力は構築が完了した段階だとまだ上がった実感はありません。
少なからず関連はしていると思うので、実際に対応を行ってみて変化を感じるかどうか様子を見てみたいと思います。

今後にむけて

サービスをLinuxの上で運用していくことはまだしばらく続くと思うので、さらなる理解と障害対応力を磨いて行きたいと思います。
そのために次はこれらの書籍をよんでみようかと思っています。

www.seshop.com

www.oreilly.co.jp

TerraformのStateをS3からTerraform Cloudに移行する

TerraformのStateをチームで管理するときに、複数の環境からplanやapplyを実行できるようにするためにS3などのクラウドのストレージをバックエンドにすることが多いと思います。

通常のストレージをバックエンドにしていると、複数の環境から同時にapplyが実行されたときに壊れてしまう可能性があります。CIで実行するようにしていても、設定が適切ではなく平行に動いてしまうような場合などに壊れる可能性があります。

Terraform CloudにはStateのロック機能がありこのような事故から開放されるため、Stateの管理をS3からTerraform Cloudに移行したいと思います。

環境

  • Terraform v0.12.24
  • 元のBackendはS3
  • Terraform Cloudのアカウントは作成済み

手順

Terraform Cloudへのアクセス設定

まずはterraformコマンドがTerraform Cloudにアクセスできるようにします。

terraformのバージョンが0.12.21以降の場合は terraform login コマンドを実行して、ブラウザに表示されたトークンを入力すれば完了です。

www.terraform.io

loginコマンドが使えない場合は $HOME.terraformrc というファイルを作成し、以下のようにトークンを設定します。

credentials "app.terraform.io" {
  token = "xxxxxx.atlasv1.zzzzzzzzzzzzz"
}

また、TF_CLI_CONFIG_FILE という環境変数を設定すれば、任意の場所に任意の名前で設定ファイルを作成できます。

www.terraform.io

www.terraform.io

backendの書き換え

S3の設定が書かれていたbackendをTerraform Cloudのものに書き換えます。

  backend "s3" {
    ...
  }

これを削除して以下のように書き換えます。

  backend "remote" {
    hostname     = "app.terraform.io"
    organization = "your_organization_name"

    workspaces {
      name = "your_workspaces_name"
    }
  }

移行

準備が整ったので移行させていきます。

terraform init を実行するだけで移行ができます。
実行すると以下のように聞かれるので yes を入力します。

$ terraform init

Initializing the backend...
Backend configuration changed!

Terraform has detected that the configuration specified for the backend
has changed. Terraform will now check for existing state in the backends.


Terraform detected that the backend type changed from "s3" to "remote".
Acquiring state lock. This may take a few moments...
Do you want to copy existing state to the new backend?
  Pre-existing state was found while migrating the previous "s3" backend to the
  newly configured "remote" backend. No existing state was found in the newly
  configured "remote" backend. Do you want to copy this state to the new "remote"
  backend? Enter "yes" to copy and "no" to start with an empty state.

  Enter a value:

yes を入力し、問題なく成功すれば以下のように表示されます。

Successfully configured the backend "remote"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

これで移行が完了です。

参考

Kubernetes上で動くGoサーバーでプロファイラを動かしWeb UIで表示する

Goにはプロファイラとして標準パッケージにpprofが搭載されています。
pprofの使い方としてはすでに多数の優良記事が存在するため、ここでは扱いません。

今回はpprofをk8s上で動くサーバーに対して実行し、結果をWebUIで表示する必要があったのでそのやり方を紹介します。

環境

インフラ構成

  • GoサーバーのHTTP API用のPortが80
  • pprof用のPortが6060
  • プロファイラ結果のWeb UI用のPortが8080
    • このPortをServiceのNodePortでMacに公開

やり方

pprofの設定

まずは通常通りpprofの設定をしてGoをビルドします。

import (
    ...
    _ "net/http/pprof"
)

func main() {
    ...

    go func() {
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()

    ...
}

このバイナリがk8sのPodで動いているとします。

Serviceの定義

通常のAPIへのアクセスに使うServiceが以下です。

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: my-service
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 80

これに追加してpprofのWebページにアクセスするためのServiceを定義します。
このServiceのTypeをNodePortにすることでお手軽にアクセスできるようになります。

apiVersion: v1
kind: Service
metadata:
  name: my-service-pprof
spec:
  selector:
    app: my-service-api
  type: NodePort
  ports:
    - name: http-pprof
      protocol: TCP
      port: 8080
      targetPort: 8080
      nodePort: 30080

pprofの実行

Goのバイナリが動くPodに入り以下のコマンドでpprofを実行します。

$ pprof -http=0.0.0.0:8080 /usr/local/bin/my-api http://localhost:6060/debug/pprof/profile

ここで大事なのはpprofのWebページのアドレスを 0.0.0.0 にすることです。ここを 127.0.0.1 にしていると外部からアクセスできなくなってしまいます。

あとは計測したいエンドポイントにアクセスし以下のようなメッセージが表示された後、ホストからNodePortの 30080 にアクセスすればプロファイル結果がWebUIで表示されます。

Saved profile in /root/pprof/pprof.my-api.samples.cpu.001.pb.gz
Serving web UI on http://0.0.0.0:8080
Couldn't find a suitable web browser!
Set the BROWSER environment variable to your desired browser.

Magiskのroot化を維持したままOTAアップデートする

Magiskでroot化するとOTAアップデートが無効化されるため、セキュリティパッチなどのOSのアップデートを行うことができなくなります。
しかし、適切な手順を踏むことでroot化を維持したままOTAアップデートを行うことができます。

検証環境

  • Pixel3
  • Android 10
  • TWRPを使用しないroot化(boot.imgのパッチ)

手順

注意事項

以下の手順の中で再起動を促すボタンが数回表示されますが、再起動してはいけない場面が多数あります。
手順の中で再起動をすると記載したとき以外は再起動のボタンが表示されてもタップしないように注意してください。
root化を維持できなくなります。

大まかな流れ

  • 一時的にMagiskをアンインストール
  • アップデートをインストール
  • アップデート後のイメージにMagiskをインストールするように設定する

Magiskのアンインストール

まずはOTAアップデートをインストールするために一時的にMagiskをアンインストールします。

Magisk Managerを起動し、アンインストールをタップします。
その後に表示されるダイアログでイメージの復元を選択します。
復元が完了しました!と表示されればOKです。

ここで再起動をしてはいけません。

アップデートのインストール

1つ前の作業で正規のイメージになったため、アップデートのインストールができるようになりました。正規の手順でOTAアップデートを行います。

バイスの[設定]→[システム]→[システムの更新]に移動し、 [ダウンロードとインストール]ボタンを押してインストールします。

インストールが完了すると再起動のボタンが表示されますが、タップしてはいけません。

Magiskの再インストール

再度Magisk Managerを開き、インストールをタップします。
タップ後に表示されるダイアログで非アクティブスロットにインストール(OTA後)を選択します。
警告が表示されますが、はいを選択してインストールを行います。
インストールが完了すると、再起動のボタンが表示されるのでタップして再起動します。

これでOTAアップデートにMagiskがインストールされます。

参考

Magiskでroot化したPixel3のテザリング制限を解除する

民泊系と呼ばれるSIMを使っている場合、制限がかかっていてテザリングをすることができません。

しかしMagiskでroot化していればこの制限を解除でき、民泊系の数百GB/月という容量を使ってテザリングをすることができます。

前提

必要なモジュールのインストール

テザリングの制限を解除するためにMagiskHide Props Configを使います。
このツールはbusyboxというツールに依存しているためこちらもインストールします。

インストールはMagisk Managerのタブにあるダウンロードを表示し、そこにあるモジュールの一覧から見つけ出して画面の案内にそってインストールします。

テザリング制限を解除

適当なターミナルアプリを用意して以下のコマンドを順番に入力していけば解除できます。
ターミナルアプリがインストールされていない場合は以下のアプリを入れましょう。

play.google.com

ターミナルから以下のコマンドを順に入力します。

  • su
  • props
  • 4
  • n
  • net.tethering.noprovisioning
  • true
  • y

yを入力後に端末が再起動します。

再起動後、設定からテザリングを有効にできます。