Skip to content

Commit

Permalink
Merge pull request #845 from newrelic/feat/validate-config
Browse files Browse the repository at this point in the history
(feat): validate CLI config
  • Loading branch information
ctrombley authored May 13, 2021
2 parents 42b30a0 + a4f727f commit b1e82c9
Show file tree
Hide file tree
Showing 17 changed files with 364 additions and 149 deletions.
40 changes: 36 additions & 4 deletions cmd/newrelic/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func initializeProfile() {
var accountID int
var region string
var licenseKey string
var insightsInsertKey string
var err error

credentials.WithCredentials(func(c *credentials.Credentials) {
Expand All @@ -56,6 +57,7 @@ func initializeProfile() {
envAccountID := os.Getenv("NEW_RELIC_ACCOUNT_ID")
region = os.Getenv("NEW_RELIC_REGION")
licenseKey = os.Getenv("NEW_RELIC_LICENSE_KEY")
insightsInsertKey = os.Getenv("NEW_RELIC_INSIGHTS_INSERT_KEY")

// If we don't have a personal API key we can't initialize a profile.
if apiKey == "" {
Expand Down Expand Up @@ -96,12 +98,21 @@ func initializeProfile() {
}
}

if insightsInsertKey == "" {
// We should have an API key by now, so fetch the insights insert key for it.
insightsInsertKey, err = fetchInsightsInsertKey(nrClient, accountID)
if err != nil {
log.Error(err)
}
}

if !hasProfileWithDefaultName(c.Profiles) {
p := credentials.Profile{
Region: region,
APIKey: apiKey,
AccountID: accountID,
LicenseKey: licenseKey,
Region: region,
APIKey: apiKey,
AccountID: accountID,
LicenseKey: licenseKey,
InsightsInsertKey: insightsInsertKey,
}

err = c.AddProfile(defaultProfileName, p)
Expand Down Expand Up @@ -166,6 +177,27 @@ func fetchLicenseKey(client *newrelic.NewRelic, accountID int) (string, error) {
return "", types.ErrorFetchingLicenseKey
}

func fetchInsightsInsertKey(client *newrelic.NewRelic, accountID int) (string, error) {
// Check for an existing key first
keys, err := client.APIAccess.ListInsightsInsertKeys(accountID)
if err != nil {
return "", types.ErrorFetchingInsightsInsertKey
}

// We already have a key, return it
if len(keys) > 0 {
return keys[0].Key, nil
}

// Create a new key if one doesn't exist
key, err := client.APIAccess.CreateInsightsInsertKey(accountID)
if err != nil {
return "", types.ErrorFetchingInsightsInsertKey
}

return key.Key, nil
}

// fetchAccountID will try and retrieve an account ID for the given user. If it
// finds more than one account it will returrn an error.
func fetchAccountID(client *newrelic.NewRelic) (int, error) {
Expand Down
2 changes: 2 additions & 0 deletions cmd/newrelic/command_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ func TestInitializeProfile(t *testing.T) {
assert.Equal(t, apiKey, c.Profiles[defaultProfileName].APIKey)
assert.NotEmpty(t, c.Profiles[defaultProfileName].Region)
assert.NotEmpty(t, c.Profiles[defaultProfileName].AccountID)
assert.NotEmpty(t, c.Profiles[defaultProfileName].LicenseKey)
assert.NotEmpty(t, c.Profiles[defaultProfileName].InsightsInsertKey)

// Ensure that we don't Fatal out if the default profile already exists, but
// was not specified in the default-profile.json.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ require (
github.com/llorllale/go-gitlint v0.0.0-20200802191503-5984945d4b80
github.com/mitchellh/go-homedir v1.1.0
github.com/mitchellh/mapstructure v1.4.1
github.com/newrelic/newrelic-client-go v0.58.5
github.com/newrelic/newrelic-client-go v0.59.0
github.com/newrelic/tutone v0.6.1
github.com/pkg/errors v0.9.1
github.com/psampaz/go-mod-outdated v0.8.0
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -527,8 +527,8 @@ github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iP
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-retryablehttp v0.6.8 h1:92lWxgpa+fF3FozM4B3UZtHZMJX8T5XT+TFdCxsPyWs=
github.com/hashicorp/go-retryablehttp v0.6.8/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4=
github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
Expand Down Expand Up @@ -760,8 +760,8 @@ github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5w
github.com/nbutton23/zxcvbn-go v0.0.0-20201221231540-e56b841a3c88/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8=
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA=
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8=
github.com/newrelic/newrelic-client-go v0.58.5 h1:Bp9vzKjc/9nQXvO1q/cdRW6YppGhXAHh8GZrlevIrVM=
github.com/newrelic/newrelic-client-go v0.58.5/go.mod h1:+wOK+2m1ClVuAqnpMAW7v924ryKW1eYylvkPJR0b3PA=
github.com/newrelic/newrelic-client-go v0.59.0 h1:SQsZmVdE0elfioi3EoMCv0sNyKsZ4QnYOeoUfVzzkzA=
github.com/newrelic/newrelic-client-go v0.59.0/go.mod h1:CIVFEfoZomM9/V1eowdwCv9TG6Eid5X587leQVIwCKo=
github.com/newrelic/tutone v0.6.1 h1:hO3gumrOvTeIQjHHEB8J9oGloC0GHxbIAXU1FazSe6Q=
github.com/newrelic/tutone v0.6.1/go.mod h1:dOSVZu/5kTucS3dxLIf6S5Ra3FPzBcT9NDBm4kfDGx0=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
Expand Down
43 changes: 43 additions & 0 deletions internal/diagnose/command_validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package diagnose

import (
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"

"github.com/newrelic/newrelic-cli/internal/client"
"github.com/newrelic/newrelic-cli/internal/utils"
"github.com/newrelic/newrelic-client-go/newrelic"
)

var cmdValidate = &cobra.Command{
Use: "validate",
Short: "Validate your CLI configuration and connectivity",
Long: `Validate your CLI configuration and connectivity.
Checks the configuration in the default or specified configuation profile by sending
data to the New Relic platform and verifying that it has been received.`,
Example: "\tnewrelic diagnose validate",
Run: func(cmd *cobra.Command, args []string) {
client.WithClient(func(nrClient *newrelic.NewRelic) {
v := NewConfigValidator(nrClient)
err := v.ValidateConfig(utils.SignalCtx)
if err != nil {
if err == ErrDiscovery {
log.Fatal("Failed to detect your system's hostname. Please contact New Relic support.")
}
if err == ErrPostEvent {
log.Fatal("There was a failure posting data to New Relic.")
}
if err == ErrValidation {
log.Fatal("There was a failure locating the data that was posted to New Relic.")
}

log.Fatal(err)
}
})
},
}

func init() {
Command.AddCommand(cmdValidate)
}
87 changes: 87 additions & 0 deletions internal/diagnose/config_validator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package diagnose

import (
"context"
"errors"
"fmt"
"reflect"

"github.com/google/uuid"
log "github.com/sirupsen/logrus"

"github.com/shirou/gopsutil/host"

"github.com/newrelic/newrelic-cli/internal/credentials"
"github.com/newrelic/newrelic-cli/internal/utils/validation"
"github.com/newrelic/newrelic-client-go/newrelic"
)

const (
validationEventType = "NrIntegrationError"
)

type ConfigValidator struct {
client *newrelic.NewRelic
*validation.PollingNRQLValidator
}

type ValidationTracerEvent struct {
EventType string `json:"eventType"`
Hostname string `json:"hostname"`
Purpose string `json:"purpose"`
GUID string `json:"guid"`
}

func NewConfigValidator(client *newrelic.NewRelic) *ConfigValidator {
v := validation.NewPollingNRQLValidator(&client.Nrdb)
v.MaxAttempts = 20

return &ConfigValidator{
client: client,
PollingNRQLValidator: v,
}
}

func (c *ConfigValidator) ValidateConfig(ctx context.Context) error {
defaultProfile := credentials.DefaultProfile()

i, err := host.InfoWithContext(ctx)
if err != nil {
log.Error(err)
return ErrDiscovery
}

evt := ValidationTracerEvent{
EventType: validationEventType,
Hostname: i.Hostname,
Purpose: "New Relic CLI configuration validation",
GUID: uuid.NewString(),
}

log.Printf("Sending tracer event to New Relic.")

if err = c.client.Events.CreateEvent(defaultProfile.AccountID, evt); err != nil {
log.Error(reflect.TypeOf(err))
log.Error(err)
return ErrPostEvent
}

query := fmt.Sprintf(`
FROM %s
SELECT count(*)
WHERE hostname LIKE '%s%%'
AND guid = '%s'
SINCE 10 MINUTES AGO
`, evt.EventType, evt.Hostname, evt.GUID)

if _, err = c.Validate(ctx, query); err != nil {
log.Error(err)
err = ErrValidation
}

return err
}

var ErrDiscovery = errors.New("discovery failed")
var ErrPostEvent = errors.New("posting an event failed")
var ErrValidation = errors.New("validation failed")
17 changes: 17 additions & 0 deletions internal/install/discovery/noop_process_filterer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package discovery

import (
"context"

"github.com/newrelic/newrelic-cli/internal/install/types"
)

type NoOpProcessFilterer struct{}

func NewNoOpProcessFilterer() *NoOpProcessFilterer {
return &NoOpProcessFilterer{}
}

func (f *NoOpProcessFilterer) filter(ctx context.Context, processes []types.GenericProcess, manifest types.DiscoveryManifest) ([]types.MatchedProcess, error) {
return []types.MatchedProcess{}, nil
}
3 changes: 2 additions & 1 deletion internal/install/execution/go_task_recipe_executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ import (
"gopkg.in/yaml.v2"

"github.com/go-task/task/v3/taskfile"
"github.com/stretchr/testify/require"

"github.com/newrelic/newrelic-cli/internal/credentials"
"github.com/newrelic/newrelic-cli/internal/install/types"
"github.com/stretchr/testify/require"
)

func TestExecute_SystemVariableInterpolation(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion internal/install/recipe_installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ func (i *RecipeInstaller) executeAndValidate(ctx context.Context, m *types.Disco
var validationDurationMilliseconds int64
start := time.Now()
if r.ValidationNRQL != "" {
entityGUID, err = i.recipeValidator.Validate(ctx, *m, *r)
entityGUID, err = i.recipeValidator.ValidateRecipe(ctx, *m, *r)
if err != nil {
validationDurationMilliseconds = time.Since(start).Milliseconds()
msg := fmt.Sprintf("encountered an error while validating receipt of data for %s: %s", r.Name, err)
Expand Down
3 changes: 2 additions & 1 deletion internal/install/recipes/recipe_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import (
"net/http"
"net/url"

"github.com/newrelic/newrelic-cli/internal/install/types"
"gopkg.in/yaml.v2"

"github.com/newrelic/newrelic-cli/internal/install/types"
)

type RecipeFileFetcherImpl struct {
Expand Down
1 change: 1 addition & 0 deletions internal/install/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ var ErrInterrupt = errors.New("operation canceled")

// nolint: golint
var ErrorFetchingLicenseKey = errors.New("Oops, we're having some difficulties fetching your license key. Please try again later, or see our documentation for installing manually https://docs.newrelic.com/docs/using-new-relic/cross-product-functions/install-configure/install-new-relic")
var ErrorFetchingInsightsInsertKey = errors.New("error retrieving Insights insert key")
2 changes: 1 addition & 1 deletion internal/install/validation/mock_recipe_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func NewMockRecipeValidator() *MockRecipeValidator {
return &MockRecipeValidator{}
}

func (m *MockRecipeValidator) Validate(ctx context.Context, dm types.DiscoveryManifest, r types.OpenInstallationRecipe) (string, error) {
func (m *MockRecipeValidator) ValidateRecipe(ctx context.Context, dm types.DiscoveryManifest, r types.OpenInstallationRecipe) (string, error) {
m.ValidateCallCount++

var err error
Expand Down
Loading

0 comments on commit b1e82c9

Please sign in to comment.