GCP ApiGateway + CloudRun で CORS を設定する

構成 GCP ApiGateway -> CloudRun ApiGateway で OpenAPI yaml を定義している 1. OpenAPI yaml で x-google-endpoints に allowCors: True を設定して、バックエンドでCORSリクエストを処理する x-google-endpoints: - name: ${managed_service} allowCors: True # バックエンドでCORSリクエストを処理する 2. CloudRun で OPTIONS メソッドを設定する // 以下のように cors ミドルウェアを挟む http.HandleFunc("/example", cors(user.Handler)) // 定義はこんな感じ func cors(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Method == "OPTIONS" { slog.Info("==> Reply OPTIONS Request", "status", http.StatusNoContent) w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") w.Header().Set("Access-Control-Allow-Headers", "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization") w.Header().Set("Access-Control-Expose-Headers", "Content-Length,Content-Range") w.Header().Set("Access-Control-Max-Age", "1728000") // 20 days: reduce preflight requests w.WriteHeader(http.StatusNoContent) // 204 return } next.ServeHTTP(w, r) } } Refs Endpoints の CORS サポートを有効にする | Cloud Endpoints with OpenAPI | Google Cloud

2024-11-15 ·  2024-11-15 · 1 分 · 105 文字

tflint Tips

はじめに tflint 利用時のメモ 想定するディレクトリ構成 環境は dev, prd など 環境毎にディレクトリを切る 各環境の main.tf から使用したい module を使う module は 機能単位でディレクトリを切る cognitoとかで切ってるけど、サービスの有効無効など、もう少し大きな単位で切っても良いイメージ ├── envs │ ├── dev │ │ ├── main.tf │ │ ├── outputs.tf │ │ ├── swagger.yaml │ │ ├── terraform.tfvars │ │ └── variables.tf │ └── prd │ ├── main.tf │ └── ... └── module_aws ├── cognito │ ├── main.tf │ ├── outputs.tf │ └── variables.tf └── s3 ├── main.tf ├── outputs.tf └── variables.tf tflint の設定ファイルの読み込み優先順位: tflint/docs/user-guide/config.md at master · terraform-linters/tflint File passed by the –config option File set by the TFLINT_CONFIG_FILE environment variable Current directory (./.tflint.hcl) Home directory (~/.tflint.hcl) awsでこんな設定をしてみた # 結構口うるさいタイプ plugin "terraform" { enabled = true preset = "all" } # `tflint --init` needed plugin "aws" { enabled = true # deep_check = true version = "0.34.0" source = "github.com/terraform-linters/tflint-ruleset-aws" } # rule "terraform_required_version" { # enabled = false # } # # rule "terraform_required_providers" { # enabled = false # } よくでてくるエラーは以下 variable.tf ではなく、variables.tf にする output は outputs.tf に書く outputs.tf は空でも要る output, variable には description を書く 無視する方法は以下 以下のように tflint-ignore を書く # tflint-ignore: terraform_required_version # tflint-ignore: terraform_required_providers module を再帰的にチェックはしない --recursive を指定しても、ロードしている module のなかまでチェックされない ref: tflint の仕様や使い方についてのメモ 対象のディレクトリ(module)に移動または、–chdirで指定して、モジュールのチェックを行う必要がある => 想定構成のディレクトリでは、構成のルートディレクトリから --recursive つけて tflint すればいい => envs配下も、module_aws 配下もチェックされる Terraform 的には 暗黙的 プロバイダの継承 を推奨? Providers Within Modules - Configuration Language | Terraform | HashiCorp Developer For convenience in simple configurations, a child module automatically inherits default provider configurations from its parent. This means that explicit provider blocks appear only in the root module, and downstream modules can simply declare resources for that provider and have them automatically associated with the root provider configurations. ...

2024-11-08 ·  2024-11-09 · 2 分 · 396 文字

FullCalendar + shadcn Dialog で再レンダリングされるのを防ぐ

問題 FullCalendar と 同じページに shadcn Dialog を組み込み、 shadcn (Radix UI) Dialog の open ステートを useState で管理していると、 ダイアログの open/close 時に FullCalendar が再レンダリングされてしまう。 GoogleCalendarのイベントや、独自イベントがある場合、再レンダリングされると、 イベントが消えてしまう。 解決方法 ref, forwardRefs, useImperativeHandle を使って、再レンダリングを防ぐ。 以下詳細 まずは Dialog側 import { Ref, useMemo, useCallback, useState, forwardRef, useImperativeHandle, } from 'react'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, } from '@/components/ui/dialog'; type SampleProps = { }; export interface SampleDialogRefInterface { open: () => void; close: () => void; } const SampleDialogButtonInner = (_: SampleProps, ref: Ref<SampleDialogRefInterface>) => { const [isOpen, setIsOpen] = useState(false); useImperativeHandle(ref, () => ({ open: () => setIsOpen(true), close: () => setIsOpen(false), })); return ( <Dialog open={isOpen} onOpenChange={setIsOpen}> {/* 外部からOpenしたいので、DialogTriggerは使わない */} {/* <DialogTrigger asChild> <Button>ボタン</Button> </DialogTrigger> */} <DialogContent className='h-[90%] overflow-hidden'> {/* 省略 */} </DialogContent> </Dialog> ); // } }; const SampleDialogButton = forwardRef(SampleDialogButtonInner); export default SampleDialogButton; SampleProps 親側から渡す props SampleDialogRefInterface ダイアログとして公開したいメソッドを定義する _: SampleProps props は使わないので、_ で受け取る。propsがないとエラー ref: Ref<SampleDialogRefInterface> ref を受け取る const [isOpen, setIsOpen] = useState(false); ダイアログの open/close ステートを useState で管理 <Dialog open={isOpen} onOpenChange={setIsOpen}> isOpen, setIsOpen を Dialog の open, onOpenChange で紐付け useImperativeHandle ref として公開して、外部から呼ばれた場合の動作を定義 Dialogの開閉を外部から制御できるようにする const SampleDialogButton = forwardRef(SampleDialogButtonInner) forwardRef で ref を受け取るコンポーネントを作成 FullCalendar 側(親側) import { EventClickArg } from '@fullcalendar/core'; import FullCalendar from '@fullcalendar/react'; import { useRef, useCallback } from 'react'; import SampleDialogButton, { SampleDialogRefInterface, } from '@/app/_components/SampleDialogButton'; export default function Calendar() { const sampleDialogRef = useRef<SampleDialogRefInterface>(null); const onDateClick = useCallback( (arg: DateClickArg) => { const target = arg.dateStr; sampleDialogRef.current.open(); }, [], ); const onEventClick = useCallback((arg: EventClickArg) => { if (!sampleDialogRef.current) { return; } sampleDialogRef.current.open(); }, []); return ( <> <SampleDialogButton ref={sampleDialogRef} /> <FullCalendar dateClick={onDateClick} eventClick={onEventClick} /> </> ); } const sampleDialogRef = useRef<SampleDialogRefInterface>(null); Dialog側の公開IF型の ref を作成 const onDateClick = useCallback( FullCalendar の dateClick イベントハンドラ クリックされた日付を取得し、ダイアログを開く const onEventClick = useCallback((arg: EventClickArg) => { FullCalendar の eventClick イベントハンドラ クリックされたイベントを取得し、ダイアログを開く <SampleDialogButton ref={sampleDialogRef} /> Dialogコンポーネントをレンダリング ref に sampleDialogRef を渡す Refs refとforwardRef 、 useImperativeHandleについて調べた時のノート #React - Qiita

2024-09-03 ·  2024-10-23 · 2 分 · 302 文字

GitHub Actions で maven release コマンドを発行する with tailscale で

はじめに VPN配下でしかアクセスできない Maven リポジトリに Tailscale を利用してアクセスして、 Github Actions で maven release コマンドを発行する方法が必要になったので、構築した。 以下詳細 事前準備 各種 Secret 値を用意する Tailscale で TS_OAUTH_CLIENT_ID, TS_OAUTH_SECRET を発行 GitHub Actions で以下プラグインを使用する為 tailscale/github-action: A GitHub Action to connect your workflow to your Tailscale network. GitHub Actions 内から Maven Release コマンドで Git リポジトリにコミットする必要があるので、コミットユーザ用に 秘密鍵設定を行う 以下コマンドで秘密鍵を作成しておく ssh-keygen -t rsa -b 4096 -f id_rsa -C "github-actions" Github Actions で 使用する Secrets の登録 GitHub - Settings - Secrets and variables - Actions - Repository secrets に 以下キーで登録 TS_OAUTH_CLIENT_ID: Tailscale で発行した OAuth Client ID TS_OAUTH_SECRET: Tailscale で発行した OAuth Secret ACTIONS_SSH_PRIVATE_KEY: 作成した秘密鍵の内容 GitHub - Settings - Deploy Keys に公開鍵を登録して、生成した秘密鍵でのアクセスを許可する GitHub - Settings - Deploy keys - Add deploy key から 公開鍵を登録 Github Actions workflow の実装 name: Build or Release on: workflow_dispatch: inputs: target: type: choice required: true description: Deploy target options: - sample - sample-mybatis stage: type: choice required: false description: Specify stage(mvn profile) if needed(No need if release build(force prd)) options: - dev - prd release: type: boolean description: Is release? default: false env: CACHE_PATH_DOCKER: /tmp/docker_image jobs: CleanupRuns: runs-on: ubuntu-latest steps: - uses: rokroskar/workflow-run-cleanup-action@v0.2.2 env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" BuildOrRelease: needs: CleanupRuns runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 1 - name: Show inputs run: | echo "target: ${{ github.event.inputs.target }}" echo "stage: ${{ github.event.inputs.stage }}" echo "release: ${{ github.event.inputs.release }}" echo "GITHUB_ACTIONS: $GITHUB_ACTIONS" - name: Tailscale # GitHub Actions で Tailscale を利用する uses: tailscale/github-action@v2 with: oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }} oauth-secret: ${{ secrets.TS_OAUTH_SECRET }} tags: tag:ci - name: Install SSH Key # GitHub Actions Maven Release ビルドを行う際に使用する秘密鍵をRunnnerのユーザの鍵として登録しておく uses: shimataro/ssh-key-action@v2 with: key: ${{ secrets.ACTIONS_SSH_PRIVATE_KEY }} name: id_rsa known_hosts: unnecessary - name: Prepare Maven Build run: | git config --global user.name 'GitHub Actions' git config --global user.email 'github-actions@users.noreply.github.com' mkdir -p ~/.m2 # ここで作成する settings.xml は 以下を参照 cat<.github/workflows/assets/settings.xml.part.head.tmpl > ~/.m2/settings.xml eval "echo \"$(cat<.github/workflows/assets/settings.xml.part.body.variable.tmpl)\"" >> ~/.m2/settings.xml cat<.github/workflows/assets/settings.xml.part.tail.tmpl >> ~/.m2/settings.xml # cat ~/.m2/settings.xml mkdir -p ${{ env.CACHE_PATH_DOCKER }} env: NEXUS_USERNAME: ${{ secrets.NEXUS_USERNAME }} NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }} - name: Cache Docker Image Define # 下記で使用する Docker イメージのキャッシュを定義 id: cache_docker_image_define run: | echo "tag=sample-app:1.0.0" >> $GITHUB_OUTPUT echo "tar_path=${{ env.CACHE_PATH_DOCKER }}/docker_image.tar" >> $GITHUB_OUTPUT - name: Enable Docker Image # この Workflow が正常に終了したら path をキャッシュ。 # すでに key が存在する場合、path にキャッシュをリストアする。 # GitHub Actions で Docker イメージのキャッシュとリストア(with actions/cache@v2) #GitHubActions - Qiita # https://qiita.com/KEINOS/items/433182390132c274649a id: cache_docker_image uses: actions/cache@v4 with: path: ${{ env.CACHE_PATH_DOCKER }} key: ${{ steps.cache_docker_image_define.outputs.tag }} - name: Load Docker image if exists # キャッシュがある場合は tar をロードしてイメージ一覧に追加 if: steps.cache_docker_image.outputs.cache-hit == 'true' run: docker load --input ${{ steps.cache_docker_image_define.outputs.tar_path }} - name: Cache Maven Repository # Maven リポジトリのキャッシュを定義 uses: actions/cache@v4 id: cache_maven_repository with: path: ~/.m2/repository restore-keys: | mvn-repository- - name: Build ${{ github.event.inputs.target }} ${{ github.event.inputs.stage }} id: build run: | opt_stage= input_stage=${{ github.event.inputs.stage }} if [[ -n "$input_stage" ]]; then opt_stage="stage=$input_stage" fi make build-${{ github.event.inputs.target }} $opt_stage # ここで ビルドに使用した Docker イメージを保存 # タグを複数並べて保存できる docker save --output ${{ steps.cache_docker_image_define.outputs.tar_path }} ${{ steps.cache_docker_image_define.outputs.tag }} - name: 'Upload Artifact' # 成果物の保存 uses: actions/upload-artifact@v4 with: name: ${{ github.event.inputs.target }}-${{ github.event.inputs.stage }} path: | sample-app/target/sample-app-*-bin.zip Notice: needs: BuildOrRelease if: always() steps: - name: Notice # if: ${{ env.release == 'true' }} # if: ${{ secrets.SLACK_WEBHOOK_URL != '' }} uses: 8398a7/action-slack@v3 env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} with: status: custom fields: workflow,job,commit,repo,ref,author,took custom_payload: | { attachments: [{ color: '${{ needs.BuildOrRelease.outputs.result }}' === 'success' ? 'good' : 'danger', text: `${{ needs.BuildOrRelease.outputs.build_or_release }} Succeseeded by ${process.env.AS_AUTHOR}\n${{ needs.BuildOrRelease.outputs.artifact_files }}`, }] } Maven ~/.m2/settings.xml の設定例 nexus-app の部分が Tailscale でアクセスしたい Host名 Tailscale 上の Machine 名でアクセスできる <?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> <pluginGroups> </pluginGroups> <proxies> </proxies> <servers> <!-- pom.xml 内で 指定する Gitユーザ情報 --> <server> <id>github-git-ssh</id> <username>git</username> <privateKey>~/.ssh/id_rsa</privateKey> <passphrase/> </server> <server> <id>app-public-repository</id> <username>${NEXUS_USERNAME}</username> <password>${NEXUS_PASSWORD}</password> </server> <server> <id>app-snapshot-repository</id> <username>${NEXUS_USERNAME}</username> <password>${NEXUS_PASSWORD}</password> </server> <server> <id>app-release-repository</id> <username>${NEXUS_USERNAME}</username> <password>${NEXUS_PASSWORD}</password> </server> </servers> <mirrors> <mirror> <id>central-unblock-url</id> <mirrorOf>central</mirrorOf> <name>Central Repository</name> <url>https://repo.maven.apache.org/maven2</url> </mirror> <mirror> <id>app-public-repository</id> <mirrorOf>app-public-repository</mirrorOf> <name>app Maven2 Public Repository</name> <url>http://nexus-app/nexus/content/groups/public/</url> </mirror> <mirror> <id>app-release-repository</id> <mirrorOf>app-release-repository</mirrorOf> <name>app Maven2 Release Repository</name> <url>http://nexus-app/nexus/content/repositories/releases/</url> </mirror> </mirrors> <profiles> </profiles> </settings> <?xml version="1.0"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> : <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.scm.id>github-git-ssh</project.scm.id> </properties> : <scm> <connection>scm:git:ssh://git@github.com:/owner/repo-name.git</connection> <developerConnection>scm:git:ssh://git@github.com:/owner/repo-name.git</developerConnection> <tag>HEAD</tag> </scm> :

2024-08-01 ·  2026-07-01 · 4 分 · 645 文字

TypeScriptで型宣言を優先する理由

はじめに TypeScriptで型の管理方法にはいくつかの選択肢があり、型アサーション、型宣言、型推論などがある。 疑問 なぜ型アサーションよりも型宣言を優先すべきなのか。 型アサーションは便利に見えるが、実際にどのような問題を引き起こす可能性があるのか。 まとめ 型アサーションよりも型宣言を優先すべき型宣言よりも型推論できる状態にすべき 型宣言を使用することで、型安全性が向上し、コードの可読性が高まる。 IDEのサポートが強化され、リファクタリングが容易になる。 TypeScriptの型推論機能を活用することで、冗長なコードを減らせる。 以下詳細 型安全性の向上 型宣言は変数やパラメータの型を明示的に定義するため、コンパイル時に型チェックが行われる。 型アサーションはコンパイラに対して「この型で間違いない」と主張するため、潜在的なエラーを見逃す可能性がある。 コードの可読性 型宣言はコードの意図をより明確に示す。他の開発者がコードを読む際に、変数や関数の期待される型が一目で分かる。 IDEのサポート 型宣言を使用すると、IDEの自動補完やリファクタリング機能がより効果的に機能する。 リファクタリングの容易さ 型宣言を使用していると、後でコードを変更する際に型の不整合を早期に発見できる。 型アサーションを多用していると、これらの問題が見逃される可能性が高くなる。 型推論の活用 TypeScriptの型推論は非常に強力。 型宣言を適切に使用することで、型推論をより効果的に活用でき、冗長なコードを減らせる。 簡単な例: // 型アサーション(避けるべき) const value = getSomeValue() as string; // 型宣言(推奨) const value: string = getSomeValue(); // さらに良い方法(型推論を活用) const value = getSomeValue(); // valueの型は自動的に推論される 型宣言を使用することで、コードの品質と保守性が向上し、長期的にはより堅牢なアプリケーションの開発につながる。 ただし、どうしても型アサーションが必要な場合もあるので、完全に避けることはできない。適切な判断で使い分けることが重要。 Refs TypeScriptの型入門 #TypeScript - Qiita 【TypeScript】できるだけ as を避ける理由

2024-07-26 ·  2024-08-02 · 1 分 · 58 文字