【2019/3/22 追記】C#でOracle Cloud Infrastructure Service APIを扱おう

こんにちは。id:k-furusawa--gです。今回はC#でWebAPI開発っぽい記事を書きたいと思います。

OCIのSDKはC#向けが存在しないため(リクエストが多かったら対応されるそうです!)、APIを直接呼び出します。対象はOracle Cloud Infrastructure Service APIのObject Storageとなります。なぜObject Storageなのかというと結果を確認しやすいからです!

公式ドキュメント docs.cloud.oracle.com

認証と各メソッドは公式サンプルをそのまま流用します。最低限こちらに目を通したことを前提に記事を書きますので、まだの方は先にお読みください。

公式サンプル docs.cloud.oracle.com



では早速やっていきます。

まずは認証などに利用する各設定を代入します。

// tenancy OCID
private string tenancyId = "ocid1.tenancy.oc1..a***";
// compartment OCID
private string compartmentId = "ocid1.tenancy.oc1..a***";
// user OCID
private string userId = "ocid1.user.oc1..a***";
// API Key Fingerprint
private string fingerprint = "8e:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:fa";
// private key path
private string privateKeyPath = "oci_api_key.pem";
// private key password
private string privateKeyPassphrase = "Password";

認証に必要な変数の内容はOCIDとなりますので、各環境に合わせて取得し設定してください。

API Keyは本サービスを呼び出すユーザーに設定します。
API Keyの設定は当ブログの Oracle Cloud Infrastructure CLIの導入 (後編:環境設定) - Cloudii blog の記事をご参考ください。

cloudii.atomitech.jp



各OCIDとAPI Key確認のために名前空間(namespace)を得てみます。
名前空間はObjectStorageを扱う際に必須になる情報です。

var signer = new RequestSigner(tenancyId, userId, fingerprint, privateKeyPath, privateKeyPassphrase);

// GET with query parameters
var uri = new Uri($"https://objectstorage.us-ashburn-1.oraclecloud.com/n");
var request = (HttpWebRequest)WebRequest.Create(uri);
request.Method = "GET";
request.Accept = "application/json";
signer.SignRequest(request);
var webResponse = (HttpWebResponse)request.GetResponse();
var response = new StreamReader(webResponse.GetResponseStream()).ReadToEnd();
Console.WriteLine($"Response: {response}");

return response;

コンソールに名前空間の情報が出ればOKです。大体はテナント名になるようですね。

ちなみにHttpWebRequestじゃなくてHttpClientを使って最初は書いていたのですが、HttpClient用に公式サンプルを書き換えるとなぜかAuthエラーになってしまうので、今回はサンプル通りHttpWebRequestを利用しています。
Fiddlerなどで確認すると最終的に流れるリクエストは全く同じなのに通らない理由が解明できませんでした(成功した人がいたら教えてください・・・!)。



同じ要領でObject Storageのbucket操作を書いてみます。せっかくなのでメソッドを分けてAPIとして呼び出せるようにします。

受け取り用のモデル

public class ObjectStorageBucketModel
{
    public virtual string nameSpace { get; set; }
    
    public virtual string name { get; set; }
    
    public virtual string compartmentId { get; set; }
    
    public virtual string createdBy { get; set; }
    
    public virtual string timeCreated { get; set; }
    
    public virtual string etag { get; set; }
}

呼び出しメソッド

/// <summary>
/// バケット一覧を得る
/// </summary>
public virtual List<ObjectStorageBucketModel> GetBuckets(string namespaceName)
{
    // 認証設定
    var signer = new RequestSigner(tenancyId, userId, fingerprint, privateKeyPath, privateKeyPassphrase);

    var uri = new Uri($"https://objectstorage.us-ashburn-1.oraclecloud.com/n/{namespaceName}/b?compartmentId={compartmentId}&limit=100");
    var request = (HttpWebRequest)WebRequest.Create(uri);

    request.Method = "GET";
    request.Accept = "application/json";
    
    signer.SignRequest(request);
    
    var webResponse = (HttpWebResponse)request.GetResponse();

    var serializer = new DataContractJsonSerializer(typeof(List<ObjectStorageBucketModel>));

    var res = (List<ObjectStorageBucketModel>)serializer.ReadObject(webResponse.GetResponseStream());

    return res;
}

基本となるGETです。ObjectStorageのバケットは必ず名前空間の下に作成されます。


GETができたのでPOSTとDELETEも作ります。

/// <summary>
/// バケットを作成する
/// </summary>
public virtual ObjectStorageBucketModel CreateBucket(string namespaceName, string bucketName)
{
    // 認証設定
    var signer = new RequestSigner(tenancyId, userId, fingerprint, privateKeyPath, privateKeyPassphrase);

    var uri = new Uri($"https://objectstorage.us-ashburn-1.oraclecloud.com/n/{namespaceName}/b/");
    var request = (HttpWebRequest)WebRequest.Create(uri);
    var body = string.Format(@"{{""name"":""{0}"",""compartmentId"":""{1}"",""publicAccessType"":""ObjectRead""}}", bucketName, compartmentId);
    var bytes = Encoding.UTF8.GetBytes(body);

    request.Method = "POST";
    request.Accept = "application/json";
    request.ContentType = "application/json";
    request.Headers["x-content-sha256"] = Convert.ToBase64String(SHA256.Create().ComputeHash(bytes));
    // ContentLengthは必須
    request.ContentLength = bytes.Length;

    using (var stream = request.GetRequestStream())
    {
        stream.Write(bytes, 0, bytes.Length);
    }

    signer.SignRequest(request);

    var webResponse = (HttpWebResponse)request.GetResponse();

    var serializer = new DataContractJsonSerializer(typeof(ObjectStorageBucketModel));

    var res = (ObjectStorageBucketModel)serializer.ReadObject(webResponse.GetResponseStream());

    return res;
}

/// <summary>
/// バケットを削除する
/// </summary>
public virtual bool DeleteBucket(string namespaceName, string bucketName)
{
    // 認証設定
    var signer = new RequestSigner(tenancyId, userId, fingerprint, privateKeyPath, privateKeyPassphrase);

    var uri = new Uri($"https://objectstorage.us-ashburn-1.oraclecloud.com/n/{namespaceName}/b/{bucketName}");
    var request = (HttpWebRequest)WebRequest.Create(uri);

    request.Method = "DELETE";
    request.Accept = "application/json";

    signer.SignRequest(request);

    ExecuteRequest(request);

    return true;
}

こんな感じです。失敗時の事を考慮していないのでご注意ください。
特にDELETEは成功時はレスポンスがEmptyで、失敗時のみコードが返るのでエラーハンドリングをしっかりしたほうがいいでしょう。

また、POST時はSha256指定が必須です。ヘッダにx-content-sha256をつけるのを忘れないでください。
これはInstanceでもObjectStorageでもVCNでも共通のようです。
公式ドキュメントのExampleでは指定があったりなかったりしますが、指定しないと怒られます。



オブジェクトもやってみます。

受け取り用モデル。

public class ObjectListModel {

    public virtual string name { get; set; }

    public virtual string size { get; set; }

    public virtual string timeCreated { get; set; }

    public virtual string md5 { get; set; }

}

public class ObjectsModel {
    public List<ObjectListModel> objects { get; set; }
}

各呼び出し。

/// <summary>
/// オブジェクト一覧を得る
/// </summary>
public virtual ObjectsModel GetObjects(string namespaceName, string bucketName)
{
    // 認証設定
    var signer = new RequestSigner(tenancyId, userId, fingerprint, privateKeyPath, privateKeyPassphrase);

    var uri = new Uri($"https://objectstorage.us-ashburn-1.oraclecloud.com/n/{namespaceName}/b/{bucketName}/o?fields=size,timeCreated,md5&limit=100");
    var request = (HttpWebRequest)WebRequest.Create(uri);

    request.Method = "GET";
    request.Accept = "application/json";

    signer.SignRequest(request);

    var webResponse = (HttpWebResponse)request.GetResponse();

    var serializer = new DataContractJsonSerializer(typeof(ObjectsModel));

    var res = (ObjectsModel)serializer.ReadObject(webResponse.GetResponseStream());
    
    return res;
}

/// <summary>
/// オブジェクトをPUTする
/// </summary>
public virtual bool PutObject(string namespaceName, string bucketName, string objectName)
{
    // 認証設定
    var signer = new RequestSigner(tenancyId, userId, fingerprint, privateKeyPath, privateKeyPassphrase);

    var uri = new Uri($"https://objectstorage.us-ashburn-1.oraclecloud.com/n/{namespaceName}/b/{bucketName}/o/{objectName}");
    var body = "Hello Object Storage Service!!!";
    var bytes = Encoding.UTF8.GetBytes(body);

    var request = (HttpWebRequest)WebRequest.Create(uri);
    request.Method = "PUT";
    request.Accept = "application/json";
    request.ContentType = "application/json";
    // Lengthは必須
    request.ContentLength = bytes.Length;

    using (var stream = request.GetRequestStream())
    {
        stream.Write(bytes, 0, bytes.Length);
    }

    signer.SignRequest(request, true);

    Console.WriteLine($"Authorization header: {request.Headers["authorization"]}");

    ExecuteRequest(request);

    return true;
}

/// <summary>
/// オブジェクトを削除する
/// </summary>
public virtual bool DeleteObject(string namespaceName, string bucketName, string objectName)
{
    // 認証設定
    var signer = new RequestSigner(tenancyId, userId, fingerprint, privateKeyPath, privateKeyPassphrase);

    var uri = new Uri($"https://objectstorage.us-ashburn-1.oraclecloud.com/n/{namespaceName}/b/{bucketName}/o/{objectName}");
    var request = (HttpWebRequest)WebRequest.Create(uri);

    request.Method = "DELETE";
    request.Accept = "application/json";

    signer.SignRequest(request);

    Console.WriteLine($"Authorization header: {request.Headers["authorization"]}");

    ExecuteRequest(request);

    return true;
}

先程と同様に失敗時の事は考慮してません。

コントローラ実装は割愛します。

実装出来たら呼び出してみます!
今回は開発でswagger使っているのでswaggerで確認します。

f:id:k-furusawa--g:20190110183114j:plain
swaggerで確認

同じObjectStorageサービスなのにBucketとObjectでコントローラを分けたので違和感を感じますが、役割的には別なので今回は分けました。



まずBucketを作成します。
名前はmyBucket01にします。

f:id:k-furusawa--g:20190110181337j:plain
Bucket作成

200OKで作成したバケット内容が返ってきました。



GETでBucket一覧を得てみます。

f:id:k-furusawa--g:20190110171443j:plain
GETしてBucket一覧を得る

いい感じですね! 

myBucket01にファイルをアップロードしてみます。

f:id:k-furusawa--g:20190110172131j:plain
オブジェクトをアップロード

myBucket01myObject.txtというファイルをアップロードしてみました。

GETでオブジェクト一覧を得て確認してみます。

f:id:k-furusawa--g:20190110172442j:plain
オブジェクト一覧取得

myObject.txtがちゃんと取得できました。

用心深くダッシュボードでも確認してみます。

f:id:k-furusawa--g:20190110172715j:plain
ダッシュボードでも確認

ありますね。問題なさそうです。

かなりざっくりとした感じになってしまいましたが、大凡の実装方法は把握していただけたと思います。
インスタンス(Compute)やVCN(Networking)も同じ要領で呼び出せるのでCloud Infrastructure ClassicのREST APIよりかは統一性がありますね。
ドキュメントがもうちょっと詳しかったら言うことないと思います!

現在、こんな感じでC#向けにSDK を作ってます(まずは私達が利用するAPIのみ優先して実装中)。
またどこかのタイミングで開発関連の記事を書いていきたいと思います!

2019/3/22 追記 OCI SDK NET 公開しました!

cloudii.atomitech.jp