newrelic-lambda-cli 0.9.13__py2.py3-none-any.whl → 0.9.15__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- newrelic_lambda_cli/api.py +12 -13
- newrelic_lambda_cli/apm.py +346 -0
- newrelic_lambda_cli/cli/__init__.py +2 -0
- newrelic_lambda_cli/cli/apm.py +121 -0
- newrelic_lambda_cli/cli/layers.py +6 -0
- newrelic_lambda_cli/layers.py +17 -4
- newrelic_lambda_cli/types.py +16 -0
- {newrelic_lambda_cli-0.9.13.dist-info → newrelic_lambda_cli-0.9.15.dist-info}/METADATA +18 -4
- {newrelic_lambda_cli-0.9.13.dist-info → newrelic_lambda_cli-0.9.15.dist-info}/RECORD +13 -11
- {newrelic_lambda_cli-0.9.13.dist-info → newrelic_lambda_cli-0.9.15.dist-info}/WHEEL +0 -0
- {newrelic_lambda_cli-0.9.13.dist-info → newrelic_lambda_cli-0.9.15.dist-info}/entry_points.txt +0 -0
- {newrelic_lambda_cli-0.9.13.dist-info → newrelic_lambda_cli-0.9.15.dist-info}/licenses/LICENSE +0 -0
- {newrelic_lambda_cli-0.9.13.dist-info → newrelic_lambda_cli-0.9.15.dist-info}/top_level.txt +0 -0
newrelic_lambda_cli/api.py
CHANGED
|
@@ -96,24 +96,23 @@ class NewRelicGQL(object):
|
|
|
96
96
|
"""
|
|
97
97
|
res = self.query(
|
|
98
98
|
"""
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
99
|
+
query ($accountId: Int!) {
|
|
100
|
+
actor {
|
|
101
|
+
apiAccess {
|
|
102
|
+
keySearch(query: {types: INGEST, scope: {accountIds: [$accountId]}}) {
|
|
103
|
+
keys {
|
|
104
|
+
key
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
108
109
|
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
110
|
""",
|
|
112
111
|
accountId=self.account_id,
|
|
113
112
|
)
|
|
114
113
|
try:
|
|
115
|
-
return res["actor"]["
|
|
116
|
-
except KeyError:
|
|
114
|
+
return res["actor"]["apiAccess"]["keySearch"]["keys"][0]["key"]
|
|
115
|
+
except (KeyError, IndexError):
|
|
117
116
|
return None
|
|
118
117
|
|
|
119
118
|
def get_linked_account_by_id(self, id):
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
Example usage:
|
|
6
|
+
|
|
7
|
+
>>> from newrelic_lambda_cli.api import NewRelicGQL
|
|
8
|
+
>>> gql = NewRelicGQL("api key here", "account id here")
|
|
9
|
+
>>> gql.get_linked_accounts()
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from gql import Client, gql
|
|
14
|
+
from gql.transport.requests import RequestsHTTPTransport
|
|
15
|
+
|
|
16
|
+
import click
|
|
17
|
+
import requests
|
|
18
|
+
import json
|
|
19
|
+
|
|
20
|
+
from newrelic_lambda_cli.cliutils import failure, success
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class NRGQL_APM(object):
|
|
24
|
+
def __init__(self, account_id, api_key, region="us"):
|
|
25
|
+
try:
|
|
26
|
+
self.account_id = int(account_id)
|
|
27
|
+
except ValueError:
|
|
28
|
+
raise ValueError("Account ID must be an integer")
|
|
29
|
+
|
|
30
|
+
self.api_key = api_key
|
|
31
|
+
|
|
32
|
+
if region == "us":
|
|
33
|
+
self.url = "https://api.newrelic.com/graphql"
|
|
34
|
+
elif region == "eu":
|
|
35
|
+
self.url = "https://api.eu.newrelic.com/graphql"
|
|
36
|
+
elif region == "staging":
|
|
37
|
+
self.url = "https://staging-api.newrelic.com/graphql"
|
|
38
|
+
else:
|
|
39
|
+
raise ValueError("Region must be one of 'us' or 'eu'")
|
|
40
|
+
|
|
41
|
+
transport = RequestsHTTPTransport(url=self.url, use_json=True)
|
|
42
|
+
transport.headers = {"api-key": self.api_key}
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
self.client = Client(transport=transport, fetch_schema_from_transport=True)
|
|
46
|
+
except Exception:
|
|
47
|
+
self.client = Client(transport=transport, fetch_schema_from_transport=False)
|
|
48
|
+
|
|
49
|
+
def query(self, query, timeout=None, **variable_values):
|
|
50
|
+
return self.client.execute(
|
|
51
|
+
gql(query), timeout=timeout, variable_values=variable_values or None
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def get_entity_guids_from_entity_name(self, entity_name) -> dict[str, str]:
|
|
55
|
+
entity_dicts = {}
|
|
56
|
+
res = self.query(
|
|
57
|
+
f"""
|
|
58
|
+
query {{
|
|
59
|
+
actor {{
|
|
60
|
+
entitySearch(query: "name LIKE '{entity_name}' AND accountId = {self.account_id}") {{
|
|
61
|
+
results {{
|
|
62
|
+
entities {{
|
|
63
|
+
guid
|
|
64
|
+
name
|
|
65
|
+
type
|
|
66
|
+
}}
|
|
67
|
+
}}
|
|
68
|
+
}}
|
|
69
|
+
}}
|
|
70
|
+
}}
|
|
71
|
+
"""
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
data = res
|
|
76
|
+
entities = data["actor"]["entitySearch"]["results"]["entities"]
|
|
77
|
+
for entity in entities:
|
|
78
|
+
if entity["name"] == entity_name:
|
|
79
|
+
entity_dicts[entity["type"]] = entity["guid"]
|
|
80
|
+
return entity_dicts
|
|
81
|
+
except (KeyError, TypeError) as e:
|
|
82
|
+
print(f"An error occurred parsing the response: {e}")
|
|
83
|
+
return None
|
|
84
|
+
except Exception as e:
|
|
85
|
+
print(f"An error occurred: {e}")
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
def get_entity_alert_details(self, entity_guid) -> dict:
|
|
89
|
+
res = self.query(
|
|
90
|
+
f"""
|
|
91
|
+
query {{
|
|
92
|
+
actor {{
|
|
93
|
+
entity(guid: "{entity_guid}") {{
|
|
94
|
+
name
|
|
95
|
+
guid
|
|
96
|
+
reporting
|
|
97
|
+
alertSeverity
|
|
98
|
+
}}
|
|
99
|
+
account(id: {self.account_id}) {{
|
|
100
|
+
alerts {{
|
|
101
|
+
nrqlConditionsSearch(searchCriteria: {{queryLike: "{entity_guid}"}}) {{
|
|
102
|
+
nrqlConditions {{
|
|
103
|
+
id
|
|
104
|
+
name
|
|
105
|
+
enabled
|
|
106
|
+
description
|
|
107
|
+
policyId
|
|
108
|
+
nrql {{
|
|
109
|
+
query
|
|
110
|
+
}}
|
|
111
|
+
terms {{
|
|
112
|
+
operator
|
|
113
|
+
priority
|
|
114
|
+
threshold
|
|
115
|
+
thresholdDuration
|
|
116
|
+
thresholdOccurrences
|
|
117
|
+
}}
|
|
118
|
+
}}
|
|
119
|
+
}}
|
|
120
|
+
}}
|
|
121
|
+
}}
|
|
122
|
+
}}
|
|
123
|
+
}}
|
|
124
|
+
"""
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
print(f"Querying alert details for Lambda entity")
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
data = res
|
|
131
|
+
# Check for GraphQL errors in the response
|
|
132
|
+
if "errors" in data:
|
|
133
|
+
print("Error in GraphQL response:")
|
|
134
|
+
for error in data["errors"]:
|
|
135
|
+
print(f"- {error.get('message')}")
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
# Extract the entity data from the response
|
|
139
|
+
actor_data = data.get("actor", {})
|
|
140
|
+
entity_data = actor_data.get("entity")
|
|
141
|
+
|
|
142
|
+
if not entity_data:
|
|
143
|
+
print(f"Error: Could not find data for entity with GUID: {entity_guid}")
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
# Extract alert conditions from the new path and add them to our entity_data dictionary
|
|
147
|
+
conditions_search_result = (
|
|
148
|
+
actor_data.get("account", {})
|
|
149
|
+
.get("alerts", {})
|
|
150
|
+
.get("nrqlConditionsSearch", {})
|
|
151
|
+
)
|
|
152
|
+
entity_data["alertConditions"] = (
|
|
153
|
+
conditions_search_result.get("nrqlConditions", [])
|
|
154
|
+
if conditions_search_result
|
|
155
|
+
else []
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
return entity_data
|
|
159
|
+
|
|
160
|
+
except (KeyError, TypeError) as e:
|
|
161
|
+
print(f"An error occurred parsing the response: {e}")
|
|
162
|
+
return None
|
|
163
|
+
except Exception as e:
|
|
164
|
+
print(f"An error occurred: {e}")
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
def create_alert_for_new_entity(
|
|
168
|
+
self, lambda_entity_selected_alerts, lambda_entity_guid, apm_entity_guid
|
|
169
|
+
):
|
|
170
|
+
# Create a list of new NRQL conditions with modified queries
|
|
171
|
+
new_nrql_conditions = []
|
|
172
|
+
for alert_condition in lambda_entity_selected_alerts:
|
|
173
|
+
original_description = alert_condition.get("description") or ""
|
|
174
|
+
new_description = (
|
|
175
|
+
f"{original_description} migrated from Lambda entity".strip()
|
|
176
|
+
)
|
|
177
|
+
alert_query = alert_condition["nrql"]["query"]
|
|
178
|
+
alert_query = create_apm_alert_query(
|
|
179
|
+
alert_query, lambda_entity_guid, apm_entity_guid
|
|
180
|
+
)
|
|
181
|
+
new_condition = {
|
|
182
|
+
"name": alert_condition["name"] + " - apm_migrated",
|
|
183
|
+
"description": new_description,
|
|
184
|
+
"enabled": True,
|
|
185
|
+
"nrql": {"query": alert_query},
|
|
186
|
+
"terms": alert_condition["terms"],
|
|
187
|
+
}
|
|
188
|
+
policy_id = alert_condition.get("policyId")
|
|
189
|
+
new_nrql_conditions.append((new_condition, policy_id))
|
|
190
|
+
|
|
191
|
+
# Process each condition individually
|
|
192
|
+
results = []
|
|
193
|
+
for new_condition, policy_id in new_nrql_conditions:
|
|
194
|
+
# Validate policy_id
|
|
195
|
+
try:
|
|
196
|
+
policy_id_int = int(policy_id)
|
|
197
|
+
except (ValueError, TypeError):
|
|
198
|
+
print(
|
|
199
|
+
f"Error: Invalid policy ID '{policy_id}' for condition '{new_condition['name']}'. Skipping."
|
|
200
|
+
)
|
|
201
|
+
continue
|
|
202
|
+
|
|
203
|
+
# Format terms as proper GraphQL objects
|
|
204
|
+
terms_list = []
|
|
205
|
+
for term in new_condition["terms"]:
|
|
206
|
+
term_str = "{"
|
|
207
|
+
term_str += f"operator: {term['operator']}, "
|
|
208
|
+
term_str += f"priority: {term['priority']}, "
|
|
209
|
+
term_str += f"threshold: {term['threshold']}, "
|
|
210
|
+
term_str += f"thresholdDuration: {term['thresholdDuration']}, "
|
|
211
|
+
term_str += f"thresholdOccurrences: {term['thresholdOccurrences']}"
|
|
212
|
+
term_str += "}"
|
|
213
|
+
terms_list.append(term_str)
|
|
214
|
+
|
|
215
|
+
terms_str = "[" + ", ".join(terms_list) + "]"
|
|
216
|
+
|
|
217
|
+
mutation = f"""
|
|
218
|
+
mutation {{
|
|
219
|
+
alertsNrqlConditionStaticCreate(
|
|
220
|
+
accountId: {self.account_id},
|
|
221
|
+
condition: {{
|
|
222
|
+
name: "{new_condition['name']}"
|
|
223
|
+
description: "{new_condition['description']}"
|
|
224
|
+
enabled: {str(new_condition['enabled']).lower()}
|
|
225
|
+
nrql: {{
|
|
226
|
+
query: "{new_condition['nrql']['query']}"
|
|
227
|
+
}}
|
|
228
|
+
terms: {terms_str}
|
|
229
|
+
}},
|
|
230
|
+
policyId: "{policy_id_int}"
|
|
231
|
+
) {{
|
|
232
|
+
id
|
|
233
|
+
name
|
|
234
|
+
description
|
|
235
|
+
nrql {{
|
|
236
|
+
query
|
|
237
|
+
}}
|
|
238
|
+
terms {{
|
|
239
|
+
operator
|
|
240
|
+
priority
|
|
241
|
+
threshold
|
|
242
|
+
thresholdDuration
|
|
243
|
+
thresholdOccurrences
|
|
244
|
+
}}
|
|
245
|
+
}}
|
|
246
|
+
}}
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
try:
|
|
250
|
+
res = self.query(mutation)
|
|
251
|
+
# Check for GraphQL errors in the response
|
|
252
|
+
if "errors" in res:
|
|
253
|
+
print("Error in GraphQL response:")
|
|
254
|
+
for error in res["errors"]:
|
|
255
|
+
print(f"- {error.get('message')}")
|
|
256
|
+
continue
|
|
257
|
+
|
|
258
|
+
result = res["alertsNrqlConditionStaticCreate"]
|
|
259
|
+
results.append(result)
|
|
260
|
+
success(f"Successfully migrated to alert: {result['name']}")
|
|
261
|
+
|
|
262
|
+
except (KeyError, TypeError) as e:
|
|
263
|
+
print(f"An error occurred parsing the response: {e}")
|
|
264
|
+
continue
|
|
265
|
+
except Exception as e:
|
|
266
|
+
print(f"An error occurred: {e}")
|
|
267
|
+
continue
|
|
268
|
+
|
|
269
|
+
return results
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
lambda_entity_alert_metric = {
|
|
273
|
+
"cwBilledDuration": "apm.lambda.transaction.billed_duration",
|
|
274
|
+
"cwDuration": "apm.lambda.transaction.duration",
|
|
275
|
+
"cwInitDuration": "apm.lambda.transaction.init_duration",
|
|
276
|
+
"cwMaxMemoryUsed": "apm.lambda.transaction.max_memory_used",
|
|
277
|
+
"cwMemorySize": "apm.lambda.transaction.memory_size",
|
|
278
|
+
"cloudWatchBilledDuration": "apm.lambda.transaction.billed_duration",
|
|
279
|
+
"cloudWatchDuration": "apm.lambda.transaction.duration",
|
|
280
|
+
"cloudWatchInitDuration": "apm.lambda.transaction.init_duration",
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def check_apm_migrated_alerts(lambda_entity_data, apm_entity_data):
|
|
285
|
+
"""
|
|
286
|
+
Check which Lambda alerts have not been migrated to APM yet.
|
|
287
|
+
Returns a list of Lambda alert conditions that don't have corresponding APM migrated versions.
|
|
288
|
+
"""
|
|
289
|
+
if not lambda_entity_data or "alertConditions" not in lambda_entity_data:
|
|
290
|
+
print("No alert conditions found for Lambda entity.")
|
|
291
|
+
return []
|
|
292
|
+
|
|
293
|
+
if not apm_entity_data or "alertConditions" not in apm_entity_data:
|
|
294
|
+
print("No alert conditions found for APM entity.")
|
|
295
|
+
return []
|
|
296
|
+
|
|
297
|
+
alerts_not_migrated = []
|
|
298
|
+
lambda_alert_conditions = lambda_entity_data["alertConditions"]
|
|
299
|
+
apm_alert_conditions = apm_entity_data["alertConditions"]
|
|
300
|
+
apm_alerts_name = [condition["name"] for condition in apm_alert_conditions]
|
|
301
|
+
|
|
302
|
+
for lambda_condition in lambda_alert_conditions:
|
|
303
|
+
migrated_lambda_alert_name = lambda_condition["name"] + " - apm_migrated"
|
|
304
|
+
if migrated_lambda_alert_name not in apm_alerts_name:
|
|
305
|
+
alerts_not_migrated.append(lambda_condition)
|
|
306
|
+
else:
|
|
307
|
+
print(f"Alert already migrated, skipping: {lambda_condition['name']}")
|
|
308
|
+
|
|
309
|
+
return alerts_not_migrated
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def select_lambda_entity_impacted_alerts(entity_data, apm_entity_data=None):
|
|
313
|
+
if not entity_data or "alertConditions" not in entity_data:
|
|
314
|
+
print("No alert conditions found.")
|
|
315
|
+
return []
|
|
316
|
+
|
|
317
|
+
selected_alerts = []
|
|
318
|
+
alert_conditions = entity_data["alertConditions"]
|
|
319
|
+
for condition in alert_conditions:
|
|
320
|
+
alert_query = condition["nrql"]["query"]
|
|
321
|
+
has_lambda_invocation = "AwsLambdaInvocation" in alert_query
|
|
322
|
+
has_cloudwatch_metrics = any(
|
|
323
|
+
metric in alert_query for metric in lambda_entity_alert_metric.keys()
|
|
324
|
+
)
|
|
325
|
+
if has_lambda_invocation and has_cloudwatch_metrics:
|
|
326
|
+
print(f"Selected alert for migration: {condition['name']}")
|
|
327
|
+
selected_alerts.append(condition)
|
|
328
|
+
if apm_entity_data:
|
|
329
|
+
selected_alerts = check_apm_migrated_alerts(
|
|
330
|
+
{"alertConditions": selected_alerts}, apm_entity_data
|
|
331
|
+
)
|
|
332
|
+
if len(selected_alerts) == 0:
|
|
333
|
+
print(
|
|
334
|
+
"No alerts met the migration criteria (must contain 'AwsLambdaInvocation' or CloudWatch metrics)"
|
|
335
|
+
)
|
|
336
|
+
return selected_alerts
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def create_apm_alert_query(alert_query, lambda_entity_guid, apm_entity_guid):
|
|
340
|
+
for key, value in lambda_entity_alert_metric.items():
|
|
341
|
+
if key in alert_query:
|
|
342
|
+
alert_query = alert_query.replace(key, value)
|
|
343
|
+
break
|
|
344
|
+
apm_alert_query = alert_query.replace("AwsLambdaInvocation", "Metric")
|
|
345
|
+
apm_alert_query = apm_alert_query.replace(lambda_entity_guid, apm_entity_guid)
|
|
346
|
+
return apm_alert_query
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import click
|
|
4
4
|
|
|
5
5
|
from newrelic_lambda_cli.cli import (
|
|
6
|
+
apm,
|
|
6
7
|
functions,
|
|
7
8
|
integrations,
|
|
8
9
|
layers,
|
|
@@ -21,6 +22,7 @@ def cli(ctx, verbose):
|
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
def register_groups(group):
|
|
25
|
+
apm.register(group)
|
|
24
26
|
functions.register(group)
|
|
25
27
|
integrations.register(group)
|
|
26
28
|
otel_ingestions.register(group)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import boto3
|
|
4
|
+
import click
|
|
5
|
+
|
|
6
|
+
from newrelic_lambda_cli import api, apm, otel_ingestions, permissions, integrations
|
|
7
|
+
from newrelic_lambda_cli.types import (
|
|
8
|
+
AlertsMigrate,
|
|
9
|
+
)
|
|
10
|
+
from newrelic_lambda_cli.cli.decorators import add_options, AWS_OPTIONS, NR_OPTIONS
|
|
11
|
+
from newrelic_lambda_cli.cliutils import done, failure
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.group(name="apm")
|
|
15
|
+
def apm_mode_group():
|
|
16
|
+
"""Manage New Relic APM Mode of AWS Lambda instrumentation"""
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def register(group):
|
|
21
|
+
group.add_command(apm_mode_group)
|
|
22
|
+
apm_mode_group.add_command(alerts_migrate)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@click.command(name="alerts-migrate")
|
|
26
|
+
@click.option(
|
|
27
|
+
"--nr-account-id",
|
|
28
|
+
"-a",
|
|
29
|
+
envvar="NEW_RELIC_ACCOUNT_ID",
|
|
30
|
+
help="New Relic Account ID",
|
|
31
|
+
metavar="<account_id>",
|
|
32
|
+
required=True,
|
|
33
|
+
type=click.INT,
|
|
34
|
+
)
|
|
35
|
+
@click.option(
|
|
36
|
+
"--nr-api-key",
|
|
37
|
+
"-k",
|
|
38
|
+
envvar="NEW_RELIC_API_KEY",
|
|
39
|
+
help="New Relic User API Key",
|
|
40
|
+
metavar="<key>",
|
|
41
|
+
required=True,
|
|
42
|
+
)
|
|
43
|
+
@click.option(
|
|
44
|
+
"--nr-region",
|
|
45
|
+
default="us",
|
|
46
|
+
envvar="NEW_RELIC_REGION",
|
|
47
|
+
help="New Relic Account Region",
|
|
48
|
+
metavar="<region>",
|
|
49
|
+
show_default=True,
|
|
50
|
+
type=click.Choice(["us", "eu", "staging"]),
|
|
51
|
+
)
|
|
52
|
+
@add_options(AWS_OPTIONS)
|
|
53
|
+
@click.option(
|
|
54
|
+
"function",
|
|
55
|
+
"--function",
|
|
56
|
+
"-f",
|
|
57
|
+
help="AWS Lambda function name or ARN",
|
|
58
|
+
metavar="<arn>",
|
|
59
|
+
multiple=False,
|
|
60
|
+
required=True,
|
|
61
|
+
)
|
|
62
|
+
@click.option(
|
|
63
|
+
"excludes",
|
|
64
|
+
"--exclude",
|
|
65
|
+
"-e",
|
|
66
|
+
help="Functions to exclude (if using 'all, 'installed', 'not-installed aliases)",
|
|
67
|
+
metavar="<name>",
|
|
68
|
+
multiple=True,
|
|
69
|
+
)
|
|
70
|
+
@click.pass_context
|
|
71
|
+
def alerts_migrate(ctx, **kwargs):
|
|
72
|
+
"""Migrate New Relic AWS Lambda Alerts to APM mode"""
|
|
73
|
+
input = AlertsMigrate(session=None, verbose=ctx.obj["VERBOSE"], **kwargs)
|
|
74
|
+
input = input._replace(
|
|
75
|
+
session=boto3.Session(
|
|
76
|
+
profile_name=input.aws_profile, region_name=input.aws_region
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Validate required parameters
|
|
81
|
+
if not input.nr_api_key:
|
|
82
|
+
failure(
|
|
83
|
+
"New Relic API key is required. Provide it via --nr-api-key or NEW_RELIC_API_KEY environment variable.",
|
|
84
|
+
exit=True,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
print("Started migration of alerts")
|
|
88
|
+
if ctx.obj["VERBOSE"]:
|
|
89
|
+
print("Will migrate alerts for {}".format(input.function))
|
|
90
|
+
print(f"Using account ID: {input.nr_account_id}")
|
|
91
|
+
print(f"Using region: {input.nr_region}")
|
|
92
|
+
|
|
93
|
+
# setup client
|
|
94
|
+
client = apm.NRGQL_APM(
|
|
95
|
+
account_id=input.nr_account_id,
|
|
96
|
+
api_key=input.nr_api_key,
|
|
97
|
+
region=input.nr_region,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
print(f"Getting entity GUID for function: {input.function}")
|
|
101
|
+
result = client.get_entity_guids_from_entity_name(input.function)
|
|
102
|
+
lambda_entity_guid = ""
|
|
103
|
+
apm_entity_guid = ""
|
|
104
|
+
for entity_type, entity_guid in result.items():
|
|
105
|
+
if entity_type == "AWSLAMBDAFUNCTION":
|
|
106
|
+
lambda_entity_guid = entity_guid
|
|
107
|
+
if entity_type == "APPLICATION":
|
|
108
|
+
apm_entity_guid = entity_guid
|
|
109
|
+
lambda_entity_data = client.get_entity_alert_details(lambda_entity_guid)
|
|
110
|
+
apm_entity_data = client.get_entity_alert_details(apm_entity_guid)
|
|
111
|
+
lambda_entity_selected_alerts = apm.select_lambda_entity_impacted_alerts(
|
|
112
|
+
lambda_entity_data, apm_entity_data
|
|
113
|
+
)
|
|
114
|
+
if lambda_entity_selected_alerts:
|
|
115
|
+
client.create_alert_for_new_entity(
|
|
116
|
+
lambda_entity_selected_alerts, lambda_entity_guid, apm_entity_guid
|
|
117
|
+
)
|
|
118
|
+
else:
|
|
119
|
+
print(
|
|
120
|
+
"No alerts need to be migrated - all eligible alerts have already been migrated."
|
|
121
|
+
)
|
|
@@ -133,6 +133,12 @@ def register(group):
|
|
|
133
133
|
is_flag=True,
|
|
134
134
|
help="Enable sending Lambda function logs via the New Relic Lambda Extension",
|
|
135
135
|
)
|
|
136
|
+
@click.option(
|
|
137
|
+
"--slim",
|
|
138
|
+
is_flag=True,
|
|
139
|
+
default=False,
|
|
140
|
+
help="New Relic slim Layer without opentelemetry for Node.js",
|
|
141
|
+
)
|
|
136
142
|
@click.option(
|
|
137
143
|
"--disable-function-logs",
|
|
138
144
|
is_flag=True,
|
newrelic_lambda_cli/layers.py
CHANGED
|
@@ -48,7 +48,12 @@ def index(region, runtime, architecture):
|
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
def layer_selection(
|
|
51
|
-
available_layers,
|
|
51
|
+
available_layers,
|
|
52
|
+
runtime,
|
|
53
|
+
architecture,
|
|
54
|
+
upgrade=False,
|
|
55
|
+
existing_layer_arn=None,
|
|
56
|
+
slim=False,
|
|
52
57
|
):
|
|
53
58
|
if upgrade and existing_layer_arn:
|
|
54
59
|
base_arn = existing_layer_arn.rsplit(":", 1)[0]
|
|
@@ -65,7 +70,11 @@ def layer_selection(
|
|
|
65
70
|
layer_options = [
|
|
66
71
|
layer["LatestMatchingVersion"]["LayerVersionArn"] for layer in available_layers
|
|
67
72
|
]
|
|
68
|
-
|
|
73
|
+
if slim:
|
|
74
|
+
for arn in layer_options:
|
|
75
|
+
if "-slim:" in arn:
|
|
76
|
+
success("Layer %s selected (slim)" % arn)
|
|
77
|
+
return arn
|
|
69
78
|
if sys.stdout.isatty():
|
|
70
79
|
output = "\n".join(
|
|
71
80
|
[
|
|
@@ -86,11 +95,14 @@ def layer_selection(
|
|
|
86
95
|
except IndexError:
|
|
87
96
|
failure("Invalid layer selection")
|
|
88
97
|
else:
|
|
89
|
-
|
|
98
|
+
click.echo(
|
|
90
99
|
"Discovered multiple layers for runtime %s (%s):\n%s\n"
|
|
91
|
-
"
|
|
100
|
+
"To add a particular Layer ARN use --layer-arn"
|
|
92
101
|
% (runtime, architecture, "\n".join(layer_options))
|
|
93
102
|
)
|
|
103
|
+
selected = layer_options[0]
|
|
104
|
+
success("Layer %s selected (auto)" % selected)
|
|
105
|
+
return selected
|
|
94
106
|
|
|
95
107
|
|
|
96
108
|
def _add_new_relic(input, config, nr_license_key):
|
|
@@ -167,6 +179,7 @@ def _add_new_relic(input, config, nr_license_key):
|
|
|
167
179
|
architecture,
|
|
168
180
|
upgrade=input.upgrade,
|
|
169
181
|
existing_layer_arn=existing_layer_arn,
|
|
182
|
+
slim=input.slim,
|
|
170
183
|
)
|
|
171
184
|
|
|
172
185
|
update_kwargs = {
|
newrelic_lambda_cli/types.py
CHANGED
|
@@ -117,6 +117,7 @@ LAYER_INSTALL_KEYS = [
|
|
|
117
117
|
"disable_extension_logs",
|
|
118
118
|
"java_handler_method",
|
|
119
119
|
"esm",
|
|
120
|
+
"slim",
|
|
120
121
|
]
|
|
121
122
|
|
|
122
123
|
LAYER_UNINSTALL_KEYS = [
|
|
@@ -141,6 +142,19 @@ SUBSCRIPTION_INSTALL_KEYS = [
|
|
|
141
142
|
"otel",
|
|
142
143
|
]
|
|
143
144
|
|
|
145
|
+
ALERTS_MIGRATE_KEYS = [
|
|
146
|
+
"session",
|
|
147
|
+
"aws_profile",
|
|
148
|
+
"aws_region",
|
|
149
|
+
"aws_permissions_check",
|
|
150
|
+
"nr_account_id",
|
|
151
|
+
"nr_api_key",
|
|
152
|
+
"nr_region",
|
|
153
|
+
"function",
|
|
154
|
+
"excludes",
|
|
155
|
+
"verbose",
|
|
156
|
+
]
|
|
157
|
+
|
|
144
158
|
SUBSCRIPTION_UNINSTALL_KEYS = [
|
|
145
159
|
"session",
|
|
146
160
|
"aws_profile",
|
|
@@ -166,5 +180,7 @@ OtelIngestionUpdate = namedtuple("OtelIngestionUpdate", OTEL_INGESTION_UPDATE_KE
|
|
|
166
180
|
LayerInstall = namedtuple("LayerInstall", LAYER_INSTALL_KEYS)
|
|
167
181
|
LayerUninstall = namedtuple("LayerUninstall", LAYER_UNINSTALL_KEYS)
|
|
168
182
|
|
|
183
|
+
AlertsMigrate = namedtuple("AlertsMigrate", ALERTS_MIGRATE_KEYS)
|
|
184
|
+
|
|
169
185
|
SubscriptionInstall = namedtuple("SubscriptionInstall", SUBSCRIPTION_INSTALL_KEYS)
|
|
170
186
|
SubscriptionUninstall = namedtuple("SubscriptionUninstall", SUBSCRIPTION_UNINSTALL_KEYS)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: newrelic-lambda-cli
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.15
|
|
4
4
|
Summary: A CLI to install the New Relic AWS Lambda integration and layers.
|
|
5
5
|
Home-page: https://github.com/newrelic/newrelic-lambda-cli
|
|
6
6
|
Author: New Relic
|
|
@@ -64,11 +64,11 @@ A CLI to install the New Relic AWS Lambda integration and layers.
|
|
|
64
64
|
|
|
65
65
|
| Runtime | Versions |
|
|
66
66
|
|-------------|------------------------|
|
|
67
|
-
| Python | `python3.
|
|
67
|
+
| Python | `python3.8`, `python3.9`, `python3.10`, `python3.11`, `python3.12`, `python3.13` |
|
|
68
68
|
| Node.js | `nodejs16.x`, `nodejs18.x`, `nodejs20.x`, `nodejs22.x` |
|
|
69
|
-
| .NET |
|
|
69
|
+
| .NET | `dotnet6`, `dotnet8` |
|
|
70
70
|
| Java | `java8.al2`, `java11`, `java17`, `java21` |
|
|
71
|
-
| Provided | `provided
|
|
71
|
+
| Provided | `provided.al2`, `provided.al2023` |
|
|
72
72
|
| Ruby | `ruby3.2`, `ruby3.3`, `ruby3.4` |
|
|
73
73
|
|
|
74
74
|
**Note:** Automatic handler wrapping is only supported for Node.js, Python, Java, and Ruby. For other runtimes,
|
|
@@ -78,6 +78,7 @@ manual function wrapping is required using the runtime specific New Relic agent.
|
|
|
78
78
|
|
|
79
79
|
* Python >= 3.7 <= 3.13
|
|
80
80
|
* Retrieve your [New relic Account ID](https://docs.newrelic.com/docs/accounts/install-new-relic/account-setup/account-id) and [User API Key](https://docs.newrelic.com/docs/apis/get-started/intro-apis/types-new-relic-api-keys#user-api-key)
|
|
81
|
+
* Retrieve your [New Relic Ingest License Key](https://docs.newrelic.com/docs/apis/intro-apis/new-relic-api-keys/#personal-api-key)
|
|
81
82
|
|
|
82
83
|
## Recommendations
|
|
83
84
|
|
|
@@ -199,6 +200,7 @@ newrelic-lambda layers install \
|
|
|
199
200
|
| `--function` or `-f` | Yes | The AWS Lambda function name or ARN in which to add a layer. Can provide multiple `--function` arguments. Will also accept `all`, `installed` and `not-installed` similar to `newrelic-lambda functions list`. |
|
|
200
201
|
| `--nr-account-id` or `-a` | Yes | The [New Relic Account ID](https://docs.newrelic.com/docs/accounts/install-new-relic/account-setup/account-id) this function should use. Can also use the `NEW_RELIC_ACCOUNT_ID` environment variable. |
|
|
201
202
|
| `--exclude` or `-e` | No | A function name to exclude while installing layers. Can provide multiple `--exclude` arguments. Only checked when `all`, `installed` and `not-installed` are used. See `newrelic-lambda functions list` for function names. |
|
|
203
|
+
| `--slim` | No | The flag `--slim` adds the Node.js layer without OpenTelemetry dependencies, resulting in a lighter size. |
|
|
202
204
|
| `--layer-arn` or `-l` | No | Specify a specific layer version ARN to use. This is auto detected by default. |
|
|
203
205
|
| `--upgrade` or `-u` | No | Permit upgrade to the latest layer version for this region and runtime. |
|
|
204
206
|
| `--disable-extension` | No | Disable the [New Relic Lambda Extension](https://github.com/newrelic/newrelic-lambda-extension). |
|
|
@@ -209,6 +211,7 @@ newrelic-lambda layers install \
|
|
|
209
211
|
| `--aws-profile` or `-p` | No | The AWS profile to use for this command. Can also use `AWS_PROFILE`. Will also check `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables if not using AWS CLI. |
|
|
210
212
|
| `--aws-region` or `-r` | No | The AWS region this function is located. Can use `AWS_DEFAULT_REGION` environment variable. Defaults to AWS session region. |
|
|
211
213
|
| `--nr-api-key` or `-k` | No | Your [New Relic User API Key](https://docs.newrelic.com/docs/apis/get-started/intro-apis/types-new-relic-api-keys#user-api-key). Can also use the `NEW_RELIC_API_KEY` environment variable. Only used if `--enable-extension` is set and there is no New Relic license key in AWS Secrets Manager. |
|
|
214
|
+
| `--nr-ingest-key`| No | Your [New Relic Ingest License Key](https://docs.newrelic.com/docs/apis/intro-apis/new-relic-api-keys/#personal-api-key). Can be used without `--enable-extension` configured or license key in AWS Secrets Manager. |
|
|
212
215
|
| `--nr-region` | No | The New Relic region to use for the integration. Can use the `NEW_RELIC_REGION` environment variable. Can be either `eu` or `us`. Defaults to `us`. Only used if `--enable-extension` is set and there is no New Relic license key in AWS Secrets Manager. |
|
|
213
216
|
| `--nr-env-delimite` | No | Set `NR_ENV_DELIMITER` environment variable for your Lambda Function |
|
|
214
217
|
| `--nr-tags` | No | Set `NR_TAGS` environment variable for your Lambda Function |
|
|
@@ -281,6 +284,17 @@ newrelic-lambda subscriptions uninstall --function <name or arn>
|
|
|
281
284
|
| `--aws-profile` or `-p` | No | The AWS profile to use for this command. Can also use `AWS_PROFILE`. Will also check `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables if not using AWS CLI. |
|
|
282
285
|
| `--aws-region` or `-r` | No | The AWS region this function is located. Can use `AWS_DEFAULT_REGION` environment variable. Defaults to AWS session region. |
|
|
283
286
|
|
|
287
|
+
### NewRelic APM + Serverless Convergence
|
|
288
|
+
|
|
289
|
+
#### Migrate Alerts from Lambda to APM
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
newrelic-lambda apm alerts-migrate \
|
|
293
|
+
--nr-account-id <account id> \
|
|
294
|
+
--nr-api-key <api key>
|
|
295
|
+
--function <Lambda-function-name>
|
|
296
|
+
```
|
|
297
|
+
|
|
284
298
|
### NewRelic Otel Ingestions Install
|
|
285
299
|
|
|
286
300
|
#### Install Otel Log Ingestion
|
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
newrelic_lambda_cli/__init__.py,sha256=u4cy7hwbrNjK3xtldGQD_51aoNgSqGQ3IzFYsuvaIM4,149
|
|
2
|
-
newrelic_lambda_cli/api.py,sha256
|
|
2
|
+
newrelic_lambda_cli/api.py,sha256=eBGq-IV73urdqhAA2xsIdf772q0npMiuRCCiCJgS10Q,15287
|
|
3
|
+
newrelic_lambda_cli/apm.py,sha256=ZGEdmRW8jlJOl17TnTO716dwxpDSvEb5_28K6XadPDA,12475
|
|
3
4
|
newrelic_lambda_cli/cliutils.py,sha256=XDtvTlgbf2uVg01yCJrfJmmgxbF0nIL9x58ETyvefk8,685
|
|
4
5
|
newrelic_lambda_cli/functions.py,sha256=p57ZUw424BaSUA_Gw8DrYsJOYKROEHEaXgARadqwcP0,2800
|
|
5
6
|
newrelic_lambda_cli/integrations.py,sha256=r0gxfEqVHTGu3xbr4dOCDwm0-Yhoz3KntfeQRWvoWrQ,31266
|
|
6
|
-
newrelic_lambda_cli/layers.py,sha256=
|
|
7
|
+
newrelic_lambda_cli/layers.py,sha256=BL7XPMw8zal0UL5nrgDJMknfnhwSkp_LKVkBThWx_do,20673
|
|
7
8
|
newrelic_lambda_cli/otel_ingestions.py,sha256=vi1Mlfc9nRvRWV7STwK7fDXZGozG8ufKohmpHcaWGic,9250
|
|
8
9
|
newrelic_lambda_cli/permissions.py,sha256=H7v5IMpKaJIWC4Dff2YcTis4BKAAFIJr9IHWUj1LnF4,9093
|
|
9
10
|
newrelic_lambda_cli/subscriptions.py,sha256=-BYkltqiDLdmhUB_Ot4w-5vvrKcQC6aHcTBLl1mlUlI,9564
|
|
10
|
-
newrelic_lambda_cli/types.py,sha256=
|
|
11
|
+
newrelic_lambda_cli/types.py,sha256=MC22Gw5u97qcpjLrmExST_2uXSH7qceDEmdMziOslh4,3815
|
|
11
12
|
newrelic_lambda_cli/utils.py,sha256=_XU5tFx9SxEzvp_brFFQtspBz_qoUZ2H_HDxhB_Qr1U,5902
|
|
12
|
-
newrelic_lambda_cli/cli/__init__.py,sha256=
|
|
13
|
+
newrelic_lambda_cli/cli/__init__.py,sha256=vY8EdoHeI5r8DY_zELWMb2hCkm_p6oae6JYPweOq1d4,655
|
|
14
|
+
newrelic_lambda_cli/cli/apm.py,sha256=gISzrUQ7a_QqOmReRGaTdpW1INIpAjPauBbqNt1vuNM,3510
|
|
13
15
|
newrelic_lambda_cli/cli/decorators.py,sha256=a3agkVfy8omkUSL4aKblwSX95xtxYOGASULDYcJDPHk,1786
|
|
14
16
|
newrelic_lambda_cli/cli/functions.py,sha256=RSh2Cowe1_oQup8q5YRidp03z-BITo2uzvDh4zvLr4I,2601
|
|
15
17
|
newrelic_lambda_cli/cli/integrations.py,sha256=aQAWcCCU2kBmbF8fLKwKB9bzSY0uipvnojajjTkhqEs,10461
|
|
16
|
-
newrelic_lambda_cli/cli/layers.py,sha256=
|
|
18
|
+
newrelic_lambda_cli/cli/layers.py,sha256=iZ6FBvAXeo-WC48E-bgerMx3JAY_AUCI4BL5Raop6sU,7719
|
|
17
19
|
newrelic_lambda_cli/cli/otel_ingestions.py,sha256=4rTm9iYUo2qdMeqxJSrYLCA6ZXHy5bJnjDn9x54pCYc,6096
|
|
18
20
|
newrelic_lambda_cli/cli/subscriptions.py,sha256=bUupv5iv3mUkC8t31nnI3BahoKxDnUJ8Rgq4QHJcFNU,5890
|
|
19
21
|
newrelic_lambda_cli/templates/import-template.yaml,sha256=0r1yeoqpnqtEMggWomALkPG10NiANPWWBqz03rChch8,3771
|
|
20
22
|
newrelic_lambda_cli/templates/license-key-secret.yaml,sha256=ZldQaLXsyF1K2I4X_AsLdH7kRmLkPUYI3talmhqQyHg,1849
|
|
21
23
|
newrelic_lambda_cli/templates/nr-lambda-integration-role.yaml,sha256=s7T73B_k-mAwgzJrD2xn8YGUNgn2E1V7Exifrl81ViU,2874
|
|
22
|
-
newrelic_lambda_cli-0.9.
|
|
23
|
-
newrelic_lambda_cli-0.9.
|
|
24
|
-
newrelic_lambda_cli-0.9.
|
|
25
|
-
newrelic_lambda_cli-0.9.
|
|
26
|
-
newrelic_lambda_cli-0.9.
|
|
27
|
-
newrelic_lambda_cli-0.9.
|
|
24
|
+
newrelic_lambda_cli-0.9.15.dist-info/licenses/LICENSE,sha256=uuxDzQm0yfq_tNZX0tQYzsZUVRIF0jm3dBLZUojSYzI,11345
|
|
25
|
+
newrelic_lambda_cli-0.9.15.dist-info/METADATA,sha256=9Vv9rKLxLmmkFA3e_NaJROzu1oJ9lf_fquYcNLMIpXg,29411
|
|
26
|
+
newrelic_lambda_cli-0.9.15.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
|
|
27
|
+
newrelic_lambda_cli-0.9.15.dist-info/entry_points.txt,sha256=iks2k9Y4WNgIecsDzreIvMV9pGCjwwKTf33LKKvl2A8,65
|
|
28
|
+
newrelic_lambda_cli-0.9.15.dist-info/top_level.txt,sha256=dxX2w58VgSUFiPD8C_lFuY-T2C1kjfeY0xi8iTh0r44,20
|
|
29
|
+
newrelic_lambda_cli-0.9.15.dist-info/RECORD,,
|
|
File without changes
|
{newrelic_lambda_cli-0.9.13.dist-info → newrelic_lambda_cli-0.9.15.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{newrelic_lambda_cli-0.9.13.dist-info → newrelic_lambda_cli-0.9.15.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|