diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..25c7f37 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @acompany-develop/platform diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..be67c63 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,16 @@ +# Description of changes・変更内容の説明 + +# Ticket link・チケットリンク +https://www.notion.so/acompany-ac/<チケットID> + +# Testing method・実施したテスト方法 (CI/Manual) +- UTs : +- E2E test : +- Integration test : + + +# Remarks・備考 + + +# Review guidelines +[Generic PR Review guidelines・一般的な PR レビューのガイドライン](https://www.notion.so/acompany-ac/Generic-PR-Review-guidelines-PR-13b269d8558680f6918bdccee7812efb?pvs=4) diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml new file mode 100644 index 0000000..d3299de --- /dev/null +++ b/.github/workflows/ai-review.yml @@ -0,0 +1,31 @@ +name: "Run ai-reviewer" + +permissions: + pull-requests: write + contents: read + +on: + pull_request: + types: [opened, reopened, ready_for_review] + workflow_dispatch: + +jobs: + run-review: + runs-on: ubuntu-latest + steps: + - name: AI Review Bot + uses: acompany-develop/ai-reviewer@latest + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + LANGUAGE: |- + Japanese + English + PRIORITY_FILTER: |- + PRIORITY:HIGH + PRIORITY:MEDIUM + PRIORITY:LOW + PRIORITY:POSITIVE + # ref: https://www.notion.so/acompany-ac/Generic-PR-Review-guidelines-PR-13b269d8558680f6918bdccee7812efb + EXTRA_PROMPT: |- + ${{ vars.PR_REVIEW_POLICY_FOR_LLM }} diff --git a/.github/workflows/reassign-reviewer.yml b/.github/workflows/reassign-reviewer.yml new file mode 100644 index 0000000..b6300ee --- /dev/null +++ b/.github/workflows/reassign-reviewer.yml @@ -0,0 +1,59 @@ +name: Re-assign reviewers even after approval +on: + pull_request_review: + types: [submitted] + +jobs: + keep_team_reviewers: + runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: read + steps: + - name: Re-assign platform Team After Approval + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.WORKFLOW_PAT }} + script: | + const { owner, repo, number } = context.issue; + const review = context.payload.review; + + console.log(`Review submitted by ${review.user.login} with state: ${review.state}`); + + // Check if this was an approval + if (review.state === 'approved') { + console.log('Review was an approval, checking team reviewers status'); + + try { + const teamSlug = "platform"; + + // Re-request review using the correct team slug format + await github.rest.pulls.requestReviewers({ + owner, + repo, + pull_number: number, + team_reviewers: [teamSlug] + }); + + console.log(`Successfully re-requested review from team: ${teamSlug}`); + } catch (error) { + console.error('Error re-requesting team review:'); + console.error(`Status: ${error.status}`); + console.error(`Message: ${error.message}`); + + // If re-requesting fails, fall back to a comment + try { + await github.rest.issues.createComment({ + owner, + repo, + issue_number: number, + body: '✅ This PR has been approved, but other members of @acompany-develop/platform may still want to review it.' + }); + console.log('Added comment mentioning the team as fallback'); + } catch (commentError) { + console.error('Error adding comment:', commentError.message); + } + } + } else { + console.log('Review was not an approval, no action taken'); + } diff --git a/.github/workflows/ruff-check.yml b/.github/workflows/ruff-check.yml new file mode 100644 index 0000000..fe2797e --- /dev/null +++ b/.github/workflows/ruff-check.yml @@ -0,0 +1,141 @@ +name: "Ruff Format & Lint" + +permissions: + contents: read + pull-requests: write + +on: + pull_request: + types: [opened, synchronize, reopened] + paths: + - '**.py' + workflow_dispatch: + +jobs: + ruff-check: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.ref }} + + - name: Install ruff without running check or format + uses: astral-sh/ruff-action@v3 + with: + args: "--version" + + - name: Run Ruff Format & Lint Check + id: ruff-format-lint + run: | + lf='\n' + message="## Ruff チェック結果" + + # フォーマットチェック + message+="${lf}### フォーマット" + FORMAT_FAILED=0 + ruff format --check > format_output.txt 2>&1 || FORMAT_FAILED=1 + FORMAT_OUTPUT=$(cat format_output.txt) + + echo "$FORMAT_OUTPUT" + + # フォーマット結果の処理 + if [ "$FORMAT_FAILED" == "1" ]; then + message+="${lf}❌ フォーマットの問題が検出されました。" + message+="${lf}\`\`\`" + message+="${lf}${FORMAT_OUTPUT}" + message+="${lf}\`\`\`" + else + message+="${lf}✅ フォーマットの問題はありませんでした。" + if [ -n "$FORMAT_OUTPUT" ]; then + message+="${lf}\`\`\`" + message+="${lf}${FORMAT_OUTPUT}" + message+="${lf}\`\`\`" + fi + fi + + # # Lintチェック + message+="${lf}### Lint" + LINT_FAILED=0 + ruff check > lint_output.txt 2>&1 || LINT_FAILED=1 + LINT_OUTPUT=$(cat lint_output.txt) + + echo "$LINT_OUTPUT" + + # Lint結果の処理 + if [ "$LINT_FAILED" == "1" ]; then + message+="${lf}❌ Lint の問題が検出されました。" + message+="${lf}\`\`\`" + message+="${lf}${LINT_OUTPUT}" + message+="${lf}\`\`\`" + else + message+="${lf}✅ Lint の問題はありませんでした。" + if [ -n "$LINT_OUTPUT" ]; then + message+="${lf}\`\`\`" + message+="${lf}${LINT_OUTPUT}" + message+="${lf}\`\`\`" + fi + fi + + # Base64エンコードして出力 + echo "message=$(echo "$message" | base64 -w 0)" >> $GITHUB_OUTPUT + echo "format_failed=$FORMAT_FAILED" >> $GITHUB_OUTPUT + echo "lint_failed=$LINT_FAILED" >> $GITHUB_OUTPUT + + - name: Comment PR with Results + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + // Base64デコードして元のメッセージを取得 + const base64Message = '${{ steps.ruff-format-lint.outputs.message }}'; + const message = Buffer.from(base64Message, 'base64').toString('utf-8'); + + // 改行を正しく処理するために、\nをリテラル改行に置換 + const formattedMessage = message.replace(/\\n/g, '\n'); + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number + }); + + const botComment = comments.find(comment => + comment.user.login === 'github-actions[bot]' && + comment.body.includes('Ruff チェック結果') + ); + + if (botComment) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: formattedMessage + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: formattedMessage + }); + } + + - name: Output Results for non-PR events + if: github.event_name != 'pull_request' + run: | + # Base64デコードして表示(printfで改行を処理) + printf "%b" "$(echo "${{ steps.ruff-format-lint.outputs.message }}" | base64 -d)" + + - name: Fail if checks failed + if: steps.ruff-format-lint.outputs.format_failed == '1' || steps.ruff-format-lint.outputs.lint_failed == '1' + run: | + if [ "${{ steps.ruff-format-lint.outputs.format_failed }}" == "1" ]; then + echo "::error::フォーマットチェックが失敗しました" + fi + if [ "${{ steps.ruff-format-lint.outputs.lint_failed }}" == "1" ]; then + echo "::error::Lintチェックが失敗しました" + fi + exit 1 diff --git a/functions/cross_table/function/handler.py b/functions/cross_table/function/handler.py new file mode 100644 index 0000000..32466b0 --- /dev/null +++ b/functions/cross_table/function/handler.py @@ -0,0 +1,146 @@ +import os +import sys +import traceback +from datetime import datetime + +sys.path.insert(0, "/work/function/packages") # functionが依存するパッケージのパス + +WORK_DIR = "/work" +INPUT_A_PATH = f"{WORK_DIR}/inputs/input_1" +INPUT_B_PATH = f"{WORK_DIR}/inputs/input_2" +OUTPUT_A_PATH = f"{WORK_DIR}/outputs/output_1" +OUTPUT_B_PATH = f"{WORK_DIR}/outputs/output_2" +DOWNLOAD_DIR = "downloads/" + +THRESHOLD = 2 # 集計数がこの値未満の行は出力されない + + +def print_log(msg): + current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + try: + os.makedirs(DOWNLOAD_DIR, exist_ok=True) + with open(os.path.join(DOWNLOAD_DIR, "app.log"), "a") as log_file: + log_file.write(f"[{current_time}]:[handler.py]: {msg}\n") + os.makedirs(OUTPUT_A_PATH, exist_ok=True) + with open(os.path.join(OUTPUT_A_PATH, "app.log"), "a") as log_file: + log_file.write(f"[{current_time}]:[handler.py]: {msg}\n") + os.makedirs(OUTPUT_B_PATH, exist_ok=True) + with open(os.path.join(OUTPUT_B_PATH, "app.log"), "a") as log_file: + log_file.write(f"[{current_time}]:[handler.py]: {msg}\n") + except Exception: + pass # 出力ディレクトリへの書き込みに失敗しても続行 + + +# メモリ使用量と実行時間を計測するための関数 +def get_memory_usage(): + """現在のメモリ使用量を取得""" + import psutil + + process = psutil.Process(os.getpid()) + memory_info = process.memory_info() + return memory_info.rss / 1024 / 1024 # MB単位 + + +def print_memory_usage(stage_name): + """メモリ使用量を表示""" + try: + memory_mb = get_memory_usage() + print_log(f"[{stage_name}] メモリ使用量: {memory_mb:.2f} MB") + except Exception as e: + print_log(f"Failed to get memory usage: {e.__class__.__name__}") + return 0 + + +def run(): + try: + print_memory_usage("開始時") + print_log("handler.run: Started.") + + import polars as pl + + print_log("handler.run: Imported successfully.") + + # 入力データを読み込む + lf_a = pl.scan_csv(os.path.join(INPUT_A_PATH, "input_a.csv")) + print_log("handler.run: Read input_a.csv successfully.") + print_memory_usage("input_a.csv読み込み後") + lf_b = pl.scan_csv(os.path.join(INPUT_B_PATH, "input_b.csv")) + print_log("handler.run: Read input_b.csv successfully.") + print_memory_usage("input_b.csv読み込み後") + + # キー列を特定 + key_a = lf_a.columns[0] + key_b = lf_b.columns[0] + + # 2. Join前のリネーム処理 + # dataset_a の列名をリネーム (id以外) + cols_to_rename_a = [col for col in lf_a.columns if col != key_a] + rename_map_a = {col: f"0:{col}" for col in cols_to_rename_a} + lf_a_renamed = lf_a.rename(rename_map_a) + print_memory_usage("0_列名リネーム後") + + # dataset_b の列名をリネーム (id以外) + cols_to_rename_b = [col for col in lf_b.columns if col != key_b] + rename_map_b = {col: f"1:{col}" for col in cols_to_rename_b} + lf_b_renamed = lf_b.rename(rename_map_b) + print_memory_usage("1_列名リネーム後") + print_log("handler.run: Renamed columns successfully.") + + # 3. リネーム済みのLazyFrameをJoin + lf_joined = lf_a_renamed.join( + lf_b_renamed, left_on=key_a, right_on=key_b, how="inner" + ) + print_log("handler.run: Merged successfully with leftmost columns.") + print_memory_usage("Join後") + + # 4. 全Attribute列でGroup By & Count + # id以外の全ての列(a_...とb_...)をグループ化のキーに指定 + attribute_cols = [ + col for col in lf_joined.columns if col != key_a and col != key_b + ] + + lf_summary = lf_joined.group_by(attribute_cols).agg( + pl.count().alias("number_of_rows") + ) + print_memory_usage("Group By & Count後") + + # 5. 列の整形 + # number_of_rows を先頭に持ってくる + # 列名をソートしてから指定 + sorted_cols = ["number_of_rows"] + sorted(attribute_cols) + lf_final = lf_summary.select(sorted_cols) + + filtered = lf_final.filter(pl.col("number_of_rows") >= THRESHOLD) + print_log(f"handler.run: Filtered successfully with threshold {THRESHOLD}.") + print_memory_usage("Filter後") + + # 計算を実行して結果を表示 + final_result = filtered.collect(streaming=True) + print_memory_usage("Collect後") + + # CSV形式で出力 + try: + os.makedirs(OUTPUT_A_PATH, exist_ok=True) + os.makedirs(OUTPUT_B_PATH, exist_ok=True) + + final_result.write_csv(os.path.join(OUTPUT_A_PATH, "output.csv")) + print_log("handler.run: Saved a's output.csv successfully.") + print_memory_usage("a's output.csv保存後") + final_result.write_csv(os.path.join(OUTPUT_B_PATH, "output.csv")) + print_log("handler.run: Saved b's output.csv successfully.") + print_memory_usage("b's output.csv保存後") + except Exception as e: + print_log(f"handler.run: Error saving results: {str(e)}") + + print_log("handler.run: DONE.") + print_memory_usage("終了時") + + except BaseException as e: + print_log(f"handler.run: ERROR: {str(e)}") + try: + os.makedirs(DOWNLOAD_DIR, exist_ok=True) + with open(os.path.join(DOWNLOAD_DIR, "error.log"), "w") as error_file: + traceback.print_exc(file=error_file) + except Exception: + pass # エラーログの書き込みに失敗しても続行 + raise e diff --git a/functions/cross_table/inputs/input_1/input_a.csv b/functions/cross_table/inputs/input_1/input_a.csv new file mode 100644 index 0000000..65d4c3a --- /dev/null +++ b/functions/cross_table/inputs/input_1/input_a.csv @@ -0,0 +1,10 @@ +id,height,weight +id1,170,70 +id2,180,60 +id3,170,60 +id4,170,70 +id5,180,60 +id6,180,60 +id7,180,60 +id8,180,60 +id9,170,60 \ No newline at end of file diff --git a/functions/cross_table/inputs/input_2/input_b.csv b/functions/cross_table/inputs/input_2/input_b.csv new file mode 100644 index 0000000..a149696 --- /dev/null +++ b/functions/cross_table/inputs/input_2/input_b.csv @@ -0,0 +1,10 @@ +id,dominant +id1,right +id2,right +id3,right +id4,left +id5,left +id6,right +id7,right +id8,left +id9,right \ No newline at end of file diff --git a/functions/cross_table/requirements.txt b/functions/cross_table/requirements.txt new file mode 100644 index 0000000..355f04d --- /dev/null +++ b/functions/cross_table/requirements.txt @@ -0,0 +1,8 @@ +numpy==1.26.4 +python-dateutil==2.8.2 +pytz==2023.3 +six==1.16.0 +tzdata==2023.3 +polars==0.19.19 +psutil==5.9.5 +pyarrow==14.0.1 \ No newline at end of file diff --git a/functions/join/function/handler.py b/functions/join/function/handler.py new file mode 100644 index 0000000..5d051cb --- /dev/null +++ b/functions/join/function/handler.py @@ -0,0 +1,78 @@ +import os +import sys +import traceback +from datetime import datetime + +""" +Note: External packages are supposed to be installed in function/packages. +""" +sys.path.insert(0, "/work/function/packages") # functionが依存するパッケージのパス + +WORK_DIR = "/work" # functionが参照可能なディレクトリパス +INPUT_A_PATH = ( + f"{WORK_DIR}/inputs/input_1" # acompany専用入力データのmount先パス +) +INPUT_B_PATH = ( + f"{WORK_DIR}/inputs/input_2" # bcompany専用入力データのmount先パス +) +OUTPUT_A_PATH = ( + f"{WORK_DIR}/outputs/output_1" # acompany専用出力データのmount先パス +) + + +def print_log(msg): + current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + try: + os.makedirs(OUTPUT_A_PATH, exist_ok=True) + with open(os.path.join(OUTPUT_A_PATH, "app.log"), "a") as log_file: + log_file.write(f"[{current_time}]:[handler.py]: {msg}\n") + except Exception: + pass # 出力ディレクトリへの書き込みに失敗しても続行 + + +# メモリ使用量と実行時間を計測するための関数 +def print_memory_usage(): + try: + import psutil + + process = psutil.Process(os.getpid()) + memory_info = process.memory_info() + print_log( + f"Current memory usage: {memory_info.rss / 1024**2:.2f} MB" + ) # RSS: Resident Set Size (in MB) + return memory_info.rss + except Exception as e: + print_log(f"Failed to get memory usage. {str(e)}") + return 0 + + +def run(): + try: + import pandas as pd + + df_a = pd.read_csv(os.path.join(INPUT_A_PATH, "input_a.csv")) + df_b = pd.read_csv(os.path.join(INPUT_B_PATH, "input_b.csv")) + + # Join on leftmost columns of both dataframes + key_a = df_a.columns[0] + key_b = df_b.columns[0] + result = pd.merge(df_a, df_b, left_on=key_a, right_on=key_b) + # Drop the redundant key + result = result.drop(columns=[key_a, key_b]) + + print_log("join result shape:" + str(result.shape)) + print_memory_usage() + + os.makedirs(OUTPUT_A_PATH, exist_ok=True) + + # 結果の保存 + result.to_csv(os.path.join(OUTPUT_A_PATH, "output.csv"), index=False) + + except BaseException as e: + try: + os.makedirs(OUTPUT_A_PATH, exist_ok=True) + with open(os.path.join(OUTPUT_A_PATH, "error.log"), "w") as error_file: + traceback.print_exc(file=error_file) + except Exception: + pass # エラーログの書き込みに失敗しても続行 + raise e diff --git a/functions/join/inputs/input_1/input_a.csv b/functions/join/inputs/input_1/input_a.csv new file mode 100644 index 0000000..ab1238b --- /dev/null +++ b/functions/join/inputs/input_1/input_a.csv @@ -0,0 +1,11 @@ +id,name,age,income +1,Alice,25,50000 +2,Bob,30,75000 +3,Charlie,35,100000 +4,Dave,40,120000 +5,Eve,45,150000 +6,Frank,50,180000 +7,Grace,55,200000 +8,Heidi,60,250000 +9,Ivan,65,300000 +10,Jane,70,350000 \ No newline at end of file diff --git a/functions/join/inputs/input_2/input_b.csv b/functions/join/inputs/input_2/input_b.csv new file mode 100644 index 0000000..7bede2f --- /dev/null +++ b/functions/join/inputs/input_2/input_b.csv @@ -0,0 +1,11 @@ +id,occupation,education +1,Engineer,Bachelor +2,Doctor,Master +3,Lawyer,Doctorate +4,Teacher,Bachelor +5,Artist,Master +6,Manager,Master +7,Consultant,Doctorate +8,Designer,Bachelor +9,Architect,Master +10,Professor,Doctorate \ No newline at end of file diff --git a/functions/join/requirements.txt b/functions/join/requirements.txt new file mode 100644 index 0000000..483a6fe --- /dev/null +++ b/functions/join/requirements.txt @@ -0,0 +1,3 @@ +numpy==1.23.5 +pandas==2.0.3 +psutil==5.9.5 diff --git a/notebooks/img/apc_sequence.svg b/notebooks/img/apc_sequence.svg new file mode 100644 index 0000000..cdaa7c2 --- /dev/null +++ b/notebooks/img/apc_sequence.svg @@ -0,0 +1 @@ +AutoPrivacy DCRAutoPrivacy DCR手順 2: 環境変数設定手順 3: ライブラリのインストール手順 4: APC-CLIのセットアップ手順 5: プロファイル設定手順 6: 入出力設定ファイルの生成手順 7: 認証とヘルスチェック手順 8: プロジェクト設定手順 9: 関数ストレージへのアップロード手順 10: Cleanroomデプロイ手順 11: データのCleanroomへのコピー手順 12: Cleanroomの実行手順 13: 結果のダウンロード手順 14: クリーンアップ(オプション)企業間でデータの機密性を保ちながら安全なデータ連携が完了ユーザー1ユーザー2.envファイルで環境変数を設定.envファイルで環境変数を設定packagesディレクトリに依存ライブラリをインストールpackagesディレクトリに依存ライブラリをインストールCLIバイナリをダウンロード・インストールCLIバイナリをダウンロード・インストールapc --version でインストール確認apc --version でインストール確認apc configure --profile user1 で設定を行い、USER IDを生成apc configure --profile user2 で設定を行い、USER IDを生成encrypted-files.yamlを作成apc auth-login --profile user1認証完了apc auth-login --profile user2認証完了apc healthcheck --profile user1ヘルスチェック結果apc healthcheck --profile user2ヘルスチェック結果apc set-project PROJECT_ID --profile user1apc set-project PROJECT_ID --profile user2apc function-storage upload --source function/ --profile user1FunctionStoragePathを返却apc cleanroom deploy --source FunctionStoragePath --name join_app --profile user1Cleanroomのデプロイapc cleanroom data cp input_1/ join_app:input_1 --profile user1apc cleanroom data cp input_2/ join_app:input_2 --profile user2apc cleanroom run join_app --profile user1Cleanroomの実行実行完了通知apc cleanroom data cp join_app:output_1 output_1/ --profile user1user1用の結果を提供結果を復号して確認apc cleanroom data cp join_app:output_2 output_2/ --profile user2user2用の結果を提供結果を復号して確認apc cleanroom delete join_app --profile user1Cleanroomアプリケーションの削除apc function-storage delete FunctionStoragePath --profile user1アップロードされた関数の削除ユーザー1ユーザー2 \ No newline at end of file diff --git a/notebooks/mmd/README.md b/notebooks/mmd/README.md new file mode 100644 index 0000000..4d266fb --- /dev/null +++ b/notebooks/mmd/README.md @@ -0,0 +1,7 @@ +## mermaidのSVG画像への変換 +```bash +# コマンドのインストール +npm install -g @mermaid-js/mermaid-cli +# 変換 +mmdc -i -o +``` \ No newline at end of file diff --git a/notebooks/mmd/apc_sequence.mmd b/notebooks/mmd/apc_sequence.mmd new file mode 100644 index 0000000..d139db2 --- /dev/null +++ b/notebooks/mmd/apc_sequence.mmd @@ -0,0 +1,87 @@ +sequenceDiagram + actor user1 as ユーザー1 + actor user2 as ユーザー2 + participant AutoPrivacyDCR as AutoPrivacy DCR + + %% 手順 2: 環境変数設定 + Note over user1, user2: 手順 2: 環境変数設定 + user1->>user1: .envファイルで環境変数を設定 + user2->>user2: .envファイルで環境変数を設定 + + %% 手順 3: ライブラリのインストール + Note over user1, user2: 手順 3: ライブラリのインストール + user1->>user1: packagesディレクトリに依存ライブラリをインストール + user2->>user2: packagesディレクトリに依存ライブラリをインストール + + %% 手順 4: APC-CLIのセットアップ + Note over user1, user2: 手順 4: APC-CLIのセットアップ + user1->>user1: CLIバイナリをダウンロード・インストール + user2->>user2: CLIバイナリをダウンロード・インストール + user1->>user1: apc --version でインストール確認 + user2->>user2: apc --version でインストール確認 + + %% 手順 5: プロファイル設定 + Note over user1, user2: 手順 5: プロファイル設定 + user1->>user1: apc configure --profile user1 で設定を行い、USER IDを生成 + user2->>user2: apc configure --profile user2 で設定を行い、USER IDを生成 + + %% 手順 6: 入出力設定ファイルの生成 + Note over user1, user2: 手順 6: 入出力設定ファイルの生成 + user1->>user1: encrypted-files.yamlを作成 + + %% 手順 7: 認証とヘルスチェック + Note over user1, user2: 手順 7: 認証とヘルスチェック + user1->>AutoPrivacyDCR: apc auth-login --profile user1 + AutoPrivacyDCR->>user1: 認証完了 + user2->>AutoPrivacyDCR: apc auth-login --profile user2 + AutoPrivacyDCR->>user2: 認証完了 + + user1->>AutoPrivacyDCR: apc healthcheck --profile user1 + AutoPrivacyDCR->>user1: ヘルスチェック結果 + user2->>AutoPrivacyDCR: apc healthcheck --profile user2 + AutoPrivacyDCR->>user2: ヘルスチェック結果 + + %% 手順 8: プロジェクト設定 + Note over user1, user2: 手順 8: プロジェクト設定 + user1->>AutoPrivacyDCR: apc set-project PROJECT_ID --profile user1 + user2->>AutoPrivacyDCR: apc set-project PROJECT_ID --profile user2 + + %% 手順 9: 関数ストレージへのアップロード + Note over user1, user2: 手順 9: 関数ストレージへのアップロード + user1->>AutoPrivacyDCR: apc function-storage upload --source function/ --profile user1 + AutoPrivacyDCR->>user1: FunctionStoragePathを返却 + + %% 手順 10: Cleanroomデプロイ + Note over user1, user2: 手順 10: Cleanroomデプロイ + user1->>AutoPrivacyDCR: apc cleanroom deploy --source FunctionStoragePath --name join_app --profile user1 + AutoPrivacyDCR->>AutoPrivacyDCR: Cleanroomのデプロイ + + %% 手順 11: データのCleanroomへのコピー + Note over user1, user2: 手順 11: データのCleanroomへのコピー + user1->>AutoPrivacyDCR: apc cleanroom data cp input_1/ join_app:input_1 --profile user1 + user2->>AutoPrivacyDCR: apc cleanroom data cp input_2/ join_app:input_2 --profile user2 + + %% 手順 12: Cleanroomの実行 + Note over user1, user2: 手順 12: Cleanroomの実行 + user1->>AutoPrivacyDCR: apc cleanroom run join_app --profile user1 + AutoPrivacyDCR->>AutoPrivacyDCR: Cleanroomの実行 + AutoPrivacyDCR->>user1: 実行完了通知 + + %% 手順 13: 結果のダウンロード + Note over user1, user2: 手順 13: 結果のダウンロード + user1->>AutoPrivacyDCR: apc cleanroom data cp join_app:output_1 output_1/ --profile user1 + AutoPrivacyDCR->>user1: user1用の結果を提供 + user1->>user1: 結果を復号して確認 + + user2->>AutoPrivacyDCR: apc cleanroom data cp join_app:output_2 output_2/ --profile user2 + AutoPrivacyDCR->>user2: user2用の結果を提供 + user2->>user2: 結果を復号して確認 + + %% 手順 14: クリーンアップ(オプション) + Note over user1, user2: 手順 14: クリーンアップ(オプション) + user1->>AutoPrivacyDCR: apc cleanroom delete join_app --profile user1 + AutoPrivacyDCR->>AutoPrivacyDCR: Cleanroomアプリケーションの削除 + user1->>AutoPrivacyDCR: apc function-storage delete FunctionStoragePath --profile user1 + AutoPrivacyDCR->>AutoPrivacyDCR: アップロードされた関数の削除 + + Note over user1, user2: 企業間でデータの機密性を保ちながら
安全なデータ連携が完了 diff --git a/notebooks/tutorial/basic_apc_cli_tutorial.ipynb b/notebooks/tutorial/basic_apc_cli_tutorial.ipynb new file mode 100644 index 0000000..34fb0c6 --- /dev/null +++ b/notebooks/tutorial/basic_apc_cli_tutorial.ipynb @@ -0,0 +1,757 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# APC-CLI 基本チュートリアル 実行フロー\n", + "\n", + "基本的に各ステップを上から順に実行することで、AutoPrivacy DCR上で関数を実行することができます。\n", + "AutoPrivacy DCRの操作をCLIから行うツールとして、APC-CLIというコマンドラインツールを用います。\n", + "\n", + "このJupyterNotebookは[こちら](https://github.com/acompany-develop/dcr-docs-examples/blob/main/notebooks/tutorial/basic_apc_cli_tutorial.ipynb)に上がっているため、ステップ1の環境変数さえ設定すれば、あとは各セルを実行するだけで、AutoPrivacy DCRで処理を実行できるようになっています。\n", + "\n", + "## 概要\n", + "\n", + "このスクリプトは以下の主要なステップで構成されています:\n", + "1. 全体概要\n", + "2. 環境変数設定\n", + "3. ライブラリのインストール\n", + "4. APC-CLI のセットアップ\n", + "5. プロファイル設定\n", + "6. 入出力の設定ファイルの生成\n", + "7. 認証とヘルスチェック\n", + "8. プロジェクト設定\n", + "9. 関数ストレージへのアップロード\n", + "10. Cleanroom デプロイ\n", + "11. データの Cleanroom へのコピー\n", + "12. Cleanroom の実行\n", + "13. 結果のダウンロード\n", + "14. クリーンアップ" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "-------------" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. 全体概要\n", + "Cleanroomを用いることで、二者間で安全にデータを共有し実行することが可能となります。APC-CLI実行時に基本的にprofile(user)を指定するのですが、profileが異なれば異なるユーザーから実行されることを想定しているため、異なるマシンから実行することが可能です(同一のマシンから実行することも可能)。\n", + "具体的なフロー図は以下のようになります。\n", + "\n", + "
フロー図\n", + "\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "-------------" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. 環境変数設定\n", + "ファイル・ディレクトリ構成としては、今回は以下のようなものを想定します。この詳細については[こちら](https://acompany-develop.github.io/autoprivacy-cloud/apc-dcr/user-guide/user-files/function-directory.html)から参照できます。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "```\n", + "FUNCTION_SOURCE_PATH\n", + "├── function # 関数のパス\n", + "│ └── handler.py\n", + "│ └── packages # 依存パッケージのパス\n", + "├── inputs # 入力データのパス\n", + "│ ├── input_1\n", + "│ └── input_2\n", + "└── outputs # 出力データのパス\n", + " ├── output_1\n", + " └── output_2\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "このチュートリアルではjoin関数をCleanroom上で実行します。join関数の全実装は[こちら](https://github.com/acompany-develop/dcr-docs-examples/blob/main/functions/join/function/handler.py)のようになります。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "実行に必要な環境変数を.envファイルに書き込みます。`API_URL`, `ATTESTATION_URL`, `ATTESTATION_API_VERSION`, `MR_ENCLAVE`, `MR_SIGNER`, `CLIENT_ID1`, `CLIENT_SECRET1`, `CLIENT_ID2`, `CLIENT_SECRET2`, `PROJECT_ID`という環境変数の値はAutoPrivacy DCRサービス提供者から提供されたものを使用します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "%%bash\n", + "# 基本パスを変数として定義。今回は、本リポジトリ構成に合わせている。\n", + "FUNCTION_SOURCE_PATH=\"../../functions/join\"\n", + "\n", + "# .envファイルを動的に生成\n", + "cat > .env << EOF\n", + "profile1=\"user1\" # マシンに保存される設定に紐づく識別子。ユーザーが自由に設定することが出来る。\n", + "profile2=\"user2\" # マシンに保存される設定に紐づく識別子。ユーザーが自由に設定することが出来る。\n", + "API_URL=\n", + "ATTESTATION_URL=\n", + "ATTESTATION_API_VERSION=\n", + "MR_ENCLAVE=\n", + "MR_SIGNER=\n", + "CLIENT_ID1=\n", + "CLIENT_SECRET1=\n", + "CLIENT_ID2=\n", + "CLIENT_SECRET2=\n", + "PROJECT_ID=\n", + "FUNCTION_SOURCE_PATH=\"${FUNCTION_SOURCE_PATH}\"\n", + "ENCRYPTED_FILES_PATH=\"${FUNCTION_SOURCE_PATH}/encrypted-files.yaml\" # 入出力設定ファイルのパス\n", + "FUNCTION_DIRECTORY_PATH=\"${FUNCTION_SOURCE_PATH}/function\" # 関数のパス\n", + "INPUT_1_PATH=\"${FUNCTION_SOURCE_PATH}/inputs/input_1\" # 入力データのパス\n", + "INPUT_2_PATH=\"${FUNCTION_SOURCE_PATH}/inputs/input_2\" # 入力データのパス\n", + "OUTPUT_1_PATH=\"${FUNCTION_SOURCE_PATH}/outputs/output_1\" # 出力データのパス\n", + "OUTPUT_2_PATH=\"${FUNCTION_SOURCE_PATH}/outputs/output_2\" # 出力データのパス\n", + "EOF" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + ".envファイルを環境変数として読み込みます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "%%bash\n", + "pip install python-dotenv" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from dotenv import load_dotenv\n", + "\n", + "# .envファイルを読み込み\n", + "load_dotenv(\".env\", override=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "-------------" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. ライブラリのインストール\n", + "\n", + "Cleanroom上での関数実行で使用するライブラリを`packages`ディレクトリにインストールします。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "%%bash\n", + "pip install --platform manylinux2014_x86_64 \\\n", + " --only-binary=:all: \\\n", + " --python-version 3.10 \\\n", + " --target=$FUNCTION_SOURCE_PATH/function/packages \\\n", + " -r $FUNCTION_SOURCE_PATH/requirements.txt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "-------------" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. APC-CLI のセットアップ\n", + "\n", + "詳細なAPC-CLIのセットアップ方法については、[こちら](https://acompany-develop.github.io/autoprivacy-cloud/apc-cli/getting-started/installation.html)から参照できます。\n", + "\n", + "まず、APC-CLIをダウンロードします。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "%%bash\n", + "\n", + "# OSを判定\n", + "OS=$(uname -s)\n", + "\n", + "case $OS in\n", + " \"Darwin\")\n", + " # macOS\n", + " echo \"macOS detected\"\n", + " wget https://github.com/acompany-develop/apc-cli/releases/download/1.1.1/apc-darwin-arm64.zip\n", + " unzip apc-darwin-arm64.zip\n", + " mv apc-darwin-arm64 apc\n", + " chmod +x apc\n", + " ;;\n", + " \"Linux\")\n", + " # Linux\n", + " echo \"Linux detected\"\n", + " wget https://github.com/acompany-develop/apc-cli/releases/download/1.1.1/apc-linux-x64.zip\n", + " unzip apc-linux-x64.zip\n", + " mv apc-linux-x64 apc\n", + " chmod +x apc\n", + " ;;\n", + " *)\n", + " echo \"Unknown OS: $OS\"\n", + " exit 1\n", + " ;;\n", + "esac" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "ダウンロードしたAPC-CLIのパスを通します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "os.environ[\"PATH\"] = os.path.dirname(os.path.abspath(\"apc\")) + \":\" + os.environ[\"PATH\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "APC-CLIが正しくインストールされているか確認します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "%%bash\n", + "apc --version" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "-------------" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. プロファイル設定\n", + "各プロファイルに対して種設定情報を入力し、User IDを生成します。\n", + "今回のチュートリアルでは標準出力されるUserIDを環境変数に読み込むために以下のようにコマンドを実行します。\n", + "コマンドの詳細は [configure コマンド リファレンス](https://acompany-develop.github.io/autoprivacy-cloud/apc-cli/commands/configure.html) から参照できます。\n", + "\n", + "まずは、`profile1`について設定します。出力された`USER_ID`は次の入出力設定のステップで使用します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "%%bash\n", + "configure_output_1=$(expect <`と``を5で出力された`profile1`と`profile2`のUser IDに置き換えてください。**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "%%bash\n", + "cat > $ENCRYPTED_FILES_PATH << EOF\n", + "inputs:\n", + " input_1: &user_a_id \n", + " input_2: &user_b_id \n", + "outputs:\n", + " output_1: *user_a_id\n", + " output_2: *user_b_id\n", + "EOF\n", + "cat $ENCRYPTED_FILES_PATH" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "-------------" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. 認証とヘルスチェック\n", + "\n", + "各プロファイルでログインします。コマンドの詳細は [auth-login コマンド リファレンス](https://acompany-develop.github.io/autoprivacy-cloud/apc-cli/commands/auth-login.html) から参照できます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "%%bash\n", + "apc auth-login --profile $profile1\n", + "apc auth-login --profile $profile2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "各プロファイルで、サーバーに対してヘルスチェックを行い、正常にサーバーと通信ができているかを確認します。コマンドの詳細は [healthcheck コマンド リファレンス](https://acompany-develop.github.io/autoprivacy-cloud/apc-cli/commands/healthcheck.html) から参照できます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "%%bash\n", + "apc healthcheck --profile $profile1\n", + "apc healthcheck --profile $profile2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "-------------" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 8. プロジェクト設定\n", + "\n", + "各プロファイルで同じプロジェクトを設定します。コマンドの詳細は [set-project コマンド リファレンス](https://acompany-develop.github.io/autoprivacy-cloud/apc-cli/commands/set-project.html) から参照できます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "%%bash\n", + "apc set-project $PROJECT_ID --profile $profile1\n", + "apc set-project $PROJECT_ID --profile $profile2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "-------------" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 9. 関数ストレージへのアップロード\n", + "\n", + "関数ディレクトリのパスを指定した上で、実行する関数をCleanroom上にアップロードします。コマンドの詳細は [function-storage コマンド リファレンス](https://acompany-develop.github.io/autoprivacy-cloud/apc-cli/commands/function-storage.html#function-storage-upload) から参照できます。なお、出力された`FunctionStoragePath`は次のCleanroomデプロイのステップで使用します。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "%%bash\n", + "apc function-storage upload --source $FUNCTION_DIRECTORY_PATH --profile $profile1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "-------------" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 10. Cleanroom デプロイ\n", + "\n", + "アップロードされた関数を使用してCleanroomアプリケーションをデプロイします。\n", + "コマンドの詳細は [cleanroom deploy コマンド リファレンス](https://acompany-develop.github.io/autoprivacy-cloud/apc-cli/commands/cleanroom.html#cleanroom-deploy) から参照できます。\n", + "\n", + "**ただし、実行前に`--source`オプションの引数のを9で出力された`FunctionStoragePath`の値に置き換えてください。**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "%%bash\n", + "apc cleanroom deploy \\\n", + " --source \\\n", + " --name join_app \\\n", + " --runtime python3.10 \\\n", + " --handler handler.run \\\n", + " --encrypted-files $ENCRYPTED_FILES_PATH \\\n", + " --memory 2 \\\n", + " --profile $profile1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "-------------" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 11. データの Cleanroom へのコピー\n", + "\n", + "各プロファイルから、ローカルの入力データをCleanroom上にコピーします。コマンドの詳細は [cleanroom copyコマンド リファレンス](https://acompany-develop.github.io/autoprivacy-cloud/apc-cli/commands/cleanroom-data.html) から参照できます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "%%bash\n", + "apc cleanroom data cp $INPUT_1_PATH join_app:input_1 --profile $profile1\n", + "apc cleanroom data cp $INPUT_2_PATH join_app:input_2 --profile $profile2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "-------------" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 12. Cleanroom の実行\n", + "\n", + "デプロイされたCleanroom上のアプリケーションを実行します。コマンドの詳細は [cleanroom run コマンド リファレンス](https://acompany-develop.github.io/autoprivacy-cloud/apc-cli/commands/cleanroom.html#cleanroom-run) から参照できます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "%%bash\n", + "apc cleanroom run join_app --profile $profile1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "-------------" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 13. 結果のダウンロード\n", + "\n", + "実行結果を各プロファイルにダウンロードします。コマンドの詳細は [cleanroom data コマンド リファレンス](https://acompany-develop.github.io/autoprivacy-cloud/apc-cli/commands/cleanroom-data.html) から参照できます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "%%bash\n", + "apc cleanroom data cp join_app:output_1 $OUTPUT_1_PATH --profile $profile1\n", + "apc cleanroom data cp join_app:output_2 $OUTPUT_2_PATH --profile $profile2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "-------------" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 14. クリーンアップ\n", + "\n", + "使用したリソースに変更を加える必要があり、削除したい場合はリソースをクリーンアップすることができます。" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Cleanroom上のアプリケーションを削除します。コマンドの詳細は [cleanroom delete コマンド リファレンス](https://acompany-develop.github.io/autoprivacy-cloud/apc-cli/commands/cleanroom.html#cleanroom-delete) から参照できます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "%%bash\n", + "apc cleanroom delete join_app --profile $profile1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Cleanroom上にアップロードされた関数を削除します。コマンドの詳細は [function-storage delete コマンド リファレンス](https://acompany-develop.github.io/autoprivacy-cloud/apc-cli/commands/function-storage.html#function-storage-delete) から参照できます。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "%%bash\n", + "apc function-storage delete $FUNCTION_STORAGE_PATH --profile $profile1" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.17" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +}