container_images/gce-img-resource/check/check_command.go (87 lines of code) (raw):
package check
import (
"context"
"fmt"
"sort"
"strings"
"time"
gceimgresource "github.com/GoogleCloudPlatform/guest-test-infra/container_images/gce-img-resource"
"google.golang.org/api/compute/v1"
)
/*
{
"source": {
"project": "some-project",
"family": "some-family",
"regexp": "rhel-8-v([0-9]+).*",
"readyOnly": true,
},
"version": { "name": "rhel-8-v20220322" }
}
*/
// Request is the input of a resource check.
type Request struct {
Source gceimgresource.Source `json:"source"`
Version gceimgresource.Version `json:"version"`
}
// Response is the output of a resource check.
type Response []gceimgresource.Version
// Run performs a check for image versions.
func Run(request Request) (Response, error) {
ctx := context.Background()
computeService, err := compute.NewService(ctx)
if err != nil {
return Response{}, err
}
call := computeService.Images.List(request.Source.Project)
var filter []string
if request.Source.ReadyOnly {
filter = append(filter, "(status = READY)")
}
if request.Source.Family != "" {
filter = append(filter, fmt.Sprintf("(family = %s)", request.Source.Family))
}
if len(filter) > 0 {
call = call.Filter(strings.Join(filter, " "))
}
var images []*compute.Image
var token string
for il, err := call.PageToken(token).Do(); ; il, err = call.PageToken(token).Do() {
if err != nil {
return Response{}, err
}
images = append(images, il.Items...)
if il.NextPageToken == "" {
break
}
token = il.NextPageToken
}
// "By default, results are returned in alphanumerical order based on the resource name."
// - https://cloud.google.com/compute/docs/reference/rest/v1/images/list
// "[the] check script...must print the array of new versions, in chronological order (oldest first)"
// - https://concourse-ci.org/implementing-resource-types.html
sort.Slice(images, func(i, j int) bool {
// image.CreationTimestamp is a string in rfc3339 format.
itime, _ := time.Parse(time.RFC3339, images[i].CreationTimestamp)
jtime, _ := time.Parse(time.RFC3339, images[j].CreationTimestamp)
return itime.Unix() < jtime.Unix()
})
// No version specified, return only the latest image.
if request.Version.Name == "" && len(images) > 0 {
image := images[len(images)-1]
version, err := mkVersion(image.Name, image.CreationTimestamp)
if err != nil {
return Response{}, err
}
return Response{version}, nil
}
// Requested version must at least be included in the response.
response := Response{request.Version}
var start bool
for _, image := range images {
if image.Name == request.Version.Name {
// Start appending from the image after the matching version, aka 'newer'.
start = true
continue
}
if image.Deprecated != nil && image.Deprecated.State == "DEPRECATED" {
continue
}
if start {
version, err := mkVersion(image.Name, image.CreationTimestamp)
if err != nil {
return Response{}, err
}
response = append(response, version)
}
}
return response, nil
}
func mkVersion(name, timestring string) (gceimgresource.Version, error) {
creationTime, err := time.Parse(time.RFC3339, timestring)
if err != nil {
return gceimgresource.Version{}, err
}
return gceimgresource.Version{
Name: name,
Version: fmt.Sprintf("%d", creationTime.Unix()),
}, nil
}