-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdeploy
More file actions
executable file
·341 lines (281 loc) · 11 KB
/
Copy pathdeploy
File metadata and controls
executable file
·341 lines (281 loc) · 11 KB
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
#!/bin/bash
# deploy - Simplified Django Deployment Script
#
# Usage: ./deploy <repository_url> <branch>
#
# Parameters:
# repository_url - Git repository URL (https:// or git@)
# branch - Branch name to deploy (slashes converted to hyphens)
#
# Examples:
# ./deploy https://github.com/myorg/myapp.git main
# ./deploy git@github.com:myorg/myapp.git feature/new-ui
# ./deploy https://github.com/myorg/myapp.git qa
set -e # Exit on any error
# Source common logging utilities
source "$(dirname "$0")/scripts/logging-utils.sh"
# Additional logging function for deployment steps
log_step() {
if [[ -t 1 ]]; then
echo -e "${BLUE}[STEP]${NC} $1"
else
echo "[STEP] $1"
fi
}
# Show usage information
show_usage() {
cat << EOF
Usage: ./deploy <repository_url> <branch>
PARAMETERS:
repository_url Git repository URL (https:// or git@)
branch Branch name to deploy (uses deploy-{branch}.yml)
BRANCH NAMING:
- Branch names with slashes are converted to hyphens for filesystem compatibility
- Each branch uses its own configuration file: deploy-{branch}.yml
- Examples: 'main' → deploy-main.yml, 'feature/auth' → deploy-feature-auth.yml
EXAMPLES:
# Deploy main branch
./deploy https://github.com/myorg/myapp.git main
# Deploy feature branch (slash converted to hyphen)
./deploy git@github.com:myorg/myapp.git feature/new-ui
# Deploy qa branch
./deploy https://github.com/myorg/myapp.git qa
# Deploy development branch
./deploy git@github.com:myorg/myapp.git dev
REQUIREMENTS:
- Ubuntu LTS server with Python 3.8+
- Git installed and configured
- SSH keys configured for git@ URLs
- sudo privileges for system operations
EOF
}
# Validate parameters
# Convert branch name to filesystem-safe format
convert_branch_name() {
local branch="$1"
# Replace slashes with hyphens for filesystem compatibility
echo "${branch//\//-}"
}
validate_parameters() {
if [[ $# -lt 2 ]]; then
log_error "Missing required parameters"
show_usage
exit 1
fi
REPO_URL="$1"
BRANCH="$2"
# Validate repository URL format
if [[ ! "$REPO_URL" =~ ^(https://|git@|git://|file://|/) ]]; then
log_error "Invalid repository URL format. Must start with 'https://', 'git@', 'git://', 'file://', or be an absolute path"
exit 1
fi
# Convert branch name for filesystem compatibility
NORMALIZED_BRANCH=$(convert_branch_name "$BRANCH")
log_info "Repository: $REPO_URL"
log_info "Branch: $BRANCH"
log_info "Normalized branch: $NORMALIZED_BRANCH"
}
# Resolve configuration file with fallback logic
resolve_config_file() {
local temp_dir="$1"
local normalized_branch="$2"
# Try branch-specific config first: deploy-{normalized-branch}.yml
local branch_config="deploy-${normalized_branch}.yml"
if [[ -f "$temp_dir/$branch_config" ]]; then
echo "$branch_config"
return 0
fi
# Fall back to default config: deploy.yml
local default_config="deploy.yml"
if [[ -f "$temp_dir/$default_config" ]]; then
echo "$default_config"
return 0
fi
# No valid config found
return 1
}
# Validate configuration file exists and is valid YAML
validate_config_file() {
local repo_url="$1"
local branch="$2"
local temp_dir="/tmp/config-check-$(date +%s)-$$"
log_step "Resolving and validating configuration file" >&2
# Clone repository to temporary location for config validation
log_info "Cloning repository for configuration validation..." >&2
if ! git clone --depth 1 --branch "$branch" "$repo_url" "$temp_dir" >&2 2>/dev/null; then
# Try cloning without specifying branch, then checkout
if ! git clone --depth 1 "$repo_url" "$temp_dir" >&2; then
log_error "Failed to clone repository for configuration validation: $repo_url" >&2
exit 1
fi
cd "$temp_dir"
if ! git checkout "$branch" >&2 2>/dev/null; then
log_error "Failed to checkout '$branch' for configuration validation" >&2
cleanup_temp_dir "$temp_dir"
exit 1
fi
else
cd "$temp_dir"
fi
# Resolve configuration file using fallback logic
local config_file
if ! config_file=$(resolve_config_file "$temp_dir" "$(convert_branch_name "$branch")"); then
log_error "❌ No valid configuration file found in repository" >&2
log_error "Looking for:" >&2
log_error " 1. deploy-$(convert_branch_name "$branch").yml (branch-specific)" >&2
log_error " 2. deploy.yml (default fallback)" >&2
log_error "" >&2
log_error "Available deployment configuration files:" >&2
if ls "$temp_dir"/deploy*.yml >&2 2>/dev/null; then
log_error "Please create 'deploy.yml' as default or 'deploy-$(convert_branch_name "$branch").yml' for this branch" >&2
else
log_error "No deployment configuration files found in repository" >&2
log_error "Please create a deployment configuration file (deploy.yml or deploy-$(convert_branch_name "$branch").yml)" >&2
fi
cleanup_temp_dir "$temp_dir"
exit 1
fi
log_info "✅ Using configuration file: $config_file" >&2
# Export the resolved config file for use by the main deployment
export RESOLVED_CONFIG_FILE="$config_file"
# Validate YAML syntax
log_info "Validating YAML syntax..." >&2
if ! python3 -c "import yaml; yaml.safe_load(open('$config_file'))" 2>/dev/null; then
log_error "❌ Configuration file '$config_file' contains invalid YAML syntax" >&2
log_error "Please check the file for syntax errors (indentation, quotes, etc.)" >&2
cleanup_temp_dir "$temp_dir"
exit 1
fi
# Validate required fields
log_info "Validating required configuration fields..." >&2
if ! python3 -c "
import yaml
import sys
required_fields = ['name', 'repo', 'python_version', 'services']
config = yaml.safe_load(open('$config_file'))
missing_fields = []
for field in required_fields:
if field not in config:
missing_fields.append(field)
if missing_fields:
print(f'Missing required fields: {missing_fields}', file=sys.stderr)
sys.exit(1)
if not isinstance(config.get('services'), list) or len(config['services']) == 0:
print('Configuration must have at least one service defined', file=sys.stderr)
sys.exit(1)
print('✓ Configuration validation passed')
" 2>&1; then
log_error "❌ Configuration file '$config_file' is missing required fields or has invalid structure" >&2
cleanup_temp_dir "$temp_dir"
exit 1
fi
log_info "✅ Configuration file validation passed" >&2
cleanup_temp_dir "$temp_dir"
}
# Clone repository and checkout specified branch
clone_and_checkout() {
local repo_url="$1"
local branch="$2"
local temp_dir="/tmp/deployment-$(date +%s)-$$"
log_step "Cloning repository and checking out $branch" >&2
# Clone repository
log_info "Cloning repository to $temp_dir" >&2
if ! git clone "$repo_url" "$temp_dir" >&2; then
log_error "Failed to clone repository: $repo_url" >&2
exit 1
fi
# Change to repository directory
cd "$temp_dir"
# Fetch all branches and tags
log_info "Fetching all branches and tags" >&2
git fetch --all --tags >&2
# Try to checkout as branch first, then as commit SHA
log_info "Checking out $branch" >&2
if git checkout "$branch" >&2 2>/dev/null; then
log_info "Successfully checked out branch: $branch" >&2
elif git checkout -b "deploy-$branch" "$branch" >&2 2>/dev/null; then
log_info "Successfully checked out commit SHA: $branch" >&2
elif git checkout "origin/$branch" -b "$branch" >&2 2>/dev/null; then
log_info "Successfully checked out remote branch: origin/$branch" >&2
else
log_error "Failed to checkout '$branch'. Branch or commit not found." >&2
cleanup_temp_dir "$temp_dir"
exit 1
fi
log_info "✓ Repository prepared at $temp_dir" >&2
echo "$temp_dir" # Return temp directory path to stdout
}
# Clean up temporary directory
cleanup_temp_dir() {
local temp_dir="$1"
if [[ -n "$temp_dir" && -d "$temp_dir" ]]; then
log_info "Cleaning up temporary directory: $temp_dir"
rm -rf "$temp_dir"
fi
}
# Main execution function
main() {
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
local deployer_script="$script_dir/scripts/deploy.py"
# Create deployment log with timestamp naming convention (YYYYMMDDHHMMSS.log)
local log_timestamp=$(date +%Y%m%d%H%M%S)
local log_file="$script_dir/logs/${log_timestamp}.log"
# Ensure logs directory exists
mkdir -p "$script_dir/logs"
# Validate input parameters first
validate_parameters "$@"
# Create deployment header in log
{
echo "[DEPLOY] $REPO_URL $BRANCH"
echo "========================================"
echo ""
} | tee "$log_file"
log_step "Starting Django deployment automation" | tee -a "$log_file"
# Validate configuration file before proceeding with full deployment
validate_config_file "$REPO_URL" "$BRANCH" 2>&1 | tee -a "$log_file"
# Use the resolved config file from validation
CONFIG_FILE="$RESOLVED_CONFIG_FILE"
echo "DEBUG: CONFIG_FILE is: $CONFIG_FILE" | tee -a "$log_file"
# If CONFIG_FILE is empty, default to deploy.yml
if [[ -z "$CONFIG_FILE" ]]; then
CONFIG_FILE="deploy.yml"
echo "DEBUG: Defaulting CONFIG_FILE to: $CONFIG_FILE" | tee -a "$log_file"
fi
# Verify deployer script exists
if [[ ! -f "$deployer_script" ]]; then
log_error "Deployer script not found: $deployer_script" | tee -a "$log_file"
exit 1
fi
# Clone repository and checkout branch
local temp_repo_dir
temp_repo_dir=$(clone_and_checkout "$REPO_URL" "$BRANCH" 2> >(tee -a "$log_file" >&2))
# Ensure cleanup happens on exit
trap "cleanup_temp_dir '$temp_repo_dir'" EXIT
# Run the Python deployer with the config from the cloned repository
log_step "Starting deployment with Python orchestrator" | tee -a "$log_file"
# Make config file path absolute since we're changing directories
local absolute_config_file="$temp_repo_dir/$CONFIG_FILE"
cd "$temp_repo_dir"
if python3 "$deployer_script" \
"$CONFIG_FILE" \
"$BRANCH" \
--verbose 2>&1 | tee -a "$log_file"; then
log_info "🎉 Deployment completed successfully!" | tee -a "$log_file"
log_info "📋 Full deployment log: $log_file"
exit 0
else
log_error "Deployment failed" | tee -a "$log_file"
log_error "📋 Full deployment log: $log_file"
exit 1
fi
}
# Handle command line arguments
case "${1:-}" in
-h|--help|help|"")
show_usage
exit 0
;;
*)
main "$@"
;;
esac