Skip to content

Commit 76b8644

Browse files
authored
Add filter alias support to layer/subscription commands (#70)
* Add alias support to layer/subscription commands Closes #69 * Add test for get_aliased_functions
1 parent 5a39228 commit 76b8644

File tree

8 files changed

+163
-24
lines changed

8 files changed

+163
-24
lines changed

README.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ newrelic-lambda layers install \
119119

120120
| Option | Required? | Description |
121121
|--------|-----------|-------------|
122-
| `--function` or `-f` | Yes | The AWS Lambda function name or ARN in which to add a layer. Can provide multiple `--function` arguments. |
122+
| `--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`. |
123+
| `--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. |
123124
| `--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. |
124125
| `--layer-arn` or `-l` | No | Specify a specific layer version ARN to use. This is auto detected by default. |
125126
| `--upgrade` or `-u` | No | Permit upgrade to the latest layer version for this region and runtime. |
@@ -134,7 +135,8 @@ newrelic-lambda layers uninstall --function <name or arn>
134135

135136
| Option | Required? | Description |
136137
|--------|-----------|-------------|
137-
| `--function` or `-f` | Yes | The AWS Lambda function name or ARN in which to remove a layer. Can provide multiple `--function` arguments. |
138+
| `--function` or `-f` | Yes | The AWS Lambda function name or ARN in which to remove a layer. Can provide multiple `--function` arguments. Will also accept `all`, `installed` and `not-installed` similar to `newrelic-lambda functions list`. |
139+
| `--exclude` or `-e` | No | A function name to exclude while uninstalling layers. Can provide multiple `--exclude` arguments. Only checked when `all`, `installed` and `not-installed` are used. See `newrelic-lambda functions list` for function names. |
138140
| `--layer-arn` or `-l` | No | Specify a specific layer version ARN to remove. This is auto detected by default. |
139141
| `--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. |
140142
| `--aws-region` or `-r` | No | The AWS region this function is located. Can use `AWS_DEFAULT_REGION` environment variable. Defaults to AWS session region. |
@@ -170,7 +172,8 @@ newrelic-lambda subscriptions install \--function <name or arn>
170172

171173
| Option | Required? | Description |
172174
|--------|-----------|-------------|
173-
| `--function` or `-f` | Yes | The AWS Lambda function name or ARN in which to add a log subscription. Can provide multiple `--function` arguments. |
175+
| `--function` or `-f` | Yes | The AWS Lambda function name or ARN in which to add a log subscription. Can provide multiple `--function` arguments. Will also accept `all`, `installed` and `not-installed` similar to `newrelic-lambda functions list`. |
176+
| `--exclude` or `-e` | No | A function name to exclude while installing subscriptions. Can provide multiple `--exclude` arguments. Only checked when `all`, `installed` and `not-installed` are used. See `newrelic-lambda functions list` for function names. |
174177
| `--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. |
175178
| `--aws-region` or `-r` | No | The AWS region this function is located. Can use `AWS_DEFAULT_REGION` environment variable. Defaults to AWS session region. |
176179

@@ -182,7 +185,8 @@ newrelic-lambda subscriptions uninstall --function <name or arn>
182185

183186
| Option | Required? | Description |
184187
|--------|-----------|-------------|
185-
| `--function` or `-f` | Yes | The AWS Lambda function name or ARN in which to remove a log subscription. Can provide multiple `--function` arguments. |
188+
| `--function` or `-f` | Yes | The AWS Lambda function name or ARN in which to remove a log subscription. Can provide multiple `--function` arguments. Will also accept `all`, `installed` and `not-installed` similar to `newrelic-lambda functions list`. |
189+
| `--exclude` or `-e` | No | A function name to exclude while uninstalling subscriptions. Can provide multiple `--exclude` arguments. Only checked when `all`, `installed` and `not-installed` are used. See `newrelic-lambda functions list` for function names. |
186190
| `--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. |
187191
| `--aws-region` or `-r` | No | The AWS region this function is located. Can use `AWS_DEFAULT_REGION` environment variable. Defaults to AWS session region. |
188192

newrelic_lambda_cli/cli/functions.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@ def list(aws_profile, aws_region, aws_permissions_check, filter):
3232
"""List AWS Lambda Functions"""
3333
_, rows = shutil.get_terminal_size((80, 50))
3434
session = boto3.Session(profile_name=aws_profile, region_name=aws_region)
35+
3536
if aws_permissions_check:
3637
permissions.ensure_lambda_list_permissions(session)
38+
3739
funcs = functions.list_functions(session, filter)
3840

3941
def _format(funcs, header=False):
@@ -59,4 +61,5 @@ def _format(funcs, header=False):
5961
)
6062
buffer = []
6163
return
64+
6265
click.echo(_format(buffer, header=True))

newrelic_lambda_cli/cli/layers.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from newrelic_lambda_cli import layers, permissions
77
from newrelic_lambda_cli.cliutils import done, failure, success
88
from newrelic_lambda_cli.cli.decorators import add_options, AWS_OPTIONS
9+
from newrelic_lambda_cli.functions import get_aliased_functions
910

1011

1112
@click.group(name="layers")
@@ -40,6 +41,14 @@ def register(group):
4041
multiple=True,
4142
required=True,
4243
)
44+
@click.option(
45+
"excludes",
46+
"--exclude",
47+
"-e",
48+
help="Functions to exclude (if using 'all, 'installed', 'not-installed aliases)",
49+
metavar="<name>",
50+
multiple=True,
51+
)
4352
@click.option(
4453
"--layer-arn",
4554
"-l",
@@ -60,6 +69,7 @@ def install(
6069
aws_region,
6170
aws_permissions_check,
6271
functions,
72+
excludes,
6373
layer_arn,
6474
upgrade,
6575
):
@@ -69,6 +79,8 @@ def install(
6979
if aws_permissions_check:
7080
permissions.ensure_lambda_install_permissions(session)
7181

82+
functions = get_aliased_functions(session, functions, excludes)
83+
7284
install_success = True
7385

7486
for function in functions:
@@ -96,14 +108,24 @@ def install(
96108
multiple=True,
97109
required=True,
98110
)
111+
@click.option(
112+
"excludes",
113+
"--exclude",
114+
"-e",
115+
help="Functions to exclude (if using 'all, 'installed', 'not-installed aliases)",
116+
metavar="<name>",
117+
multiple=True,
118+
)
99119
@click.pass_context
100-
def uninstall(ctx, aws_profile, aws_region, aws_permissions_check, functions):
120+
def uninstall(ctx, aws_profile, aws_region, aws_permissions_check, functions, excludes):
101121
"""Uninstall New Relic AWS Lambda Layers"""
102122
session = boto3.Session(profile_name=aws_profile, region_name=aws_region)
103123

104124
if aws_permissions_check:
105125
permissions.ensure_lambda_uninstall_permissions(session)
106126

127+
functions = get_aliased_functions(session, functions, excludes)
128+
107129
uninstall_success = True
108130

109131
for function in functions:

newrelic_lambda_cli/cli/subscriptions.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from newrelic_lambda_cli import permissions, subscriptions
55
from newrelic_lambda_cli.cliutils import done, failure, success
66
from newrelic_lambda_cli.cli.decorators import add_options, AWS_OPTIONS
7+
from newrelic_lambda_cli.functions import get_aliased_functions
78

89

910
@click.group(name="subscriptions")
@@ -29,13 +30,23 @@ def register(group):
2930
multiple=True,
3031
required=True,
3132
)
32-
def install(aws_profile, aws_region, aws_permissions_check, functions):
33+
@click.option(
34+
"excludes",
35+
"--exclude",
36+
"-e",
37+
help="Functions to exclude (if using 'all, 'installed', 'not-installed aliases)",
38+
metavar="<name>",
39+
multiple=True,
40+
)
41+
def install(aws_profile, aws_region, aws_permissions_check, functions, excludes):
3342
"""Install New Relic AWS Lambda Log Subscriptions"""
3443
session = boto3.Session(profile_name=aws_profile, region_name=aws_region)
3544

3645
if aws_permissions_check:
3746
permissions.ensure_subscription_install_permissions(session)
3847

48+
functions = get_aliased_functions(session, functions, excludes)
49+
3950
install_success = True
4051

4152
for function in functions:
@@ -61,13 +72,23 @@ def install(aws_profile, aws_region, aws_permissions_check, functions):
6172
multiple=True,
6273
required=True,
6374
)
64-
def uninstall(aws_profile, aws_region, aws_permissions_check, functions):
75+
@click.option(
76+
"excludes",
77+
"--exclude",
78+
"-e",
79+
help="Functions to exclude (if using 'all, 'installed', 'not-installed aliases)",
80+
metavar="<name>",
81+
multiple=True,
82+
)
83+
def uninstall(aws_profile, aws_region, aws_permissions_check, functions, excludes):
6584
"""Uninstall New Relic AWS Lambda Log Subscriptions"""
6685
session = boto3.Session(profile_name=aws_profile, region_name=aws_region)
6786

6887
if aws_permissions_check:
6988
permissions.ensure_subscription_uninstall_permissions(session)
7089

90+
functions = get_aliased_functions(session, functions, excludes)
91+
7192
uninstall_success = True
7293

7394
for function in functions:

newrelic_lambda_cli/functions.py

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,27 @@
44
from newrelic_lambda_cli import utils
55

66

7-
def list_functions(session, filter_choice):
7+
def list_functions(session, filter=None):
88
client = session.client("lambda")
99

10-
# set all if the filter_choice is "all" or there is no filter_choice active.
11-
all = filter_choice == "all" or not filter_choice
10+
all = filter == "all" or not filter
1211

1312
pager = client.get_paginator("list_functions")
14-
for func_resp in pager.paginate():
15-
funcs = func_resp.get("Functions", [])
16-
17-
for f in funcs:
18-
f.setdefault("x-new-relic-enabled", False)
19-
for layer in f.get("Layers", []):
13+
for res in pager.paginate():
14+
funcs = res.get("Functions", [])
15+
for func in funcs:
16+
func.setdefault("x-new-relic-enabled", False)
17+
for layer in func.get("Layers", []):
2018
if layer.get("Arn", "").startswith(
2119
utils.get_arn_prefix(session.region_name)
2220
):
23-
f["x-new-relic-enabled"] = True
21+
func["x-new-relic-enabled"] = True
2422
if all:
25-
yield f
26-
elif filter_choice == "installed" and f["x-new-relic-enabled"]:
27-
yield f
28-
elif filter_choice == "not_installed" and not f["x-new-relic-enabled"]:
29-
yield f
23+
yield func
24+
elif filter == "installed" and func["x-new-relic-enabled"]:
25+
yield func
26+
elif filter == "not-installed" and not func["x-new-relic-enabled"]:
27+
yield func
3028

3129

3230
def get_function(session, function_name):
@@ -42,3 +40,37 @@ def get_function(session, function_name):
4240
):
4341
return None
4442
raise click.UsageError(str(e))
43+
44+
45+
def get_aliased_functions(session, functions, excludes):
46+
"""
47+
Retrieves functions for 'all, 'installed' and 'not-installed' aliases and appends
48+
them to existing list of functions.
49+
"""
50+
aliases = [
51+
function.lower()
52+
for function in functions
53+
if function.lower() in ("all", "installed", "not-installed")
54+
]
55+
56+
functions = [
57+
function
58+
for function in functions
59+
if function.lower()
60+
not in ("all", "installed", "not-installed", "newrelic-log-ingestion")
61+
and function not in excludes
62+
]
63+
64+
if not aliases:
65+
return utils.unique(functions)
66+
67+
for alias in set(aliases):
68+
for function in list_functions(session, alias):
69+
if (
70+
"FunctionName" in function
71+
and "newrelic-log-ingestion" not in function["FunctionName"]
72+
and function["FunctionName"] not in excludes
73+
):
74+
functions.append(function["FunctionName"])
75+
76+
return utils.unique(functions)

newrelic_lambda_cli/layers.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import requests
44

55
from newrelic_lambda_cli import utils
6-
from newrelic_lambda_cli.cliutils import failure
6+
from newrelic_lambda_cli.cliutils import failure, success
77
from newrelic_lambda_cli.functions import get_function
88

99

@@ -22,11 +22,12 @@ def _add_new_relic(config, region, layer_arn, account_id, allow_upgrade):
2222
"Unsupported Lambda runtime for '%s': %s"
2323
% (config["Configuration"]["FunctionArn"], runtime)
2424
)
25+
return
2526

2627
handler = config["Configuration"]["Handler"]
2728
runtime_handler = utils.RUNTIME_CONFIG.get(runtime, {}).get("Handler")
2829
if not allow_upgrade and handler == runtime_handler:
29-
failure(
30+
success(
3031
"Already installed on function '%s'. Pass --upgrade (or -u) to allow "
3132
"upgrade or reinstall to latest layer version."
3233
% config["Configuration"]["FunctionArn"]
@@ -46,6 +47,13 @@ def _add_new_relic(config, region, layer_arn, account_id, allow_upgrade):
4647
# discover compatible layers...
4748
available_layers = index(region, runtime)
4849

50+
if not available_layers:
51+
failure(
52+
"No Lambda layers published for '%s' runtime: %s"
53+
% (config["Configuration"]["FunctionArn"], runtime)
54+
)
55+
return
56+
4957
# TODO: MAke this a layer selection screen
5058
if len(available_layers) > 1:
5159
message = ["Discovered layers for runtime (%s)" % runtime]

newrelic_lambda_cli/utils.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,13 @@ def validate_aws_profile(ctx, param, value):
8282
raise click.BadParameter(e.fmt)
8383
else:
8484
return value
85+
86+
87+
def unique(seq):
88+
"""Returns unique values in a sequence while preserving order"""
89+
seen = set()
90+
# Why assign seen.add to seen_add instead of just calling seen.add?
91+
# Python is a dynamic language, and resolving seen.add each iteration is more costly
92+
# than resolving a local variable.
93+
seen_add = seen.add
94+
return [x for x in seq if not (x in seen or seen_add(x))]

tests/test_functions.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import boto3
2+
import mock
3+
from moto import mock_lambda
4+
from newrelic_lambda_cli.functions import get_aliased_functions
5+
6+
7+
@mock_lambda
8+
@mock.patch("newrelic_lambda_cli.functions.list_functions", autospec=True)
9+
def test_get_aliased_functions(mock_list_functions, aws_credentials):
10+
"""
11+
Asserts that get_aliased_functions adds functions matching one of the alias filters
12+
"""
13+
session = boto3.Session(region_name="us-east-1")
14+
15+
assert get_aliased_functions(session, [], []) == []
16+
assert get_aliased_functions(session, ["foo"], []) == ["foo"]
17+
assert get_aliased_functions(session, ["foo", "bar"], ["bar"]) == ["foo"]
18+
assert get_aliased_functions(session, ["foo", "bar", "baz"], ["bar"]) == [
19+
"foo",
20+
"baz",
21+
]
22+
23+
mock_list_functions.return_value = [{"FunctionName": "aliased-func"}]
24+
assert get_aliased_functions(session, ["foo", "bar", "all"], []) == [
25+
"foo",
26+
"bar",
27+
"aliased-func",
28+
]
29+
30+
mock_list_functions.return_value = [
31+
{"FunctionName": "aliased-func"},
32+
{"FunctionName": "ignored-func"},
33+
{"FunctionName": "newrelic-log-ingestion"},
34+
]
35+
assert get_aliased_functions(session, ["foo", "bar", "all"], ["ignored-func"]) == [
36+
"foo",
37+
"bar",
38+
"aliased-func",
39+
]

0 commit comments

Comments
 (0)