2020年7月13日月曜日

私の「de:code夏まつり #3」[完全版]

2020年7月8日(水)にオンラインで開催された「de:code夏まつり ~よりコミュニティを楽しくするためのコミュニティのすすめ #3」にて、タイトル「AzureとSDKとGoと私」で発表をさせて頂きました。


以下、自分目線ながらそこに至るまでの経緯を記録として。

de:code 2020

今年もマイクロソフト主催のITエンジニア向け技術カンファレンス「de:code 2020」が開催されました。しかしながら世界的なコロナウィルスの流行により今年はデジタルイベントとして6月17日(水)から7月17日(金)までの1ヶ月間のオンライン開催となりました。

de:code夏まつり

日本では、コミュニティサイドから「de:code 2020」を盛り上げたり楽しんだりしようとMicrosoft Open Tech Night主催でコミュニティイベント「de:code夏まつり」が6月に2回、7月に1回開催されました。

なぜ私が発表することになったのか?

2020年6月24日(水)に第2回の「de:code夏まつり」が開催されました。

その中で「MS Techクイズまつり」があり、第5問で「Azure Functions(3.x系のランタイム)でサポートされていない言語はどれ?」という問題が出題され、その答えは「Go」でした。

私的には「おぉ、マイクロソフトさんのイベントで"Go"の二文字が!」と喜んだのも束の間、Goがサポートされていないことへの悔しさが湧き上がりました。


2014年にサティア ナデラ氏がマイクロソフトCEOに就任して以降、マイクロソフトのOSSへの貢献や対応には目を見張るものがあり、Goに関してもAzureでの対応が多くなりました。

  • Azure App Service:GoでのWebアプリ作成
  • Azure SDK:Goのサポート
  • Azure Kubernetes Service:Go製OSSの採用(Docker、Kubernetes、など)

しかしながら、他のプログラミング言語と比べて「対応サービスの少なさ」「正式リリースまでの長さ」「公式サイトへ掲載されるまでの遅さ」など、Goには出遅れ感があります。

原因のひとつとが「Goの開発者」および「Goで使うユーザー」が少ないという現実です。

私は「マイクロソフトにはGoもしっかりサポートして欲しい!」と願っており、マイクロソフトとGoの繋がりをもっと知ってもらおうとツイートしたり発表したりしていました。

2018年からは、その思いは大きくなり、できるものなら「de:code」でGoを発表したい!と思うようになりました。2020年も「MS Techクイズまつり」のAzure FunctionsでのGoの未サポートを見て思わず「やはりde:codeでGoを発表したい!」とツイートしました。

その日の夜遅く、タマコナさん(@tamagawaconan)と「de:codeでの発表、出来るものなら」「口にしたことは、実現しますよ。」と話をしていました。


その直後、突然ちょまど(@chomado)さんから「de:code夏まつりでAzure SDK for Goについて発表できませんか?」とのお声掛けを頂きました。


この時は「会話をしていたすぐ後に」「ちょまどさんから」「de:code関連イベントで発表」という予想だにしなかった流れが一気に押し寄せて、何が何だかで驚きまくり動揺しまくり。


私はちょまどさんをフォローしていませんでしたが「こんな奇跡は二度と来ない!」と神速でフォロー&快諾して「de:codeでの発表」へ導いて頂くことになりました。

 
私とAzure SDK for Go

マイクロソフトの有識者を認定する制度「MVP アワード」において、唯一Goが絡める部分ということもあり、Azure SDK for Goは2016年のv8のころからウォッチしていました。

2020年に入って本格的にやって行こうと、ブログやQiitaに記事を投稿したり、ツイートしたり、GitHubでドキュメントを日本語化したりコントリビュートしたりしています。

 
発表スライドの作成

イベントにはITエンジニア以外の方々も多く参加されるため、Azure SDK for Goはニッチ過ぎると考え、Goの紹介やAzureやSDKや私のことを交えて、お伝えしたいことをまとめることにしました。

発表時間は10分ということで、スライド作成後に何度かリハーサルを行なったのですが、12分の壁が超えられませんでした。発表スライドは後から公開して見て頂くことが出来るため、内容の削除は行わず、当日は少し早口で詳細な部分を読み飛ばして進めることにしました。


2日間のリハーサル

2020年7月6日(月)、7日(火)の2日間、オンラインにて全体リハーサルが行われました。

1日目は、主に各発表者のスライドや音声の確認、YouTube Live画面の調整などが行われました。今回はMicrosoft Teamsを使っての接続で、私は使うのが初めてでしたので、そちらの操作の確認なども行いました。

テキストで少しやりとりをしていたちょまどさんを含めて、皆さまと顔合わせをするのは初めてで、画面越しに見ても皆さんの「つよつよさ」が伝わってきました。緊張しつつも会話できるところでは声を出したり笑ったりするなどして、少しでも早く馴染めるように心がけました。


確認事項は1日目で完了していたのですが、新参者のことを少しでも見知りしておいて頂けた方が良いかと思い、2日目も参加しました。2日目は「MS Techクイズ」のテスト運用も行われ、準備や技術や担当者の方々のすごさを垣間見れました。


進行役のおだしょー(@MS_odasho)さんからは、顔見知りの皆さんと分け隔てなく話かけて頂き、スムーズに会話をしたり確認事項をこなす事ができました。

天の声役(?)のRie(@Rie_Moriguchi)さんには、内輪のハッシュタグ(?)の存在が知られていてビックリしました。


Tadaさんチームの皆さま、配信の準備、調整、対応、お疲れ様でした!m(__)m

いざ、開催!

2020年7月8日(水)、ついに開催日となりました。

雰囲気作りの意味合いも含めて、関係者は開始1時間前に集合しました。

この時、おだしょーさんによるSpecial GuestのAI「りんな」の紹介テストが行われたのですが、ギリギリまで音声を出せずに焦りまくっていたおだしょーさんを皆さんでフォローすることで、リラックスできたり一体感が生まれたような気がしました。

オープニングでは、そんな「りんな」の音声出力に失敗するも、二度目に成功。


続いての「MS Techクイズ」では150人以上の方々が同時参加されて、ワイワイとお祭り気分が味わえました。

 
いざ、発表!

マイクロソフトな皆様にGoを知って使ってサポートして頂きたく、日本のGopherを代表する勢いで挑ませて頂きました。



元々発表スライドのボリュームがあったのと、私が発表する時点で時間が押していたため、拍車のかかった早口になってしまったことはGo了承ください。多少なりとも緊張していたのも理由のひとつかも。


発表後にJourneyman(@beajourneyman)さんがまとめられたTogetterにて自分の発表について確認させて頂きました。


個別で前向きなツイートを頂いたり、概ね良い方向に捉えて頂けたようで、ツイートして頂いた皆さま、ありがとうGoざいます!q@w@p



日本マイクロソフト公式TwitterのひとつであるMicrosoft Tech(@msdevjp)さんに「Go」をツイートして頂いたことは「Microsoft x Go」の大切な一歩を踏み出せた大事な証となりました。


皆さんの発表

改めて発表者の皆さんを見てみますと、MVPの方、コミュニティの関係者、社長さんなど「つよつよ」で「すごすご」の方々ばかり。お目当ての発表を見られた皆さんは、きっとご満足されたことでしょう!

 
クロージング

発表公募の第4回の開催告知のサプライズに、おだしょーさんの「じゃあな!」にはコミュニティ愛を感じました。

 
初夏の夜の夢...

携わってくださった皆さま、感謝ありがとう!引き続き、マイクロソフトにGoをサポートして頂くべく、Azure SDK for Goを中心にまったりと活動して参ります。

サンキューコミュニティ!フォーエバーコミュニティ!q@w@p

P.S. 個人的には、ちょまどさんから面と向かって「岩田プロ」と呼ばれる日が来るとは夢にも思っていませんでしたね...q@w@p その後、皆さんからも呼んで頂けました。

2020年7月10日金曜日

Azure Functions for Goの歴史

Azure Functionsは、関数単位で処理を実行するAzureサービスです。


2020年6月現在、Azure Functionsは

  • C#, JavaScript, F#, Java, PowerShell, Python, TypeScript

を正式サポートしていますが、残念ながらココにGoはありません。


Azure/azure-sdk-for-goにAzure Functions for Goに関するイシューがあります。


このイシューを読み進めると、Azure Functions for Goの歴史を垣間見ることができます。

2017年12月、当初、Private Preview版への招待が開始されました。


2018年頃は、開発が停滞したり再開したりしました。

2019年10月、開発担当者がマイクロソフト社を辞められていました。

2019年12月、別の実装方法が模索されることになりました。

2020年5月、その答えが「Azure Functions custom handlers」でした。


私もリアルタイムにその流れを目撃していたのですが、azure.microsoft.comに一瞬ブログ記事が掲載されたのですが、すぐに取り消されてしまいました。

現在「Azure Functions custom handlers」はPublic Previewであり、正式リリースされた暁には、公式サイトにも再度アナウンス記事が掲載されることでしょう。

Goなサンプル

2020年6月13日土曜日

GitHubのAzure/go-autorestのREADME(v14.1.1)を日本語訳

今後の理解のために、GitHubのAzure/go-autorestのREADME(v14.1.1)を日本語訳しました。


go-autorestは、GoでAzureへアクセスする際の認証や、Goの基本型を変換するヘルパーを利用する際になどに使用します。

2020年6月7日日曜日

Azure SDK for GoのGitHubのREADMEの日本語訳をv43.0.0に最新化

Azure SDK for GoのGitHubのREADMEを日本語訳していたものを2020年6月2日にリリースされたv43.0.0の内容で最新化しました。


初回の v8.0.1-beta 以来、今回で2回目の更新です。

この活動には元ネタがあって、以前 @ayatokura さんがVSCodeがリリースされる都度、リリースノートの日本語訳をGitHubに公開されていたのを見て「これはよいかも!」と私も始めてみたものです。

今回更新した理由は、次の通りです。

  • 私自身がこれからAzure SDK for Goを触り始めるため、最新の内容を知っておきたかった。
  • Google検索にて「Azure SDK for Go」で検索すると上位に表示され、情報が古いままだとダメだと思ったため。(2020年6月7日時点:少なくとも私の環境からは公式の次に表示されています)
2020年6月7日にGoogle検索で「Azure SDK for Go」を検索した結果のスクリーンショット


引き続き、まったりと。

2020年5月4日月曜日

Azure SDK for Goでの認証方式

「Azure SDK for Go documentation」サイトの「Authentication methods in the Azure SDK for Go」ページを読み進めました。

本セクションでは、Azure SDK for Goを使ってAzureで認証するための形式と方式が紹介されています。

ここでは、対象ページの内容をベースに...というか結果的にGoogle翻訳による日本語翻訳状態となり、私なりの言い回しを加えています。ちなみに、"type"は"形式"に、"method"は"方式"に、日本語訳しています。

利用可能な認証の形式と方式


Azure SDK for Goは複数の資格情報セットと認証形式を提供し、認証形式は複数の認証方式(認証情報をSDKに取り込む操作)で呼び出せます。

提供されている認証形式は、次の通りです。

証明書ベース形式


クライアント資格情報形式

  • アプリケーションに構成済みのサービスプリンシパルを組み込んで認証します。詳細については「Create a service principal with Azure CLI」を参照してください。

AzureリソースのマネージドID形式

  • アプリケーションは、Azure ADが自動的にAzureサービスに提供したマネージドIDを使ってAzure AD認証をサポートするAzureサービスを認証します。詳細は「Managed identities for Azure resources」を参照してください。

デバイストークン形式

  • 多要素認証を有効にしたユーザーのデバイスにトークンを渡し、WebブラウザーでMicrosoftサインインにアクセスしてAzure ADアカウントで認証します。詳細は「Use device token authentication」を参照してください。

ユーザー名/パスワード形式

  • ユーザー名とパスワードで認証します。多要素認証が無効状態でのAzure ADサインインのため、セキュリティ面で不安があります。

【重要】クライアント資格情報形式以外の認証形式を使用する場合、アプリケーションはAzure ADに登録されている必要があります。詳細は「Integrating applications with Azure Active Directory」を参照してください。

【注意】特別な要件がない限り、ユーザー名/パスワード形式は避けてください。ユーザーベースのサインインが必要な場合はデバイストークン形式で対応してください。

これらの認証形式は、さまざまな認証方式で利用できます。

環境ベース方式

  • シェルの環境変数から資格情報を読み取ります。

ファイルベース方式

  • サービスプリンシパルの資格情報などをファイルから読み込みます。

クライアントベース方式

  • コード内のオブジェクトを使用し、プログラムの実行中に資格情報を提供する必要があります。

デバイストークン方式

  • ユーザーはトークンを使用してWebブラウザーからインタラクティブにサインインする必要があります。

すべての認証機能と認証方式は、github.com/Azure/go-autorest/autorest/azure/authパッケージで利用できます。

【注意】特別な要件がない限り、クライアントベース方式は避けてください。認証情報をハードコード化する傾向にあり、認証要件が変更された場合のSDKリリースで機能しなくなる可能性があります。

環境ベース方式を使用する


アプリケーションを実行する前にシェルの環境変数を構成し、実行時にこれらの環境変数を読み取り、Azureで認証します。

環境ベース方式は、デバイストークン形式を除くすべての認証形式をサポートしており、次の順序で評価されます。

  • クライアント資格情報形式
  • 証明書ベース形式
  • ユーザー名/パスワード形式
  • AzureリソースのマネージドID形式

認証形式に未設定の値があるか拒否された場合、SDKは自動的に次の認証形式を試行し、形式がなくなるとエラーを返します。

次の表は、環境ベース形式でサポートされる認証形式ごとに設定する必要がある環境変数の詳細を示しています。

クライアント資格情報形式

        AZURE_TENANT_ID
                サービスプリンシパルが属するActive DirectoryテナントのID。
        AZURE_CLIENT_ID
                サービスプリンシパルの名前またはID。
        AZURE_CLIENT_SECRET
                サービスプリンシパルに関連付けられているシークレット。

証明書ベース形式

        AZURE_TENANT_ID
                証明書が登録されているActive DirectoryテナントのID。
        AZURE_CLIENT_ID
                証明書に関連付けられているアプリケーションクライアントID。
        AZURE_CERTIFICATE_PATH
                クライアント証明書ファイルへのパス。
        AZURE_CERTIFICATE_PASSWORD
                クライアント証明書のパスワード。

ユーザー名/パスワード形式

        AZURE_TENANT_ID
                ユーザーが属するActive DirectoryテナントのID。
        AZURE_CLIENT_ID
                アプリケーションクライアントID。
        AZURE_USERNAME
                サインインに使用するユーザー名。
        AZURE_PASSWORD
                サインインに使用するパスワード。

AzureリソースのマネージドID形式

        マネージドID形式に資格情報は必要ありません。
        アプリケーションは、マネージドIDを使用するように構成
        されたAzureリソースで実行されている必要があります。
        詳細については「Managed identities for Azure resources」を
        参照してください。

既定のAzureパブリッククラウド以外のクラウドまたは管理エンドポイントに接続するには、次の環境変数を設定します。例えば、Azure Stack、別の地域のクラウド、または従来のデプロイモデルを使用している場合です。

        AZURE_ENVIRONMENT
                接続するクラウド環境の名前。
        AZURE_AD_RESOURCE
                接続時に使用するActive DirectoryリソースID。
                管理エンドポイントへのURIとして使用します。

Goコードでは、NewAuthorizerFromEnvironment関数を呼び出してオーソライザーオブジェクトを取得します。このオブジェクトは、クライアントのAuthorizerプロパティで設定され、Azureへのアクセスを許可します。

        import "github.com/Azure/go-autorest/autorest/azure/auth"
        authorizer, err := auth.NewAuthorizerFromEnvironment()

Azure Stackでの認証


Azure Stackで認証するには、次の環境変数を設定する必要があります。

        AZURE_AD_ENDPOINT
                Active Directoryエンドポイント。
        AZURE_AD_RESOURCE
                Active DirectoryリソースID。

これらの環境変数は、Azure Stackメタデータ情報から取得できます。メタデータを取得するには、Azure Stack環境でWebブラウザーを開き、次のURLを使用します:(ResourceManagerURL)/metadata/endpoints?api-version=1.0

ResourceManagerURLは、Azure Stackデプロイメントのリージョン名、マシン名、および外部の完全修飾ドメイン名(FQDN)によって異なります。

        開発キット
                https://management.local.azurestack.external/
        統合システム
                https://management.(region).ext-(machine-name).(FQDN)

Azure StackでAzure SDK for Goを使用する方法の詳細については「Use API version profiles with Go in Azure Stack」を参照してください。

ファイルベース方式を使用する


ファイルベース方式では、Azure CLIによって生成されたファイル形式を使用します。--sdk-authパラメータを使用して新しいサービスプリンシパルを作成するときに、このファイルを簡単に作成できます。CLIはstdoutに出力するため、出力をファイルにリダイレクトします。

        az ad sp create-for-rbac --sdk-auth > azure.auth

AZURE_AUTH_LOCATION環境変数を、許可ファイルが配置されている場所に設定します。この環境変数はアプリケーションによって読み取られ、その中の資格情報が解析されます。実行時に認証ファイルを選択する必要がある場合は、os.Setenvhttps://golang.org/pkg/os/#Setenv関数を使用してプログラムの環境を操作します。

認証情報を読み込むには、NewAuthorizerFromFile関数を呼び出します。環境ベース方式とは異なり、ファイルベース方式にはリソースエンドポイントが必要です。

        import "github.com/Azure/go-autorest/autorest/azure/auth"
        authorizer, err := NewAuthorizerFromFile(azure.PublicCloud.ResourceManagerEndpoint)

サービスプリンシパルの使用とアクセス許可の管理の詳細については「Create a service principal with Azure CLI」を参照してください。

デバイストークン方式を使用する


ユーザーにインタラクティブにサインインさせる場合は、デバイストークン方式が最適です。ユーザーにトークンを渡してMicrosoftサインインサイトに貼り付け、Azure Active Directory(AAD)アカウントで認証します。この認証方式は、標準のユーザー名/パスワード方式とは異なり、多要素認証が有効になっているアカウントをサポートします。

NewDeviceFlowConfig関数を使用してDeviceFlowConfigオーソライザーを作成します。結果のオブジェクトでAuthorizerを呼び出して、認証プロセスを開始します。デバイスフロー認証は、認証フロー全体が完了するまでプログラムの実行をブロックします。

        import "github.com/Azure/go-autorest/autorest/azure/auth"
        deviceConfig := auth.NewDeviceFlowConfig(applicationID, tenantID)
        authorizer, err := deviceConfig.Authorizer()

認証クライアントを使用する


特定の形式の認証が必要で、プログラムがユーザーからの認証情報を読み込んで処理を進める場合は、auth.AuthorizerConfigインターフェースに準拠する任意のクライアントを使用できます。次の場合は、このインターフェースを実装する型を使用します。

  • インタラクティブなプログラムを書く
  • 専用の構成ファイルを使用する
  • 用意されている認証形式では対応できない要件である

【警告】Azure資格情報をアプリケーションにハードコードしないでください。アプリケーションのバイナリーにシークレットを入れると、アプリケーションが実行されているかどうかにかかわらず、攻撃者がシークレットを簡単に抽出できます。これにより、資格情報が承認されているすべてのAzureリソースが危険にさらされます。

次の表に、AuthorizerConfigインターフェースに準拠するSDKの形式を示します。

        証明書ベース形式
                ClientCertificateConfig
        クライアント資格情報形式
                ClientCredentialsConfig
        AzureリソースのマネージドID形式
                MSIConfig
        ユーザー名/パスワード形式
                UsernamePasswordConfig

関連付けられたNew関数を使用してオーセンティケーターを作成し、結果のオブジェクトに対してAuthorizeを呼び出して認証します。たとえば、証明書ベース形式を使用するには次のようになります:

        import "github.com/Azure/go-autorest/autorest/azure/auth"
        certificateAuthorizer := auth.NewClientCertificateConfig(certificatePath, certificatePassword, clientID, tenantID)
        authorizerToken, err := certificateAuthorizer.Authorize()

2020年5月1日金曜日

Goで月末日の時は増加減した月の月末日を返す

GoのtimeパッケージのAddDate()関数ですが、例えば10月31日に1ヶ月を足すと11月31日となりDateの正規化によって12月1日になります。

package main

import (
        "fmt"
        "time"
)

func main() {
        t := time.Date(2019, 10, 31, 0, 0, 0, 0, time.Local)
        fmt.Println(t)
        fmt.Println(t.AddDate(0, 1, 0), "(Add 1 month)")
}

実行結果

2019-10-31 00:00:00 +0000 UTC
2019-12-01 00:00:00 +0000 UTC (Add 1 month)

そこで、月末日の時は増減した月の月末日、すなわち、10月31日に1ヶ月を足すと11月30日となる関数「AddMonth()」を考えてみました。(注意:横スクロールしてください)
package main

import (
        "fmt"
        "time"
)

// AddMonth returns the time corresponding to adding the
// given number of months to t.
// For example, AddMonth(t, 2) applied to January 1, 2011
// (= t) returns March 1, 2011.
//
// AddMonth does not normalize its result in the same way
// that Date does, so, for example, adding one month to
// October 31 yields November 30.
func AddMonth(t time.Time, months int) time.Time {
        lastMonthDay := func(t time.Time) int {
                return time.Date(t.Year(), t.Month()+1, 1, 0, 0, 0, 0, t.Location()).AddDate(0, 0, -1).Day()
        }

        // Creating 1st Date from t and adding months because AddDate() normalizes t.
        am := time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location()).AddDate(0, months, 0)
        ad := lastMonthDay(am)

        if ld := lastMonthDay(t); t.Day() == ld || t.Day() > ad {
                return time.Date(am.Year(), am.Month(), ad, am.Hour(), am.Minute(), am.Second(), am.Nanosecond(), am.Location())
        }

        return t.AddDate(0, months, 0)
}

func main() {
        cnt := 14

        fmt.Println("First Days")
        ft := time.Date(2020, 2, 1, 0, 0, 0, 0, time.Local)
        for i := -cnt; i <= -1; i++ {
                fmt.Println(AddMonth(ft, i), "(Add", i, "month)")
        }
        fmt.Println(ft)
        for i := 1; i <= cnt; i++ {
                fmt.Println(AddMonth(ft, i), "(Add", i, "month)")
        }

        fmt.Println()

        fmt.Println("Last Days")
        lt := time.Date(2020, 2, 29, 0, 0, 0, 0, time.Local)
        for i := -cnt; i <= -1; i++ {
                fmt.Println(AddMonth(lt, i), "(Add", i, "month)")
        }
        fmt.Println(lt)
        for i := 1; i <= cnt; i++ {
                fmt.Println(AddMonth(lt, i), "(Add", i, "month)")
        }
}

実行結果

First Days
2018-12-01 00:00:00 +0000 UTC (Add -14 month)
2019-01-01 00:00:00 +0000 UTC (Add -13 month)
2019-02-01 00:00:00 +0000 UTC (Add -12 month)
2019-03-01 00:00:00 +0000 UTC (Add -11 month)
2019-04-01 00:00:00 +0000 UTC (Add -10 month)
2019-05-01 00:00:00 +0000 UTC (Add -9 month)
2019-06-01 00:00:00 +0000 UTC (Add -8 month)
2019-07-01 00:00:00 +0000 UTC (Add -7 month)
2019-08-01 00:00:00 +0000 UTC (Add -6 month)
2019-09-01 00:00:00 +0000 UTC (Add -5 month)
2019-10-01 00:00:00 +0000 UTC (Add -4 month)
2019-11-01 00:00:00 +0000 UTC (Add -3 month)
2019-12-01 00:00:00 +0000 UTC (Add -2 month)
2020-01-01 00:00:00 +0000 UTC (Add -1 month)
2020-02-01 00:00:00 +0000 UTC
2020-03-01 00:00:00 +0000 UTC (Add 1 month)
2020-04-01 00:00:00 +0000 UTC (Add 2 month)
2020-05-01 00:00:00 +0000 UTC (Add 3 month)
2020-06-01 00:00:00 +0000 UTC (Add 4 month)
2020-07-01 00:00:00 +0000 UTC (Add 5 month)
2020-08-01 00:00:00 +0000 UTC (Add 6 month)
2020-09-01 00:00:00 +0000 UTC (Add 7 month)
2020-10-01 00:00:00 +0000 UTC (Add 8 month)
2020-11-01 00:00:00 +0000 UTC (Add 9 month)
2020-12-01 00:00:00 +0000 UTC (Add 10 month)
2021-01-01 00:00:00 +0000 UTC (Add 11 month)
2021-02-01 00:00:00 +0000 UTC (Add 12 month)
2021-03-01 00:00:00 +0000 UTC (Add 13 month)
2021-04-01 00:00:00 +0000 UTC (Add 14 month)

Last Days
2018-12-31 00:00:00 +0000 UTC (Add -14 month)
2019-01-31 00:00:00 +0000 UTC (Add -13 month)
2019-02-28 00:00:00 +0000 UTC (Add -12 month)
2019-03-31 00:00:00 +0000 UTC (Add -11 month)
2019-04-30 00:00:00 +0000 UTC (Add -10 month)
2019-05-31 00:00:00 +0000 UTC (Add -9 month)
2019-06-30 00:00:00 +0000 UTC (Add -8 month)
2019-07-31 00:00:00 +0000 UTC (Add -7 month)
2019-08-31 00:00:00 +0000 UTC (Add -6 month)
2019-09-30 00:00:00 +0000 UTC (Add -5 month)
2019-10-31 00:00:00 +0000 UTC (Add -4 month)
2019-11-30 00:00:00 +0000 UTC (Add -3 month)
2019-12-31 00:00:00 +0000 UTC (Add -2 month)
2020-01-31 00:00:00 +0000 UTC (Add -1 month)
2020-02-29 00:00:00 +0000 UTC
2020-03-31 00:00:00 +0000 UTC (Add 1 month)
2020-04-30 00:00:00 +0000 UTC (Add 2 month)
2020-05-31 00:00:00 +0000 UTC (Add 3 month)
2020-06-30 00:00:00 +0000 UTC (Add 4 month)
2020-07-31 00:00:00 +0000 UTC (Add 5 month)
2020-08-31 00:00:00 +0000 UTC (Add 6 month)
2020-09-30 00:00:00 +0000 UTC (Add 7 month)
2020-10-31 00:00:00 +0000 UTC (Add 8 month)
2020-11-30 00:00:00 +0000 UTC (Add 9 month)
2020-12-31 00:00:00 +0000 UTC (Add 10 month)
2021-01-31 00:00:00 +0000 UTC (Add 11 month)
2021-02-28 00:00:00 +0000 UTC (Add 12 month)
2021-03-31 00:00:00 +0000 UTC (Add 13 month)
2021-04-30 00:00:00 +0000 UTC (Add 14 month)

月末日に関しては、12ヶ月の月末日の並びから

        ...,31|31,28&29,31,30,31,30,31,31,30,31,30,31|31,...

30日が連続することがないことを考慮して、以下のケースで動作確認しました。(基:基準日の月末日)

        -1 基 +1
        -- -- --
        31,28,31
        31,29,31
        31,30,31
        28,31,30
        29,31,30
        30,31,30
        30,31,31
        31,31,28
        31,31,29
        31,31,30

例)基準日が2019年2月28日で前後2ヶ月の月末日を出力するmain()関数内のコード。

        tl := time.Date(2019, 2, 28, 0, 0, 0, 0, time.Local)
        fmt.Println(AddMonth(tl, -2), "(Add -2 month)")
        fmt.Println(AddMonth(tl, -1), "(Add -1 month)")
        fmt.Println(tl)
        fmt.Println(AddMonth(tl, 1), "(Add 1 month)")
        fmt.Println(AddMonth(tl, 2), "(Add 2 month)")

2020.05.02 11月30日の時に-1ヶ月すると10月31日ではなく10月30日になっていた不具合を修正しました。

2020.05.03 2020年3月31日の時に-1ヶ月すると2月29日ではなく3月1日になっていた不具合を修正しました。

2020年4月29日水曜日

GoでSubversionからパッケージをgo getして実行する(自マシン内完結型)

社内や個人でSubversionを使ってGoコードを管理しているGopherには朗報!?Go 1.14から、goコマンドはmoduleモードでSubversionリポジトリーをサポートしました。

しかしながら、具体的な手順がなかなか見つかりませんでした。そこで「自マシン内のSubversionサーバーに登録されたGoの外部パッケージを、自マシン内で取得して実行する」ことを色々と目標に試してみました。実際に動作した時は心の中でガッツポーツ!q@w@p

Subversionサーバーの構築

自マシンに「Subversion」と「Apache HTTP Server」をインストールします。

Apache HTTP Serverには、Subversionと連動してHTTP/HTTPS通信でアクセスできる仕組みが用意されています。

今回は、諸事情によりno_ssl版のためHTTP通信でアクセスした場合の内容となっています。openssl版&HTTPS通信であればスマートに進められるはずです。

ちなみに、Subversionからの外部パッケージの取得には、svnコマンドが使用されます。

本筋ではないため、具体的なインストールやセットアップについては割愛します。

Subversionリポジトリーの作成

svnadmin createコマンドで新規リポジトリーを作成します。

リポジトリー内に、branches/、tags/、trunk/、の定番ディレクトリーを作成します。

ここでは以下のパス構成を想定しています。

svn://127.0.0.1/go/trunk/src/

本筋ではないため、具体的な手順は割愛します。

GOPROXYとGOSUMDBの無効化

今回は、諸事情により外部へのインターネット接続が制限されているため、moduleに関する2つの環境変数の値を無効化します。

GOPROXYとGOSUMDBの初期値を確認します。

$ go env GOPROXY
https://proxy.golang.org,direct

$ go env GOSUMDB
sum.golang.org

GOPROXYとGOSUMDBに無効値を設定します。

$ go env -w GOPROXY=direct
$ go env -w GOSUMDB=off

GOPATH上への移動

以降、GOPATH上で作業します。GOPATHは以下のコマンドで確認できます。

$ go env GOPATH

外部パッケージをSubversionに登録

外部パッケージとして、Add()関数を持つcalcパッケージを用意します。

calc/ディレクトリーを作成します。

$ mkdir calc

calc/ディレクトリー内に移動します。

$ cd calc

modの初期化を行います。

$ go mod init

作成されたgo.modファイルのmodule行をURLパスに修正します。

修正前:

module calc

go 1.14

修正後:

module 127.0.0.1/svn/go/trunk/src/calc.svn

go 1.14

calc.goを作成します。

$ vim calc.go

package calc

func Add(x, y int) int {
return x + y
}

calc/ディレクトリーをSubversionにチェックインします。
# 私はsvnコマンドではなく、Eclipseでチェックインしています m(__)m

svn://127.0.0.1/go/trunk/src/
|
+--calc/
   |
   +--calc.go
   +--go.mod

メイン パッケージの作成

test/ディレクトリーを作成します。

$ mkdir test

test/ディレクトリー内に移動します。

$ cd test

modの初期化を行います。

$ go mod init

main.goファイルを作成します。

$ vim main.go

package main

import (
"fmt"

calc "127.0.0.1/svn/go/trunk/src/calc.svn"
)

func main() {
fmt.Println(calc.Add(1, 2))
}

外部パッケージURLのホストが自マシンの場合、IPアドレス(ここでは127.0.0.1)で指定します。localhostで指定すると「import path does not begin with hostname」エラーになります。

通常でしたら、ここでmain.goファイルを実行します。

$ go run main.go

この時、import句の"127.0.0.1/svn/go/trunk/src/calc.svn"にHTTPS通信でアクセスして自動で外部パッケージが取得されます。

https://127.0.0.1/svn/go/trunk/src/calc.svn?go-get=1

しかし、今回はHTTP通信のためエラーとなります。そのため、go getコマンドを実行して手動で外部パッケージを取得します。

通常、go getするURLにはHTTPS通信でアクセスします。

$ go get 127.0.0.1/svn/go/trunk/src/calc.svn

今回はHTTP通信のため、-insecure オプションを付けてgo getします。

$ go get -insecure 127.0.0.1/svn/go/trunk/src/calc.svn

main.goファイルを実行すると、外部パッケージ「calc」のAdd(1, 2)が実行されて、結果の3が標準出力されます!

$ go run main.go
3

メイン パッケージのモジュール情報

go.sumファイルが作成されます。

$ vim -R go.sum

127.0.0.1/svn/go/trunk/src/calc.svn v0.0.0-YYYYMMDD... h1:...=
127.0.0.1/svn/go/trunk/src/calc.svn v0.0.0-YYYYMMDD.../go.mod h1:...=

go.modファイルに外部パッケージが追加されます。

$ vim -R go.mod

module test

go 1.14

require 127.0.0.1/svn/go/trunk/src/calc.svn v0.0.0-YYYYMMDD... // indirect

気づいた点など

go getではデフォルトでHTTPS通信が行われています。
→Subversionと連動させるApache HTTP Serverはopenssl版を使いましょう。

Subversionの外部パッケージURLの最後には「.svn」が必要です。
→GihHub経由に慣れていると、なかなか気付けない。

import句にて、Subversion経由の外部パッケージには別名を付けて使用します。
→本ページでは"127.0.0.1/svn/go/trunk/src/calc.svn"にcalcと別名付けています。

関連するGoのソースコード
→使われているsvnコマンドの引数とかがわかります。