銀の光と碧い空

クラウドなインフラとC#なアプリ開発の狭間にいるエンジニアの日々

GitHub ActionsでAzure Web Appsにデプロイするときに同時に環境変数を書き換えたい

とりあえず実現できたのですが、もっとうまくある方法があるのではと思っています。

GitHub Actionsを使ってAzure Web Appにデプロイしたい場合、ひとまずazure/webapps-deploy@v3を使って次のように書けます。

      - name: Deploy to Azure Web App
        id: deploy-to-webapp
        uses: azure/webapps-deploy@v3
        with:
          app-name: 'ghu-demo-webapp'
          slot-name: 'Production'
          package: .

github.com

このとき同時にWeb Appsの環境変数も書き換えたい時があります。具体的にはデプロイしたコードのコミットハッシュを記録しておきたい、といった目的です。探した限り、webapps-deployのアクションでは環境変数も更新する機能はない模様で、ひとまずAzure CLIを実行するアクションを使ってデプロイ後に次のアクションを実行するように定義しました。METADATA_COMMITという名前の環境変数にコミットハッシュを値としたものを追加(更新)します。

      - name: Update Env Vars
        id: update-envvar
        uses: Azure/cli@v2.1.0
        with:
          inlineScript: "az webapp config appsettings set -g ghu-demo -n ghu-demo-webapp --settings METADATA_COMMIT=${{ github.sha }}"

これで目的は果たせるのですが、アプリのデプロイと環境変数の更新を連続して別々に実行するのがちょっと非効率という気がしています。そもそも同時に更新する方法自体ないのかもしれないですが、そこも確認できず。 ひとまず調査した記録して残しておきました。

OpenFeature .NET SDKを試してみた(4) 1.5で導入されたInMemoryProviderを試してみる

しばらく触っていない間に v1.5.1 までリリースされていました。v1.5 で InMemoryProvider が追加され、ようやくflagdなどのProviderを別途用意せずに検証できるようになりました。

github.com

利用方法はドキュメントにもあるようにこれだけなのですが、これだけでは何もフラグ定義がはいっていないので、追加する必要があります。

await Api.Instance.SetProviderAsync(new InMemoryProvider());

フラグ定義はDictionary<string, Flag>型でコンストラクタかUpdateFlagsメソッドの引数で指定します。文字列型のフラグであれば次のようになります。

var flags = new Dictionary<string, Flag>
{
    {
        "myStringFlag", new Flag<string>(
            variants: new Dictionary<string, string>{
                { "key1", "val1" },
                { "key2", "val2" },
                { "key3", "val3" }
            },
            defaultVariant: "key1"
        )
    }
};

await Api.Instance.SetProviderAsync(new InMemoryProvider(flags));

//フラグを取得するとき
var strValue = await client.GetStringValue("myStringFlag", "foo");

Flagを定義する際、variantsとdefaultVariantが必須で、任意でcontextEvaluatorを指定できます。contextEvaluatorについてはContextを試す際にあわせて使ってみようと思います。

さて、数値型、ブーリアン型は文字列型と同様にstringをdoubleやboolに変えれば利用できます。OpenFeature .NETはこの他にDateTimeと今まで挙げた型を要素にするリストと辞書を格納できるようです。これらはobject型として取り扱うようになっていました。

まず、DateTimeのときです。variantsを定義するときに、Dictionary<string, Value>として、ValueのコンストラクタにDateTimeを指定します。ValueではなくDateTimeを直接指定すると、フラグを取り出すときにSDK内で行う型チェックで値が取得できなくなりました。 フラグを取り出すときは、GetObjectValue(もしくはGetObjectDetails)を呼び出し、object型で返ってきた値をAsDateTimeでDateTimeとして取り出します。ここはInMemoryProviderに限らず同じ処理になります。

var flags = new Dictionary<string, Flag>
{
    {
        "myDateTimeFlag", new Flag<Value>(
            variants: new Dictionary<string, Value>{
                {
                    "foo", new Value(DateTime.Now)
                },
                {
                    "bar", new Value(DateTime.MaxValue)
                }
            },
            defaultVariant: "foo"
        )
    }
};

await Api.Instance.SetProviderAsync(new InMemoryProvider(flags));

//フラグを取得するとき
var dateTimeValue = (await client.GetObjectValue("myDateTimeFlag", null)).AsDateTime;

リストや辞書の場合は次のようになります。

var flags = new Dictionary<string, Flag>
{
    {
        "myListFlag", new Flag<Value>(
            variants: new Dictionary<string, Value>{
                {"foo", new Value([new Value("foo1"), new Value("foo1")])},
                {"bar", new Value([new Value("bar1"), new Value("bar1")])}
            },
            defaultVariant: "foo"
        )
    },
    {
        "myStructureFlag", new Flag<Value>(
            variants: new Dictionary<string, Value>{
                {
                    "foo", new Value(new Structure(new Dictionary<string, Value>
                    {
                        { "foo1", new Value("foo1") },
                        { "foo2", new Value("foo2") }
                    }))
                },
                {
                    "bar", new Value(new Structure(new Dictionary<string, Value>
                    {
                        { "bar1", new Value("bar1") },
                        { "bar2", new Value("bar2") }
                    }))
                }
            },
            defaultVariant: "foo"
        )
    }
};

await Api.Instance.SetProviderAsync(new InMemoryProvider(flags));

//フラグを取得するとき
var listValue = (await client.GetObjectValue("myListFlag", null)).AsList;
var structureValue = (await client.GetObjectValue("myStructureFlag", null)).AsStructure;
foreach (var (k, v) in structureValue)
{
    //vがstringの場合
    Console.WriteLine($"{k}={v.AsString}");
}

まずリストの場合はValueのコンストラクタにIListもしくはIImmutableListを指定します。実装を見るとなぜこの2つなのかがわかります。上のコードだとコンストラクタが用意されているIListC#12で導入されたコレクション式で生成していることになります。

github.com

辞書型の場合は、辞書のデータをDictionary<string, Value>で用意してStructureのコンストラクタに与えて、StructureをValueのコンストラクタに与えます。マトリョーシカのような構造ですが、これで辞書型を格納したフラグを定義できました。取り出すときは AsStructure プロパティでStructureが取れて、IEnumerable<KeyValuePair<string, Value>>を実装しているのでforeach文などで取り出せます。

ちょっと定義に仕方にくせがある気がしますが、こんな感じになります。

MS LearnのAOAIのDALL-E 3を使用して画像を生成するサンプルを動かす方法

Azure OpenAI Serviceを試しているのですが、以下のページのDALL-E 3を使うC#のサンプルコードが動かなかったので、2024年2月25日現在で動かした手順を載せておきます。フィードバック済みです。

learn.microsoft.com

まず前提条件が足りていません。DALL-E 3モデルをデプロイしておく必要があるのですが、このためにはOpenAI serviceリソースをSweden Centralに作成しておく必要があります*1

OpenAI serviceを作成したら、ドキュメントの手順通りエンドポイントとキーをメモっておきます。

次にAzure OpenAI Studioに移動して、管理>デプロイメニューを開きます。ここで、dall-e-3 モデルのモデルがデプロイされていることを確認します。自分は最初からデプロイされていましたが、ない場合は新規にデプロイすればよいと思われます。ここで、デプロイ名をメモっておきます。

そして動いたサンプルコードは以下の通りです。

using Azure.AI.OpenAI;
using Azure;

var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT");
var key = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY");

OpenAIClient client = new(new Uri(endpoint), new AzureKeyCredential(key));

var imageGenerations = await client.GetImageGenerationsAsync(
    new ImageGenerationOptions()
    {
        Prompt = "a happy monkey eating a banana, in watercolor",
        Size = ImageSize.Size1024x1024,
        DeploymentName = "Dalle3"
    });

// Image Generations responses provide URLs you can use to retrieve requested images
Uri imageUri = imageGenerations.Value.Data[0].Url;

// Print the image URI to console:
Console.WriteLine(imageUri);

Environment.GetEnvironmentVariable でないと動かないのは比較的わかりやすいですが、ImageGenerationOptions のプロパティに DeploymentName が指定されていないと例外がスローされます。値は先ほどメモっておいたdall-e-3モデルのデプロイ名です。また自分が試したときは Sizeプロパティが ImageSize.Size256x256 だとエラーになりました。

以上の手順でドキュメント通り、生成された画像のURLが表示されました。

*1:もしかしたらこのドキュメントはDALL-E 2向けかもしれませんが、いずれにせよこのドキュメントのままでは動きませんでした