1+ name : MSHV Infra Setup
2+ on :
3+ workflow_call :
4+ inputs :
5+ ARCH :
6+ description : ' Architecture for the VM'
7+ required : true
8+ type : string
9+ KEY :
10+ description : ' SSH Key Name'
11+ required : true
12+ type : string
13+ OS_DISK_SIZE :
14+ description : ' OS Disk Size in GB'
15+ required : true
16+ type : string
17+ RG :
18+ description : ' Resource Group Name'
19+ required : true
20+ type : string
21+ VM_SKU :
22+ description : ' VM SKU'
23+ required : true
24+ type : string
25+ secrets :
26+ MI_CLIENT_ID :
27+ required : true
28+ RUNNER_RG :
29+ required : true
30+ STORAGE_ACCOUNT_PATHS :
31+ required : true
32+ ARCH_SOURCE_PATH :
33+ required : true
34+ USERNAME :
35+ required : true
36+ outputs :
37+ PRIVATE_IP :
38+ description : ' Private IP of the VM'
39+ value : ${{ jobs.infra-setup.outputs.PRIVATE_IP }}
40+ concurrency :
41+ group : ${{ github.workflow }}-${{ github.ref }}
42+ cancel-in-progress : true
43+ jobs :
44+ infra-setup :
45+ name : ${{ inputs.ARCH }} VM Provision
46+ runs-on :
47+ - self-hosted
48+ - Linux
49+ outputs :
50+ PRIVATE_IP : ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
51+ steps :
52+ - name : Install & login to AZ CLI
53+ env :
54+ MI_CLIENT_ID : ${{ secrets.MI_CLIENT_ID }}
55+ run : |
56+ set -e
57+ echo "Installing Azure CLI if not already installed"
58+ if ! command -v az &>/dev/null; then
59+ curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
60+ else
61+ echo "Azure CLI already installed"
62+ fi
63+ az --version
64+ echo "Logging into Azure CLI using Managed Identity"
65+ az login --identity --client-id ${MI_CLIENT_ID}
66+
67+ - name : Get Location
68+ id : get-location
69+ env :
70+ SKU : ${{ inputs.VM_SKU }}
71+ STORAGE_ACCOUNT_PATHS : ${{ secrets.STORAGE_ACCOUNT_PATHS }}
72+ run : |
73+ set -e
74+ # Extract vCPU count from SKU (e.g., "Standard_D2s_v3" => 2)
75+ vcpu=$(echo "$SKU" | sed -n 's/^Standard_[A-Za-z]\+\([0-9]\+\).*/\1/p')
76+ if [[ -z "$vcpu" ]]; then
77+ echo "Cannot extract vCPU count from SKU: $SKU"
78+ exit 1
79+ fi
80+
81+ SUPPORTED_LOCATIONS=$(echo "$STORAGE_ACCOUNT_PATHS" | jq -r 'to_entries[] | .key')
82+
83+ for location in $SUPPORTED_LOCATIONS; do
84+ family=$(az vm list-skus --size "$SKU" --location "$location" --resource-type "virtualMachines" --query '[0].family' -o tsv)
85+ if [[ -z "$family" ]]; then
86+ echo "Cannot determine VM family for SKU: $SKU in $location"
87+ continue
88+ fi
89+
90+ usage=$(az vm list-usage --location "$location" --query "[?name.value=='$family'] | [0]" -o json)
91+ current=$(echo "$usage" | jq -r '.currentValue')
92+ limit=$(echo "$usage" | jq -r '.limit')
93+
94+ if [[ $((limit - current)) -ge $vcpu ]]; then
95+ echo "Sufficient quota found in $location"
96+ echo "location=$location" >> "$GITHUB_OUTPUT"
97+ exit 0
98+ fi
99+ done
100+
101+ echo "No location found with sufficient vCPU quota for SKU: $SKU"
102+ exit 1
103+
104+ - name : Create Resource Group
105+ id : rg-setup
106+ env :
107+ LOCATION : ${{ steps.get-location.outputs.location }}
108+ RG : ${{ inputs.RG }}
109+ STORAGE_ACCOUNT_PATHS : ${{ secrets.STORAGE_ACCOUNT_PATHS }}
110+ run : |
111+ set -e
112+ echo "Creating Resource Group: $RG"
113+ # Create the resource group
114+ echo "Creating resource group in location: ${LOCATION}"
115+ az group create --name ${RG} --location ${LOCATION}
116+ echo "Resource group created successfully."
117+
118+ - name : Generate SSH Key
119+ id : generate-ssh-key
120+ env :
121+ KEY : ${{ inputs.KEY }}
122+ run : |
123+ set -e
124+ echo "Generating SSH key: $KEY"
125+ mkdir -p ~/.ssh
126+ ssh-keygen -t rsa -b 4096 -f ~/.ssh/${KEY} -N ""
127+
128+ - name : Create VM
129+ id : vm-setup
130+ env :
131+ KEY : ${{ inputs.KEY }}
132+ LOCATION : ${{ steps.get-location.outputs.location }}
133+ OS_DISK_SIZE : ${{ inputs.OS_DISK_SIZE }}
134+ RG : ${{ inputs.RG }}
135+ RUNNER_RG : ${{ secrets.RUNNER_RG }}
136+ USERNAME : ${{ secrets.USERNAME }}
137+ VM_SKU : ${{ inputs.VM_SKU }}
138+ VM_IMAGE_NAME : ${{ inputs.ARCH }}_${{ steps.get-location.outputs.location }}_image
139+ VM_NAME : ${{ inputs.ARCH }}_${{ steps.get-location.outputs.location }}_${{ github.run_id }}
140+ run : |
141+ set -e
142+ echo "Creating $VM_SKU VM: $VM_NAME"
143+
144+ # Extract subnet ID from the runner VM
145+ echo "Retrieving subnet ID..."
146+ SUBNET_ID=$(az network vnet list --resource-group ${RUNNER_RG} --query "[?contains(location, '${LOCATION}')].{SUBNETS:subnets}" | jq -r ".[0].SUBNETS[0].id")
147+ if [[ -z "${SUBNET_ID}" ]]; then
148+ echo "ERROR: Failed to retrieve Subnet ID."
149+ exit 1
150+ fi
151+
152+ # Extract image ID from the runner VM
153+ echo "Retrieving image ID..."
154+ IMAGE_ID=$(az image show --resource-group ${RUNNER_RG} --name ${VM_IMAGE_NAME} --query "id" -o tsv)
155+ if [[ -z "${IMAGE_ID}" ]]; then
156+ echo "ERROR: Failed to retrieve Image ID."
157+ exit 1
158+ fi
159+
160+ # Create VM
161+ az vm create \
162+ --resource-group ${RG} \
163+ --name ${VM_NAME} \
164+ --subnet ${SUBNET_ID} \
165+ --size ${VM_SKU} \
166+ --location ${LOCATION} \
167+ --image ${IMAGE_ID} \
168+ --os-disk-size-gb ${OS_DISK_SIZE} \
169+ --public-ip-sku Standard \
170+ --storage-sku Premium_LRS \
171+ --public-ip-address "" \
172+ --admin-username ${USERNAME} \
173+ --ssh-key-value ~/.ssh/${KEY}.pub \
174+ --security-type Standard \
175+ --output json
176+
177+ echo "VM creation process completed successfully."
178+
179+ - name : Get VM Private IP
180+ id : get-vm-ip
181+ env :
182+ RG : ${{ inputs.RG }}
183+ VM_NAME : ${{ inputs.ARCH }}_${{ steps.get-location.outputs.location }}_${{ github.run_id }}
184+ run : |
185+ set -e
186+ echo "Retrieving VM Private IP address..."
187+ # Retrieve VM Private IP address
188+ PRIVATE_IP=$(az vm show -g ${RG} -n ${VM_NAME} -d --query privateIps -o tsv)
189+ if [[ -z "$PRIVATE_IP" ]]; then
190+ echo "ERROR: Failed to retrieve private IP address."
191+ exit 1
192+ fi
193+ echo "PRIVATE_IP=$PRIVATE_IP" >> $GITHUB_OUTPUT
194+
195+ - name : Wait for SSH availability
196+ env :
197+ KEY : ${{ inputs.KEY }}
198+ PRIVATE_IP : ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
199+ USERNAME : ${{ secrets.USERNAME }}
200+ run : |
201+ echo "Waiting for SSH to be accessible..."
202+ timeout 120 bash -c 'until ssh -o StrictHostKeyChecking=no -i ~/.ssh/${KEY} ${USERNAME}@${PRIVATE_IP} "exit" 2>/dev/null; do sleep 5; done'
203+ echo "VM is accessible!"
204+
205+ - name : Remove Old Host Key
206+ env :
207+ PRIVATE_IP : ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
208+ run : |
209+ set -e
210+ echo "Removing the old host key"
211+ ssh-keygen -R $PRIVATE_IP
212+
213+ - name : SSH into VM and Install Dependencies
214+ env :
215+ KEY : ${{ inputs.KEY }}
216+ PRIVATE_IP : ${{ steps.get-vm-ip.outputs.PRIVATE_IP }}
217+ USERNAME : ${{ secrets.USERNAME }}
218+ run : |
219+ set -e
220+ ssh -i ~/.ssh/${KEY} -o StrictHostKeyChecking=no ${USERNAME}@${PRIVATE_IP} << EOF
221+ set -e
222+ echo "Logged in successfully."
223+ echo "Installing dependencies..."
224+ sudo tdnf install -y git moby-engine moby-cli clang llvm pkg-config make gcc glibc-devel
225+ echo "Installing Rust..."
226+ curl -sSf https://sh.rustup.rs | sh -s -- --default-toolchain stable --profile default -y
227+ export PATH="\$HOME/.cargo/bin:\$PATH"
228+ cargo --version
229+ sudo mkdir -p /etc/docker/
230+ echo '{"default-ulimits":{"nofile":{"Hard":65535,"Name":"nofile","Soft":65535}}}' | sudo tee /etc/docker/daemon.json
231+ sudo systemctl stop docker
232+ sudo systemctl enable docker.service
233+ sudo systemctl enable containerd.service
234+ sudo systemctl start docker
235+ sudo groupadd -f docker
236+ sudo usermod -a -G docker ${USERNAME}
237+ sudo systemctl restart docker
238+ EOF
0 commit comments