[Go] Slack을 터미널로 사용하기

🧠·2022년 9월 8일
3

Go

목록 보기
3/5
post-thumbnail

소스 코드: https://github.com/Son0-0/slack-terminal

귀찮은 EC2 접근

평소 AWS EC2에 접근하기 위해 terminal에 아래와 같이 ssh 명령어와 pem 키를 이용하여 접근한다.

$ ssh -i /Users/son/Desktop/*****.pem xxxxx@******

그러다 명령어를 매번 적는 것이 귀찮아 해당 명령어를 쉘 스크립트로 작성하여 실행했고

// server.sh
ssh -i /Users/son/Desktop/*****.pem xxxxx@******

$ ./server.sh

이번에는 그것마저 귀찮아 자주 사용하는 메신저인 슬랙에서 바로 서버에 접근할 수는 없을까 생각하다가 slash command라는 것을 발견했다.


Slack Bot Slash Command

https://api.slack.com

위 사이트에 접근하면 슬랙 워크 스페이스 및 채널에서 활용할 수 있는 app을 만들 수 있다.

Create New App 을 클릭하여 App을 만들 수 있는데 From scratch 를 눌러 진행하면 된다.

Features 에서 Slash Commands를 누르면 다음과 같은 화면을 볼 수 있는데 여기서 Create New Command를 하여 슬랙 명령어를 설정해 주면 된다.

각 필드는 다음과 같이 설정해 주었는데 명령어가 전달될 서버의 도메인 주소 또는 ip 주소를 입력하고 /slack/cmd path로 명령어가 전달될 수 있도록 설정해 준다.

위 과정을 모두 마치고 슬랙 채널에서 아까 설정해 준 명령어를 입력하면 다음과 같은 메시지를 받을 수 있다.

이제 모두 준비가 되었고 해당 명령어를 처리해 줄 서버를 켜두면 된다.


Command 실행을 위한 Server

https://api.slack.com/interactivity/slash-commands

위 사이트에 접근하면 슬랙 Slash Command를 통해 어떤 데이터들을 전달받는지 볼 수 있는데 슬랙 채널에 /server ls -al 명령어를 입력했다면 text 필드에 ls -al 이 들어오게 된다.

Content-type은 applicaition/json 이 아닌 application/x-www-form-urlencoded 이다

main.go

package main

import (
	"fmt"
	"log"
	"net/http"
	"os"

	"github.com/Son0-0/slack-terminal/handlers"
)

func main() {
	outLog, err := os.OpenFile("out.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	log.SetOutput(outLog)
	defer outLog.Close()

	if handlers.HandleError(err) {
		panic(err)
	}

	http.HandleFunc("/slack/cmd", handlers.ExecCommand)

	fmt.Println("Server running on port 9999")
	http.ListenAndServe(":9999", nil)
}

슬랙 명령어 처리를 위한 간단한 API 서버를 Go 언어로 작성하였다. 모든 요청을 logging 하기 위해 stdout을 out.log 파일로 설정해 주었다. 슬랙 Slash Command를 통해 명령어를 입력하면 /slack/cmd 경로로 요청이 들어오고 handler에 등록해 둔 ExecCommand 함수가 실행되도록 작성하였다.

/handlers/error.go

package handlers

import "log"

func HandleError(err error) bool {
	if err != nil {
		log.Printf("[ERROR] %v", err.Error())
		return true
	}
	return false
}

에러 처리를 쉽게 하기 위해 해당 함수를 따로 만들었고 파라미터로 들어오는 err가 nil이 아닐 때 [ERROR] err message 포맷으로 로깅하도록 코드를 작성하였다.

/handlers/cmd.go

package handlers

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os/exec"
	"time"
)

func ExecCommand(w http.ResponseWriter, r *http.Request) {
	err := r.ParseForm()
	if HandleError(err) {
		fmt.Fprintln(w, "parse error", http.StatusInternalServerError)
	}

	length := len(r.Form["text"])
	if length != 0 {
		log.Printf("$ %s %s\n", r.Form["user_id"], r.Form["text"][0])

		ctx, cancel := context.WithTimeout(context.Background(), 2000*time.Millisecond)
		defer cancel()

		cmd := exec.CommandContext(ctx, "bash", "-c", r.Form["text"][0])
		stdout, err := cmd.Output()

		if HandleError(err) {
			fmt.Fprintf(w, "*ERROR*\n>msg: `%v`", err)
			return
		}
		fmt.Fprintf(w, "```"+string(stdout)+"```")
	}
}

직접적으로 슬랙을 통해 전달된 명령어를 처리하는 함수다.

슬랙으로부터 받은 요청에서 명령어를 추출하는 과정을 거치는데 그전에 ParseForm 메소드를 호출해 준다. 직접 실행할 명령어는 r.Formtext 필드에 있다. 해당 명령어를 입력한 유저를 로깅하는 것도 필요할 것 같아 user_id 필드에서 유저를 추출하여 로그를 작성해 준다.

2초 이상의 시간이 필요한 명령어의 경우 강제로 종료되도록 context.WithTimeout을 통해 timeout 시간을 설정해 주었고 CommandContext를 통해 슬랙으로부터 전달받은 명령어를 실행한다.


결과

슬랙 앱을 설치한 워크 스페이스에서 /server ls 명령어와 /server ls -al 명령어를 입력한 결과이다.

out.log 파일에 아래와 같이 로그가 작성되는 것을 볼 수 있다.

2022/09/08 12:57:26 $ [U0000000002] ls
2022/09/08 12:57:46 $ [U0000000002] ls -al
2022/09/08 13:00:23 $ [U0000000002] docker ps -a
2022/09/08 13:00:32 $ [U0000000002] docker images
2022/09/08 13:04:34 $ [U0000000002] tail out.log

해당 로그도 /server tail out.log 명령어를 입력하면 슬랙으로 받아볼 수 있다.

리눅스 서버에서 실행되는 여러 명령어를 입력할 수 있으며 간단한 메시지 입력을 통해 EC2 서버를 제어할 수 있다.

profile
: )

0개의 댓글