blob: 40d533b414b79e5dfa00e95411cc4e579a8b735e [file] [log] [blame]
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)
}