kakakakakku blog

Weekly Tech Blog: Keep on Learning!

カックマイクラ実況 YouTube 振り返り(開設1年🎉)

2023年7月1日に開設した YouTube チャンネル「カックマイクラ実況」を1年間続けてみた❗️簡単に振り返ろうと思う.

www.youtube.com

ちなみに「6ヶ月間」の振り返りは以下の記事にまとめてある📝機材などは特に変化なし.

kakakakakku.hatenablog.com

Stats (~2024/06/30) 👾

  • 動画: 71本(2023年12月から+46本)
  • チャンネル登録者数: 61人(2023年12月から+45人)
    • チャンネル登録者数「50人」達成 🎉
  • 視聴回数: 22,306回(2023年12月から+21,089回)
    • YouTube ショート「AI に家を建築してもらおう」「5000回再生」達成 🎉

www.youtube.com

YouTube ショート 👾

2024年3月頃から YouTube ショートを作り始めた.計31本も作った❗️最初は今まで作った動画のダイジェストとして作っていて,2024年5月頃からは完全に YouTube ショートのみ作ることにして,マインクラフトのアップデート紹介・Tips 紹介・AI 建築シリーズなどの企画を考えてコツコツと作った.

YouTube ショートを作って驚いたのは「ショートフィード」経由の新規流入が多いことだった.今まで YouTube 動画を作っても誰にも見られず,動画自体に流入してもらうことへのハードルが高いという課題があった.YouTube ショートだとアルゴリズムはわからないけど,チャンネル登録者以外にリーチできて,数名でも興味を持ってもらえる人に見てもらうことができた💡視聴回数の増加・チャンネル登録者数の増加はまさに YouTube ショートの結果と言える.

テクノロジーネタ 👾

2024年1月の振り返り記事に「2024年は "マインクラフト x テクノロジー" な動画も作りたい」という目標を書いた通り,コマンド紹介・Mod 紹介・スナップショットアップデート紹介・AI 建築などの動画を多く作った.Wiki を見ながらコマンドのオプションを調べたり,GitHub リポジトリを見ながら Mod の実装を調べたりする作業は普段の技術調査と同じ流れで特に苦労することなく楽しめた💡僕自身の強みを活かせていると思う \( 'ω')/

例えば ride コマンドを使った以下の動画は個人的におすすめ❗️(全然見られてないけどーw)

www.youtube.com

www.youtube.com

AI 建築シリーズ 👾

2024年6月から個人的に「AI 建築シリーズ」と呼んでいるけど,ChatGPT などの LLM に生成してもらったコードを使ってマインクラフトの建築をするというネタで YouTube ショートを作り始めた💡

www.youtube.com

www.youtube.com

Raspberry Jam Mod (raspberryjammod) を使うと,マインクラフトのバージョンは古めだけど Python から直接マインクラフトを操作できる.プロンプトを工夫すれば,LLM で Raspberry Jam Mod のコードを生成できる👌技術的な勘所がないと環境構築が大変そうではあるけど,特にハマるところはなかった.

github.com

今後 👾

仕事(本業)も増えてきてて YouTube に使える自由時間は減っていく傾向にあるけど,今後も趣味としてコツコツ YouTube 活動は続けていこうと思う❗️チャンネル登録をしてもらえると本当に嬉しいのでもし良かったらポチッとお願いしまーす🙏

www.youtube.com

Apache Ivy の configurations でテストに必要な依存関係を取得する

Apache Ivy を使って Maven Repository からライブラリを取得するときに,例えば CI 環境では JUnit や Mockito などの「テストに必要な依存関係」も取得したいという場面がある.Apache Ivy では configurations(コンフィグレーション) を使って柔軟に依存関係を管理できる👌

サンプルコード

Apache Ivy の ivy.xml を以下のように書いた.ポイントは configurations タグで,今回は compile(コンパイルに必要な依存関係)と test(テストに必要な依存関係)という2種類のコンフィグレーションを設定した.Apache Ivy のドキュメントを見ると,アプリケーション単位の依存関係などさらに柔軟に設定している例もあった💡そして dependency タグを書くときに conf="compile->default"conf="test->default" のようにコンフィグレーションを紐付ければ OK ✅

<ivy-module version="2.0">
    <info organisation="sandbox" module="module"/>

    <configurations>
        <conf name="compile" />
        <conf name="test" />
    </configurations>

    <dependencies>
        <!-- compile -->
        <dependency org="com.google.code.gson" name="gson" rev="2.11.0" conf="compile->default"/>
        <!-- test -->
        <dependency org="org.junit.jupiter" name="junit-jupiter-api" rev="5.10.3" conf="test->default"/>
        <dependency org="org.mockito" name="mockito-core" rev="5.12.0" conf="test->default"/>
        <dependency org="org.mockito" name="mockito-junit-jupiter" rev="5.12.0" conf="test->default"/>
    </dependencies>
</ivy-module>

そして Apache Ant の build.xml には resolve ターゲットと resolve-test ターゲットを書いておく.

<project xmlns:ivy="antlib:org.apache.ivy.ant">
    <target name="resolve">
        <ivy:retrieve type="jar" conf="compile"/>
    </target>
    <target name="resolve-test" depends="resolve">
        <ivy:retrieve type="jar" conf="test"/>
    </target>
</project>

動作確認

ant resolve コマンドを実行すると,コンパイルに必要な Gson とその依存関係を取得できる👌

error_prone_annotations-2.27.0.jar
gson-2.11.0.jar

ant resolve-test コマンドを実行すると,テストに必要な JUnit / Mockito とその依存関係を取得できる👌 そして resolve-test ターゲットには resolve ターゲットへの依存も設定しているため,コンパイルに必要な依存関係も取得できている \( 'ω')/

apiguardian-api-1.1.2.jar
byte-buddy-1.14.15.jar
byte-buddy-agent-1.14.15.jar
error_prone_annotations-2.27.0.jar
gson-2.11.0.jar
junit-jupiter-api-5.10.3.jar
junit-platform-commons-1.10.3.jar
mockito-core-5.12.0.jar
mockito-junit-jupiter-5.12.0.jar
objenesis-3.3.jar
opentest4j-1.3.0.jar

ドキュメント

以下に Apache Ivy の configurations に関連するドキュメントを載せておく🔗

ant.apache.org

ant.apache.org

ant.apache.org

関連記事

kakakakakku.hatenablog.com

Casbin 認可ポリシーを DynamoDB に保存できるアダプター「python-dycasbin」を試した

Casbin で認可ポリシーを保存する1番簡単な選択肢は CSV ファイルだけど,アダプターを使うと認可ポリシーをデータベースで管理できる👌今回は PyCasbin で,ドキュメントに載っている DynamoDB Adapter (python-dycasbin) を使って,Casbin 認可ポリシーを Amazon DynamoDB に保存してみた.試したログをまとめておく❗️

casbin.org

また LoadPolicy()SavePolicy() など,アダプターインタフェースを実装すればカスタムアダプターも実装できる👀

casbin.org

👾 requirements.txt

今回は PyCasbin と python-dycasbin の最新バージョンを使う👌

casbin==1.36.2
python-dycasbin==0.4.1

pypi.org

pypi.org

👾 model.conf

そして,Casbin Model はドキュメントにも載っている ACL (Access Control List) をそのまま使う👌

[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act

casbin.org

👾 add-policy.py

まず,python-dycasbin を使って Amazon DynamoDB テーブルに Casbin 認可ポリシーを登録する.今回はサンプルとして Amazon S3 を参考に s3:PutObject アクションを制御することにした.

  • sub (kakakakakku) は obj1 (arn:aws:s3:::sample-bucket-1/*) に対して act (s3:PutObject) できる
  • sub (kakakakakku) は obj2 (arn:aws:s3:::sample-bucket-2/*) に対して act (s3:PutObject) できる

また Amazon DynamoDB は LocalStack を使ってローカル環境でアクセスできるようにしておく✅

import casbin
from python_dycasbin import adapter

adapter = adapter.Adapter(endpoint_url='http://localhost:4566')

e = casbin.Enforcer('model.conf', adapter, True)

sub = 'kakakakakku'
obj1 = 'arn:aws:s3:::sample-bucket-1/*'
obj2 = 'arn:aws:s3:::sample-bucket-2/*'
act = 's3:PutObject'

e.add_policy(sub, obj1, act)
e.add_policy(sub, obj2, act)

add-policy.py を実行すると,Adapter 経由で自動的に Amazon DynamoDB テーブル(デフォルトだと casbin_rule)が構築される.Adapter クラスの初期化 (__init__) で毎回テーブルを構築しようと試みるところは微妙に感じるし,テーブル設定として WCU / RCU が 10 で固定されているのも厳しい気がした😇オンデマンドキャパシティを選択するオプションがあっても良さそう.

github.com

登録した Casbin 認可ポリシーは Amazon DynamoDB テーブルでは以下のように登録されていた.

$ awslocal dynamodb scan --table-name casbin_rule
{
    "Items": [
        {
            "ptype": {
                "S": "p"
            },
            "v0": {
                "S": "kakakakakku"
            },
            "v1": {
                "S": "arn:aws:s3:::sample-bucket-1/*"
            },
            "id": {
                "S": "bf571b8e16cef131aeac79c15a800421"
            },
            "v2": {
                "S": "s3:PutObject"
            }
        },
        {
            "ptype": {
                "S": "p"
            },
            "v0": {
                "S": "kakakakakku"
            },
            "v1": {
                "S": "arn:aws:s3:::sample-bucket-2/*"
            },
            "id": {
                "S": "46808bf495552ff34293bdb9f8f0a9b2"
            },
            "v2": {
                "S": "s3:PutObject"
            }
        }
    ],
    "Count": 2,
    "ScannedCount": 2,
    "ConsumedCapacity": null
}

登録されたデータ(アイテム)は LocalStack Resource Browser でも確認できた👌

👾 enforce.py

今度は arn:aws:s3:::sample-bucket-1/*arn:aws:s3:::sample-bucket-2/*arn:aws:s3:::sample-bucket-3/* に対する s3:PutObject アクションの許可を確認する.

import casbin
from python_dycasbin import adapter

adapter = adapter.Adapter(endpoint_url='http://localhost:4566')

e = casbin.Enforcer('model.conf', adapter, True)

sub = 'kakakakakku'
obj1 = 'arn:aws:s3:::sample-bucket-1/*'
obj2 = 'arn:aws:s3:::sample-bucket-2/*'
obj3 = 'arn:aws:s3:::sample-bucket-3/*'
act = 's3:PutObject'

e.enforce(sub, obj1, act)
e.enforce(sub, obj2, act)
e.enforce(sub, obj3, act)

期待通りに True, True, False と認可判断ができていた❗️

2024-06-21 19:00:00,000 Request: kakakakakku, arn:aws:s3:::sample-bucket-1/*, s3:PutObject ---> True
2024-06-21 19:00:00,000 Request: kakakakakku, arn:aws:s3:::sample-bucket-2/*, s3:PutObject ---> True
2024-06-21 19:00:00,000 Request: kakakakakku, arn:aws:s3:::sample-bucket-3/*, s3:PutObject ---> False

ちなみに python-dycasbin の実装を読むと,Amazon DynamoDB テーブルに対して Scan を実行していて気になる💨Amazon DynamoDB テーブルに GSI (Global Secondary Index) を追加して,Scan ではなく Query を使うように変更したプルリクエストも出ているようだった.

👾 remove-policy.py

最後は arn:aws:s3:::sample-bucket-2/* に対する Casbin 認可ポリシーを削除してみる.

import casbin
from python_dycasbin import adapter

adapter = adapter.Adapter(endpoint_url='http://localhost:4566')

e = casbin.Enforcer('model.conf', adapter, True)

sub = 'kakakakakku'
obj2 = 'arn:aws:s3:::sample-bucket-2/*'
act = 's3:PutObject'

e.remove_policy(sub, obj2, act)

もう一度 s3:PutObject アクションの許可を確認したところ,期待通りに True, False, False と認可判断ができていた❗️

2024-06-21 19:00:00,000 Request: kakakakakku, arn:aws:s3:::sample-bucket-1/*, s3:PutObject ---> True
2024-06-21 19:00:00,000 Request: kakakakakku, arn:aws:s3:::sample-bucket-2/*, s3:PutObject ---> False
2024-06-21 19:00:00,000 Request: kakakakakku, arn:aws:s3:::sample-bucket-3/*, s3:PutObject ---> False

Amazon DynamoDB テーブルのデータ(アイテム)も削除されていた👌

$ awslocal dynamodb scan --table-name casbin_rule
{
    "Items": [
        {
            "ptype": {
                "S": "p"
            },
            "v0": {
                "S": "kakakakakku"
            },
            "v1": {
                "S": "arn:aws:s3:::sample-bucket-1/*"
            },
            "id": {
                "S": "bf571b8e16cef131aeac79c15a800421"
            },
            "v2": {
                "S": "s3:PutObject"
            }
        }
    ],
    "Count": 1,
    "ScannedCount": 1,
    "ConsumedCapacity": null
}

Powertools for AWS Lambda (Python) の Validation で UUID フォーマットをバリデーションする

Powertools for AWS Lambda (Python) の Validation でプロパティの「UUID フォーマット」をチェックする場合は以下のようにスキーマを実装すると良さそう👌

{
    '$schema': 'http://json-schema.org/draft-07/schema',
    'type': 'object',
    'required': ['id'],
    'properties': {
        'id': {
            'type': 'string',
            'pattern': '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$',
        },
    },
}

便利な Powertools for AWS Lambda (Python) の Validation に関しては前に紹介記事を書いた📝

kakakakakku.hatenablog.com

公式ドキュメントも参照📝

docs.powertools.aws.dev

検証環境

今回は AWS SAM を使って Amazon API Gateway (REST API) と AWS Lambda 関数を構築する.Amazon API Gateway に POST リクエストを送ってバリデーションロジックの確認をした❗️

動作確認

👾 template.yaml

AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31

Resources:
  Function:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: powertools-validation-uuid
      CodeUri: src/
      Handler: app.lambda_handler
      Runtime: python3.12
      Architectures:
        - x86_64
      Layers:
        - arn:aws:lambda:ap-northeast-1:017000801446:layer:AWSLambdaPowertoolsPythonV2:73
      Events:
        Api:
          Type: Api
          Properties:
            Path: /
            Method: POST

👾 app.py

今回はサンプルとしてパラメータが正常であれば 200 OK を返して,異常であれば 400 BAD_REQUEST を返すようにした.

import json
from http import HTTPStatus

from aws_lambda_powertools.utilities.validation import SchemaValidationError, envelopes, validate

SCHEMA = {
    '$schema': 'http://json-schema.org/draft-07/schema',
    'type': 'object',
    'required': ['id'],
    'properties': {
        'id': {
            'type': 'string',
            'pattern': '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$',
        },
    },
}


def main(event):
    try:
        validate(event=event, schema=SCHEMA, envelope=envelopes.API_GATEWAY_REST)
    except SchemaValidationError as e:
        return {
            'statusCode': HTTPStatus.BAD_REQUEST,
            'body': json.dumps({'message': e.validation_message}),
        }

    return {
        'statusCode': HTTPStatus.OK,
        'body': json.dumps({'message': 'ok'}),
    }


def lambda_handler(event, context):
    return main(event)


if __name__ == '__main__':
    with open('./events/event.json', 'r') as f:
        event = json.load(f)

    response = main(event)
    print(response['body'])

動作確認

ok

event.json

{
    "id": "92380de4-cfe1-4b9c-9a67-2928dcba10e0"
}

UUID Generator で取得した UUID を id プロパティに指定すると ok になった🙆‍♂

$ curl -s -X POST --data @event.json ${ENDPOINT}
{"message": "ok"}

data.id must match pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$

event.json

{
    "id": "92380de4-cfe1-4b9c-9a67-2928dcba10e00"
}

UUID 以外のフォーマット(1文字余分に追加した)で id プロパティを指定するとバリデーションエラーになった🙅‍♂️

$ curl -s -X POST --data @event.json ${ENDPOINT}
{"message": "data.id must match pattern ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"}

JSON Schema 2019-09

ちなみに JSON Schema 自体はビルトインフォーマットとして uuid をサポートしているけど,Powertools for AWS Lambda (Python) の Validation で 'format': 'uuid' とスキーマを実装すると Unknown format: uuid というエラーが出てしまう🔥

json-schema.org

実は uuid フォーマットは JSON Schema 2019-09(1つ前は JSON Schema draft-07)でサポートされていて,Powertools for AWS Lambda (Python) の Validation が依存している fastjsonschema (Fast JSON schema for Python)draft-04draft-06draft-07 のみをサポートしているという背景があって現状では使えなかった📝

horejsek.github.io

boto3 Config で Amazon S3 Transfer Acceleration エンドポイントを使えるようにする

Amazon S3 で Transfer Acceleration を有効化すると,エッジロケーションを活用してオブジェクトを高速にアップロード・ダウンロードできるようになる.そして bucketname.s3-accelerate.amazonaws.com というエンドポイントが追加される👀

aws.amazon.com

docs.aws.amazon.com

boto3 x Amazon S3 Transfer Acceleration

Python (boto3) で Amazon S3 Transfer Acceleration エンドポイントを使う場合は Boto3 Config の use_accelerate_endpoint で設定できる👌今回は検証も兼ねて Config 設定の動作確認をしてみた.

boto3.amazonaws.com

準備

自宅(東京)から動作確認をするため,今回は us-east-1(バージニア)に Amazon S3 バケットを作った.そして aws s3api put-bucket-accelerate-configuration コマンドで Amazon S3 Transfer Acceleration を有効化した.あと mkfile 50m 50mb コマンドで 50MB のダミーファイルを作っておく📁

$ aws s3 mb s3://transfer-acceleration-sandbox --region us-east-1
$ aws s3api put-bucket-accelerate-configuration --bucket transfer-acceleration-sandbox --accelerate-configuration Status=Enabled --region us-east-1

1. Config 設定なし

今回はエンドポイントを確認するため botocore のデバッグログを出力できるようにしておく👌

import time
import uuid

import boto3
import botocore

botocore.session.Session().set_debug_logger()

session = boto3.Session(profile_name='xxxxx', region_name='us-east-1')

s3_client = session.client(
    's3',
)

start = time.time()
s3_client.upload_file('50mb', 'transfer-acceleration-sandbox', f'50mb-{uuid.uuid4().hex}')
end = time.time()

print(f'{end - start} seconds')

実行すると s3.amazonaws.com エンドポイントにリクエストを送信していた🛜

Sending http request:
(中略)
url=https://transfer-acceleration-sandbox.s3.amazonaws.com/50mb-a3637f080158476fa0333dc838d700d7?uploadId=xxx
(中略)

計3回実行したアップロード時間は以下だった💡

  • 35.75226593017578 seconds
  • 36.11813402175903 seconds
  • 42.113038063049316 seconds

2. Config 設定あり

今度は Config で use_accelerate_endpoint を設定する❗️

import time
import uuid

import boto3
import botocore

botocore.session.Session().set_debug_logger()

session = boto3.Session(profile_name='xxxxx', region_name='us-east-1')

s3_client = session.client(
    's3',
    config=boto3.session.Config(
        s3={
            'use_accelerate_endpoint': True,
        },
    ),
)

start = time.time()
s3_client.upload_file('50mb', 'transfer-acceleration-sandbox', f'50mb-{uuid.uuid4().hex}')
end = time.time()

print(f'{end - start} seconds')

実行すると今度は s3-accelerate.amazonaws.com エンドポイントにリクエストを送信していた🛜

Sending http request:
(中略)
url=https://transfer-acceleration-sandbox.s3-accelerate.amazonaws.com/50mb-8b5a312964c94d5a8e803655e1aa6792?uploadId=xxx
(中略)

計3回実行したアップロード時間は以下だった💡速くなってる〜 \( 'ω')/

  • 14.759350061416626 seconds
  • 12.844246625900269 seconds
  • 13.640271663665771 seconds