Go Cloud クライアントライブラリ の App Engine Admin API(cloud.google.com/go/appengine/apiv1) を使用して、App Engine の defaultサービスに dispatch_rules(送信ルート)を設定するサンプルコードです。
実行方法
PROJECT_ID=<GCのプロジェクトID> MODE=<ADD or DELETE> go run main.go
サンプルコード
main.go
package main
import (
"context"
"fmt"
"log/slog"
"os"
"strings"
appengine "cloud.google.com/go/appengine/apiv1"
"cloud.google.com/go/appengine/apiv1/appenginepb"
"google.golang.org/protobuf/types/known/fieldmaskpb"
)
func main() {
gaeRepo := InjectGAERepository()
if gaeRepo == nil {
slog.Error("Error creating App Engine client")
return
}
defer gaeRepo.Close()
// Application 情報の取得
app, err := gaeRepo.GetDetails()
if err != nil {
slog.Error("Error getting App Engine details", "err", err)
return
}
slog.Info("App Engine Application Info", "ID", app.Id, "Name", app.Name, "AuthDomain", app.AuthDomain)
var newRules []*appenginepb.UrlDispatchRule
switch strings.ToUpper(os.Getenv("MODE")) {
case "ADD":
for i, d := range app.DispatchRules {
slog.Info(fmt.Sprintf("Existing Dispatch Rule %d", i), "Domain", d.Domain, "Path", d.Path, "Service", d.Service)
if d.Domain == DispatchDomain &&
d.Path == DispatchPathPattern &&
d.Service == DispatchServiceName {
slog.Info("Dispatch rule already exists. No changes made.")
return
}
}
// 新しいルールを追加(順番が優先順位になるため、先頭に追加)
newRule := &appenginepb.UrlDispatchRule{
Domain: DispatchDomain,
Path: DispatchPathPattern,
Service: DispatchServiceName,
}
newRules = append([]*appenginepb.UrlDispatchRule{newRule}, app.DispatchRules...)
case "DELETE":
// 既存の DispatchRules から該当ルールのみを除外
isRuleRemoved := false
for i, d := range app.DispatchRules {
slog.Info(fmt.Sprintf("Existing Dispatch Rule %d", i), "Domain", d.Domain, "Path", d.Path, "Service", d.Service)
if d.Domain == DispatchDomain &&
d.Path == DispatchPathPattern &&
d.Service == DispatchServiceName {
isRuleRemoved = true
continue // このルールを除外
}
newRules = append(newRules, d)
}
if !isRuleRemoved {
slog.Info("No matching dispatch rule found to remove. No changes made.")
return
}
default:
slog.Error("Invalid MODE. Set MODE to 'ADD' or 'DELETE'.")
return
}
req := gaeRepo.GenerateDispatchRulesUpdateRequest(newRules)
_, err = gaeRepo.UpdateApplication(req)
if err != nil {
slog.Error("Error updating App Engine application", "err", err)
return
}
slog.Info("Dispatch rules updated successfully.")
}
const (
DispatchDomain = "*" // 送信ルートのドメイン
DispatchPathPattern = "/routing-target-url" // 送信ルートのパスパターン
DispatchServiceName = "default" // urlパターン(送信ルートのパスパターン)に一致したリクエストを処理するサービスの名前
)
type GAERepository struct {
client *appengine.ApplicationsClient
}
func InjectGAERepository() *GAERepository {
client, err := NewAppEngineClient()
if err != nil {
slog.Error("Error creating App Engine client", "err", err)
return nil
}
return &GAERepository{client: client}
}
func NewAppEngineClient() (*appengine.ApplicationsClient, error) {
ctx := context.Background()
client, err := appengine.NewApplicationsClient(ctx)
if err != nil {
msg := fmt.Sprintf("Failed to create App Engine ApplicationsClient: %v", err)
slog.Error(msg)
return nil, err
}
slog.Info("App Engine ApplicationsClient initialized successfully.")
return client, nil
}
func (r *GAERepository) Close() error {
return r.client.Close()
}
// Application 情報の取得
func (r *GAERepository) GetDetails() (*appenginepb.Application, error) {
ctx := context.Background()
projectID := os.Getenv("PROJECT_ID")
if projectID == "" {
msg := "Environment variable PROJECT_ID is not set."
slog.Error(msg)
return nil, fmt.Errorf(msg)
}
// Application 名は "apps/{projectID}"
req := &appenginepb.GetApplicationRequest{
Name: "apps/" + projectID,
}
// Application 情報の取得
app, err := r.client.GetApplication(ctx, req)
if err != nil {
msg := fmt.Sprintf("Failed to get application info for project %s: %v", projectID, err.Error())
slog.Error(msg)
return nil, err
}
slog.Info("Application details retrieved successfully.", "id", app.Id)
return app, nil
}
// dispatch_rulesを更新するためのリクエストを生成
func (r *GAERepository) GenerateDispatchRulesUpdateRequest(newRules []*appenginepb.UrlDispatchRule) *appenginepb.UpdateApplicationRequest {
projectID := os.Getenv("PROJECT_ID")
req := &appenginepb.UpdateApplicationRequest{
Name: "apps/" + projectID, // Application 名は "apps/{projectID}"
Application: &appenginepb.Application{
DispatchRules: newRules,
},
UpdateMask: &fieldmaskpb.FieldMask{
Paths: []string{"dispatch_rules"}, // dispatchRules のみを更新する
// フィールド名参照: https://cloud.google.com/appengine/docs/admin-api/reference/rest/v1/apps#Application
},
}
return req
}
func (r *GAERepository) UpdateApplication(req *appenginepb.UpdateApplicationRequest) (*appenginepb.Application, error) {
ctx := context.Background()
op, err := r.client.UpdateApplication(ctx, req)
if err != nil {
msg := fmt.Sprintf("Failed to initiate application update: %v", err)
slog.Error(msg)
return nil, err
}
updatedApp, err := op.Wait(ctx)
if err != nil {
msg := fmt.Sprintf("Error waiting for application update to complete: %v", err)
slog.Error(msg)
return nil, err
}
slog.Info("Application updated successfully.", "id", updatedApp.Id)
return updatedApp, nil
}
MODE=ADD にすると default サービスに /routing-target-url が追加され、
MODE=DELETE にすると そのルートが削除されます。