0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Go】App Engine Admin API を使って GAE サービスに dispatch_rules(送信ルート)を設定するサンプルコード

0
Posted at

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 にすると そのルートが削除されます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?