Skip to content

Commit 3c22c38

Browse files
johnsonjbradfitz
authored andcommitted
env/windows: automate buildlet image creation
Generate a Windows image that will start a buildlet on boot and have the dependencies needed for building/testing go/cgo. provisioning: - `sysprep.ps1`: disables unneeded features (eg UAC) and downloads dependencies (stage0, gcc) - `startup.ps1`: sets up a user account for unattended login. this can't be done in the sysprep stage because the `local machine` does not yet exist to create accounts under. helpers: - `build.bash`: builds a single image, creates a vm from the image and verifies it with `test_buildlet.bash` - `make.bash`: builds a set of images - `connect.bash`: helper to RDP into a machine for troubleshooting - `test_buildlet.bash`: validation script to exercise a buildlet Updates golang/go#17513 Change-Id: I4812ed1fc9862ae0aa44b712ea270fd52d0c505f Reviewed-on: https://go-review.googlesource.com/41142 Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent f8afd1d commit 3c22c38

File tree

9 files changed

+331
-0
lines changed

9 files changed

+331
-0
lines changed

env/windows/.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
instance.txt
2+
.envrc
3+
out/*

env/windows/README.md

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Windows buildlet images
2+
3+
Windows images are built by creating and configuring VMs in GCP then capturing the image to the GCP Project.
4+
5+
The provisioning happens in two stages:
6+
- [sysprep.ps1](./sysprep.ps1): Downloads and unpacks dependencies, disabled unneeded Windows features (eg UAC)
7+
- [startup.ps1](./startup.ps1): Creates and configures user for unattended login to launch the buildlet
8+
9+
## Prerequisite: Setup a firewall rule
10+
Allow traffic to instances tagged `allow-dev-access` on tcp:80, tcp:3389
11+
12+
```bash
13+
# restrict this down to your local network
14+
source_range=0.0.0.0/0
15+
16+
gcloud compute firewall-rules create --allow=tcp:80,tcp:3389 --target-tags allow-dev-access --source-ranges $source_range allow-dev-access
17+
```
18+
19+
## Examples/Tools
20+
21+
### Build and test a single base image
22+
Builds a buildlet from the BASE_IMAGE and sets it up with and An image is captured and then a new VM is created from that image and validated with [test_buildlet.bash](./test_buildlet.bash).
23+
24+
```bash
25+
export PROJECT_ID=YOUR_GCP_PROJECT
26+
export BASE_IMAGE=windows-server-2016-dc-core-v20170214
27+
export IMAGE_PROJECT=windows-cloud
28+
29+
./build.bash
30+
```
31+
32+
### Build all targets
33+
```bash
34+
./make.bash
35+
```
36+
37+
### Build/test golang
38+
```bash
39+
instance_name=golang-buildlet-test
40+
external_ip=$(gcloud compute instances describe golang-buildlet-test --project=${PROJECT_ID} --zone=${ZONE} --format="value(networkInterfaces[0].accessConfigs[0].natIP)")
41+
./test_buildlet.bash $external_ip
42+
```
43+
44+
### Troubleshoot via RDP
45+
```bash
46+
./connect.bash <instance_name>
47+
```

env/windows/build.bash

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#!/bin/bash
2+
3+
# Copyright 2017 The Go Authors. All rights reserved.
4+
# Use of this source code is governed by a BSD-style
5+
# license that can be found in the LICENSE file.
6+
7+
set -eu
8+
9+
ZONE="us-central1-f"
10+
BUILDER_PREFIX="${1-golang}"
11+
IMAGE_NAME="${1-${BASE_IMAGE}}"
12+
INSTANCE_NAME="${BUILDER_PREFIX}-buildlet"
13+
TEST_INSTANCE_NAME="${BUILDER_PREFIX}-buildlet-test"
14+
MACHINE_TYPE="n1-standard-4"
15+
BUILDLET_IMAGE="windows-amd64-${IMAGE_NAME}"
16+
IMAGE_PROJECT=$IMAGE_PROJECT
17+
BASE_IMAGE=$BASE_IMAGE
18+
19+
function wait_for_buildlet() {
20+
external_ip=$1
21+
seconds=5
22+
23+
echo "Waiting for buildlet at ${external_ip} to become responsive"
24+
until curl "http://${external_ip}" 2>/dev/null; do
25+
echo "retrying ${external_ip} in ${seconds} seconds"
26+
sleep "${seconds}"
27+
done
28+
}
29+
30+
#
31+
# 0. Cleanup images/instances from prior runs
32+
#
33+
echo "Destroying existing instances (if exists)"
34+
yes "Y" | gcloud compute instances delete "$INSTANCE_NAME" --project="$PROJECT_ID" --zone="$ZONE" || true
35+
yes "Y" | gcloud compute instances delete "$TEST_INSTANCE_NAME" --project="$PROJECT_ID" --zone="$ZONE" || true
36+
echo "Destroying existing image (if exists)"
37+
yes "Y" | gcloud compute images delete "$BUILDLET_IMAGE" --project="$PROJECT_ID" || true
38+
39+
40+
#
41+
# 1. Create base instance
42+
#
43+
echo "Creating target instance"
44+
gcloud compute instances create --machine-type="$MACHINE_TYPE" "$INSTANCE_NAME" \
45+
--image "$BASE_IMAGE" --image-project "$IMAGE_PROJECT" \
46+
--project="$PROJECT_ID" --zone="$ZONE" \
47+
--metadata="buildlet-binary-url=https://storage.googleapis.com/go-builder-data/buildlet.windows-amd64" \
48+
--metadata-from-file=sysprep-specialize-script-ps1=sysprep.ps1,windows-startup-script-ps1=startup.ps1 --tags=allow-dev-access
49+
50+
echo ""
51+
echo "Fetch logs with:"
52+
echo ""
53+
echo gcloud compute instances get-serial-port-output "$INSTANCE_NAME" --zone="$ZONE" --project="$PROJECT_ID"
54+
echo ""
55+
external_ip=$(gcloud compute instances describe "$INSTANCE_NAME" --project="$PROJECT_ID" --zone="$ZONE" --format="value(networkInterfaces[0].accessConfigs[0].natIP)")
56+
57+
wait_for_buildlet "$external_ip"
58+
59+
#
60+
# 2. Image base instance
61+
#
62+
63+
echo "Shutting down instance"
64+
gcloud compute instances stop "$INSTANCE_NAME" \
65+
--project="$PROJECT_ID" --zone="$ZONE"
66+
67+
echo "Capturing image"
68+
gcloud compute images create "$BUILDLET_IMAGE" --source-disk "$INSTANCE_NAME" --source-disk-zone "$ZONE" --project="$PROJECT_ID"
69+
70+
echo "Removing base machine"
71+
yes "Y" | gcloud compute instances delete "$INSTANCE_NAME" --project="$PROJECT_ID" --zone="$ZONE" || true
72+
73+
#
74+
# 3. Verify image is valid
75+
#
76+
77+
echo "Creating new machine with image"
78+
gcloud compute instances create --machine-type="$MACHINE_TYPE" --image "$BUILDLET_IMAGE" "$TEST_INSTANCE_NAME" \
79+
--project="$PROJECT_ID" --metadata="buildlet-binary-url=https://storage.googleapis.com/go-builder-data/buildlet.windows-amd64" \
80+
--tags=allow-dev-access --zone="$ZONE"
81+
82+
test_image_ip=$(gcloud compute instances describe "$TEST_INSTANCE_NAME" --project="$PROJECT_ID" --zone="$ZONE" --format="value(networkInterfaces[0].accessConfigs[0].natIP)")
83+
wait_for_buildlet "$test_image_ip"
84+
85+
echo "Performing test build"
86+
./test_buildlet.bash "$test_image_ip"
87+
88+
echo "Removing test instance"
89+
yes "Y" | gcloud compute instances delete "$TEST_INSTANCE_NAME" --project="$PROJECT_ID" --zone="$ZONE" || true
90+
91+
echo "Success! A new buildlet can be created with the following command"
92+
echo "gcloud compute instances create --machine-type='$MACHINE_TYPE' '$INSTANCE_NAME' \
93+
--metadata='buildlet-binary-url=https://storage.googleapis.com/go-builder-data/buildlet.windows-amd64' \
94+
--image '$BUILDLET_IMAGE' --image-project '$PROJECT_ID'"

env/windows/connect.bash

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/bin/bash
2+
3+
# Copyright 2017 The Go Authors. All rights reserved.
4+
# Use of this source code is governed by a BSD-style
5+
# license that can be found in the LICENSE file.
6+
7+
set -eu
8+
9+
ZONE=us-central1-f
10+
INSTANCE_NAME="${1:-golang-buildlet}"
11+
12+
# Set, fetch credentials
13+
yes "Y" | gcloud compute reset-windows-password "${INSTANCE_NAME}" --user wingopher --project="${PROJECT_ID}" --zone="${ZONE}" > instance.txt
14+
15+
echo ""
16+
echo "Instance credentials: "
17+
echo ""
18+
cat instance.txt
19+
20+
echo ""
21+
echo "Connecting to instance: "
22+
echo ""
23+
24+
username="$(grep username instance.txt | cut -d ':' -f 2 | xargs echo -n)"
25+
password="$(grep password instance.txt | sed 's/password:\s*//' | xargs echo -n)"
26+
hostname="$(grep ip_address instance.txt | cut -d ':' -f 2 | xargs echo -n)"
27+
28+
echo xfreerdp -u "${username}" -p "'${password}'" -n "${hostname}" --ignore-certificate "${hostname}"
29+
xfreerdp -u "${username}" -p "${password}" -n "${hostname}" --ignore-certificate "${hostname}"

env/windows/make.bash

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/bin/bash
2+
3+
# Copyright 2017 The Go Authors. All rights reserved.
4+
# Use of this source code is governed by a BSD-style
5+
# license that can be found in the LICENSE file.
6+
7+
set -e -u
8+
9+
declare -A public_images
10+
11+
public_images=(
12+
['server-2016-v2']='windows-server-2016-dc-core-v20170214'
13+
['server-2008r2-v2']='windows-server-2008-r2-dc-v20170214'
14+
['server-2012r2-v2']='windows-server-2012-r2-dc-core-v20170214'
15+
)
16+
17+
mkdir -p out
18+
19+
for image in "${!public_images[@]}"; do
20+
prefix=$image
21+
base_image=${public_images[$image]}
22+
23+
BASE_IMAGE="$base_image" IMAGE_PROJECT='windows-cloud' ./build.bash "$prefix" |& tee "out/${base_image}.txt" &
24+
done
25+
26+
27+
wait
File renamed without changes.

env/windows/startup.ps1

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright 2017 The Go Authors. All rights reserved.
2+
# Use of this source code is governed by a BSD-style
3+
# license that can be found in the LICENSE file.
4+
5+
Set-StrictMode -Version Latest
6+
7+
function Test-RegistryKeyExists($path, $name)
8+
{
9+
$key = Get-Item -LiteralPath $path -ErrorAction SilentlyContinue
10+
($key -and $null -ne $key.GetValue($name, $null)) -ne $false
11+
}
12+
13+
$builder_dir = "C:\golang"
14+
$bootstrap_exe_path = "$builder_dir\bootstrap.exe"
15+
16+
# Create a buildlet user
17+
$buildlet_user = "buildlet"
18+
$buildlet_password = "bUi-dL3ttt"
19+
net user $buildlet_user $buildlet_password /ADD
20+
net localgroup administrators $buildlet_user /ADD
21+
22+
# Run the bootstrap program on login
23+
$bootstrap_cmd = "cmd /k ""cd $builder_dir && $bootstrap_exe_path"""
24+
New-ItemProperty -Path "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run" -Name "Buildlet" -PropertyType ExpandString -Value $bootstrap_cmd -Force
25+
26+
# Setup autologon and reboot
27+
$RegPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
28+
if ((Test-RegistryKeyExists $RegPath "DefaultUsername") -eq $false) {
29+
Remove-ItemProperty -Path 'HKLM:SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -Name 'AutoLogonCount' -Force
30+
Set-ItemProperty $RegPath "AutoAdminLogon" -Value "1" -type String
31+
Set-ItemProperty $RegPath "DefaultUsername" -Value "$buildlet_user" -type String
32+
Set-ItemProperty $RegPath "DefaultPassword" -Value "$buildlet_password" -type String
33+
Set-ItemProperty $RegPath "LogonCount" -Value "99999999" -type String
34+
shutdown /r /t 0
35+
}

env/windows/sysprep.ps1

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Copyright 2017 The Go Authors. All rights reserved.
2+
# Use of this source code is governed by a BSD-style
3+
# license that can be found in the LICENSE file.
4+
5+
Set-StrictMode -Version Latest
6+
7+
# Helpers
8+
function Get-FileFromUrl(
9+
[string] $URL,
10+
[string] $Output)
11+
{
12+
Add-Type -AssemblyName "System.Net.Http"
13+
14+
$client = New-Object System.Net.Http.HttpClient
15+
$request = New-Object System.Net.Http.HttpRequestMessage -ArgumentList @([System.Net.Http.HttpMethod]::Get, $URL)
16+
$responseMsg = $client.SendAsync($request)
17+
$responseMsg.Wait()
18+
19+
if (!$responseMsg.IsCanceled)
20+
{
21+
$response = $responseMsg.Result
22+
if ($response.IsSuccessStatusCode)
23+
{
24+
$downloadedFileStream = [System.IO.File]::Create($Output)
25+
$copyStreamOp = $response.Content.CopyToAsync($downloadedFileStream)
26+
$copyStreamOp.Wait()
27+
$downloadedFileStream.Close()
28+
if ($copyStreamOp.Exception -ne $null)
29+
{
30+
throw $copyStreamOp.Exception
31+
}
32+
}
33+
}
34+
}
35+
36+
# Disable automatic updates, windows firewall, error reporting, and UAC
37+
#
38+
# - They'll just interrupt the builds later.
39+
# - We don't care about security since this isn't going to be Internet-facing.
40+
# - No ports will be accessible once the image is built.
41+
# - We can be trusted to run as a real Administrator
42+
New-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name NoAutoUpdate -Value 1 -Force | Out-Null
43+
new-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting" -Name Disabled -Value 1 -Force | Out-Null
44+
new-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting" -Name DontShowUI -Value 1 -Force | Out-Null
45+
netsh advfirewall set allprofiles state off
46+
netsh firewall set opmode mode=disable profile=ALL
47+
New-ItemProperty -Path HKLM:Software\Microsoft\Windows\CurrentVersion\policies\system -Name EnableLUA -PropertyType DWord -Value 0 -Force | Out-Null
48+
49+
# Download buildlet
50+
$url = "https://storage.googleapis.com/go-builder-data/buildlet-stage0.windows-amd64.untar"
51+
$builder_dir = "C:\golang"
52+
$bootstrap_exe_path = "$builder_dir\bootstrap.exe"
53+
mkdir $builder_dir
54+
Get-FileFromUrl -URL $url -Output $bootstrap_exe_path
55+
56+
# Download and unpack dependencies
57+
$dep_dir = "C:\godep"
58+
$gcc32_tar = "$dep_dir\gcc32.tar.gz"
59+
$gcc64_tar = "$dep_dir\gcc64.tar.gz"
60+
mkdir $dep_dir
61+
Get-FileFromUrl -URL "https://storage.googleapis.com/godev/gcc5-1-tdm32.tar.gz" -Output "$gcc32_tar"
62+
Get-FileFromUrl -URL "https://storage.googleapis.com/godev/gcc5-1-tdm64.tar.gz" -Output "$gcc64_tar"
63+
64+
# Extract GCC
65+
$extract32_args=@("--untar-file=$gcc32_tar", "--untar-dest-dir=$dep_dir")
66+
& $bootstrap_exe_path $extract32_args
67+
$extract64_args=@("--untar-file=$gcc64_tar", "--untar-dest-dir=$dep_dir")
68+
& $bootstrap_exe_path $extract64_args

env/windows/test_buildlet.bash

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/bin/bash
2+
3+
# Copyright 2017 The Go Authors. All rights reserved.
4+
# Use of this source code is governed by a BSD-style
5+
# license that can be found in the LICENSE file.
6+
7+
set -ue
8+
9+
hostname="$1"
10+
BUILDLET="windows-amd64-gce@${hostname}"
11+
12+
echo "Pushing go1.4, go1.8 to buildlet"
13+
gomote puttar -url https://storage.googleapis.com/golang/go1.8.src.tar.gz "$BUILDLET"
14+
gomote put14 "$BUILDLET"
15+
16+
echo "Building go (32-bit)"
17+
gomote run -e GOARCH=386 -e GOHOSTARCH=386 -path 'C:/godep/gcc32/bin,$WORKDIR/go/bin,$PATH' -e 'GOROOT=c:\workdir\go' "$BUILDLET" go/src/make.bat
18+
19+
# Go1.8 has failing tests on some windows versions.
20+
# Push a new release when avaliable or update this to use master.
21+
#echo "Running tests for go (32-bit)"
22+
#gomote run -e GOARCH=386 -e GOHOSTARCH=386 -path 'C:/godep/gcc32/bin,$WORKDIR/go/bin,$PATH' -e 'GOROOT=C:\workdir\go' "$BUILDLET" go/bin/go.exe tool dist test -v --no-rebuild
23+
24+
echo "Building go (64-bit)"
25+
gomote run -path '$PATH,C:/godep/gcc64/bin,$WORKDIR/go/bin,$PATH' -e 'GOROOT=c:\workdir\go' "$BUILDLET" go/src/make.bat
26+
27+
#echo "Running tests for go (64-bit)"
28+
#gomote run -path 'C:/godep/gcc64/bin,$WORKDIR/go/bin,$PATH' -e 'GOROOT=C:\workdir\go' "$BUILDLET" go/bin/go.exe tool dist test -v --no-rebuild

0 commit comments

Comments
 (0)