Change:
We should support image family in node e2e image config. It makes image validation much easier on GCE.
Cool idea :) Adding a link to for anyone like me who hadn't heard of them before :)

Discussion:
package main import ( <ins> "context" </ins> "flag" "fmt" "io/ioutil"

// Defaults to using only the latest image. Acceptable values are [0, # of images that match the regex). // If the number of existing previous images is lesser than what is desired, the test will use that is available. PreviousImages int `json:"previous_images,omitempty"` <ins> // ImageFamily is the image family to use. The latest image from the image family will be used. ImageFamily string `json:"image_family,omitempty"` </ins> Machine   string    `json:"machine,omitempty"` Resources Resources `json:"resources,omitempty"`

for shortName, imageConfig := range externalImageConfig.Images { var images []string isRegex, name := false, shortName <del> if imageConfig.ImageRegex != "" && imageConfig.Image == "" { </del> <ins> if (imageConfig.ImageRegex != "" || imageConfig.ImageFamily != "") && imageConfig.Image == "" { </ins> isRegex = true <del> images, err = getGCEImages(imageConfig.ImageRegex, imageConfig.Project, imageConfig.PreviousImages) </del> <ins> images, err = getGCEImages(imageConfig.ImageRegex, imageConfig.ImageFamily, imageConfig.Project, imageConfig.PreviousImages) </ins> if err != nil { <del> klog.Fatalf("Could not retrieve list of images based on image prefix %q: %v", imageConfig.ImageRegex, err) </del> <ins> klog.Fatalf("Could not retrieve list of images based on image prefix %q and family %q: %v", imageConfig.ImageRegex, imageConfig.ImageFamily, err) </ins> } } else { images = []string{imageConfig.Image}

func (a byCreationTime) Swap(i, j int)      { a[i], a[j] = a[j], a[i] } // Returns a list of image names based on regex and number of previous images requested. <del> func getGCEImages(imageRegex, project string, previousImages int) ([]string, error) { ilc, err := computeService.Images.List(project).Do() if err != nil { return nil, fmt.Errorf("Failed to list images in project %q: %v", project, err) } </del> <ins> func getGCEImages(imageRegex, imageFamily string, project string, previousImages int) ([]string, error) { </ins> imageObjs := []imageObj{} imageRe := regexp.MustCompile(imageRegex) <del> for _, instance := range ilc.Items { if imageRe.MatchString(instance.Name) { creationTime, err := time.Parse(time.RFC3339, instance.CreationTimestamp) if err != nil { return nil, fmt.Errorf("Failed to parse instance creation timestamp %q: %v", instance.CreationTimestamp, err) } io := imageObj{ creationTime: creationTime, name:         instance.Name, </del> <ins> if err := computeService.Images.List(project).Pages(context.Background(), func(ilc *compute.ImageList) error { for _, instance := range ilc.Items { if imageRegex != "" && !imageRe.MatchString(instance.Name) { continue } if imageFamily != "" && instance.Family != imageFamily { continue } creationTime, err := time.Parse(time.RFC3339, instance.CreationTimestamp) if err != nil { return fmt.Errorf("failed to parse instance creation timestamp %q: %v", instance.CreationTimestamp, err) } io := imageObj{ creationTime: creationTime, name:         instance.Name, } klog.V(4).Infof("Found image %q based on regex %q and family %q in project %q", io.string(), imageRegex, imageFamily, project) imageObjs = append(imageObjs, io) </ins> } <del> klog.V(4).Infof("Found image %q based on regex %q in project %q", io.string(), imageRegex, project) imageObjs = append(imageObjs, io) } </del> <ins> return nil }, ); err != nil { return nil, fmt.Errorf("failed to list images in project %q: %v", project, err) </ins> } sort.Sort(byCreationTime(imageObjs)) images := []string{}