PHPの`$`と`->`がつらくなったらマクロを使えば良いことに気づいた

最近PHPを書くことが多いのですが、変数の前につける$(ドル記号)とメソッド呼び出しやプロパティ呼び出しに使う->(矢印)の指の動きの効率悪さに耐えられなくなりました。。。

PHPStormを使っているといい感じに$を入力しなくても変換してくれたりするのですが、その動きもまた気持ち悪く感じてしまうのでどうしたものかと思っていました。

f:id:kenev:20190624210413g:plain
慣れないと気持ち悪い

そこで今更ながら発見したのがエディタの「マクロ」機能です!

pleiades.io

マクロ作成

f:id:kenev:20190624210909p:plain

PHPStormであれば上のキャプチャのように「Edit > Macros > Start Macro Recording」を実行することでマクロの記録を始めることができます。

記録中は下図のようなインジケーターが表示されます。

f:id:kenev:20190624210939p:plain
マクロ記録中

ここでマクロとして入力したいキーを押します。$用のマクロを作りたいので$を入力すると、記録されているのがわかります。

f:id:kenev:20190624211226p:plain

そして赤い■をクリックするとマクロの記録が終わるので、このマクロに名前をつけてあげます。わかりやすく$にしておきます。

f:id:kenev:20190624211317p:plain

これでマクロは完成です!

同じ要領で->も作っておきましょう。

マクロのショートカット作成

マクロが完成したら、今度は自分の好きなショートカットでこれが入力されるようにします。

「Preferences > Keymap」に進んで、虫眼鏡のところでmacroと入力しましょう。下のように絞り込まれるはずです。

f:id:kenev:20190624211651p:plain

ここに先程作成した$->があるのがわかります。あとはここに自分の好きなキーの組み合わせを設定することで、以後このキーを入力すれば$->が入力できるようになります!

ちなみに僕は

  • $ctrl+s
  • ->ctrl+.

という設定にしました。ちょっとですけど快適になった気がします!

他の皆様がどうしているのか気になったりします(むしろ気にしてないですかね。。。)

まとめ

  • PHPStormのマクロを設定した
  • マクロならコードスニペットとは違ったショートカットを作ることができる

VSCodeであれば下記拡張機能を使えばできそうな気がします!

github.com

explainshell.comはシェルの強力な助っ人

f:id:kenev:20190616075801p:plain
explainshell.com

最近explainshell.comなるものを今更ながら知ることができたので紹介します!

explainshell.com

概要

シェルコマンドを全部覚えるのって大変です。なんとなく覚えてても、オプションがどんな意味をもっていたかまではなかなか思い出せないです。おまけにシェルは強力なのでパイプ(|)でどんどん繋げていい感じにコマンドを書いていくことができます。そんな芸術ともいうコマンドを見ては、manを見たりググったりして、分解しながらコマンドを理解していっているのはきっと僕だけじゃないはず。

そこで便利なのがこのexplainshell.comというサイトです!

使い方

使い方はいたって簡単で、インプットに知りたいコマンドを入力するだけ。

試しに最近覗いてみた下のk3sのインストールスクリプトから抜粋してみます。

https://get.k3s.io

lsof | sed -e 's/^[^0-9]*//g; s/  */\t/g' | grep -w 'k3s/data/[^/]*/bin/containerd-shim' | cut -f1 | sort -n -u

f:id:kenev:20190616080716p:plain

すごい!全部のコマンドに丁寧な説明がいい感じにつくんです!

そして「←→」で順番にコマンドの解説を表示していくことも可能。

f:id:kenev:20190616081012g:plain

とってもシンプルですけど、ブックマークしておけば困ったときにサクッと使えて重宝しそうです!

おまけ

おまけですが、僕の場合はAlfredにショートカットを作ってすぐに調べられるようにしてます。

www.alfredapp.com

参考までにWeb Searchの設定は下のようにしています。

f:id:kenev:20190616085456p:plain

そうすると下のようにすぐに調べることができます!

f:id:kenev:20190616091822g:plain

まとめ

  • explainshell.comを試した
  • シェルコマンド調べるときに便利
  • Alfredなどツールと連携すればさらに強力!

Node.jsではてなの自分の記事一覧を取得する

はてなの自分の記事一覧をサクッと取得できるようにしたかったのですが、意外と

  • Node.jsで書かれている
  • 結果をJSONにする

ものが見当たらなかったので結局自分で作ることにしました!

ツールの流れ

はてなから自分の記事一覧を取得する流れは下のようになります。

  1. はてなのAtomPubを使う(認証はこの記事ではBasic認証を使います)
  2. エンドポイントをGETすると最新の記事が10件とれる
  3. 結果はXMLなのでJSONに変換する
  4. 2のレスポンスに次の10件のURLが記載されているので、全部取得終わるまで2, 3を繰り返す

はてなのAtomPub

はてなのAtomPubの仕様については以下Hatena Developer Centerに記載されています。

developer.hatena.ne.jp

認証のところに書かれていますが、取得時に使える認証は

の3種類です。個人的にしか使わないし、簡単な方法でとりたいので今回はBasic認証を使うことにしました!

Basic認証に必要なユーザー名ははてなIDで、パスワードはAPIキーを使用します。

APIキーははてなブログの管理画面から設定>詳細設定に行った場所にあります!

f:id:kenev:20190614063328p:plain
設定>詳細設定

下の方を見るとAtomPubの項目があり、そこにAPIキーがあるのでメモっておきましょう!

f:id:kenev:20190614063417p:plain
APIキー

ブラウザからGETしてみる

それでは、試しに記事一覧をブラウザから取得してみます。

ドキュメントにも記載がありますが、以下のURLで取得できます。

GET https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry

僕の場合はてなID=kenevブログID=kenfdev.hateblo.jpなので、以下になります。

https://blog.hatena.ne.jp/kenev/kenfdev.hateblo.jp/atom/entry

これをブラウザに入れると、下のようにBasic認証が聞かれるはずです。

f:id:kenev:20190614063856p:plain

ここにユーザー名APIキーを入力すると、下のようなXMLが表示されます。

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"
      xmlns:app="http://www.w3.org/2007/app">
  <link rel="first" href="https://blog.hatena.ne.jp/{はてなID}}/{ブログID}/atom/entry" />
  <link rel="next" href="https://blog.hatena.ne.jp/{はてなID}/{ブログID}/atom/entry?page=1377584217" />
  <title>ブログタイトル</title>
  <link rel="alternate" href="http://{ブログID}/"/>
  <updated>2013-08-27T15:17:06+09:00</updated>
  <author>
    <name>{はてなID}</name>
  </author>
  <generator uri="http://blog.hatena.ne.jp/" version="100000000">Hatena::Blog</generator>
  <id>hatenablog://blog/2000000000000</id>
  
  <entry>
    <id>tag:blog.hatena.ne.jp,2013:blog-{はてなID}-20000000000000-3000000000000000</id>
    <link rel="edit" href="https://blog.hatena.ne.jp/{はてなID}/
    ブログID}/atom/edit/2500000000"/>
    <link rel="alternate" type="text/html" href="http://{ブログID}/entry/2013/09/02/112823"/>
    <author><name>{はてなID}</name></author>
    <title>記事タイトル</title>
    <updated>2013-09-02T11:28:23+09:00</updated>
    <published>2013-09-02T11:28:23+09:00</published>
    <app:edited>2013-09-02T11:28:23+09:00</app:edited>
    <summary type="text"> 記事本文 リスト1 リスト2 内容 </summary>
    <content type="text/x-hatena-syntax">
      ** 記事本文
      - リスト1
      - リスト2
      内容
    </content>
    <hatena:formatted-content type="text/html" xmlns:hatena="http://www.hatena.ne.jp/info/xmlns#">
      &lt;div class=&quot;section&quot;&gt;
      &lt;h4&gt;記事本文&lt;/h4&gt;
      &lt;ul&gt;
      &lt;li&gt;リスト1&lt;/li&gt;
      &lt;li&gt;リスト2&lt;/li&gt;
      &lt;/ul&gt;&lt;p&gt;内容&lt;/p&gt;
      &lt;/div&gt;
    </hatena:formatted-content>
    <app:control>
      <app:draft>no</app:draft>
    </app:control>
  </entry>
  <entry>
  ...
  </entry>  
  ...
</feed>

XMLが取得できそうだとわかったところで、Node.jsで実装してみます!

Node.jsで取得する

まずはソースを載せちゃってからポイントを解説します。

const axios = require('axios');
const parseString = require('xml2js').parseString;

const hatenaAPIUser = '<USER_ID>';
const hatenaAPIPassword = '<API_KEY>';
const entryUrl = `https://blog.hatena.ne.jp/<USER_ID>/<BLOG_ID>/atom/entry`;

const creds = `${hatenaAPIUser}:${hatenaAPIPassword}`;
const encoded = Buffer.from(creds).toString('base64');
const authorizationHeader = `Basic ${encoded}`;

/**
 * Promise based timeout
 * @param {number} msec wait milliseconds
 */
function wait(msec) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve();
    }, msec);
  });
}

/**
 * Promise based XML->JSON Parser
 * @param {object} data XML Data
 */
function parseStringPromise(data) {
  return new Promise((resolve, reject) => {
    parseString(data, (err, result) => {
      if (err) {
        reject(err);
        return;
      }
      resolve(result);
    });
  });
}

/**
 * Get all Hatena entries recursively
 * @param {string} url Entry URL
 */
async function getAllEntries(url) {
  console.log('fetching:', url);
  const entries = await axios.get(url, {
    headers: {
      Authorization: authorizationHeader,
    },
  });

  const {
    feed: { entry, link },
  } = await parseStringPromise(entries.data);

  const next = link.find(l => l.$.rel === 'next');

  const parsedEntries = entry
    .filter(e => {
      const {
        'app:control': [
          {
            'app:draft': [isDraft],
          },
        ],
      } = e;
      return !(isDraft === 'yes');
    })
    .map(e => {
      const categories = e.category ? e.category : [];

      return {
        id: e.id[0],
        title: e.title[0],
        url: e.link[1].$.href,
        published: e.published[0],
        publishedAt: new Date(e.published[0]).getTime(),
        updated: e.updated[0],
        updatedAt: new Date(e.updated[0]).getTime(),
        summary: e.summary[0]._,
        categories: categories.map(c => c.$.term),
      };
    });

  if (next) {
    // recursively fetch if 'next' exists
    const nextHref = next.$.href;
    await wait(500);
    const nextEntries = await getAllEntries(nextHref);
    return [...parsedEntries, ...nextEntries];
  } else {
    return parsedEntries;
  }
}

getAllEntries(entryUrl).then(console.log);

ポイントとしては

  • 外部リクエストにはaxiosを使い、BASIC認証用のHTTPヘッダーを作成している
  • XMLからJSONにはxml2jsというライブラリを使っている
  • 無限ループで負荷かけちゃうの怖いのでリクエスト間に500ミリ秒待つ
  • 最終的なJSONの形は自分で決める

となります。順番に説明します!

外部リクエストにはaxiosを使い、BASIC認証用のHTTPヘッダーを作成している

最近axiosを使う機会が多かったので、なんとなく外部リクエストのライブラリはaxiosにしました。 ライブラリは割とどうでもいいのですが、ポイントはBASIC認証のHTTPヘッダーを作っている部分です。

const creds = `${hatenaAPIUser}:${hatenaAPIPassword}`;
const encoded = Buffer.from(creds).toString('base64');
const authorizationHeader = `Basic ${encoded}`;

...

  const entries = await axios.get(url, {
    headers: {
      Authorization: authorizationHeader,
    },
  });

...

BASIC認証を実現するためにはHTTPヘッダーに Authorization というものを作って、中身はBasic <ユーザー名:パスワードをbase64エンコードした値>となります。

XMLからJSONにはxml2jsというライブラリを使っている

XMLからJSONにするために下のライブラリを使っています。

github.com

axiosで取得したXMLの中身をこのライブラリに通すことで、JSONを取得することができます。

const parseString = require('xml2js').parseString;

/**
 * Promise based XML->JSON Parser
 * @param {object} data XML Data
 */
function parseStringPromise(data) {
  return new Promise((resolve, reject) => {
    parseString(data, (err, result) => {
      if (err) {
        reject(err);
        return;
      }
      resolve(result);
    });
  });
}

...

  const {
    feed: { entry, link },
  } = await parseStringPromise(entries.data);

...

個人的にCallbackがあまり好きじゃないので、Promiseにわざわざ変換したfunction書いてますけど、これは別に必要ないです。Promiseにしてawaitしたかっただけです。

無限ループで負荷かけちゃうの怖いのでリクエスト間に500ミリ秒待つ

コードがバグっていてはてなさんのサーバーに変な攻撃を起こしてしまうのが怖いので、リクエスト間に待ちを発生させるようにしてます。

/**
 * Promise based timeout
 * @param {number} msec wait milliseconds
 */
function wait(msec) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve();
    }, msec);
  });
}

...

  if (next) {
    // recursively fetch if 'next' exists
    const nextHref = next.$.href;
    await wait(500);
    const nextEntries = await getAllEntries(nextHref);
    return [...parsedEntries, ...nextEntries];
  } else {
    return parsedEntries;
  }

...

先程と同じく、setTimeoutをPromiseにしてawait使いたかったのでわざわざfunction作っています。あとは再帰呼び出しをする直前にawait wait(500)と書いて500ミリ秒待つようにしています。

最終的なJSONの形は自分で決める

XMLからJSONに変換したものは、そのままではものすごく使いづらいので、適宜興味のある項目だけ抜き取って最終的な形に整形します。

...
      const categories = e.category ? e.category : [];

      return {
        id: e.id[0],
        title: e.title[0],
        url: e.link[1].$.href,
        published: e.published[0],
        publishedAt: new Date(e.published[0]).getTime(),
        updated: e.updated[0],
        updatedAt: new Date(e.updated[0]).getTime(),
        summary: e.summary[0]._,
        categories: categories.map(c => c.$.term),
      };
...

こうすると、下のようなJSONになります。

{
  "id": "xxxxxxxxxxxxxxxxxxx",
  "title": "AWS SAA再認定のために5日間頑張ったこと",
  "url": "https://kenfdev.hateblo.jp/entry/saa-recertification",
  "published": "2019-03-08T20:42:58+09:00",
  "publishedAt": 1552045378000,
  "updated": "2019-03-08T20:42:58+09:00",
  "updatedAt": 1552045378000,
  "summary": "昨日AWSソリューションアーキテクト・アソシエイト(以下SAA)に再認定されました。3月中に取得しないといけないというミッションがあったので慌てて先週申し込みました。試験会場の関係で試験日まで5日しか無かったのですが、合格できたので備忘録を残しておきます。 TL;DR(まとめ) …",
  "categories": ["AWS"]
}

まとめ

以上ではてなの記事一覧の取得ができます!最後はfsを使ったりしてファイルにJSONを保存すれば何か他の用途に使うことができるかと思います。

以下リポジトリにコードを格納していますので、興味がある方は使ってみてください!

github.com

「k3sとラズパイでフロントエンド開発に挑戦した話」を発表しました

今週は2つのイベントでLTをさせていただきました!ほぼ同じ内容で登壇したので、2日連続で参加された方には申し訳なかったのですが、少し内容を変える努力はしました。。。

rancherjp.connpass.com

cnjp.connpass.com

発表資料

 

ストーリー

フロントエンジニアでラズパイ持っててkubernetesに興味があったら、とりあえずラズパイ上にNuxt.js乗せたくなりますよね!?

ということで、今回はNuxt.jsをラズパイに入れたk3s上にデプロイすることに挑戦したときの話をしました!ポイントとしては下のとおり!

  • arm32v7/nodeの alpine だと npm install でコケる
  • なのでARMのイメージで alpine はあきらめる
  • docker create manifest でマルチアーキイメージを作れる(ARMとx86_64で同じイメージ名!)
  • Dockerfileの FROMARG を使うとイメージレイヤのキャッシュが効かない疑惑がある

ヘルプをもらえました!

node のイメージを素で使ってましたけど、 node-slim にしては? というアドバイスがいただけました!

f:id:kenev:20190608003541p:plain
879mb→310mb

半分以下には落とせました!けれど2桁までは落としたい気持ちがあります。。。

ARG 使うとキャッシュは効かない!? についてのアドバイスもいただけました!

上の公式ドキュメント読みましたけど、毎回同じ値にしてるのにキャッシュ効かないんですよねー。これは引き続き調査 or Dockerfileを分ければいいのかなと思ったり。

おまけ

こういうコメントがもらえてうれしいです!アプリ寄りな話を増やしたい気持ちはあるんですよね。

なかなか時間を作るのにも苦労していますが、今後もフロントエンドとコンテナをからめていきたいです!

カックさん(@kakakakakku)さんのメンタリングを卒業しました

3月から5月の3ヶ月間に渡るカックさん(@kakakakakku)のブログメンタリングを卒業しました!

ここに卒業記事を残し、今後についても書き残しておきたいと思います。

メンタリングをなぜ受けたのか

僕はアウトプットすること自体は好きで、ブログを書いたりイベントに登壇したりは不定期にしていました。自分でも認識していた問題はアウトプットの習慣化がまるでできていなかった点です。その点を改善すべく、メンタリングを受ける決意をしました。

アウトプットの習慣化がなぜできないのか

「なぜアウトプットの習慣化ができないのですか?」と聞かれたら多くの人が「忙しくて時間が無い」と答えるかと思います。

今、「うんうん」と頷いた方、ぜひぜひ下の記事を読んでいただきたいです。これだけでもマインドセットが変わると思います!

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

僕も「時間が無い」と思っていた中の一人ですが、「時間が無い」と定義していたのは自分です。誰かに強制的に自分の時間を奪われていたわけではありません(そういう人もいるかもしれませんが…)。時間の使い方をコントロールできていなかったのが大きな原因だと気づくことができました。

3ヶ月の実績

3ヶ月の実績は下のとおりです。

f:id:kenev:20190603205019p:plain
PV

項目 3月初 5月末
Twitter Follower 393 410
はてブ 0 12
読者数 0 31

メンタリングが始まって結構すぐにCloud Native Kansaiという大きめなイベントでLT登壇することができたので、PVがすごく上がっています。

そのときの記事がこちら。

kenfdev.hateblo.jp

けれど、この波に乗ることができずにいっきに低迷!そこからOPAネタを書いていきながら、最後は書評に挑戦して徐々にPVを上げていくことができたかなと思っています(まだまだですが)。

驚くような結果にはならなかったので反省していますが、今後も継続して精進していきます!

課題

卒業とともにカックさんに最後のフィードバックをもらいました。中でも特に僕が今後も改善していきたい点については「満足できるクオリティに達するまでの距離を遠くに置きがち」なところです。週1のアウトプットを出していくことはできるようになったのですが、1アウトプットするまでに要している時間が多すぎると感じています。

いつもどうしても

  • まとまりが悪い
  • ストーリー性が見えにくい
  • もっと違う観点を取り入れたほうがいいのでは
  • などなどetc.

と考えてしまって、書いては消して、書いては消してを続けてしまいます。「小さく考える力」が圧倒的に足りていないのを痛感しています。 カックさんにも薦められた「小さな習慣」を読み、「常にアジャイルに、小さく考えられるように」改善し続けたいと思っています!

小さな習慣

小さな習慣

メンター、カックさんについて

カックさんは本当にすごい方です。そのすごさは他のメンティの卒業エントリを見てもわかると思います。今後もエントリは増えていくと思うので、下のようにググったりTwitter検索した内容を確認すると勉強になります。

www.google.com

twitter.com

すごい点は数え切れないほどあるのですが、僕からも1点共有しておきたいです。

それは「やってみせる・やらせてみる・フィードバックする」のサイクルが本当にうまく回っていると感じた点です。

この記事を書いている最中にもカックさんからこんなツイートが流れています。

カックさん自身、かなり多忙なはずなのですが、毎週必ずアウトプットをしています。メンターであるカックさんがやってみせてから、メンティにもやらせてみる。そして、やらせてみた後は必ずフィードバックがもらえます。フィードバックは「よかった」「悪かった」なんてシンプルなフィードバックじゃなくて、かなり濃厚で、親身なフィードバックがいただけます。すごいのは、それらのフィードバックや他の通常な会話も合わせて、メンティの僕が精神的に負荷を感じることが無い点です。「がんばろう」という気持ちに素直になりますし、「しんどい」ときにはどう乗り越えていくことができるか相談に乗ってくれます。メンターの鏡です!自分自身で最高のメンターによるメンタリングを経験することができたので、僕もこうなれるように、この点についても日々精進していきたいです。

今後について

このメンタリングを通して毎週最低1記事を書くということが習慣化できたので、自分は成長したと感じています。メンタリング中に初挑戦した書評ともちょうどフィットするのですが、メンタリングを受けたことで、セルフマネジメントのレベルが上がって、ブログを書くことの習慣化が可能になったのかなと思います。

今後3ヶ月の目標としては

としたいです。

習慣化したのを継続すること(当たり前ですが!)、質の高い記事が書けるようになること。そして、英語のアウトプットも増やしていけたらと思います!(世界は広い!)

またちょっと長い目な記事になってしまいましたが、以上で卒業報告とさせていただきます!

カックさん(@kakakakakku)、本当にありがとうございました!!!

Unit Test好きにはたまらない!Conftestで設定ファイルのテストをしてみた

KubeConでOpen Policy Agent関連の発表を追っていたところ、面白そうなプレゼンを発見しました!

Unit Testing Your Kubernetes Configuration with Open Policy Agent

僕はUnit Test大好き人間なのでUnit Testと聞くだけで興奮するのですが、それにさらにOpen Policy Agentが関わっているときたら放っておけません!

もしOpen Policy Agentについて初めて聞いたという方は以下の記事でも紹介しているので興味があればぜひ!

kenfdev.hateblo.jp

Conftest

ConftestはYAMLあるいはJSONで定義された設定ファイルに対してテストを書けるというツールです。

面白いのは、テストに使うのがOpen Policy AgentのRegoというポリシー用の言語だという点です。上に紹介した記事にも書いていますが、さらに気になる人は公式サイトでもRegoについて詳しく書かれているのでぜひ読んでみてください。

www.openpolicyagent.org

インストール

Macにインストール。僕はbrewを使いましたが、様々な方法でインストールできますので、READMEをチェックしましょう!

brew tap instrumenta/instrumenta
brew install conftest

インストールはこれで完了です!

テストを書く

ではテストを書いてみましょう!KubernetesYAMLがテストできるってところがハイライトされがちですけどYAML, JSONならなんでもOKです!

ということでせっかくなのでチュートリアルには無いCircleCIのYAMLを対象にしました!

テストを書くにあたって重要なのはRegoという言語を使うということです。

Regoはポリシーに特化した言語で、ちょっとトリッキーなところもありますが、宣言的で読みやすい言語だと思います。

例えばCircleCIの以下のようなconfigがあったとします。

version: 2.0
jobs:
  build:
    docker:
      - image: circleci/buildpack-deps
    steps:
      - checkout
  test:
    docker:
      - image: kenfdev/golang:1.12
    steps:
      - checkout
workflows:
  version: 2
  build_and_test:
    jobs:
      - build
      - test

これを.circleci/config.ymlに置きます。

conftest を導入すると何ができるかというと、このYAMLの記述内容に対してポリシーを書くことができます。

具体的にはこんなポリシーを割り当てることができます

  • version: 2.1 以上しか使っちゃいけない
  • CircleCI純正のイメージしか使っちゃいけない
  • コンテナのイメージにlatestを使っちゃいけない

などなどです。

実際にこのポリシーを適用したテストを順番に書いてみます。

conftestはデフォルトでpolicyディレクトリ配下の.regoファイルを見てくれる様なので、circleci.regoというファイルを作ります。

touch policy/circleci.rego

この中にルールを書いていきます。

packageを指定

Regoのpackageはデフォルトでmainが使われます。これは--namespaceオプションで変更可能とのこと。

なので、packagemainにしましょう。

package main

version: 2.1 以上しか使っちゃいけない

「CircleCIのOrbs機能を使いたいから、Versionを必ず2.1以上にしたい」というポリシーがあるかもしれません。その場合に書けるポリシーは以下のとおり。

deny[msg] {
 input.version < 2.1
 msg = "Use version 2.1 or higher"
}

denyになる条件を探すので、「どうなっているとルール違反か?」というマインドが必要です。inputというのはRegoで特別な変数で、conftestの場合ここにテスト対象のYAMLあるいはJSONが入っています。なのでinput.versionversionにアクセスできます。

conftestを実行してみます!

$ conftest test config.yml
config.yml
   Use version 2.1 or higher

2.0config.ymlに指定しているので怒られてますね!

CircleCI純正のイメージしか使っちゃいけない

こんなユースケースがあるかどうかはさておき、使うコンテナのimageにポリシーを設けたい場合の例になります。

必ずimagecircleci/から始まっているかどうかをチェックしてみます!

deny[msg] {
  docker_images := input.jobs[_].docker[_].image
  not startswith(docker_images, "circleci/")
  msg = "Only use official CircleCI images"
}

Regoのちょっとトリッキーな書き方をさっそく取り入れてみました。

docker_images := input.jobs[_].docker[_].image

これを言語化すると「jobsの中に含まれる複数のキー(build, test)の中にあるdockerという配列の中のimageキーの値」になります。

こうやって書くことでOPAがよしなに順番に拾ってくれます。そして、それらがcircleci/で始まってなかったらアウトにします。

not startswith(docker_images, "circleci/")

ここで使っているstartswithはRegoに予め組み込まれているビルトイン関数です。OPAのドキュメントにも載っているので、どういうものがあるかチェックしておくといざとなったら使えて便利です!

www.openpolicyagent.org

circleci/で始まっていないもの」を探すので、先頭にnotをつけている点もお忘れなく!

実行してみます!

$ conftest test config.yml
config.yml
   Only use official CircleCI images
   Use version 2.1 or higher

kenfdev/golang:1.12 を使っているので怒られていますね!

コンテナのイメージにlatestを使っちゃいけない

またまたimageに関連するポリシーです。安定したCIを回すためにimageに必ずタグを指定するというポリシーが必要になるかもしれません。latestを使っていたらルール違反にしましょう。

deny[msg] {
  docker_images := input.jobs[_].docker[_].image
  tag_is_latest(split(docker_images, ":"))
  msg = "Do not use `latest` container image tags"
}

# helpers
tag_is_latest(images) {
  count(images) < 2
}

tag_is_latest([_, tag]) {
  tag == "latest"
}

docker_imagesはさっきと同じですが、2行目がまたちょっとトリッキーです。latestを検知する方法は以下の2つの観点にしています。

  • :で分割した場合に要素が2個より小さい
  • :で分割して末端側(tag側)の値がlatestになっている

いずれかにヒットしたらルール違反にします!ここでポイントなのは、「いずれか」ということ。つまりOR条件になります。Regoのルール内の行は全てANDになり、同じルール名のルール(この場合tag_is_latest)を並べるとORになるという特性があります。これはIncremental Definitionsと言って公式ドキュメントでも説明があるので要チェックです。

www.openpolicyagent.org

また、僕が以前書いた記事にもANDとOR、そしてルール内から別のルールを呼び出すことについて触れていますので興味があればぜひ参考にしてみてください!

kenfdev.hateblo.jp

上のやり方ではtag_is_latestというルールを別途2つ作って、それぞれの判定条件を書きました。

:で分割した場合に要素が2個より小さい
tag_is_latest(images) {
  count(images) < 2
}

imagesにはsplit(docker_images, ":")で分割された値が入ってきます。今回の例であれば下のようになります。

["circleci/buildpack-deps"] # 要素数は1つ
["kenfdev/golang", "1.12"] # 要素数は2つ

タグが無い場合のデフォルトがlatestになるので、分割された要素の数が2より小さいかどうかをチェックします。

:で分割して末端側(tag側)の値がlatestになっている
tag_is_latest([_, tag]) {
  tag == "latest"
}

また新たな記法が引数の場所([_, tag])に登場しました。これは、いい感じに配列の値を分割代入させる書き方です。

例えば下のように書くとイメージしやすいと思います。

[name, tag] = ["circleci/buildpack-deps", "latest"]

nameにはcircleci/buildpack-depsが対応し、tagにはlatestが対応して代入されるようなイメージです。でも、nameには今回は特に興味がないので、上の例では[_, tag]と書いています。そして、ルール内ではtag == "latest"というように評価することで、taglatestかどうかチェックすることができます。

それでは、テストを実行してみましょう!

$ conftest test config.yml
config.yml
   Only use official CircleCI images
   Do not use `latest` container image tags
   Use version 2.1 or higher

image: circleci/buildpack-depsがタグを指定していないので、ちゃんと怒られていますね!ちなみにimage: circleci/buildpack-deps:latestとしてもちゃんと怒られます。

conftestでテストができました!念の為ポリシーの全体像も載せておきます。

# .circleci/policy/circleci.rego
package main

deny[msg] {
 input.version < 2.1
 msg = "Use version 2.1 or higher"
}

deny[msg] {
  docker_images := input.jobs[_].docker[_].image
  not startswith(docker_images, "circleci/")
  msg = "Only use official CircleCI images"
}

deny[msg] {
  docker_images := input.jobs[_].docker[_].image
  tag_is_latest(split(docker_images, ":"))
  msg = "Do not use `latest` container image tags"
}

# helpers
tag_is_latest(images) {
  count(images) < 2
}

tag_is_latest([_, tag]) {
  tag == "latest"
}

config.ymlの修正

怒られっぱなしなのもちょっと悲しいので、config.ymlを直しましょう!

全てのポリシーを満たすために下のように修正しました。

version: 2.1 # 2.1に変更
jobs:
  build:
    docker:
      - image: circleci/buildpack-deps:jessie # latest使うのやめた
    steps:
      - checkout
  test:
    docker:
      - image: circleci/golang:1.12 # circleciのイメージに変更した
    steps:
      - checkout
workflows:
  version: 2
  build_and_test:
    jobs:
      - build
      - test

それではテスト実行です!

$ conftest test config.yml
config.yml

ちょっとわかりづらいんですけど、何も怒られなくなりましたね!

いい感じにconftestで設定ファイルに対してテストが書けることがわかりました。Regoへの慣れは必要ですが、今後Open Policy Agentも様々な場所で使われていくことになりそうなので、Regoは学んでおいて損は無いんじゃないかなと思っています。

今回のコードは以下のリポジトリに置いてます。

github.com

まとめ

  • YAML, JSONの設定ファイルに対してテストが書けるconftestを試した
  • conftestは宣言的にOpen Policy AgentのRegoを使ってテストを書ける
  • どこがルール違反になっているか、場所も教えてくれるとさらにうれしい
  • 全部ポリシーをパスしてたらもうちょっとうれしい出力がほしい(笑)
  • 今後に期待したい!!!

参考

Conftestを使ったテストの例 github.com

Open Policy Agentのチュートリアル www.openpolicyagent.org

セルフマネジメントについて考える / 『管理ゼロで成果はあがる〜「見直す・なくす・やめる」で組織を変えよう』を読んで②

管理ゼロで成果はあがる〜「見直す・なくす・やめる」で組織を変えよう」という本を読み終えました!

この本の中でも特に「生産的に働く」ことについては以下の記事で書評を書いたので、興味がある方はぜひ!

kenfdev.hateblo.jp

この記事では、第2部で紹介されている「セルフマネジメントのレベル」と「師匠が背中を見せる」ことについて注目した書評を書きます。

目次

  • はじめに
  • 第1部 生産的に働く ~楽に成果をあげるために“見直す”
    • やり方を見直す ~「ふりかえり」で抜本的に生産性を改善する
    • 生産性を見直す ~「時間対効果」の高い仕事をする
    • タスクを見直す ~「タスクばらし」で小口化する
    • やる気を見直す ~無理に上げない、なくさない状況をつくる
    • 信頼関係を見直す ~「心理的安全性」を生み出す環境
    • 会議を見直す ~口を動かすだけでなく、いっしょに手を動かす
    • 雑談を見直す ~ホウレンソウから「ザッソウ」へ
    • 社内業務を見直す ~人手に頼らない「業務ハック」で改善を続ける
    • 価値を見直す ~受託脳よりも提案脳で考える
  • 第2部 自律的に働く ~人を支配しているものを“なくす”
    • 管理をなくす ~セルフマネジメントで働くチームをつくる
    • 階層をなくす ~「ホラクラシー」組織を実現する仕組み
    • 評価をなくす ~個人の成長と会社の貢献の「すりあわせ」をする
    • 数字をなくす ~組織のビジョンよりも自分のためならがんばれる
    • 組織の壁をなくす ~信頼しあえる企業文化の育て方
    • 急募をなくす ~仕事があっても、いい人がいなければ採用しない
    • 教育をなくす ~自分の頭で考える社員の育て方
    • 制度をなくす ~本質ありきで考える「そもそも思考」
    • 通勤をなくす ~働く場所に縛られない「リモートチーム」
  • 第3部 独創的に働く ~常識や慣習に従うことを“やめる”
    • 既存のビジネスモデルに従うのをやめる ~納品のない受託開発
    • 顧客を説得する営業をやめる ~対等な関係を作るマーケティング
    • 新規事業の指示命令をやめる ~部活から生まれるイノベーション
    • 規模を追求することをやめる ~組織の大きさもコントロールしない
    • 会社らしくすることをやめる ~文化をつないでいくコミュニティ
  • おわりに

セルフマネジメントの3つのレベル

本書では「管理をなくす」にあたって、セルフマネジメントについて紹介されています。「仕事」、「組織」、「自分」という3つの観点で、それぞれレベルを3つに分けて定義しています。

観点 Lv1 Lv2 Lv3
仕事 タスクを管理する リソースを管理する 価値を生み出す
組織 周囲に伝える 周囲と協調する 周囲を活かす
自分 休憩をとる 安定して働く 将来を考える

何ができていると、どこのレベルにいるかというのが述べられています。「自分はそれぞれの観点でどこのレベルにいるのか?」というのを確認できて面白いですし、何ができればレベルが上がるのかというのも参考にすることができます。

「休憩をとる」ということ

セルフマネジメントのレベルの中でも注目したいのは「自分」の観点の中のLv1に入っている「休憩をとる」ことです。

休憩が習慣化していない人にとっては「いつ休憩をとるのか?」というのが難しいと感じそうですが、僕はポモドーロ・テクニックがここにフィットすると感じました。

何度か僕のブログで紹介していますが、ポモドーロ・テクニックについては僕のブログメンターでもあるカックさん( @kakakakakku )の以下の記事がおすすめです!

kakakakakku.hatenablog.com

ポモドーロ・テクニックを使って「25分間は超絶に集中し、その後は5分間休憩をとる」というサイクルを繰り返していくと休憩も1つのタスクのように感じられるようになり、自然と習慣化もしていきます。

だれかが管理しないと休めないようでは、マネジメントする側の負担は大きいままです。

と、本書でも述べられています。休憩を自らとれるかとれないかは、自分だけの問題ではないということに気づく必要があります。休憩の習慣化は小さく見えてセルフマネジメントにおいては大きな一歩だと感じました。

師匠が背中を見せる

本書には「教育をなくす」という章があり、「教育する」のではなく「仕事の中で育てる」方法について紹介しています。

これには以下3つのポイントが述べられています。

  • やってみせる
  • やらせてみる
  • フィードバックする

本書では育てる側を「師匠」、育てられる側を「弟子」と呼んでいて、1点目の「やってみせる」というのは「師匠が弟子に背中を見せる」ということです。ポイントとなるのは、いかにして師匠は弟子に自分の技術を盗ませるかです。そのためにまずは「やってみせる」ことが大事だと述べられています。個人的にふりかえってみても、物理的に離れていた新人に仕事をやってみてもらったときよりも、隣に座って一緒に仕事をしていた新人の方が圧倒的に成長のスピードが早かったのが記憶に残っています。これは、自然と自分の仕事を「やってみせる」ことができていたのではないかと思います。

気になった点としては、最初から物理的に離れている者同士(リモート)の場合、「やってみせる」にはどうしたら良いかということです。フルリモートをしている身としては今後の課題として追求していきたいです。

まとめ

  • 『管理ゼロで成果はあがる〜「見直す・なくす・やめる」で組織を変えよう』を読んだ
  • セルフマネジメントに悩んでいるor興味がある人にオススメできる
  • セルフマネジメントできる人を増やしたい人にもオススメできる
  • フルリモートにおける師弟関係について追求していきたい