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) }