20200918のGoに関する記事は6件です。

A Tour of Go メモ 【7】五日目

A Tour of GO

A Tour of Go

interface

インターフェース型と同じ関数を実装していない物はインターフェース型に代入できない

func main() {
    var a Abser
    f := MyFloat(-math.Sqrt2)
    v := Vertex{3, 4}

        // MyFloatにはAbser()が実装されている
    a = f
        // *VertexにもAbser()は実装されている
    a = &v

     // aは*Vertexではないため、Aber()が実装されていない
    a = v

    fmt.Println(a.Abs())
}

type MyFloat float64

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

type Vertex struct {
    X, Y float64
}

// *vertexにはAbserと同じAbs()関数が実装されている
// Vertexのポインタ型に対して、実装している
func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

メソッドを実装し、インターフェースを実装する

type I interface {
    M()
}

type T struct {
    S string
}

// T に I(インターフェース)と同じメソッドを実装する
func (t T) M() {
    fmt.Println(t.S)
}

func main() {
    //TはIと同じメソッドを持っているので、I型と判断されるため、i(I型)に代入できる
    var i I = T{"hello"}
    i.M()
}

Interface Values

type I interface {
    M()
}

type T struct {
    S string
}

func (t *T) M() {
    fmt.Println(t.S)
}

type F float64

func (f F) M() {
    fmt.Println(f)
}

func main() {
    var i I

    i = &T{"Hello"}
    describe(i)
    i.M()    // (&{Hello}, *main.T) Hello
    //M() は元となるT型に宣言した同名のメソッドが呼び出される

    i = F(math.Pi)
    describe(i)
    i.M()   //(3.141592653589793, main.F) 3.141592653589793
   //M() は元となるT型に宣言した同名のメソッドが呼び出される
}

Interface values with nil underlying values

type I interface {
    M()
}

type T struct {
    S string
}

func (t *T) M() {
    if t == nil {
        fmt.Println("<nil>")
        return
    }
    fmt.Println(t.S)
}

func main() {
    // この時点ではiはnilである
    var i I

    // Tには何も入っていない,nilである
    var t *T
    // しかし、 iにはnilであるT型が入っているので、i自体は非nilである
    i = t
    describe(i)  // (<nil>, *main.T) <nil> 
    i.M()

    i = &T{"hello"}
    describe(i)  // (&{hello}, *main.T) <nil>
    i.M()
}
func describe(i I) {
    fmt.Printf("(%v, %T)\n", i, i)
}

Nil interface values

type I interface {
    M()
}

func main() {
    //値も具体的な型も持っていない
    var i I
    describe(i)
    i.M()  // 何も値も持たないインターフェース型からはメソッドを呼べない
}

func describe(i I) {
    fmt.Printf("(%v, %T)\n", i, i)
}

The empty interface

空のインターフェースにはどんな型の値も扱うことができる

func main() {
    var i interface{}
    describe(i)

    i = 42
    describe(i)

    i = "hello"
    describe(i)
}

func describe(i interface{}) {
    fmt.Printf("(%v, %T)\n", i, i)
}

Type assertions

string型をインターフェース型に変換した場合、
インターフェース型の中身が"hello"だったとしても、string型として扱うことができない。
そのため、再度インターフェース型からstring型に戻す作業が必要になる

func main() {
    var i interface{} = "hello"

    s := i.(string)
    fmt.Println(s)
    // okがiの中身がstring型値を保持しているかをboolで返す
    s, ok := i.(string)
    fmt.Println(s, ok)

    f, ok := i.(float64)
    fmt.Println(f, ok)

    f = i.(float64) // panic
    fmt.Println(f)
}

Stringers

fmtパッケージのソース
Printlnメソッドが実行された時、引数がStringer型だったらstring()が実行されるはず
574行目あたりだろうか・・・

type Person struct {
    Name string
    Age  int
}

//fmtのパッケージに以下の Stringer インターフェースの記述がある
************************************************************
type Stringer interface {
    String() string
}
************************************************************

//Stringer型と同じString()メソッドを宣言すると Person型にStringerインターフェースを実装できる
func (p Person) String() string {
    return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
    a := Person{"Arthur Dent", 42}
    z := Person{"Zaphod Beeblebrox", 9001}


    fmt.Println(a, z)
}

Errors

組み込みのerrorインターフェースを実装し、エラーメッセージをカスタマイズする

type MyError struct {
    When time.Time
    What string
}

// error型のインターフェースを実装
func (e *MyError) Error() string {
    return fmt.Sprintf("at %v, %s",
        e.When, e.What)
}
//実行すると現在時刻と文字列が入った構造体を返す
func run() error {
    return &MyError{
        time.Now(),
        "it didn't work",
    }
}

func main() {
    // もし、run()で戻り値があったら、戻り値を出力
    if err := run(); err != nil {
        fmt.Println(err)
    }
}

Readers

func main() {
    r := strings.NewReader("Hello, Reader!")

    b := make([]byte, 8)
    for {
        // Readメソッドはint型とerror型の戻り値がある
        n, err := r.Read(b)
        fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
        fmt.Printf("b[:n] = %q\n", b[:n])
        //ストリームが終わったら、Readメソッドが返すio.EOFというエラーがerrに入るので、
        //それを判定して、ループを抜ける
        if err == io.EOF {
            break
        }
    }
}

Images

import (
    "fmt"
    "image"
)
*****************************************************************
*imageパッケージの中身

type Image interface {
    ColorModel returns the Image's color model.
    ColorModel() color.Model
    //Bounds returns the domain for which At can return non-zero color.
    // The bounds do not necessarily contain the point (0, 0).
    Bounds() Rectangle
    // At returns the color of the pixel at (x, y).
    // At(Bounds().Min.X, Bounds().Min.Y) returns the upper-left pixel of the grid.
    // At(Bounds().Max.X-1, Bounds().Max.Y-1) returns the lower-right one.
    At(x, y int) color.Color
}

type RGBA struct {
    // Pix holds the image's pixels, in R, G, B, A order. The pixel at
    // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*4].
    Pix []uint8
    // Stride is the Pix stride (in bytes) between vertically adjacent pixels.
    Stride int
    // Rect is the image's bounds.
    Rect Rectangle
}
//RGBAにはAt, Boundes ColorModel()などのメソッドがあるので、Imageインターフェースが実装されている
func (p *RGBA) At(x, y int) color.Color
func (p *RGBA) ColorModel() color.Model
func (p *RGBA) Bounds() Rectangle


func Rect(x0, y0, x1, y1 int) Rectangle
//Rect is shorthand for Rectangle{Pt(x0, y0), Pt(x1, y1)}.

func NewRGBA(r Rectangle) *RGBA
//NewRGBA returns a new RGBA image with the given bounds.

*colorのパッケージ
type Color interface {
    // RGBA returns the alpha-premultiplied red, green, blue and alpha values
    // for the color. Each value ranges within [0, 0xffff], but is represented
    // by a uint32 so that multiplying by a blend factor up to 0xffff will not
    // overflow.
    //
    // An alpha-premultiplied color component c has been scaled by alpha (a),
    // so has valid values 0 <= c <= a.
    RGBA() (r, g, b, a uint32)
}
*****************************************************************


func main() {
    // Rect()でRectange型の構造体を作成
    // Rectange型を引数として、RGBA型の構造体を作成
    m := image.NewRGBA(image.Rect(0, 0, 100, 100))
    fmt.Println(m.Bounds())
    fmt.Println(m.At(0, 0).RGBA())
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GOPATHについてメモ

Goの環境構築をしていてGOPATHについて結理解するのに時間がかかったのでまとめ
他の言語と全然違って戸惑い?

GOPATHとは

GOPATHは、ワークスペースのルートのこと
ソースコードとかパッケージとかコンパイルした後の実行ファイルを全部ぶちこむ?

デフォルトは $HOME/goで、Goのインストールディレクトリじゃなければどこでもいい。

※みた感じデフォルトのまま使っている人が多そう。

フォルダ構成

例えばGOPATHを$HOME/goにしたときのフォルダ構成はこんな感じ

  go
   ├── src
   ├── pkg
   └── bin

src

ソースコードを置くディレクトリ。一ディレクトリ一アプリケーションが基本。
packageがmainであれば実行可能アプリケーション、違ったらアプリケーションパッケージ

pkg

アプリケーションパッケージが保存される

bin

実行ファイルが保存される。

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

ゴルーチンでWaitGroupを使う

はじめに

ゴルーチンを使った処理の際に登場するWaitGroupについて勉強したので記しておきます。

コード

WaitGroupを使わない場合

まずはWaitGroupを使用しない場合です。
f1( )もf2( )も非常に速く実行されるため、関数が独立して実行されているかどうかで違いがほとんど出ません。
そのため、timeパッケージの関数Sleepを使って両方の関数に遅延を追加しています。

package main

import (
    "fmt"
    "time"
)

func f1() {
    for i := 0; i < 10; i++ {
        time.Sleep(1 * time.Microsecond)
        fmt.Printf("%d ", i)
    }
}

func f2() {
    for i := 100; i < 110; i++ {
        time.Sleep(1 * time.Microsecond)
        fmt.Printf("%d ", i)
    }
}

func main() {
    go f1()
    go f2()
}

これを実行してみてください。
コードはこちら→ https://play.golang.org/p/8z21HqgIB21

恐らく何も表示されないと思います。
これはf1( )とf2( )が数字を出力する前にmain関数が終了してしまったからです。
実行結果をきちんと見るためにはmain関数の最後に遅延を追加する必要があります。

遅延を追加したコードは以下になります。

package main

import (
    "fmt"
    "time"
)

func f1() {
    for i := 0; i < 10; i++ {
        time.Sleep(1 * time.Microsecond)
        fmt.Printf("%d ", i)
    }
}

func f2() {
    for i := 100; i < 110; i++ {
        time.Sleep(1 * time.Microsecond)
        fmt.Printf("%d ", i)
    }
}

func main() {
    go f1()
    go f2()
    time.Sleep(100 * time.Millisecond)
}

これを実行してみると(コード→https://play.golang.org/p/LmovZBNCSFm) 、
以下のようにきちんと出力されています。

 100 0 101 1 102 2 103 3 4 104 105 5 6 106 7 107 8 108 109 9 

WaitGroupを使う場合

次はWaitGroupを使用する場合を見てみましょう。
WaitGroupを使用しない場合、関数Sleepを使ってプログラムの終了を遅延させ、ゴルーチンが処理を完了する前にプログラムを終了させないようにしました。

WaitGroupはsyncパッケージに含まれており、次の処理を開始する前にすべてのゴルーチンが処理を完了しているかどうか確認する場面に対応する仕組みです。

使い方は以下の通り。

  • WaitGroupを宣言する
  • Add()を使ってWaitGroupのカウンタを初期化する
  • ゴルーチンが作業を完了するとDone()を使ってカウントを減らす
  • Wait()でカウンタが0になるまで実行を中断する

では、実際に先ほどのコードをWaitGroupを使って書いてみます。

package main

import (
    "fmt"
    "sync"
    "time"
)

func f1(wg *sync.WaitGroup) {
    for i := 0; i < 10; i++ {
        time.Sleep(1 * time.Millisecond)
        fmt.Printf("%d ", i)
    }
    wg.Done()
}

func f2(wg *sync.WaitGroup) {
    for i := 100; i < 110; i++ {
        time.Sleep(1 * time.Millisecond)
        fmt.Printf("%d ", i)
    }
    wg.Done()
}

func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    go f1(&wg)
    go f2(&wg)
    wg.Wait()
}

実行結果は以下のようになります。
コード→https://play.golang.org/p/2ODVxIi5aXm

100 0 1 101 102 2 3 103 4 104 105 5 6 106 107 7 108 8 9 109 

しっかり出力されています。
どうしてそうなるのでしょうか。

まず、WaitGroupの変数wgを定義します。
次にAddを呼び出し、引数に2を渡します。
そして、f1( )、f2( )が呼び出されると、それぞれカウンタが1少なくなります。
Wait()を呼び出しているので、Done()が2回呼び出されカウンタが0になるまで実行は中断されます。

仮に、Done()を呼び出さず、カウンタが0にならなかった場合はエラーとなります。

0 100 101 1 102 2 103 3 4 104 105 5 106 6 7 107 108 8 9 109 fatal error: all goroutines are asleep - deadlock!

最後に

今までWaitGroupって何?という状態でしたが、何をしているのか理解することができました。
少しでも参考になれば幸いです!

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

[Go]ゴルーチンでWaitGroupを使う

はじめに

ゴルーチンを使った処理の際に登場するWaitGroupについて勉強したので記しておきます。

コード

WaitGroupを使わない場合

まずはWaitGroupを使用しない場合です。
f1( )もf2( )も非常に速く実行されるため、関数が独立して実行されているかどうかで違いがほとんど出ません。
そのため、timeパッケージの関数Sleepを使って両方の関数に遅延を追加しています。

package main

import (
    "fmt"
    "time"
)

func f1() {
    for i := 0; i < 10; i++ {
        time.Sleep(1 * time.Microsecond)
        fmt.Printf("%d ", i)
    }
}

func f2() {
    for i := 100; i < 110; i++ {
        time.Sleep(1 * time.Microsecond)
        fmt.Printf("%d ", i)
    }
}

func main() {
    go f1()
    go f2()
}

これを実行してみてください。
コードはこちら→ https://play.golang.org/p/8z21HqgIB21

恐らく何も表示されないと思います。
これはf1( )とf2( )が数字を出力する前にmain関数が終了してしまったからです。
実行結果をきちんと見るためにはmain関数の最後に遅延を追加する必要があります。

遅延を追加したコードは以下になります。

package main

import (
    "fmt"
    "time"
)

func f1() {
    for i := 0; i < 10; i++ {
        time.Sleep(1 * time.Microsecond)
        fmt.Printf("%d ", i)
    }
}

func f2() {
    for i := 100; i < 110; i++ {
        time.Sleep(1 * time.Microsecond)
        fmt.Printf("%d ", i)
    }
}

func main() {
    go f1()
    go f2()
    time.Sleep(100 * time.Millisecond)
}

これを実行してみると(コード→https://play.golang.org/p/LmovZBNCSFm) 、
以下のようにきちんと出力されています。

 100 0 101 1 102 2 103 3 4 104 105 5 6 106 7 107 8 108 109 9 

WaitGroupを使う場合

次はWaitGroupを使用する場合を見てみましょう。
WaitGroupを使用しない場合、関数Sleepを使ってプログラムの終了を遅延させ、ゴルーチンが処理を完了する前にプログラムを終了させないようにしました。

WaitGroupはsyncパッケージに含まれており、次の処理を開始する前にすべてのゴルーチンが処理を完了しているかどうか確認する場面に対応する仕組みです。

使い方は以下の通り。

  • WaitGroupを宣言する
  • Add()を使ってWaitGroupのカウンタを初期化する
  • ゴルーチンが作業を完了するとDone()を使ってカウントを減らす
  • Wait()でカウンタが0になるまで実行を中断する

では、実際に先ほどのコードをWaitGroupを使って書いてみます。

package main

import (
    "fmt"
    "sync"
    "time"
)

func f1(wg *sync.WaitGroup) {
    for i := 0; i < 10; i++ {
        time.Sleep(1 * time.Millisecond)
        fmt.Printf("%d ", i)
    }
    wg.Done()
}

func f2(wg *sync.WaitGroup) {
    for i := 100; i < 110; i++ {
        time.Sleep(1 * time.Millisecond)
        fmt.Printf("%d ", i)
    }
    wg.Done()
}

func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    go f1(&wg)
    go f2(&wg)
    wg.Wait()
}

実行結果は以下のようになります。
コード→https://play.golang.org/p/2ODVxIi5aXm

100 0 1 101 102 2 3 103 4 104 105 5 6 106 107 7 108 8 9 109 

しっかり出力されています。
どうしてそうなるのでしょうか。

まず、WaitGroupの変数wgを定義します。
次にAddを呼び出し、引数に2を渡します。
そして、f1( )、f2( )が呼び出されると、それぞれカウンタが1少なくなります。
Wait()を呼び出しているので、Done()が2回呼び出されカウンタが0になるまで実行は中断されます。

仮に、Done()を呼び出さず、カウンタが0にならなかった場合はエラーとなります。

0 100 101 1 102 2 103 3 4 104 105 5 106 6 7 107 108 8 9 109 fatal error: all goroutines are asleep - deadlock!

最後に

今までWaitGroupって何?という状態でしたが、何をしているのか理解することができました。
少しでも参考になれば幸いです!

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

Slash Commandsで自分が使用している端末のグローバルIPは取得できるのか【API Gateway + Lambda(Go)】

はじめに

こんにちは。先月AWS ChatbotからRDSを操作していて、とても簡単にSlackから実行できて感動したので、第2弾です!今回はSlackから自分のグローバルIPを取得できるのかを、試してみました!

結論

残念ながら、Slackから自分のグローバルIPは取得できませんでした。

Slackで自分のグローバルIPを取得するべく、Slash CommandsというSlackのapiと、AWSのAPI Gateway、Lambdaを使って試みたので、その過程について備忘録です。

方法検討

自分のグローバルIPを取得して、それを引数としてLambdaを実行したかったので、AWSを使う方針で進めていきます。

調査したところ、SlackからLambdaを実行する方法を2つ見つけました。

  • API Gatewayを使う方法(Slash Commandsの作成)
  • AWS Chatbotを使う方法

API Gatewayはアクセス元のグローバルIPを取得できるというのを知ったので、今回はAPI Gatewayを使う方法(Slash Commandsの作成)で実装することにしました!

構成図

構成は下図の通りです。
ちなみに、Slash commandsは3000ms以内に応答を返す必要があるので、処理時間が長いものに関しては先にSlackに応答を返す必要があるみたいです。今回はアクセス元IPを返すだけなので、こちらの構成で実装しました。
参考:https://api.slack.com/interactivity/slash-commands

Slash_commands.png

やってみる

Lambda関数の作成

今回はチャンネルを識別し、指定チャンネルから実行されていれば、アクセス元のグローバルIPを返すというLambda関数を作りました。

チャンネルを識別する意図としては、Slash Commandsは、ワークスペース内の全チャンネル、全DMで使えるもので、チャンネルを指定することができません。今回作成するSlash CommandsはグローバルIPを返すだけなので、どこで実行できても問題はないのですが、実際には指定したプライベートチャンネルのメンバーのみしか実行できないようにしたかったので、実行チャンネルを識別し、実行権限があるのかを判断する処理を入れています。

IAMロール

特別必要な権限はないので、基本的なLambdaアクセス権限をアタッチしたロールを使用します。

Slackから送られるデータと構造

Slackから送られるデータと構造は下記の通りです。
※ Valueは削除しています。本来はそれぞれ値が入っています。
※ 後述のAPI Gatewayのマッピングテンプレートを使用した場合の構造です。

{
  "querystring":{
    "api_app_id":"",
    "channel_id":"",
    "channel_name":"",
    "command":"",
    "response_url":"",
    "team_domain":"",
    "team_id":"",
    "text":"",
    "token":"",
    "trigger_id":"",
    "user_id":"",
    "user_name":""
  },
  "source_ip":""
}

ソースコード

package main

import (
    "github.com/aws/aws-lambda-go/lambda"
    "os"
)

type MyEvent struct {
    Querystring Querystring `json:"querystring"`
    SourceIp string `json:"source_ip"`
}

type Querystring struct {
    ChannelId string `json:"channel_id"`
}

/**************************
    処理実行
**************************/
func run(event MyEvent) (interface{}, error) {
    // os.Getenv()でLambdaの環境変数を取得
    if event.Querystring.ChannelId == os.Getenv("channelId") {
        return event.SourceIp, nil
    }
    res := "実行権限がありません。"
    return res, nil
}

/**************************
    メイン
**************************/
func main() {
    lambda.Start(run)
}

API Gatewayの作成

API Gatewayの作成を行います。
POSTメソッドを作成し、先ほど作成したLambda関数を紐付けます。

そして、マッピングテンプレートの設定をしていきます。
[統合リクエスト]をクリックします。

スクリーンショット 2020-09-06 23.39.41.png

最下部にあるマッピングテンプレートを開き、リクエスト本文のパススルーはテンプレートが定義されていない場合 (推奨)を選択します。そして、[マッピングテンプレートの追加]をクリックします。

スクリーンショット 2020-09-06 23.45.21.png

[マッピングテンプレートの追加]を押したら出てくるテキストボックスにapplication/x-www-form-urlencodedを入力、保存します。

テンプレートの入力フォームが出てくるので、下記のマッピングテンプレートを貼り付け、[保存]をクリックします。

スクリーンショット 2020-09-06 23.43.58.png

【マッピングテンプレート】

#set($rawAPIData = $input.path('$'))
## escape any quotes
#set($rawAPIData = $rawAPIData.replace('"', '\"'))

## first we get the number of "&" in the string, this tells us if there is more than one key value pair
#set($countAmpersands = $rawAPIData.length() - $rawAPIData.replace("&", "").length())

## if there are no "&" at all then we have only one key value pair.
## we append an ampersand to the string so that we can tokenise it the same way as multiple kv pairs.
## the "empty" kv pair to the right of the ampersand will be ignored anyway.
#if ($countAmpersands == 0)
 #set($rawPostData = $rawAPIData + "&")
#end

## now we tokenise using the ampersand(s)
#set($tokenisedAmpersand = $rawAPIData.split("&"))

## we set up a variable to hold the valid key value pairs
#set($tokenisedEquals = [])

## now we set up a loop to find the valid key value pairs, which must contain only one "="
#foreach( $kvPair in $tokenisedAmpersand )
 #set($countEquals = $kvPair.length() - $kvPair.replace("=", "").length())
 #if ($countEquals == 1)
  #set($kvTokenised = $kvPair.split("="))
  #if ($kvTokenised[0].length() > 0)
   ## we found a valid key value pair. add it to the list.
   #set($devNull = $tokenisedEquals.add($kvPair))
  #end
 #end
#end

{
"querystring" : {
#foreach( $kvPair in $tokenisedEquals )
  ## finally we output the JSON for this pair and append a comma if this isn't the last pair
  #set($kvTokenised = $kvPair.split("="))
  #if($kvTokenised.size() == 2 && $kvTokenised[1].length() > 0)
    #set($kvValue = $kvTokenised[1])
  #else
    #set($kvValue = "")
  #end
  #if( $foreach.hasNext )
    #set($itemDelimiter = ",")
  #else
    #set($itemDelimiter = "")
  #end
 "$kvTokenised[0]" : "$kvValue"$itemDelimiter
#end
},
"source_ip" : "$context.identity.sourceIp"
}

最後にリソースのデプロイを行い、発行されたURLをコピーしておきます。

スクリーンショット 2020-09-07 0.17.36.png

Slash Commandsの作成

Slack appの作成

Slack apiからSlack appを作成していきます。

[Create New App]をクリックするとAppの作成画面に遷移します。

スクリーンショット 2020-09-05 13.26.37.png

ここで、Slack Appの名前Appを実行したいワークスペースを選択します。
そして、[Create App]をクリックするとSlack Appが完成します。

スクリーンショット 2020-09-05 13.27.43.png

Slash commandsの作成

Appの設定ページに移動して、Slash commandsを作成します。

左側のメニューから[Slash commands]を選択し、[Create New Commands]をクリックします。

スクリーンショット 2020-09-05 13.28.36.png

次に、Slackから送信するコマンド名前述のAPI Gatewayで取得したリクエストURL説明文の3つを入力します。今回は、/get_ipというコマンド名を設定しました。

スクリーンショット 2020-09-05 13.44.48.png

今回は引数を入力しないのでヒントは入れませんでしたが、引数など入力時の補足がある場合はUsage Hintに入力しておきます。Escape channels, users, and links sent to your appにチェックを入れることで、引数にユーザ名を入れる時に、ユーザ名だけではなくユニークなユーザIDも送ることができ、アカウントを識別できるそうです。今回はアカウントの識別は不要なので、チェックは入れませんでした。

それぞれ入力完了後、[Save]をクリックすると、Slash commandsが完成します。

Botの名前設定

このままAppのインストールに進むと、(App名)にはインストールするボットユーザーがありません。というエラーが出て、Appをインストールできなかったので、Appの設定ページから、Botを作成します。

ちなみに、App名が小文字英数字の場合は、自動でBotが設定されるみたいので、初期値が入っていればこのフローは不要です。わたしはIP取得マンというApp名にしていたためボットユーザが作られなかったのだと思われます。

左側のメニューから[App Home]を選択し、Your App's Presence in Slackの[Edit]をクリックします。

スクリーンショット 2020-09-06 18.19.39.png

Botの表示名ユーザ名を入力して[Add]をクリックすると、Botの名前が設定されます。

スクリーンショット 2020-09-06 18.19.51.png

Appのインストール

いよいよAppのインストールです。

左側のメニューから[Install App]を選択し、[Install App to Workspace]をクリックします。

スクリーンショット 2020-09-06 18.20.06.png

作成したAppが指定したワークスペースにアクセスするのを許可すると、グローバルIPを取得するコマンドの完成です。

実行結果

実行方法

今回作成したコマンド/get_ipをメッセージとして送信します。

スクリーンショット 2020-09-13 19.22.55.png

指定チャンネルから実行した場合

指定チャンネルから実行した場合のレスポンスは次の通りです。

スクリーンショット 2020-09-06 19.13.55.png

IPアドレスが返ってきました!!!

ただ、自分のグローバルIPを調べてみると・・・違う。。
これはSlackのグローバルIPアドレスですね。。。

残念ながら、自分のグローバルIPアドレスを取得することはできませんでした。

指定外のチャンネルから実行した場合

ちなみに、指定外のチャンネルから実行した場合のレスポンスは次の通りです。

スクリーンショット 2020-09-06 19.13.45.png

これで、実行チャンネルを識別し、指定チャンネル外からは実行できないことが確認できました!

おわりに

結果、SlackからグローバルIPを取得することはできませんでしたが、SlackのIPは取得することができました。ただ、実行する度に違うIPアドレスが返ってくることも確認できました。今回とは関係ないですが、Slackは複数のグローバルIPアドレスを持っていることがわかりました。

前回に引き続き、SlackからLambdaを実行してみましたが、AWS Chatbotと比較してSlash commandsの良いところは個人DMで使えるというところかなと感じました。反対に、Slash commandsの場合は処理時間が3000msを越えるものは、まず応答を返すようにしないといけないところは少し面倒かな、、と思いました。

今回は、残念な結果に終わってしまったので、また違う方法を考えます。SlackからグローバルIPを取得するいい方法があるよっていう方いらっしゃいましたら、教えていただけると嬉しいです!

参考

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

GOPAHTとかGOROOTってなんやねん

GOROOTとGOPATHとは?ってなったのでメモ。

GOROOT

goをinstallした際に、go本体のソースが置かれる場所。
fmtとかosみたいな、goをinstallしたら初めから付いてくる標準パッケージのソースもここにある。

以下のコマンドを叩くとマシンのGOROOTを吐いてくれる。

$ go env GOROOT
/usr/local/Cellar/go/1.15/libexec

GOPATH

後々追加した、外部のパッケージが置かれる場所。go getとかで入手したパッケージはここに置かれる。

これも以下のコマンドを叩くとマシンのGOPATHを吐いてくれる。

$ go env GOPARH
/Users/userName/go

まとめ

GOROOT:go本体+標準パッケージ
GOPATH:その他の外部パッケージ

ってイメージかな?

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