20200430のGoに関する記事は13件です。

社内Go PackageのためにGoDocで静的なHTMLを出力したい

はじめに

「社内で管理しているGoのパッケージのGoDocを誰でも閲覧できるようにしたい」というモチベーションによる記事です。

Wantedlyさんではこちらの記事のようにKubernetesでGoDocサーバ(gddo-server)を走らせることで社内GoDoc環境を構築しています。

今回はこのアプローチではなく、静的なHTMLをCI上で生成しそれをS3にホスティングすることで特にサーバを持たずに社内GoDocを運用する方針にしました。

環境・バージョン

Go: go version go1.13.4 darwin/amd64
GoDoc(golang.org/x/tools/cmd/godoc): v0.0.0 (4b814e0)

やったこと

CIでGoDocを元にHTMLを生成しそれを社内Amazon S3へアップロードします。

CI上でgodoc(golang.org/x/tools/cmd/godoc)のコマンドを取得した後、素直にgodocで静的HTMLを取得したかったのですが、現状(2020年4月30日時点)のgodocのバージョンにおいては、静的なHTMLを生成するオプション等は存在しないと下記のIssueページで知りました。

https://github.com/golang/go/issues/2381

このIssueページを読むと、wgetを使えばなんとか対応できるとの記載がありました。

試したらたしかにちゃんとHTMLを生成してくれたので、CIのスクリプトに組み込みます。

下記の例ではGitLab CIのYamlファイルからの抜粋ですが、シェルな部分はLinuxなCI環境なら同じように動くはずです。

.gitlab-ci.ymlの抜粋
  stage: generate-godoc-html
  variables:
    ARTIFACTS_DIR: artifacts
    GODOC_OUT: godoc
    GODOC_SERVER: localhost:6060
  script:
    - godoc -http=${GODOC_SERVER} & # バックグラウンド実行
    - sleep 1 # 一応1秒寝かす
    # ↓wgetでHTMLを生成する
    - wget -r -np -N -E -p -k http://${GODOC_SERVER}/pkg/【対象パッケージのPath】
    # ↓(あってもなくてもホスティング先で表示はされる)HTML内の"localhost:6060"なPathをホスティング先のものに置換してやる
    - find ./${GODOC_SERVER}/ -name "*.html" -print0 | xargs -0 sed -i -e "s/http:\/\/${GODOC_SERVER}\/src\/【対象パッケージのPath】/https:\/\/【GitレポジトリのPath】"
    - find ./${GODOC_SERVER}/ -name "*.html" -print0 | xargs -0 sed -i -e "s/http:\/\/${GODOC_SERVER}/https:\/\/【S3のPath】\/${GODOC_OUT}\/master/"
    # ↓生成したHTMLらを移動
    - mkdir -p ${ARTIFACTS_DIR}/${CI_PIPELINE_ID}/${GODOC_OUT}
    - mv ${GODOC_SERVER}/* ${ARTIFACTS_DIR}/${CI_PIPELINE_ID}/${GODOC_OUT}
  artifacts:
    paths:
      - ${ARTIFACTS_DIR}/${CI_PIPELINE_ID}/${GODOC_OUT} # 保存
    expire_in: 3 days

  stage: upload-to-s3
  script:
    - aws s3 cp ${ARTIFACTS_DIR}/${CI_PIPELINE_ID}/${GODOC_OUT} 【対象S3バケットのPath】 --recursive

上記wgetのオプションのクセが強いので解説です。

-r : 再帰的にダウンロードする
-np : 親ディレクトリを見ない
-N : ローカルより新しいものでない限りファイルを取得しない
-E : 拡張子.htmlをhtmlファイルに追加 (拡張子がない場合)
-p : 各ページに必要なファイルをすべてダウンロード (css, js, 画像)
-k : リンクを相対リンクに変換

また、HTML内のリンクの整合を取るために上記のように、sedコマンドでlocalhost:6060な部分を置換しています。これにより、GoDoc内のリンクやソースコード先に問題なく飛ぶことができます。詳細を把握するためにはローカル環境で検証することをおすすめします。

これが走った後、対象のS3ホスティングのURLにアクセスすると、以下のようにGoDocを見ることができました。

スクリーンショット 2020-04-30 20.58.50.png

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

_cgo_export.c:3:10: fatal error: 'stdlib.h' file not found の 解決方法(Catalina版)

概要

go get したときに出たエラーです。macOSのバージョンによって解消方法が異なるようなので、Catalina 10.15での解決方法です。

結論

以下コマンドで、環境変数をセットして、該当コマンドを実行したところ、解消しました。

export SDKROOT="$(xcrun --sdk macosx --show-sdk-path)"

内容

DBから gorm structs を生成するツールを検証しようとローカルにインストールしようとしたところ発生しました。
go get -u github.com/smallnest/gen

# runtime/cgo
_cgo_export.c:3:10: fatal error: 'stdlib.h' file not found

調べたこと

全く同じエラーのQiitaがあったのですが、macOS 10.14 で、Catalina 10.15 では解消しませんでした。
https://qiita.com/gold-kou/items/739958f996825cf33b89

かなり昔から継続しているissueがあったので読み進めました。
途中の解決方法もOSのバージョンに依存していました。
最後まで読み進めて、Catalina 10.15での解決方法の記載がいくつかありました。
Cコンパイラの環境変数やllvmをアンインストールする方法の記載がありますが、影響範囲が読めなかったので、過去の解決方法でもSDKをopenすることで解消していることから、SDKのパスをCLIで釣って環境変数にセットする方法を採用しました。

export SDKROOT="$(xcrun --sdk macosx --show-sdk-path)"

https://github.com/golang/go/issues/27921

感想

めでたし、めでたし

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Goで指定したバージョンのパッケージをインストールしたい

いざ自分がやろうとした際にちょっとだけ迷ったのでメモしときます。

バージョン指定してパッケージをインストール

go get <パッケージ名>@<指定するバージョン>

例えば、 gqlgen の v0.10.2 をインストールしたい場合はこうなります。

go get github.com/99designs/gqlgen@v0.10.2

本編はここまで。

go.mod でパッケージ管理しているときに自分がした勘違い

ここからはおまけです。
go.mod でパッケージ管理をしている場合、以下のようなファイルがあると思います。

go.mod
module github.com/tcshy/hoge

go 1.13

require (
    github.com/99designs/gqlgen v0.10.2
    github.com/aws/aws-sdk-go v1.28.9
    github.com/gin-gonic/gin v1.5.0
    ・・・
)

このとき、 go.mod と同じディレクトリにいる状態で go get github.com/99designs/gqlgen を打てば go.mod と同じバージョンでインストールされるのかなーと初めは思ったんですが、

$ go get github.com/99designs/gqlgen

$ gqlgen version
v0.11.3

見事に最新版になってる。
go.mod 内のバージョン情報も v0.11.3 にアップデートされちゃってる。

go.mod
module github.com/tcshy/hoge

go 1.13

require (
    github.com/99designs/gqlgen v0.11.3
    github.com/aws/aws-sdk-go v1.28.9
    github.com/gin-gonic/gin v1.5.0
    ・・・
)

ってことなんでむやみに go get <パッケージ名> でインストールすると知らない間にバージョンあがってる、って事がありますね。

もし間違ったバージョンをインストールしてしまったら

一旦パッケージを削除してから、指定のバージョンを再インストールするといいと思います。
パッケージの削除方法としては、以下みたいにGOPATH配下にあるバイナリ(とソースコードもあれば)を手で消す感じらしいです。
go uninstall パッケージ名 とか使えるようになったらいいですね)

$ cd $GOPATH
$ rm -f bin/gqlgen
$ rm -rf src/github.com/99designs

# rm コマンドに -rf つけると確認されずにファイルが消えるんで、GOPAHTHとかパッケージ名などは各自確認してからコマンド打ってね!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WINDOWS OSでAWS LAMBDAにGOをデプロイ

目的

現在、WINDWOS10 OSでGOを用いてWEBサービスを開発しており、
AWS LAMBDAを活用する場面がありましたが、
実際にデプロイを行う際に、無駄にはまったところがあったので、
この度は、実際にデプロイしてみてはまったことや感想を共有したいと思います。

AWS LAMBDAにGOをデプロイする方法

基本的でデプロイする方法はAWSの公式ドキュメントに記載されてあります。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/golang-package.html
特にデプロイパッケージHandlerがデプロイとは密接に関係があるので、
この辺はしっかりと読みましょう。

僕は、しっかり読まずいくつが見逃してしまい、苦労しましたね、、、:joy_cat:

僕がはまったところ

①ソースコードをLINUX用しなかったこと

問題

僕は以前、LAMBDAにPYTHON3をデプロイした経験があります。その際は、OSを気にせずデプロイしてもうまく動いてくれたので、
この度、GOをデプロイする際も、特に意識せず適当にアップロードしましたが、当然なことにエラーになりました。

解決策

LINUXにビルドすれば問題解決です。
AWS LAMBDAは実際にはコンテナ環境のAmazon Linuxの上動作するので、
デプロイするものもこの環境に合わせてビルドしてアップロードしなければならないとのことです。

WINDWOS環境でLINUX用にビルドする方法は、
cmdを用いて、デプロイ対象パスに移動し、
set GOOS=linux ←ビルドオプションをLINUXに設定
go build -o main main.go←ビルド ※このファイル名をLAMBDA操作画面にてハンドラとして入力
build-lambda-zip.exe -output main.zip main←ビルドしたファイルをZIPに圧縮する

build-lambda-zip.exeは
GOPATHから github.com/aws/aws-lambda-go/cmd/build-lambda-zip 入力するとダウンロードできます。

②ハンドラ名入力ミス

問題

LINUX用にビルドして、ZIPファイルをアップロード完了してコードテストを行うと
下のようなエラーメッセージが表示されました。

関数の実行から返された結果が以下のエリアに表示されます。
関数から結果を返す方法の詳細については、こちらを参照してください。

{
  "errorMessage": "fork/exec /var/task/☆☆: no such file or directory",
  "errorType": "PathError"
}

☆☆はLAMBDA画面上記載したハンドラに該当します。
1.png

解決策

ビルドしたファイルと、LAMBDA操作画面で入力したハンドラ名が異なるため発生するエラーでした。
例えば、go build -o main main.go でビルドした場合は、ハンドラにmainと入力しないとLambdaがパスを認識できず、エラーになります。
必ずビルドしたファイル名とハンドラ名を統一しましょう。

2.png
僕は「ハンドラ」と記載されてあったため、
lambda.Startに登録するハンドラを書くのかなと思い、
ビルドパス名と異なる値を入力して、はまりました。。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NHK番組表API叩いてSlackで通知する

はじめに

なんか面白そうなAPIとか探していたら、NHK番組表APIなるものを発見しました。

NHKっていったら、紅白、高校野球、朝ドラ、大河、天才テレビくんとか忍たま乱太郎とか、昔からそんなイメージ。
最近だと、キングダムとか進撃の巨人なんかも放送してたり、昔のイメージを覆しはじめてますねー。
ちなみに最近見つけた僕のイチオシは「香川照之の昆虫すごいぜ!」です。

ちなみに、NHKって不定期放送多くないですかね?
LIFE!とか、見逃しちゃうんですよね〜。
ウッチャンのコント番組見ると、笑う犬思い出しちゃいますよね〜。ミル姉さんとか。

ということで、今回はLambda使ってサーバーレスな感じで、見たい番組・好きなタレントが出ている番組をSlackに通知する、お手軽アプリケーション作ろうかと思います。

言語はGo(勉強中)です。

環境など

  • AWS Lambda
  • Amazon CloudWatch Events(cron式)
  • 言語:Go
  • NHK番組表API(Program List API)

NHK番組表APIについて

APIを使用するにあたって、こちらから、ユーザ登録が必要です。
メールアドレスの登録をした後、アプリの登録をしてAPIキーを発行する必要があります。

今回使用するProgram List APIの仕様確認やAPIの試打は、こちらから。

Goの実装

ディレクトリ構成

以下のような構成にしました。

% tree
.
└── nhk_api_test
    ├── README.md
    ├── cmd
    │   └── nhk_api_test
    │       └── main.go // main関数
    ├── go.mod
    ├── go.sum
    ├── payload.go      // Slackのwebhookで使う構造体
    ├── proglamList.go  // 番組表APIの構造体
    └── webhook.go      // メインロジック

それぞれの実装について、ざっくり説明します。
Go触ってて、学んだところとか。

main.go

main.go
package main

import (
    "github.com/aws/aws-lambda-go/lambda"
    nhk "github.com/tkl4230/nhk_api_test"
)

func webhook() {
    nhk.Webhook()
}

func main() {
    lambda.Start(webhook)
}

特に主だった処理はしていません。

Lambdaの開始とwebhook.goWebhook()を呼び出しているだけです。
goでは、javaとかみたくインスタンス.関数()ではなくpackage.関数()で呼び出します。
外部パッケージの関数を呼び出す時は、その関数の頭文字は大文字で宣言する必要があります。

関数名が小文字の場合は、外部パッケージからは見えなくなり、同一パッケージからの呼び出しのみ可能となります。
同一パッケージ内の関数呼び出しの場合関数名で呼び出せます。

あと、importで宣言しているlambdaライブラリを使用するためには以下のコマンド叩いておく必要があります。

go get github.com/aws/aws-lambda-go/lambda

proglamList.go

Program List APIで返ってくるjsonをマッピングするための構造体を宣言してます。

proglamList.go
package nhk_api_test

import "time"

type Program struct {
    List List `json:"list"`
}

type List struct {
    E1 []E1 `json:"e1"`
}

type E1 struct {
    ID        string    `json:"id"`
    EventID   string    `json:"event_id"`
    StartTime time.Time `json:"start_time"`
    EndTime   time.Time `json:"end_time"`
    Area      Area      `json:"area"`
    Service   Service   `json:"service"`
    Title     string    `json:"title"`
    Subtitle  string    `json:"subtitle"`
    Content   string    `json:"content"`
    Act       string    `json:"act"`
    Genres    []string  `json:"genres"`
}

type Area struct {
    ID   string `json:"id"`
    Name string `json:"name"`
}

type Service struct {
    ID    string `json:"id"`
    Name  string `json:"name"`
    LogoS Logo   `json:"logo_s"`
    LogoM Logo   `json:"logo_m"`
    LogoL Logo   `json:"logo_l"`
}

type Logo struct {
    URL    string `json:"url"`
    Width  string `json:"width"`
    Height string `json:"height"`
}

各フィールドの後ろでjson:"xxx"と記述することで、jsonを解析した時にjsonのプロパティとgoのフィールドを紐づけてくれます。

payload.go

Webhookに設定するPayloadの構造体を宣言して、Slackに通知するメッセージ内容の作成と設定をしています。

payload.go
package nhk_api_test

import (
    "bytes"
    "fmt"
    "text/template"
    "time"
)

const timeLayout = "15:04"

// Payload.Text用のテンプレート
var tpl = `
{{ range $key, $val := . -}}
日時: {{ $key }}
{{ range $index, $var := $val }}
番組: {{ $var.Title }}
出演者: {{ $var.Act }}
時間: {{ timeFmt $var.StartTime }} 〜 {{ timeFmt $var.EndTime }}
{{ end }}
------------------------------
{{ end }}
`

type payload struct {
    Channel   string `json:"channel"`
    UserName  string `json:"username,omitempty"`
    Text      string `json:"text"`
    IconUrl   string `json:"icon_url,omitempty"`
    IconEmoji string `json:"icon_emoji,omitempty"`
}

// Payload.Textの作成と設定
func (p *payload) setText(m map[string][]E1) {

    // テンプレート内で使用する関数の設定
    fMap := template.FuncMap{
        "timeFmt": timeFmt,
    }

    // テンプレートの割当
    tpl := template.Must(template.New("tpl").Funcs(fMap).Parse(tpl))

    // テンプレートからデータ出力
    var buffer bytes.Buffer
    if err := tpl.Execute(&buffer, m); err != nil {
        fmt.Println(err)
    }

    // 出力したデータを文字列化
    p.Text = buffer.String()
}

// テンプレートに渡す関数。
// 番組表APIの日時の形式を hh:mm にしてくれる。
func timeFmt(date time.Time) string {
    return date.Format(timeLayout)
}

goにはクラスという概念がないため、構造体に属するメソッドを定義する時は、以下のように書き、レシーバ型に構造体を指定します。

func (レシーバ値 レシーバ型) 関数名()

上記の書き方で、クラス内のメソッドを宣言するように、構造体に属したメソッドを宣言することになります。

なので、このファイルのfunc (p *payload) setText()はpayload構造体に属したメソッドの宣言になります。

普通のfunc 関数名()は、パッケージに属する感じですかね。

呼び出す時もパッケージ名.関数()ではなく、構造体を生成して、構造体.関数()になります(具体的にはこちらの3-a(詳細はロジック内)を参照)。

あと*は、ポインタ渡すかどうかです。Javaでいう、参照渡しに近いかと思います(要勉強)。

webhook.go

内容としては、主な処理を書いてます。
処理に必要な値は環境変数として外出ししているものもあります。
処理の流れとしては、以下の通りです。

  • 現在日取得
    • TZがUTCなので注意です。
    • 取得するjsonがyyyy--mm-dd.jsonなので、そこで使用します。
  • 以下の処理はループとなり、現在日を基準に環境変数:TERMに設定した回数、翌日以降のデータ取得を行います。
    • NHK番組表APIをコール
    • 取得したjsonの解析
    • jsonデータの中に環境変数:PERFORMER, PROGRAM_NAMEに部分一致する番組があれば、その番組データをmapに格納。
  • Webhookに設定するpayload構造体の生成。
    • mapに番組データがある場合、テンプレートを元にメッセージ生成
  • Webhook実行
webhook.go
package nhk_api_test

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "net/url"
    "os"
    "strconv"
    "strings"
    "time"
)

var (
    // 番組表APIのAPIキー
    apiKey = os.Getenv("API_KEY")
    // 探したい出演者(カンマ区切り)
    performer = os.Getenv("PERFORMER")
    // 探したい番組名(カンマ区切り)
    programName = os.Getenv("PROGRAM_NAME")
    // 探したい期間(Max7日)
    term = os.Getenv("TERM")
    // WebhookのURL
    webhookURL = os.Getenv("WEBHOOK_URL")
    // Slackのチャンネル名
    channel = os.Getenv("CHANNEL")
)

const (
    // 日付のフォーマットProgram List APIのjson名で使用
    dateLayout = "2006-01-02"
    // URL
    programListURL = "https://api.nhk.or.jp/v2/pg/list/130/e1/%s.json?key=%s"
)

// Webhook メインロジック
func Webhook() {

    // 今日の日付取得
    // LambdaはUTCなので、9H補正
    now := time.Now()
    now = now.Add(time.Duration(9) * time.Hour)

    // 期間設定を数値に変換
    term, _ := strconv.Atoi(term)

    // 見たい番組表格納用マップ
    // K: 日付、V: E1構造体
    programMap := make(map[string][]E1)
    // API実行(3日先まで)
    for i := 0; i < term; i++ {
        // URIに渡す日付を計算
        d := now.AddDate(0, 0, i)
        date := d.Format(dateLayout)

        // 番組表APIの呼び出し
        reqURL := fmt.Sprintf(programListURL, date, apiKey)
        resp, err := http.Get(reqURL)
        if err != nil {
            log.Println("エラー発生")
            log.Println(fmt.Sprintf("Error:%s", err))
            log.Println(fmt.Sprintf("Request:%s", reqURL))
            return
        }

        if resp.StatusCode != 200 {
            log.Println("ステータスコード異常")
            log.Println(fmt.Sprintf("Status code:%v", resp.StatusCode))
            log.Println(fmt.Sprintf("Request:%s", reqURL))
            return
        }

        defer resp.Body.Close()

        // 番組表APIレスポンスのjson解析
        var p Program
        if err := json.NewDecoder(resp.Body).Decode(&p); err != nil {
            log.Println("json解析でエラー発生")
            log.Println(err)
            return
        }

        // 見たい番組名・出演者があれば、pListに格納
        pList := make([]E1, 0)
        for _, e1 := range p.List.E1 {
            if isNoticeTarget(e1) {
                pList = append(pList, e1)
            }
        }

        // 見たい番組名・出演者がなければ、翌日へ。
        l := len(pList)
        if l == 0 {
            log.Printf("%vに、探してる番組・出演者はありませんでした。\n", date)
            continue
        }
        log.Printf("%vに、%v件の番組が見つかりました。\n", date, l)

        // マップに格納
        programMap[date] = pList
    }

    // payloadの生成
    payload := payload{
        Channel:   channel,
        UserName:  "nhk番組取得お知らせ",
        IconEmoji: ":ghost:",
        Text:      "探している番組は見つかりませんでした。",
    }

    // 見たい番組が見つかれば、メッセージ(Text)を更新。
    if len(programMap) > 0 {
        payload.setText(programMap)
    }

    // jsonのエンコード
    jsonByte, err := json.Marshal(payload)
    if err != nil {
        log.Println("jsonのエンコードでエラー発生")
        log.Println(err)
        return
    }

    // Webhookの呼び出し
    _, err = http.PostForm(
        webhookURL,
        url.Values{"payload": {string(jsonByte)}},
    )
    if err != nil {
        log.Println("Webhookの実行でエラー発生")
        log.Println(err)
        return
    }
}

// Webhookで通知する対象か判定
func isNoticeTarget(e1 E1) bool {

    // 出演者のチェック
    performerList := strings.Split(performer, ",")
    for _, v := range performerList {
        if strings.Contains(e1.Act, v) {
            return true
        }
    }

    // 番組のチェック
    programNameList := strings.Split(programName, ",")
    for _, v := range programNameList {
        if strings.Contains(e1.Title, v) {
            return true
        }
    }
    return false
}

所々出てくるアンスコのところは戻り値で不要なものを読み捨ててます。
変数宣言のところでos.Getenv("XXX")の記載がありますが、こちらで環境変数を読み込んでいます。

AWSの設定

デプロイパッケージの作成

こちら、Lambdaの方に設定します。

GOOS=linux go build -o webhook main.go
zip webhook.zip webhook

Lambda

lambda1.png
nhk_program_webhookという関数名にしました。
トリガーにCloudWatch Eventsを設定しています。

lambda2.png
ハンドラはwebhookとしています。
先ほど作成したデプロイパッケージはこちらからアップロードします。

lambda3.png
環境変数6つ。
なんとなく思いついたものを設定してみました。

CloudWatch Events

cloudwatch.png
画面はLambdaの管理画面ですけど、CloudWatch Eventsの設定です。
毎日22:10(日本で7:10)に動くようにしてます。

動かしてみた

slack.png

CloudWatchではなく、テスト実行ですが、動きました。
(CloudWatchのトリガーからも動くことは確認済みです)

なんと、昆虫すごいぜ! 再放送してます!!
すでに4回目っていうことで、乗り遅れてますが、要チェックです!!

ということで、NHK番組表API叩いてSlack通知する、でした。
ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GitHubのSecretsを活用して、GitHub Actionsで安全に機密情報を扱う

はじめに

GitHub repositoryにyamlファイルを書くことで、Commit時やPR反映時等色々な場面で利用できるGitHub Actions。
そんなGitHub Actionsに処理を書いていると、外部サービスと連携するためにアクセストークンのような機密情報を保持したくなります。
この記事では、その機密情報の扱うためのSecretsの使い方について記載します。

GitHub repositoryのSecrets

使い方は簡単で、GitHub内のsecretを設定したいリポジトリのページを開き、下記Settingsから設定を行います。

例えばSecretSettingという設定を追加した場合は、以下のように secrets.SecretSetting を変数に代入することで、run内に書いたスクリプトでsecretが扱えるようになります。

    - name: Access to secret
      env:
        SECRET_SETTING: ${{ secrets.SecretSetting }}
      run: |
     ##access to $SECRET_SETTING if this script is bash

詳細は公式ページを参照ください。

注意
上記Settingsを扱えるのは、リポジトリのオーナーのみです。

GitHub APIを利用してより汎用的に

上記の方法でも事足りるのですが、Secretsの設定はリポジトリごとにしかできず、さらにその権限はリポジトリのオーナーごととなっています。
大量にリポジトリがある場合は骨の折れるので、APIを利用して設定を行います。

APIを利用する方法の一つとして、tokenを発行する方法があります。Private repositoryを使う場合はrepo Full control of private repositoriesが必要です。

tokenが取得できたらあとはAPIを叩くだけ。
APIの仕様はこちら。Updateする際はpublic keyを取得した上でLibSodiumを利用してkeyを暗号化するのが特徴です。

ざっくりシーケンスはこんな感じ

sequence

また、GitHub APIを利用するためのgolangライブラリが公開されていますので、プログラムで利用することも楽ちん。
上記を紹介した記事Github Actions Secrets API を使って Go で Secrets を更新するを参考に、golangでのサンプルコマンドを作成しています。

github-secret

参考

公式ページ
GitHub ヘルプ: 暗号化されたシークレットの作成と保存
GitHub Developer: REST API v3 Secrets
Libsodium documentation

その他参考
Github Actions Secrets API を使って Go で Secrets を更新する
GitHub APIの仕組みを知り、APIでrepositoryを作成しよう

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(Go言語・WebAssembly)Webページで並列処理はできるのか?

前書き

Go言語を勉強する中でgoルーチンをWebAsseblyにコンパイルした場合、
ブラウザ上で並列処理はきちんと行っているのか気になったので確認しました。

結果

問題なく並列処理ができました。
画面サンプル
click

以降はソースコードの記述です。
コンパイルの方法などは以前の記事を参照してください。
(Go言語)WebAssemblyで簡単なWebサイトを作ってみた

実行環境

OS:Windows 10 64bit
言語環境:go version go1.14.1
ブラウザ:Firefox バージョン: 74.0

ソースコード

htmlファイル
該当部分のみ抜粋
<body>
  <button type="submit" id="button1" >START</button><br>
  <div id="time1" >時刻1:</div>
  <div id="time2" >時刻2:</div>
  <div id="time3" >時刻3:</div>
</body>
時刻を取得し文字列を返す
func Get_time() string {
    now := time.Now()
    str := strconv.Itoa(now.Hour()) + ":" + strconv.Itoa(now.Minute()) + ":" + strconv.Itoa(now.Second())
    return str
}
メイン関数
package main
import (
    "strconv"
    "syscall/js"
    "time"
)
func main() {
    //イベントの管理-
    get_three_time("button1", "time1", "time2", "time3")
    <-make(chan struct{}, 0)
}
実装処理
func get_three_time(id string, output1 string, output2 string, output3 string) {
    //idから対象のオブジェクトを取得
    object := js.Global().Get("document").Call("getElementById", id)
    out1 := js.Global().Get("document").Call("getElementById", output1)
    out2 := js.Global().Get("document").Call("getElementById", output2)
    out3 := js.Global().Get("document").Call("getElementById", output3)
    //ゴールーチンで実行し
    action := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
        go func() {
            time.Sleep(time.Duration(0) * time.Second)//0秒待機
            out1.Set("innerText", "時刻1:"+Get_time())
        }()
        go func() {
            time.Sleep(time.Duration(2) * time.Second)//2秒待機
            out2.Set("innerText", "時刻2:"+Get_time())
        }()
        go func() {
            time.Sleep(time.Duration(4) * time.Second)//4秒待機
            out3.Set("innerText", "時刻3:"+Get_time())
        }()
        return nil
    })
    //アクションの呼び出し
    object.Call("addEventListener", "click", action)
}

結果・感想

Go言語+WebassemblyでGoルーチンによる並列処理を実装してみました。
もともとJavascritpには同期/非同期処理があり、それらと動作の違いはないかもしれません。

CPU視点で見れば、マルチスレッドによる非同期処理が役に立つ場面が存在しますが、
はたしてブラウザ上のバイナリコードが、複数のコア上で動作するのか?
という疑問が残ります。

仮説ですが、
スクリプトの実行がブラウザの1プロセス上として動作するならマルチスレッドとはなりませし、ブラウザやCPUアーキテクチャに依存する可能性があります。

しかしながら、ソースコードで並列処理を実装できることはGo言語のメリットです。
フロントエンドにJavascriptやPHPなくGo言語を採用する理由の一つになるのではないでしょうか

次はGo言語でAPIサーバを構築して、それをもとにSPAなwebページの構築を目指しています。
できる限りGo言語でWEBシステムを構築してくために少しづつですが前に進んでいる実感があります。
これからも頑張っていこうと思います。

ここまで読んできただきありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Goで作成したc-sharedなライブラリでexportしたはずの関数が見つからない

事象

node-ffiで呼び出そうとするとこうなる。

Error: Dynamic Symbol Retrieval Error: Win32 error 127

原因

exportの先頭にスペースを入れてはいけない。

// OK
//export FuncName

// NG
// export FuncName
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Go】echoでハマったこと

echoを使っていてハマったことまとめ

概要

Go言語のWebフレームワークであるechoを使っていてハマったことについて、メモとしてまとめていこうと思います。

middleware

CORS

CORSWithConfig()の戻り値とe.Use()の引数が不一致

事象

自分の作ったフロントエンドからのみアクセスできるようにカスタムCORS設定をしようとして以下のようなコードを作成しました。

package main

import (
    "net/http"

    "github.com/labstack/echo/middleware"
    "github.com/labstack/echo/v4"
)

func main() {
    e := echo.New()
    e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
        AllowOrigins: []string{"http://localhost:3000"},
        AllowMethods: []string{http.MethodGet, http.MethodPut, http.MethodPost, http.MethodDelete},
    }))
    e.GET("/public", public)
    e.Logger.Fatal(e.Start(":1323"))
}

func public(c echo.Context) error {
    return c.String(http.StatusOK, "hello public!")
}

すると、以下のようなエラーに遭遇しました。

cannot use middleware.CORSWithConfig(middleware.CORSConfig literal) (type "github.com/labstack/echo".MiddlewareFunc) as type "github.com/labstack/echo/v4".MiddlewareFunc in argument to e.Use

どうやらe.Use()ではv4MiddlewareFunを引数として要求しているようですが、
CORSWithConfig()の戻り値はv4ではないMiddlewareFunらしいです。

解決策

最初は、キャストすればいいかと思ったのですが、うまくいきませんでした。
そこで、少し調べてみるととても簡単なことで、自分がGoのバージョン管理などに不慣れなことが原因でした。

問題の箇所は、以下のimoprt文になります。

    "github.com/labstack/echo/middleware"
    "github.com/labstack/echo/v4"

ここで、echoについてはv4を指定してインポートしているのですが、middlewareは何も指定しておりません。
そのため、インポートされたmiddlewareechoの間でバージョンの不一致が起きてしまい先ほどのエラーが生じたようです。

解決策は、簡単で以下のようにmiddlewareにもv4を付けてあげるだけ。

    "github.com/labstack/echo/v4/middleware"

これでエラーは出なくなり、無事にCORSの設定ができました。

終わりに

今後もechoを使う中で自分がハマったことや、コメントでいただいたハマりポイントについて書いていきたいと思います。
何かご指摘や、ハマりポイントなどありましたらコメントよろしくお願いします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GoのCommonMistakes/Using reference to loop iterator variableに対応するC++/Rustのコード

に、

CommonMistakes/Using reference to loop iterator variable:

func main() {
    var out []*int
    for i := 0; i < 3; i++ {
        out = append(out, &i)
    }
    fmt.Println("Values:", *out[0], *out[1], *out[2])
    fmt.Println("Addresses:", out[0], out[1], out[2])
}

に対応するC++とRustのコードが書かれているけど、上記のGoコードでは別にiの寿命はforループで切れるわけではないので、C++の未定義動作になるコードやRustの "borrowed value does not live long enough" エラーになるコードとは対応していないと思う(コメントにあるエラーの方が対応としては適切だと思う。多分)。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

qiisync-test

test 投稿です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Golang TempDir,TempFile作成

Golang TempDir,TempFile作成する

main.go
package main

import (
    "fmt"
    "io/ioutil"
    "os"
)

func main() {
    dir, _ := ioutil.TempDir("", "aaa")
    fmt.Println(dir)

    fp, _ := ioutil.TempFile(dir, "xxx")
    fpath := fp.Name()
    fmt.Println(fpath)

    fp2, _ := ioutil.TempFile(dir, "yyy")
    fpath2 := fp2.Name()
    fmt.Println(fpath2)

    fp.Close()
    fp2.Close()
    os.RemoveAll(dir)
}


//C:\Users\####\AppData\Local\Temp\aaa661041139
//C:\Users\####\AppData\Local\Temp\aaa661041139\xxx642705078
//C:\Users\####\AppData\Local\Temp\aaa661041139\yyy013873821


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ソートを勉強してみる by Go ②

ソートを勉強してみる by Go ① の続き。
クイックソートしてみました。意外とすんなりいきました。

package main

import (
    "fmt"
    "math/rand"
)

func main() {
    length := 20
    list := make([]int, length)
    for i := 0; i < len(list); i++ {
        list[i] = (rand.Int() >> 56)
    }

    sort(list, 0, len(list)-1)

    for i := 0; i < len(list); i++ {
        fmt.Println(list[i])
    }
}

func sort(list []int, start int, end int) {
    if start >= end {
        return
    }
    var stack int

    left := start
    right := end - 1
    marker := end
    for {
        for ; left <= marker; left++ {
            if list[left] > list[marker] {
                break
            }
            if left == marker {
                sort(list, start, end-1)
                return
            }
        }

        for ; right >= left; right-- {
            if right == left {
                stack = list[right]
                list[right] = list[marker]
                list[marker] = stack
                sort(list, start, right-1)
                sort(list, right+1, end)
                return
            }
            if list[right] < list[marker] {
                stack = list[right]
                list[right] = list[left]
                list[left] = stack
                break
            }
        }
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む