ドワンゴの「ものづくりが好き」というエンジニア文化の話

この記事は ドワンゴ Advent Calendar 2023 の16日目の記事です。

新卒採用イベントで会社説明をする機会があり、その中でもタイトルにもさせてもらっている通り「ものづくりが好き」をはじめとするドワンゴのエンジニア文化などの話をさせてもらいました。

ドワンゴのエンジニア文化のスライド
会社説明で利用したスライドの抜粋

特にスライド中にも書いてありますが「とりあえず作ってみる」「不便なものはハックする」「あらゆるものをカスタマイズする」は、会社説明の中でもフォーカスしたかったものの時間の関係で話せなかったため、これまで自身で作成したツールの紹介や利用している技術を紹介できればと思います。

AWSマネジメントコンソールへのログインを行うためのCLIツール

このツールは、AWS CLIを利用する際に設定するであろう ~/.aws/credentials に保存されているアクセスキー等を用いて、フェデレーショントークン・サインイントークンの取得を経てAWSマネジメントコンソールへのログインURLを生成しデフォルトのブラウザで開くツールです。

AWSマネジメントコンソールへのログインを行うためのCLIのデモ
動作イメージ

今振り返ると多要素認証(MFA)をバイパスしログインできるためセキュリティ的にはよろしくないですが、当時は手元のコードでなんらかAWSのリソースを変更しAWSマネジメントコンソール上で目視確認を手早くやりたかったため、このようなツールを作成しました。

クロスプラットフォームで動作することを求めていたので、このツールはGoで作成しています。 複数のサブコマンドを用意していないものの、ヘルプをいい感じにできるという理由でspf13/cobraを使った実装をしました。

% awslogin -h
AWSマネジメントコンソールに簡単にログインできるやつ

Usage:
  awslogin [flags]

Flags:
      --profile string         利用するプロファイル (default "default")
  -d, --duration-seconds int   セッションの有効期間(秒) (default 900)
  -f, --policy-file string     ログイン時にアタッチするIAMポリシーのJSONファイル
      --print-url              ログインURLを表示する
  -h, --help                   help for awslogin

AWSマネジメントコンソールへのログインURL生成については大まかに以下の通りです。

// ① STSクライアントの初期化
stsClient := sts.New(session.Must(
  session.NewSessionWithOptions(
    session.Options{
      // AWS CLIと同様に --profile オプションや環境変数 AWS_PROFILE だったりで profile を設定
      Profile: profile,
    },
  ),
), aws.NewConfig())

// ② GetFederationTokenで一時的な認証情報を取得
federationToken, _ := stsClient.GetFederationToken(&sts.GetFederationTokenInput{
  DurationSeconds: aws.Int64(3600),
  Name:            aws.String("awslogin"),
  Policy:          aws.String(`{"Version":"2012-10-17","Statement":{"Effect":"Allow","Action":"*","Resource":"*"}}`),
})

// ③ ②で取得した認証情報を用いてサインイントークンを取得
session, _ := json.Marshal(struct {
  SessionID    string `json:"sessionId"`
  SessionKey   string `json:"sessionKey"`
  SessionToken string `json:"sessionToken"`
}{
  SessionID:    *federationToken.Credentials.AccessKeyId,
  SessionKey:   *federationToken.Credentials.SecretAccessKey,
  SessionToken: *federationToken.Credentials.SessionToken,
})
federationQuery := url.Values{}
federationQuery.Set("Action", "getSigninToken")
federationQuery.Set("SessionType", "json")
federationQuery.Set("Session", string(session))
signinTokenResponse, _ := http.Get("https://signin.aws.amazon.com/federation" + "?" + federationQuery.Encode())
defer signinTokenResponse.Body.Close()

// ④ ③で取得したサインイントークンを用いてAWSマネジメントコンソールへのログインURLを生成
signinTokenResponseBody, _ := io.ReadAll(signinTokenResponse.Body)
signinToken := &struct {
  Token string `json:"SigninToken"`
}{}
json.Unmarshal(signinTokenResponseBody, signinToken)
consoleUrlQuery := url.Values{}
consoleUrlQuery.Set("Action", "login")
consoleUrlQuery.Set("SigninToken", signinToken.Token)
consoleUrlQuery.Set("Destination", "https://console.aws.amazon.com/")
consoleUrl := "https://signin.aws.amazon.com/federation" + "?" + consoleUrlQuery.Encode()

詳細にはカスタム ID ブローカーに対する AWS コンソールへのアクセスの許可を参照してください。

AWSマネジメントコンソールへのログインURLをWebブラウザで開く部分については、OSに依存するため以下のように実装しています。 おそらくだいたい動作すると思いますが、特にLinuxの際の分岐については自信はありません。

var err error

switch runtime.GOOS {
case "linux":
  err = exec.Command("xdg-open", consoleUrl).Start()
case "windows":
  err = exec.Command("cmd", "/c", "start", consoleUrl).Start()
case "darwin":
  err = exec.Command("open", consoleUrl).Start()
default:
  return fmt.Errorf("サポートされていないOSです: %s", runtime.GOOS)
}

Goは手続き的に雑に書け、今回この記事のためにv1.21でも動くか実装時のコードを眺めつつ検証しながら書き直していますが、短時間で開発時の思惑などもキャッチアップできるなとあらためて感じています。

似たようなJIRAのチケットを量産するためのURLを生成するChrome拡張

ドワンゴではタスク管理にAtlassian製品であるJIRAを利用しています。

特に教育事業においては、N予備校のコンテンツを制作している部門から開発部門へのコミュニケーションにおいてJIRAチケットを起票し、例えば問い合わせや調査などを依頼いただくようなことが多々あります。(NFC事業における「お願いチケット(通称 おねチケ)」に近い仕組みかと思います。)

私達のチームではこれらのチケットは問い合わせや依頼の種別に合わせてラベルや説明にそれぞれテンプレートを用意しており、以下のようにドキュメントやSlackチャンネルの関連ページで共有しているリンクからチケット作成画面に遷移するとテンプレートが適用された状態でチケットの作成ができます。

ドキュメントにまとめられているチケット作成リンク
ドキュメントにまとめられているチケット作成リンク

Slackチャンネルの関連ページにまとめられているチケット作成リンク
Slackチャンネルの関連ページにまとめられているチケット作成リンク

チケットの作成イメージについては以下の通りです。

リンクを踏んだ際の画面遷移
リンクを踏んだ際の画面遷移

仕組みとしてはURLのクエリパラメータをJIRAがいい感じに各フィールドに入力してくれているという単純なものですが、与えるクエリパラメータの組み合わせについては普段JIRAを利用しているだけではあまり馴染みの無いものが多く、とくにカスタムフィールドなどを利用していると試行錯誤しなければいけません。

また起票用のURLについても一度作っただけで運用に残り続けることは少なく、必要に応じてテンプレートの変更などが生じます。

こういった変更のたびに、クエリパラメータを試行錯誤してURLを作り直すという点について面倒に感じていたため、見出しにもあるようなChrome拡張をつくりました。

Chrome拡張のデモ
拡張機能としてはチケット作成画面に「チケット起票リンクをコピー」というボタンが表示されるものになります

この「チケット起票リンクをコピー」のボタンを押下すると、すでに入力済みのフィールドをクエリパラメータに変換したチケット起票のためのURLをクリップボードにコピーします。

社内で稼働しているJIRAのライセンス・バージョンでは以下のようなコードで実現できました。

// JIRAのダイアログでチケットを起票する場合は form[name='jiraform'] のようなセレクタでフォームを取得できる
const form = document.querySelector("form#issue-create");
if (!form) {
  throw new Error();
}
const params = new URLSearchParams();

// 無視するフォーム内のフィールド
const ignoreParams = ["formToken", "atl_token", "Create"];

// フォーム内を走査しフィールドの入力値からクエリパラメータを生成
Array.from(form.elements).forEach((e) => {
  let key;
  let value;

  switch (e.constructor) {
    // 担当者や報告者、ラベルなど
    case HTMLSelectElement:
      key = e.name;
      if (ignoreParams.includes(key) || key === "") {
        break;
      }

      if (e.multiple) {
        // 複数選択可能なもの(ラベルが該当)については複数の値を含める
        Array.from(e.selectedOptions).forEach((option) => {
          if (value !== "") {
            params.append(key, option.value);
          }
        });
      } else {
        value = e.value;
        if (value !== "") {
          params.append(key, value);
        }
      }

      break;

    // 要約や説明など
    case HTMLInputElement:
    case HTMLTextAreaElement:
      key = e.name;
      if (ignoreParams.includes(key) || key === "") {
        break;
      }

      value = e.value;
      if (value !== "") {
        params.append(key, value);
      }
  }
});

console.log(
  // 社内では CreateIssueDetails!init.jspa のようなパスになっている
  `${"JIRAのチケット起票のURL"}?${params.toString()}`
);

Chrome拡張については、デベロッパーモードを有効にした状態での「パッケージ化されていない拡張機能を読み込む」のおかげでインクリメンタルに動作確認ができ、簡単にやりたいことを実現できるのでとても便利に利用しています。

メンバーの出勤簿上から工数を同時に確認できるようにするChrome拡張

ドワンゴでは出退勤の記録とは別に、プロジェクトに対してどれだけ稼働をしたのかを工数としてメンバー各々が入力し記録しています。 それに対し部長やマネージャーは、メンバーがどのようなプロジェクトに対して稼働しているかやプロジェクト毎にどれだけの稼働が生じているかなどを勤怠の承認と合わせて確認しています。

利用しているシステムの都合上、勤怠を承認するためにメンバーの出勤簿のページの確認と工数の確認については別々となっており、特に工数の確認についてはCSVファイルをダウンロードしての確認という具合に不便に感じていました。

「不便なものはハックする」ということから見出しの通りChrome拡張を作りました。 具体的には以下のようにメンバーの出勤簿ページに対して、「工数確認」というようなボタンを挿入し、そのボタンがクリックされると同日の工数を表示するというようなものになっています。

メンバーの出勤簿上から工数を同時に確認できるようにするChrome拡張のイメージ
実際の出勤簿はお見せできないためあくまでもイメージになります

仕組みとしては挿入したボタン押下後に、出勤簿ページ内にiframeで工数情報が乗っているCSVファイルの出力ページを開きCSVをダウンロード・パースし出勤簿ページに反映ということを行っています。

複数ページで同期的に動作することやファイルのダウンロードが発生することから、バッググラウンドに ServiceWorker を動かし、ページそれぞれがメッセージに反応して動くような作りをしています。

メッセージングのイメージ

出勤簿ページでは、ボタン押下に反応して ServiceWorker を通して iframe で開かれた工数出力ページ内で出力設定を行うフォームを操作しサブミットしています。

フォームのサブミットにより再度工数出力ページが読み込まれると、また出勤簿ページから出力されたCSVのダウンロードするよう ServiceWorker にメッセージを送信します。

ServiceWorker はこのメッセージから工数出力ページに対してCSVのダウンロードリンクの取得を試み、得られたダウンロードリンクからCSVファイルをダウンロードし、出勤簿ページにレスポンスします。

出勤簿ページでは最終的に得られたCSVファイルをパースし、工数情報を出勤簿ページに反映します。

簡略化していますが、以下のようなシーケンスで処理を実現しています。

工数確認のシーケンス

このChrome拡張は特に同僚のマネージャーにも好評をいただいており、自身もこの拡張のおかげで工数の確認がしやすくなりました。

「ものづくりが好き」というエンジニア文化から、このChrome拡張は以前動作しなくなった際にも同僚からプルリクエストをもらい改修が行われるということもありました。

動かなくなったときにもらったプルリク

まとめ

今回はGoで作ったCLIとChrome拡張2種の3つツールを紹介しました。

自身では他にもGoogleカレンダーの面接の予定に対して面接中のメモをとるためのGoogleドキュメントを自動で作成するGASや、入社から何日経過したかを確認できるページなども作ってきました。

自分以外にも社内ではSlackボットをはじめとする様々なツールが作られており、全社的にも「ものづくりが好き」という文化を感じますし、便利なツールに対しては感謝や称賛などをし合うような雰囲気もあるかと思います。

新卒採用イベントで会社説明をさせてもらったことを発端に記事を書いてしまったため、採用PRのような締め括りになってしまいますが、この記事をきっかけにドワンゴのエンジニア文化に興味を持っていただければ幸いです。

We are hiring!

株式会社ドワンゴの教育事業では、一緒に未来の当たり前の教育をつくるメンバーを募集しています。

カジュアル面談も行っています。 お気軽にご連絡ください! カジュアル面談応募フォームはこちら www.nnn.ed.nico 開発チームの取り組み、教育事業の今後については、他の記事や採用資料をご覧ください。 speakerdeck.com

25卒新卒採用もはじまっています! blog.nnn.dev