#!/usr/bin/env bash # # stack-status.sh - Show stack service status # # Usage: stack-status.sh -n [-f format] # # Environment variables: # PORTAINER_URL - Portainer instance URL (e.g., https://portainer.example.com:9443) # PORTAINER_API_KEY - API access token # # Options: # -n name Stack name (required) # -i id Stack ID (alternative to -n) # -f format Output format: table (default), json # -h Show this help set -euo pipefail # Default values STACK_NAME="" STACK_ID="" FORMAT="table" # Parse arguments while getopts "n:i:f:h" opt; do case $opt in n) STACK_NAME="$OPTARG" ;; i) STACK_ID="$OPTARG" ;; f) FORMAT="$OPTARG" ;; h) head -18 "$0" | grep "^#" | sed 's/^# \?//' exit 0 ;; *) echo "Usage: $0 -n [-f format]" >&2 exit 1 ;; esac done # Validate environment if [[ -z "${PORTAINER_URL:-}" ]]; then echo "Error: PORTAINER_URL environment variable not set" >&2 exit 1 fi if [[ -z "${PORTAINER_API_KEY:-}" ]]; then echo "Error: PORTAINER_API_KEY environment variable not set" >&2 exit 1 fi if [[ -z "$STACK_NAME" && -z "$STACK_ID" ]]; then echo "Error: Either -n or -i is required" >&2 exit 1 fi # Remove trailing slash from URL PORTAINER_URL="${PORTAINER_URL%/}" # Function to make API requests api_request() { local method="$1" local endpoint="$2" curl -s -w "\n%{http_code}" -X "$method" \ -H "X-API-Key: ${PORTAINER_API_KEY}" \ "${PORTAINER_URL}${endpoint}" } # Get stack info by name if [[ -n "$STACK_NAME" ]]; then response=$(api_request GET "/api/stacks") http_code=$(echo "$response" | tail -n1) body=$(echo "$response" | sed '$d') if [[ "$http_code" != "200" ]]; then echo "Error: Failed to list stacks (HTTP $http_code)" >&2 exit 1 fi stack_info=$(echo "$body" | jq --arg name "$STACK_NAME" '.[] | select(.Name == $name)') if [[ -z "$stack_info" || "$stack_info" == "null" ]]; then echo "Error: Stack '$STACK_NAME' not found" >&2 exit 1 fi STACK_ID=$(echo "$stack_info" | jq -r '.Id') ENDPOINT_ID=$(echo "$stack_info" | jq -r '.EndpointId') STACK_NAME=$(echo "$stack_info" | jq -r '.Name') else response=$(api_request GET "/api/stacks/${STACK_ID}") http_code=$(echo "$response" | tail -n1) body=$(echo "$response" | sed '$d') if [[ "$http_code" != "200" ]]; then echo "Error: Failed to get stack (HTTP $http_code)" >&2 exit 1 fi stack_info="$body" ENDPOINT_ID=$(echo "$stack_info" | jq -r '.EndpointId') STACK_NAME=$(echo "$stack_info" | jq -r '.Name') fi # Get stack type STACK_TYPE=$(echo "$stack_info" | jq -r '.Type') STACK_STATUS=$(echo "$stack_info" | jq -r 'if .Status == 1 then "active" elif .Status == 2 then "inactive" else "unknown" end') # Get containers for this stack # Containers are labeled with com.docker.compose.project or com.docker.stack.namespace response=$(api_request GET "/api/endpoints/${ENDPOINT_ID}/docker/containers/json?all=true") http_code=$(echo "$response" | tail -n1) body=$(echo "$response" | sed '$d') if [[ "$http_code" != "200" ]]; then echo "Error: Failed to get containers (HTTP $http_code)" >&2 exit 1 fi # Filter containers belonging to this stack # Check both compose project label and stack namespace label containers=$(echo "$body" | jq --arg name "$STACK_NAME" '[.[] | select( (.Labels["com.docker.compose.project"] == $name) or (.Labels["com.docker.stack.namespace"] == $name) )]') container_count=$(echo "$containers" | jq 'length') # Output based on format if [[ "$FORMAT" == "json" ]]; then jq -n \ --arg name "$STACK_NAME" \ --arg id "$STACK_ID" \ --arg status "$STACK_STATUS" \ --arg type "$STACK_TYPE" \ --argjson containers "$containers" \ '{ stack: { name: $name, id: ($id | tonumber), status: $status, type: (if $type == "1" then "swarm" elif $type == "2" then "compose" else "kubernetes" end) }, containers: [$containers[] | { name: .Names[0], id: .Id[0:12], image: .Image, state: .State, status: .Status, created: .Created }] }' exit 0 fi # Table output echo "Stack: $STACK_NAME (ID: $STACK_ID)" echo "Status: $STACK_STATUS" echo "Type: $(if [[ "$STACK_TYPE" == "1" ]]; then echo "swarm"; elif [[ "$STACK_TYPE" == "2" ]]; then echo "compose"; else echo "kubernetes"; fi)" echo "Containers: $container_count" echo "" if [[ "$container_count" -gt 0 ]]; then echo "CONTAINER ID NAME IMAGE STATE STATUS" echo "------------ -------------------------------------- ------------------------------ ---------- ------" echo "$containers" | jq -r '.[] | [ .Id[0:12], .Names[0], .Image, .State, .Status ] | @tsv' | while IFS=$'\t' read -r id name image state status; do # Clean up container name (remove leading /) name="${name#/}" # Truncate long values name="${name:0:38}" image="${image:0:30}" printf "%-12s %-38s %-30s %-10s %s\n" "$id" "$name" "$image" "$state" "$status" done else echo "No containers found for this stack." echo "" echo "Note: If the stack was recently created or is inactive, containers may not exist yet." fi