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

re-enable install validation, refactor configuration and profiles modules #948

Merged
merged 24 commits into from
Jul 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f86b34d
refactor(configuration): add ConfigProvider
ctrombley Jul 1, 2021
73f7f12
refactor(configuration): add configuration providers for CLI config a…
ctrombley Jul 1, 2021
45b5800
refactor: replace client and profile instantiation helpers
ctrombley Jul 2, 2021
e2e5e84
refactor(profiles): add an interactive profile creation command
ctrombley Jul 3, 2021
f95bf9a
refactor: update config command to use new config stores
ctrombley Jul 6, 2021
555a59b
refactor: fix tests and delete unused code
ctrombley Jul 7, 2021
e6758a9
refactor: fix tests
ctrombley Jul 7, 2021
6a5e168
refactor: add client requirements to most commands
ctrombley Jul 7, 2021
4cc4a0b
refactor: fix linting issues
ctrombley Jul 7, 2021
56aee0c
refactor: fix back-compat compilation issues
ctrombley Jul 7, 2021
ef23da1
refactor: add top-level flags for log level, profile name, and accoun…
ctrombley Jul 8, 2021
097d63f
refactor: default region to US
ctrombley Jul 8, 2021
d3ba4b5
refactor: add override capable getters to config provider
ctrombley Jul 8, 2021
f89d763
refactor: fix tests
ctrombley Jul 9, 2021
f8fe97d
refactor: incorporate review feedback
ctrombley Jul 9, 2021
48c5b83
refactor: use fetched license key and insights insert key during inst…
ctrombley Jul 9, 2021
af055fa
refactor: incorporate review feedback
ctrombley Jul 9, 2021
cfd5c26
refactor(install): merge branch 'main' into refactor/configuration
Julien4218 Jul 9, 2021
850a7e1
refactor: incorporate code review feedback
ctrombley Jul 9, 2021
35c0f42
chore: Merge branch 'refactor/configuration' of github.com:newrelic/n…
ctrombley Jul 9, 2021
3ae73bd
refactor: add docstring
ctrombley Jul 10, 2021
73d71f1
refactor: add SetValueFunc and case fold region values
ctrombley Jul 12, 2021
bced43c
docs(profile): fix command flag names
ctrombley Jul 12, 2021
3fc117e
fix(install): do not use deprecated api to fetch license key
ctrombley Jul 12, 2021
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
222 changes: 20 additions & 202 deletions cmd/newrelic/command.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,23 @@
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/config"
configAPI "github.com/newrelic/newrelic-cli/internal/config/api"
"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 +30,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 := configAPI.GetLogLevel()
config.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(configAPI.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 +67,11 @@ func init() {
cobra.OnInitialize(initConfig)

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

func initConfig() {
Expand Down
Loading