diff --git a/.github/workflows/dapr-bot.yml b/.github/workflows/dapr-bot.yml new file mode 100644 index 00000000..05bbb984 --- /dev/null +++ b/.github/workflows/dapr-bot.yml @@ -0,0 +1,34 @@ +name: dapr-bot + +on: + issue_comment: + types: [created] + +jobs: + bot-run: + name: bot-processor + runs-on: ubuntu-latest + permissions: + issues: write + contents: read + env: + GITHUB_TOKEN: ${{ github.token }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Golang + uses: actions/setup-go@v4 + with: + go-version: ~1.21 + cache-dependency-path: | + ./.github/workflows/dapr-bot/ + + - name: go-bot-mod + working-directory: ./.github/workflows/dapr-bot/ + run: go get ./... + - name: go-bot-run + + working-directory: ./.github/workflows/dapr-bot/ + run: go run . diff --git a/.github/workflows/dapr-bot/Makefile b/.github/workflows/dapr-bot/Makefile new file mode 100644 index 00000000..d5a1e77f --- /dev/null +++ b/.github/workflows/dapr-bot/Makefile @@ -0,0 +1,17 @@ +GO_COMPAT_VERSION=1.21 + +.PHONY: cover +cover: + go test -coverprofile=cover.out ./ && go tool cover -html=cover.out + +.PHONY: tidy +tidy: ## Updates the go modules + go mod tidy -compat=$(GO_COMPAT_VERSION) + +.PHONY: test +test: + go test -count=1 \ + -race \ + -coverprofile=coverage.txt \ + -covermode=atomic \ + ./... \ No newline at end of file diff --git a/.github/workflows/dapr-bot/bot.go b/.github/workflows/dapr-bot/bot.go new file mode 100644 index 00000000..82efb632 --- /dev/null +++ b/.github/workflows/dapr-bot/bot.go @@ -0,0 +1,95 @@ +package main + +import ( + "context" + "errors" + "fmt" + "net/http" + "strings" + + "github.com/google/go-github/v55/github" +) + +var ( + errCommentBodyEmpty = errors.New("comment body is empty") + errIssueClosed = errors.New("issue is closed") + errIssueAlreadyAssigned = errors.New("issue is already assigned") + errUnauthorizedClient = errors.New("possibly unauthorized client issue") +) + +type issueInterface interface { + CreateComment(ctx context.Context, owner string, repo string, number int, comment *github.IssueComment) (*github.IssueComment, *github.Response, error) + AddAssignees(ctx context.Context, owner string, repo string, number int, assignees []string) (*github.Issue, *github.Response, error) +} + +type Bot struct { + ctx context.Context + issueClient issueInterface +} + +func NewBot(ghClient *github.Client) *Bot { + return &Bot{ + ctx: context.Background(), + issueClient: ghClient.Issues, + } +} + +func (b *Bot) HandleEvent(ctx context.Context, event Event) (res string, err error) { + commentBody := event.IssueCommentEvent.Comment.GetBody() + + // split the comment after any potential new lines + newline := strings.Split(strings.ReplaceAll(commentBody, "\r\n", "\n"), "\n")[0] + + command := strings.Split(newline, " ")[0] + + if command[0] != '/' { + return "no command found", err + } + + switch command { + case "/assign": + assignee, err := b.AssignIssueToCommenter(event) + res = fmt.Sprintf("👍 Issue assigned to %s", assignee) + if err == nil { + err = b.CreateIssueComment(fmt.Sprintf("🚀 Issue assigned to you @%s", assignee), event) + } else { + err = b.CreateIssueComment("⚠️ Unable to assign issue", event) + } + if err != nil { + return fmt.Sprintf("failed to comment on issue: %v", err), err + } + } + return +} + +func (b *Bot) CreateIssueComment(body string, event Event) error { + if body == "" { + return errCommentBodyEmpty + } + ctx := context.Background() + comment := &github.IssueComment{ + Body: github.String(body), + } + _, response, err := b.issueClient.CreateComment(ctx, event.GetIssueOrg(), event.GetIssueRepo(), event.GetIssueNumber(), comment) + if err != nil || response.StatusCode == http.StatusNotFound { + return fmt.Errorf("failed to create comment: %v%v", err, response.StatusCode) + } + return nil +} + +func (b *Bot) AssignIssueToCommenter(event Event) (string, error) { + if event.GetIssueState() == "closed" { + return "", errIssueClosed + } + + if len(event.GetIssueAssignees()) > 0 { + return "", errIssueAlreadyAssigned + } + + ctx := context.Background() + _, response, err := b.issueClient.AddAssignees(ctx, event.GetIssueOrg(), event.GetIssueRepo(), event.GetIssueNumber(), []string{event.GetIssueUser()}) + if response.StatusCode == http.StatusNotFound { + return "", errUnauthorizedClient + } + return event.GetIssueUser(), err +} diff --git a/.github/workflows/dapr-bot/bot_test.go b/.github/workflows/dapr-bot/bot_test.go new file mode 100644 index 00000000..e54724bb --- /dev/null +++ b/.github/workflows/dapr-bot/bot_test.go @@ -0,0 +1,206 @@ +package main + +import ( + "context" + "net/http" + "testing" + + "github.com/google/go-github/v55/github" + "github.com/jinzhu/copier" + "github.com/stretchr/testify/assert" +) + +var testBot *Bot = &Bot{ + ctx: context.Background(), + issueClient: &testClient{}, +} + +type testClient struct { + issue *github.Issue + issueComment *github.IssueComment + resp *github.Response +} + +func (tc *testClient) CreateComment(ctx context.Context, org, repo string, number int, comment *github.IssueComment) (*github.IssueComment, *github.Response, error) { + return tc.issueComment, tc.resp, nil +} + +func (tc *testClient) AddAssignees(ctx context.Context, org, repo string, number int, assignees []string) (*github.Issue, *github.Response, error) { + return tc.issue, tc.resp, nil +} + +func TestNewBot(t *testing.T) { + t.Run("create a bot test", func(t *testing.T) { + bot := NewBot(github.NewClient(nil)) + assert.NotNil(t, bot) + }) +} + +func TestHandleEvent(t *testing.T) { + t.Run("handle valid event", func(t *testing.T) { + tc := testClient{ + resp: &github.Response{Response: &http.Response{StatusCode: http.StatusOK}}, + } + testBot.issueClient = &tc + ctx := context.Background() + var testEventCopy Event + errC := copier.CopyWithOption(&testEventCopy, &testEvent, copier.Option{DeepCopy: true}) + if errC != nil { + t.Error(errC) + } + testEventCopy.IssueCommentEvent.Comment.Body = github.String("/assign") + res, err := testBot.HandleEvent(ctx, testEventCopy) + assert.NoError(t, err) + assert.NotEmpty(t, res) + }) + + t.Run("handle valid (longer body) event", func(t *testing.T) { + tc := testClient{ + resp: &github.Response{Response: &http.Response{StatusCode: http.StatusOK}}, + } + testBot.issueClient = &tc + ctx := context.Background() + var testEventCopy Event + errC := copier.CopyWithOption(&testEventCopy, &testEvent, copier.Option{DeepCopy: true}) + if errC != nil { + t.Error(errC) + } + testEventCopy.IssueCommentEvent.Comment.Body = github.String("/assign \r \ntest body") + res, err := testBot.HandleEvent(ctx, testEventCopy) + assert.NoError(t, err) + assert.NotEmpty(t, res) + }) + + t.Run("handle unable to assign", func(t *testing.T) { + tc := testClient{ + resp: &github.Response{Response: &http.Response{StatusCode: http.StatusNotFound}}, + } + testBot.issueClient = &tc + ctx := context.Background() + var testEventCopy Event + errC := copier.CopyWithOption(&testEventCopy, &testEvent, copier.Option{DeepCopy: true}) + if errC != nil { + t.Error(errC) + } + testEventCopy.IssueCommentEvent.Comment.Body = github.String("/assign") + res, err := testBot.HandleEvent(ctx, testEventCopy) + assert.Error(t, err) + assert.NotEmpty(t, res) + }) + + t.Run("handle no event", func(t *testing.T) { + tc := testClient{} + testBot.issueClient = &tc + ctx := context.Background() + var testEventCopy Event + errC := copier.CopyWithOption(&testEventCopy, &testEvent, copier.Option{DeepCopy: true}) + if errC != nil { + t.Error(errC) + } + testEventCopy.IssueCommentEvent.Comment.Body = github.String("assign") + res, err := testBot.HandleEvent(ctx, testEventCopy) + assert.NoError(t, err) + assert.Equal(t, "no command found", res) + }) +} + +func TestCreateIssueComment(t *testing.T) { + t.Run("failure to create issue comment", func(t *testing.T) { + tc := testClient{ + resp: &github.Response{Response: &http.Response{StatusCode: http.StatusNotFound}}, + } + testBot.issueClient = &tc + err := testBot.CreateIssueComment("test", testEvent) + assert.Error(t, err) + }) + + t.Run("create issue comment", func(t *testing.T) { + tc := testClient{ + resp: &github.Response{Response: &http.Response{StatusCode: http.StatusOK}}, + } + testBot.issueClient = &tc + err := testBot.CreateIssueComment("test", testEvent) + assert.NoError(t, err) + }) + + t.Run("create issue comment with empty body", func(t *testing.T) { + tc := testClient{ + resp: &github.Response{Response: &http.Response{StatusCode: http.StatusOK}}, + } + testBot.issueClient = &tc + err := testBot.CreateIssueComment("", testEvent) + assert.Error(t, err) + }) +} + +func TestAssignIssueToCommenter(t *testing.T) { + t.Run("failure to assign issue to commenter", func(t *testing.T) { + tc := testClient{ + resp: &github.Response{Response: &http.Response{StatusCode: http.StatusNotFound}}, + } + testBot.issueClient = &tc + assignee, err := testBot.AssignIssueToCommenter(testEvent) + assert.Error(t, err) + assert.Empty(t, assignee) + }) + + t.Run("successfully assign issue to commenter", func(t *testing.T) { + tc := testClient{ + resp: &github.Response{Response: &http.Response{StatusCode: http.StatusOK}}, + } + testBot.issueClient = &tc + var testEventCopy Event + errC := copier.CopyWithOption(&testEventCopy, &testEvent, copier.Option{DeepCopy: true}) + if errC != nil { + t.Error(errC) + } + testEventCopy.IssueCommentEvent.Issue.Assignees = []*github.User{} + assignee, err := testBot.AssignIssueToCommenter(testEventCopy) + assert.NoError(t, err) + assert.Equal(t, "testCommentLogin", assignee) + }) + + t.Run("attempt to assign a closed issue", func(t *testing.T) { + tc := testClient{} + testBot.issueClient = &tc + var testEventCopy Event + errC := copier.CopyWithOption(&testEventCopy, &testEvent, copier.Option{DeepCopy: true}) + if errC != nil { + t.Error(errC) + } + testEventCopy.IssueCommentEvent.Issue.State = github.String("closed") + assignee, err := testBot.AssignIssueToCommenter(testEventCopy) + assert.Error(t, err) + assert.Empty(t, assignee) + }) + + t.Run("issue already assigned to user", func(t *testing.T) { + tc := testClient{} + testBot.issueClient = &tc + var testEventCopy Event + errC := copier.CopyWithOption(&testEventCopy, &testEvent, copier.Option{DeepCopy: true}) + if errC != nil { + t.Error(errC) + } + testEventCopy.IssueCommentEvent.Issue.Assignees = []*github.User{{Login: github.String("testCommentLogin")}} + assignee, err := testBot.AssignIssueToCommenter(testEventCopy) + assert.Error(t, err) + assert.Empty(t, assignee) + }) + + t.Run("issue already assigned to another user", func(t *testing.T) { + tc := testClient{ + resp: &github.Response{Response: &http.Response{StatusCode: http.StatusOK}}, + } + testBot.issueClient = &tc + var testEventCopy Event + errC := copier.CopyWithOption(&testEventCopy, &testEvent, copier.Option{DeepCopy: true}) + if errC != nil { + t.Error(errC) + } + testEventCopy.IssueCommentEvent.Issue.Assignees = []*github.User{{Login: github.String("testCommentLogin2")}} + assignee, err := testBot.AssignIssueToCommenter(testEventCopy) + assert.Error(t, err) + assert.Empty(t, assignee) + }) +} diff --git a/.github/workflows/dapr-bot/event.go b/.github/workflows/dapr-bot/event.go new file mode 100644 index 00000000..473e1953 --- /dev/null +++ b/.github/workflows/dapr-bot/event.go @@ -0,0 +1,62 @@ +package main + +import ( + "encoding/json" + "errors" + + "github.com/google/go-github/v55/github" +) + +type Event struct { + Type string `json:"type"` + Path string `json:"path"` + IssueCommentEvent *github.IssueCommentEvent `json:"issue_comment_event"` +} + +func ProcessEvent(eventType string, eventPath string, data []byte) (e Event, err error) { + var issueCommentEvent *github.IssueCommentEvent + if eventPath == "" { + return Event{}, errors.New("invalid event path") + } + switch eventType { + case "issue_comment": + err = json.Unmarshal(data, &issueCommentEvent) + if err != nil { + return Event{}, err + } + } + e = Event{ + Type: eventType, + Path: eventPath, + IssueCommentEvent: issueCommentEvent, + } + return +} + +func (e *Event) GetIssueAssignees() []string { + assignees := make([]string, 0) + for _, assignee := range e.IssueCommentEvent.Issue.Assignees { + assignees = append(assignees, assignee.GetLogin()) + } + return assignees +} + +func (e *Event) GetIssueNumber() int { + return e.IssueCommentEvent.Issue.GetNumber() +} + +func (e *Event) GetIssueOrg() string { + return e.IssueCommentEvent.Repo.Owner.GetLogin() +} + +func (e *Event) GetIssueRepo() string { + return e.IssueCommentEvent.Repo.GetName() +} + +func (e *Event) GetIssueState() string { + return e.IssueCommentEvent.Issue.GetState() +} + +func (e *Event) GetIssueUser() string { + return e.IssueCommentEvent.Comment.User.GetLogin() +} diff --git a/.github/workflows/dapr-bot/event_test.go b/.github/workflows/dapr-bot/event_test.go new file mode 100644 index 00000000..c3b1ff08 --- /dev/null +++ b/.github/workflows/dapr-bot/event_test.go @@ -0,0 +1,109 @@ +package main + +import ( + "bytes" + "encoding/gob" + "encoding/json" + "testing" + + "github.com/google/go-github/v55/github" + "github.com/stretchr/testify/assert" +) + +var testEvent Event = Event{ + Type: "issue_comment", + Path: "test/test", + IssueCommentEvent: &github.IssueCommentEvent{ + Issue: &github.Issue{ + Assignees: []*github.User{{Login: github.String("testAssignee")}}, + Number: github.Int(123), + State: github.String("testState"), + }, + Repo: &github.Repository{ + Owner: &github.User{Login: github.String("testOrg")}, + Name: github.String("testName"), + }, + Comment: &github.IssueComment{ + User: &github.User{Login: github.String("testCommentLogin")}, + }, + }, +} + +func TestProcessEvent(t *testing.T) { + testEventData, err := json.Marshal(testEvent) + if err != nil { + t.Fatalf("failed to marshal event: %v", err) + } + t.Run("process event", func(t *testing.T) { + event, err := ProcessEvent(testEvent.Type, testEvent.Path, testEventData) + assert.NoError(t, err) + assert.NotNil(t, event) + assert.Equal(t, "test/test", event.Path) + }) + + t.Run("process event with empty path", func(t *testing.T) { + event, err := ProcessEvent(testEvent.Type, "", testEventData) + assert.Error(t, err) + assert.Empty(t, event) + }) + + var randomData bytes.Buffer + encoder := gob.NewEncoder(&randomData) + encoder.Encode("random_data") + + t.Run("process issue_comment event", func(t *testing.T) { + event, err := ProcessEvent(testEvent.Type, testEvent.Path, testEventData) + assert.NoError(t, err) + assert.NotNil(t, event) + assert.Equal(t, "issue_comment", event.Type) + }) + + t.Run("process invalid event", func(t *testing.T) { + event, err := ProcessEvent(testEvent.Type, testEvent.Path, randomData.Bytes()) + assert.Error(t, err) + assert.Empty(t, event) + }) +} + +func TestGetIssueAssignees(t *testing.T) { + t.Run("get assignees", func(t *testing.T) { + assignees := testEvent.GetIssueAssignees() + assert.Equal(t, 1, len(assignees)) + assert.Equal(t, "testAssignee", assignees[0]) + }) +} + +func TestGetIssueNumber(t *testing.T) { + t.Run("get issue number", func(t *testing.T) { + number := testEvent.GetIssueNumber() + assert.Equal(t, 123, number) + }) +} + +func TestGetIssueOrg(t *testing.T) { + t.Run("get issue org", func(t *testing.T) { + org := testEvent.GetIssueOrg() + assert.Equal(t, "testOrg", org) + }) +} + +func TestGetIssueRepo(t *testing.T) { + t.Run("get issue repo", func(t *testing.T) { + repo := testEvent.GetIssueRepo() + assert.Equal(t, "testName", repo) + }) +} + +func TestGetIssueState(t *testing.T) { + t.Run("get issue state", func(t *testing.T) { + state := testEvent.GetIssueState() + assert.Equal(t, "testState", state) + }) +} + +func TestGetIssueUser(t *testing.T) { + t.Run("get issue user", func(t *testing.T) { + user := testEvent.GetIssueUser() + assert.Equal(t, "testCommentLogin", user) + }) +} diff --git a/.github/workflows/dapr-bot/go.mod b/.github/workflows/dapr-bot/go.mod new file mode 100644 index 00000000..92904a7e --- /dev/null +++ b/.github/workflows/dapr-bot/go.mod @@ -0,0 +1,20 @@ +module github.com/dapr/go-sdk/.github/workflows/dapr-bot + +go 1.21 + +require ( + github.com/google/go-github/v55 v55.0.0 + github.com/jinzhu/copier v0.4.0 + github.com/stretchr/testify v1.8.4 +) + +require ( + github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect + github.com/cloudflare/circl v1.3.3 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/crypto v0.12.0 // indirect + golang.org/x/sys v0.11.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/.github/workflows/dapr-bot/go.sum b/.github/workflows/dapr-bot/go.sum new file mode 100644 index 00000000..032416ae --- /dev/null +++ b/.github/workflows/dapr-bot/go.sum @@ -0,0 +1,38 @@ +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= +github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= +github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github/v55 v55.0.0 h1:4pp/1tNMB9X/LuAhs5i0KQAE40NmiR/y6prLNb9x9cg= +github.com/google/go-github/v55 v55.0.0/go.mod h1:JLahOTA1DnXzhxEymmFF5PP2tSS9JVNj68mSZNDwskA= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= +github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/.github/workflows/dapr-bot/main.go b/.github/workflows/dapr-bot/main.go new file mode 100644 index 00000000..4bc96451 --- /dev/null +++ b/.github/workflows/dapr-bot/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "context" + "log" + "os" + + "github.com/google/go-github/v55/github" +) + +func main() { + ctx := context.Background() + githubToken := os.Getenv("GITHUB_TOKEN") + if githubToken == "" { + log.Fatal("GITHUB_TOKEN is required") + } + + ghClient := github.NewClient(nil).WithAuthToken(githubToken) + bot := NewBot(ghClient) + eventType := os.Getenv("GITHUB_EVENT_NAME") + eventPath := os.Getenv("GITHUB_EVENT_PATH") + data, err := os.ReadFile(eventPath) + if err != nil { + log.Fatalf("failed to read event: %v", err) + } + event, err := ProcessEvent(eventType, eventPath, data) + if err != nil { + log.Fatalf("failed to process event: %v", err) + } + log.Printf("processing event: %s", event.Type) + + res, err := bot.HandleEvent(ctx, event) + if err != nil { + log.Fatalf("failed to handle event: %v", err) + } + log.Println(res) +} diff --git a/.github/workflows/test-dapr-bot.yml b/.github/workflows/test-dapr-bot.yml new file mode 100644 index 00000000..328ad944 --- /dev/null +++ b/.github/workflows/test-dapr-bot.yml @@ -0,0 +1,51 @@ +name: Test Dapr-Bot + +on: + push: + paths: # Explicitly declare which paths + - ".github/workflows/dapr-bot.yml" + - ".github/workflows/dapr-bot/*" + pull_request: + branches: + - main + paths: # Explicitly declare which paths + - ".github/workflows/dapr-bot.yml" + - ".github/workflows/dapr-bot/*" + +jobs: + build: + name: Test on ${{ matrix.gover }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + gover: + - "1.21" + env: + GOVER: ${{ matrix.gover }} + GOLANGCILINT_VER: v1.54.2 + + steps: + - name: Setup + uses: actions/setup-go@v4 + with: + go-version: ${{ env.GOVER }} + + - name: Checkout + uses: actions/checkout@v4 + + - name: Tidy + working-directory: ./.github/workflows/dapr-bot + run: make tidy + + - name: Test + working-directory: ./.github/workflows/dapr-bot + run: make test + + - name: Lint + uses: golangci/golangci-lint-action@v3 + with: + version: ${{ env.GOLANGCILINT_VER }} + working-directory: ./.github/workflows/dapr-bot + skip-cache: true + args: --timeout=10m0s --config ../../../.golangci.yml