From b35740b97a2151df2a500b947b7f5f7e182ceacb Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 6 Sep 2017 12:50:36 -0300 Subject: [PATCH 1/3] Add Assembla support (#54) * Add assembla commithook implementation and test * Add assembla to supported webhooks * Update readme to support Assembla * Fix lint issues found by bitrise run test * Fix indentation * Change TriggeredBy to wekbook * Add and check for empty commit hash --- .gitignore | 1 + README.md | 22 +++ service/hook/assembla/assembla.go | 153 ++++++++++++++++ service/hook/assembla/assembla_test.go | 234 +++++++++++++++++++++++++ service/hook/endpoint.go | 2 + 5 files changed, 412 insertions(+) create mode 100644 service/hook/assembla/assembla.go create mode 100644 service/hook/assembla/assembla_test.go diff --git a/.gitignore b/.gitignore index 87abf285..fa66117f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .bitrise* gin-bin .gows.user.yml +.idea diff --git a/README.md b/README.md index ed7fbf5b..a00e86bd 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,8 @@ If the (commit) message includes `[skip ci]` or `[ci skip]` no build will be tri * handled on the path: `/h/gogs/BITRISE-APP-SLUG/BITRISE-APP-API-TOKEN` * [Deveo](https://deveo.com) * handled on the path: `/h/deveo/BITRISE-APP-SLUG/BITRISE-APP-API-TOKEN` +* [Assembla](https://assembla.com) + * handled on the path: `/h/assembla/BITRISE-APP-SLUG/BITRISE-APP-API-TOKEN` ### GitHub - setup & usage: @@ -151,6 +153,25 @@ a [Deveo](https://deveo.com) *repository*. That's all! The next time you __push code__ or __push a new tag__ a build will be triggered (if you have Trigger mapping defined for the event(s) on Bitrise). +### Assembla - setup & usage: + +Follow these steps to add your `bitrise-webhooks` URL to your [Assembla](https://assembla.com) *space*. + +1. Open your *space* on [assembla.com](https://assembla.com) or your organisation's assembla domain +2. Go to the `Webhooks` section of the space +3. Select `Create New Webhook` +4. Set `Title` to `BitRise Webhook` +5. Specify the `bitrise-webhooks` URL (`.../h/assembla/BITRISE-APP-SLUG/BITRISE-APP-API-TOKEN`) in the `External url` field +6. Select `application/json` in the `Content type` field +7. Paste the following code to `Content`: +```json +{"assembla": {"space": "%{space}", "action": "%{action}", "object": "%{object}"}, "message": {"title": "%{title}", "body": "%{body}", "author": "%{author}"}, "git": {"repository_suffix": "%{repository_suffix}", "repository_url": "%{repository_url}", "branch": "%{branch}", "commit_id": "%{commit_id}"}} +``` +8. Select `Code commits` in the `Post updates about:` section +9. Click `Add` + +That's all! The next time you __push code__ a build will be triggered (if you have Trigger mapping defined for the event(s) on Bitrise). + ### Slack - setup & usage: You can register the `bitrise-webhooks` URL (`.../h/slack/BITRISE-APP-SLUG/BITRISE-APP-API-TOKEN`) as either @@ -401,3 +422,4 @@ response provider will be used. * [Chad Robinson](https://github.com/crrobinson14) - `Gogs` support * [Rafael Nobre](https://github.com/nobre84) - Environment variables support in `Slack` commands * [Tuomas Peippo](https://github.com/tume)- Skip CI feature +* [Erik Poort](https://github.com/ErikMediaMonks) - `Assembla` support diff --git a/service/hook/assembla/assembla.go b/service/hook/assembla/assembla.go new file mode 100644 index 00000000..61d123a6 --- /dev/null +++ b/service/hook/assembla/assembla.go @@ -0,0 +1,153 @@ +package assembla + +// +// Docs: https://articles.assembla.com/assembla-basics/learn-more/post-information-to-external-systems-using-webhooks +// + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + + "github.com/bitrise-io/bitrise-webhooks/bitriseapi" + hookCommon "github.com/bitrise-io/bitrise-webhooks/service/hook/common" +) + +// -------------------------- +// --- Webhook Data Model --- + +// SpaceEventModel ... +type SpaceEventModel struct { + Space string `json:"space"` + Action string `json:"action"` + Object string `json:"object"` +} + +// MessageEventModel ... +type MessageEventModel struct { + Title string `json:"title"` + Body string `json:"body"` + Author string `json:"author"` +} + +// GitEventModel ... +type GitEventModel struct { + RepositorySuffix string `json:"repository_suffix"` + RepositoryURL string `json:"repository_url"` + Branch string `json:"branch"` + CommitID string `json:"commit_id"` +} + +// PushEventModel ... +type PushEventModel struct { + SpaceEventModel SpaceEventModel `json:"assembla"` + MessageEventModel MessageEventModel `json:"message"` + GitEventModel GitEventModel `json:"git"` +} + +// --------------------------------------- +// --- Webhook Provider Implementation --- + +// HookProvider ... +type HookProvider struct{} + +func detectContentType(header http.Header) (string, error) { + contentType := header.Get("Content-Type") + if contentType == "" { + return "", errors.New("No Content-Type Header found") + } + + return contentType, nil +} + +func detectAssemblaData(pushEvent PushEventModel) error { + if (pushEvent.GitEventModel.CommitID == "") || + (pushEvent.GitEventModel.Branch == "") || + (pushEvent.GitEventModel.RepositoryURL == "") || + (pushEvent.GitEventModel.RepositorySuffix == "") { + return errors.New("Webhook is not correctly setup, make sure you post updates about 'Code commits' in Assembla") + } + + if (pushEvent.GitEventModel.CommitID == "---") || + (pushEvent.GitEventModel.Branch == "---") || + (pushEvent.GitEventModel.RepositoryURL == "---") || + (pushEvent.GitEventModel.RepositorySuffix == "---") { + return errors.New("Webhook is not correctly setup, make sure you post updates about 'Code commits' in Assembla") + } + + return nil +} + +func transformPushEvent(pushEvent PushEventModel) hookCommon.TransformResultModel { + if pushEvent.SpaceEventModel.Action != "committed" { + return hookCommon.TransformResultModel{ + Error: fmt.Errorf("Action was not 'committed', was: %s", pushEvent.SpaceEventModel.Action), + } + } + if pushEvent.MessageEventModel.Body == "" { + return hookCommon.TransformResultModel{ + Error: fmt.Errorf("Message body can't be empty"), + } + } + if pushEvent.MessageEventModel.Author == "" { + return hookCommon.TransformResultModel{ + Error: fmt.Errorf("Message author can't be empty"), + } + } + if pushEvent.GitEventModel.Branch == "" { + return hookCommon.TransformResultModel{ + Error: fmt.Errorf("Git branch can't be empty"), + } + } + if pushEvent.GitEventModel.CommitID == "" { + return hookCommon.TransformResultModel{ + Error: fmt.Errorf("Git commit id can't be empty"), + } + } + + triggerAPIParams := []bitriseapi.TriggerAPIParamsModel{ + { + BuildParams: bitriseapi.BuildParamsModel{ + CommitMessage: pushEvent.MessageEventModel.Body, + Branch: pushEvent.GitEventModel.Branch, + CommitHash: pushEvent.GitEventModel.CommitID, + }, + TriggeredBy: "webhook", + }, + } + + return hookCommon.TransformResultModel{ + TriggerAPIParams: triggerAPIParams, + } +} + +// TransformRequest ... +func (hp HookProvider) TransformRequest(r *http.Request) hookCommon.TransformResultModel { + contentType, err := detectContentType(r.Header) + if err != nil { + return hookCommon.TransformResultModel{ + Error: fmt.Errorf("Issue with Headers: %s", err), + } + } + if contentType != hookCommon.ContentTypeApplicationJSON { + return hookCommon.TransformResultModel{ + Error: fmt.Errorf("Content-Type is not supported: %s", contentType), + } + } + + if r.Body == nil { + return hookCommon.TransformResultModel{ + Error: fmt.Errorf("Failed to read content of request body: no or empty request body"), + } + } + + var pushEvent PushEventModel + if err := json.NewDecoder(r.Body).Decode(&pushEvent); err != nil { + return hookCommon.TransformResultModel{ + Error: fmt.Errorf("Failed to parse request body as JSON: %s", err), + } + } + + return transformPushEvent(pushEvent) +} diff --git a/service/hook/assembla/assembla_test.go b/service/hook/assembla/assembla_test.go new file mode 100644 index 00000000..65c4d04d --- /dev/null +++ b/service/hook/assembla/assembla_test.go @@ -0,0 +1,234 @@ +package assembla + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/require" + "github.com/bitrise-io/bitrise-webhooks/bitriseapi" + "io/ioutil" + "strings" +) + +func Test_detectContentType(t *testing.T) { + t.Log("Push event - should handle") + { + header := http.Header{ + "Content-Type": {"application/json"}, + } + contentType, err := detectContentType(header) + require.NoError(t, err) + require.Equal(t, "application/json", contentType) + } +} + +func Test_transformPushEvent(t *testing.T) { + t.Log("Do Transform - code push") + { + pushEvent := PushEventModel{ + SpaceEventModel: SpaceEventModel{ + Space: "Space name", + Action: "committed", + Object: "Changeset", + }, + MessageEventModel: MessageEventModel{ + Title: "1 commits [branchname]", + Body: "ErikPoort pushed 1 commits [branchname]\n", + Author: "ErikPoort", + }, + GitEventModel: GitEventModel{ + RepositorySuffix: "origin", + RepositoryURL: "git@git.assembla.com:username/project.git", + Branch: "branchname", + CommitID: "sha1chars11", + }, + } + + // OK + { + hookTransformResult := transformPushEvent(pushEvent) + err := detectAssemblaData(pushEvent) + require.Equal(t, err, nil) + require.NoError(t, hookTransformResult.Error) + require.False(t, hookTransformResult.ShouldSkip) + require.Equal(t, []bitriseapi.TriggerAPIParamsModel{ + { + BuildParams: bitriseapi.BuildParamsModel{ + CommitMessage: "ErikPoort pushed 1 commits [branchname]\n", + Branch: "branchname", + CommitHash: "sha1chars11", + }, + TriggeredBy: "webhook", + }, + }, hookTransformResult.TriggerAPIParams) + require.Equal(t, false, hookTransformResult.DontWaitForTriggerResponse) + } + } +} + +func Test_incorrectPostOptions(t *testing.T) { + t.Log("Git Push update") + { + pushEvent := PushEventModel{ + SpaceEventModel: SpaceEventModel{ + Space: "Space name", + Action: "committed", + Object: "Changeset", + }, + MessageEventModel: MessageEventModel{ + Title: "1 commits [branchname]", + Body: "ErikPoort pushed 1 commits [branchname]\n", + Author: "ErikPoort", + }, + GitEventModel: GitEventModel{ + RepositorySuffix: "---", + RepositoryURL: "---", + Branch: "---", + CommitID: "---", + }, + } + + // OK + { + err := detectAssemblaData(pushEvent) + require.EqualError(t, err, "Webhook is not correctly setup, make sure you post updates about 'Code commits' in Assembla") + } + } +} + +func Test_emptyGitEventOptions(t *testing.T) { + t.Log("Git Push update") + { + pushEvent := PushEventModel{ + SpaceEventModel: SpaceEventModel{ + Space: "Space name", + Action: "committed", + Object: "Changeset", + }, + MessageEventModel: MessageEventModel{ + Title: "1 commits [branchname]", + Body: "ErikPoort pushed 1 commits [branchname]\n", + Author: "ErikPoort", + }, + GitEventModel: GitEventModel{ + RepositorySuffix: "", + RepositoryURL: "", + Branch: "", + CommitID: "", + }, + } + + // OK + { + err := detectAssemblaData(pushEvent) + require.EqualError(t, err, "Webhook is not correctly setup, make sure you post updates about 'Code commits' in Assembla") + } + } +} + +const ( + sampleCodePushData = `{ + "assembla": { + "space": "Space name", + "action": "committed", + "object": "Changeset" + }, + "message": { + "title": "1 commits [branchname]", + "body": "ErikPoort pushed 1 commits [branchname]\n", + "author": "ErikPoort" + }, + "git": { + "repository_suffix": "origin", + "repository_url": "git@git.assembla.com:username/project.git", + "branch": "branchname", + "commit_id": "sha1chars11" + } + }` + sampleIncorrectJSONData = `{ + "assembla": { + "space": "Space name", + "action": "committed", + "object": "Changeset", + }, + "message": { + "title": "1 commits [branchname]", + "body": "ErikPoort pushed 1 commits [branchname]\n", + "author": "ErikPoort", + }, + "git": { + "repository_suffix": "origin", + "repository_url": "git@git.assembla.com:username/project.git", + "branch": "branchname", + "commit_id": "sha1chars11", + } + }` +) + +func Test_HookProvider_TransformRequest(t *testing.T) { + provider := HookProvider{} + + t.Log("Unsupported Content-Type") + { + request := http.Request{ + Header: http.Header{ + "Content-Type": {"not/supported"}, + }, + } + hookTransformResult := provider.TransformRequest(&request) + require.False(t, hookTransformResult.ShouldSkip) + require.EqualError(t, hookTransformResult.Error, "Content-Type is not supported: not/supported") + } + + t.Log("No Request Body") + { + request := http.Request{ + Header: http.Header{ + "Content-Type": {"application/json"}, + }, + } + hookTransformResult := provider.TransformRequest(&request) + require.False(t, hookTransformResult.ShouldSkip) + require.EqualError(t, hookTransformResult.Error, "Failed to read content of request body: no or empty request body") + } + + t.Log("Test with Sample Code Push data") + { + request := http.Request{ + Header: http.Header{ + "Content-Type": {"application/json"}, + }, + Body: ioutil.NopCloser(strings.NewReader(sampleCodePushData)), + } + hookTransformResult := provider.TransformRequest(&request) + require.NoError(t, hookTransformResult.Error) + require.False(t, hookTransformResult.ShouldSkip) + require.Equal(t, []bitriseapi.TriggerAPIParamsModel{ + { + BuildParams: bitriseapi.BuildParamsModel{ + CommitMessage: "ErikPoort pushed 1 commits [branchname]\n", + Branch: "branchname", + CommitHash: "sha1chars11", + }, + TriggeredBy: "webhook", + }, + }, hookTransformResult.TriggerAPIParams) + require.Equal(t, false, hookTransformResult.DontWaitForTriggerResponse) + } +} + +func Test_IncorrectJSONData(t *testing.T) { + provider := HookProvider{} + + t.Log("Test with incorrect JSON data") + { + request := http.Request{ + Header: http.Header{ + "Content-Type": {"application/json"}, + }, + Body: ioutil.NopCloser(strings.NewReader(sampleIncorrectJSONData)), + } + hookTransformResult := provider.TransformRequest(&request) + require.Error(t, hookTransformResult.Error) + } +} \ No newline at end of file diff --git a/service/hook/endpoint.go b/service/hook/endpoint.go index 19f8b9dd..bc8acad0 100644 --- a/service/hook/endpoint.go +++ b/service/hook/endpoint.go @@ -18,6 +18,7 @@ import ( "github.com/bitrise-io/bitrise-webhooks/service/hook/gogs" "github.com/bitrise-io/bitrise-webhooks/service/hook/slack" "github.com/bitrise-io/bitrise-webhooks/service/hook/visualstudioteamservices" + "github.com/bitrise-io/bitrise-webhooks/service/hook/assembla" "github.com/gorilla/mux" ) @@ -30,6 +31,7 @@ func supportedProviders() map[string]hookCommon.Provider { "gitlab": gitlab.HookProvider{}, "gogs": gogs.HookProvider{}, "deveo": deveo.HookProvider{}, + "assembla": assembla.HookProvider{}, } } From 14aacab8ca5a6b86806172561bfdf5d1e77b62b5 Mon Sep 17 00:00:00 2001 From: Viktor Benei Date: Wed, 6 Sep 2017 17:52:52 +0200 Subject: [PATCH 2/3] assembla.go : go fmt / format normalization --- service/hook/assembla/assembla.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/service/hook/assembla/assembla.go b/service/hook/assembla/assembla.go index 61d123a6..7d1ed2bd 100644 --- a/service/hook/assembla/assembla.go +++ b/service/hook/assembla/assembla.go @@ -19,31 +19,31 @@ import ( // SpaceEventModel ... type SpaceEventModel struct { - Space string `json:"space"` - Action string `json:"action"` - Object string `json:"object"` + Space string `json:"space"` + Action string `json:"action"` + Object string `json:"object"` } // MessageEventModel ... type MessageEventModel struct { - Title string `json:"title"` - Body string `json:"body"` - Author string `json:"author"` + Title string `json:"title"` + Body string `json:"body"` + Author string `json:"author"` } // GitEventModel ... type GitEventModel struct { - RepositorySuffix string `json:"repository_suffix"` - RepositoryURL string `json:"repository_url"` - Branch string `json:"branch"` - CommitID string `json:"commit_id"` + RepositorySuffix string `json:"repository_suffix"` + RepositoryURL string `json:"repository_url"` + Branch string `json:"branch"` + CommitID string `json:"commit_id"` } // PushEventModel ... type PushEventModel struct { - SpaceEventModel SpaceEventModel `json:"assembla"` - MessageEventModel MessageEventModel `json:"message"` - GitEventModel GitEventModel `json:"git"` + SpaceEventModel SpaceEventModel `json:"assembla"` + MessageEventModel MessageEventModel `json:"message"` + GitEventModel GitEventModel `json:"git"` } // --------------------------------------- @@ -65,14 +65,14 @@ func detectAssemblaData(pushEvent PushEventModel) error { if (pushEvent.GitEventModel.CommitID == "") || (pushEvent.GitEventModel.Branch == "") || (pushEvent.GitEventModel.RepositoryURL == "") || - (pushEvent.GitEventModel.RepositorySuffix == "") { + (pushEvent.GitEventModel.RepositorySuffix == "") { return errors.New("Webhook is not correctly setup, make sure you post updates about 'Code commits' in Assembla") } if (pushEvent.GitEventModel.CommitID == "---") || (pushEvent.GitEventModel.Branch == "---") || (pushEvent.GitEventModel.RepositoryURL == "---") || - (pushEvent.GitEventModel.RepositorySuffix == "---") { + (pushEvent.GitEventModel.RepositorySuffix == "---") { return errors.New("Webhook is not correctly setup, make sure you post updates about 'Code commits' in Assembla") } From 2c888975735140af52a40da04b54dc9cbae19830 Mon Sep 17 00:00:00 2001 From: Viktor Benei Date: Wed, 6 Sep 2017 17:59:15 +0200 Subject: [PATCH 3/3] v1.1.28 --- version/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version/version.go b/version/version.go index a564dacc..ce8418bc 100644 --- a/version/version.go +++ b/version/version.go @@ -1,4 +1,4 @@ package version // VERSION ... -const VERSION = "1.1.27" +const VERSION = "1.1.28"