Go

Prev Next

Classic/VPC環境で利用できます。

Go(Go Language)形式のアクションを作成して様々な方法で活用する方法と、そのユースケースを紹介します。

アクション作成

Goコードは、1つ以上の Goソースファイルを持つことができます。Goソースで作成した Actionのエントリポイントは、Main Packageにある関数です。Main関数のデフォルト名は Mainですが、ユーザーの選択により他の名前に変更できます。ただし、名前の先頭は常に大文字で表記されます。また、この Main関数は以下の説明のような形で作成する必要があります。

func Main(event map[string]interface{}) map[string]interface{}

Goで作成したコードは複数の関数を含めますが、main関数はプログラムのエントリポイントとして宣言される必要があります。この点を考慮して名前と場所を含めて「Hello World」を出力する Go形式の簡単なサンプルコードの helloは、次の通りです。

package main
import "log"

func Main(obj map[string]interface{}) map[string]interface{} {
  name, ok := obj["name"].(string)
  if !ok {
    name = "world"
  }
  msg := make(map[string]interface{})
  msg["message"] = "Hello, " + name + "!"
  log.Printf("name=%s\n", name)
  return msg
}

上記で作成したコードを使用してコンソールで「hello」という名前のアクションを作成する手順は、次の通りです。

cloudfunctions-exmaple-go_v2_01_ko

サポート可能な形態

実行環境でサポート可能な形態は、次の通りです。

  • AMD64アーキテクチャ用にコンパイルされた Linux ELF実行ファイルの実行可能なバイナリ
  • AMD64アーキテクチャ用にコンパイルされた Linux ELF実行ファイルが含まれていて、最上位に execという名前の実行ファイルが含まれている zipファイル
  • Goでコンパイルされた単一ソースファイル
  • 最上位レベル(フォルダ)に実行バイナリファイルを含まない zipファイルは、その後にコンパイルされて実行
参考

GOOS=Linuxと GOARCH=amd64ですべての Goをサポートするプラットフォームで、有効な形式のバイナリをクロスコンパイルできます。事前コンパイル機能を使用して実行環境と同じコンパイラを使用した方がより安全です。

Go Modulesを使ってアクションを作成

go1.19バージョンで外部ライブラリを使うためには、Go Modulesを使用します。Go Modulesを使用しない場合、ソースコードを直接含める必要があります。
以下は、go modを使ってアクションで下記のパッケージを取得する方法のステップバイステップガイドです。この方法はインターネット通信が可能である必要があり、VPC環境の場合は NAT G/Wの設定が必要です。NAT G/Wに関する詳細は、NAT Gatewayご利用ガイドをご参照ください。

import "github.com/rs/zerolog"
  1. 新しいモジュールの初期化

次のコマンドを実行して新しいモジュールを初期化します。このコマンドを実行すると、go.modファイルが作成されます。

go mod init <module>
  1. main.go作成

下記のサンプルコードを参照して main.goを作成します。

package main

import (
	"github.com/rs/zerolog"
	"github.com/rs/zerolog/log"
)

func init() {
	zerolog.TimeFieldFormat = ""
}

func Main(obj map[string]interface{}) map[string]interface{} {
	name, ok := obj["name"].(string)
	if !ok {
		name = "world"
	}
	log.Debug().Str("name", name).Msg("Hello")
	msg := make(map[string]interface{})
	msg["module-main"] = "Hello, " + name + "!"
	return msg
}
  1. 依存関係のインポート

以下のコマンドを実行して go.modファイルを更新し、go.sumファイルを作成します。

go get github.com/rs/zerolog@v1.19.0
  1. アクションパッケージングとアップロード

以下のコマンドを実行してパッケージを圧縮し、アップロードしてアクションを作成します。

zip -r action.zip go.mod go.sum main.go

パッケージと vendorを使用してアクションを作成

コードを作成していると、1つのアクションファイル以外に依存ファイルを一緒にパッケージングする必要がある場合があります。このような場合、関連するファイルを1つのファイルに圧縮してパッケージングし、圧縮されたファイルを利用してアクションを作成できます。

zipファイルを利用してアクションを作成する場合、可能な3つの形式は次の通りです。

  • mainパッケージの中にすべての機能を実装した場合
  • mainパッケージの他に一部パッケージを分離して構成した場合
  • 機能の実装のために、外部に依存関係にある部分を含む形で実装した場合(include third party dependencies)

すべての機能が基本パッケージにある場合、すべてのソースファイルを zipファイルの最上位レベルに配置します。

パッケージフォルダを使用する

一部機能がメイン関数の実行部分と異なるパッケージに属する場合、hello/のような形式でパッケージ名を作成してフォルダをパッケージングする必要があります。パッケージングのユースケースは、次の通りです。

golang-main-package/
- src/
   - main.go
   - hello/
       - hello.go
       - hello_test.go

テストを実行してエラーなしで編集するには、srcフォルダを使用する必要があります。基本パッケージの内容は src/の下位に、helloパッケージのソースコードは hello/フォルダに配置します。しかし、使用するためには下位パッケージを import "hello"のようにインポートする必要があります。これは、ローカル開発環境でコンパイルする場合、ユーザーの GOPATHに srcの上位ディレクトリを設定する必要があることを意味します。 ユーザーが VSCodeといったエディタを使用する場合、go.inferGopathオプションを有効にする必要があります。
ソースを送る際は、最上位ディレクトリではなく、srcフォルダの内容を以下のように圧縮してください。

cd src
zip -r ../hello.zip *
cd ..

上記の内容に対するサンプルコードは、次の通りです。

  • src/main.go
package main

import (
	"fmt"
	"hello"
)

// Main forwading to Hello
func Main(args map[string]interface{}) map[string]interface{} {
	fmt.Println("Main")
	return hello.Hello(args)
}
  • src/hello/hello.go
package hello

import (
	"fmt"
)

// Hello receive an event in format
// { "name": "Mike"}
// and returns a greeting in format
// { "greetings": "Hello, Mike"}
func Hello(args map[string]interface{}) map[string]interface{} {
	res := make(map[string]interface{})
	greetings := "world"
	name, ok := args["name"].(string)
	if ok {
		greetings = name
	}
	res["golang-main-package"] = "Hello, " + greetings
	fmt.Printf("Hello, %s\n", greetings)
	return res
}
  • src/hello/hello_test.go
package hello

import (
	"encoding/json"
	"fmt"
)

func ExampleHello() {
	var input = make(map[string]interface{})
	input["name"] = "Mike"
	output := Hello(input)
	json, _ := json.Marshal(output)
	fmt.Printf("%s", json)
	// Output:
	// Hello, Mike
	// {"golang-main-package":"Hello, Mike"}
}

func ExampleHello_noName() {
	var input = make(map[string]interface{})
	output := Hello(input)
	json, _ := json.Marshal(output)
	fmt.Printf("%s", json)
	// Output:
	// Hello, world
	// {"golang-main-package":"Hello, world"}
}

vendorフォルダを使用する

他の3rd-partyライブラリを使用する場合、ランタイムはコンパイルする際にインターネットを通じてそのライブラリをダウンロードするのではなく、vendorフォルダ構造を使用してダウンロードして配置する必要があります。ここでは、depツールを使用する方法について説明します。
vendorフォルダに srcフォルダ、パッケージフォルダ、vendorフォルダが含まれている必要があります。最上位フォルダでは動作しません。mainパッケージに含まれたファイルを使用するには、最上位フォルダではなく mainと明示された下位フォルダに配置します。例えば、ファイル src/hello/hello.goで以下のようなパッケージを importする必要があるとします。

import "github.com/sirupsen/logrus"

このような場合、vendorフォルダを作成するには、以下の手順に従ってください。

  1. depツールをインストールします。

  2. src/helloフォルダに移動します。

    cd ./src/hello
    
    • srcフォルダではなく、src/helloであることにご注意ください。
  3. DEPPROJECTROOT=$(realpath $PWD/../..) dep initを実行します。

    • このツールは、使用されたライブラリを検索・検知して2つのマニフェストファイル Gopkg.lock、Gopkg.tomlを作成
    • 既にマニフェストファイルがある場合、dep ensureを実行すると vendorフォルダが作成され、依存ファイルをダウンロード

構造をまとめると、次の通りです。

golang-hello-vendor
- src/
    - hello.go
    - hello/
      - Gopkg.lock
      - Gopkg.toml
         - hello.go
         - vendor/
            - github.com/...
            - golang.org/...

上記の内容に対するサンプルコードは、次の通りです。

  • hello.go
package main

import (
	"fmt"
	"hello"
)

// Main forwading to Hello
func Hello(args map[string]interface{}) map[string]interface{} {
	fmt.Println("Entering Hello")
	return hello.Hello(args)
}
  • hello/hello.go
package hello

import (
	"os"
	"github.com/sirupsen/logrus"
)

var log = logrus.New()

// Hello receive an event in format
// { "name": "Mike"}
// and returns a greeting in format
// { "greetings": "Hello, Mike"}
func Hello(args map[string]interface{}) map[string]interface{} {
	log.Out = os.Stdout
	res := make(map[string]interface{})
	greetings := "world"
	name, ok := args["name"].(string)
	if ok {
		greetings = name
	}
	res["golang-hello-vendor"] = "Hello, " + greetings
	log.WithFields(logrus.Fields{"greetings": greetings}).Info("Hello")
	return res
}

バージョン管理システムで vendorフォルダを再度作成できるので、別途保存する必要はありません。マニフェストファイルだけ保存すればいいですが、コンパイルされた状態でアクションを作成するには、vendorフォルダを含める必要があります。
main関数で3rd-partyライブラリを使用する場合、最上位にある mainパッケージのファイルを mainフォルダに移動させて vendorフォルダを作成する必要があります。