【2019/6/11 Update】【世界初!】.Net向けOCISDKを作成してみた【C#】

皆さんこんにちは。肩の痛みがひどいid:k-furusawa--gです。

前回の記事の最後に、自社用のSDKを作成しているという話をしました。 cloudii.atomitech.jp

今回は実際に作成したC#向けOCISDKをご紹介したいと思います(たぶん世界初!)。githubのリポジトリををpublicで公開していますので、ご興味のある方は是非 Clone してみてください。

github.com

すべての機能をカバーできたわけではありませんが、VirtualNetworkとInstance周りは最低限そろえました。

なるべくjavaSDKに沿った内容にしていますが、命名規則は C# 寄りにしているのと、クライアント作成とコンフィグ周りを AWS SDK for .NET を参考にしているので完全に沿っているわけではありません。AWS SDK 使いやすいですからね。

関数名や変数名などはOCI APIと同じなるようにしています。コメントまで同じになるように移植しました(あまりに長いのは省略しましたが)。なのでOCI APIのマニュアルを読んでいただければ自然と呼び出し方もわかると思います。

Oracle Cloud Infrastructure API Documentation

本記事の目次

使い方

実装の中身はgithubのほうを見ていただくとして、使い方をざっくりご紹介します。 まずサービスクライアントを作成します。作成する際にはOCIへの接続情報をコンフィグで作成する必要があります。

var config = new ClientConfig
{
    TenancyId = "ocid1.tenancy.oc1..aaaaaaaazgvmqpl...",
    UserId = "ocid1.user.oc1..aaaaaaaart65...",
    Fingerprint = "8e:00:b3...",
    PrivateKeyPath = "C:\oracle\oci\oci_api_key.pem",
    PrivateKeyPassphrase = ""
};

クライアントコンフィグの作成です。この辺りは説明不要だと思います。各OCIDの位置はOCI公式ドキュメントに記載があるのでそちらを確認してください。

必要なキーとOCID

……とはいえ、いちいち書くの面倒ですよね。なので公式のjavaSDK同様に、CLIなどで利用するコンフィグファイル読み込みも実装してあります。

[DEFAULT]
tenancy=ocid1.tenancy.oc1..test
user=ocid1.user.oc1..test
fingerprint=1a:2b:3c:4d:5e:6f
key_file=~/.oci/api_key.pem
custom_compartment_id=ocid1.compartment.oc1..test
pass_phrase=password

[myProfile]
tenancy=ocid1.tenancy.oc1..test
user=ocid1.user.oc1..myProfile
fingerprint=7g:8h:9i:0j:1k:2l
key_file=~/.oci/my_key.pem
custom_compartment_id=ocid1.compartment.oc1..Mytest
pass_phrase=passwaord

こんな感じのコンフィグファイルを用意して、任意のフォルダに保存します(デフォルトでは~/.oci/config)。もしくはOCI CLIを導入しておくのもいいですね。当ブログでもCLIの導入を紹介しております。

Oracle Cloud Infrastructure CLIの導入 - Cloudii blog

利用方法はjavaSDKに合わせてあります。

var configReader = ConfigFileReader.Parse("~/.oci/config");

とすることで、DEFAULTのプロファイルを読み込んでくれます。myProfileのほうを使いたい場合は

var configReader = ConfigFileReader.Parse("~/.oci/config", "myProfile");

と指定すればmyProfileの情報を読み込んでくれます。あとはクライアントコンフィグに当てはめるだけです。

var config = new ClientConfig
{
    TenancyId = configReader.Get("tenancy"),
    UserId = configReader.Get("user"),
    Fingerprint = configReader.Get("fingerprint"),
    PrivateKeyPath = configReader.Get("key_file"),
    PrivateKeyPassphrase = configReader.Get("pass_phrase")
};

ではやっていきましょう。順番的に Compartment を取得してみましょう。

IdentityClient identityClient = new IdentityClient(config)
{
    Region = Regions.US_ASHBURN_1
};

Identity 用のクライアント作成です。 コンフィグを渡してリージョンを指定します。リージョンはjavaSDKに記載のあったものを取り合えず選べます。東京リージョンが増えたら JP-TOKYO-1 とかになるんですかね。

クライアントを作成したらGETしてみましょう。

// Tenancy情報を得る
var getTenancyRequest = new GetTenancyRequest()
{
    TenancyId = identityClient.Config.TenancyId
};
var getTenacy = identityClient.GetTenancy(getTenancyRequest);

// 得られたTenancy情報のIDをつかって Compartment 一覧を得る。
var listCompartmenRequest = new ListCompartmentRequest()
{
    CompartmentId = getTenacy.Tenancy.Id,
    Limit = 10
};
var listCompartment = identityClient.ListCompartment(listCompartmenRequest);

ちょっと丁寧すぎですが、せっかくなので TenancyId を取得してからそれを利用して配下にある Compartment 一覧を得ます。 これでもいいですが、特定の Compartment の情報だけが欲しいなら一覧の中から特定の Compartment のIDを取ってGETします。

var getCompartmentRequest = new GetCompartmentRequest() {
    CompartmentId = "ocid1.compartment.oc1..Mytest"
};
var getCompartment = identityClient.GetCompartment(getCompartmentRequest);

何のひねりもない素直な取り方ですが、こんな感じです。 Compartment が取得できたので、みんな大好きインスタンスを取得しましょう。

var computeClient = new ComputeClient(config)
{
    Region = Regions.US_ASHBURN_1
};

Computeサービス用のクライアント作成です。Identity 用のクライアント作成と同じですね。

クライアントを作成したらGETしてみましょう。

var listInstanceRequest = new ListInstancesRequest()
{
    // 得られたCompartmentを指定
    CompartmentId =  getCompartment.Compartment.Id,
    Limit = 50,
    LifecycleState = ListInstancesRequest.LifecycleStates.RUNNING,
    SortOrder = SortOrder.ASC
};
var listInstance = computeClient.ListInstances(listInstanceRequest);

Instance を List で取得する部分です。動いていないインスタンスは省きたいのでLifecycleStateRUNNINGを指定しました。LifecycleStateは各リソースによって指定できる値が違いますので、リクエストクラスに列挙型で定義してあります。この辺はjavaSDKと同じですね。

取得はこの程度です。ほぼすべてのリソースがこの手順で取得できます。 取得したなら作成もやりたくなるものです。次はきっとみんな最初にやる作業 VCN の作成をしてみます。

var vnClient = new VirtualNetworkClient(config)
{
    Region = Regions.US_ASHBURN_1
};

VirtualNetwork のクライアント作成です。説明不要です。

var createVcnRequest = new CreateVcnRequest()
{
    OpcRetryToken = "29102840ks843",
    CreateVcnDetails = new CreateVcnDetails()
    {
        CompartmentId = vnClient.Config.TenancyId,
        DisplayName = "ExampleVCN",
        CidrBlock = "10.0.0.0/16"
    }
};
var newVCN = vnClient.CreateVcn(createVcnRequest);

VCNの作成です。作成するVCNの Detail を作成してリクエストに含めます。

OpcRetryTokenですが、これはタイムアウトやサーバエラーで再実行が行われた際に同じリクエストであることを示すトークンです。一時的なもので一度登録されたトークンは24時間保持されます。

作ったので次は更新しましょう。作成したVCNの名前を変更します。

var updateVcnRequest = new UpdateVcnRequest()
{
    IfMatch = newVCN.ETag,
    VcnId = newVCN.Vcn.Id,
    UpdateVcnDetails = new UpdateVcnDetails()
    {
        DisplayName = "UpdateExampleVcn"
    }
};
var updateVcn = client.UpdateVcn(updateVcnRequest);

更新です。必須ではありませんが衝突を避けるためにIfMatchをつけることができます。OCIはリソース操作を行うとETagを返してくれるので(一部返さないのもあります)、これを更新や削除でIfMatchに入れると、同じETagを持つリソースであるかをチェックしてくれます。

OCI独自ではなくHTTPではよくある機能ですね。「IfMatch」でググっても出てくるのでそちらを参考にしてください。OCI APIのDocにはちょっとした説明が書いてあるだけのようでした。REST APIのDocに説明があるのでそちらを張らせていただきます。

データ整合性タスク

ではこのVCNはいらないので消しましょう。更新した際に返ってきたレスポンス内容をそのまま使います。

DeleteVcnRequest deleteVcnRequest = new DeleteVcnRequest()
{
    IfMatch = updateVcn.ETag,
    VcnId = updateVcn.Vcn.Id
};
var deleteVcn = client.DeleteVcn(deleteVcnRequest);

削除の際にもETagをつけたほうが安心ですね。

これでVCNの取得、作成、更新、削除ができましたね。インスタンスも似たような感じでできます。

var launchInstanceRequest = new LaunchInstanceRequest()
{
    LaunchInstanceDetails = new LaunchInstanceDetails()
    {
        AvailabilityDomain = "HgjG:US-ASHBURN-AD-3",
        CompartmentId = config.TenancyId,
        DisplayName = "SDKExampleInstance",
        Shape = "VM.Standard2.2",
        CreateVnicDetails = new CreateVnicDetails()
        {
            SubnetId = "ocid1.subnet.oc1.iad.aaaa..."
        },
        SourceDetails = new InstanceSourceDetails()
        {
            SourceType = "image",
            ImageId= "ocid1.image.oc1.iad.aaa..."
        }
    }
};
var newInstance = computeClient.LaunchInstance(launchInstanceRequest);

インスタンス作成の例となります。インスタンスの作成はLaunchInstanceなのでご注意ください。

AvailabilityDomainShapeもそれぞれ取得できるので、取得しながら組み立ててもいいです。クライアントが違うのでちょっと面倒ですけど。

インスタンス作成はドキュメントの必須項目が恐ろしいくらいに分かりにくいので、よく読んだほうがいいでしょう(私は1時間くらいはまった……)。 条件によって必須になる項目や、廃止予定の項目は色変えるくらいしてほしいですね。そのあたりもSDKで分かりやすくできたらなと思いましたが、さすがに無理でした(公式に期待)。

var terminateInstanceRequest = new TerminateInstanceRequest()
{
    InstanceId = "ocid1.instance.oc1.iad.ab...."
};
computeClient.TerminateInstance(terminateInstanceRequest);

インスタンス削除の例です。インスタンスの削除はTerminateInstanceです。これは大丈夫ですね。

ざっとですがこんな感じで使えます。サポートしている機能は私たちが使う予定でいる範囲に留めているのでかなり少ないですが、ぽつぽつ追加していこうと思っています。あと非同期用のクライアントを用意していないので、用途によっては使い物にならないです。そのあたりもやっていかないとですね。

あとはまぁ……Exception周りがそのままthrowしているだけなので少しちゃんと調理してあげたほうがいいかもですね。

というかオラクルさんの公式が出すほうが早いかもしれませんね。要望があれば作るとオラクルの中の人が発言されていたので、これを機にOCI SDK .Netが開発されたらいいですね!

ちなみにこちらのSDKは将来的にNuGetにも上げられたらなと思っております。

2019/6/11 Update

待望のNuGetにプッシュしました!

www.nuget.org

検索でもひっかかるようになっています。ご興味のある方は dotnet add してください!

MITラインセンスなのでどなたでもご利用いただけます。私は責任持ちませんけど!

OCIはAzure連携が発表されたばかりですので、公式のSDKがそろそろ出てくるんじゃないかな? って勝手に思ってます!

まだまだ足りていないのでバージョンは0.5にしておりますが、バージョン1を目指して細々と頑張っていきたいと思っております!