Skip to content

Commit 98fa2be

Browse files
author
Katie West
authored
Merge pull request #171 from newrelic/kwest/lambda-1197
Kwest/lambda 1197
2 parents 7d8ce09 + 7109ee3 commit 98fa2be

File tree

7 files changed

+228
-89
lines changed

7 files changed

+228
-89
lines changed

newrelic_lambda_cli/integrations.py

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,20 @@
77
import click
88
import json
99

10-
from newrelic_lambda_cli.cliutils import failure, success
10+
from newrelic_lambda_cli.cliutils import failure, success, warning
1111
from newrelic_lambda_cli.functions import get_function
1212
from newrelic_lambda_cli.types import (
1313
IntegrationInstall,
1414
IntegrationUninstall,
1515
IntegrationUpdate,
1616
)
17-
from newrelic_lambda_cli.utils import catch_boto_errors
17+
from newrelic_lambda_cli.utils import catch_boto_errors, NR_DOCS_ACT_LINKING_URL
1818

1919
INGEST_STACK_NAME = "NewRelicLogIngestion"
2020
LICENSE_KEY_STACK_NAME = "NewRelicLicenseKeySecret"
2121

2222
__cached_license_key_policy_arn = None
23+
__cached_license_key_nr_account_id = None
2324

2425

2526
def _get_role(session, role_name):
@@ -44,10 +45,27 @@ def _check_for_ingest_stack(session):
4445
return _get_cf_stack_status(session, INGEST_STACK_NAME)
4546

4647

47-
def _get_cf_stack_status(session, stack_name):
48+
def _get_cf_stack_status(session, stack_name, nr_account_id=None):
4849
"""Returns the status of the CloudFormation stack if it exists"""
4950
try:
5051
res = session.client("cloudformation").describe_stacks(StackName=stack_name)
52+
if nr_account_id is not None:
53+
stack_output_account_id = _get_stack_output_value(session, ["NrAccountId"])
54+
# Checking length of outputs here to protect against installs done with older CLI versions.
55+
# We don't want to constantly warn users who installed on previous versions with no outputs.
56+
if (
57+
len(stack_output_account_id) > 0
58+
and str(nr_account_id) not in stack_output_account_id
59+
):
60+
warning(
61+
"WARNING: Managed secret already exists in this region for New Relic account {0}.\n"
62+
"Current CLI behavior limits the setup of one managed secret per region.\n"
63+
"To set up an additional secret for New Relic account {1} see our docs:\n{2}.\n"
64+
"Or run this command with --disable-license-key-secret to avoid attemping to create a new managed secret.".format(
65+
stack_output_account_id, nr_account_id, NR_DOCS_ACT_LINKING_URL
66+
)
67+
)
68+
5169
except botocore.exceptions.ClientError as e:
5270
if (
5371
e.response
@@ -590,7 +608,9 @@ def auto_install_license_key(input):
590608
@catch_boto_errors
591609
def install_license_key(input, nr_license_key, policy_name=None, mode="CREATE"):
592610
assert isinstance(input, (IntegrationInstall, IntegrationUpdate))
593-
lk_stack_status = _get_cf_stack_status(input.session, LICENSE_KEY_STACK_NAME)
611+
lk_stack_status = _get_cf_stack_status(
612+
input.session, LICENSE_KEY_STACK_NAME, input.nr_account_id
613+
)
594614
if lk_stack_status is not None:
595615
success("Managed secret already exists")
596616
return True
@@ -611,8 +631,14 @@ def install_license_key(input, nr_license_key, policy_name=None, mode="CREATE"):
611631
elif update_mode:
612632
parameters.append({"ParameterKey": "PolicyName", "UsePreviousValue": True})
613633

614-
parameters.append(
615-
{"ParameterKey": "LicenseKey", "ParameterValue": nr_license_key}
634+
parameters.extend(
635+
(
636+
{"ParameterKey": "LicenseKey", "ParameterValue": nr_license_key},
637+
{
638+
"ParameterKey": "NrAccountId",
639+
"ParameterValue": str(input.nr_account_id),
640+
},
641+
)
616642
)
617643

618644
change_set_name = "%s-%s-%d" % (
@@ -677,11 +703,24 @@ def remove_license_key(input):
677703
success("Done")
678704

679705

680-
def _get_license_key_policy_arn(session):
681-
"""Returns the policy ARN for the license key secret if it exists"""
706+
def _get_license_key_outputs(session):
707+
"""Returns the account id and policy ARN for the license key secret if they exist"""
708+
global __cached_license_key_nr_account_id
682709
global __cached_license_key_policy_arn
683-
if __cached_license_key_policy_arn:
684-
return __cached_license_key_policy_arn
710+
if __cached_license_key_nr_account_id and __cached_license_key_policy_arn:
711+
return [__cached_license_key_nr_account_id, __cached_license_key_policy_arn]
712+
713+
account_id, policy_arn = _get_stack_output_value(
714+
session, ["NrAccountId", "ViewPolicyARN"]
715+
)
716+
if account_id is not None:
717+
__cached_license_key_nr_account_id = account_id
718+
if policy_arn is not None:
719+
__cached_license_key_policy_arn = policy_arn
720+
return [account_id, policy_arn]
721+
722+
723+
def _get_stack_output_value(session, output_keys):
685724
client = session.client("cloudformation")
686725
try:
687726
stacks = client.describe_stacks(StackName=LICENSE_KEY_STACK_NAME).get(
@@ -700,13 +739,13 @@ def _get_license_key_policy_arn(session):
700739
if not stacks:
701740
return None
702741
stack = stacks[0]
703-
output_key = "ViewPolicyARN"
704-
for output in stack.get("Outputs", []):
705-
if output["OutputKey"] == output_key:
706-
__cached_license_key_policy_arn = output["OutputValue"]
707-
return __cached_license_key_policy_arn
708-
else:
709-
return None
742+
output_values = []
743+
for output_key in output_keys:
744+
for output in stack.get("Outputs", []):
745+
if output["OutputKey"] == output_key:
746+
value = output["OutputValue"] or None
747+
output_values.append(value)
748+
return output_values
710749

711750

712751
@catch_boto_errors

newrelic_lambda_cli/layers.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from newrelic_lambda_cli import api, subscriptions, utils
1212
from newrelic_lambda_cli.cliutils import failure, success, warning
1313
from newrelic_lambda_cli.functions import get_function
14-
from newrelic_lambda_cli.integrations import _get_license_key_policy_arn
14+
from newrelic_lambda_cli.integrations import _get_license_key_outputs
1515
from newrelic_lambda_cli.types import LayerInstall, LayerUninstall
1616
from newrelic_lambda_cli.utils import catch_boto_errors
1717

@@ -188,7 +188,24 @@ def install(input, function_arn):
188188
failure("Could not find function: %s" % function_arn)
189189
return False
190190

191-
policy_arn = _get_license_key_policy_arn(input.session)
191+
nr_account_id, policy_arn = _get_license_key_outputs(input.session)
192+
# If a managed secret exists but it was created with a different NR account id and license key
193+
# We want to notify the user and point them to documentation on how to proceed.
194+
if (
195+
policy_arn
196+
and nr_account_id != str(input.nr_account_id)
197+
and not input.nr_api_key
198+
):
199+
raise click.UsageError(
200+
"A managed secret already exists in this region for New Relic account {0}. "
201+
"Creating one managed secret per region is currently supported via the cli.\n"
202+
"To set up an additional secret for New Relic account {1} see our docs:\n{2}.\n"
203+
"Or you can re-run this command with "
204+
"`--nr-api-key` argument with your New Relic API key to set your license "
205+
"key in a NEW_RELIC_LICENSE_KEY environment variable instead.".format(
206+
nr_account_id, input.nr_account_id, utils.NR_DOCS_ACT_LINKING_URL
207+
)
208+
)
192209
if input.enable_extension and not policy_arn and not input.nr_api_key:
193210
raise click.UsageError(
194211
"In order to use `--enable-extension`, you must first run "
@@ -201,7 +218,12 @@ def install(input, function_arn):
201218
)
202219

203220
nr_license_key = None
204-
if not policy_arn and input.nr_api_key and input.nr_region:
221+
if (
222+
not policy_arn
223+
or nr_account_id != str(input.nr_account_id)
224+
and input.nr_api_key
225+
and input.nr_region
226+
):
205227
gql = api.validate_gql_credentials(input)
206228
nr_license_key = api.retrieve_license_key(gql)
207229

@@ -319,7 +341,7 @@ def uninstall(input, function_arn):
319341
)
320342
return False
321343
else:
322-
policy_arn = _get_license_key_policy_arn(input.session)
344+
_, policy_arn = _get_license_key_outputs(input.session)
323345
if policy_arn:
324346
_detach_license_key_policy(
325347
input.session, config["Configuration"]["Role"], policy_arn

newrelic_lambda_cli/templates/license-key-secret.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,16 @@ Parameters:
1818
ViewPolicyExportName:
1919
Type: String
2020
Default: NewRelic-ViewLicenseKeyPolicyARN
21+
NrAccountId:
22+
Type: String
2123

2224
Resources:
2325
LicenseKeySecret:
2426
Type: 'AWS::SecretsManager::Secret'
2527
Properties:
2628
Description: The New Relic license key, for sending telemetry
2729
Name: !Sub "${SecretName}"
28-
SecretString: !Sub '{ "LicenseKey": "${LicenseKey}" }'
30+
SecretString: !Sub '{ "LicenseKey": "${LicenseKey}", "NrAccountId": "${NrAccountId}" }'
2931
ViewNewRelicLicenseKeyPolicy:
3032
Type: 'AWS::IAM::ManagedPolicy'
3133
Properties:
@@ -51,3 +53,7 @@ Outputs:
5153
Value: !Ref ViewNewRelicLicenseKeyPolicy
5254
Export:
5355
Name: !Sub "${AWS::StackName}-${ViewPolicyExportName}"
56+
NrAccountId:
57+
Value: !Ref NrAccountId
58+
Export:
59+
Name: !Sub "${AWS::StackName}-NrAccountId"

newrelic_lambda_cli/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import botocore
77
import click
88

9+
NR_DOCS_ACT_LINKING_URL = "https://docs.newrelic.com/docs/serverless-function-monitoring/aws-lambda-monitoring/enable-lambda-monitoring/account-linking/#manually-configuring-the-license-key-secret"
910
NEW_RELIC_ARN_PREFIX_TEMPLATE = "arn:aws:lambda:%s:451483290750"
1011
RUNTIME_CONFIG = {
1112
"dotnetcore3.1": {"LambdaExtension": True},

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
setup(
88
name="newrelic-lambda-cli",
9-
version="0.5.5",
9+
version="0.6.0",
1010
python_requires=">=3.3",
1111
description="A CLI to install the New Relic AWS Lambda integration and layers.",
1212
long_description=README,

tests/test_integrations.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
remove_log_ingestion_function,
2222
install_license_key,
2323
remove_license_key,
24-
_get_license_key_policy_arn,
24+
_get_license_key_outputs,
25+
_get_stack_output_value,
2526
get_aws_account_id,
2627
update_log_ingestion_function,
2728
remove_integration_role,
@@ -203,7 +204,9 @@ def test_install_license_key(success_mock):
203204
cf_client = MagicMock(name="cloudformation", **cf_mocks)
204205
mock_client_factory.side_effect = [cf_client, cf_client]
205206

206-
result = install_license_key(integration_install(session=session), "1234abcd")
207+
result = install_license_key(
208+
integration_install(session=session, nr_account_id=12345), "1234abcd"
209+
)
207210
assert result is True
208211

209212
cf_client.assert_has_calls(
@@ -213,6 +216,7 @@ def test_install_license_key(success_mock):
213216
TemplateBody=ANY,
214217
Parameters=[
215218
{"ParameterKey": "LicenseKey", "ParameterValue": "1234abcd"},
219+
{"ParameterKey": "NrAccountId", "ParameterValue": "12345"},
216220
],
217221
Capabilities=["CAPABILITY_NAMED_IAM"],
218222
Tags=[],
@@ -266,13 +270,26 @@ def test_remove_license_key(success_mock):
266270
success_mock.assert_called_once()
267271

268272

269-
def test__get_license_key_policy_arn():
273+
def test__get_license_key_outputs():
274+
with patch(
275+
"newrelic_lambda_cli.integrations._get_stack_output_value"
276+
) as mock_get_stack_output:
277+
mock_get_stack_output.return_value = [12345, "policy_arn"]
278+
session = MagicMock()
279+
result = _get_license_key_outputs(session)
280+
assert result == mock_get_stack_output.return_value
281+
mock_get_stack_output.assert_called_once_with(
282+
session, ["NrAccountId", "ViewPolicyARN"]
283+
)
284+
285+
286+
def test__get_stack_output_value():
270287
session = MagicMock()
271288
with patch.object(session, "client") as mock_client_factory:
272289
cf_client = MagicMock(name="cloudformation")
273290
mock_client_factory.side_effect = cf_client
274291

275-
_get_license_key_policy_arn(session)
292+
_get_stack_output_value(session, ["NrAccountId", "ViewPolicyARN"])
276293

277294
cf_client.assert_has_calls(
278295
[call().describe_stacks(StackName="NewRelicLicenseKeySecret")],

0 commit comments

Comments
 (0)