blob: faf237a8bddb7cedc30f4b8eb465a894254e0e7a [file] [log] [blame]
package main
import (
"context"
"fmt"
"io/fs"
"os"
"path"
"path/filepath"
"regexp"
"strings"
"go.skia.org/infra/cd/go/cd"
"go.skia.org/infra/cd/go/stages"
"go.skia.org/infra/go/docker"
"go.skia.org/infra/go/exec"
"go.skia.org/infra/go/git"
"go.skia.org/infra/go/gitauth"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/vfs"
"go.skia.org/infra/task_driver/go/lib/git_steps"
"go.skia.org/infra/task_driver/go/td"
)
func updateRefs(ctx context.Context, dockerClient docker.Client, repo, workspace, email, louhiPubsubProject, executionID, srcRepo, srcCommit string) error {
ctx = td.StartStep(ctx, td.Props("Update References"))
defer td.EndStep(ctx)
// Initialize git authentication.
ts, err := git_steps.Init(ctx, true)
if err != nil {
return td.FailStep(ctx, err)
}
if _, err := gitauth.New(ctx, ts, "/tmp/.gitcookies", true, email); err != nil {
return td.FailStep(ctx, err)
}
httpClient := httputils.DefaultClientConfig().WithTokenSource(ts).With2xxOnly().Client()
// Create a shallow clone of the repo.
checkoutDir, err := shallowClone(ctx, repo, git.DefaultRef)
if err != nil {
return td.FailStep(ctx, err)
}
// Create a branch.
gitExec, err := git.Executable(ctx)
if err != nil {
return td.FailStep(ctx, err)
}
if _, err := exec.RunCwd(ctx, checkoutDir, gitExec, "checkout", "-b", "update", "-t", git.DefaultRemoteBranch); err != nil {
return td.FailStep(ctx, err)
}
// Read the stage file from the repo.
stageFile, err := stages.DecodeFile(filepath.Join(checkoutDir, stages.StageFilePath))
if err != nil {
if os.IsNotExist(skerr.Unwrap(err)) {
stageFile = &stages.StageFile{
Images: map[string]*stages.Image{},
}
} else {
return td.FailStep(ctx, err)
}
}
// Read the information about the images we built.
imageInfo, err := readBuildImagesJSON(ctx, workspace)
if err != nil {
return td.FailStep(ctx, err)
}
// Find-and-replace each of the image references.
if err := td.Do(ctx, td.Props("Update Image References"), func(ctx context.Context) error {
imageRegexes := make([]*regexp.Regexp, 0, len(imageInfo.Images))
imageReplace := make([]string, 0, len(imageInfo.Images))
for _, image := range imageInfo.Images {
registry, repository, _, err := docker.SplitImage(image.Image)
if err != nil {
return err
}
if _, ok := stageFile.Images[image.Image]; ok {
// Use the stagemanager to update the image references.
sm := stages.NewStageManager(ctx, vfs.Local(checkoutDir), dockerClient, stages.GitilesCommitResolver(httpClient))
if err := sm.SetStage(ctx, image.Image, "latest", image.Tag); err != nil {
return err
}
} else {
// Retrieve the digest and perform a simple find-and-replace.
manifest, err := dockerClient.GetManifest(ctx, registry, repository, image.Tag)
if err != nil {
return err
}
newReg, newRepl := findRegexesAndReplaces(image, manifest.Digest)
imageRegexes = append(imageRegexes, newReg...)
imageReplace = append(imageReplace, newRepl...)
}
}
return filepath.WalkDir(checkoutDir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
} else if d.IsDir() {
if d.Name() == ".git" {
return fs.SkipDir
} else {
return nil
}
}
// Read the file.
contents, err := os.ReadFile(path)
if err != nil {
return err
}
existingStats, err := os.Stat(path)
if err != nil {
return err
}
contentsStr := string(contents)
// Replace all instances of the old image specification with the new.
for idx, re := range imageRegexes {
contentsStr = re.ReplaceAllString(contentsStr, imageReplace[idx])
}
// Write out the updated file.
contents = []byte(contentsStr)
if err := os.WriteFile(path, contents, existingStats.Mode()); err != nil {
return err
}
return nil
})
}); err != nil {
return td.FailStep(ctx, err)
}
// Upload a CL.
imageList := make([]string, 0, len(imageInfo.Images))
for _, image := range imageInfo.Images {
imageList = append(imageList, path.Base(image.Image))
}
commitSubject := fmt.Sprintf("Update %s", strings.Join(imageList, ", "))
return cd.MaybeUploadCL(ctx, checkoutDir, commitSubject, srcRepo, srcCommit, louhiPubsubProject, executionID)
}
func findRegexesAndReplaces(image *SingleImageInfo, digest string) ([]*regexp.Regexp, []string) {
// Update instances of "image/path@sha256:digest"
ymlRegex := regexp.MustCompile(fmt.Sprintf(`%s@sha256:[a-f0-9]+`, image.Image))
ymlReplace := fmt.Sprintf("%s@%s", image.Image, digest)
// Replace Bazel container_pull specifications.
cpRegex, cpReplace := bazelRegexAndReplaceForContainerPull(image, digest)
return []*regexp.Regexp{ymlRegex, cpRegex}, []string{ymlReplace, cpReplace}
}
func bazelRegexAndReplaceForContainerPull(image *SingleImageInfo, digest string) (*regexp.Regexp, string) {
const regexTmpl = `(container_pull\(\s*name\s*=\s*"%s",\s*digest\s*=\s*)"sha256:[a-f0-9]+",`
regex := regexp.MustCompile(fmt.Sprintf(regexTmpl, path.Base(image.Image)))
const replTmpl = `$1"%s",`
replace := fmt.Sprintf(replTmpl, digest)
return regex, replace
}