diff --git a/context/context.go b/context/context.go index ea72551..37f0c40 100644 --- a/context/context.go +++ b/context/context.go @@ -85,7 +85,6 @@ func NewContext(cfg *utils.Config, manifest *utils.Manifest) (*Context, error) { newRelic, err := hooks.NewNewRelicClient( cfg.HookConfig.NewRelicConfig, manifest.HookManifest.NewRelicManifest) - if err != nil { return nil, err } diff --git a/examples/manifest.sample.yml b/examples/manifest.sample.yml index 19dcbbf..bb3aee7 100644 --- a/examples/manifest.sample.yml +++ b/examples/manifest.sample.yml @@ -19,7 +19,7 @@ hooks: slack: channel: channel newRelic: - applicationId: "123" + application: "Webhooks - {{.env}}" execCommand: onPredeploy: - { command: "ls", args: ["-lah"] } diff --git a/hooks/newrelic.go b/hooks/newrelic.go index 8f1dce0..f38bbbb 100644 --- a/hooks/newrelic.go +++ b/hooks/newrelic.go @@ -5,17 +5,23 @@ import ( "encoding/json" "errors" "fmt" + "html/template" + "io" + "io/ioutil" "net/http" + "strings" "github.com/remyLemeunier/contactkey/utils" log "github.com/sirupsen/logrus" ) type NewRelicClient struct { - Url string - ApiKey string - ApplicationId string - Stop bool + HttpClient *http.Client + Url string + ApiKey string + ApplicationFilter string + ApplicationId int + Stop bool } type NewRelicDeployment struct { @@ -25,10 +31,33 @@ type NewRelicDeployment struct { user string } +type NewRelicApplicationList struct { + Applications []struct { + Id int `json:"id"` + Name string `json:"Name"` + } `json:"applications"` +} + func (c NewRelicClient) PreDeployment(userName string, env string, service string, podVersion string) error { + var filter bytes.Buffer + filterTmpl, err := template.New("filter").Parse(c.ApplicationFilter) + if err != nil { + return err + } + + if err := filterTmpl.Execute(&filter, struct{ env string }{env}); err != nil { + } + + appId, err := c.findApplicationId(filter.String()) + if err != nil { + return err + } + c.ApplicationId = appId + description := fmt.Sprintf("Deploying %s %s on %s", service, podVersion, env) d := &NewRelicDeployment{ description: description, + revision: podVersion, user: userName, } return c.CreateDeployment(d) @@ -51,47 +80,99 @@ func NewNewRelicClient(cfg utils.NewRelicConfig, manifest utils.NewRelicManifest return nil, errors.New("You need to define an apiKey for newrelic in the config.") } - if manifest.ApplicationId == "" { + if manifest.ApplicationFilter == "" { return nil, errors.New("You need to define an applicationId for newrelic in the manifest.") } - return &NewRelicClient{ - Url: cfg.Url, - ApiKey: cfg.ApiKey, - ApplicationId: manifest.ApplicationId, - Stop: manifest.StopOnError, - }, nil + c := &NewRelicClient{ + HttpClient: &http.Client{}, + Url: cfg.Url, + ApiKey: cfg.ApiKey, + Stop: manifest.StopOnError, + ApplicationFilter: manifest.ApplicationFilter, + } + + return c, nil } -// https://rpm.newrelic.com/api/explore/application_deployments/create -func (c NewRelicClient) CreateDeployment(d *NewRelicDeployment) error { - client := &http.Client{} - url := fmt.Sprintf("%s/v2/applications/%s/deployments.json", - c.Url, - c.ApplicationId, - ) +func (c NewRelicClient) findApplicationId(nameFilter string) (int, error) { + var applications NewRelicApplicationList + + filter := strings.NewReader(fmt.Sprintf("filter[name]=%s", nameFilter)) + request, err := c.NewRequest("GET", "v2/applications.json", filter) + + if err != nil { + return 0, err + } + + response, err := c.HttpClient.Do(request) + if err != nil { + return 0, err + } + if response.StatusCode != http.StatusOK { + return 0, errors.New("HTTP error from NewRelic") + } + + defer response.Body.Close() + bodyJson, err := ioutil.ReadAll(response.Body) + if err != nil { + return 0, err + } + err = json.Unmarshal(bodyJson, &applications) + if err != nil { + return 0, err + } log.WithFields(log.Fields{ - "url": url, - }).Debug("Creating NewRelic deployment.") + "statusCode": response.StatusCode, + }).Debug("NewRelic response") - body := &bytes.Buffer{} - if err := json.NewEncoder(body).Encode(d); err != nil { - return err + if len(applications.Applications) == 0 { + return 0, fmt.Errorf("application %s not found", nameFilter) + } + + return applications.Applications[0].Id, nil +} + +func (c NewRelicClient) NewRequest(method string, route string, body io.Reader) (*http.Request, error) { + url := fmt.Sprintf("%s/%s", c.Url, route) + + request, err := http.NewRequest(method, url, body) + if err != nil { + return nil, err } - request, err := http.NewRequest("POST", url, nil) request.Header.Add("X-Api-Key", c.ApiKey) request.Header.Set("Content-Type", "application/json") request.Header.Set("Accept", "application/json") - response, err := client.Do(request) + log.WithFields(log.Fields{ + "url": request.URL, + "method": request.Method, + }).Debug("NewRelic request") + return request, nil +} + +// https://rpm.newrelic.com/api/explore/application_deployments/create +func (c NewRelicClient) CreateDeployment(d *NewRelicDeployment) error { + body := &bytes.Buffer{} + if err := json.NewEncoder(body).Encode(d); err != nil { + return err + } + + request, err := c.NewRequest("POST", fmt.Sprintf("v2/applications/%d/deployments.json", c.ApplicationId), body) + if err != nil { + return err + } + + response, err := c.HttpClient.Do(request) if err != nil { return err } + log.WithFields(log.Fields{ "statusCode": response.StatusCode, - }).Debug("NewRelic response status code.") + }).Debug("NewRelic response") if response.StatusCode != http.StatusCreated { return errors.New(fmt.Sprintf("NewRelic status code: %d", response.StatusCode)) diff --git a/hooks/newrelic_test.go b/hooks/newrelic_test.go index 20cde9e..d04e55f 100644 --- a/hooks/newrelic_test.go +++ b/hooks/newrelic_test.go @@ -1,32 +1,55 @@ package hooks import ( + "io/ioutil" "net/http" "net/http/httptest" "testing" ) +var GetApplications = []byte(`{ + "applications": [ + { + "id": 456, + "name": "Webhooks" + } + ] +}`) + +var GetApplicationsEmpty = []byte(`{"applications": []}`) + var apiStub = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.RequestURI { - case "/v2/applications/456/deployments.json": - if r.Header.Get("X-Api-Key") != "123" { - w.WriteHeader(http.StatusUnauthorized) - } else { + body, _ := ioutil.ReadAll(r.Body) + + if r.Header.Get("X-Api-Key") != "123" { + w.WriteHeader(http.StatusUnauthorized) + } else { + switch r.RequestURI { + case "/v2/applications.json": + w.WriteHeader(http.StatusOK) + if string(body) == "filter[name]=webhook" { + w.Write(GetApplications) + } else { + w.Write(GetApplicationsEmpty) + } + + case "/v2/applications/456/deployments.json": w.WriteHeader(http.StatusCreated) + + default: + w.WriteHeader(http.StatusBadRequest) } - default: - w.WriteHeader(http.StatusBadRequest) } })) func TestCreateDeployment(t *testing.T) { c := &NewRelicClient{ + HttpClient: &http.Client{}, Url: apiStub.URL, + ApplicationId: 456, ApiKey: "123", - ApplicationId: "456", Stop: false, } - //c.Log.SetLevel(log.DebugLevel) d := &NewRelicDeployment{} err := c.CreateDeployment(d) @@ -34,3 +57,21 @@ func TestCreateDeployment(t *testing.T) { t.Errorf("Unexpected err : %q", err) } } + +func TestFindApplicationId(t *testing.T) { + c := &NewRelicClient{ + HttpClient: &http.Client{}, + Url: apiStub.URL, + ApiKey: "123", + Stop: false, + } + // log.SetLevel(log.DebugLevel) + + appId, err := c.findApplicationId("webhook") + if err != nil { + t.Errorf("Unexpected err : %q", err) + } + if appId != 456 { + t.Errorf("Unexpected appId : %q", appId) + } +} diff --git a/utils/manifest.go b/utils/manifest.go index 8524a0a..065f55e 100644 --- a/utils/manifest.go +++ b/utils/manifest.go @@ -58,8 +58,8 @@ type SlackManifest struct { } type NewRelicManifest struct { - ApplicationId string `mapstructure:"applicationId"` - StopOnError bool `mapstructure:"stopOnError"` + ApplicationFilter string `mapstructure:"applicationFilter"` + StopOnError bool `mapstructure:"stopOnError"` } type ExecCommandManifest struct { diff --git a/utils/testdata/manifest.yaml b/utils/testdata/manifest.yaml index 8e34288..85a59d5 100644 --- a/utils/testdata/manifest.yaml +++ b/utils/testdata/manifest.yaml @@ -16,7 +16,7 @@ hooks: slack: channel: channel newRelic: - applicationId: "456" + applicationFilter: "Webhooks - {{.env}}" execCommand: onPredeploy: - { command: "ls", args: ["-lah"] }