diff --git a/.devcontainer/scripts/postCreate.sh b/.devcontainer/scripts/postCreate.sh index 0c2ad59..e34fc87 100755 --- a/.devcontainer/scripts/postCreate.sh +++ b/.devcontainer/scripts/postCreate.sh @@ -4,7 +4,7 @@ set -euo pipefail echo "🔧 Installing devcontainer CLI..." npm install -g @devcontainers/cli -FEATURES=("ag" "amazon-q-cli" "gcloud-cli" "zip") +FEATURES=("ag" "amazon-q-cli" "aws-sam-cli" "gcloud-cli" "zip") BASE_IMAGES=("debian:latest" "ubuntu:latest" "mcr.microsoft.com/devcontainers/base:ubuntu") # Run autogenerated tests for each image diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d2aa4cb..483a863 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -15,6 +15,7 @@ jobs: features: - ag - amazon-q-cli + - aws-sam-cli - gcloud-cli - zip baseImage: @@ -38,6 +39,8 @@ jobs: features: - ag - amazon-q-cli + - aws-sam-cli + - gcloud-cli - zip steps: - uses: actions/checkout@v4 diff --git a/README.md b/README.md index 8c48bcc..5eafd09 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ This repository contains following features: - [ag](./src/ag/README.md): Installs ag (The Silver Searcher), a fast grep-like text search tool - [amazon-q-cli](./src/amazon-q-cli/README.md): Install Amazon Q CLI for AWS development +- [aws-sam-cli](./src/aws-sam-cli/README.md): Installs AWS SAM CLI for serverless application development - [gcloud-cli](./src/gcloud-cli/README.md): Installs Google Cloud CLI (gcloud) for Google Cloud Platform development - [zip](./src/zip/README.md): Installs zip and unzip CLI tools for compression and extraction @@ -21,6 +22,7 @@ These examples show how to use features from this repository in a devcontainer: "features": { "ghcr.io/jajera/features/ag:1": {}, "ghcr.io/jajera/features/amazon-q-cli:1": {}, + "ghcr.io/jajera/features/aws-sam-cli:1": {}, "ghcr.io/jajera/features/gcloud-cli:1": {}, "ghcr.io/jajera/features/zip:1": {} } @@ -52,6 +54,10 @@ Similar to the [`devcontainers/features`](https://github.com/devcontainers/featu │ │ ├── devcontainer-feature.json │ │ ├── install.sh │ │ └── README.md +│ ├── aws-sam-cli +│ │ ├── devcontainer-feature.json +│ │ ├── install.sh +│ │ └── README.md │ ├── gcloud-cli │ │ ├── devcontainer-feature.json │ │ ├── install.sh @@ -63,6 +69,7 @@ Similar to the [`devcontainers/features`](https://github.com/devcontainers/featu ├── test │ ├── ag │ ├── amazon-q-cli +│ ├── aws-sam-cli │ ├── gcloud-cli │ └── zip ... diff --git a/src/aws-sam-cli/README.md b/src/aws-sam-cli/README.md new file mode 100644 index 0000000..1703ca4 --- /dev/null +++ b/src/aws-sam-cli/README.md @@ -0,0 +1,75 @@ +# AWS SAM CLI (aws-sam-cli) + +Install [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html) - AWS Serverless Application Model CLI for serverless application development. + +## Example Usage + +```json +"features": { + "ghcr.io/jajera/features/aws-sam-cli:1": {} +} +``` + +## Options + +| Options Id | Description | Type | Default Value | +|-----|-----|-----|-----| +| version | Select or enter a AWS SAM CLI version | string | latest | + +## Installation Details + +This feature automatically: + +- **Detects system architecture** (x86_64, aarch64) and operating system +- **Downloads appropriate binary** for your system (Linux or macOS) +- **Installs for both root and non-root users**: + - Root: Installs to `/usr/local/bin` with system-wide bash completion + - Non-root: Installs to `~/.local/bin` with user-specific bash completion +- **Sets up bash completion** automatically +- **Handles dependencies** (curl, unzip) with appropriate privilege handling + +## Supported Platforms + +- **Operating Systems**: Linux, macOS +- **Architectures**: x86_64 (Intel/AMD 64-bit), aarch64 (ARM 64-bit) +- **Base Images**: Tested on Debian, Ubuntu, and Microsoft DevContainer base images +- **User Contexts**: Works as both root and non-root users + +## Commands Available + +After installation, the following commands are available: + +- `sam` - Main AWS SAM CLI command for serverless application development + +## Common SAM CLI Commands + +```bash +# Initialize a new SAM application +sam init + +# Build your SAM application +sam build + +# Deploy your SAM application +sam deploy + +# Start local API Gateway +sam local start-api + +# Invoke a function locally +sam local invoke + +# Validate SAM template +sam validate +``` + +## Compatibility + +- **Package Management**: Uses apt-get (Debian/Ubuntu), yum (RHEL/CentOS), or Homebrew (macOS) with appropriate privileges +- **PATH**: Automatically adds `~/.local/bin` to PATH for non-root installations +- **Dependencies**: Automatically installs curl and unzip if not present + +## Reference + +- [AWS SAM CLI Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html) +- [AWS SAM CLI GitHub Repository](https://github.com/aws/aws-sam-cli) diff --git a/src/aws-sam-cli/devcontainer-feature.json b/src/aws-sam-cli/devcontainer-feature.json new file mode 100644 index 0000000..c97fe22 --- /dev/null +++ b/src/aws-sam-cli/devcontainer-feature.json @@ -0,0 +1,10 @@ +{ + "name": "AWS SAM CLI", + "id": "aws-sam-cli", + "version": "1.0.0", + "description": "Installs AWS SAM CLI for serverless application development", + "documentationURL": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html", + "installsAfter": [ + "ghcr.io/devcontainers/features/common-utils" + ] +} diff --git a/src/aws-sam-cli/install.sh b/src/aws-sam-cli/install.sh new file mode 100755 index 0000000..2732f32 --- /dev/null +++ b/src/aws-sam-cli/install.sh @@ -0,0 +1,274 @@ +#!/bin/sh +set -e + +echo "Activating feature 'aws-sam-cli'" + +# Detect OS and architecture +OS="$(uname -s)" +ARCH="$(uname -m)" + +echo "✅ Detected OS: $OS" +echo "✅ Detected ARCH: $ARCH" + +# Skip install if already present +if command -v sam >/dev/null 2>&1; then + echo "✅ AWS SAM CLI is already installed. Skipping install." + sam --version + exit 0 +fi + +echo "Installing required dependencies..." + +# Function to run package manager with appropriate privileges +run_pkg_mgr() { + if [ "$(id -u)" -eq 0 ]; then + "$@" + elif command -v sudo >/dev/null 2>&1; then + sudo "$@" + else + echo "⚠️ Warning: Running as non-root user without sudo. Package installation may fail." + "$@" + fi +} + +# Install dependencies based on OS +case "$OS" in +Linux) + # Try to install dependencies, but don't fail if we can't + if command -v apt-get >/dev/null 2>&1; then + echo "Installing dependencies via apt-get..." + if ! run_pkg_mgr apt-get update -y; then + echo "⚠️ Warning: Could not update package lists. Proceeding anyway..." + fi + + if ! command -v curl >/dev/null 2>&1 || ! command -v unzip >/dev/null 2>&1; then + echo "Installing curl and unzip..." + if ! run_pkg_mgr apt-get install -y curl unzip; then + echo "⚠️ Warning: Could not install curl/unzip via apt. Checking if already available..." + if ! command -v curl >/dev/null 2>&1; then + echo "❌ ERROR: curl is required but not available" + exit 1 + fi + if ! command -v unzip >/dev/null 2>&1; then + echo "❌ ERROR: unzip is required but not available" + exit 1 + fi + fi + else + echo "✅ curl and unzip are already available" + fi + elif command -v yum >/dev/null 2>&1; then + echo "Installing dependencies via yum..." + if ! command -v curl >/dev/null 2>&1 || ! command -v unzip >/dev/null 2>&1; then + if ! run_pkg_mgr yum install -y curl unzip; then + echo "⚠️ Warning: Could not install curl/unzip via yum. Checking if already available..." + if ! command -v curl >/dev/null 2>&1; then + echo "❌ ERROR: curl is required but not available" + exit 1 + fi + if ! command -v unzip >/dev/null 2>&1; then + echo "❌ ERROR: unzip is required but not available" + exit 1 + fi + fi + else + echo "✅ curl and unzip are already available" + fi + else + echo "⚠️ Warning: No supported package manager found. Checking if curl and unzip are available..." + if ! command -v curl >/dev/null 2>&1; then + echo "❌ ERROR: curl is required but not available" + exit 1 + fi + if ! command -v unzip >/dev/null 2>&1; then + echo "❌ ERROR: unzip is required but not available" + exit 1 + fi + fi + ;; +Darwin) + echo "Installing dependencies via Homebrew..." + if command -v brew >/dev/null 2>&1; then + if ! command -v curl >/dev/null 2>&1 || ! command -v unzip >/dev/null 2>&1; then + brew install curl unzip + else + echo "✅ curl and unzip are already available" + fi + else + echo "⚠️ Warning: Homebrew not found. Checking if curl and unzip are available..." + if ! command -v curl >/dev/null 2>&1; then + echo "❌ ERROR: curl is required but not available" + exit 1 + fi + if ! command -v unzip >/dev/null 2>&1; then + echo "❌ ERROR: unzip is required but not available" + exit 1 + fi + fi + ;; +*) + echo "❌ Unsupported operating system: $OS" + exit 1 + ;; +esac + +# Set install directory based on user permissions +if [ "$(id -u)" -eq 0 ]; then + INSTALL_DIR="/usr/local/bin" + COMPLETION_DIR="/etc/bash_completion.d" + BASHRC_FILE="/etc/bash.bashrc" + PROFILE_FILE="/etc/profile" + ZSHRC_FILE="/etc/zsh/zshrc" + ZPROFILE_FILE="/etc/zsh/zprofile" +else + INSTALL_DIR="$HOME/.local/bin" + COMPLETION_DIR="$HOME/.bash_completion.d" + BASHRC_FILE="$HOME/.bashrc" + PROFILE_FILE="$HOME/.profile" + ZSHRC_FILE="$HOME/.zshrc" + ZPROFILE_FILE="$HOME/.zprofile" + # Ensure local bin directory exists and is in PATH + mkdir -p "$INSTALL_DIR" + if ! echo "$PATH" | grep -q "$HOME/.local/bin"; then + export PATH="$HOME/.local/bin:$PATH" + fi +fi + +echo "📁 Install directory: $INSTALL_DIR" + +# Download and install +TEMP_DIR=$(mktemp -d) +cd "$TEMP_DIR" + +echo "⬇️ Downloading AWS SAM CLI..." + +# Determine architecture-specific URL +case "$ARCH" in +x86_64) + ARCH_NAME="x86_64" + ;; +aarch64|arm64) + ARCH_NAME="aarch64" + ;; +*) + echo "❌ Unsupported architecture: $ARCH" + exit 1 + ;; +esac + +# Download SAM CLI +if [ "$OS" = "Darwin" ]; then + # macOS + SAM_URL="https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-mac-${ARCH_NAME}.zip" +else + # Linux + SAM_URL="https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-${ARCH_NAME}.zip" +fi + +echo "Downloading from: $SAM_URL" +if ! curl -L -o sam-cli.zip "$SAM_URL"; then + echo "❌ ERROR: Failed to download AWS SAM CLI from $SAM_URL" + echo "Checking if the URL is accessible..." + curl -I "$SAM_URL" || echo "URL is not accessible" + exit 1 +fi + +# Verify the downloaded file is valid +if [ ! -s sam-cli.zip ]; then + echo "❌ ERROR: Downloaded file is empty" + exit 1 +fi + +# Check if the file is actually a zip file +if ! unzip -t sam-cli.zip >/dev/null 2>&1; then + echo "❌ ERROR: Downloaded file is not a valid zip file" + echo "File size: $(wc -c < sam-cli.zip) bytes" + echo "First 100 bytes:" + head -c 100 sam-cli.zip | hexdump -C + exit 1 +fi + +echo "📦 Installing AWS SAM CLI..." +unzip -o sam-cli.zip + +# Install SAM CLI +echo "Installing AWS SAM CLI to $INSTALL_DIR..." +if [ -f "dist/sam" ]; then + # Copy the entire dist directory to preserve the bundled Python libraries + cp -r dist "$INSTALL_DIR/" + chmod +x "$INSTALL_DIR/dist/sam" + # Create a symlink for easier access + ln -sf "$INSTALL_DIR/dist/sam" "$INSTALL_DIR/sam" +elif [ -f "sam" ]; then + cp sam "$INSTALL_DIR/" + chmod +x "$INSTALL_DIR/sam" +else + echo "❌ ERROR: Could not find sam binary in downloaded package" + ls -la + exit 1 +fi + +# Clean up +cd / +rm -rf "$TEMP_DIR" + +# Verify installation +if command -v sam >/dev/null 2>&1; then + echo "✅ AWS SAM CLI installed successfully." + sam --version +elif [ -f "$INSTALL_DIR/sam" ]; then + echo "✅ AWS SAM CLI installed successfully at $INSTALL_DIR/sam." + "$INSTALL_DIR/sam" --version +else + echo "❌ ERROR: 'sam' command not found in PATH or at $INSTALL_DIR" + echo "PATH: $PATH" + echo "Contents of $INSTALL_DIR:" + ls -la "$INSTALL_DIR" 2>/dev/null || echo "Directory does not exist" + exit 1 +fi + +# Setup bash completion +echo "Setting up bash completion..." +mkdir -p "$COMPLETION_DIR" + +COMPLETION_FILE="$COMPLETION_DIR/sam" +if command -v sam >/dev/null 2>&1; then + sam completion bash >"$COMPLETION_FILE" 2>/dev/null || { + echo "⚠️ Warning: Could not generate bash completion for sam" + } +elif [ -f "$INSTALL_DIR/sam" ]; then + "$INSTALL_DIR/sam" completion bash >"$COMPLETION_FILE" 2>/dev/null || { + echo "⚠️ Warning: Could not generate bash completion for sam" + } +fi + +# Source completion in the appropriate bashrc +if [ -f "$COMPLETION_FILE" ]; then + if [ "$(id -u)" -eq 0 ]; then + # System-wide completion + if ! grep -q 'source /etc/bash_completion.d/sam' "$BASHRC_FILE" 2>/dev/null; then + echo 'source /etc/bash_completion.d/sam' >>"$BASHRC_FILE" + fi + else + # User-specific completion + COMPLETION_LINE="source \$HOME/.bash_completion.d/sam" + grep -qxF "$COMPLETION_LINE" "$BASHRC_FILE" 2>/dev/null || echo "$COMPLETION_LINE" >>"$BASHRC_FILE" + fi +fi + +# For testing purposes, ensure SAM CLI command is visible in /usr/local/bin +# This helps with test visibility across different user contexts +if [ "$(id -u)" -ne 0 ]; then + local_bin_path="$HOME/.local/bin/sam" + usr_local_bin_path="/usr/local/bin/sam" + + if [ -f "$local_bin_path" ] && [ ! -f "$usr_local_bin_path" ]; then + echo "🔁 Attempting to symlink sam to /usr/local/bin for test visibility" + # Try to create symlink, but don't fail if we can't (permission issues) + ln -sf "$local_bin_path" "$usr_local_bin_path" 2>/dev/null || { + echo "⚠️ Could not create symlink to /usr/local/bin (permission denied) - continuing" + } + fi +fi + +echo "✅ AWS SAM CLI installation complete!" diff --git a/test/aws-sam-cli/test.sh b/test/aws-sam-cli/test.sh new file mode 100755 index 0000000..0ea5a3b --- /dev/null +++ b/test/aws-sam-cli/test.sh @@ -0,0 +1,42 @@ +#!/bin/bash +set -e + +# Import test library +source dev-container-features-test-lib + +# Basic checks - check multiple possible locations +check "sam command exists" bash -c 'command -v sam || test -f /usr/local/bin/sam || test -f ~/.local/bin/sam' + +# Test that sam command actually works - try different locations if needed +if command -v sam >/dev/null 2>&1; then + check "sam command works" sam --version +elif test -f /usr/local/bin/sam; then + check "sam command works" /usr/local/bin/sam --version +elif test -f ~/.local/bin/sam; then + check "sam command works" ~/.local/bin/sam --version +else + echo "❌ No sam command found in any expected location" + exit 1 +fi + +# Bash completion: check if installed and sourced +# Check for system-wide completion first +if test -f /etc/bash_completion.d/sam; then + if test -f /etc/bash.bashrc && grep -q "source /etc/bash_completion.d/sam" /etc/bash.bashrc; then + check "bash completion is sourced" echo "System-wide bash completion is properly configured" + else + echo "⚠️ System-wide bash completion file exists but not properly sourced" + fi +# Check for user-specific completion +elif test -f ~/.bash_completion.d/sam; then + if test -f ~/.bashrc && grep -q "source.*\.bash_completion\.d/sam" ~/.bashrc; then + check "bash completion is sourced" echo "User-specific bash completion is properly configured" + else + echo "⚠️ User-specific bash completion file exists but not properly sourced" + fi +else + echo "⚠️ Bash completion not found at /etc/bash_completion.d/sam or ~/.bash_completion.d/sam — skipping related check." +fi + +# Report results +reportResults