DRYな備忘録

Don't Repeat Yourself.

Travis-CIによるイベントホームページ自動デプロイと告知ツイートの自動化 #YUKEMULI

背景

自分が関わっているイベントのホームページをGitHubで管理、GitHub Pagesでホストしているんですが、

  1. masterブランチが更新されたら自動でデプロイしたい
  2. ホームページの更新内容ってほとんど「イベントに関する新しい情報」に他ならないので、これを自動で告知としてツイートできればいいのではないか

という気持ちがありました。

TL;DR

1月19日(土)に鶴見のスーパー銭湯でDJイベントやるから風呂入るついでに顔出してくれるとそれはとってもうれしいな!

yukemuli.dance

Hugoによる静的サイト生成

前々より、僕自身は「GitHub PagesでホストするんならJekyllでいいじゃん、カスタマイズしやすいし」派だったのですが、 いかんせんJekyllだとGitHub Pages用にすぐ使えるテーマが少ないという問題があります。自分でTheme書けるんだったらJekyllで十分良いと思うんですけどね。

そこでちょっと前に話題になったHugoです。Hugoというのは、静的サイト生成ツールであり、Markdownで書いといたらいい感じのHTML吐いてくれる、みたいな雑な理解です。

gohugo.io

Goで出来てるとかビルドが早いとかは顧客価値ではないと思うので置いといて、特筆すべき点は、そのThemeの開発しやすさ・自作Theme開発後簡単にデリバリできるエコシステム、結果として利用可能なテーマが豊富にあるというところだと思います。

実際、今回はデザインに関してはベースになるテーマをオーガナイザDJのたこっぷ氏に選んでもらい、すきまき先生アートディレクションのもと、僕が実装する形だったので、ある程度テーマができていて選択肢が多いhugoが良いかという技術選定でした。

HugoとTravisの連携と自動デプロイ

ただし、Hugoは(今のところ)GitHub Pagesにサポートされていないため、自動でGitHub Pagesに反映(便宜的にこれをデプロイと言ってます)するためには、たとえばindex.htmlを更新するコミットをCI上で作成 & Repoにプッシュするという要件が発生します。

その点、Jekyllはめっちゃ楽なんですよ、ビルド済みHTMLをコミットする必要が無いので。

ここでHugoのGitHub Pagesへの自動デプロイの詳細は解説しませんが、インターネッツに参考例がゴロゴロしているのでぜひ厳選のうえご参考ください。

強いて注意点があるなら、

  1. コミット&プッシュするアカウントのSSH PRIVATE KEY を、Travisが提供する暗号化を施してプロジェクトに含める必要がある
  2. 以前にJekyllによってGitHub Pagesがビルドされたことがあるリポジトリには.nojekyllという空ファイルをコミットしJekyllを使わないことを明示する必要がある

という2点が重要な知見でした。

ホームページ更新内容と「告知」の概念

さて、一方で「イベントの告知」というオペレーションについての知見なのですが、これは常々巧いなあと思っているとーま先輩から着想を得ました。

彼はイベントの情報を一挙に公開せず、小出しにすることにより告知機会そのものを増やし、自然と露出・拡散が促されていました。

「段階的に情報を小出しにする」という特徴は、gitのコミットに非常に親和性があると思われます。

そこで、コミットメッセージを告知サマリとすることでコミットログから告知文言を自動生成するという結論に帰着し、これを実装しました。

このへん👇

github.com

今後の展望

もちろん、コミットメッセージを告知サマリにする、ということは「適切なコミット単位」を意識し、意味のあるコミットログを積み重ねていくことに他なりません。ので、開発やっててとてもたのしいです。

それと同時に、不可避的に「告知する必要の無い開発案件のコミット」も発生するので、今後はこれを除外していく仕組みも必要かと思われます。

現時点では、コミットメッセージに DONOTANNOUNCE: みたいなのをつけたらそのコミットメッセージは告知ツイートに含めない、みたいなのでいっかなーって思ってる。

あと、コミットログの取得→リリースアナウンス、みたいなワークフローはわりと他のプロジェクトでも使うので、インターフェースを整えてこれをnpmパッケージに切り出すのも面白いかな、と思います。

まとめ 告知

1月19日(土)に鶴見のスーパー銭湯でDJイベントやるから風呂入るついでに顔出してくれるとそれはとってもうれしいな!

yukemuli.dance

前半はまったり寝転がったりできる「宴会モード」。後半はVJもつけて「クラブモード」となります。会場の「ラクスパ鶴見」がふつうに良いスーパー銭湯なので、スーパー銭湯をメインディッシュに、ちょっと顔出してくれるだけでもオッケーっす!

お待ちしてます ;)

DRYな備忘録として

さようなら、CWL

このエントリは、 Common Workflow Language (CWL) Advent Calendar 2018 - Qiita の25日目です。

tl;dr

CWLって何よ

2017年4月にベルリンから逃げ帰って、某大学研究所に研究員として就職しました。当初は、自分が持ってるバイオインフォの個人事業で独立しようと思っており、半分はその営業・コネクション作りのつもりで行ったのですが、なんやかんやでそこで働くことになってました。今にして思えば、よい判断だったと思います。理由は最後のほうで書く。

ゲノム解析のプラットフォーム構築、あるいはその技術検証」というようなミッションで、ゲノム解析の実情・課題国内のスパコンの利用実態、日本および海外のクラウドサービスの利用状況、Docker / Singularity を用いた仮想化・再現性の担保などの研究?実装をしました。

そんな中、国際学会でもにわかに注目を集めていたのが「Common Workflow Language」略して「CWL」です。

CWLって何よ、っていうのは、この辺見たらよろしい。

世界中のゲノム解析作業者(←そのすべてが「バイオインフォマティシャン」とは限らないところがミソ)がそれぞれのスパコンのスペックで、秘伝のパッケージがインストールされた環境で、秘伝のシェルスクリプトを丹念につくりあげて論文を発表するも、当然それでは再現性が無かったりしてどーなのよ、となるのを「じゃあ統一記法があったらいいんでしょ」って発想で「Common」な「Workflow」の「Language」を 作りたかったらしい 作ったのが「Common Workflow Language」です。

正直しんどい

「お、じゃあいっちょCWLの思想信条を理解するのも兼ねて、エンジンをつくってみよじゃないの」と思ってGoによるシンプルなエンジンの実装を始めたんですけど、これがまた、しんどい。色々な概念をモリモリにしてしまって、仕様が収集つかなくなってる感じがあります。

細かいことは、去年 Webプログラマから見た「CWL」の功績と罪過 - DRYな備忘録 でガッツリ書いたのでここでは詳細は割愛しますが。

すっげー乱暴に言ったら、哲学が先行しており、実践がついてきてなくね?という感じです。

帰納的アプローチ

CWL自身も「いや、ウチは別に絶対的な統一記法を発明しようとしているわけちゃうよ?」「他にもいろいろあるからええやつ選んだらええんちゃう?」という態度です。

であるのであれば、無理にCWLに追従するのではなく、独自のワークフロー記法を作るぐらいの勢いでやるのがいいんじゃないかと思うんですが。

そのとき、CWLのように「かくあるべき」から降りてきて、途中途中に要求を吸収していくようなやり方ではなく、それぞれの参加者がそれぞれの業務フローに特化したソフトウェアをまずつくり、共通概念を抽出していくような、いわば帰納的なアプローチを取るべきだ(あるいはCWLはそういうアプローチを取るべきだったのではないか。まあそれをやるにはワールドワイドすぎるとは思うけど)と思うわけです僕は。

そんなわけで、ってほど必然性は無いんですが、僕は仕事の一環で「クラウドサービス上で任意のワークフローを動かし結果だけを得るエンジン」を作っておりました。「hotsub」っていうんですけど。

名前が某ポルノサイトに酷似してるってツッコミを受けました。ポルノだけに。

12月は無職でした

もちろん、業務フローに特化したエンジンを作ると、そこそこ長所短所・得手不得手があり、なんとなく職場ではこの "hotsub" 使われずじまいだったので、いちおう目標であった論文だけ(インターネットジャーナルですけど)投稿して、11月末に退職、ターンエンドと相成りました。

転職活動のほうは、各方面に迷惑をかけつつも、1月からちゃんと就職できることになりました。ありがとうございました。

となると12月無職になるわけですが、友人各位はご存知のように(?)当方は貯金など一切無く、12月どうやって行きていけばいいのかワカラン、仕方ないので持ち前のコネクションとコミュ力ゲノム解析の単発受託などをやり糊口を凌いでおりました。

そう、まさにこの "hotsub" を使ってね!

このままでも「hotsub」を使って受託をこなすのはドチャクソ便利なんですが、やはりソフトウェアというのは、実際に運用してみて始めて洗練されていくもので、いろいろな課題や要望が見えてきました。

そのうちの一つが「hotsubというエンジンに依存しないジョブインターフェースの定義」であります。

この延長に「ワークフロー記述方法」が透けて見えており、かなり手応えを感じます。(時すでに退職おそし感)

今後の展望

すでにhotsub*1がCWLの実行を部分的にサポートしているのですが、今後はさらにhotsubが完全に「CWLのクラウド上実行のエンジン」となっていく展望は具体的に見えているのですが、いかんせん俺はもうバイオインフォ関係ない人になっちゃう。

加えて、CWLのコミュニティ活動の一環として上記 yacle*2の実装も、時間さえあれば進めていくつもりですが、もちろん給料など出ないのでありますから、牛の歩みになってしまうと思われます。

興味関心としても、上記のように「CWLは絶対のワークフロー記述方法ではない」という立場ですので、今後は僕は「CWLの動向をかなり遠くから見守りつつ、興味範囲でエンジンの実装と、独自ワークフロー記法の発明を進める」という感じになっていくと思われます。

まとめ

ドイツから帰ったとき、いきなり個人事業に集中していたら、たぶんこんなに業界のことを知れなかっただろうし、ネットワークもつくれなかったと思う。特に、CWL界隈・ワークフロー界隈・ワークフローミートアップのお歴々には、ソフトウェアへの貴重なご助言・フィードバックをいただき、彼ら無しでは僕はきっとバイオインフォで一切成果を出せなかったとすら思います。マジで。

なんかまともな謝辞になった。

まぁ、まとまらないんですけど、まとめると、「さようなら、CWL」となります。

WETな DRYな備忘録として

TypeScriptとExpoで始めるReactNativeアプリ開発

背景

Expo SDK v31 から、標準でTypeScriptをサポートしているらしく、ホンマかいな、というエントリです。

ゴール

  • Mac上のシミュレータで、アプリが動く
  • ソースコードがTypeScriptで書かれている

うるせえ動くもん見せろ

はい

github.com

以下、ログなので読まなくていいです。

簡単なアプリの作成

% expo --version
2.4.0
% expo init
? Choose a project name: MyTypeScriptRNApp
? Choose a template: blank
[17:33:56] Extracting project files...
[17:34:02] Customizing project...

Your project is ready at /Users/otiai10/proj/react-native/MyTypeScriptRNApp
To get started, you can type:

  cd MyTypeScriptRNApp
  expo start
% cd MyTypeScriptRNApp
% expo start

# 中略
  To run the app with live reloading, choose one of:
  • Sign in as @otiai10 in Expo Client on Android or iOS. Your projects will automatically appear in the "Projects" tab.
  • Scan the QR code above with the Expo app (Android) or the Camera app (iOS).
  • Press a for Android emulator, or i for iOS simulator.
  • Press e to send a link to your phone with email/SMS.

# とのことなので、 i と打つ

f:id:otiai10:20181120173814p:plain:w300

くっそ簡単やんけ。

以下、遊び心。

--- a/App.js
+++ b/App.js
@@ -5,7 +5,7 @@ export default class App extends React.Component {
   render() {
     return (
       <View style={styles.container}>
-        <Text>Open up App.js to start working on your app!</Text>
+        <Text style={{fontSize: 120, fontFamily: "Courier"}}>Foo Bar Bla Bla</Text>
       </View>
     );
   }

こんなすると、

f:id:otiai10:20181120174710p:plain:w300

こんななる。

TypeScriptにしていく

github.com

公式にサポートしているらしいので、いっちょ適当にやってみる。

% npm install --save-dev typescript

# tsconfig.jsonをつくる
% ./node_modules/.bin/tsc --init
# 拡張子変えるだけ
% mv App.js App.tsx
% expo start

f:id:otiai10:20181120180434p:plain:w300

めっちゃいけてるやんけ。

開発環境ととのえる

エディタに、 @types がもろもろ無いというお叱りを受けている。

f:id:otiai10:20181120180643p:plain

このままではなんのためのTypeScriptやねん、という感じなので。

% npm install --save-dev \
  @types/react \
  @types/react-native \
  @types/expo

f:id:otiai10:20181120180939p:plain

jsxね。

--- a/tsconfig.json
+++ b/tsconfig.json
@@ -6,7 +6,7 @@
     // "lib": [],                             /* Specify library files to be included in the compilation. */
     // "allowJs": true,                       /* Allow javascript files to be compiled. */
     // "checkJs": true,                       /* Report errors in .js files. */
-    // "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
+    "jsx": "react-native",                    /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
     // "declaration": true,                   /* Generates corresponding '.d.ts' file. */
     // "declarationMap": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. */
     // "sourceMap": true,                     /* Generates corresponding '.map' file. */

f:id:otiai10:20181120181551p:plain

TypeScriptっぽいことをしてみる。(返り値の型を定義しただけ)

--- a/App.tsx
+++ b/App.tsx
@@ -1,14 +1,26 @@
 import React from 'react';
-import { StyleSheet, Text, View } from 'react-native';
+import {
+  StyleSheet, Text, View,
+  TouchableHighlight,
+} from 'react-native';

 export default class App extends React.Component {
   render() {
     return (
       <View style={styles.container}>
         <Text style={{fontSize: 120, fontFamily: "Courier"}}>Foo Bar Bla Bla</Text>
+        <TouchableHighlight onPress={() => this._showTime()}>
+          <Text>What time is it now?</Text>
+        </TouchableHighlight>
       </View>
     );
   }
+  _showTime() {
+    alert(this._getText());
+  }
+  _getText(): string {
+    return (new Date()).toLocaleString();
+  }
 }

 const styles = StyleSheet.create({

f:id:otiai10:20181120182142p:plain

いいですね。

DRYな備忘録として

速習 React 速習シリーズ

速習 React 速習シリーズ

React Native+Expoではじめるスマホアプリ開発 ~JavaScriptによるアプリ構築の実際~

React Native+Expoではじめるスマホアプリ開発 ~JavaScriptによるアプリ構築の実際~

Learning React Native: Building Native Mobile Apps with JavaScript (English Edition)

Learning React Native: Building Native Mobile Apps with JavaScript (English Edition)

Mac上で「歌声りっぷ」を使う【Boot Camp】【Windows10】

背景

  • ある曲のボーカルを抽出したものが欲しい(公式のオフボーカルは手元にある)
    • Ableton Live でオフボーカルトラックの逆位相をオリジナルトラックにぶつける方法でボーカルを抽出しようとした
      • あんまりうまくいかない
    • PhonicMind で1曲だけ利用枠購入してボーカルを抽出しようとした
      • 曲調によってはうまくいく
  • Macだけど「歌声りっぷ」も使ってみて、品質を比較したい

※ 本エントリ末尾に、実際に聞ける形で結果の比較があります。

概要

  1. MacBoot Campで、Windows10を使えるようにする
    1. Windows10のプロダクトキーをAmazonで購入しとく
    2. Windows10のISOファイルをダウンロードする
    3. Windows10のインストールと起動+初期化
  2. MacWindowsを選択的に起動する
    1. 電源オフ状態 → Mac / Windows の選択
    2. Mac起動時 → Windows
    3. Windows起動時 → Mac
  3. 「歌声りっぷ」を利用可能な状態にする
    1. ソースのダウンロードとlzhの解凍
  4. 「歌声りっぷ」の使用
    1. 16bitのwavでなければいけない
    2. 実行!

手順の記録

1. MacBoot Campで、Windows10を使えるようにする

1-a. Windows10のプロダクトキーをAmazonで購入しとく

箱でも変えるっぽいけど、オンラインコードで買ったほうがいろいろ楽だと思われ。僕はこちらをポチった。金無いけど。

購入が完了して「注文履歴」→「注文の詳細」で「ライブラリに移動」すると、プロダクトキーが表示されてる。

f:id:otiai10:20181019151248p:plain

f:id:otiai10:20181019151947p:plain

このプロダクトキー、携帯のカメラなりでメモっとくのがいいです。1-cでWindowsをアクティベートするとき、当然ながらこの画面をこのPCで開く術が無いのでw

1-b. Windows10のISOファイルをダウンロードする

上記で購入したものは、Windowsをマシンにインストールしてアクティベートするときに必要な「プロダクトキー」であり、WindowsのOSそのものではないので、OSのソースコードをダウンロードしてくる必要があります。まあまあデカい。

1-c. Windows10のインストールと起動+初期化

Boot Camp Assistant」という標準でMacに入ってるアプリケーションを使って、上記でダウンロードできたWindows10のISOファイルを元に、Windows10をこのマシンにインストールする。必要なソフトウェアのダウンロードなどを含むので、ある程度太い回線につないで実行するのがよいかと思われます。

f:id:otiai10:20181019154341p:plain

インストールが終わると、Windowsでの再起動を提案されるので、Yesです。

Windowsが立ち上がり、言語やキーボードレイアウトなどを聞かれたのち、プロダクトキーを入力する場面になるが、それはもう1-aで購入しているので、それを使う。

f:id:otiai10:20181019155155p:plain

2. MacWindowsを選択的に起動する

2-a. 電源オフ状態 → Mac / Windows の選択

support.apple.com

以上。

2-b. Mac起動時 → Windows

support.apple.com

以上。

2-c. Windows起動時 → Mac

これが問題で、ほんまは「右下の△」→「ひし形マーク」→「OS Xでの再起動」を選択したらBootCampできるらしんだけど、macOS High Sierra からどうやらそれが動かないらしいので、

2-aと同じ方法を取る、つまり一度シャットダウンするよりほかなさそう。

参考: itkhoshi.com

3.「歌声りっぷ」を利用可能な状態にする

無事、MacでWindows10が動くことが確認できたので、つぎに「歌声りっぷ」を調達する。

3-a. ソースのダウンロードとlzhの解凍

ソースそのものはここでダウンロード可能

www.vector.co.jp

ただ、落としてきたものがlzhファイルで、この解凍ソフトウェアがデフォルトで入っていないので、

forest.watch.impress.co.jp

これを落としてきて、ショートカットをデスクトップに作成して、そこに「歌声りっぷ」のlzhファイルをドラッグアンドドロップすると、無事、解凍され、中に.exeファイルがあったので、これをめんどくさいのでそのままデスクトップに移動した。

4. 「歌声りっぷ」の使用

4-a. 16bitのwavでなければいけない

GUIめんどいので、MacなりLinux環境で、ffmpegCLIをつかった

# オリジナルトラックとオフボーカルトラック両方用意する
% ffmpeg -i ./original.mp3 -acodec pcm_s16le original.wav
% ffmpeg -i ./offvocal.mp3 -acodec pcm_s16le offvocal.wav

4-b. 実行!

Google Driveかなんかに入れて、Windowsに渡して、歌声りっぷする。した。

結果の比較

Phonic Mind
Ableton Live
歌声りっぷ

結論

  • 今回のケースでは「Phonic Mind」が段違いに性能悪いように聞こえるが、曲によってはバッチリ抜けることもある
  • 「Ableton Live」で自分で頑張る場合には、ここからさらにEffect/Filterなどでならしていく余地があるし、やっぱり楽
  • 何も設定いじらず、「歌声りっぷ」が今回は最も良い結果になった。がんばってWindows環境構築してよかった

謝辞

どうしても歌声りっぷ使ってみたいという今回の検証にあたって、大好きなトラックメーカーであるハナカミリュウさん、DJの先生であるKAZZONE大先生、間接的ではありますがCoNoSyuNya氏、にたいへん助けていただきました。ありがとうございます。

なお、今回がんばってアレしたソレで、はじめてのマッシュアップトラックをつくって、来たる 11月2日(金曜日)の Sound Sandbox vol.3 で流そうと画策しています!どうぞ遊びに来てください!

soundsandbox.tokyo

で、完成したもの

上記のイベントでかけた。マッシュアップたのしい。

soundcloud.com

DRYな備忘録として

CSSアニメーションで水面の波紋を表現

背景

某これウィジェット*1のローディングインジケータに、水面の波紋のアニメーションGIFを使ってたんですが、アニメーションGIFをインターネッツで漁ったりライセンス確認しなきゃならんうえにカスタマイズできないのがしんどくなったので、CSSで作れないかなと思った次第。

方針

  1. 丸いdivをつくる
  2. 大きさを keyframe animation する
  3. box-shadow inset とかで波っぽくする
  4. 外に行くにつれて薄くすれば消えていく感じになるかな
  5. それらを複数、ずらして重ねればよさそう
  6. 楕円にして水面表現する
  7. その他のチューニング

うるせえ動くもの見せろ

これです

あと読まなくていいです。

1. 丸いdivをつくる

div.circle {
  width:  80%;
  height: 80%;
  border: solid thin #303030;
  border-radius: 50%;
}

f:id:otiai10:20180902000244p:plain

2. 大きさを keyframe animation する

div.circle {
  /* 略 */
  animation: wave 2s infinite;
}

@keyframes wave {
  from {
    width:  0%;
    height: 0%;
  }
  to {
    width:  100%;
    height: 100%;
  }
}

f:id:otiai10:20180902001043g:plain

Chromeのデフォルトのtiming-functionが都合よくうごいてる。

3. box-shadow inset とかで波っぽくする

@keyframes wave {
  from {
    /* 略 */
    box-shadow: 0 0 100px inset #3f3f3f;
  }
  to {
    /* 略 */
  }
}

f:id:otiai10:20180902001939g:plain

だいぶそれっぽいやんけ。

4. 外に行くにつれて薄くすれば消えていく感じになるかな

@keyframes wave {
  from {
    /* 略 */
  }
  to {
    /* 略 */
    opacity: 0;
  }
}

f:id:otiai10:20180902001758g:plain

かなりそれっぽいやんけ。

5. それらを複数、ずらして重ねればよさそう

      <div class="box">
        <div class="circle layer-0"></div>
        <div class="circle layer-1"></div>
        <div class="circle layer-2"></div>
        <div class="circle layer-3"></div>
      </div>
.box {
    /* 略 */
    position: relative;
}

.circle {
    /* 略 */
    position: absolute;
}

div.circle.layer-0 {
  animation-delay: 0s;
}
div.circle.layer-1 {
  animation-delay: 0.2s;
}
div.circle.layer-2 {
  animation-delay: 0.4s;
}
div.circle.layer-3 {
  animation-delay: 0.6s;
}

f:id:otiai10:20180902002930g:plain

できてるやん。

6. 楕円にして水面表現する

これ、サイズが相対なので、transform使うまでもねえわ。

.box {
    height: 10vw;
    /* 略 */
}

f:id:otiai10:20180902004633g:plain

もうほぼ水面やんけ。

水滴が4つ落ちるのではなく、1滴に対して4種類の波が発生するべき

今、4つの水滴が逐次的に落ちて、それぞれ同じ波が4つ生まれているけど、自然には、1つの水滴が落ちて、複数の種類の波が発生すべきなので、ずらし方を改善する。

f:id:otiai10:20180902004509g:plain

いい感じやんけ。

色をちゃんとする

色のセンス無いんだわ〜

f:id:otiai10:20180902005414g:plain

横波をつけてみる

まず沈み込んで、盛り上がって、もっかい沈み込んで、で平常状態に戻る、みたいな感じかな。

f:id:otiai10:20180902010901g:plain

それっぽくなったけど、典型的な「がんばった結果もっさいデザイン」になった気がする。shadowいらないんじゃないかな。

雑感

  • opacityは多く使うのはパフォーマンス的に難あるかもしれない
  • ミニマムな実装としてはこれで満たしてるから、あとはチューニング
  • 他にも方法あったらおしえてください
  • わりとちゃんと働いてがっつり給料もらえる職場があれば紹介してください

DRYな備忘録として

今すぐ使えるCSSレシピブック

今すぐ使えるCSSレシピブック

Go GCP Client で ComputeEngine インスタンスの作成・取得

やりたいことを gcloud SDK で確認

% gcloud compute instances create \
    --project otiai10-sandbox \
    --zone asia-northeast1-a \
    testetst

% gcloud compute instances list \
    --project otiai10-sandbox \
    --filter zone:asia-northeast1-a

Go GCP Client でだいたい同じことやる

これです

github.com

以下全文

package main

import (
    "context"
    "flag"
    "fmt"
    "log"
    "time"

    "google.golang.org/api/googleapi"

    "github.com/otiai10/debug"
    "golang.org/x/oauth2/google"
    compute "google.golang.org/api/compute/v1"
)

var (
    project string
    zone    string
    name    string
)

func init() {
    flag.StringVar(&project, "project", "otiai10-sandbox", "Project name on GCP")
    flag.StringVar(&zone, "zone", "asia-northeast1-a", "Zone of GCP")
    flag.StringVar(&name, "name", "test-instance", "Instance name to create")
    flag.Parse()
}

func main() {

    ctx := context.Background()
    client, err := google.DefaultClient(ctx, compute.ComputeScope)
    if err != nil {
        debug.Fatalln(err)
    }

    service, err := compute.New(client)
    if err != nil {
        debug.Fatalln(err)
    }

    // Read
    instance, err := service.Instances.Get(project, zone, name).Do()

    if err == nil && instance != nil {
        // Delete
        log.Printf("Instance found, trying to delete.")
        _, err := service.Instances.Delete(project, zone, name).Do()
        if err != nil {
            debug.Fatalln(err)
        }
        for count := 0; ; count++ {
            fmt.Print(".")
            _, err := service.Instances.Get(project, zone, name).Do()
            if apierror, ok := err.(*googleapi.Error); ok && apierror.Code == 404 {
                fmt.Print("\n")
                break
            }
            time.Sleep(2 * time.Second)
            if count > 15 {
                debug.Fatalln("Couldn't wait for instance deletion ;(")
            }
        }
        log.Printf("Deleted: %v", name)
    } else if apierror, ok := err.(*googleapi.Error); !ok || apierror.Code != 404 {
        debug.Fatalln(err)
    }

    // Create
    instance = &compute.Instance{
        Description: "This is test",
        Name:        name,
        MachineType: fmt.Sprintf("zones/%s/machineTypes/n1-standard-1", zone),
        NetworkInterfaces: []*compute.NetworkInterface{
            &compute.NetworkInterface{
                Network: fmt.Sprintf("https://www.googleapis.com/compute/v1/projects/%s/global/networks/default", project),
            },
        },
        Disks: []*compute.AttachedDisk{
            &compute.AttachedDisk{
                AutoDelete: true,
                Boot:       true,
                Type:       "PERSISTENT",
                InitializeParams: &compute.AttachedDiskInitializeParams{
                    SourceImage: "projects/debian-cloud/global/images/debian-9-stretch-v20180806",
                    DiskSizeGb:  10,
                },
            },
        },
    }
    _, err = service.Instances.Insert(project, zone, instance).Do()
    if err != nil {
        debug.Fatalln(err)
    }

    log.Println("Created:", instance.Name)
}

雑感

  • AWSより概念が細分化されており、SDKとしての Hello World まで行くのはわりと大変だったけど、一度動いてしまったら、概念が明確で、とても使いやすい印象

DRYな備忘録として

クラウドエンジニア養成読本[クラウドを武器にするための知識&実例満載! ] (Software Design plusシリーズ)

クラウドエンジニア養成読本[クラウドを武器にするための知識&実例満載! ] (Software Design plusシリーズ)

【メモ】Slackチャンネルへの画像の投稿

参考

curl

% curl -XPOST "https://slack.com/api/files.upload" \
    -H "Content-Type: multipart/form-data" \
    -F file=@/Users/otiai10/Desktop/ritsu.jpeg \
    -F token=xoxb-123-456-xxxxxxxxx \
    -F channels=bot-dev \
    | jq
{
  "ok": true,
  "file": {
    "url_private": "https://files.slack.com/files-pri/ABCDEFGHIJKLMNOPQRSTUVWXYZ/ritsu.jpeg",
    "url_private_download": "https://files.slack.com/files-pri/ABCDEFGHIJKLMNOPQRSTUVWXYZ/download/ritsu.jpeg",
    // いろいろ省略
}

DRY