tech.guitarrapc.cóm

Technical updates

2018年に使ったサービス

2017年から2018年でどうなったのか見てみます。昨年よりも少し細かく書きました。

tech.guitarrapc.com

基本方針は変わりません。

  • 有料・無料で同一程度のサービスがある場合無料のものを選択する
  • 有料のサービスはサブスクリプションで、月ベースでのみ使う

いずれにしても、手間がかかることに対してどこまで時間を省略できるかが鍵なので、不便を感じたら改善していくのです。

生活の中でやり方をサービスに合わせてどんどん変えているので、2017年と2018年でも結構普段の生活が変わってたり。

目次

有料

2017年から継続して有料課金しているサービスと2018年に新たに使い始めたサービスです。

サービス名 価格 種類 タイプ 用途
AWS $3.59/month クラウド 継続 Route53 / Lambda / S3 / KMS
Amazon Prime \3900/year 買い物 継続 ネットショッピング
GitHub Pro $7.00/month DVC 継続 Protected Branch を含めた有料機能 + コントリビューター3名を超えるため
GitKraken $49/year Git GUI 継続 macOS/Windows両方で利用
G Suite Basic \600/user/month オフィス 継続 Gmail / Group / Calendar / Drive & Document / Photo
Jump+ \900/month 雑誌 継続 そろそろおわりかな
TeamsId $3/user/month パスワード管理 継続 チーム管理の楽さが神がかってる
Udemy コース次第 スクール 継続 技術系の動画スクール。微妙
はてなブログ \8434/year ブログ 継続 今使ってるマネージドなブログサービス
マガジンポケット \840/month 雑誌 新規 そろそろおわりかな。新たに新規で追加したサービスです。
Money Forward \500/month 家計簿 新規 キャッシュフロー管理。新たに新規で追加したサービスです。
GCP \500/month クラウド 無料->有料 開発環境。これまで無料でしたが、有料に切り替えました。
Unity Plus \4,200/month 開発 無料->有料 開発環境。これまで無料でしたが、有料に切り替えました。

AWS

主にRoute53と開発環境、検証環境に利用しています。昨年考えていたGCPでのDNS管理は自社の方で行っており、変化や違いを両者で見ています。 設定はTerraform化されておりprivate repository + Atlantis + ngrock on localで運用してます。

Amazon Prime

以前から利用しています。ネットでの購入は原則Amazonに集約させているため、Primeがあるおかげで素早く調達できており助かっています。

GitHub Pro

2013年からずっとです。2019年1月に無料プランでもPrivate Repository を持てるようになりました。 個人アカウントでもProtected Branch などを使っているため今のところ継続しています。

GitKraken

私自身、公私でmacOSとWindowsの両方を使っています。そのためGit GUIはずっと悩ましい問題ですが、2018年はGitKrakenが完全に独壇場でした。OS問わず挙動が安定しており、SourceTreeにあってGitKrakenにない機能はいくつかありますが、SourceTreeのWindowsでの不安定さ、OS間で同じ体験を出来る方が格段にメリットに感じます。

Profileの分離で認証も分離できることもあり、お手伝いをするにあたってもかなり便利に感じます。

唯一感じるのは、GitHub が OAuth App 認証なので認可をしないといけないことでしょうか。

G Suite Basic

個人のSSOは安定してこれです。

やはりGroup の存在が一番大きく、Google認証に対応しているかどうかはサービス選択の基本の条件になりつつあります。

Jump+

読む作品をかなり減らしたものの継続しています。 ワンピースとDr. Stone とアクタージュが面白いです。逆にいうとこれしか読んでいないです。

TeamsId

2018年、終始お世話になりました。認証やデータが突然見えなくなったことがあったのがたまに傷ですが、チームでの管理の面では本当に楽です。

と、いいつつ記事を書いている2019年1月時点で、Bitwarden に乗り換えていますが。

Udemy

あまりみないのですが、AWS 周りで自分が普段から触らないサービスをちらちらみたりしていました。 それぐらいかな? Udemy の Youtube CMが本当に苦手です。

はてなブログ

カスタムドメインのため Pro を1年更新で使っています。

https 対応されたけど、2018年はあまりブログを書かなかったこともあり対応は先日でした。記事を書いていて感じるのですが、はてなブログにわくわくしていない自分を感じます。

マガジンポケット

以前はKindle だったのですが、アプリが出てたので切り替えました。

こちらも3作品程度しかみなくなったので、そろそろかな。

Money Foward

Zaim プレミアムからの乗り換えです。

乗り換えにあたり、Zaimの利用でうまくできなかった買い物フローを見直してシステムに合わせるようにしました。主に2点です。

  • 基本的に決済をクレジット化
  • どうしても現金の場合は現金収支だけつけて明細はオミットする

これでクレジット明細の連動によって、何もしなくてもMoney Forward で財務状態が可視化、トラッキングされます。たまに区分が抜けるのだけつけれておしまいです。

ここまで省力化しないとやらない現実がありました。

GCP

個人のリソースはAWS 多いのですが、会社としてはGCP がメインなのです。 ということで個人の開発環境です。

Unity Plus

2017年にやめたのですが再開です。 この間に Unity Teams Advanced が来てて続けておけばよかった感が半端ないです。

買い切り

サブスクリプションではない買い切りです。サービス以外にアプリやハードもあります。実際のところ書いていないハードは他にもありますが、開発と関連するところはこんな感じで。

昨年からの継続が混じっています。

サービス名 価格 種類 タイプ 用途
Flawless $35/買い切り 開発 継続 iOS開発アプリ (2017年~
Genius Productivity Bundle $8.99/買い切り PDF取り込み 新規 スマホでの事務処理アプリ (2018年~
Charles Proxy \1080 / 買い切り 開発 新規 iPhone でのプロクシ解析アプリ (2018年~
Tile \3,110/個 スマートタグ 新規 なくしたくないもののGPSトラッキング (2018年~
Google Home \15120/個 スマートスピーカー 新規 ok, google (2018年~
Apple TV 4K \19800/個 リモートディスプレイ 新規 macBookの映像だしたりAmazon Prime Video

Flawless

iOS 開発でどうしてもほしくなるのですねぇ。便利。

Genius Productivity Bundle

お仕事の過程で、PDFスキャンとかサインが必要な時に重宝します。 とはいえ丁寧なスキャンが必要な時はスキャナー使っています。

Charlles

iOS開発で便利。iOS 上の Fiddler 的な。

Tile

2018年12月に大事なものをなくすことが生じて、システム的に防止するためにこれにしました。

Mamorio や Qrio Tag 使ってましたが、電池交換できないし誤作動多いしでこっちに切り替えです。

Google Home

一番活躍するのはキッチンタイマーだったり.... Echoではありません

Apple TV 4K

年末に導入しました。

家では MacBook と Windowsを併用しているのですが、MacBook の時にモニターに投影するのがなかなかいかついのです。

duet は安定性の面では日常的につかいたいものではなく、Apple TV で代用することにしました。

あとは、Prime Video を試すことが稀にあります。

無料

サービス名 価格 種類 タイプ 用途
Azure 無料 クラウド 継続 AzureFunctions / AppService / AKS メイン
Azure DevOps 無料 CI 継続 .NET / PowerShell ビルド (Visual Studio Team Service のブランド変更)
Appveyor 無料 CI 継続 .NET/PowerShell ビルド
Circle CI 無料 CI 継続 .NET / Golang / 他 ビルド
draw.io 無料 構成図 継続 構成図を描くのに便利
Docker Hub 無料 コンテナリポジトリ 継続 コンテナ置き場
Eight 無料 名刺管理 継続 名刺はEight で交換しましょ....
Fastly 無料 CDN 継続 検証に Developer Account で。
Google Analytics 無料 Web 継続 ブログとか
Google Adsense 無料 広告 継続 ブログ
Kibela 無料 Wiki 継続 ドキュメント/Wiki/ Blog / 勉強会メモ
Slack 無料 チャット 継続 個人のいろいろストックです
Wunderlist 無料 TODO 継続 TODOの管理
一休 無料 予約 継続 レストラン、ホテル他
IFTTT 無料 グルー 継続 連動系はこっち
Zapier 無料 グルー 継続 連動系の検証に
duet 無料 リモートディスプレイ 継続 MacBook のディスプレイ拡張
LinkedIn 無料 プロフィール 新規 紹介
Wantedly 無料 プロフィール 新規 紹介
JapanTaxi 無料 タクシー 新規 困ったときに便利
Integromat 無料 グルー 新規 インスタントは処理はこっち
ネットワークプリント 無料 プリンター 新規 コンビニプリント

Azure

主に技術検証や無料内でのサービス利用なので無料枠で足りてるのですが、Subscription が切れたのでどうしようかなです。

Azure DevOps

Visual Studio Team Service からのリブランドですね。

Windows 系のビルド検証に使ってたのですが、YAMLサポートされたので楽になりました。無料以上は使わないです。

Appveyor

去年書きませんでしたが使ってはいます。

.NET 系のツールが入ったイメージでビルドできるので確かに楽ではありますが、癖が強くてなかなかアレ。

Circle CI

主たるビルドサービスは安定して CircleCI です。

draw.io

構成図はこれで。GitHub や Google Drive で保存しています。

Docker Hub

コンテナ置き場、2018年はほそぼそいろいろ起きました。

Eight

Eight で名刺交換って言われたことないんですけど、どうなってるんでしょうか。(Eightで名刺交換したいといわれたら好感度めっちゃ高いです)

Fastly

CDNだいじだいじ

Google Analytics

ブログの閲覧はこれ。

あと、G Suites の管理画面もとってます。

Google Adsense

試しに設定して放置しています。

Kibela

esa から乗り換えて一年、デザイン/UX変更が大きくなかなか考えるものがあります。

サービスの進化やロードマップ見ると、あんまり魅力を感じなくなりつつあるのですが、どうしようか考えています。

Slack

基本的な通知ややりとりはこっち。

Wunderlist

なくならないのかしら? Microsoft TODO に乗り換えかなぁ

一休

たまに使うたびに重宝します。

IFTTT

Slack への連動はこの子です。

Zapier

会社では使っているので、主に検証用途です。

duet

MacBook でディスプレイを簡単に拡張するのに使っています。

LinkedIn

昨年はいろいろ活動してたので LinkedIn は重宝しました。 だいたい海外や転職サイト、エージェントからばかりです。

国内はほぼ0。

Wantedly

いろいろ見てましたが、ちょっとどうなんだろう.....? かなり慎重に見ています。

メッセージ時々いただくのですが、反応しないのは、良くない話しか聞かない/私自身そういう経験したからです。

カジュアルに、「話を聞きに行く側」と「話を聞かれにきた側」で認識の差を感じます。

とはいえ、私もカジュアルな話という時に逸脱した経験あるので、悩ましいのかな。

JapanTaxi

困ったときにタクシーいるのかしら確認に便利です。 あと急いているときにしかタクシーのらないので、先に支払っておけるのはすばら。

Integromat

Integromat + IFTTT が基本です。

tech.guitarrapc.com

ネットワークプリント

うちにプリンターをおきたくないので、Google Drive から 転送してコンビニプリントしています。

無料継続(利用がなかったもの)

いらないというより、使う機会が乏しい

サービス名 価格 種類 タイプ 用途
Heroku 無料 PaaS 継続 アプリホスト
Visual Studio App Center 無料 CI/Analytics 継続 開発
Cloud Craft 無料 構成図 継続 3D系を書かなかった
inVision 無料 デザイン 継続 プロトタイプ前でした

Heroku

Heroku でホスティングしたいものがなかった。

Serverless で実装がおおいので自然と減ります。

Visual Studio App Center

仕事ではたまに使うのですが、昨年は個人では使いませんでした。

Cloud Craft

Draw.io に活躍の場が奪われています。

inVision

2017年は触ってたのですが、2018年は気付くと使わなかったです。

解約

サービス名 価格 種類 タイプ 用途
Zaim Premium ¥360/month 家計簿 切り替え 手作業が入っていたのがよくなかった
News Picks \1500/month ニュース 解約 始めはおもしろかったのに
Qrio SmarTag \4298/個 スマートタグ 切り替え 落として壊れた、アプリとの連動がよくなかった、電池が...
Mamorio \4298/個 スマートタグ 切り替え 落として壊れた、電池が...
Google Play Music \900/month 音楽ストリーミング 解約 Google Home で入ってみたのですがいらなかった
Grammarly 無料 文法チェック 解約 英語の文法チェック

Zaim Premium

Zaim本当に面倒で、特にレシートの取り込むとかは嫌でした。

現金での支払いが求められる店舗は本当に多く、この明細を把握することが負担の原因だったように思います。

クレジットを99% 前提にすることで基本的に確認不要、現金はおろした時点で決め打ちにすることでざっくりですが手間なしでの管理に倒すべきでした。ということでこれをMoney Fowardで実践して昨年はストレスなしでした。

News Picks

知人が読んでいる内容が面白くいくつか見てましたが、2018年4月ごろから面白い記事が減り、開くことが減ったため解約しました。

Qrio SmarTag

欲しいのはタグとアプリ(スマホ) が離れたときのアラートではなかった感があります。

GPS でのトラッキングがしっかりされていて、通知だけくればいいのだなぁと。

ちょくちょくアプリからみえなくなってたのもふむ。

落として壊れたのでさようなら。

Mamorio

Qrio とほぼ同様です。

こちらも落として壊れたのでさようなら。

Qrio も MAmorio も、普段開かないけど開いた時に位置がロストしていたりログアウトされてるので致命的に感じます。

Google Play Music

Google Home で音楽をお願いするときに、Spotify か Google Play Music なのですが、結局音楽をGoogle Home でお願いしないのでやめました。

使ってたのですが、音楽ストリーミング自体あまり好んでおらず、たいがい辞めています。

Grammarly

ほぼ機能しなくなったのでさようなら。またどこかで使いたいです。

今後使う可能性があるサービス

サービス名 価格 種類 タイプ 用途
Bitwarden 無料 パスワード管理 切り替え マルチプラットフォームでの動作
Kyash 無料 ウオレット 新規 新規 | 割り勘時かな
Lingvist 無料 英語 新規 リハビリ
paypay 無料 QR決済 新規 WeChat的な決済だけのやつ

Bitwarden

2018年12月にみてて切り替えを検討し、2019年早々に切り替え完了しています。

tech.guitarrapc.com

Kyash

割り勘の機会が少ないので、今のところ必要性にかられないのですがあってもいい感じがします。

Lingvist

英語は使わないと忘れるので。 この間、いろいろ出てこなくて焦りました。

paypay

触らずに避けています。どうしようかな?

検討して使わなかったサービス

サービス名 価格 種類 タイプ 用途
Flow $4.79 タスク管理 新規 タスク管理
Microsoft Flow グルー 無料 新規 IFTTT + Integromat で十分
Polca 無料 ファンディング 新規 フレンド間のファンディング
DropBox Paper \1000/month Wiki 新規 Paper だけ使いたい
ScrapBox 無料 Wiki 新規 とっちらかりすぎた

Flow

GitHub Projects で十分なので、今はいらない感。

Microsoft Flow

むやみと複雑なので好みではないです。

Polca

こういうのとりあえず触ってみるんですが、ちょっと身内感すぎて微妙でした。

DropBox Paper

触り心地は良いのですが、Drop Box を避けている (G Suites と重複してる) ので、巻き込まれるのがいやです。 単品サービスなら使うのですが。

Paper を使うたいという欲求に対して高いと感じます。

www.dropbox.com

ScrapBox

Wikiというよりは、感覚的に落書きやホワイトボードのため込みです。

使い勝手いいのですが、まとめたい、関連したものを集約したいというときにちょっと悩ましいというか、考えることになりどうしようかと考えています。

物自体は同時編集できるので理想的なので、また使うかも。

.NET Core の Generic Host で WebJobs を利用する

Generic Host を使った場合でも、これまで .NET Core で書いてきた処理は問題なく組み込むことができます。 最近 Azure WebJobs を Generic Host で使う機会があったので見てみましょう。

※ 社内向けブログの転載なのでシリーズ化します。

目次

TL;DR;

Azure Storage Blob/Queue/Table などの更新を受けて処理を書くために自前でポーリングを書くのはそれなりに大変です。 これを担保するWebJobs という仕組みがすでにあります。これを使ってAzure Functions を書くのと同じ感覚で実装が追加できます。

ここでは.NET Core のコンソールアプリでWeb Jobs を追加して実装する方法を見てみましょう。

事前に読んでおきたい

tech.guitarrapc.com

WebJobs の追加

HostBuilder にWebJobsを追加してみましょう。

nuget パッケージの追加

HostBuilder にWebJobs 処理を追加するため、次のnugetパッケージを追加しておきます。

    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions" Version="3.0.1" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="3.0.2" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Logging.ApplicationInsights" Version="3.0.3" />

HostBuilder を組む

nugetパッケージを追加したことで、これで次のように AzureStorage をトリガーとした処理をかけます。

class Program
{
    static async Task Main(string[] args)
    {
        await new HostBuilder()
            .ConfigureWebJobs(b =>
            {
                // must before ConfigureAppConfiguration
                b.AddAzureStorageCoreServices()
                .AddAzureStorage();
            })
            .ConfigureAppConfiguration((hostContext, configApp) =>
            {
                // Configの追加
                hostContext.HostingEnvironment.EnvironmentName = System.Environment.GetEnvironmentVariable("NETCORE_ENVIRONMENT") ?? "production";
                configApp.SetBasePath(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName));
                configApp.AddCommandLine(args);
                configApp.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json");
            })
            .ConfigureServices(services =>
            {
               // サービス処理のDI
                services.AddSingleton<IFooService, FooService>();
            })
            .ConfigureLogging((context, b) =>
            {
                b.SetMinimumLevel(LogLevel.Debug);

               // Console ロガーの追加
                b.AddConsole();

                // NLog や Log4Net、SeriLog などを追加

                // あるいはApplication Insight の追加
            })
            .RunConsoleAsync()
    }
}

StorageのConnection Strings を設定ファイルに組む

コード上にべた書きするとビルド環境ごとの切り替えや差し替えが面倒ですし、リポジトリにあげるのもまずいでしょう。 Generic Host では、appsettings.json や任意のjson/xml などを読み込むことができます。

もちろんMSI を使えば ConnectionStrings も不要になるので、検討するといいでしょう。

ここでは、appsettings.<環境>.json を読むように組んだので、appsettings.Development.json にコネクションを追加しておきましょう。

{
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  },
  "ConnectionStrings": {
    "AzureWebJobsDashboard": "DefaultEndpointsProtocol=https;AccountName=xxxxxxxx;AccountKey=XXXXXXXXXXXXXX;EndpointSuffix=core.windows.net",
    "AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=xxxxxxxx;AccountKey=XXXXXXXXXXXXXX;EndpointSuffix=core.windows.net"
  }
}

WebJobs のトリガーを書く

感覚的には、AzureFunctions のトリガーを書くのと同じです。

IFooService がDIされているので、これをコンストラクタで受けるFooFunctionクラスを書いてみましょう。

    [ErrorHandler]
    public class FooFunction
    {
        private readonly IFooService service;

        public FooFunction(IFooService service)
        {
            this.service = service;
        }

        [Singleton]
        public async Task QueueTrigger([QueueTrigger("yourqueue")] string queueItem, ILogger log)
        {
            log.LogInformation($"C# Queue trigger function processed: {queueItem}");
            // 何か処理
        }
    }

どうでしょうか? ごくごく普通のAzureFunctions な感じで書けて、Queue のポーリングも不要です。

同様の感覚でcronもかけたりするので、Queue を受けてXXXな処理をするというケースでは、WebJobs をベースすると楽でしょう。

まとめ

Generic Host でも Webjobs を組めるということは、Azure Storage をはじめとした Azure Functionsでやるような処理を自前でハンドルしたいときに、ホスティング環境を問わないということになります。 もし今後 .NET Core + WebJobs をする場合、これがデファクトになるんじゃないでしょうか。

Tips

HostBuilderの初期化時に、注意があります。 必ず ConfigureWebJobsConfigureAppConfiguration の前に置きましょう。初期化できずに死にます。

ok

        await new HostBuilder()
            .ConfigureWebJobs(b =>
            {
                // must before ConfigureAppConfiguration
                b.AddAzureStorageCoreServices()
                .AddAzureStorage();
            })
            .ConfigureAppConfiguration((hostContext, configApp) =>
            {
                // Configの追加
                hostContext.HostingEnvironment.EnvironmentName = System.Environment.GetEnvironmentVariable("NETCORE_ENVIRONMENT") ?? "production";
                configApp.SetBasePath(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName));
                configApp.AddCommandLine(args);
                configApp.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json");
            })

no

        await new HostBuilder()
            .ConfigureAppConfiguration((hostContext, configApp) =>
            {
                // Configの追加
                hostContext.HostingEnvironment.EnvironmentName = System.Environment.GetEnvironmentVariable("NETCORE_ENVIRONMENT") ?? "production";
                configApp.SetBasePath(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName));
                configApp.AddCommandLine(args);
                configApp.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json");
            })
            .ConfigureWebJobs(b =>
            {
                // must before ConfigureAppConfiguration
                b.AddAzureStorageCoreServices()
                .AddAzureStorage();
            })

Ref

Introducing Windows Azure WebJobs WebJobs in Azure with .NET Core 2.1 Azure/azure-webjobs-sdk SanderRossel/netcore-webjob

.NET Core で Generic Host を利用する

ASP.NET Core 2.1 で追加された Generic Host (汎用ホスト) は、non-Web App アプリの作成をASP.NET Core と似た書き心地で提供します。

今後のスタンダードとなる見込みですが、どのようにして Generic Hostを利用するのか見てみましょう。

※ 社内向けブログの転載なのでシリーズ化します。

目次

TL;DR;

ここでは、.NET Core のコンソールアプリをGeneric Hostとして実装する方法を見てみましょう。

Generic Host とは

.NET Core 2.1 で追加された Generic Host (汎用ホスト)は、これからの.NET Coreにおける中心となる実装で、現在のASP.NET Core MVC で用いられている WebHostを参考に作られています。今はWebHost は Web専用、それ以外はGeneric Hostで実装ですが、今後はGeneric Hostに集約されていくロードマップです。

https://docs.microsoft.com/ja-jp/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-2.2

HostBuilder を起点にConfiguration、Logger、DI、各種機能をチェーンで追加、初期化できるのが特徴で、ホスト(アプリ) の起動と有効期間をうまく制御しようという意図が垣間見えます。

WebHost (ASP.NET Core) との違い

IHostBuilderをどのように構成するかはほぼ違いがありません。ASP.NET Core MVC では StartupクラスでConfigure したりAddするのが慣例ですが、中身はそこまで大きく違いません。

違いがでるのは、ASP.NET Core MVC がやっていた暗黙的なインフラ部分です。

ASP.NET Core MVC では、ASPNETCORE_ENVIRONMENT環境変数があると自動的に値を見てDevelopment なら Developmentに切り替え、なかったりするとProduction として扱ってくれました。

しかし、現状の Generic Hostにこの仕組みはなく、.NET Core 3.0 で入る予定のようです。

https://github.com/aspnet/AspNetCore/issues/4150

それまでは次のような処理が必要なので入ると嬉しいですね。

            .ConfigureAppConfiguration((hostContext, configApp) =>
            {
                hostContext.HostingEnvironment.EnvironmentName = System.Environment.GetEnvironmentVariable("NETCORE_ENVIRONMENT") ?? "production";
            })

Generic Host を使う目的

ASP.NET Core MVC と似たものになるように感じますが、Stack Overflow やGitHub Issueを見てるとまだこれからすそ野を広げていく感じとも感じます。特にこのあたりは共通しています。

  • Web UI と Web API を構築するプロセスの統一
  • テストの容易性を考慮したアーキテクチャ
  • 最新のクライアント側フレームワークと開発ワークフローの統合
  • 組み込まれている依存性の注入

Configure 処理の追加

Generic Host で処理を追加する場合は、Microsoft.Extensions.Xxxxx なパッケージを追加することになります。 例えば先ほどの構成なら、次のパッケージになります。

    <PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.2.0" />
    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.0" />
    <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="2.2.0" />

書き心地

WebHost の体験をもとにしているので、書き心地はASP.NET Core (OWIN) とほぼ同じで、定番の実装を組み込むまで一本の流れが出来上がります。

Generic Host を使わずに.NET Core ConsoleでDIする場合は次のように書いていました。*1 個人的にはかなり書き心地が悪くつらい思いをしていました。

gist.github.com

一方で、Generic Host では次のように書けます。

gist.github.com

比べてみると違いは明らかで、Generic Hostの書き心地は処理の追加方法に統一感が出たので好ましいと感じます。

先ほどの Generic Host で組んでみる例では、必要な処理を ConfigureXxxx で追加しました。つまり、そのConfigureXxxx を外せばその処理は追加されません。

特にDIベースでロガーが入るのは確かに便利で、ILoggerで隠蔽されているので、.NET あるなるだったロガーの差し替え面倒だなぁと感じるケースはかなり軽減するように感じています。逆に言うと、こういうのがいらないシンプルな実装では使う強い理由はなく、今まで通り書けばいいとも感じます。

サービス処理をDIする

Generic Host では、実際に呼び出したい処理を次のようにDIできます。

            .ConfigureServices(services =>
            {
               // サービス処理のDI
                services.AddSingleton<IBarService, BarService>();
            })

よくあるDIですが、これを書いておけば実際に処理をしたいクラス(FunctionA とします) で次のように書くと BarService がコンストラクタインジェクションされます。

    public class FunctionA
    {
        private readonly IBarService service;
        public FunctionA(IBarService service)
        {
            this.service = service;
        }

        public async Task HogeAsync()
        {
            // this.service を使って何か処理
        }
    }

まとめ

DI とかテンプレート的に構成していくことを含めて、なんとなくMicrosoft はこの書き方をオシススメテキソウな気配がします。 さくっと動かす系じゃない場合は、こっちの書き方は広まりそうかもかも?

Tips

IHostBuilder.RunConsoleAsync, IHostBuilder.Start, Host.StartAsync, IHost.RunAsync の違い

ref Stack Overflow

https://stackoverflow.com/questions/52413002/whats-the-difference-between-these-ways-to-start-run-a-generic-host-in-asp-net/52413414

ASP.NET Core 3.0ではGenericHostから構成になるかも?

ASP.NET Team のTweetによると、ASP.NET Core 3.0 では、Generic HostにConfigureWebHostDefaults で構成できるようです。

想定通りの書き方ですね。

Ref

.NET での汎用ホスト

アプリケーション停止処理の実装例

*1:まったく同じ処理ではありません

パスワード管理をTeamsId から Bitwarden に移行した

今のパスワード管理に小さな不満があるので長年次のパスワード管理をさがしていたのですが、Bitwardenが今ある全ての望みをかなえてくれました。

bitwarden.com

今回、TeamsId から Bitwarden に全面移行したのでその移行についてメモをしておきます。

目次

今まで使ってきたパスワード管理

個人のパスワード管理は、2015年3月まではMeldiumを使ってましたが Discontinueが発表されてからはTeamsId を使っていました。

www.teamsid.com

TeamsId を見返す

TeamsId (運営はSplashData) の良いと感じてる点は次の通りで多くの面で満足していました価格は$3user/month です。

  • Web上で管理される
  • 少人数でのパスワード共有も管理が容易
  • Chrome拡張があり入力が容易
  • iPhoneアプリもある
  • Googleログインで統制できる
  • G Suite の9 squareに表示できる

一方で、3年つかっていると細かい不満がたまってきました。

  • Web管理画面で描画が遅い
  • 反映までの3-5secの間に他のレコードを触ると意図と異なったレコードを編集することがある
  • 利用者の声が他に全然おらずサービス終わることありそうという気配
  • 日本語サービス名にすると全レコードが表示されなくなる
  • サポートの対応が技術的に掘り下げ弱い

特に日本語サービス名にした時に表示されなくなるのはかなり焦るものがあり、半年前に3度目が発生し改善の見込みが見えないので乗り換えを考えていました。

機能数よりは、少人数での共有のしやすさ、使い勝手の良さを評価しています。

www.itqlick.com

検討したサービス

いくつか検討し、仕事でも使ったりすることで探ってきました。

1Password、LastPass、KeePass、Zohoいずれも使い勝手と価格と少人数での共有の面からあまり満足できるものはなく悩みが多かったです。ちょうど1Password Teams が発表されたタイミングもあり触っていたのですが結局TeamsId でいいやとなっていました。

1password.com

www.lastpass.com

ここ1年は、Dashlane がワンチャンかと思っていましたが、決定的な理由がないのでペンディングしていました。 お高めですし。

無料パスワードマネージャー | Dashlane

やりたいこととのバランス、使い勝手はそこまで変わらないなら乗り換えないというのは自分の行動を見ていてもそういうものかなぁと思います。個人的には、クラウド管理でもいいと思っており、ローカル/自前クラウド管理は避けたいところです。(必要ならやりますが必然性は感じていない)

Bitwarden

Bitwardenはたびたび見ていましたが、あまり興味がでなかったものの2018年末に改めて良いという話を聞いて試しました。

bitwarden.com

オープンソースなのは結構良いと思っているのですが、妙な処理があっても全コードは見てられないので気付けるかなぁと思いつつ。しかし、最悪コード読めるのはいいと感じています。 それでも不安なら kubernetes クラスタ組めばいいと思います。

2FA して2人で共有できればいいので、PersonalもBusinessも無料で十分満たせそうです。 $5 + $2(user) でも十分安いので、課金しています。

YubiKeyに移行も進めているので、Personal だけ Premium もあり得ます。

ブラウザ拡張、スマホアプリ共に十分使い勝手は良く感じます。

Bitwardenの使い方

これは多くの記事があるのであえて書くことはないと感じるのでそちらをどうぞ。

tips4life.me

excesssecurity.com

TeamsIdからの移行

ぱっと使ってみた感じはよさそうなので、今のTeamsId のレコードを移行します。残念ながら TeamsId はありません、同一会社のSpashIdがあるのに。

試しにTeamsIdのエキスポートcsvをそのまま取り込んでみると、すさまじいことになったので推奨できません。Bitwarden のフォルダがまともに消せなくていやになりそうになったのは内緒です。

仕方ないので、Bitwarden の Generic Importフォーマットで取り込みを試みます。ドキュメントではcsv とあります。

しかし、Fields は入れ子のレコードでExportしてみると大変なことになるのが分かります。

folder,favorite,type,name,notes,fields,login_uri,login_username,login_password,login_totp
Social,1,login,Twitter,,,twitter.com,me@example.com,password123,
,,login,My Bank,Bank PIN is 1234,"PIN: 1234
Question 1: Blue",https://www.wellsfargo.com/home.jhtml,john.smith,password123456,
,,login,EVGA,,,https://www.evga.com/support/login.asp,hello@bitwarden.com,fakepassword,TOTPSEED123
,,note,My Note,"This is a secure note.

Notes can span multiple lines.",,,,,

Csvは行を跨ぐ、入れ子データの取り扱いが面倒なので避けたいところです。 ドキュメントにはありませんが、jsonでインポートできます。

{
  "folders": [],
  "items": [
    {
      "id": "2fb7acc2-41dc-4659-9dc3-a9d301283339",
      "organizationId": null,
      "folderId": null,
      "type": 3,
      "name": "card sample",
      "notes": "note line 1",
      "favorite": false,
      "card": {
        "cardholderName": "Jane Doe",
        "brand": "Visa",
        "number": "123456778",
        "expMonth": "1",
        "expYear": "2021",
        "code": "1234"
      },
      "collectionIds": null
    },
    {
      "id": "5a9eb019-e092-43ae-ac30-a9d301279b2a",
      "organizationId": null,
      "folderId": null,
      "type": 1,
      "name": "LoginSample",
      "notes": "note line 1\nnote line 2",
      "favorite": false,
      "login": {
        "uris": [
          {
            "match": null,
            "uri": "https://example.com"
          }
        ],
        "username": "username",
        "password": "password",
        "totp": "authenticator key"
      },
      "collectionIds": null
    },
    {
      "id": "254a958e-dc04-4330-aa05-a9d3012801c0",
      "organizationId": null,
      "folderId": null,
      "type": 1,
      "name": "LoginSample2",
      "notes": null,
      "favorite": false,
      "fields": [
        {
          "name": "custom",
          "value": "value",
          "type": 0
        },
        {
          "name": "custom hiden",
          "value": "value",
          "type": 1
        },
        {
          "name": "custom boolean",
          "value": "true",
          "type": 2
        }
      ],
      "login": {
        "uris": [
          {
            "match": null,
            "uri": "https://example.com"
          }
        ],
        "username": "username",
        "password": "password",
        "totp": "authenticaticator key"
      },
      "collectionIds": null
    }
  ]
}

こちらは素直なフォーマットで型変換も容易です。

ということで、TeamsId Csv -> Bitwarden Json への変換を書きましょう。

下準備

TeamsId は、Export結果にタグは含みません。あくまでもフィールドとしてすべて出てくるので、Bitwardenに取り込みたい項目はFieldに割り当てておきます。

私の場合は、Bitwarden Personalに取り込む場合は、TeamsId でGroupフィールドを作って割り当てたいグループ名をいれました。

実装

パーサーライブラリとして公開しました。*1

github.com

TeamsId のPersonalデータ -> Bitwarden の Personal データ変換、TeamsId の Organizationデータ -> Bitwarden の Organizationデータの両方に対応しています。

Dockerfile、.NET Coreも対応済みです。使い方はSampleを用意したのでどうぞ。

github.com

これに限らずですが、作業はLinqPad でやっていたので C# です。こういうパーサーを書くとき、LinqPadは非常に便利です。

変換結果を見ながら書き換えできます。*2

実装のポイントだけメモしておきます。

TeamsId Csv の解析

C# で Csv パーサーを探していくつか試しましたが、結果 CsvHealper が最も使いやすかったです。AutoMapperもあり、改行レコードがあっても素直に処理してくれます。.NETStandard も対応しています。

github.com

joshclose.github.io

TeamsId はカスタムフィールドがあるとどんどんExport時のカラムが増えるので、40フィールドのマッピングをするのはつらいのでAutoMapperはほしいところです。さて、利用に際してはStreamで読み込みが必要なので軽いラッパーだけ用意しました。

public class CsvParser
{
    private readonly string path;

    public CsvParser(string path)
    {
        this.path = path;
    }

    public T[] Parse<T>() where T : class
    {
        using (var reader = new StreamReader(path))
        using (var csv = new CsvReader(reader))
        {
            csv.Configuration.MissingFieldFound = null;
            var records = csv.GetRecords<T>();
            return records.ToArray();
        }
    }
}

TeamsId のフィールド解析、値取得

TeamsId のExport Csv は、次のフォーマットになります。

description,note,Field0,Type0,Value0,Field1,Type1,Value1,Field2,Type2,Value2,Field3,Type3,Value3,Field4,Type4,Value4,Field5,Type5,Value5,Field6,Type6,Value6,Field7,Type7,Value7,Field8,Type8,Value8,Field9,Type9,Value9,Field10,Type10,Value10,Field11,Type11,Value11,Field12,Type12,Value12,Field13,Type13,Value13,Field14,Type14,Value14,Field15,Type15,Value15

description、note以外は、レコード1つあたり FieldN, TypeN, ValueN の連続です。そのため、今回はリフレクションしてフィールドの値から型にマッピングします。

C# 7 から使えるswitch でのwhen 句を用いることで、強力に条件分岐ができるので、フィールドを特定のプロパティに割り当てたい時に便利でした。

    private FieldRecord ParseFieldRecord(PropertyInfo[] props, Type t, TeamsIdDefinition source)
    {
        var fieldRecord = new FieldRecord();
        // get FieldXX properties via reflection
        var fieldRecords = props
            .Where(x => Match(x.Name, @"Field\d+"))
            .Where(x => GetPropertyValue(source, t, x.Name) != "")
            .Select(x => (key: GetPropertyValue(source, t, x.Name), value: GetPropertyValue(source, t, x.Name.Replace("Field", "Value"))))
            .ToArray();

        // get field's value and categolize each via field name regex pattern
        var secureMemoList = new List<(string, string)>();
        var memoList = new List<(string, string)>();
        foreach (var record in fieldRecords)
        {
            switch (record.key)
            {
                case var _ when Match(record.key, "url") && fieldRecord.Url == null:
                    fieldRecord.Url = record.value;
                    break;
                case var _ when Match(record.key, "email|e-mail") && fieldRecord.Email == null:
                    fieldRecord.Email = record.value;
                    break;
                case var _ when Match(record.key, "username") && fieldRecord.UserName == null:
                    fieldRecord.UserName = record.value;
                    break;
                case var _ when Match(record.key, "password") && fieldRecord.Password == null:
                    fieldRecord.Password = record.value;
                    break;
                case var _ when Match(record.key, "pass|access|secret|pin|token"):
                    secureMemoList.Add((record.key, record.value));
                    break;
                case var _ when Match(record.key, "group"):
                    fieldRecord.Group = record.value;
                    break;
                default:
                    memoList.Add((record.key, record.value));
                    break;
            }
        }
        fieldRecord.Fields = memoList.ToArray();
        fieldRecord.SecureFields = secureMemoList.ToArray();
        return fieldRecord;
    }

    private bool Match(string text, string pattern)
    {
        return Regex.IsMatch(text, pattern, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
    }

    private string GetPropertyValue(TeamsIdDefinition record, Type t, string propertyField)
    {
        return (string)t.GetProperty(propertyField).GetValue(record);
    }

手元の300レコードは素直にインポートできたので、だいたいのケースではつかえそうです。

変換時の注意

Bitwarden Personal では FolderId が GUIDで定義、マッピングしておかないと死にます。(実装は対応済みで、Sampleに定義例があります。)

Bitwarden Organizationでは、OrganizationIdとCollectionIds を定義してマッピングしましょう。(実装は対応済みで、Sampleに定義例があります。)

インポート

変換した結果はJsonで出力されるのでBitwarden で取り込めば完了です!

まとめ

幸せな感じなので少し様子を見てみましょう。

*1:TeamsIdの利用者数からして需要はなさそう

*2:黒字で塗りつぶしたらさっぱりわからない

はてなブログを https 対応するためにmixed content を検知する MixedContentCheckerを作った

前回の記事でhttps 化の前段階として、はてなブログの全URLを取得しました。

tech.guitarrapc.com

https化を有効にすると、mixed content が出るようになるので有効にします。

あとは、https 化したページに httpコンテンツが混じっている時に起こる、mixed content 警告一覧を取得して直してみましょう。

なお、直すのは手作業です。

目次

TL;DR;

dockerでchrome ヘッドレスをSeleniumで動かして、logからmixed content があるか検知してログ出力する。

検知したページは、3パターンの修正のいずれかをちまちまかけていく。

環境

今回は、PowerShell と C# で実装しました。golang は実装中。

いずれの環境も、docker で ubuntu 18.04 環境で実行します。

  • PowerShell : mcr.microsoft.com/powershell:ubuntu-xenial
  • C# : microsoft/dotnet:2.2-runtime-bionic
  • golang : 未実装

処理の流れ

どの言語も変わらず、chrome のログから mixed content を取得します。

  • URLにchrome headlessで自動アクセスする
  • chrome driver を初期化
  • 指定したURLにアクセス
  • ログから mixed content をフィルター
  • 該当ログがあれば markdown テーブルフォーマットで出力

実装

リポジトリおいておきます。

github.com

実行方法は、READMEをみてください。

MixedContentChecker/README.md at master · guitarrapc/MixedContentChecker · GitHub

ローカル実行もできますが、Dockerで動かすことで、手元にSeleniumやChromeヘッドレスドライバーを用意したり、環境初期化で困ったりすることを割けます。

こういうのをローカルで動かす意味はあんまりないので、Dockerで動かすのがいいでしょう。

PowerShell

Dockerfileです。特に何も気にせず、粛々とchrome headless + chrome driver + selenium をいれます。

gist.github.com

前回のサイトマップから全URLを取得するスクリプトを呼び出しつつ、Chrome Driver + Selenium処理を書きます。

gist.github.com

ポイントは、chrome driverに渡した引数 "--no-sandbox" です。

C# では起こらないのですが、PowerShellからChrome ヘッドレスを実行するときは、--no-sandbox がないと実行できないようです。(はまった)

CSharp

Dockerfileです。先ほどと違い、C# のビルド時にselenium + chrome driverは入るので、chrome driverだけ入れます。

gist.github.com

先ほどのPowerShellと異なり、Parallel.Foreachdによる並列処理を用意しました。

gist.github.com

記事が480以上あるため、1つ1つにアクセスしていると終わりません。単純に1ページ5秒としても、2400sec (40min) かかります。実際は、OneDrive の写真埋め込みページなどで60secかかったりしていたのでもっとです。

おおよそCPUコア数で並列がいいのですが、Docker内部への割り当てしだいです。今回は、15並列で不安定になり10並列で安定したのでデフォルト値にしています。Docker実行時にパラーメータを変えたいので、環境変数から値を渡せるようにしています。*1

10並列で、10分ぐらいでおわるようになったのでだいたいこれぐらい感があります。

あとは、先ほど同様にSeleniumでchrome headlessを動かすだけです。

処理の全体はリポジトリをみてください。

MixedContentChecker/Program.cs at master · guitarrapc/MixedContentChecker · GitHub

Golang

[TBD]

修正

3つのパターンで修正します。

  • httpをhttps にする
  • 存在しないURLを消す
  • はてなフォトがembedded記法なら記事の保存しなおし
  • Google Web Master をhttpsで取り直し

http -httpsへの置き換え

心を無にしてやりました。 はてなブログにAPIで取得、保存しなおしはちょっと面倒感があります。

記事を全コピー、vscodeで置換、貼り付け直して保存です。サシミタンポポ

また、デザインページもhttp -> https が必要です。私の場合は、Google フォントと外部cssなどでしたが、ついでにptengine やzenback 当りが邪魔をしていたので外しました。zenbackははてなブログで関連記事機能があるので不要になってました。

github.com

存在しないURLを消す

特にOneDrive のimage埋め込みと2013ぐらいの古い記事でした。

OneDriveは、生imageがOneDriveにあったものははてなフォトにいったんおいています。OneDriveのimage埋め込みは、以前コメントでも指摘受けていたので、もう二度とつかわないでしょう。

存在しないURLは404で取得できるので消しました。

はてなフォト

はてなフォトは、生urlでなくはてな記法によるid埋め込みの場合、記事を保存しなおすことでhttps化されます。 粛々と記事を保存しなおします。サシミタンポポ

help.hatenablog.com

結果

1122 + 296 + 70 + 22 + 187 なので、1697件だったようです。

sample/logs においておきました。

MixedContentChecker/samples/logs at master · guitarrapc/MixedContentChecker · GitHub

手作業なら死んでました。修正は手作業なのでもうやりたくないです。

あと、セキュリティ警告でページ表示できない状況も直ったようです、すいませんでした。

まとめ

golang 書いてから上げようと思いましたが、やってすでに1週間立つのでとりあえず記事にしていくスタイルで。

*1:Docker実行だと、引数より環境変数の方が素直で扱いやすくて好みです。