263 lines
8.5 KiB
Go
263 lines
8.5 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"os/exec"
|
|
"os/user"
|
|
"flag"
|
|
"fmt"
|
|
"strings"
|
|
"io/ioutil"
|
|
"log"
|
|
"time"
|
|
)
|
|
|
|
type OnFail int
|
|
|
|
const (
|
|
IgnoreOnFail OnFail = iota
|
|
WarnOnFail
|
|
ExitOnFail
|
|
)
|
|
|
|
var build_instance string
|
|
var build_project string
|
|
var build_zone string
|
|
var dest_image string
|
|
var dest_family string
|
|
var dest_project string
|
|
var launch_instance string
|
|
var source_image_family string
|
|
var source_image_project string
|
|
var repository_url string
|
|
var repository_branch string
|
|
var version string
|
|
var SSH_FLAGS string
|
|
var INTERNAL_extra_source string
|
|
var verbose bool
|
|
var username string
|
|
|
|
func init() {
|
|
user, err := user.Current()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
username = user.Username
|
|
|
|
flag.StringVar(&build_instance, "build_instance",
|
|
username+"-build", "Instance name to create for the build")
|
|
flag.StringVar(&build_project, "build_project",
|
|
mustShell("gcloud config get-value project"), "Project to use for scratch")
|
|
flag.StringVar(&build_zone, "build_zone",
|
|
mustShell("gcloud config get-value compute/zone"),
|
|
"Zone to use for scratch resources")
|
|
flag.StringVar(&dest_image, "dest_image",
|
|
"vsoc-host-scratch-"+username, "Image to create")
|
|
flag.StringVar(&dest_family, "dest_family", "",
|
|
"Image family to add the image to")
|
|
flag.StringVar(&dest_project, "dest_project",
|
|
mustShell("gcloud config get-value project"), "Project to use for the new image")
|
|
flag.StringVar(&launch_instance, "launch_instance", "",
|
|
"Name of the instance to launch with the new image")
|
|
flag.StringVar(&source_image_family, "source_image_family", "debian-11",
|
|
"Image familty to use as the base")
|
|
flag.StringVar(&source_image_project, "source_image_project", "debian-cloud",
|
|
"Project holding the base image")
|
|
flag.StringVar(&repository_url, "repository_url",
|
|
"https://github.com/google/android-cuttlefish.git",
|
|
"URL to the repository with host changes")
|
|
flag.StringVar(&repository_branch, "repository_branch",
|
|
"main", "Branch to check out")
|
|
flag.StringVar(&version, "version", "", "cuttlefish-common version")
|
|
flag.StringVar(&SSH_FLAGS, "INTERNAL_IP", "",
|
|
"INTERNAL_IP can be set to --internal-ip run on a GCE instance."+
|
|
"The instance will need --scope compute-rw.")
|
|
flag.StringVar(&INTERNAL_extra_source, "INTERNAL_extra_source", "",
|
|
"INTERNAL_extra_source may be set to a directory containing the source for extra packages to build.")
|
|
flag.BoolVar(&verbose, "verbose", true, "print commands and output (default: true)")
|
|
flag.Parse()
|
|
}
|
|
|
|
func shell(cmd string) (string, error) {
|
|
if verbose {
|
|
fmt.Println(cmd)
|
|
}
|
|
b, err := exec.Command("/bin/sh", "-c", cmd).CombinedOutput()
|
|
if verbose {
|
|
fmt.Println(string(b))
|
|
}
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return strings.TrimSpace(string(b)), nil
|
|
}
|
|
|
|
func mustShell(cmd string) string {
|
|
if verbose {
|
|
fmt.Println(cmd)
|
|
}
|
|
out, err := shell(cmd)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if verbose {
|
|
fmt.Println(out)
|
|
}
|
|
return strings.TrimSpace(out)
|
|
}
|
|
|
|
func gce(action OnFail, gceArg string, errorStr ...string) (string, error) {
|
|
cmd := "gcloud " + gceArg
|
|
out, err := shell(cmd)
|
|
if out != "" {
|
|
fmt.Println(out)
|
|
}
|
|
if err != nil && action != IgnoreOnFail {
|
|
var buf string
|
|
fmt.Sprintf(buf, "gcloud error occurred: %s", err)
|
|
if (len(errorStr) > 0) {
|
|
buf += " [" + errorStr[0] + "]"
|
|
}
|
|
if action == ExitOnFail {
|
|
panic(buf)
|
|
}
|
|
if action == WarnOnFail {
|
|
fmt.Println(buf)
|
|
}
|
|
}
|
|
return out, err
|
|
}
|
|
|
|
func waitForInstance(PZ string) {
|
|
for {
|
|
time.Sleep(5 * time.Second)
|
|
_, err := gce(WarnOnFail, `compute ssh `+SSH_FLAGS+` `+PZ+` `+
|
|
build_instance+` -- uptime`)
|
|
if err == nil {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func packageSource(url string, branch string, version string, subdir string) {
|
|
repository_dir := url[strings.LastIndex(url, "/")+1:]
|
|
debian_dir := mustShell(`basename "`+repository_dir+`" .git`)
|
|
if subdir != "" {
|
|
debian_dir = repository_dir + "/" + subdir
|
|
}
|
|
mustShell("git clone " + url + " -b "+branch)
|
|
mustShell("dpkg-source -b " + debian_dir)
|
|
mustShell("rm -rf " + debian_dir)
|
|
mustShell("ls -l")
|
|
mustShell("pwd")
|
|
}
|
|
|
|
func createInstance(instance string, arg string) {
|
|
_, err := gce(WarnOnFail, `compute instances describe "`+instance+`"`)
|
|
if err != nil {
|
|
gce(ExitOnFail, `compute instances create `+arg+` "`+instance+`"`)
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
gpu_type := "nvidia-tesla-p100-vws"
|
|
PZ := "--project=" + build_project + " --zone=" + build_zone
|
|
|
|
dest_family_flag := ""
|
|
if dest_family != "" {
|
|
dest_family_flag = "--family=" + dest_family
|
|
}
|
|
|
|
scratch_dir, err := ioutil.TempDir("", "")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
oldDir, err := os.Getwd()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
os.Chdir(scratch_dir)
|
|
packageSource(repository_url, repository_branch, "cuttlefish-common_" + version, "")
|
|
os.Chdir(oldDir)
|
|
|
|
abt := os.Getenv("ANDROID_BUILD_TOP")
|
|
source_files := `"` + abt + `/device/google/cuttlefish/tools/create_base_image_gce.sh"`
|
|
source_files += " " + `"` + abt + `/device/google/cuttlefish/tools/update_gce_kernel.sh"`
|
|
source_files += " " + `"` + abt + `/device/google/cuttlefish/tools/remove_old_gce_kernel.sh"`
|
|
source_files += " " + scratch_dir + "/*"
|
|
if INTERNAL_extra_source != "" {
|
|
source_files += " " + INTERNAL_extra_source + "/*"
|
|
}
|
|
|
|
delete_instances := build_instance + " " + dest_image
|
|
if launch_instance != "" {
|
|
delete_instances += " " + launch_instance
|
|
}
|
|
|
|
gce(WarnOnFail, `compute instances delete -q `+PZ+` `+delete_instances,
|
|
`Not running`)
|
|
gce(WarnOnFail, `compute disks delete -q `+PZ+` "`+dest_image+
|
|
`"`, `No scratch disk`)
|
|
gce(WarnOnFail, `compute images delete -q --project="`+build_project+
|
|
`" "`+dest_image+`"`, `Not respinning`)
|
|
gce(WarnOnFail, `compute disks create `+PZ+` --image-family="`+source_image_family+
|
|
`" --image-project="`+source_image_project+`" "`+dest_image+`"`)
|
|
gce(ExitOnFail, `compute accelerator-types describe "`+gpu_type+`" `+PZ,
|
|
`Please use a zone with `+gpu_type+` GPUs available.`)
|
|
createInstance(build_instance, PZ+
|
|
` --machine-type=n1-standard-16 --image-family="`+source_image_family+
|
|
`" --image-project="`+source_image_project+
|
|
`" --boot-disk-size=200GiB --accelerator="type=`+gpu_type+
|
|
`,count=1" --maintenance-policy=TERMINATE --boot-disk-size=200GiB`)
|
|
|
|
waitForInstance(PZ)
|
|
|
|
// Ubuntu tends to mount the wrong disk as root, so help it by waiting until
|
|
// it has booted before giving it access to the clean image disk
|
|
gce(WarnOnFail, `compute instances attach-disk `+PZ+` "`+build_instance+
|
|
`" --disk="`+dest_image+`"`)
|
|
|
|
// beta for the --internal-ip flag that may be passed via SSH_FLAGS
|
|
gce(ExitOnFail, `beta compute scp `+SSH_FLAGS+` `+PZ+` `+source_files+
|
|
` "`+build_instance+`:"`)
|
|
|
|
// Update the host kernel before installing any kernel modules
|
|
// Needed to guarantee that the modules in the chroot aren't built for the
|
|
// wrong kernel
|
|
gce(WarnOnFail, `compute ssh `+SSH_FLAGS+` `+PZ+` "`+build_instance+
|
|
`" -- ./update_gce_kernel.sh`)
|
|
// TODO rammuthiah if the instance is clobbered with ssh commands within
|
|
// 5 seconds of reboot, it becomes inaccessible. Workaround that by sleeping
|
|
// 50 seconds.
|
|
time.Sleep(50 * time.Second)
|
|
gce(ExitOnFail, `compute ssh `+SSH_FLAGS+` `+PZ+` "`+build_instance+
|
|
`" -- ./remove_old_gce_kernel.sh`)
|
|
|
|
gce(ExitOnFail, `compute ssh `+SSH_FLAGS+` `+PZ+` "`+build_instance+
|
|
`" -- ./create_base_image_gce.sh`)
|
|
gce(ExitOnFail, `compute instances delete -q `+PZ+` "`+build_instance+`"`)
|
|
gce(ExitOnFail, `compute images create --project="`+build_project+
|
|
`" --source-disk="`+dest_image+`" --source-disk-zone="`+build_zone+
|
|
`" --licenses=https://www.googleapis.com/compute/v1/projects/vm-options/global/licenses/enable-vmx `+
|
|
dest_family_flag+` "`+dest_image+`"`)
|
|
gce(ExitOnFail, `compute disks delete -q `+PZ+` "`+dest_image+`"`)
|
|
|
|
if launch_instance != "" {
|
|
createInstance(launch_instance, PZ+
|
|
` --image-project="`+build_project+`" --image="`+dest_image+
|
|
`" --machine-type=n1-standard-4 --scopes storage-ro --accelerator="type=`+
|
|
gpu_type+`,count=1" --maintenance-policy=TERMINATE`)
|
|
}
|
|
|
|
fmt.Printf("Test and if this looks good, consider releasing it via:\n"+
|
|
"\n"+
|
|
"gcloud compute images create \\\n"+
|
|
" --project=\"%s\" \\\n"+
|
|
" --source-image=\"%s\" \\\n"+
|
|
" --source-image-project=\"%s\" \\\n"+
|
|
" \"%s\" \\\n"+
|
|
" \"%s\"\n",
|
|
dest_project, dest_image, build_project, dest_family_flag, dest_image)
|
|
}
|