Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(install): [WIP] start of localhost validation for infra agent #950

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
221 changes: 19 additions & 202 deletions cmd/newrelic/command.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,22 @@
package main

import (
"context"
"errors"
"fmt"
"os"
"strconv"

"github.com/jedib0t/go-pretty/v6/text"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"

"github.com/newrelic/newrelic-cli/internal/client"
"github.com/newrelic/newrelic-cli/internal/credentials"
"github.com/newrelic/newrelic-cli/internal/install/types"
"github.com/newrelic/newrelic-cli/internal/configuration"
"github.com/newrelic/newrelic-cli/internal/output"
"github.com/newrelic/newrelic-cli/internal/utils"
"github.com/newrelic/newrelic-client-go/newrelic"
"github.com/newrelic/newrelic-client-go/pkg/accounts"
"github.com/newrelic/newrelic-client-go/pkg/nerdgraph"
)

var outputFormat string
var outputPlain bool

const defaultProfileName string = "default"
var (
outputFormat string
outputPlain bool
)

// Command represents the base command when called without any subcommands
var Command = &cobra.Command{
Expand All @@ -37,202 +29,23 @@ var Command = &cobra.Command{
}

func initializeCLI(cmd *cobra.Command, args []string) {
initializeProfile(utils.SignalCtx)
}

func initializeProfile(ctx context.Context) {
var accountID int
var region string
var licenseKey string
var insightsInsertKey string
var err error

credentials.WithCredentials(func(c *credentials.Credentials) {
if c.DefaultProfile != "" {
err = errors.New("default profile already exists, not attempting to initialize")
return
}

apiKey := os.Getenv("NEW_RELIC_API_KEY")
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 == "" {
err = errors.New("api key not provided, not attempting to initialize default profile")
return
}

// Default the region to US if it's not in the environment
if region == "" {
region = "US"
}

// Use the accountID from the environment if we have it.
if envAccountID != "" {
accountID, err = strconv.Atoi(envAccountID)
if err != nil {
err = fmt.Errorf("couldn't parse account ID: %s", err)
return
}
}

// We should have an API key by this point, initialize the client.
client.WithClient(func(nrClient *newrelic.NewRelic) {
// If we still don't have an account ID try to look one up from the API.
if accountID == 0 {
accountID, err = fetchAccountID(nrClient)
if err != nil {
return
}
}

if licenseKey == "" {
// We should have an account ID by now, so fetch the license key for it.
licenseKey, err = fetchLicenseKey(ctx, nrClient, accountID)
if err != nil {
log.Error(err)
return
}
}

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,
InsightsInsertKey: insightsInsertKey,
}

err = c.AddProfile(defaultProfileName, p)
if err != nil {
return
}

log.Infof("profile %s added", text.FgCyan.Sprint(defaultProfileName))
}

if len(c.Profiles) == 1 {
err = c.SetDefaultProfile(defaultProfileName)
if err != nil {
err = fmt.Errorf("error setting %s as the default profile: %s", text.FgCyan.Sprint(defaultProfileName), err)
return
}

log.Infof("setting %s as default profile", text.FgCyan.Sprint(defaultProfileName))
}
})
})

if err != nil {
log.Debugf("couldn't initialize default profile: %s", err)
}
}

func hasProfileWithDefaultName(profiles map[string]credentials.Profile) bool {
for profileName := range profiles {
if profileName == defaultProfileName {
return true
}
}

return false
}

func fetchLicenseKey(ctx context.Context, client *newrelic.NewRelic, accountID int) (string, error) {
var licenseKey string
retryFunc := func() error {
l, err := execLicenseKeyRequest(ctx, client, accountID)
if err != nil {
return err
}

licenseKey = l
return nil
}

r := utils.NewRetry(3, 1, retryFunc)
if err := r.ExecWithRetries(ctx); err != nil {
return "", err
}

return licenseKey, nil
}

func execLicenseKeyRequest(ctx context.Context, client *newrelic.NewRelic, accountID int) (string, error) {
query := `query($accountId: Int!) { actor { account(id: $accountId) { licenseKey } } }`

variables := map[string]interface{}{
"accountId": accountID,
}
logLevel := configuration.GetLogLevelWithFlagOverride()
configuration.InitLogger(logLevel)

resp, err := client.NerdGraph.QueryWithContext(ctx, query, variables)
if err != nil {
return "", err
if client.NRClient == nil {
client.NRClient = createClient()
}

queryResp := resp.(nerdgraph.QueryResponse)
actor := queryResp.Actor.(map[string]interface{})
account := actor["account"].(map[string]interface{})

if l, ok := account["licenseKey"]; ok {
if l != nil {
return l.(string), nil
}
}

return "", types.ErrorFetchingLicenseKey
}

func fetchInsightsInsertKey(client *newrelic.NewRelic, accountID int) (string, error) {
// Check for an existing key first
keys, err := client.APIAccess.ListInsightsInsertKeys(accountID)
func createClient() *newrelic.NewRelic {
c, err := client.NewClient(configuration.GetActiveProfileName())
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) {
params := accounts.ListAccountsParams{
Scope: &accounts.RegionScopeTypes.IN_REGION,
}

accounts, err := client.Accounts.ListAccounts(params)
if err != nil {
return 0, err
}

if len(accounts) == 1 {
return accounts[0].ID, nil
// An error was encountered initializing the client. This may not be a
// problem since many commands don't require the use of an initialized client
log.Debugf("error initializing client: %s", err)
}

return 0, errors.New("multiple accounts found, please set NEW_RELIC_ACCOUNT_ID")
return c
}

// Execute adds all child commands to the root command and sets flags appropriately.
Expand All @@ -253,7 +66,11 @@ func init() {
cobra.OnInitialize(initConfig)

Command.PersistentFlags().StringVar(&outputFormat, "format", output.DefaultFormat.String(), "output text format ["+output.FormatOptions()+"]")
Command.PersistentFlags().StringVar(&configuration.FlagProfileName, "profileName", configuration.DefaultProfileName, "the authentication profile to use")
Command.PersistentFlags().BoolVar(&outputPlain, "plain", false, "output compact text")
Command.PersistentFlags().BoolVar(&configuration.FlagDebug, "debug", false, "debug level logging")
Command.PersistentFlags().BoolVar(&configuration.FlagTrace, "trace", false, "trace level logging")
Command.PersistentFlags().IntVarP(&configuration.FlagAccountID, "accountId", "a", 0, "trace level logging")
}

func initConfig() {
Expand Down
Loading