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,465 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Python Lambda Layer Creator with UV and version specification
|
|
4
|
+
# Usage:
|
|
5
|
+
# ./create_python_layer.sh -i numpy==1.26.0,pandas==2.1.3
|
|
6
|
+
# ./create_python_layer.sh -i numpy==1.26.0,pandas,boto3==1.34.0 -n my-layer.zip
|
|
7
|
+
# ./create_python_layer.sh --packages=requests==2.31.0,boto3 --no-uv
|
|
8
|
+
|
|
9
|
+
set -e # Exit on error
|
|
10
|
+
set -u # Treat unset variables as errors
|
|
11
|
+
|
|
12
|
+
# Generate unique temporary directory
|
|
13
|
+
TEMP_DIR=$(mktemp -d)
|
|
14
|
+
WORK_DIR="$TEMP_DIR/layer-build"
|
|
15
|
+
|
|
16
|
+
# Default values
|
|
17
|
+
PACKAGES=""
|
|
18
|
+
LAYER_NAME=""
|
|
19
|
+
PYTHON_VERSION="3.14" # Default to Python 3.14
|
|
20
|
+
PYTHON_VERSION_SPECIFIED=false
|
|
21
|
+
VENV_DIR="python"
|
|
22
|
+
USE_UV=true
|
|
23
|
+
ORIGINAL_DIR=$(pwd)
|
|
24
|
+
|
|
25
|
+
# Colors for output
|
|
26
|
+
RED='\033[0;31m'
|
|
27
|
+
GREEN='\033[0;32m'
|
|
28
|
+
YELLOW='\033[1;33m'
|
|
29
|
+
BLUE='\033[0;34m'
|
|
30
|
+
NC='\033[0m'
|
|
31
|
+
|
|
32
|
+
# Security functions
|
|
33
|
+
sanitize_filename() {
|
|
34
|
+
local filename="$1"
|
|
35
|
+
# Remove dangerous characters: /, \, :, |, <, >, ?, *, ", ', `, $, (, ), {, }, ;, &, !
|
|
36
|
+
filename=$(echo "$filename" | sed 's/[\/\\:|<>?*"\`$(){};&!]//g')
|
|
37
|
+
# Remove leading/trailing dots and hyphens
|
|
38
|
+
filename=$(echo "$filename" | sed 's/^[.-]*//' | sed 's/[.-]*$//')
|
|
39
|
+
# Limit length
|
|
40
|
+
echo "${filename:0:100}"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
validate_python_version() {
|
|
44
|
+
local version="$1"
|
|
45
|
+
# Allow only numbers and dots in Python version (e.g., 3.9, 3.14.2)
|
|
46
|
+
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then
|
|
47
|
+
printf "${RED}Error: Invalid Python version format: $version${NC}\n"
|
|
48
|
+
printf "Python version must be in format X.Y or X.Y.Z (e.g., 3.14, 3.14.2)\n"
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
escape_package_name() {
|
|
54
|
+
local pkg="$1"
|
|
55
|
+
# Whitelist for Python: A-Za-z0-9._- (with version operators: = > < ~ !)
|
|
56
|
+
# FIXED: Place hyphen at the end of character class to avoid regex range interpretation
|
|
57
|
+
echo "$pkg" | sed 's/[^A-Za-z0-9._=><~!+-]//g'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Extract base package name from version specification
|
|
61
|
+
# Example: numpy==1.26.0 -> numpy
|
|
62
|
+
# Example: requests>=2.31.0 -> requests
|
|
63
|
+
extract_package_name() {
|
|
64
|
+
local pkg="$1"
|
|
65
|
+
# Remove version specification operators and everything after
|
|
66
|
+
echo "$pkg" | sed 's/[=<>!~].*$//'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Extract version specification from package string
|
|
70
|
+
# Example: numpy==1.26.0 -> ==1.26.0
|
|
71
|
+
# Example: requests>=2.31.0 -> >=2.31.0
|
|
72
|
+
extract_version_spec() {
|
|
73
|
+
local pkg="$1"
|
|
74
|
+
if [[ "$pkg" =~ [\=\<\>!~] ]]; then
|
|
75
|
+
echo "$pkg" | grep -o '[=<>!~].*' || echo ""
|
|
76
|
+
else
|
|
77
|
+
echo ""
|
|
78
|
+
fi
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Cleanup function
|
|
82
|
+
cleanup() {
|
|
83
|
+
if [ -d "$TEMP_DIR" ]; then
|
|
84
|
+
rm -rf "$TEMP_DIR"
|
|
85
|
+
fi
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# Trap to ensure cleanup on exit
|
|
89
|
+
trap cleanup EXIT
|
|
90
|
+
|
|
91
|
+
# Parse command line arguments
|
|
92
|
+
while [[ $# -gt 0 ]]; do
|
|
93
|
+
case "$1" in
|
|
94
|
+
-i|--packages)
|
|
95
|
+
if [[ -n "${2:-}" && "${2:-}" != -* ]]; then
|
|
96
|
+
PACKAGES="$2"
|
|
97
|
+
shift 2
|
|
98
|
+
else
|
|
99
|
+
printf "${RED}Error: $1 requires an argument${NC}\n"
|
|
100
|
+
printf "Example: $1 numpy==1.26.0,requests\n"
|
|
101
|
+
exit 1
|
|
102
|
+
fi
|
|
103
|
+
;;
|
|
104
|
+
--packages=*)
|
|
105
|
+
PACKAGES="${1#*=}"
|
|
106
|
+
shift
|
|
107
|
+
;;
|
|
108
|
+
-n|--name)
|
|
109
|
+
if [[ -n "${2:-}" && "${2:-}" != -* ]]; then
|
|
110
|
+
LAYER_NAME="$2"
|
|
111
|
+
shift 2
|
|
112
|
+
else
|
|
113
|
+
printf "${RED}Error: $1 requires an argument${NC}\n"
|
|
114
|
+
printf "Example: $1 my-python-layer.zip\n"
|
|
115
|
+
exit 1
|
|
116
|
+
fi
|
|
117
|
+
;;
|
|
118
|
+
--name=*)
|
|
119
|
+
LAYER_NAME="${1#*=}"
|
|
120
|
+
shift
|
|
121
|
+
;;
|
|
122
|
+
--python-version)
|
|
123
|
+
if [[ -n "${2:-}" && "${2:-}" != -* ]]; then
|
|
124
|
+
PYTHON_VERSION="$2"
|
|
125
|
+
PYTHON_VERSION_SPECIFIED=true
|
|
126
|
+
validate_python_version "$PYTHON_VERSION"
|
|
127
|
+
shift 2
|
|
128
|
+
else
|
|
129
|
+
printf "${RED}Error: $1 requires an argument${NC}\n"
|
|
130
|
+
printf "Example: $1 3.14\n"
|
|
131
|
+
exit 1
|
|
132
|
+
fi
|
|
133
|
+
;;
|
|
134
|
+
--python-version=*)
|
|
135
|
+
PYTHON_VERSION="${1#*=}"
|
|
136
|
+
PYTHON_VERSION_SPECIFIED=true
|
|
137
|
+
validate_python_version "$PYTHON_VERSION"
|
|
138
|
+
shift
|
|
139
|
+
;;
|
|
140
|
+
--no-uv)
|
|
141
|
+
USE_UV=false
|
|
142
|
+
shift
|
|
143
|
+
;;
|
|
144
|
+
-h|--help)
|
|
145
|
+
cat << 'EOF'
|
|
146
|
+
Python Lambda Layer Creator with UV
|
|
147
|
+
|
|
148
|
+
Usage:
|
|
149
|
+
./create_python_layer.sh -i numpy==1.26.0,pandas==2.1.3
|
|
150
|
+
./create_python_layer.sh --packages=numpy==1.26.0,pandas,boto3==1.34.0 -n my-layer.zip
|
|
151
|
+
./create_python_layer.sh -i flask==3.0.0 --no-uv
|
|
152
|
+
|
|
153
|
+
Options:
|
|
154
|
+
-i, --packages Comma-separated list of Python packages (with optional versions)
|
|
155
|
+
-n, --name Name of the output zip file
|
|
156
|
+
--python-version Python version (default: 3.14)
|
|
157
|
+
--no-uv Use pip/venv instead of uv
|
|
158
|
+
-h, --help Show this help message
|
|
159
|
+
|
|
160
|
+
Version Specification:
|
|
161
|
+
Package versions can be specified using standard Python version specifiers:
|
|
162
|
+
numpy==1.26.0 # Exact version
|
|
163
|
+
pandas>=2.1.0 # Greater than or equal
|
|
164
|
+
requests>2.30.0 # Greater than
|
|
165
|
+
scipy<=1.11.0 # Less than or equal
|
|
166
|
+
tensorflow~=2.15.0 # Compatible release
|
|
167
|
+
Django!=3.2.0 # Version exclusion
|
|
168
|
+
|
|
169
|
+
Examples:
|
|
170
|
+
./create_python_layer.sh -i numpy==1.26.0
|
|
171
|
+
./create_python_layer.sh -i requests==2.31.0,boto3==1.34.0 --python-version=3.14
|
|
172
|
+
./create_python_layer.sh --packages=pandas==2.1.3,scikit-learn==1.3.0 --no-uv -n ml-layer.zip
|
|
173
|
+
EOF
|
|
174
|
+
exit 0
|
|
175
|
+
;;
|
|
176
|
+
*)
|
|
177
|
+
printf "${RED}Unknown option: $1${NC}\n"
|
|
178
|
+
printf "Use -h or --help for usage information\n"
|
|
179
|
+
exit 1
|
|
180
|
+
;;
|
|
181
|
+
esac
|
|
182
|
+
done
|
|
183
|
+
|
|
184
|
+
# Check if packages are provided
|
|
185
|
+
if [ -z "$PACKAGES" ]; then
|
|
186
|
+
printf "${RED}Error: Packages argument is required${NC}\n"
|
|
187
|
+
printf "Use -i or --packages to specify packages (comma-separated)\n"
|
|
188
|
+
printf "Example: ./create_python_layer.sh -i numpy==1.26.0,requests\n"
|
|
189
|
+
exit 1
|
|
190
|
+
fi
|
|
191
|
+
|
|
192
|
+
# Check if uv is available safely
|
|
193
|
+
if [ "$USE_UV" = true ]; then
|
|
194
|
+
if ! command -v uv >/dev/null 2>&1; then
|
|
195
|
+
printf "${YELLOW}Warning: uv not found, falling back to pip/venv${NC}\n"
|
|
196
|
+
USE_UV=false
|
|
197
|
+
fi
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
# Check dependencies
|
|
201
|
+
if ! command -v zip &> /dev/null; then
|
|
202
|
+
printf "${RED}Error: 'zip' command is not installed${NC}\n"
|
|
203
|
+
exit 1
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
if ! command -v python3 &> /dev/null && ! command -v python &> /dev/null; then
|
|
207
|
+
printf "${RED}Error: 'python' command is not installed${NC}\n"
|
|
208
|
+
exit 1
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
# Sanitize packages input using whitelist
|
|
212
|
+
SANITIZED_PACKAGES=""
|
|
213
|
+
IFS=',' read -ra PACKAGE_ARRAY <<< "$PACKAGES"
|
|
214
|
+
for pkg in "${PACKAGE_ARRAY[@]}"; do
|
|
215
|
+
# Trim whitespace
|
|
216
|
+
pkg=$(echo "$pkg" | xargs)
|
|
217
|
+
# Escape package name using whitelist
|
|
218
|
+
escaped_pkg=$(escape_package_name "$pkg")
|
|
219
|
+
if [ -n "$escaped_pkg" ]; then
|
|
220
|
+
SANITIZED_PACKAGES="${SANITIZED_PACKAGES}${SANITIZED_PACKAGES:+,}$escaped_pkg"
|
|
221
|
+
else
|
|
222
|
+
printf "${YELLOW}Warning: Package name '$pkg' contains no valid characters after sanitization${NC}\n"
|
|
223
|
+
fi
|
|
224
|
+
done
|
|
225
|
+
|
|
226
|
+
if [ -z "$SANITIZED_PACKAGES" ]; then
|
|
227
|
+
printf "${RED}Error: No valid packages provided after sanitization${NC}\n"
|
|
228
|
+
exit 1
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
# Check if any package names were changed
|
|
232
|
+
if [ "$PACKAGES" != "$SANITIZED_PACKAGES" ]; then
|
|
233
|
+
printf "${YELLOW}Warning: Some package names were sanitized:${NC}\n"
|
|
234
|
+
printf " Original: $PACKAGES\n"
|
|
235
|
+
printf " Sanitized: $SANITIZED_PACKAGES\n"
|
|
236
|
+
PACKAGES="$SANITIZED_PACKAGES"
|
|
237
|
+
fi
|
|
238
|
+
|
|
239
|
+
printf "${BLUE}=========================================${NC}\n"
|
|
240
|
+
printf "${GREEN}Python Lambda Layer Creator${NC}\n"
|
|
241
|
+
printf "${BLUE}=========================================${NC}\n"
|
|
242
|
+
printf "Packages: $PACKAGES\n"
|
|
243
|
+
printf "Python version: $PYTHON_VERSION\n"
|
|
244
|
+
printf "Using UV: $USE_UV\n"
|
|
245
|
+
if [ -n "$LAYER_NAME" ]; then
|
|
246
|
+
printf "Output name: $LAYER_NAME\n"
|
|
247
|
+
fi
|
|
248
|
+
printf "\n"
|
|
249
|
+
|
|
250
|
+
# Step 1: Create working directory
|
|
251
|
+
printf "[1/7] Creating directory structure...\n"
|
|
252
|
+
mkdir -p "$WORK_DIR"
|
|
253
|
+
cd "$WORK_DIR"
|
|
254
|
+
|
|
255
|
+
# Step 2: Create virtual environment
|
|
256
|
+
printf "[2/7] Creating virtual environment...\n"
|
|
257
|
+
|
|
258
|
+
TARGET_PYTHON="python${PYTHON_VERSION}"
|
|
259
|
+
|
|
260
|
+
# Check if target python exists
|
|
261
|
+
if ! command -v "$TARGET_PYTHON" >/dev/null 2>&1; then
|
|
262
|
+
if [ "$PYTHON_VERSION_SPECIFIED" = false ]; then
|
|
263
|
+
printf "${YELLOW}Warning: Default $TARGET_PYTHON not found. Checking for fallback...${NC}\n"
|
|
264
|
+
if command -v python3 >/dev/null 2>&1 && python3 -V >/dev/null 2>&1; then
|
|
265
|
+
TARGET_PYTHON="python3"
|
|
266
|
+
elif command -v python >/dev/null 2>&1 && python -V >/dev/null 2>&1; then
|
|
267
|
+
TARGET_PYTHON="python"
|
|
268
|
+
else
|
|
269
|
+
printf "${RED}Error: No python interpreter found${NC}\n"
|
|
270
|
+
exit 1
|
|
271
|
+
fi
|
|
272
|
+
|
|
273
|
+
# Update PYTHON_VERSION to match the fallback
|
|
274
|
+
DETECTED_VER=$($TARGET_PYTHON -c 'import sys; print(".".join(map(str, sys.version_info[:2])))')
|
|
275
|
+
printf "${YELLOW}Falling back to $TARGET_PYTHON ($DETECTED_VER)${NC}\n"
|
|
276
|
+
PYTHON_VERSION="$DETECTED_VER"
|
|
277
|
+
fi
|
|
278
|
+
fi
|
|
279
|
+
|
|
280
|
+
if [ "$USE_UV" = true ]; then
|
|
281
|
+
printf " Using UV to create venv...\n"
|
|
282
|
+
if ! uv venv --python "$TARGET_PYTHON" "$VENV_DIR"; then
|
|
283
|
+
printf "${RED}Error: Failed to create venv with uv${NC}\n"
|
|
284
|
+
exit 1
|
|
285
|
+
fi
|
|
286
|
+
else
|
|
287
|
+
printf " Using venv module...\n"
|
|
288
|
+
if command -v "$TARGET_PYTHON" >/dev/null 2>&1; then
|
|
289
|
+
"$TARGET_PYTHON" -m venv "$VENV_DIR"
|
|
290
|
+
else
|
|
291
|
+
printf "${RED}Error: $TARGET_PYTHON not found${NC}\n"
|
|
292
|
+
exit 1
|
|
293
|
+
fi
|
|
294
|
+
fi
|
|
295
|
+
|
|
296
|
+
# Activate virtual environment
|
|
297
|
+
set +u
|
|
298
|
+
if [ -f "$VENV_DIR/Scripts/activate" ]; then
|
|
299
|
+
source "$VENV_DIR/Scripts/activate"
|
|
300
|
+
elif [ -f "$VENV_DIR/bin/activate" ]; then
|
|
301
|
+
source "$VENV_DIR/bin/activate"
|
|
302
|
+
else
|
|
303
|
+
printf "${RED}Error: Cannot find activation script in $VENV_DIR${NC}\n"
|
|
304
|
+
exit 1
|
|
305
|
+
fi
|
|
306
|
+
set -u
|
|
307
|
+
|
|
308
|
+
# Step 3: Install packages with versions
|
|
309
|
+
printf "[3/7] Installing packages...\n"
|
|
310
|
+
if [ "$USE_UV" = true ]; then
|
|
311
|
+
printf " Installing with UV...\n"
|
|
312
|
+
# Convert to array for safe expansion
|
|
313
|
+
IFS=',' read -ra PKG_ARRAY <<< "$PACKAGES"
|
|
314
|
+
uv pip install "${PKG_ARRAY[@]}"
|
|
315
|
+
else
|
|
316
|
+
printf " Installing with pip...\n"
|
|
317
|
+
# Convert to array for safe expansion
|
|
318
|
+
IFS=',' read -ra PKG_ARRAY <<< "$PACKAGES"
|
|
319
|
+
pip install "${PKG_ARRAY[@]}"
|
|
320
|
+
fi
|
|
321
|
+
|
|
322
|
+
# Count packages from command argument
|
|
323
|
+
PACKAGE_COUNT=$(echo "$PACKAGES" | tr ',' '\n' | wc -l | tr -d ' ')
|
|
324
|
+
|
|
325
|
+
# Step 4: Determine layer name
|
|
326
|
+
printf "[4/7] Determining layer name...\n"
|
|
327
|
+
if [ -z "$LAYER_NAME" ]; then
|
|
328
|
+
if [ "$PACKAGE_COUNT" -eq 1 ]; then
|
|
329
|
+
PKG_FULL="$PACKAGES"
|
|
330
|
+
PKG_NAME=$(extract_package_name "$PKG_FULL")
|
|
331
|
+
VERSION_SPEC=$(extract_version_spec "$PKG_FULL")
|
|
332
|
+
|
|
333
|
+
printf " Single package: $PKG_NAME\n"
|
|
334
|
+
|
|
335
|
+
# Extract version from installed package
|
|
336
|
+
if [ "$USE_UV" = true ]; then
|
|
337
|
+
PKG_INFO=$(uv pip show "$PKG_NAME" 2>/dev/null || true)
|
|
338
|
+
else
|
|
339
|
+
PKG_INFO=$(pip show "$PKG_NAME" 2>/dev/null || true)
|
|
340
|
+
fi
|
|
341
|
+
|
|
342
|
+
if [ -n "$PKG_INFO" ]; then
|
|
343
|
+
# Use safer extraction methods
|
|
344
|
+
INSTALLED_VERSION=$(echo "$PKG_INFO" | grep -E '^Version:' | head -1 | awk '{print $2}')
|
|
345
|
+
|
|
346
|
+
if [ -z "$INSTALLED_VERSION" ]; then
|
|
347
|
+
INSTALLED_VERSION=$(echo "$PKG_INFO" | grep -E '^version:' | head -1 | awk '{print $2}')
|
|
348
|
+
fi
|
|
349
|
+
|
|
350
|
+
if [ -z "$INSTALLED_VERSION" ]; then
|
|
351
|
+
INSTALLED_VERSION=$(echo "$PKG_INFO" | grep -i '^version[[:space:]]*:' | head -1 | awk '{print $2}')
|
|
352
|
+
fi
|
|
353
|
+
|
|
354
|
+
if [ -n "$INSTALLED_VERSION" ]; then
|
|
355
|
+
# Sanitize version string using whitelist
|
|
356
|
+
INSTALLED_VERSION=$(echo "$INSTALLED_VERSION" | sed 's/[^A-Za-z0-9._=><~!+-]//g')
|
|
357
|
+
INSTALLED_VERSION=$(echo "$INSTALLED_VERSION" | sed 's/\.post[0-9]*//' | sed 's/\.dev[0-9]*//' | sed 's/\+.*//')
|
|
358
|
+
|
|
359
|
+
# If user specified a version, use it in the name
|
|
360
|
+
if [ -n "$VERSION_SPEC" ]; then
|
|
361
|
+
# Extract just the version number from spec (remove operators)
|
|
362
|
+
SPEC_VERSION=$(echo "$VERSION_SPEC" | sed 's/^[=<>!~]*//')
|
|
363
|
+
LAYER_NAME="${PKG_NAME}-${SPEC_VERSION}-python${PYTHON_VERSION}"
|
|
364
|
+
printf " Specified version: $SPEC_VERSION\n"
|
|
365
|
+
printf " Using versioned name\n"
|
|
366
|
+
else
|
|
367
|
+
LAYER_NAME="${PKG_NAME}-${INSTALLED_VERSION}-python${PYTHON_VERSION}"
|
|
368
|
+
printf " Installed version: $INSTALLED_VERSION\n"
|
|
369
|
+
fi
|
|
370
|
+
else
|
|
371
|
+
LAYER_NAME="${PKG_NAME}-$(date +%Y%m%d)-python${PYTHON_VERSION}"
|
|
372
|
+
printf " Could not extract version, using date-based name\n"
|
|
373
|
+
fi
|
|
374
|
+
else
|
|
375
|
+
LAYER_NAME="${PKG_NAME}-$(date +%Y%m%d)-python${PYTHON_VERSION}"
|
|
376
|
+
printf " No package info found, using date-based name\n"
|
|
377
|
+
fi
|
|
378
|
+
else
|
|
379
|
+
LAYER_NAME="python-$(date +%Y%m%d)-python${PYTHON_VERSION}"
|
|
380
|
+
printf " Multiple packages, using date-based name\n"
|
|
381
|
+
fi
|
|
382
|
+
|
|
383
|
+
# Sanitize the layer name
|
|
384
|
+
LAYER_NAME=$(sanitize_filename "$LAYER_NAME")
|
|
385
|
+
fi
|
|
386
|
+
|
|
387
|
+
# Additional sanitization
|
|
388
|
+
LAYER_NAME=$(sanitize_filename "$LAYER_NAME")
|
|
389
|
+
|
|
390
|
+
# Check for path traversal in layer name
|
|
391
|
+
if [[ "$LAYER_NAME" =~ \.\. ]] || [[ "$LAYER_NAME" =~ ^/ ]]; then
|
|
392
|
+
printf "${RED}Error: Invalid layer name (path traversal detected)${NC}\n"
|
|
393
|
+
exit 1
|
|
394
|
+
fi
|
|
395
|
+
|
|
396
|
+
# Ensure .zip extension
|
|
397
|
+
if [[ ! "$LAYER_NAME" =~ \.zip$ ]]; then
|
|
398
|
+
LAYER_NAME="${LAYER_NAME}.zip"
|
|
399
|
+
fi
|
|
400
|
+
|
|
401
|
+
# Step 5: Show installed packages
|
|
402
|
+
printf "[5/7] Listing installed packages...\n"
|
|
403
|
+
if [ "$USE_UV" = true ]; then
|
|
404
|
+
uv pip list --format freeze
|
|
405
|
+
else
|
|
406
|
+
pip list --format freeze
|
|
407
|
+
fi
|
|
408
|
+
|
|
409
|
+
# Deactivate virtual environment
|
|
410
|
+
set +u
|
|
411
|
+
deactivate
|
|
412
|
+
set -u
|
|
413
|
+
|
|
414
|
+
# Step 6: Create zip file
|
|
415
|
+
printf "[6/7] Creating zip file: $LAYER_NAME\n"
|
|
416
|
+
cd "$WORK_DIR"
|
|
417
|
+
printf " Zipping 'python' directory...\n"
|
|
418
|
+
zip -r "$LAYER_NAME" "python" -q
|
|
419
|
+
printf " Zip file created successfully\n"
|
|
420
|
+
|
|
421
|
+
# Step 7: Move to final location
|
|
422
|
+
printf "[7/7] Moving to final location...\n"
|
|
423
|
+
if [[ -f "$LAYER_NAME" ]]; then
|
|
424
|
+
mv "$LAYER_NAME" "$ORIGINAL_DIR/"
|
|
425
|
+
else
|
|
426
|
+
printf "${RED}Error: Zip file not created${NC}\n"
|
|
427
|
+
exit 1
|
|
428
|
+
fi
|
|
429
|
+
|
|
430
|
+
printf "\n"
|
|
431
|
+
printf "${BLUE}=========================================${NC}\n"
|
|
432
|
+
printf "${GREEN}✅ SUCCESS: Python Lambda Layer Created${NC}\n"
|
|
433
|
+
printf "${BLUE}=========================================${NC}\n"
|
|
434
|
+
printf "📁 File: $ORIGINAL_DIR/$LAYER_NAME\n"
|
|
435
|
+
printf "🐍 Python Version: $PYTHON_VERSION\n"
|
|
436
|
+
printf "⚡ Tool: $(if [ "$USE_UV" = true ]; then echo "UV"; else echo "pip/venv"; fi)\n"
|
|
437
|
+
printf "📦 Size: $(du -h "$ORIGINAL_DIR/$LAYER_NAME" | cut -f1)\n"
|
|
438
|
+
printf "📊 Package Count: $PACKAGE_COUNT\n"
|
|
439
|
+
|
|
440
|
+
# Output installed packages with versions for description
|
|
441
|
+
printf "Installed packages: "
|
|
442
|
+
cd "$WORK_DIR/python"
|
|
443
|
+
INSTALLED_PKGS=""
|
|
444
|
+
IFS=',' read -ra PKG_ARRAY <<< "$PACKAGES"
|
|
445
|
+
for pkg_full in "${PKG_ARRAY[@]}"; do
|
|
446
|
+
pkg_name=$(extract_package_name "$pkg_full")
|
|
447
|
+
|
|
448
|
+
# Get installed version from pip show or metadata
|
|
449
|
+
if [ "$USE_UV" = true ]; then
|
|
450
|
+
installed_ver=$(find . -type f -name "METADATA" -path "*/${pkg_name}-*.dist-info/METADATA" -exec grep -h "^Version:" {} \; | head -1 | cut -d' ' -f2)
|
|
451
|
+
else
|
|
452
|
+
installed_ver=$(find . -type f -name "METADATA" -path "*/${pkg_name}-*.dist-info/METADATA" -exec grep -h "^Version:" {} \; | head -1 | cut -d' ' -f2)
|
|
453
|
+
fi
|
|
454
|
+
|
|
455
|
+
if [ -n "$installed_ver" ]; then
|
|
456
|
+
if [ -n "$INSTALLED_PKGS" ]; then
|
|
457
|
+
INSTALLED_PKGS="$INSTALLED_PKGS, ${pkg_name}==${installed_ver}"
|
|
458
|
+
else
|
|
459
|
+
INSTALLED_PKGS="${pkg_name}==${installed_ver}"
|
|
460
|
+
fi
|
|
461
|
+
fi
|
|
462
|
+
done
|
|
463
|
+
printf "$INSTALLED_PKGS\n"
|
|
464
|
+
|
|
465
|
+
printf "\n"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { execSync } = require('child_process');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
const isWindows = os.platform() === 'win32';
|
|
9
|
+
const scriptPath = isWindows ? path.join('scripts', 'install.ps1') : path.join('scripts', 'install.sh');
|
|
10
|
+
const fullPath = path.join(__dirname, '..', scriptPath);
|
|
11
|
+
|
|
12
|
+
if (!fs.existsSync(fullPath)) {
|
|
13
|
+
console.error(`Installation script not found: ${fullPath}`);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (isWindows) {
|
|
18
|
+
// Run PowerShell script with proper execution policy
|
|
19
|
+
execSync(`powershell -ExecutionPolicy Bypass -File "${fullPath}"`, {
|
|
20
|
+
stdio: 'inherit',
|
|
21
|
+
shell: true
|
|
22
|
+
});
|
|
23
|
+
} else {
|
|
24
|
+
// Run bash script
|
|
25
|
+
execSync(`bash "${fullPath}"`, {
|
|
26
|
+
stdio: 'inherit'
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.log('✓ Installation completed successfully');
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('✗ Installation failed:', error.message);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|