aws-lambda-layer-cli 1.4.1
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.
- package/LICENSE +21 -0
- package/README.md +393 -0
- package/bin/aws-lambda-layer.js +89 -0
- package/completion/aws-lambda-layer-completion.bash +101 -0
- package/completion/aws-lambda-layer-completion.zsh +110 -0
- package/package.json +31 -0
- package/scripts/aws-lambda-layer +821 -0
- package/scripts/build_pypi.sh +37 -0
- package/scripts/create_nodejs_layer.sh +459 -0
- package/scripts/create_python_layer.sh +465 -0
- package/scripts/install.js +34 -0
- package/scripts/install.ps1 +663 -0
- package/scripts/install.sh +107 -0
- package/scripts/pypi_resources/__init__.py +9 -0
- package/scripts/pypi_resources/cli.py +83 -0
- package/scripts/sync_version.js +24 -0
- package/scripts/uninstall.ps1 +180 -0
- package/scripts/uninstall.sh +58 -0
|
@@ -0,0 +1,821 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# AWS Lambda Layer CLI Tool
|
|
4
|
+
# Install: sudo ./scripts/install.sh
|
|
5
|
+
# Usage: aws-lambda-layer zip --nodejs express,axios -n my-layer
|
|
6
|
+
|
|
7
|
+
set -e
|
|
8
|
+
set -u
|
|
9
|
+
|
|
10
|
+
# Colors for output
|
|
11
|
+
RED='\033[0;31m'
|
|
12
|
+
GREEN='\033[0;32m'
|
|
13
|
+
YELLOW='\033[0;33m'
|
|
14
|
+
BLUE='\033[0;34m'
|
|
15
|
+
MAGENTA='\033[0;35m'
|
|
16
|
+
CYAN='\033[0;36m'
|
|
17
|
+
WHITE='\033[0;37m'
|
|
18
|
+
NC='\033[0m' # No Color
|
|
19
|
+
|
|
20
|
+
# Styles
|
|
21
|
+
BOLD='\033[1m'
|
|
22
|
+
ITALIC='\033[3m'
|
|
23
|
+
UNDERLINE='\033[4m'
|
|
24
|
+
REVERSE='\033[7m'
|
|
25
|
+
STRIKETHROUGH='\033[9m'
|
|
26
|
+
|
|
27
|
+
# Paths
|
|
28
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
29
|
+
INSTALL_DIR="/usr/local/lib/aws-lambda-layer"
|
|
30
|
+
|
|
31
|
+
# Determine script locations (prefer relative to script, fallback to global install)
|
|
32
|
+
if [ -f "$SCRIPT_DIR/create_nodejs_layer.sh" ]; then
|
|
33
|
+
NODE_SCRIPT="$SCRIPT_DIR/create_nodejs_layer.sh"
|
|
34
|
+
else
|
|
35
|
+
NODE_SCRIPT="$INSTALL_DIR/create_nodejs_layer.sh"
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
if [ -f "$SCRIPT_DIR/create_python_layer.sh" ]; then
|
|
39
|
+
PYTHON_SCRIPT="$SCRIPT_DIR/create_python_layer.sh"
|
|
40
|
+
else
|
|
41
|
+
PYTHON_SCRIPT="$INSTALL_DIR/create_python_layer.sh"
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
BIN_DIR="/usr/local/bin"
|
|
45
|
+
COMPLETION_DIR="/etc/bash_completion.d"
|
|
46
|
+
|
|
47
|
+
# Show help
|
|
48
|
+
show_help() {
|
|
49
|
+
local version_file="$SCRIPT_DIR/VERSION.txt"
|
|
50
|
+
local version="1.4.1"
|
|
51
|
+
if [ -f "$version_file" ]; then
|
|
52
|
+
version=$(cat "$version_file")
|
|
53
|
+
fi
|
|
54
|
+
printf "${BLUE}${BOLD}AWS Lambda Layer CLI Tool - ${version}${NC}\n\n"
|
|
55
|
+
|
|
56
|
+
printf "${BLUE}Usage:${NC}\n"
|
|
57
|
+
printf " aws-lambda-layer ${GREEN}zip${NC} ${YELLOW}--nodejs${NC} <packages> [options]\n"
|
|
58
|
+
printf " aws-lambda-layer ${GREEN}zip${NC} ${YELLOW}--python${NC} <packages> [options]\n"
|
|
59
|
+
printf " aws-lambda-layer ${GREEN}publish${NC} ${YELLOW}--nodejs${NC} <packages> [options]\n"
|
|
60
|
+
printf " aws-lambda-layer ${GREEN}publish${NC} ${YELLOW}--python${NC} <packages> [options]\n"
|
|
61
|
+
printf " aws-lambda-layer ${GREEN}help${NC}\n"
|
|
62
|
+
printf " aws-lambda-layer [options]\n\n"
|
|
63
|
+
|
|
64
|
+
printf "${BLUE}Commands:${NC}\n"
|
|
65
|
+
printf " ${GREEN}zip${NC} Create and package a Lambda layer as zip file\n"
|
|
66
|
+
printf " ${GREEN}publish${NC} Create and publish a Lambda layer to AWS (uses IAM credentials)\n"
|
|
67
|
+
printf " ${GREEN}help${NC} Show this help message\n\n"
|
|
68
|
+
|
|
69
|
+
printf "${BLUE}Runtime Options:${NC}\n"
|
|
70
|
+
printf " ${YELLOW}--nodejs, --node, -n${NC} Create a Node.js Lambda layer\n"
|
|
71
|
+
printf " ${YELLOW}--python, --py, -p${NC} Create a Python Lambda layer\n"
|
|
72
|
+
printf " ${YELLOW}--runtime=RUNTIME${NC} Specify runtime (nodejs or python)\n"
|
|
73
|
+
printf "${BLUE}Arguments:${NC}\n"
|
|
74
|
+
printf " <packages> Comma-separated list of packages with optional versions (required)\n"
|
|
75
|
+
|
|
76
|
+
printf "${BLUE}Common Options:${NC}\n"
|
|
77
|
+
printf " ${YELLOW}--name${NC} Name for the output zip file / layer name\n"
|
|
78
|
+
printf " ${YELLOW}--description${NC} Description for the layer (publish command only)\n"
|
|
79
|
+
printf "${BLUE}AWS Options (publish command only):${NC}\n"
|
|
80
|
+
printf " ${YELLOW}--profile${NC} AWS CLI profile to use (default: default profile)\n"
|
|
81
|
+
printf " ${YELLOW}--region${NC} AWS region (e.g., us-east-1, ap-east-1)\n"
|
|
82
|
+
printf "${BLUE}Other Options:${NC}\n"
|
|
83
|
+
printf " ${YELLOW}--node-version${NC} Node.js version (default: 24)\n"
|
|
84
|
+
printf " ${YELLOW}--python-version${NC} Python version (default: 3.14)\n"
|
|
85
|
+
printf " ${YELLOW}--no-uv${NC} Use pip/venv instead of uv\n\n"
|
|
86
|
+
|
|
87
|
+
printf "${MAGENTA}${UNDERLINE}Package Version Examples:${NC}\n"
|
|
88
|
+
printf " Node.js: express@^4.0.0, lodash@~4.17.0, axios@>=1.6.0\n"
|
|
89
|
+
printf " Python: numpy==1.26.0, pandas>=2.1.0, requests~=2.31.0\n\n"
|
|
90
|
+
|
|
91
|
+
printf "${MAGENTA}${UNDERLINE}Examples:${NC}\n"
|
|
92
|
+
printf " aws-lambda-layer ${GREEN}zip${NC} ${YELLOW}--nodejs${NC} express,lodash\n"
|
|
93
|
+
printf " aws-lambda-layer ${GREEN}zip${NC} ${YELLOW}--python${NC} \"numpy==1.26.0,pandas>=2.1.0\"\n"
|
|
94
|
+
printf " aws-lambda-layer ${GREEN}publish${NC} ${YELLOW}--nodejs${NC} express@4.18.2 ${YELLOW}--description${NC} \"Express layer\"\n"
|
|
95
|
+
printf " aws-lambda-layer ${GREEN}publish${NC} ${YELLOW}--python${NC} numpy==1.26.0 ${YELLOW}--description${NC} \"NumPy layer\"\n"
|
|
96
|
+
printf " aws-lambda-layer ${GREEN}publish${NC} ${YELLOW}--nodejs${NC} date-fns,uuid ${YELLOW}--name${NC} utility-layer ${YELLOW}--description${NC} \"Utility packages\"\n"
|
|
97
|
+
printf " aws-lambda-layer ${GREEN}publish${NC} ${YELLOW}--nodejs${NC} express@4.18.2 ${YELLOW}--profile${NC} production ${YELLOW}--region${NC} ap-east-1 ${YELLOW}--description${NC} \"Express layer\"\n\n"
|
|
98
|
+
|
|
99
|
+
printf "${YELLOW}${BOLD}Requirements for publish command:${NC}\n"
|
|
100
|
+
printf " - AWS CLI installed and configured\n"
|
|
101
|
+
printf " - IAM credentials with lambda:PublishLayerVersion permission\n"
|
|
102
|
+
printf " - Proper AWS region configuration\n"
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# Show version
|
|
106
|
+
show_version() {
|
|
107
|
+
local version_file="$SCRIPT_DIR/VERSION.txt"
|
|
108
|
+
if [ -f "$version_file" ]; then
|
|
109
|
+
local version=$(cat "$version_file")
|
|
110
|
+
echo "v$version"
|
|
111
|
+
else
|
|
112
|
+
# Fallback if VERSION file is missing (e.g. during development or if moved)
|
|
113
|
+
echo "v1.4.1"
|
|
114
|
+
fi
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# Check if AWS CLI is installed and configured
|
|
118
|
+
check_aws_cli() {
|
|
119
|
+
local profile="$1"
|
|
120
|
+
local region="$2"
|
|
121
|
+
local aws_opts=()
|
|
122
|
+
|
|
123
|
+
if ! command -v aws &> /dev/null; then
|
|
124
|
+
printf "${RED}Error: AWS CLI is not installed${NC}\n"
|
|
125
|
+
printf "Please install AWS CLI first:\n"
|
|
126
|
+
printf " https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html\n"
|
|
127
|
+
return 1
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
# Build AWS CLI options
|
|
131
|
+
if [ -n "$profile" ]; then
|
|
132
|
+
aws_opts+=("--profile" "$profile")
|
|
133
|
+
fi
|
|
134
|
+
if [ -n "$region" ]; then
|
|
135
|
+
aws_opts+=("--region" "$region")
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
# Check if AWS credentials are configured
|
|
139
|
+
if ! aws ${aws_opts[@]+"${aws_opts[@]}"} sts get-caller-identity &> /dev/null; then
|
|
140
|
+
printf "${RED}Error: AWS credentials not configured or invalid${NC}\n"
|
|
141
|
+
if [ -n "$profile" ]; then
|
|
142
|
+
printf "Profile: $profile\n"
|
|
143
|
+
fi
|
|
144
|
+
printf "Please configure AWS credentials:\n"
|
|
145
|
+
printf " aws configure"
|
|
146
|
+
if [ -n "$profile" ]; then
|
|
147
|
+
printf " --profile $profile"
|
|
148
|
+
fi
|
|
149
|
+
printf "\n"
|
|
150
|
+
printf " or set AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION environment variables\n"
|
|
151
|
+
return 1
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
return 0
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
# Get AWS account info
|
|
158
|
+
get_aws_account_info() {
|
|
159
|
+
local profile="$1"
|
|
160
|
+
local region="$2"
|
|
161
|
+
local aws_opts=()
|
|
162
|
+
|
|
163
|
+
# Build AWS CLI options
|
|
164
|
+
if [ -n "$profile" ]; then
|
|
165
|
+
aws_opts+=("--profile" "$profile")
|
|
166
|
+
fi
|
|
167
|
+
if [ -n "$region" ]; then
|
|
168
|
+
aws_opts+=("--region" "$region")
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
printf "${CYAN}Checking AWS account...${NC}\n"
|
|
172
|
+
|
|
173
|
+
# Get account ID
|
|
174
|
+
local account_id
|
|
175
|
+
account_id=$(aws ${aws_opts[@]+"${aws_opts[@]}"} sts get-caller-identity --query Account --output text 2>/dev/null)
|
|
176
|
+
if [ $? -eq 0 ]; then
|
|
177
|
+
printf " Account ID: ${GREEN}$account_id${NC}\n"
|
|
178
|
+
else
|
|
179
|
+
printf " ${YELLOW}Could not retrieve account ID${NC}\n"
|
|
180
|
+
account_id="UNKNOWN"
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
# Display profile if specified
|
|
184
|
+
if [ -n "$profile" ]; then
|
|
185
|
+
printf " Profile: ${GREEN}$profile${NC}\n"
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
# Get account aliases
|
|
189
|
+
local aliases
|
|
190
|
+
aliases=$(aws ${aws_opts[@]+"${aws_opts[@]}"} iam list-account-aliases --query 'AccountAliases' --output text 2>/dev/null)
|
|
191
|
+
if [ $? -eq 0 ] && [ -n "$aliases" ]; then
|
|
192
|
+
printf " Account Aliases: ${GREEN}$aliases${NC}\n"
|
|
193
|
+
else
|
|
194
|
+
printf " ${YELLOW}No account aliases found${NC}\n"
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
# Get region
|
|
198
|
+
local display_region="$region"
|
|
199
|
+
if [ -z "$display_region" ]; then
|
|
200
|
+
if [ -n "$profile" ]; then
|
|
201
|
+
display_region=$(aws ${aws_opts[@]+"${aws_opts[@]}"} configure get region 2>/dev/null)
|
|
202
|
+
else
|
|
203
|
+
display_region=$(aws configure get region 2>/dev/null || echo "$AWS_DEFAULT_REGION")
|
|
204
|
+
fi
|
|
205
|
+
fi
|
|
206
|
+
if [ -n "$display_region" ]; then
|
|
207
|
+
printf " Region: ${GREEN}$display_region${NC}\n"
|
|
208
|
+
else
|
|
209
|
+
printf " ${YELLOW}Region not configured${NC}\n"
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
printf "\n"
|
|
213
|
+
printf "${YELLOW}⚠️ This layer will be published to the AWS account above.${NC}\n"
|
|
214
|
+
|
|
215
|
+
# Prompt user for confirmation
|
|
216
|
+
read -p "Do you want to proceed? [Y/n]: " response
|
|
217
|
+
response=${response:-Y} # Default to Y if empty
|
|
218
|
+
|
|
219
|
+
case "$response" in
|
|
220
|
+
[Yy]|[Yy][Ee][Ss])
|
|
221
|
+
printf "${GREEN}Proceeding with layer publication...${NC}\n\n"
|
|
222
|
+
;;
|
|
223
|
+
*)
|
|
224
|
+
printf "${RED}Publication cancelled.${NC}\n"
|
|
225
|
+
exit 0
|
|
226
|
+
;;
|
|
227
|
+
esac
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
# Determine compatible runtimes for AWS
|
|
231
|
+
get_compatible_runtimes() {
|
|
232
|
+
local runtime="$1"
|
|
233
|
+
local version="$2"
|
|
234
|
+
|
|
235
|
+
case "$runtime" in
|
|
236
|
+
nodejs)
|
|
237
|
+
echo "nodejs${version}.x"
|
|
238
|
+
;;
|
|
239
|
+
python)
|
|
240
|
+
# AWS Lambda uses format like python3.14, not python3.14.x
|
|
241
|
+
echo "python${version}"
|
|
242
|
+
;;
|
|
243
|
+
*)
|
|
244
|
+
echo ""
|
|
245
|
+
;;
|
|
246
|
+
esac
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
# Check dependencies
|
|
250
|
+
check_dependencies() {
|
|
251
|
+
local runtime="$1"
|
|
252
|
+
|
|
253
|
+
# Check for zip
|
|
254
|
+
if ! command -v zip &> /dev/null; then
|
|
255
|
+
printf "${RED}Error: 'zip' command is not installed${NC}\n"
|
|
256
|
+
printf "Please install zip first.\n"
|
|
257
|
+
return 1
|
|
258
|
+
fi
|
|
259
|
+
|
|
260
|
+
# Check runtime
|
|
261
|
+
if [ "$runtime" = "nodejs" ]; then
|
|
262
|
+
if ! command -v node &> /dev/null; then
|
|
263
|
+
printf "${RED}Error: 'node' command is not installed${NC}\n"
|
|
264
|
+
printf "Please install Node.js first.\n"
|
|
265
|
+
return 1
|
|
266
|
+
fi
|
|
267
|
+
elif [ "$runtime" = "python" ]; then
|
|
268
|
+
if ! command -v python3 &> /dev/null && ! command -v python &> /dev/null; then
|
|
269
|
+
printf "${RED}Error: 'python' command is not installed${NC}\n"
|
|
270
|
+
printf "Please install Python first.\n"
|
|
271
|
+
return 1
|
|
272
|
+
fi
|
|
273
|
+
fi
|
|
274
|
+
return 0
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
# Zip command handler - creates local zip files
|
|
278
|
+
handle_zip() {
|
|
279
|
+
local runtime=""
|
|
280
|
+
local packages=""
|
|
281
|
+
|
|
282
|
+
# Parse runtime flag
|
|
283
|
+
case "${1:-}" in
|
|
284
|
+
--nodejs|--node|-n)
|
|
285
|
+
runtime="nodejs"
|
|
286
|
+
shift
|
|
287
|
+
;;
|
|
288
|
+
--python|--py|-p)
|
|
289
|
+
runtime="python"
|
|
290
|
+
shift
|
|
291
|
+
;;
|
|
292
|
+
--runtime=*)
|
|
293
|
+
runtime="${1#*=}"
|
|
294
|
+
case "$runtime" in
|
|
295
|
+
nodejs)
|
|
296
|
+
shift
|
|
297
|
+
;;
|
|
298
|
+
python)
|
|
299
|
+
shift
|
|
300
|
+
;;
|
|
301
|
+
*)
|
|
302
|
+
printf "${RED}Error: Invalid runtime specified: $runtime${NC}\n"
|
|
303
|
+
printf "Valid runtimes: nodejs, python\n"
|
|
304
|
+
exit 1
|
|
305
|
+
;;
|
|
306
|
+
esac
|
|
307
|
+
;;
|
|
308
|
+
--runtime)
|
|
309
|
+
if [[ -n "${2:-}" && "${2:-}" != -* ]]; then
|
|
310
|
+
runtime="$2"
|
|
311
|
+
shift 2
|
|
312
|
+
case "$runtime" in
|
|
313
|
+
nodejs|python)
|
|
314
|
+
;;
|
|
315
|
+
*)
|
|
316
|
+
printf "${RED}Error: Invalid runtime specified: $runtime${NC}\n"
|
|
317
|
+
printf "Valid runtimes: nodejs, python\n"
|
|
318
|
+
exit 1
|
|
319
|
+
;;
|
|
320
|
+
esac
|
|
321
|
+
else
|
|
322
|
+
printf "${RED}Error: --runtime requires an argument${NC}\n"
|
|
323
|
+
printf "Example: --runtime=nodejs or --runtime python\n"
|
|
324
|
+
exit 1
|
|
325
|
+
fi
|
|
326
|
+
;;
|
|
327
|
+
*)
|
|
328
|
+
printf "${RED}Error: Missing or invalid runtime specification${NC}\n"
|
|
329
|
+
printf "Use --nodejs, --node, -n for Node.js or --python, --py, -p for Python\n"
|
|
330
|
+
printf "Or use --runtime=nodejs or --runtime=python\n"
|
|
331
|
+
exit 1
|
|
332
|
+
;;
|
|
333
|
+
esac
|
|
334
|
+
|
|
335
|
+
# Get packages as first positional argument
|
|
336
|
+
if [[ -n "${1:-}" && "${1:-}" != -* ]]; then
|
|
337
|
+
packages="$1"
|
|
338
|
+
shift
|
|
339
|
+
else
|
|
340
|
+
printf "${RED}Error: Missing packages argument${NC}\n"
|
|
341
|
+
printf "Usage: aws-lambda-layer zip --nodejs <packages> [options]\n"
|
|
342
|
+
printf "Example: aws-lambda-layer zip --nodejs express,axios\n"
|
|
343
|
+
exit 1
|
|
344
|
+
fi
|
|
345
|
+
|
|
346
|
+
# Check dependencies
|
|
347
|
+
if ! check_dependencies "$runtime"; then
|
|
348
|
+
exit 1
|
|
349
|
+
fi
|
|
350
|
+
|
|
351
|
+
# Create output directory if it doesn't exist
|
|
352
|
+
local output_dir="output"
|
|
353
|
+
mkdir -p "$output_dir"
|
|
354
|
+
|
|
355
|
+
# Save current directory and change to output directory
|
|
356
|
+
local current_dir=$(pwd)
|
|
357
|
+
cd "$output_dir"
|
|
358
|
+
|
|
359
|
+
# Pass arguments to the appropriate script with -i
|
|
360
|
+
if [ "$runtime" = "nodejs" ]; then
|
|
361
|
+
if [ ! -f "$NODE_SCRIPT" ]; then
|
|
362
|
+
cd "$current_dir"
|
|
363
|
+
printf "${RED}Error: Node.js script not found at $NODE_SCRIPT${NC}\n"
|
|
364
|
+
printf "Please run scripts/install.sh first\n"
|
|
365
|
+
exit 1
|
|
366
|
+
fi
|
|
367
|
+
printf "${BLUE}Creating Node.js Lambda layer (local zip)...${NC}\n"
|
|
368
|
+
bash "$NODE_SCRIPT" -i "$packages" "$@"
|
|
369
|
+
local exit_code=$?
|
|
370
|
+
cd "$current_dir"
|
|
371
|
+
exit $exit_code
|
|
372
|
+
elif [ "$runtime" = "python" ]; then
|
|
373
|
+
if [ ! -f "$PYTHON_SCRIPT" ]; then
|
|
374
|
+
cd "$current_dir"
|
|
375
|
+
printf "${RED}Error: Python script not found at $PYTHON_SCRIPT${NC}\n"
|
|
376
|
+
printf "Please run scripts/install.sh first\n"
|
|
377
|
+
exit 1
|
|
378
|
+
fi
|
|
379
|
+
printf "${BLUE}Creating Python Lambda layer (local zip)...${NC}\n"
|
|
380
|
+
bash "$PYTHON_SCRIPT" -i "$packages" "$@"
|
|
381
|
+
local exit_code=$?
|
|
382
|
+
cd "$current_dir"
|
|
383
|
+
exit $exit_code
|
|
384
|
+
fi
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
# Publish command handler - publishes layer directly to AWS
|
|
388
|
+
handle_publish() {
|
|
389
|
+
local runtime=""
|
|
390
|
+
local description=""
|
|
391
|
+
local layer_name=""
|
|
392
|
+
local packages=""
|
|
393
|
+
local profile=""
|
|
394
|
+
local region=""
|
|
395
|
+
local args=()
|
|
396
|
+
|
|
397
|
+
# Parse publish-specific arguments first
|
|
398
|
+
local new_args=()
|
|
399
|
+
while [[ $# -gt 0 ]]; do
|
|
400
|
+
case "$1" in
|
|
401
|
+
--description)
|
|
402
|
+
if [[ -n "${2:-}" && "${2:-}" != -* ]]; then
|
|
403
|
+
description="$2"
|
|
404
|
+
shift 2
|
|
405
|
+
else
|
|
406
|
+
printf "${RED}Error: --description requires an argument${NC}\n"
|
|
407
|
+
exit 1
|
|
408
|
+
fi
|
|
409
|
+
;;
|
|
410
|
+
--description=*)
|
|
411
|
+
description="${1#*=}"
|
|
412
|
+
shift
|
|
413
|
+
;;
|
|
414
|
+
--name)
|
|
415
|
+
if [[ -n "${2:-}" && "${2:-}" != -* ]]; then
|
|
416
|
+
layer_name="$2"
|
|
417
|
+
shift 2
|
|
418
|
+
else
|
|
419
|
+
printf "${RED}Error: --name requires an argument${NC}\n"
|
|
420
|
+
exit 1
|
|
421
|
+
fi
|
|
422
|
+
;;
|
|
423
|
+
--name=*)
|
|
424
|
+
layer_name="${1#*=}"
|
|
425
|
+
shift
|
|
426
|
+
;;
|
|
427
|
+
--profile)
|
|
428
|
+
if [[ -n "${2:-}" && "${2:-}" != -* ]]; then
|
|
429
|
+
profile="$2"
|
|
430
|
+
shift 2
|
|
431
|
+
else
|
|
432
|
+
printf "${RED}Error: --profile requires an argument${NC}\n"
|
|
433
|
+
exit 1
|
|
434
|
+
fi
|
|
435
|
+
;;
|
|
436
|
+
--profile=*)
|
|
437
|
+
profile="${1#*=}"
|
|
438
|
+
shift
|
|
439
|
+
;;
|
|
440
|
+
--region)
|
|
441
|
+
if [[ -n "${2:-}" && "${2:-}" != -* ]]; then
|
|
442
|
+
region="$2"
|
|
443
|
+
shift 2
|
|
444
|
+
else
|
|
445
|
+
printf "${RED}Error: --region requires an argument${NC}\n"
|
|
446
|
+
exit 1
|
|
447
|
+
fi
|
|
448
|
+
;;
|
|
449
|
+
--region=*)
|
|
450
|
+
region="${1#*=}"
|
|
451
|
+
shift
|
|
452
|
+
;;
|
|
453
|
+
*)
|
|
454
|
+
new_args+=("$1")
|
|
455
|
+
shift
|
|
456
|
+
;;
|
|
457
|
+
esac
|
|
458
|
+
done
|
|
459
|
+
|
|
460
|
+
# Parse runtime flag from remaining arguments
|
|
461
|
+
set -- "${new_args[@]}"
|
|
462
|
+
case "${1:-}" in
|
|
463
|
+
--nodejs|--node|-n)
|
|
464
|
+
runtime="nodejs"
|
|
465
|
+
shift
|
|
466
|
+
;;
|
|
467
|
+
--python|--py|-p)
|
|
468
|
+
runtime="python"
|
|
469
|
+
shift
|
|
470
|
+
;;
|
|
471
|
+
--runtime=*)
|
|
472
|
+
runtime="${1#*=}"
|
|
473
|
+
case "$runtime" in
|
|
474
|
+
nodejs)
|
|
475
|
+
shift
|
|
476
|
+
;;
|
|
477
|
+
python)
|
|
478
|
+
shift
|
|
479
|
+
;;
|
|
480
|
+
*)
|
|
481
|
+
printf "${RED}Error: Invalid runtime specified: $runtime${NC}\n"
|
|
482
|
+
printf "Valid runtimes: nodejs, python\n"
|
|
483
|
+
exit 1
|
|
484
|
+
;;
|
|
485
|
+
esac
|
|
486
|
+
;;
|
|
487
|
+
--runtime)
|
|
488
|
+
if [[ -n "${2:-}" && "${2:-}" != -* ]]; then
|
|
489
|
+
runtime="$2"
|
|
490
|
+
shift 2
|
|
491
|
+
case "$runtime" in
|
|
492
|
+
nodejs|python)
|
|
493
|
+
;;
|
|
494
|
+
*)
|
|
495
|
+
printf "${RED}Error: Invalid runtime specified: $runtime${NC}\n"
|
|
496
|
+
printf "Valid runtimes: nodejs, python\n"
|
|
497
|
+
exit 1
|
|
498
|
+
;;
|
|
499
|
+
esac
|
|
500
|
+
else
|
|
501
|
+
printf "${RED}Error: --runtime requires an argument${NC}\n"
|
|
502
|
+
printf "Example: --runtime=nodejs or --runtime python\n"
|
|
503
|
+
exit 1
|
|
504
|
+
fi
|
|
505
|
+
;;
|
|
506
|
+
*)
|
|
507
|
+
printf "${RED}Error: Missing or invalid runtime specification${NC}\n"
|
|
508
|
+
printf "Use --nodejs, --node, -n for Node.js or --python, --py, -p for Python\n"
|
|
509
|
+
printf "Or use --runtime=nodejs or --runtime=python\n"
|
|
510
|
+
exit 1
|
|
511
|
+
;;
|
|
512
|
+
esac
|
|
513
|
+
|
|
514
|
+
# Get packages as first positional argument
|
|
515
|
+
if [[ -n "${1:-}" && "${1:-}" != -* ]]; then
|
|
516
|
+
packages="$1"
|
|
517
|
+
shift
|
|
518
|
+
else
|
|
519
|
+
printf "${RED}Error: Missing packages argument${NC}\n"
|
|
520
|
+
printf "Usage: aws-lambda-layer publish --nodejs <packages> [options]\n"
|
|
521
|
+
printf "Example: aws-lambda-layer publish --nodejs express,axios --description \"My layer\"\n"
|
|
522
|
+
exit 1
|
|
523
|
+
fi
|
|
524
|
+
|
|
525
|
+
# Check dependencies
|
|
526
|
+
if ! check_dependencies "$runtime"; then
|
|
527
|
+
exit 1
|
|
528
|
+
fi
|
|
529
|
+
|
|
530
|
+
# Check AWS CLI and credentials
|
|
531
|
+
if ! check_aws_cli "$profile" "$region"; then
|
|
532
|
+
exit 1
|
|
533
|
+
fi
|
|
534
|
+
|
|
535
|
+
# Show AWS account info and wait for confirmation
|
|
536
|
+
get_aws_account_info "$profile" "$region"
|
|
537
|
+
|
|
538
|
+
# Create output directory if it doesn't exist
|
|
539
|
+
local output_dir="output"
|
|
540
|
+
mkdir -p "$output_dir"
|
|
541
|
+
|
|
542
|
+
# Save current directory and change to output directory
|
|
543
|
+
local current_dir=$(pwd)
|
|
544
|
+
cd "$output_dir"
|
|
545
|
+
|
|
546
|
+
printf "${BLUE}Building Lambda layer in output directory...${NC}\n"
|
|
547
|
+
|
|
548
|
+
# Build the layer using the appropriate script
|
|
549
|
+
local build_args=("-i" "$packages")
|
|
550
|
+
if [ -n "$layer_name" ]; then
|
|
551
|
+
build_args+=("--name" "${layer_name}.zip")
|
|
552
|
+
fi
|
|
553
|
+
|
|
554
|
+
# Add remaining arguments
|
|
555
|
+
for arg in "$@"; do
|
|
556
|
+
build_args+=("$arg")
|
|
557
|
+
done
|
|
558
|
+
|
|
559
|
+
local zip_file=""
|
|
560
|
+
if [ "$runtime" = "nodejs" ]; then
|
|
561
|
+
if [ ! -f "$NODE_SCRIPT" ]; then
|
|
562
|
+
cd "$current_dir"
|
|
563
|
+
printf "${RED}Error: Node.js script not found${NC}\n"
|
|
564
|
+
exit 1
|
|
565
|
+
fi
|
|
566
|
+
bash "$NODE_SCRIPT" "${build_args[@]}" 2>&1 | tee build.log
|
|
567
|
+
|
|
568
|
+
# Check if build script succeeded
|
|
569
|
+
if [ ${PIPESTATUS[0]} -ne 0 ]; then
|
|
570
|
+
cd "$current_dir"
|
|
571
|
+
printf "${RED}Error: Node.js layer build failed${NC}\n"
|
|
572
|
+
printf "Check build log: $output_dir/build.log\n"
|
|
573
|
+
exit 1
|
|
574
|
+
fi
|
|
575
|
+
|
|
576
|
+
# Extract zip file name from build output (just the filename, not full path)
|
|
577
|
+
zip_file=$(grep -o "File: .*\.zip" "build.log" | cut -d' ' -f2 | tail -1)
|
|
578
|
+
if [ -n "$zip_file" ]; then
|
|
579
|
+
zip_file=$(basename "$zip_file")
|
|
580
|
+
else
|
|
581
|
+
# Try to find zip file in current directory
|
|
582
|
+
zip_file=$(find . -maxdepth 1 -name "*.zip" -type f | head -1 | sed 's|^\./||')
|
|
583
|
+
fi
|
|
584
|
+
|
|
585
|
+
# Extract Node.js version for compatible runtimes
|
|
586
|
+
local node_version=$(grep -o "Node.js version: [0-9.]*" "build.log" | cut -d' ' -f3 | tail -1)
|
|
587
|
+
if [ -z "$node_version" ]; then
|
|
588
|
+
node_version="24"
|
|
589
|
+
fi
|
|
590
|
+
local compatible_runtimes="nodejs${node_version}.x"
|
|
591
|
+
|
|
592
|
+
elif [ "$runtime" = "python" ]; then
|
|
593
|
+
if [ ! -f "$PYTHON_SCRIPT" ]; then
|
|
594
|
+
cd "$current_dir"
|
|
595
|
+
printf "${RED}Error: Python script not found${NC}\n"
|
|
596
|
+
exit 1
|
|
597
|
+
fi
|
|
598
|
+
bash "$PYTHON_SCRIPT" "${build_args[@]}" 2>&1 | tee build.log
|
|
599
|
+
|
|
600
|
+
# Check if build script succeeded
|
|
601
|
+
if [ ${PIPESTATUS[0]} -ne 0 ]; then
|
|
602
|
+
cd "$current_dir"
|
|
603
|
+
printf "${RED}Error: Python layer build failed${NC}\n"
|
|
604
|
+
printf "Check build log: $output_dir/build.log\n"
|
|
605
|
+
exit 1
|
|
606
|
+
fi
|
|
607
|
+
|
|
608
|
+
# Extract zip file name from build output (just the filename, not full path)
|
|
609
|
+
zip_file=$(grep -o "File: .*\.zip" "build.log" | cut -d' ' -f2 | tail -1)
|
|
610
|
+
if [ -n "$zip_file" ]; then
|
|
611
|
+
zip_file=$(basename "$zip_file")
|
|
612
|
+
else
|
|
613
|
+
# Try to find zip file in current directory
|
|
614
|
+
zip_file=$(find . -maxdepth 1 -name "*.zip" -type f | head -1 | sed 's|^\./||')
|
|
615
|
+
fi
|
|
616
|
+
|
|
617
|
+
# Extract Python version for compatible runtimes
|
|
618
|
+
local python_version=$(grep -o "Python: [0-9.]*" "build.log" | cut -d' ' -f2 | tail -1)
|
|
619
|
+
if [ -z "$python_version" ]; then
|
|
620
|
+
python_version="3.14"
|
|
621
|
+
fi
|
|
622
|
+
local compatible_runtimes="python${python_version}"
|
|
623
|
+
fi
|
|
624
|
+
|
|
625
|
+
# Check if zip file was created
|
|
626
|
+
if [ -z "$zip_file" ] || [ ! -f "$zip_file" ]; then
|
|
627
|
+
cd "$current_dir"
|
|
628
|
+
printf "${RED}Error: Failed to create layer zip file${NC}\n"
|
|
629
|
+
printf "Check build log: $output_dir/build.log\n"
|
|
630
|
+
exit 1
|
|
631
|
+
fi
|
|
632
|
+
|
|
633
|
+
# Extract package info for layer naming and description
|
|
634
|
+
local packages_info=""
|
|
635
|
+
if [ "$runtime" = "nodejs" ]; then
|
|
636
|
+
packages_info=$(grep -o "Installed packages: .*" "build.log" | cut -d' ' -f3- | tail -1)
|
|
637
|
+
elif [ "$runtime" = "python" ]; then
|
|
638
|
+
packages_info=$(grep -o "Installed packages: .*" "build.log" | cut -d' ' -f3- | tail -1)
|
|
639
|
+
fi
|
|
640
|
+
|
|
641
|
+
# Determine layer name - use just the first package name if not specified
|
|
642
|
+
if [ -z "$layer_name" ]; then
|
|
643
|
+
# Extract first package name from packages list
|
|
644
|
+
local first_package=$(echo "$packages" | cut -d',' -f1)
|
|
645
|
+
# Remove version specifiers to get just the package name
|
|
646
|
+
if [ "$runtime" = "nodejs" ]; then
|
|
647
|
+
# For Node.js: remove @version, handle scoped packages like @aws-sdk/client-s3
|
|
648
|
+
layer_name=$(echo "$first_package" | sed 's/@[0-9^~<>=].*$//' | sed 's/^@//' | tr '/' '-')
|
|
649
|
+
else
|
|
650
|
+
# For Python: remove ==version, >=version, etc.
|
|
651
|
+
layer_name=$(echo "$first_package" | sed 's/[=<>~!].*$//')
|
|
652
|
+
fi
|
|
653
|
+
fi
|
|
654
|
+
|
|
655
|
+
# Determine description - use package name with version
|
|
656
|
+
local final_description=""
|
|
657
|
+
if [ -n "$description" ]; then
|
|
658
|
+
final_description="$description - $packages_info"
|
|
659
|
+
else
|
|
660
|
+
final_description="$packages_info"
|
|
661
|
+
fi
|
|
662
|
+
|
|
663
|
+
# Limit description length (AWS limit is 256 characters)
|
|
664
|
+
if [ ${#final_description} -gt 256 ]; then
|
|
665
|
+
final_description="${final_description:0:253}..."
|
|
666
|
+
fi
|
|
667
|
+
|
|
668
|
+
printf "\n${CYAN}Publishing Lambda layer to AWS...${NC}\n"
|
|
669
|
+
printf " Layer name: ${GREEN}$layer_name${NC}\n"
|
|
670
|
+
printf " Description: ${GREEN}$final_description${NC}\n"
|
|
671
|
+
printf " Compatible runtimes: ${GREEN}$compatible_runtimes${NC}\n"
|
|
672
|
+
printf " Zip file: ${GREEN}$zip_file${NC}\n\n"
|
|
673
|
+
|
|
674
|
+
# Confirm publish
|
|
675
|
+
read -p "Do you want to publish this layer? [Y/n]: " confirm_publish
|
|
676
|
+
confirm_publish=${confirm_publish:-Y}
|
|
677
|
+
|
|
678
|
+
case "$confirm_publish" in
|
|
679
|
+
[Yy]|[Yy][Ee][Ss])
|
|
680
|
+
;;
|
|
681
|
+
*)
|
|
682
|
+
printf "${YELLOW}Publishing cancelled.${NC}\n"
|
|
683
|
+
exit 0
|
|
684
|
+
;;
|
|
685
|
+
esac
|
|
686
|
+
|
|
687
|
+
# Build AWS CLI options for publish
|
|
688
|
+
local publish_aws_opts=()
|
|
689
|
+
if [ -n "$profile" ]; then
|
|
690
|
+
publish_aws_opts+=("--profile" "$profile")
|
|
691
|
+
fi
|
|
692
|
+
if [ -n "$region" ]; then
|
|
693
|
+
publish_aws_opts+=("--region" "$region")
|
|
694
|
+
fi
|
|
695
|
+
|
|
696
|
+
# Publish the layer
|
|
697
|
+
printf "${YELLOW}Executing: aws lambda publish-layer-version${NC}\n"
|
|
698
|
+
if [ -n "$profile" ]; then
|
|
699
|
+
printf " --profile \"$profile\"\n"
|
|
700
|
+
fi
|
|
701
|
+
if [ -n "$region" ]; then
|
|
702
|
+
printf " --region \"$region\"\n"
|
|
703
|
+
fi
|
|
704
|
+
printf " --layer-name \"$layer_name\"\n"
|
|
705
|
+
printf " --description \"$final_description\"\n"
|
|
706
|
+
printf " --zip-file \"fileb://$(convert_path "$current_dir/$output_dir/$zip_file")\"\n"
|
|
707
|
+
printf " --compatible-runtimes \"$compatible_runtimes\"\n\n"
|
|
708
|
+
|
|
709
|
+
# Run AWS CLI command and capture output and exit code
|
|
710
|
+
aws ${publish_aws_opts[@]+"${publish_aws_opts[@]}"} lambda publish-layer-version \
|
|
711
|
+
--layer-name "$layer_name" \
|
|
712
|
+
--description "$final_description" \
|
|
713
|
+
--zip-file "fileb://$(convert_path "$current_dir/$output_dir/$zip_file")" \
|
|
714
|
+
--compatible-runtimes "$compatible_runtimes" \
|
|
715
|
+
--query '[LayerVersionArn, Version, Description]' \
|
|
716
|
+
--output table 2>&1 | tee publish.log
|
|
717
|
+
|
|
718
|
+
local aws_exit_code=${PIPESTATUS[0]}
|
|
719
|
+
|
|
720
|
+
if [ $aws_exit_code -eq 0 ]; then
|
|
721
|
+
printf "\n${GREEN}✅ Lambda layer published successfully!${NC}\n"
|
|
722
|
+
|
|
723
|
+
# Extract layer ARN from log
|
|
724
|
+
local layer_arn=$(grep -o "arn:aws:lambda:[^:]*:[0-9]*:layer:$layer_name:[0-9]*" "publish.log" | tail -1)
|
|
725
|
+
if [ -n "$layer_arn" ]; then
|
|
726
|
+
printf " Layer ARN: ${CYAN}$layer_arn${NC}\n"
|
|
727
|
+
|
|
728
|
+
# Show usage example
|
|
729
|
+
printf "\n${YELLOW}Usage example in Lambda function:${NC}\n"
|
|
730
|
+
printf " layers:\n"
|
|
731
|
+
printf " - $layer_arn\n"
|
|
732
|
+
printf "\n${YELLOW}To attach to existing Lambda function:${NC}\n"
|
|
733
|
+
printf " aws lambda update-function-configuration \\\\\n"
|
|
734
|
+
printf " --function-name YOUR_FUNCTION_NAME \\\\\n"
|
|
735
|
+
printf " --layers $layer_arn\n"
|
|
736
|
+
fi
|
|
737
|
+
else
|
|
738
|
+
cd "$current_dir"
|
|
739
|
+
printf "\n${RED}❌ Failed to publish Lambda layer${NC}\n"
|
|
740
|
+
printf "Check publish log: $output_dir/publish.log\n"
|
|
741
|
+
printf "\n${YELLOW}Common issues:${NC}\n"
|
|
742
|
+
printf "1. IAM permissions missing (lambda:PublishLayerVersion)\n"
|
|
743
|
+
printf "2. Layer name already exists (try different --name)\n"
|
|
744
|
+
printf "3. Zip file too large (max 50MB for direct upload)\n"
|
|
745
|
+
printf "4. Network connectivity issues\n"
|
|
746
|
+
exit 1
|
|
747
|
+
fi
|
|
748
|
+
|
|
749
|
+
# Return to original directory
|
|
750
|
+
cd "$current_dir"
|
|
751
|
+
|
|
752
|
+
printf "\n${GREEN}✅ All done! Layer is now available in your AWS account.${NC}\n"
|
|
753
|
+
printf "📁 Zip file saved: ${CYAN}$output_dir/$zip_file${NC}\n"
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
# Function to convert file paths for compatibility across environments
|
|
757
|
+
convert_path() {
|
|
758
|
+
local input_path="$1"
|
|
759
|
+
case "$(uname -s)" in
|
|
760
|
+
Linux)
|
|
761
|
+
if grep -qEi "microsoft|wsl" /proc/version &>/dev/null; then
|
|
762
|
+
# WSL detected, no conversion needed
|
|
763
|
+
echo "$input_path"
|
|
764
|
+
else
|
|
765
|
+
# Native Linux
|
|
766
|
+
echo "$input_path"
|
|
767
|
+
fi
|
|
768
|
+
;;
|
|
769
|
+
Darwin)
|
|
770
|
+
# macOS
|
|
771
|
+
echo "$input_path"
|
|
772
|
+
;;
|
|
773
|
+
CYGWIN*|MINGW*|MSYS*)
|
|
774
|
+
# Git Bash or Cygwin on Windows
|
|
775
|
+
# Prefer C:/ style to avoid fileb:// URI parsing issues with backslashes
|
|
776
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
777
|
+
cygpath -m "$input_path"
|
|
778
|
+
else
|
|
779
|
+
echo "$input_path"
|
|
780
|
+
fi
|
|
781
|
+
;;
|
|
782
|
+
*)
|
|
783
|
+
# Default case (no conversion)
|
|
784
|
+
echo "$input_path"
|
|
785
|
+
;;
|
|
786
|
+
esac
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
# Main command parsing
|
|
790
|
+
main() {
|
|
791
|
+
if [ $# -eq 0 ]; then
|
|
792
|
+
show_help
|
|
793
|
+
exit 0
|
|
794
|
+
fi
|
|
795
|
+
|
|
796
|
+
case "$1" in
|
|
797
|
+
zip)
|
|
798
|
+
shift
|
|
799
|
+
handle_zip "$@"
|
|
800
|
+
;;
|
|
801
|
+
publish)
|
|
802
|
+
shift
|
|
803
|
+
handle_publish "$@"
|
|
804
|
+
;;
|
|
805
|
+
help|--help|-h)
|
|
806
|
+
show_help
|
|
807
|
+
;;
|
|
808
|
+
--version|-v)
|
|
809
|
+
show_version
|
|
810
|
+
;;
|
|
811
|
+
*)
|
|
812
|
+
printf "${RED}Error: Unknown command '$1'${NC}\n"
|
|
813
|
+
printf "Available commands: zip, publish\n"
|
|
814
|
+
printf "Use --help for more information\n"
|
|
815
|
+
exit 1
|
|
816
|
+
;;
|
|
817
|
+
esac
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
# Run main function with all arguments
|
|
821
|
+
main "$@"
|