-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathagw
More file actions
executable file
·1749 lines (1487 loc) · 59.5 KB
/
agw
File metadata and controls
executable file
·1749 lines (1487 loc) · 59.5 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
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/bin/bash
# ==============================================================================
# UnitOne AgentGateway - Unified CLI
# ==============================================================================
#
# Single entry point for all AgentGateway operations.
#
# Usage:
# ./agw <command> [options]
#
# Commands:
# setup Interactive first-time setup
# scope Manage deployment scopes (dev, staging, prod)
# auth Configure OAuth authentication
# build Build and push image to ACR
# deploy Deploy to Azure Container Apps
# test Run E2E tests locally
# test-servers Deploy/manage test servers
# logs View container logs
# status Show deployment status
# help Show this help
#
# Examples:
# ./agw setup # First-time setup wizard
# ./agw scope set dev # Switch to dev scope
# ./agw build # Build and push to ACR
# ./agw deploy # Deploy latest image
# ./agw test # Run E2E tests locally
# ./agw logs # Stream container logs
#
# ==============================================================================
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
# ==============================================================================
# Helper Functions
# ==============================================================================
print_header() {
echo ""
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BOLD}${CYAN} $1${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
}
print_step() { echo -e "${CYAN}▶${NC} $1"; }
print_success() { echo -e "${GREEN}✓${NC} $1"; }
print_warning() { echo -e "${YELLOW}⚠${NC} $1"; }
print_error() { echo -e "${RED}✗${NC} $1"; }
print_info() { echo -e "${BLUE}ℹ${NC} $1"; }
# ==============================================================================
# Scope Management Functions
# ==============================================================================
# New structure: terraform/scopes/{global,users/<username>}/*.env
# Resolution order: users/<username>/<scope>.env -> global/<scope>.env
AGW_LOCAL_CONFIG_DIR=".agw"
AGW_CURRENT_FILE="${AGW_LOCAL_CONFIG_DIR}/current"
AGW_SCOPES_DIR="terraform/scopes"
AGW_GLOBAL_SCOPES_DIR="${AGW_SCOPES_DIR}/global"
AGW_USER_SCOPES_DIR="${AGW_SCOPES_DIR}/users"
# Get current username for user-specific scopes
get_username() {
whoami 2>/dev/null || echo "${USER:-unknown}"
}
# Get current scope name
get_current_scope() {
if [ -f "$AGW_CURRENT_FILE" ]; then
cat "$AGW_CURRENT_FILE"
fi
}
# Find scope file with resolution order: user -> global
find_scope_file() {
local scope=$1
local username=$(get_username)
# First check user-specific scope
if [ -f "${AGW_USER_SCOPES_DIR}/${username}/${scope}.env" ]; then
echo "${AGW_USER_SCOPES_DIR}/${username}/${scope}.env"
return
fi
# Fall back to global scope
if [ -f "${AGW_GLOBAL_SCOPES_DIR}/${scope}.env" ]; then
echo "${AGW_GLOBAL_SCOPES_DIR}/${scope}.env"
return
fi
# Legacy: check old location for backwards compatibility
if [ -f "${AGW_LOCAL_CONFIG_DIR}/scopes/${scope}.env" ]; then
echo "${AGW_LOCAL_CONFIG_DIR}/scopes/${scope}.env"
return
fi
}
# Get config value for current scope
get_scope_config() {
local key=$1
local scope=$(get_current_scope)
local scope_file=$(find_scope_file "$scope")
if [ -n "$scope_file" ] && [ -f "$scope_file" ]; then
grep "^${key}=" "$scope_file" 2>/dev/null | cut -d'=' -f2-
fi
}
# Get config - reads from scope config, maps common names to scope config keys
get_config() {
local key=$1
case "$key" in
acr_name) get_scope_config "AGW_ACR_NAME" ;;
container_app_name) get_scope_config "AGW_CONTAINER_APP" ;;
resource_group_name) get_scope_config "AGW_RESOURCE_GROUP" ;;
container_app_env_name) get_scope_config "AGW_CONTAINER_APP_ENV" ;;
*) get_scope_config "AGW_$(echo "$key" | tr '[:lower:]' '[:upper:]')" ;;
esac
}
# Initialize config directories
init_config_dir() {
mkdir -p "$AGW_LOCAL_CONFIG_DIR"
mkdir -p "$AGW_GLOBAL_SCOPES_DIR"
mkdir -p "${AGW_USER_SCOPES_DIR}/$(get_username)"
}
# Get terraform outputs if available (only used for scope import)
get_tf_output_raw() {
local key=$1
if [ -f "terraform/terraform.tfstate" ] || [ -d "terraform/.terraform" ]; then
(cd terraform && terraform output -raw "$key" 2>/dev/null) || echo ""
fi
}
# Get config value - prefers scope config, falls back to terraform
get_tf_output() {
local key=$1
# First try scope config
local scope_value=$(get_config "$key")
if [ -n "$scope_value" ]; then
echo "$scope_value"
return
fi
# Fall back to terraform
get_tf_output_raw "$key"
}
# Get scope/stamp identifier for this deployment
get_scope() {
# First check for configured scope
local scope=$(get_current_scope)
if [ -n "$scope" ]; then
# Return scope name with container app name if available
local app_name=$(get_scope_config "AGW_CONTAINER_APP")
if [ -n "$app_name" ]; then
echo "$scope ($app_name)"
else
echo "$scope"
fi
return
fi
# Try environment variable
if [ -n "$AGW_SCOPE" ]; then
echo "$AGW_SCOPE"
return
fi
# Try to get from terraform outputs (legacy fallback)
local app_name=$(get_tf_output_raw container_app_name)
if [ -n "$app_name" ]; then
echo "$app_name"
return
fi
# Not configured
echo ""
}
# Show banner with scope
show_banner() {
local scope=$(get_scope)
echo ""
echo -e "${BOLD}${BLUE}╔═══════════════════════════════════════════════════════════╗${NC}"
if [ -n "$scope" ]; then
echo -e "${BOLD}${BLUE}║${NC} ${BOLD}${CYAN}UnitOne AgentGateway${NC} ${BOLD}${BLUE}║${NC}"
echo -e "${BOLD}${BLUE}║${NC} ${YELLOW}Scope: ${scope}${NC}$(printf '%*s' $((36 - ${#scope})) '')${BOLD}${BLUE}║${NC}"
else
echo -e "${BOLD}${BLUE}║${NC} ${BOLD}${CYAN}UnitOne AgentGateway${NC} ${BOLD}${BLUE}║${NC}"
echo -e "${BOLD}${BLUE}║${NC} ${RED}Scope: Not configured (run ./agw setup)${NC} ${BOLD}${BLUE}║${NC}"
fi
echo -e "${BOLD}${BLUE}╚═══════════════════════════════════════════════════════════╝${NC}"
}
# ==============================================================================
# Commands
# ==============================================================================
cmd_help() {
cat << 'EOF'
UnitOne AgentGateway CLI
Usage:
./agw <command> [options]
Commands:
setup Interactive first-time setup wizard
scope Manage deployment scopes (dev, staging, prod)
auth Configure OAuth authentication
build Build and push image to ACR
deploy Deploy to Azure Container Apps
test Run E2E tests locally with Docker
test-servers Deploy test servers to Azure
logs View container logs
status Show deployment status
help Show this help
Build Options:
./agw build Build with ACR Cloud Build
./agw build --local Build locally with Docker
./agw build --deploy Build and deploy in one step
./agw build --tag v1.0.0 Build with specific tag
Deploy Options:
./agw deploy Deploy to Azure (default)
./agw deploy --azure Deploy to Azure Container Apps
./agw deploy --aws Deploy to AWS (coming soon)
./agw deploy --tag v1.0.0 Deploy specific image tag
Test Options:
./agw test Run full E2E test suite
./agw test --skip-build Use existing image
./agw test --stop Stop test containers
Test Server Options:
./agw test-servers Deploy test servers to Azure
./agw test-servers --local Start test servers locally
Scope Options:
./agw scope Show current scope
./agw scope list List all scopes
./agw scope set <name> Switch to a scope
./agw scope add <name> Add new scope interactively
./agw scope import --name <n> Import from terraform outputs
Auth Options:
./agw auth Show authentication status
./agw auth setup Configure OAuth providers
./agw auth urls Show OAuth callback URLs
./agw auth enable Require authentication
./agw auth disable Allow anonymous access
Examples:
# First time setup
./agw setup
# Build and deploy
./agw build --deploy
# Run tests
./agw test
# View logs
./agw logs --follow
EOF
}
cmd_setup() {
print_header "AgentGateway Setup"
echo "What would you like to do?"
echo ""
echo " 1) Create new environment - Deploy infrastructure with terraform"
echo " 2) Import existing - Import from terraform outputs"
echo " 3) Manual entry - Enter resource details manually"
echo ""
read -rp "Choose [1-3]: " choice
case "$choice" in
1) setup_create_new ;;
2) setup_import_existing ;;
3) setup_manual_entry ;;
*)
print_error "Invalid choice"
exit 1
;;
esac
}
setup_create_new() {
print_header "Create New Environment"
# Check if terraform directory exists
if [ ! -d "terraform" ]; then
print_error "terraform/ directory not found"
exit 1
fi
# Check Azure CLI login
print_step "Checking Azure CLI login..."
if ! az account show &>/dev/null; then
print_error "Not logged in to Azure CLI. Please run: az login"
exit 1
fi
local subscription=$(az account show --query name -o tsv)
print_success "Logged in to Azure: $subscription"
echo ""
echo "This will deploy new Azure infrastructure using terraform."
echo ""
# Collect deployment parameters
read -rp "Environment name (dev/staging/prod) [dev]: " env_name
env_name=${env_name:-dev}
if [[ ! "$env_name" =~ ^(dev|staging|prod)$ ]]; then
print_error "Environment must be dev, staging, or prod"
exit 1
fi
read -rp "Base name prefix for resources [agw]: " base_name
base_name=${base_name:-agw}
local default_rg="${base_name}-${env_name}-rg"
read -rp "Resource group name [$default_rg]: " rg_name
rg_name=${rg_name:-$default_rg}
read -rp "Azure region [eastus2]: " location
location=${location:-eastus2}
# Check if resource group exists, create if not
echo ""
print_step "Checking resource group..."
if az group show --name "$rg_name" &>/dev/null; then
print_success "Resource group exists: $rg_name"
else
print_step "Creating resource group: $rg_name"
if az group create --name "$rg_name" --location "$location" --output none; then
print_success "Created resource group: $rg_name"
else
print_error "Failed to create resource group"
exit 1
fi
fi
# Create terraform.tfvars
echo ""
print_step "Creating terraform.tfvars..."
cat > terraform/terraform.tfvars << EOF
# Auto-generated by ./agw setup
# Environment: $env_name
environment = "$env_name"
resource_group_name = "$rg_name"
location = "$location"
base_name = "$base_name"
# Initial deployment uses placeholder image
use_placeholder_image = true
enable_sticky_sessions = true
EOF
print_success "Created terraform/terraform.tfvars"
# Use env_name as scope name
local scope_name="$env_name"
echo ""
print_step "Step 1: Initialize Terraform"
(cd terraform && terraform init)
echo ""
print_step "Step 2: Deploy Infrastructure"
echo ""
echo "This will create Azure resources. Review the plan and confirm."
echo ""
(cd terraform && terraform apply)
if [ $? -ne 0 ]; then
print_error "Terraform apply failed"
exit 1
fi
echo ""
print_step "Step 3: Create Scope from Outputs"
# Read terraform outputs
local rg_name=$(cd terraform && terraform output -raw resource_group_name 2>/dev/null)
local app_name=$(cd terraform && terraform output -raw container_app_name 2>/dev/null)
local acr_name=$(cd terraform && terraform output -raw acr_name 2>/dev/null)
local env_name=$(cd terraform && terraform output -raw container_app_env_name 2>/dev/null || echo "")
if [ -z "$rg_name" ] || [ -z "$app_name" ] || [ -z "$acr_name" ]; then
print_error "Could not read terraform outputs"
exit 1
fi
echo ""
echo -e " ${GREEN}✓${NC} acr_name: $acr_name"
echo -e " ${GREEN}✓${NC} container_app_name: $app_name"
echo -e " ${GREEN}✓${NC} resource_group_name: $rg_name"
[ -n "$env_name" ] && echo -e " ${GREEN}✓${NC} container_app_env_name: $env_name"
echo ""
read -rp "Scope type - (G)lobal or (P)ersonal? [G/p]: " scope_type
if [[ "$scope_type" =~ ^[Pp]$ ]]; then
local target_dir="${AGW_USER_SCOPES_DIR}/$(get_username)"
local scope_type_label="personal"
else
local target_dir="$AGW_GLOBAL_SCOPES_DIR"
local scope_type_label="global"
fi
mkdir -p "$target_dir"
# Get URL if app exists
local url=""
local app_info=$(az containerapp show --name "$app_name" --resource-group "$rg_name" 2>/dev/null || echo "")
if [ -n "$app_info" ]; then
local fqdn=$(echo "$app_info" | jq -r '.properties.configuration.ingress.fqdn // ""')
[ -n "$fqdn" ] && url="https://${fqdn}"
fi
# Write scope file
cat > "${target_dir}/${scope_name}.env" << EOF
# Scope: $scope_name ($scope_type_label)
# Created: $(date -u +"%Y-%m-%dT%H:%M:%SZ")
# Required - identifies the deployment
AGW_RESOURCE_GROUP=$rg_name
AGW_CONTAINER_APP=$app_name
AGW_ACR_NAME=$acr_name
# Optional
AGW_CONTAINER_APP_ENV=$env_name
AGW_DESCRIPTION=Created via ./agw setup
AGW_URL=$url
EOF
print_success "Created scope: ${target_dir}/${scope_name}.env"
# Set as current scope
mkdir -p "$AGW_LOCAL_CONFIG_DIR"
echo "$scope_name" > "$AGW_CURRENT_FILE"
print_success "Switched to scope: $scope_name"
echo ""
print_step "Step 4: Build & Deploy"
read -rp "Build and deploy now? [Y/n]: " do_build
if [[ ! "$do_build" =~ ^[Nn]$ ]]; then
cmd_build --deploy
fi
echo ""
print_success "Setup complete!"
echo ""
echo "Next steps:"
echo " git add ${target_dir}/${scope_name}.env"
echo " git commit -m 'Add ${scope_name} scope'"
echo ""
[ -n "$url" ] && echo -e " Gateway: ${CYAN}${url}${NC}"
[ -n "$url" ] && echo -e " UI: ${CYAN}${url}/ui${NC}"
}
setup_import_existing() {
print_header "Import Existing Environment"
echo "Import scope from terraform outputs."
echo ""
# Detect terraform directories
local tf_dirs=()
[ -f "terraform/terraform.tfstate" ] && tf_dirs+=("terraform/ (in-repo)")
# Check for common external locations
for env in dev staging prod; do
local ext_path="$HOME/source_code/terraform/environments/$env/agentgateway"
[ -f "$ext_path/terraform.tfstate" ] && tf_dirs+=("$ext_path")
done
if [ ${#tf_dirs[@]} -eq 0 ]; then
print_warning "No terraform state files found"
echo ""
read -rp "Enter terraform directory path: " tf_dir
elif [ ${#tf_dirs[@]} -eq 1 ]; then
echo "Found terraform state: ${tf_dirs[0]}"
read -rp "Use this? [Y/n]: " confirm
if [[ "$confirm" =~ ^[Nn]$ ]]; then
read -rp "Enter terraform directory path: " tf_dir
else
tf_dir="${tf_dirs[0]}"
# Remove description suffix if present
tf_dir="${tf_dir%% (*}"
fi
else
echo "Found multiple terraform states:"
local i=1
for dir in "${tf_dirs[@]}"; do
echo " $i) $dir"
((i++))
done
echo " $i) Other (enter path)"
echo ""
read -rp "Choose [1-$i]: " tf_choice
if [ "$tf_choice" -eq "$i" ] 2>/dev/null; then
read -rp "Enter terraform directory path: " tf_dir
elif [ "$tf_choice" -ge 1 ] && [ "$tf_choice" -lt "$i" ] 2>/dev/null; then
tf_dir="${tf_dirs[$((tf_choice-1))]}"
tf_dir="${tf_dir%% (*}"
else
print_error "Invalid choice"
exit 1
fi
fi
# Validate terraform directory
if [ ! -f "$tf_dir/terraform.tfstate" ] && [ ! -d "$tf_dir/.terraform" ]; then
print_error "No terraform state found in: $tf_dir"
exit 1
fi
echo ""
print_step "Reading terraform outputs..."
local rg_name=$(cd "$tf_dir" && terraform output -raw resource_group_name 2>/dev/null)
local app_name=$(cd "$tf_dir" && terraform output -raw container_app_name 2>/dev/null)
local acr_name=$(cd "$tf_dir" && terraform output -raw acr_name 2>/dev/null)
local env_name=$(cd "$tf_dir" && terraform output -raw container_app_env_name 2>/dev/null || echo "")
if [ -z "$rg_name" ] || [ -z "$app_name" ] || [ -z "$acr_name" ]; then
print_error "Could not read required terraform outputs"
exit 1
fi
echo ""
echo -e " ${GREEN}✓${NC} acr_name: $acr_name"
echo -e " ${GREEN}✓${NC} container_app_name: $app_name"
echo -e " ${GREEN}✓${NC} resource_group_name: $rg_name"
[ -n "$env_name" ] && echo -e " ${GREEN}✓${NC} container_app_env_name: $env_name"
echo ""
read -rp "Scope name: " scope_name
if [ -z "$scope_name" ]; then
print_error "Scope name is required"
exit 1
fi
echo ""
read -rp "Scope type - (G)lobal or (P)ersonal? [G/p]: " scope_type
if [[ "$scope_type" =~ ^[Pp]$ ]]; then
local target_dir="${AGW_USER_SCOPES_DIR}/$(get_username)"
local scope_type_label="personal"
else
local target_dir="$AGW_GLOBAL_SCOPES_DIR"
local scope_type_label="global"
fi
mkdir -p "$target_dir"
# Check if scope already exists
if [ -f "${target_dir}/${scope_name}.env" ]; then
print_warning "Scope '$scope_name' already exists at ${target_dir}/${scope_name}.env"
read -rp "Overwrite? [y/N]: " overwrite
if [[ ! "$overwrite" =~ ^[Yy]$ ]]; then
echo "Cancelled."
exit 0
fi
fi
# Get URL if app exists
local url=""
local app_info=$(az containerapp show --name "$app_name" --resource-group "$rg_name" 2>/dev/null || echo "")
if [ -n "$app_info" ]; then
local fqdn=$(echo "$app_info" | jq -r '.properties.configuration.ingress.fqdn // ""')
[ -n "$fqdn" ] && url="https://${fqdn}"
fi
# Write scope file
cat > "${target_dir}/${scope_name}.env" << EOF
# Scope: $scope_name ($scope_type_label)
# Imported from: $tf_dir
# Created: $(date -u +"%Y-%m-%dT%H:%M:%SZ")
# Required - identifies the deployment
AGW_RESOURCE_GROUP=$rg_name
AGW_CONTAINER_APP=$app_name
AGW_ACR_NAME=$acr_name
# Optional
AGW_CONTAINER_APP_ENV=$env_name
AGW_DESCRIPTION=Imported from terraform
AGW_URL=$url
EOF
echo ""
print_success "Created scope: ${target_dir}/${scope_name}.env"
# Ask to switch
read -rp "Set as current scope? [Y/n]: " set_current
if [[ ! "$set_current" =~ ^[Nn]$ ]]; then
mkdir -p "$AGW_LOCAL_CONFIG_DIR"
echo "$scope_name" > "$AGW_CURRENT_FILE"
print_success "Switched to scope: $scope_name"
fi
echo ""
echo "Next steps:"
echo " git add ${target_dir}/${scope_name}.env"
echo " git commit -m 'Add ${scope_name} scope'"
}
setup_manual_entry() {
print_header "Manual Entry"
echo "Enter the Azure resource details manually."
echo ""
read -rp "Scope name (e.g., dev, staging, prod): " scope_name
if [ -z "$scope_name" ]; then
print_error "Scope name is required"
exit 1
fi
if [[ ! "$scope_name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
print_error "Invalid scope name. Use only letters, numbers, dashes, and underscores."
exit 1
fi
echo ""
read -rp "Resource Group: " rg_name
read -rp "Container App Name: " app_name
read -rp "ACR Name: " acr_name
echo ""
read -rp "Description (optional): " description
read -rp "Gateway URL (optional): " url
if [ -z "$rg_name" ] || [ -z "$app_name" ] || [ -z "$acr_name" ]; then
print_error "Resource Group, Container App, and ACR Name are required."
exit 1
fi
echo ""
read -rp "Scope type - (G)lobal or (P)ersonal? [G/p]: " scope_type
if [[ "$scope_type" =~ ^[Pp]$ ]]; then
local target_dir="${AGW_USER_SCOPES_DIR}/$(get_username)"
local scope_type_label="personal"
else
local target_dir="$AGW_GLOBAL_SCOPES_DIR"
local scope_type_label="global"
fi
mkdir -p "$target_dir"
# Check if scope already exists
if [ -f "${target_dir}/${scope_name}.env" ]; then
print_warning "Scope '$scope_name' already exists"
read -rp "Overwrite? [y/N]: " overwrite
if [[ ! "$overwrite" =~ ^[Yy]$ ]]; then
echo "Cancelled."
exit 0
fi
fi
# Write scope file
cat > "${target_dir}/${scope_name}.env" << EOF
# Scope: $scope_name ($scope_type_label)
# Created: $(date -u +"%Y-%m-%dT%H:%M:%SZ")
# Required - identifies the deployment
AGW_RESOURCE_GROUP=$rg_name
AGW_CONTAINER_APP=$app_name
AGW_ACR_NAME=$acr_name
# Optional - for display
AGW_DESCRIPTION=$description
AGW_URL=$url
EOF
echo ""
print_success "Created scope: ${target_dir}/${scope_name}.env"
# Set as current scope
mkdir -p "$AGW_LOCAL_CONFIG_DIR"
echo "$scope_name" > "$AGW_CURRENT_FILE"
print_success "Switched to scope: $scope_name"
echo ""
echo "Next steps:"
echo " git add ${target_dir}/${scope_name}.env"
echo " git commit -m 'Add ${scope_name} scope'"
}
cmd_build() {
local LOCAL_BUILD=false
local DEPLOY=false
local TAG="latest"
local NO_CACHE=""
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--local) LOCAL_BUILD=true; shift ;;
--deploy) DEPLOY=true; shift ;;
--tag) TAG="$2"; shift 2 ;;
--no-cache) NO_CACHE="--no-cache"; shift ;;
-h|--help)
echo "Usage: ./agw build [--local] [--deploy] [--tag TAG] [--no-cache]"
exit 0
;;
*) print_error "Unknown option: $1"; exit 1 ;;
esac
done
# Get ACR name
ACR_NAME=$(get_tf_output acr_name)
if [ -z "$ACR_NAME" ]; then
ACR_NAME="${ACR_NAME:-$ACR_REGISTRY}"
fi
if [ -z "$ACR_NAME" ]; then
print_error "ACR name not found. Run './agw setup' first or set ACR_REGISTRY env var."
exit 1
fi
# Check if submodule is initialized
if [ ! -f "agentgateway/Cargo.toml" ]; then
print_error "agentgateway submodule not initialized."
echo ""
echo "Run: git submodule update --init --recursive"
echo "Or clone with: git clone --recursive <repo-url>"
exit 1
fi
print_header "Building Image"
print_info "ACR: ${ACR_NAME}.azurecr.io"
print_info "Tag: $TAG"
print_info "Method: $([ "$LOCAL_BUILD" = true ] && echo "Local Docker" || echo "ACR Cloud Build")"
echo ""
# Clean target directory - not needed for ACR builds and slows down upload significantly
if [ -d "agentgateway/target" ]; then
print_step "Cleaning target directory to speed up upload..."
rm -rf agentgateway/target
print_success "Cleaned"
fi
# Ensure build scripts are executable (fixes Windows/WSL clone issues)
chmod +x agentgateway/common/scripts/*.sh 2>/dev/null || true
if [ "$LOCAL_BUILD" = true ]; then
# Use VM build script
./scripts/build-on-vm.sh --acr-name "$ACR_NAME" --tag "$TAG" $NO_CACHE
else
# ACR Cloud Build
print_step "Logging in to ACR..."
az acr login --name "$ACR_NAME"
print_step "Building in ACR (this may take 20-30 minutes)..."
az acr build \
--registry "$ACR_NAME" \
--image "unitone-agentgateway:${TAG}" \
--image "unitone-agentgateway:$(git rev-parse --short HEAD 2>/dev/null || echo 'manual')" \
--platform linux/amd64 \
--file Dockerfile.acr \
.
print_success "Build complete!"
fi
if [ "$DEPLOY" = true ]; then
cmd_deploy --tag "$TAG"
fi
}
# Provider-specific deploy functions
deploy_azure() {
local TAG=$1
# Get terraform outputs
ACR_NAME=$(get_tf_output acr_name)
APP_NAME=$(get_tf_output container_app_name)
RG_NAME=$(get_tf_output resource_group_name)
if [ -z "$ACR_NAME" ] || [ -z "$APP_NAME" ] || [ -z "$RG_NAME" ]; then
print_error "Missing terraform outputs. Run './agw setup' first."
exit 1
fi
print_header "Deploying to Azure"
print_info "App: $APP_NAME"
print_info "Image: ${ACR_NAME}.azurecr.io/unitone-agentgateway:${TAG}"
echo ""
print_step "Updating container app..."
az containerapp update \
--name "$APP_NAME" \
--resource-group "$RG_NAME" \
--image "${ACR_NAME}.azurecr.io/unitone-agentgateway:${TAG}" \
--output none
# Get URL
APP_URL=$(az containerapp show --name "$APP_NAME" --resource-group "$RG_NAME" \
--query "properties.configuration.ingress.fqdn" -o tsv 2>/dev/null || echo "")
print_success "Deployed!"
echo ""
if [ -n "$APP_URL" ]; then
echo -e " Gateway: ${CYAN}https://${APP_URL}${NC}"
echo -e " UI: ${CYAN}https://${APP_URL}/ui${NC}"
fi
}
deploy_aws() {
local TAG=$1
print_error "AWS deployment not yet implemented."
print_info "Coming soon: ECS/Fargate deployment support."
exit 1
}
cmd_deploy() {
local TAG="latest"
local PROVIDER="azure" # Default provider
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--azure) PROVIDER="azure"; shift ;;
--aws) PROVIDER="aws"; shift ;;
--tag) TAG="$2"; shift 2 ;;
-h|--help)
echo "Usage: ./agw deploy [--azure|--aws] [--tag TAG]"
echo ""
echo "Options:"
echo " --azure Deploy to Azure Container Apps (default)"
echo " --aws Deploy to AWS (coming soon)"
echo " --tag TAG Image tag to deploy (default: latest)"
exit 0
;;
*) print_error "Unknown option: $1"; exit 1 ;;
esac
done
# Dispatch to provider-specific function
case "$PROVIDER" in
azure) deploy_azure "$TAG" ;;
aws) deploy_aws "$TAG" ;;
*) print_error "Unknown provider: $PROVIDER"; exit 1 ;;
esac
}
cmd_test() {
print_header "E2E Testing"
# Pass through to existing deploy.sh (which handles E2E tests)
exec ./deploy.sh "$@"
}
cmd_test_servers() {
local LOCAL=false
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--local) LOCAL=true; shift ;;
-h|--help)
echo "Usage: ./agw test-servers [--local]"
echo " --local Start test servers locally with Docker"
echo " (default) Deploy test servers to Azure"
exit 0
;;
*) shift ;; # Pass through other args
esac
done
if [ "$LOCAL" = true ]; then
print_header "Starting Local Test Servers"
cd tests/docker
docker compose up -d mcp-test-servers
print_success "Test servers running"
echo ""
echo " PII Server: http://localhost:8000"
echo " Tool Poisoning: http://localhost:8010"
echo " Rug Pull: http://localhost:8020"
echo ""
echo "Stop with: docker compose down"
else
# Get terraform outputs
ACR_NAME=$(get_tf_output acr_name)
RG_NAME=$(get_tf_output resource_group_name)
ENV_NAME=$(get_tf_output container_app_env_name 2>/dev/null || echo "")
if [ -z "$ACR_NAME" ] || [ -z "$RG_NAME" ] || [ -z "$ENV_NAME" ]; then
print_error "Missing terraform outputs. Run './agw setup' first."
exit 1
fi
print_header "Deploying Test Servers to Azure"
bash ./scripts/deploy-test-servers.sh \
--resource-group "$RG_NAME" \
--acr-name "$ACR_NAME" \
--environment "$ENV_NAME" \
--update-gateway \
"$@"
fi
}
cmd_logs() {
local FOLLOW=false
local TAIL=100
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-f|--follow) FOLLOW=true; shift ;;
--tail) TAIL="$2"; shift 2 ;;
-h|--help)
echo "Usage: ./agw logs [-f|--follow] [--tail N]"
exit 0
;;
*) print_error "Unknown option: $1"; exit 1 ;;
esac
done
APP_NAME=$(get_tf_output container_app_name)
RG_NAME=$(get_tf_output resource_group_name)
if [ -z "$APP_NAME" ] || [ -z "$RG_NAME" ]; then
print_error "Missing terraform outputs. Run './agw setup' first."
exit 1
fi
print_info "Fetching logs for $APP_NAME..."
if [ "$FOLLOW" = true ]; then
az containerapp logs show \
--name "$APP_NAME" \
--resource-group "$RG_NAME" \
--follow
else
az containerapp logs show \
--name "$APP_NAME" \
--resource-group "$RG_NAME" \
--tail "$TAIL"
fi
}
cmd_status() {
print_header "Deployment Status"
APP_NAME=$(get_tf_output container_app_name)
RG_NAME=$(get_tf_output resource_group_name)
if [ -z "$APP_NAME" ] || [ -z "$RG_NAME" ]; then
print_warning "No deployment found. Run './agw setup' first."
exit 0
fi
print_step "Checking container app..."
# Get app info
APP_INFO=$(az containerapp show --name "$APP_NAME" --resource-group "$RG_NAME" 2>/dev/null)
if [ -z "$APP_INFO" ]; then
print_error "Container app not found: $APP_NAME"
exit 1
fi
# Parse info
APP_URL=$(echo "$APP_INFO" | jq -r '.properties.configuration.ingress.fqdn // "N/A"')
RUNNING_STATE=$(echo "$APP_INFO" | jq -r '.properties.runningStatus // "Unknown"')
IMAGE=$(echo "$APP_INFO" | jq -r '.properties.template.containers[0].image // "N/A"')
REPLICAS=$(echo "$APP_INFO" | jq -r '.properties.template.scale.minReplicas // 0')
echo ""
echo -e " ${BOLD}App:${NC} $APP_NAME"
echo -e " ${BOLD}Status:${NC} $RUNNING_STATE"