Skip to content

Commit

Permalink
Merge pull request #1 from joaodaher/feature/new-relic-hook
Browse files Browse the repository at this point in the history
NewRelic deployment hook
  • Loading branch information
lucasgomide committed Sep 27, 2017
2 parents 968bad5 + 77a92b6 commit 2527320
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 1 deletion.
1 change: 1 addition & 0 deletions .travis.yml
@@ -1,6 +1,7 @@
language: go
install: true
go:
- 1.9.x
- 1.8.x
- 1.7.x
- 1.6.x
Expand Down
8 changes: 7 additions & 1 deletion README.md
Expand Up @@ -51,7 +51,7 @@ Here is all avaliables hook's configurations and your descriptions. Remember tha
- **webhook_url** Indicates the Webhook URL to dispatch messages to Slack.

- Sentry
- **host** Tell to snith your sentry host (e.g http://sentry.io or http://sentry.self.hosted)
- **host** Tell to Snitch your sentry host (e.g http://sentry.io or http://sentry.self.hosted)
- **organization_slug** The organization slug is a unique ID used to identify your organization. (You'll find it at your sentry's configuration, probably)
- **project_slug** The Project Slug is a unique ID used to identify your project (You'll find it at your project config)
- **auth_token** The Auth Token to use the Sentry Web API. You can find more [here](https://docs.sentry.io/api/auth/#auth-tokens)
Expand All @@ -61,6 +61,12 @@ Here is all avaliables hook's configurations and your descriptions. Remember tha
- **access_token** The access token with `post_server_item` scope. You can find more [here](https://rollbar.com/docs/api/#authentication)
- **env** The application's environment variable (e.g development, production)

- NewRelic
- **host** Tell to Snitch your NewRelic API host (e.g https://api.newrelic.com)
- **application_id** The application ID is a unique ID used to identify your application in APM. (You'll find it at the end of the application's page URL)
- **api_key** The API Key to use the NewRelic REST API. You can find more [here](https://docs.newrelic.com/docs/apis/rest-api-v2/getting-started/api-keys)
- **revision** The application's current revision (e.g 0.0.1r42)

## Example

[Snitch App Sample](https://github.com/lucasgomide/snitch-app-example)
2 changes: 2 additions & 0 deletions hook/hook.go
Expand Up @@ -26,6 +26,8 @@ func Execute(h types.Hook, t types.Tsuru) {
h = &Sentry{}
case "Rollbar":
h = &Rollbar{}
case "Newrelic":
h = &NewRelic{}
default:
continue
}
Expand Down
11 changes: 11 additions & 0 deletions hook/hook_test.go
Expand Up @@ -90,6 +90,17 @@ func TestShouldExecuteHooksFromConfig(t *testing.T) {
httpmock.RegisterResponder("POST", "http://dummy.sample",
httpmock.NewStringResponder(200, `ok`))

httpmock.RegisterResponder("POST", "https://api.rollbar.com/api/1/deploy/",
httpmock.NewStringResponder(200, `ok`))

httpmock.RegisterResponder("POST", "http://sentry.com/api/0/projects/the-answer/for-everything/releases/",
httpmock.NewStringResponder(201, `ok`))
httpmock.RegisterResponder("POST", "http://sentry.com/api/0/organizations/the-answer/releases/for-everything-v15/deploys/",
httpmock.NewStringResponder(201, `ok`))

httpmock.RegisterResponder("POST", "https://api.newrelic.com/v2/applications/01234/deployments.json",
httpmock.NewStringResponder(201, `ok`))

err = config.ReadConfigFile(configFilePath)
if err != nil {
t.Error(err)
Expand Down
74 changes: 74 additions & 0 deletions hook/newrelic.go
@@ -0,0 +1,74 @@
package hook

import (
"bytes"
"errors"
"github.com/lucasgomide/snitch/types"
"net/http"
"time"
)

type NewRelic struct {
Host string
ApplicationId string
ApiKey string
Revision string
}

func (s NewRelic) CallHook(deploys []types.Deploy) error {
httpClient := &http.Client{
Timeout: time.Second * 10,
}
if err := s.createDeploy(httpClient, deploys[0]); err != nil {
return err
}
return nil
}

func (s *NewRelic) createDeploy(httpClient *http.Client, deploy types.Deploy) error {
data := []byte(`
{
"deployment": {
"revision": "` + s.Revision + `",
"changelog": "",
"description": "",
"user": "` + deploy.User + `"
}
}`)

url := s.Host+"/v2/applications/"+s.ApplicationId+"/deployments.json"
req, err := http.NewRequest("POST", url, bytes.NewReader(data))
if err != nil {
return err
}

req.Header.Add("X-Api-Key", s.ApiKey)
req.Header.Add("Content-Type", "application/json")

resp, err := httpClient.Do(req)

if err != nil {
return err
}
if resp.StatusCode != 201 {
return errors.New("NewRelic::CreateDeploy - response status code isn't 201")
}
return nil
}


func (s NewRelic) ValidatesFields() error {
if s.Host == "" {
return errors.New("Field host into NewRelic hook is required")
}
if s.ApplicationId == "" {
return errors.New("Field application_id into NewRelic hook is required")
}
if s.ApiKey == "" {
return errors.New("Field api_key into NewRelic hook is required")
}
if s.Revision == "" {
return errors.New("Field revision into NewRelic hook is required")
}
return nil
}
85 changes: 85 additions & 0 deletions hook/newrelic_test.go
@@ -0,0 +1,85 @@
package hook

import (
"github.com/lucasgomide/snitch/types"
"gopkg.in/jarcoal/httpmock.v1"
"testing"
)

var newrelic_host = "https://api.newrelic.com"


func TestNewRelicDeploySuccessful(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()

var deploys []types.Deploy
deploys = append(deploys, types.Deploy{"app-sample", "12345678909", "sha1", "user@g.com", "v15"})

s := NewRelic{newrelic_host, "app-id-here", "api-key-here", "revision-here"}

httpmock.RegisterResponder("POST", s.Host+"/v2/applications/"+s.ApplicationId+"/deployments.json",
httpmock.NewStringResponder(201, `ok`))


if err := s.CallHook(deploys); err != nil {
t.Error(err)
}
}


func TestNewRelicReturnsErrorWhenCreateDeployFails(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()

var deploys []types.Deploy
deploys = append(deploys, types.Deploy{"app-sample", "12345678909", "sha1", "user@g.com", "v15"})

s := NewRelic{newrelic_host, "app-id-here", "api-key-here", "revision-here"}

httpmock.RegisterResponder("POST", s.Host+"/v2/applications/"+s.ApplicationId+"/deployments.json",
httpmock.NewStringResponder(502, `error`))

if err := s.CallHook(deploys); err == nil {
t.Error("Expected returns error, got no error")
} else if err.Error() != "NewRelic::CreateDeploy - response status code isn't 201" {
t.Error(err)
}
}


func TestNewRelicValidateFields(t *testing.T) {
s := NewRelic{}

if err = s.ValidatesFields(); err == nil {
t.Error("Expected returns error, got nil error")
} else if err.Error() != "Field host into NewRelic hook is required" {
t.Error("Expected error Field host into NewRelic hook is required, got", err.Error())
}
s.Host = "http://abc"

if err = s.ValidatesFields(); err == nil {
t.Error("Expected returns error, got nil error")
} else if err.Error() != "Field application_id into NewRelic hook is required" {
t.Error("Expected error Field application_id into NewRelic hook is required, got", err.Error())
}
s.ApplicationId = "app-id-here"

if err = s.ValidatesFields(); err == nil {
t.Error("Expected returns error, got nil error")
} else if err.Error() != "Field api_key into NewRelic hook is required" {
t.Error("Expected error Field api_key into NewRelic hook is required, got", err.Error())
}
s.ApiKey = "api-key-here"

if err = s.ValidatesFields(); err == nil {
t.Error("Expected returns error, got nil error")
} else if err.Error() != "Field revision into NewRelic hook is required" {
t.Error("Expected error Field revision into NewRelic hook is required, got", err.Error())
}
s.Revision = "revision-here"

if err = s.ValidatesFields(); err != nil {
t.Error("Expected returns no error, got", err.Error())
}
}
15 changes: 15 additions & 0 deletions testdata/config.yaml
Expand Up @@ -2,3 +2,18 @@ slack:
webhook_url: http://dummy.sample
missing_hook:
field: value
rollbar:
access_token: abc123
env: test
sentry:
host: http://sentry.com
organization_slug: the-answer
project_slug: for-everything
auth_token: abc123
env: test
release_version: 0.0.1
newrelic:
host: https://api.newrelic.com
application_id: "01234"
api_key: 0a0b11223344
revision: 0.0.1

0 comments on commit 2527320

Please sign in to comment.