⚙️ Shell in Ansible
Leveraging shell scripting within Ansible playbooks and roles for maximum flexibility while maintaining Ansible's declarative benefits.
🎯 Ansible Shell Modules
Command Module
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 | # playbook.yml - Command module (no shell interpretation)
---
- name: System Information Gathering
hosts: all
tasks:
- name: Get system uptime
command: uptime
register: system_uptime
- name: Display uptime
debug:
msg: "System uptime: {{ system_uptime.stdout }}"
- name: Check disk space
command: df -h /
register: disk_space
- name: Verify sufficient disk space
assert:
that:
- "disk_space.stdout_lines[1].split()[4].rstrip('%') | int < 80"
fail_msg: "Disk usage exceeds 80%"
- name: Get process count
command: ps aux | wc -l
args:
stdin: "{{ lookup('pipe', 'ps aux') }}"
register: process_count
|
Shell Module
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111 | # shell-playbook.yml - Shell module (full shell interpretation)
---
- name: Advanced System Configuration
hosts: webservers
become: yes
tasks:
- name: Configure complex nginx setup
shell: |
#!/bin/bash
set -euo pipefail
# Create directories with proper permissions
mkdir -p /var/www/{html,logs,cache}
chown -R www-data:www-data /var/www
# Generate nginx configuration
cat > /etc/nginx/sites-available/default << 'EOF'
server {
listen 80;
server_name {{ inventory_hostname }};
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /static/ {
alias /var/www/html/static/;
expires 1y;
add_header Cache-Control "public, immutable";
}
}
EOF
# Test configuration
nginx -t
# Reload service
systemctl reload nginx
# Verify service status
systemctl is-active nginx
environment:
NGINX_VERSION: "{{ nginx_version | default('latest') }}"
- name: Deploy application with complex logic
shell: |
#!/bin/bash
set -euo pipefail
# Multi-step deployment with rollback capability
DEPLOY_DIR="/var/www/app"
BACKUP_DIR="/var/backups/app-$(date +%Y%m%d-%H%M%S)"
# Create backup
if [ -d "$DEPLOY_DIR" ]; then
echo "Creating backup..."
cp -r "$DEPLOY_DIR" "$BACKUP_DIR"
fi
# Deploy new version
echo "Deploying new version..."
rm -rf "$DEPLOY_DIR"
mkdir -p "$DEPLOY_DIR"
# Extract application
tar -xzf /tmp/app.tar.gz -C "$DEPLOY_DIR"
# Run database migrations
if [ -f "$DEPLOY_DIR/migrate.sh" ]; then
echo "Running database migrations..."
"$DEPLOY_DIR/migrate.sh"
fi
# Set permissions
chown -R www-data:www-data "$DEPLOY_DIR"
find "$DEPLOY_DIR" -type f -exec chmod 644 {} \;
find "$DEPLOY_DIR" -type d -exec chmod 755 {} \;
# Warm up cache
curl -s http://localhost/warmup > /dev/null
echo "Deployment completed successfully"
args:
creates: "/var/www/app/deploy-complete"
- name: Verify deployment
shell: |
#!/bin/bash
set -e
# Health checks
if ! curl -sf http://localhost/health; then
echo "Health check failed" >&2
exit 1
fi
# Performance check
RESPONSE_TIME=$(curl -w "%{time_total}" -o /dev/null -s http://localhost/)
if (( $(echo "$RESPONSE_TIME > 2.0" | bc -l) )); then
echo "Response time too slow: ${RESPONSE_TIME}s" >&2
exit 1
fi
# Mark deployment as complete
touch /var/www/app/deploy-complete
echo "Deployment verification passed"
|
🛠️ Advanced Shell Integration Patterns
Dynamic Variable Generation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 | # dynamic-vars.yml - Generate variables with shell scripts
---
- name: Dynamic Variable Generation
hosts: all
tasks:
- name: Generate system fingerprint
shell: |
#!/bin/bash
set -e
# Generate unique system fingerprint
echo "{"
echo " \"hostname\": \"$(hostname)\","
echo " \"uuid\": \"$(cat /etc/machine-id 2>/dev/null || uuidgen)\","
echo " \"os\": \"$(uname -s)\","
echo " \"arch\": \"$(uname -m)\","
echo " \"kernel\": \"$(uname -r)\","
echo " \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\""
echo "}"
register: system_fingerprint
- name: Set facts from JSON output
set_fact:
system_info: "{{ system_fingerprint.stdout | from_json }}"
- name: Display system information
debug:
msg: |
System Information:
Hostname: {{ system_info.hostname }}
UUID: {{ system_info.uuid }}
OS: {{ system_info.os }}
Architecture: {{ system_info.arch }}
Kernel: {{ system_info.kernel }}
|
Conditional Logic with Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54 | # conditional-logic.yml - Complex conditional logic
---
- name: Conditional System Configuration
hosts: all
tasks:
- name: Determine system type and configure accordingly
shell: |
#!/bin/bash
set -e
# Complex logic to determine system configuration
SYSTEM_TYPE="unknown"
CONFIG_PROFILE="default"
# Check if this is a container
if [ -f /.dockerenv ] || [ -f /run/.containerenv ]; then
SYSTEM_TYPE="container"
CONFIG_PROFILE="container"
elif [ -d /proc/vz ] && [ ! -d /proc/bc ]; then
SYSTEM_TYPE="openvz"
CONFIG_PROFILE="virtual"
elif systemd-detect-virt --quiet; then
SYSTEM_TYPE="vm"
CONFIG_PROFILE="virtual"
else
SYSTEM_TYPE="baremetal"
CONFIG_PROFILE="physical"
fi
# Additional checks
MEMORY_GB=$(free -g | awk '/^Mem:/{print $2}')
CPU_CORES=$(nproc)
if [ "$MEMORY_GB" -gt 16 ] && [ "$CPU_CORES" -gt 8 ]; then
CONFIG_PROFILE="${CONFIG_PROFILE}-highperf"
elif [ "$MEMORY_GB" -lt 4 ] || [ "$CPU_CORES" -lt 2 ]; then
CONFIG_PROFILE="${CONFIG_PROFILE}-lowres"
fi
echo "{"
echo " \"system_type\": \"$SYSTEM_TYPE\","
echo " \"config_profile\": \"$CONFIG_PROFILE\","
echo " \"memory_gb\": $MEMORY_GB,"
echo " \"cpu_cores\": $CPU_CORES"
echo "}"
register: system_classification
- name: Set system classification facts
set_fact:
classification: "{{ system_classification.stdout | from_json }}"
- name: Apply configuration based on system type
include_tasks: "profiles/{{ classification.config_profile }}.yml"
when: classification.config_profile != "default"
|
Error Handling and Retry Logic
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66 | # error-handling.yml - Robust error handling with retries
---
- name: Robust System Operations
hosts: all
tasks:
- name: Install package with retry logic
shell: |
#!/bin/bash
set -e
PACKAGE_NAME="{{ package_name }}"
MAX_ATTEMPTS=3
ATTEMPT=1
while [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
echo "Attempt $ATTEMPT/$MAX_ATTEMPTS: Installing $PACKAGE_NAME"
if apt-get update && apt-get install -y "$PACKAGE_NAME"; then
echo "Package installed successfully"
exit 0
else
EXIT_CODE=$?
echo "Installation failed with exit code $EXIT_CODE"
if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then
echo "Max attempts reached, giving up"
exit $EXIT_CODE
fi
# Wait before retry with exponential backoff
WAIT_TIME=$((2 ** ATTEMPT))
echo "Waiting $WAIT_TIME seconds before retry..."
sleep $WAIT_TIME
ATTEMPT=$((ATTEMPT + 1))
fi
done
register: package_install_result
ignore_errors: yes
- name: Handle installation result
block:
- name: Check if installation succeeded
assert:
that:
- package_install_result.rc == 0
fail_msg: "Package installation failed after all retries"
rescue:
- name: Log failure and attempt alternative installation
shell: |
#!/bin/bash
echo "Primary installation failed, trying alternative method..."
# Alternative installation logic here
echo "Alternative installation attempted"
delegate_to: localhost
- name: Notify administrators
mail:
host: localhost
port: 25
subject: "Package Installation Failed - {{ inventory_hostname }}"
body: |
Package installation for {{ package_name }} failed on {{ inventory_hostname }}.
Please investigate and resolve manually.
to: admin@example.com
|
📋 Shell Script Best Practices in Ansible
Secure Script Handling
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60 | # secure-scripts.yml - Secure shell script management
---
- name: Secure Script Execution
hosts: all
become: yes
tasks:
- name: Create secure temporary directory
tempfile:
state: directory
suffix: ansible-secure
register: secure_temp
- name: Copy secure script
copy:
content: |
#!/bin/bash
set -euo pipefail
# Secure script with proper error handling
SECURE_TEMP="{{ secure_temp.path }}"
# Validate inputs
validate_required() {
local var_name="$1"
local var_value="${!var_name:-}"
if [ -z "$var_value" ]; then
echo "Error: Required variable $var_name is not set" >&2
return 1
fi
}
# Secure credential handling
if [ -f "/run/secrets/{{ secret_name }}" ]; then
SECRET_VALUE=$(cat "/run/secrets/{{ secret_name }}")
else
echo "Error: Secret {{ secret_name }} not found" >&2
exit 1
fi
# Main logic with proper cleanup
trap 'rm -rf "$SECURE_TEMP"' EXIT
# Your secure logic here
echo "Executing secure operation..."
dest: "{{ secure_temp.path }}/secure-script.sh"
mode: '0700'
- name: Execute secure script
shell: "{{ secure_temp.path }}/secure-script.sh"
environment:
SECRET_NAME: "{{ secret_name }}"
SECURE_TEMP: "{{ secure_temp.path }}"
- name: Clean up temporary directory
file:
path: "{{ secure_temp.path }}"
state: absent
when: secure_temp.path is defined
|
Environment-Aware Scripts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 | # environment-aware.yml - Environment-specific shell integration
---
- name: Environment-Aware Configuration
hosts: all
vars:
environment_configs:
development:
script: scripts/dev-setup.sh
vars:
LOG_LEVEL: debug
DB_HOST: localhost
staging:
script: scripts/staging-setup.sh
vars:
LOG_LEVEL: info
DB_HOST: staging-db.example.com
production:
script: scripts/prod-setup.sh
vars:
LOG_LEVEL: warn
DB_HOST: prod-db.example.com
current_config: "{{ environment_configs[environment | default('development')] }}"
tasks:
- name: Execute environment-specific script
shell: |
#!/bin/bash
set -euo pipefail
# Source environment variables
export LOG_LEVEL="{{ current_config.vars.LOG_LEVEL }}"
export DB_HOST="{{ current_config.vars.DB_HOST }}"
export ENVIRONMENT="{{ environment | default('development') }}"
export TIMESTAMP="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
# Execute environment-specific setup
{{ current_config.script }}
environment:
LOG_LEVEL: "{{ current_config.vars.LOG_LEVEL }}"
DB_HOST: "{{ current_config.vars.DB_HOST }}"
ENVIRONMENT: "{{ environment | default('development') }}"
TIMESTAMP: "{{ lookup('pipe', 'date -u +%Y-%m-%dT%H:%M:%SZ') }}"
register: environment_setup_result
- name: Verify environment setup
assert:
that:
- environment_setup_result.rc == 0
fail_msg: "Environment setup failed: {{ environment_setup_result.stderr }}"
|
🎯 Real-World Examples
Example 1: Database Migration with Rollback
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115 | # database-migration.yml - Complex database migration
---
- name: Database Migration with Rollback
hosts: database_servers
become: yes
vars:
backup_dir: "/var/backups/db-{{ ansible_date_time.iso8601_basic_short }}"
tasks:
- name: Create database backup
shell: |
#!/bin/bash
set -euo pipefail
BACKUP_DIR="{{ backup_dir }}"
DB_NAME="{{ db_name }}"
echo "Creating database backup..."
mkdir -p "$BACKUP_DIR"
# Create backup with timestamp
BACKUP_FILE="$BACKUP_DIR/backup-$(date +%Y%m%d-%H%M%S).sql"
if pg_dump "$DB_NAME" > "$BACKUP_FILE"; then
echo "Backup created: $BACKUP_FILE"
# Compress backup
gzip "$BACKUP_FILE"
echo "Backup compressed: ${BACKUP_FILE}.gz"
# Record backup info
echo "{"
echo " \"backup_file\": \"${BACKUP_FILE}.gz\","
echo " \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\","
echo " \"size_bytes\": $(stat -c%s "${BACKUP_FILE}.gz")"
echo "}" > "$BACKUP_DIR/backup-info.json"
else
echo "Backup failed" >&2
exit 1
fi
register: db_backup_result
- name: Run database migrations
shell: |
#!/bin/bash
set -euo pipefail
MIGRATION_DIR="/opt/app/migrations"
DB_NAME="{{ db_name }}"
echo "Running database migrations..."
# Check current schema version
CURRENT_VERSION=$(psql -d "$DB_NAME" -t -c "SELECT version FROM schema_migrations ORDER BY applied_at DESC LIMIT 1;" 2>/dev/null || echo "0")
# Apply migrations in order
find "$MIGRATION_DIR" -name "*.sql" -type f | sort | while read -r migration; do
MIGRATION_VERSION=$(basename "$migration" | cut -d'_' -f1)
if [ "$MIGRATION_VERSION" -gt "$CURRENT_VERSION" ]; then
echo "Applying migration $MIGRATION_VERSION..."
if psql -d "$DB_NAME" -f "$migration"; then
# Record successful migration
psql -d "$DB_NAME" -c "INSERT INTO schema_migrations (version, applied_at) VALUES ('$MIGRATION_VERSION', NOW());"
echo "Migration $MIGRATION_VERSION applied successfully"
else
echo "Migration $MIGRATION_VERSION failed" >&2
exit 1
fi
fi
done
echo "All migrations completed successfully"
register: db_migration_result
ignore_errors: yes
- name: Handle migration failure with rollback
block: []
rescue:
- name: Restore database from backup
shell: |
#!/bin/bash
set -e
BACKUP_DIR="{{ backup_dir }}"
DB_NAME="{{ db_name }}"
# Find latest backup
LATEST_BACKUP=$(ls -t "$BACKUP_DIR"/backup-*.sql.gz | head -1)
if [ -n "$LATEST_BACKUP" ]; then
echo "Restoring database from backup: $LATEST_BACKUP"
# Restore database
gunzip -c "$LATEST_BACKUP" | psql "$DB_NAME"
echo "Database restored successfully"
else
echo "No backup found for rollback" >&2
exit 1
fi
when: db_backup_result is succeeded
- name: Notify team of rollback
mail:
host: localhost
subject: "Database Migration Rollback - {{ inventory_hostname }}"
body: |
Database migration failed on {{ inventory_hostname }}.
Automatic rollback has been performed.
Please investigate the issue.
to: devops-team@example.com
|
Example 2: Multi-Stage Application Deployment
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156 | # application-deployment.yml - Multi-stage deployment
---
- name: Multi-Stage Application Deployment
hosts: application_servers
become: yes
vars:
app_version: "{{ app_version | default('latest') }}"
deploy_dir: "/var/www/app"
backup_dir: "/var/backups/app-{{ ansible_date_time.iso8601_basic_short }}"
tasks:
- name: Stage 1 - Prepare Environment
shell: |
#!/bin/bash
set -euo pipefail
DEPLOY_DIR="{{ deploy_dir }}"
BACKUP_DIR="{{ backup_dir }}"
echo "=== Stage 1: Prepare Environment ==="
# Create deployment directories
mkdir -p "$DEPLOY_DIR" "$BACKUP_DIR"
# Create backup of current deployment
if [ -d "$DEPLOY_DIR/current" ]; then
echo "Creating backup of current deployment..."
cp -r "$DEPLOY_DIR/current" "$BACKUP_DIR/current-$(date +%Y%m%d-%H%M%S)"
fi
# Prepare staging directory
STAGING_DIR="$DEPLOY_DIR/staging"
rm -rf "$STAGING_DIR"
mkdir -p "$STAGING_DIR"
echo "Environment prepared successfully"
- name: Stage 2 - Deploy Application
shell: |
#!/bin/bash
set -euo pipefail
DEPLOY_DIR="{{ deploy_dir }}"
APP_VERSION="{{ app_version }}"
STAGING_DIR="$DEPLOY_DIR/staging"
echo "=== Stage 2: Deploy Application ==="
# Download application
echo "Downloading application version $APP_VERSION..."
curl -L "https://artifacts.example.com/app-$APP_VERSION.tar.gz" | tar -xz -C "$STAGING_DIR"
# Install dependencies
if [ -f "$STAGING_DIR/requirements.txt" ]; then
echo "Installing Python dependencies..."
pip3 install -r "$STAGING_DIR/requirements.txt"
fi
# Set permissions
chown -R www-data:www-data "$STAGING_DIR"
find "$STAGING_DIR" -type f -exec chmod 644 {} \;
find "$STAGING_DIR" -type d -exec chmod 755 {} \;
# Create symlinks
ln -sf "$STAGING_DIR" "$DEPLOY_DIR/new"
echo "Application deployed successfully"
- name: Stage 3 - Test and Validate
shell: |
#!/bin/bash
set -e
DEPLOY_DIR="{{ deploy_dir }}"
NEW_VERSION_DIR="$DEPLOY_DIR/new"
echo "=== Stage 3: Test and Validate ==="
# Run unit tests
if [ -f "$NEW_VERSION_DIR/tests/run-tests.sh" ]; then
echo "Running unit tests..."
"$NEW_VERSION_DIR/tests/run-tests.sh"
fi
# Health check
echo "Performing health check..."
timeout 30 bash -c 'until curl -sf http://localhost/health; do sleep 2; done'
# Performance test
RESPONSE_TIME=$(curl -w "%{time_total}" -o /dev/null -s http://localhost/)
if (( $(echo "$RESPONSE_TIME > 5.0" | bc -l) )); then
echo "Performance test failed: ${RESPONSE_TIME}s" >&2
exit 1
fi
echo "Validation completed successfully"
register: validation_result
ignore_errors: yes
- name: Stage 4 - Activate Deployment
shell: |
#!/bin/bash
set -euo pipefail
DEPLOY_DIR="{{ deploy_dir }}"
NEW_VERSION_DIR="$DEPLOY_DIR/new"
echo "=== Stage 4: Activate Deployment ==="
# Atomic switch
mv "$NEW_VERSION_DIR" "$DEPLOY_DIR/current-tmp"
ln -sfn "$DEPLOY_DIR/current-tmp" "$DEPLOY_DIR/current"
rm -rf "$DEPLOY_DIR/current-tmp"
# Restart services
systemctl restart nginx
systemctl restart app
# Verify services
systemctl is-active nginx
systemctl is-active app
echo "Deployment activated successfully"
when: validation_result is succeeded
- name: Stage 5 - Cleanup and Notification
shell: |
#!/bin/bash
set -e
DEPLOY_DIR="{{ deploy_dir }}"
BACKUP_DIR="{{ backup_dir }}"
echo "=== Stage 5: Cleanup and Notification ==="
# Cleanup old backups (keep last 5)
ls -td "$BACKUP_DIR"/*/ 2>/dev/null | tail -n +6 | xargs rm -rf
# Send notification
curl -X POST "{{ slack_webhook }}" \
-H "Content-Type: application/json" \
-d "{
\"text\": \"Application deployment completed successfully\",
\"attachments\": [{
\"color\": \"good\",
\"fields\": [
{\"title\": \"Host\", \"value\": \"{{ inventory_hostname }}\", \"short\": true},
{\"title\": \"Version\", \"value\": \"{{ app_version }}\", \"short\": true},
{\"title\": \"Timestamp\", \"value\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}
]
}]
}"
echo "Cleanup and notification completed"
|
🧪 Testing and Validation
Unit Testing Shell Scripts in Ansible
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113 | #!/bin/bash
# test-ansible-scripts.sh - Test framework for Ansible shell scripts
# Setup test environment
setup_test_environment() {
TEST_DIR=$(mktemp -d)
cd "$TEST_DIR"
# Create mock directories
mkdir -p {/var/www,/var/backups,/opt/app/migrations}
# Create mock files
echo "SELECT version FROM schema_migrations ORDER BY applied_at DESC LIMIT 1;" > mock-query.sql
echo "Mock migration content" > /opt/app/migrations/001_initial.sql
# Export test variables
export TEST_DIR
export MOCK_DB_HOST="localhost"
export MOCK_DB_NAME="testdb"
}
# Test script execution
test_script_execution() {
local script_content="$1"
local expected_exit_code="${2:-0}"
echo "Testing script execution..."
# Create temporary script
local temp_script
temp_script=$(mktemp)
echo "$script_content" > "$temp_script"
chmod +x "$temp_script"
# Execute script
local exit_code=0
"$temp_script" || exit_code=$?
if [ "$exit_code" -eq "$expected_exit_code" ]; then
echo "✅ Script execution test passed"
else
echo "❌ Script execution failed with exit code $exit_code (expected $expected_exit_code)" >&2
return 1
fi
rm -f "$temp_script"
}
# Test environment variable handling
test_environment_variables() {
local script_content="$1"
local required_vars=("$@")
echo "Testing environment variable handling..."
# Test with missing variables
for var in "${required_vars[@]}"; do
# Temporarily unset variable
local original_value="${!var}"
unset "$var"
# Test script behavior
if echo "$script_content" | bash >/dev/null 2>&1; then
echo "❌ Script should fail when $var is missing" >&2
return 1
fi
# Restore variable
if [ -n "$original_value" ]; then
export "$var"="$original_value"
fi
done
echo "✅ Environment variable handling test passed"
}
# Run all tests
run_all_tests() {
setup_test_environment
# Test basic script execution
local test_script='#!/bin/bash
echo "Hello, World!"
exit 0'
test_script_execution "$test_script" 0
# Test error handling
local error_script='#!/bin/bash
echo "This will fail" >&2
exit 1'
test_script_execution "$error_script" 1
# Test environment variables
local env_script='#!/bin/bash
if [ -z "$REQUIRED_VAR" ]; then
echo "REQUIRED_VAR is not set" >&2
exit 1
fi
echo "REQUIRED_VAR is set to: $REQUIRED_VAR"'
export REQUIRED_VAR="test_value"
test_script_execution "$env_script" 0
unset REQUIRED_VAR
test_script_execution "$env_script" 1
echo "All tests completed successfully"
}
# Execute tests
run_all_tests
|
🧾 Summary
✅ Command module: For simple, secure commands without shell interpretation
✅ Shell module: For complex logic with full shell features
✅ Dynamic variables: Generate variables using shell scripts
✅ Error handling: Robust retry logic and failure management
✅ Security: Secure credential and temporary file handling
✅ Environment awareness: Context-specific configuration
✅ Testing: Comprehensive test frameworks for shell scripts
🧾 See Also