Perl で Google Drive API をつかって特定のファイルを更新する
特定のファイルを更新する
ちょっと前、ファイルを Google Drive にアップロードした時は、同名のファイルがある場合でもアップロードが可能でした。
・・・可能でした、っていうか完全に不意打ちでしたが・・・
しかし、ファイルを更新したい時はどうすれば良いのでしょう?ってわけで、やりました。人生で初めて PATCH メソッド使いました。
URI のパラメータにファイル ID を利用していますが、だいたいはアップロードの時と同じ感じですね。
$target_fileid
で指定したファイルが更新されます。metadata の値を変えることで、ファイル名やmimeType を更新することも可能です。
#!/usr/bin/env perl use strict; use warnings; use HTTP::Request::Common; use JSON qw/encode_json/; use LWP::UserAgent; use URI::QueryParam; use URI; my $target_fileid = '1dZU7-4d52U2pRWsmMLhMEuh8Cpa0m8sI'; my $GOOGLE_DRIVE_UPLOAD_API = "https://www.googleapis.com/upload/drive/v3/files/"; my $ACCESS_TOKEN = ""; my $bearer = join ' ', ( 'Bearer', $ACCESS_TOKEN ); my $URI = URI->new( $GOOGLE_DRIVE_UPLOAD_API . $target_fileid ); $URI->query_param( uploadType => 'multipart' ); my $ua = LWP::UserAgent->new; my $res = $ua->request( PATCH $URI, 'Content-Type' => 'multipart/form-data', Authorization => $bearer, Content => [ metadata => [ undef, undef, 'Content-Type' => 'application/json;charset=UTF-8', 'Content' => encode_json( { # name => 'hogefuga.txt', # mimeType => 'plain/text', # parents => ['10kCqEUmWsWlqMdP_vF9pDGrQXFVZ-Lvr'], # id => $target_fileid, }, ), ], file => ["./hoge.txt"], ], ); print $res->code . "\n"; print $res->content . "\n"; # 200 # { # "kind": "drive#file", # "id": "19f2RrocH4I3Mdig0LkmNPghJDZnmq35f", # "name": "hoge.txt", # "mimeType": "plain/text" # }
Perl で Google Drive API をつかって特定フォルダ配下のファイル一覧を取得する
特定のフォルダ配下にあるファイル一覧を表示する
こちらは GET アクセスなので簡単だろう〜・・・とナメてかかって、割と引っかかりました。
url でパラメータを組み立てる時に 2 回 URL エンコードしてしまったというもの。
Google Drive API v3 で特定のフォルダ配下にあるファイルを引っ掛けるってのやってて、フォルダの中に一つしかファイルがないのに複数引っかかって???ってなってた。$uri->query_param( 'q' => "'$folder_id' in parents" );
— sironekotoro (@sironekotoro) 2020年11月11日ほんで、ググって見つけた C# のコード参考にしてみたら検索結果が1つだけと望んだ結果になった。$uri->query_param( 'q' => "'$folder_id' in parents and trashed = false" );
— sironekotoro (@sironekotoro) 2020年11月11日
ゴミ箱の中にあるファイルも、元どこのフォルダにあったかを記録しており、検索結果で明示的に除外する必要があると
つまり、「特定のフォルダの中にある」って条件だけじゃなくて「ゴミ箱の中にはない」とかちゃんと指定しようねというお話。まぁ、確かにね。
$folder_id
の中にあるファイル・フォルダの ID が表示されます。
#!/usr/bin/env perl use strict; use warnings; use HTTP::Tiny; use URI::QueryParam; use URI; my $ACCESS_TOKEN = ''; my $folder_id = '10kCqEUmWsWlqMdP_vF9pDGrQXFVZ-Lvr'; my $uri = URI->new('https://www.googleapis.com/drive/v3/files'); $uri->query_param( 'q' => "'$folder_id' in parents and trashed = false" ); my $bearer = join ' ', ( 'Bearer', $ACCESS_TOKEN ); my $ht = HTTP::Tiny->new( default_headers => { Authorization => $bearer } ); my $res = $ht->get($uri); print $res->{content}; # { # "kind": "drive#fileList", # "incompleteSearch": false, # "files": [ # { # "kind": "drive#file", # "id": "1dZU7-4d52U2pRWsmMLhMEuh8Cpa0m8sI", # "name": "hogefuga.txt", # "mimeType": "text/plain" # } # ] # }
query_param で組み立てている検索条件を名前での検索にする時はこんな感じ。クォーテーションの位置とかでわりと試行錯誤しました。
$uri->query_param( 'q' => "name = 'hogefuga.txt'" );
他の検索条件を使いたい人はこちら
Perl で Google Drive API のアクセストークンを更新する
今日も Perl から Google Drive API v3 をやっていきます。
リフレッシュトークンを使ってアクセストークンを更新する
Google の発行したアクセストークンは 3600 秒、つまり 1 時間で失効します。
失効する都度、アクセストークンを発行しても良いのですが(うちもそうしてた)、さすがに面倒・・・になってきました。
そこで、アクセストークンと一緒に発行されるリフレッシュトークンを利用してアクセストークンを更新します。
その際には以下のものが必要です。
それらを POST で Google のAPI になげると、新しいアクセストークンが返ってきます。
これでまだ戦えますね!
注: ACCESS_TOKEN なくても更新できたので、コード更新しております
#!/usr/bin/env perl use strict; use warnings; use Data::Dumper; use HTTP::Tiny; use JSON; use URI; my $CLIENT_ID = ""; my $CLIENT_SECRET = ""; my $REFRESH_TOKEN = ""; my $URI = URI->new('https://oauth2.googleapis.com/token'); my $ht = HTTP::Tiny->new(); my $response = $ht->request( 'POST', $URI, { content => encode_json( { client_id => $CLIENT_ID, client_secret => $CLIENT_SECRET, grant_type => 'refresh_token', refresh_token => $REFRESH_TOKEN, } ) } ); my $json = decode_json($response->{content}); print Dumper $json; # "access_token": "hogehogefoofoobarbar", # "expires_in": 3599, # "scope": "https://www.googleapis.com/auth/drive", # "token_type": "Bearer"
Perl で Google Drive API を使ってファイルをアップロードする
Google Drive API を使ってファイルをアップロードしたい!
これが超苦労しましたん・・・
先に blog で書いたとおり、自分の環境(と技能)では、Google Drive を操作するモジュールを使うことができませんでした。
しかし!
Google Drive API は REST API なので、特別モジュールを使わずともいけるはずです。
We have a REST API. You can use any language to implement your own client, you don't have to use one of the client libraries we support.
(DeepLで翻訳)私たちはREST APIを持っています。独自のクライアントを実装するために任意の言語を使用することができます。
実際、一つ前のエントリに書いたファイルリストの取得であれば、https://www.googleapis.com/drive/v3/files?access_token=アクセストークン
をブラウザの URL 欄に貼り付けてアクセスすれば表示が可能です。
ってことで、API を生で扱うツールとして curl に目をつけ、利用事例を探しました。そこから Perl の http クライアントに実装していこうという狙いです。
curl を使って Google Drive にファイルをアップロードする
curl というのは、macos や linux 、そして Windows10 にも入っている HTTP クライアントです。
Windows10 でも使えるのはついさっき知りました・・・
ターミナルやコマンドプロンプトから curl https://www.yahoo.co.jp
とかやると応答が返ってくるはずです。
curl での実装を探して見つけたサイトが以下です。割と自然な日本語のタイトルなんですが、日本語に自動翻訳されたサイトみたいです。
ここのサンプルを用いて Google Drive の任意のフォルダにファイルをアップロードすることができました。
curl -X POST \ -H "Authorization: Bearer アクセストークン" \ -F "metadata={ \ name : 'hoge.txt', \ mimeType : 'plain/text', \ parents: ['1PSb3xH000llDtXSWLaAtKEXJy5DY_Wic'] \ };type=application/json;charset=UTF-8" \ -F "file=@hoge.txt;type=plain/text" \ "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart"
ここでハマったのは、parents: ['1PSb3xH000llDtXSWLaAtKEXJy5DY_Wic'] \
の [ ]
は削除するものと思い、結果、いつまでも Google Drive の指定したフォルダの中に入らずに困ったという。
いや、先のサンプルで -H "Authorization: Bearer [ACCESS-TOKEN]" \
とあるんだけど、ここは実際には [ ]
なしで入力するので、parents のところも [ ]
いらないと思ったんですよね・・・
結局、公式のリファレンスでここにはリストを渡す、って記載を発見してやっと気づいたという。公式はいつも大事(1回目)
parents[] list
Perl を使って Google Drive にファイルをアップロードする
curl でできるなら、Perl でもできる!あとは移植していくだけや!と意気揚々といつもの use HTTP::Tiny
をタイプしますが、ここで手が止まります。
Perl を使ってファイルアップロード?
HTTP::Tiny
でファイルのアップロードどうやるんだ?
GET, POST 共に使ったことありますが、はて、ファイルアップロードはしたことない・・・
ググります。
Not easily. HTTP::Tiny contains no support for the multipart/form-data content type required by file uploads. (That's one of the reasons it's called "Tiny".)
(DeepLで翻訳)簡単にはできません。HTTP::Tiny は、ファイルのアップロードに必要な multipart/form-data コンテンツタイプをサポートしていません。(これが "Tiny" と呼ばれる理由の 1 つです)。
いや、curl でアクセスするときに multipart
とか書いてあるんやけど、それ無いのん・・・この時点で心が折れかかるんですが、大丈夫、我々には Furl
, LWP::Simple
, LWP::UserAgent
がある!
そして、どうやら POST するときは HTTP::Request::Common
でリクエストメッセージを作るのが良い、という事を知ります。
ただし "multipart/form-data" を送る場合は、post() メソッドを使ってリクエストを行うよりも、HTTP::Request::Common オブジェクトを直接扱って LWP::UserAgent へ request() させた方がいいかも知れない。boundary だのファイル名だの若干複雑なので……。
これは多分、先の Stack overflow で回答者が最後に書いていた
Correctly populating $multipart_form_data is left as an exercise for the reader. :-)
(DeepLで翻訳)multipart_form_dataを正しく入力することは、読者のための練習として残されています。)
ここを解決するのが、この HTTP::Request::Common
なのかなぁ?と思っています。
煮詰まって助けてもらう
なんとか、LWP::UserAgent
での実装にたどり着いたものの、動かない。
煮詰まったので、 Perl入学式の公開チャンネルに質問したのでした。
そこに上げた、動かないコードがこちら。
use HTTP::Request::Common; use LWP::UserAgent; my $ua = LWP::UserAgent->new; my $res = $ua->request( POST 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart', Authorization => 'Bearer アクセストークン', Content_Type => 'multipart/form-data', Content => [ metadata => encode_json( { name => 'hoge.txt', mimeType => 'plain/text', parents => ['1PSb3xH000llDtXSWLaAtKEXJy5DY_Wic'], } ), file => ["./hoge.txt"], ] ); say $res->code; # 400 say $res->content; # { # "error": { # "errors": [ # { # "domain": "global", # "reason": "badContent", # "message": "Unsupported content with type: application/octet-stream" # } # ], # "code": 400, # "message": "Unsupported content with type: application/octet-stream" # } # }
ベテランの Perl Monger 達に助けてもらいました。本当にありがとうございます。
id:anatofuz , id:karupanerura, id:shoichikaji
動かなかった原因
これは curl と LWP::UserAgent の Dump をとって分かったのですが、metadata 部のリクエストがうまく組み立てられていませんでした。あと typo も。
ちなみに、LWP の Dumper はこんな感じで入れられます。(slack で教えてもらった)
$ua->add_handler("request_send", sub { shift->dump; return }); $ua->add_handler("response_done", sub { shift->dump; return });
以下は LWP::UserAgent の Dump の抜粋です。
--xYzZY\r Content-Disposition: form-data; name="metadata"\r \r {"mimeType":"plain/text","name":"hoge.txt","parents":["10kCqEUmWsWlqMdP_vF9pDGrQXFVZ-Lvr"]}\r --xYzZY\r Content-Disposition: form-data; name="file"; filename="plain/text"\r Content-Type: text/plain\r \r hoge \r
metadata のところに Content-Type:
がありません。json のデータを送る旨明示しなければならないのですが、それがありません。
対して、file のところには Content-Type: text/plain\r
があります。
つまり、metadata のところのデータ構造の構築に失敗していました。
で、どうやってこの metadata のところに curl と同じ application/json;charset=UTF-8
を入れるんだ?となりました。
どう書けば良いかはちゃんと HTTP::Request::Common 公式にありました。公式大事(2回目)
複数の値を持つフォームフィールドは、フィールド名を繰り返すか、 配列リファレンスを渡すことで指定できます。
POST メソッドはRFC1867 で示された Form-based File Upload のために使われる multipart/form-data コンテントもサポートします。 リクエストヘッダの一つとして 'form-data' のコンテントタイプを 指定することにより、このコンテントフォーマットを利用することが出来ます。 もし $form_ref の中の値の1つが配列リファレンスであれば、それは以下の解釈で ファイル部分の指定であるように扱われます:
[ $file, $filename, Header => Value... ]
[ undef, $filename, Header => Value,..., Content => $content ]
配列での先頭の値 ($file) はオープンするファイルの名前です。 このファイルは読みこまれ、その内容がリクエストに入れられます。 もしファイルをオープンできなければルーチンは croak します。 コンテントを直接 Content ヘッダで指定したければ $file の値を undef に してください。 $filename はリクエストで報告されるファイル名です。 この値が未定義であれば、$file の基本名が使われます。 $file の値を提供したとき、ファイル名の送信をよくせいしたいなら、 $filename に空文字列を指定することができます。
なるほど。
動かないコードだと metadata に対する値は一つだけでした。
[ $file, $filename, Header => Value... ]
の $file
しか入っていない状態です。
しかも、モジュールは file
が来ることを想定してるのに、渡しているのは(JSON化した)文字列です。なるほど動かん。
metadata => encode_json( { name => 'hoge.txt', mimeType => 'plain/text', parents => ['1PSb3xH000llDtXSWLaAtKEXJy5DY_Wic'], }
これを直すとこうです。
[ undef, $filename, Header => Value,..., Content => $content ]
の形式にするコンテントを直接 Content ヘッダで指定したければ $file の値を undef に してください
に従うJSONの文字列は Content キーの値に移動
適用した配列リファレンスを metadata の値に渡しています。
metadata => [ undef, # ファイル使わないので undef undef, # ファイルからデータを取得しないので undef 'Content-Type' => 'application/json;charset=UTF-8', 'Content' => encode_json( # jsonのデータは Content キーの下に { name => 'hoge.txt', mimeType => 'plain/text', parents => ['10kCqEUmWsWlqMdP_vF9pDGrQXFVZ-Lvr'], }, ), ],
Perl で Google Drive API を使ってファイルをアップロードする(タイトル回収)
ということで、やっと Perl で Google Drive にファイルをあげることができました。
#!/usr/bin/env perl use strict; use warnings; binmode STDOUT, ":utf8"; use HTTP::Request::Common; use JSON qw/encode_json/; use LWP::UserAgent; my $GOOGLE_DRIVE_UPLOAD_API = "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart"; my $ua = LWP::UserAgent->new; my $res = $ua->request( POST $GOOGLE_DRIVE_UPLOAD_API, 'Content-Type' => 'multipart/form-data', Authorization => 'Bearer アクセストークン', Content => [ metadata => [ undef, undef, 'Content-Type' => 'application/json;charset=UTF-8', 'Content' => encode_json( { name => 'hoge.txt', mimeType => 'plain/text', parents => ['10kCqEUmWsWlqMdP_vF9pDGrQXFVZ-Lvr'], }, ), ], file => ["./hoge.txt"], ], ); print $res->code . "\n"; print $res->content . "\n"; # 200 # { # "kind": "drive#file", # "id": "19f2RrocH4I3Mdig0LkmNPghJDZnmq35f", # "name": "hoge.txt", # "mimeType": "plain/text" # }
おまけ HTTP::Tiny::Multipart
むやみやたらとググる中、コアモジュール民(自分)が大好きな HTTP::Tiny の拡張がありました。HTTP::Tiny::Multipart です。
これを使って Google Drive へのアップロードを書いてみました。こちらも動きました。
#!/usr/bin/env perl use strict; use warnings; binmode STDOUT, ":utf8"; use File::Slurp qw/read_file/; use HTTP::Tiny; use HTTP::Tiny::Multipart; use JSON; my $GOOGLE_DRIVE_UPLOAD_API = "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart"; my $ACCESS_TOKEN = ""; my $bearer = join " ", ( 'Bearer', $ACCESS_TOKEN ); my $content = read_file('./hoge.txt'); my $http = HTTP::Tiny->new( default_headers => { Authorization => $bearer } ); my $response = $http->post_multipart( $GOOGLE_DRIVE_UPLOAD_API, { metadata => { content => encode_json( { name => 'hoge.txt', mimeType => 'plain/text', parents => ['10kCqEUmWsWlqMdP_vF9pDGrQXFVZ-Lvr'], }, ), content_type => 'application/json; charset=utf-8', }, file => { filename => './hoge.txt', content => $content, content_type => 'plain/text', }, } ); print $response->{content} . "\n"; # { # "kind": "drive#file", # "id": "1UInkENiv0GApyfhJWyxgNaplw512Mmw2", # "name": "hoge.txt", # "mimeType": "plain/text" # }
後始末
Perl で Google Drive API を使ってファイル一覧を取得する
さて、Google Cloud Platform で利用する ACCESS TOKEN と REFRESH TOKEN を手にしたからには、次の段階へ向かわねばなりません。
そもそも、なんか OAuth いじるの楽しくなって色々やってしまいましたが、これは目的と手段が逆転するいつものパターンです。
楽しいんですよねー
Google Drive に上がっているファイル一覧を取得する
GET でパラメータを並べていくだけなので、簡単にできました。
注意するところとしては、一回の応答で取得できるのは100件のみ。100件以上ファイルがある場合には応答に nextPageToken
が含まれるので、次の API 組み立ての際に pageToken
を加えてアクセスって感じです。
これで連続取得ができます。
大量にファイルがあると返ってこないので、5 回取得したら(つまり 500件)ループを抜けるようにしてます。
#!/usr/bin/env perl use strict; use warnings; binmode STDOUT, ":utf8"; use HTTP::Tiny; use JSON; use URI::Escape; use URI; my $GOOGLE_DRIVE_API = "https://www.googleapis.com/drive/v3/files"; my $ACCESS_TOKEN = ""; my $count_limit = 5; # 全てのファイルを取得する my $uri = URI->new($GOOGLE_DRIVE_API); $uri->query_form( access_token => $ACCESS_TOKEN ); files($uri); sub files { my $uri = shift; my $count = 0; my $ht = HTTP::Tiny->new(); while ( $count < $count_limit ) { my $contents = decode_json( $ht->get($uri)->{content} ); $uri->query_form( access_token => $ACCESS_TOKEN, pageToken => $contents->{nextPageToken}, ); for my $content ( @{ $contents->{files} } ) { print "=" x 20 . "\n"; printf( "%-8s: %s\n", "id", $content->{id} ); printf( "%-8s: %s\n", "name", $content->{name} ); printf( "%-8s: %s\n", "mimeType", $content->{mimeType} ); printf( "%-8s: %s\n", "kind", $content->{kind} ); print "=" x 20 . "\n"; } $count++; last if !$contents->{nextPageToken}; # 最終ページには nextPageToken キーが無い } }
==================== id : 13uf_8fJph3J3raPee0Sg5rsgb-w5MIUBLVdhDhD8xSE name : チェックリスト mimeType: application/vnd.google-apps.document kind : drive#file ==================== ==================== id : 1f9ZuDCOSGUujhSSXvv_kruzI7qbkKq9FQXf2YuNn_Io name : メモ mimeType: application/vnd.google-apps.document kind : drive#file ==================== ...
Mojolicious::Lite で Google Drive API のアクセストークンを表示する
そろそろ前に進まないとと思いつつ
ここしばらく、Google の提供する API を扱うためのことやってきたんですが、やりつつ一つの疑念がありました。
途中で Web にアクセスして URL を貼り付けるところ、そのまま HTTP の GET で良いのでは?
いちいちブラウザに貼り付けなくても良さそうな・・・?
というか、最初からブラウザで動かしたら?
それって、 Perl 入学式 でやった Mojolicious::Lite で実装できそう・・・と言うことで実装してみました。
下準備としては、Google Compute Platform の API 設定の OAuth 同意画面で、アプリケーションのホームページを http://localhost:3000
にしておくことです。
言わずと知れた Mojolicious を morbo で起動した時の URL とデフォルトポートですね。
出来上がったもの
苦労したところ
Google の認証画面に遷移するところまでは割とすんなり行きました。
問題はそっから ACCESS_TOKEN と REFRESH_TOKEN つくるところで詰まりました。
結局、 Net::Google::OAuth のソースを参考にしつつ作ることに・・・
おかげで、メールアドレスやリダイレクトのURLをURLエスケープするとか、HTTP::Tiny の POST つかって JSON で送るとかを読み取ることができました。
コード
Google Drive API での利用を想定して SCOPE のところがそうなってます。
http://localhost:3000
-> (POST) -> Google の認証画面 -> (GET) -> http://localhost:3000/code
という感じで遷移します。
URL の組み立てのところが雑だなぁ・・・と思うんですが、まぁそこはそれってことで。
#!/usr/bin/env perl use Mojolicious::Lite; use HTTP::Tiny; use JSON; use URI::Escape; use URI; my ( $CLIENT_ID, $CLIENT_SECRET, ); get '/' => sub { my $c = shift; $c->render( template => 'index' ); }; get '/code' => sub { my $c = shift; my $code = $c->param('code'); # Exchange code to token my $param = { 'client_id' => $CLIENT_ID, 'client_secret' => $CLIENT_SECRET, 'redirect_uri' => 'http://localhost:3000/code/', 'code' => $code, 'grant_type' => 'authorization_code', 'access_type' => 'offline', }; my $json = encode_json($param); my $ht = HTTP::Tiny->new(); my $response = $ht->request( 'POST', 'https://accounts.google.com/o/oauth2/token', { headers => { 'Content-Type' => 'application/json; charset=utf-8', }, content => $json, }, ); $json = decode_json( $response->{content} ); $c->stash( access_token => $json->{access_token}, refresh_token => $json->{refresh_token} ); $c->render( template => 'code' ); }; post '/post' => sub { my $c = shift; $CLIENT_ID = $c->param('CLIENT_ID'); $CLIENT_SECRET = $c->param('CLIENT_SECRET'); my $MAIL = $c->param('MAIL'); my $SCOPE = $c->param('SCOPE'); my $redirect_url = uri_escape('http://localhost:3000/code/'); my $login_hint = uri_escape($MAIL); my $scope = uri_escape($SCOPE); # url組み立て my $url = "https://accounts.google.com/o/oauth2/v2/auth?client_id=$CLIENT_ID&redirect_uri=$redirect_url&scope=$scope&response_type=code&approval_prompt=force&access_type=offline"; $c->redirect_to($url); }; app->start; __DATA__ @@ index.html.ep % layout 'default'; % title 'Welcome'; <h1>Welcome to the Mojolicious real-time web framework!</h1> <form action="/post" method="post"> <ul> <li>CLIENT_ID: <input name="CLIENT_ID" size=80 type="text" value=""></li> <li>CLIENT_SECRET: <input name="CLIENT_SECRET" size=80 type="text" value=""></li> <li>MAIL: <input name="MAIL" size=20 type="text" value=""></li> <li>SCOPE: <input name="SCOPE" size=40 type="text" value="https://www.googleapis.com/auth/drive"></li> </ul> <input type="submit" value="POSTで投稿する"> </form> @@ code.html.ep % layout 'default'; % title 'Welcome'; <h1>Welcome to the Mojolicious real-time web framework!</h1> <ul> <% if ( stash('access_token') ) {%> <li>ACCESS TOKEN:<%= $access_token %></li> <% } %></textbox> <% if ( stash('refresh_token') ) {%> <li>REFRESH TOKEN:<%= $refresh_token %></li> <% } %> </ul> <a href="http://localhost:3000">トップページ</a> @@ layouts/default.html.ep <!DOCTYPE html> <html> <head><title><%= title %></title></head> <body><%= content %></body> </html>
CircleCI で Perl のテストをローカルでやってみた
環境構築から
この本を参考にしました。
Perl の例は掲載されていないですが、他の言語での例は豊富です。
なので、そっから応用していけるのではないか、していきたい!という気持ち。
$ brew install circleci
バージョン確認
$ brew info circleci circleci: stable 0.1.11393 (bottled) (以下略)
あと、Docker for Mac も必要になります。
いつもの Hello, World! の下準備
作業用のディレクトリに CircleCI 用のフォルダを作る
$ mkdir -p .circleci/
設定を記述する YAML ファイルを用意する
$ touch .circleci/config.yml
エディタで YAML ファイルを編集する
実行時に必要になる Perl の入った Docker イメージは好きなのを使ってください。Perl が入っていれば ok です、多分。
ってことで、去年くらいに作った自作の Perl 入りの Docker イメージ sironekotoro/alpine-perl
を使います。
version: 2.1 jobs: build: docker: - image: sironekotoro/alpine-perl steps: - run: perl -e 'print "Hello, World!\n"'; workflows: version: 2 workflow: jobs: - build
出来上がったファイルが文法上正しいかを確認する
$ circleci config validate Config file at .circleci/config.yml is valid.
大丈夫だったら実行します。
$ circleci local execute
ログが流れ・・・そして最後にコマンドが実行されて Success! となって終わりです。
(省略) perl -e 'print "Hello, World!\n"'; Hello, World! Success!
ちなみに、2020年11月1日より Docker Hub では pull の回数に制限が入ります。念のため、Docker Hub の認証情報を追加しておきます。
version: 2.1 references: docker_hub_authentication: &docker_hub_authentication auth: username: DockerHubでのユーザー名 password: DockerHubでのパスワード jobs: build: docker: - image: sironekotoro/alpine-perl <<: *docker_hub_authentication steps: - run: perl -e 'print "Hello, World!\n"'; workflows: version: 2 workflow: jobs: - build
circleci 上で、Perl の コマンドが実行できることがわかりました。素晴らしい!
Hello World! が動くってことは prove -l
も動くっしょ
prove -l
は Perl のテスト時に使われるコマンドですが、もちろん動きます。
実際のモジュールで試してみます。これも自作の雑モジュールです。こういうとき助かるー!
$ git clone git@github.com:sironekotoro/Acme-MetaVar.git $ cd Acme-MetaVar $ mkdir -p .circleci/ $ touch touch .circleci/config.yml
そしてお好みのエディタで .circleci/config.yml
編集します。
先の例とは、 steps セクションが異なっています。
version: 2.1 references: docker_hub_authentication: &docker_hub_authentication auth: username: DockerHubでのユーザー名 password: DockerHubでのパスワード jobs: build: docker: - image: sironekotoro/alpine-perl <<: *docker_hub_authentication steps: - checkout - run: prove -l; workflows: version: 2 workflow: jobs: - build
編集が終わったらローカルで実行です。
$ circleci local execute (中略) ====>> prove -l; prove -l; t/00_compile.t .. ok t/01_hoge.t ..... ok t/02_fuga.t ..... ok All tests successful. Files=3, Tests=3, 0 wallclock secs ( 0.04 usr 0.02 sys + 0.29 cusr 0.08 csys = 0.43 CPU) Result: PASS Success!
という感じで、CircleCI に入門したのでした。
なぜ CircleCI やろうと?
Perl のモジュールオーサリングツール Minilla で モジュールを作ると CI ツールである Travis への連携が簡単にできます。
そのテスト結果が出るのが思ったより楽しかったので、他の CI ツールはどうなんだろう?という好奇心から。
最終的にはこういうのをやりたいんですよねー
本を読むとできそうなのですが、うちが作れるかどうかはまた別の話・・・