blob: a4e6b6b2dfe5ef81c05d0af65fd00871010f8f2b [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package omaha
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/maruel/subcommands"
"go.chromium.org/luci/auth/client/authcli"
"go.chromium.org/luci/common/cli"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/common/logging"
"infra/cmd/stable_version2/internal/cmd"
gslib "infra/cmd/stable_version2/internal/gs"
"infra/cmd/stable_version2/internal/site"
"infra/cmd/stable_version2/internal/utils"
svlib "infra/cros/stableversion"
gitlib "infra/libs/git"
sv "go.chromium.org/chromiumos/infra/proto/go/lab_platform"
"infra/cmd/stable_version2/internal/cmd/validateconfig/querygs"
)
// UpdateWithOmaha subcommand: read stable version in omaha json file in GS.
var UpdateWithOmaha = &subcommands.Command{
UsageLine: `update-with-omaha [FLAGS...] -output_json /path/to/output.json`,
ShortDesc: "update stable version with omaha files",
LongDesc: `update stable vesrion with omaha json file in GS.
This command is for builder to get up-to-date stable version from omaha file in GS,
and commit them to stable version config file.
Do not use this command as part of scripts or pipelines as it's unstable.
Output is JSON encoded protobuf defined at
https://chromium.googlesource.com/chromiumos/infra/proto/+/refs/heads/main/src/lab_platform/stable_version.proto`,
CommandRun: func() subcommands.CommandRun {
c := &updateWithOmahaRun{}
c.authFlags.Register(&c.Flags, site.DefaultAuthOptions)
c.Flags.StringVar(&c.outputPath, "output_json", "", "Path where JSON encoded lab_platform.StableVersions should be written.")
c.Flags.BoolVar(&c.dryRun, "dryrun", false, "indicate if it's a dryrun for stable version update")
return c
},
}
type updateWithOmahaRun struct {
subcommands.CommandRunBase
authFlags authcli.Flags
outputPath string
dryRun bool
}
// Run implements the subcommands.CommandRun interface.
func (c *updateWithOmahaRun) Run(a subcommands.Application, args []string, env subcommands.Env) int {
if err := c.innerRun(a, args, env); err != nil {
cmd.PrintError(a.GetErr(), err)
return 1
}
return 0
}
func (c *updateWithOmahaRun) innerRun(a subcommands.Application, args []string, env subcommands.Env) error {
ctx := cli.GetContext(a, c, env)
ctx = cmd.SetupLogging(ctx)
f := &c.authFlags
outDir, err := ioutil.TempDir("", cmd.ProgramName)
if err != nil {
return err
}
defer func() {
if err := os.RemoveAll(outDir); err != nil {
logging.Errorf(ctx, "fail to remove temp dir: %s", err)
}
}()
t, err := cmd.NewAuthenticatedTransport(ctx, f)
if err != nil {
return errors.Annotate(err, "create authenticated transport").Err()
}
var gsc gslibClient
var gscImpl gslib.Client
if err := gscImpl.Init(ctx, t, utils.Unmarshaller); err != nil {
return err
}
gsc = &gscImpl
// Fetch up-to-date stable version based on omaha file
newCrosSV, err := getGSCrosSV(ctx, outDir, gsc)
if err != nil {
return err
}
// Fetch existing stable version
hc, err := cmd.NewHTTPClient(ctx, f)
if err != nil {
return err
}
gc, err := gitlib.NewClient(ctx, hc, cmd.GerritHost, cmd.GitilesHost, cmd.Project, cmd.Branch)
if err != nil {
return err
}
oldSV, err := getGitSV(ctx, gc)
logInvalidCrosSV(ctx, oldSV.GetCros())
if err != nil {
return err
}
fvFunc := MakeFirmwareVersionFunc(gsc, outDir)
newSV, err := FileBuilder(ctx, oldSV, newCrosSV, fvFunc)
if err != nil {
return err
}
validateErr := validateFile(ctx, a, t, newSV)
if c.dryRun {
content, err := svlib.WriteSVToString(newSV)
if err == nil {
fmt.Printf("%s\n", content)
}
if validateErr != nil {
return validateErr
}
if err != nil {
return err
}
return nil
}
if validateErr != nil {
return validateErr
}
changeURL, err := commitNew(ctx, gc, newSV)
if err != nil {
return err
}
logging.Debugf(ctx, "Update stable version CL: %s", changeURL)
return nil
}
func validateFile(ctx context.Context, a subcommands.Application, t http.RoundTripper, newSV *sv.StableVersions) error {
// validate config before committing it
var r querygs.Reader
if err := r.Init(ctx, t, utils.Unmarshaller, "validate-config"); err != nil {
return fmt.Errorf("initializing Google Storage client: %s", err)
}
res, err := r.ValidateConfig(ctx, newSV)
if err != nil {
return fmt.Errorf("valdating config using Google Storage: %s", err)
}
res.RemoveAllowedDUTs()
msg, err := json.MarshalIndent(res, "", " ")
if err != nil {
panic("failed to marshal JSON")
}
if count := res.AnomalyCount(); count > 0 {
fmt.Fprintf(a.GetErr(), "%s\n", msg)
return fmt.Errorf("(%d) errors detected: %s", count, msg)
}
return nil
}
// Get CrOS stable version from omaha status file.
func getGSCrosSV(ctx context.Context, outDir string, gsc gslibClient) ([]*sv.StableCrosVersion, error) {
localOSFile := filepath.Join(outDir, cmd.OmahaStatusFile)
if err := gsc.Download(cmd.OmahaGSPath, localOSFile); err != nil {
return nil, err
}
omahaBytes, err := ioutil.ReadFile(localOSFile)
if err != nil {
return nil, errors.Annotate(err, "load omaha").Err()
}
cros, err := gslib.ParseOmahaStatus(ctx, omahaBytes)
if err != nil {
return nil, errors.Annotate(err, "parse omaha").Err()
}
return cros, nil
}
// getGitSV gets stable versions from the git client.
func getGitSV(ctx context.Context, gc *gitlib.Client) (*sv.StableVersions, error) {
res, err := gc.GetFile(ctx, cmd.StableVersionConfigPath)
if err != nil {
return nil, err
}
if res == "" {
logging.Warningf(ctx, "empty stable version config file: %s", cmd.StableVersionConfigPath)
return nil, err
}
var allSV sv.StableVersions
if err := utils.Unmarshaller.Unmarshal(strings.NewReader(res), &allSV); err != nil {
return nil, err
}
return &allSV, nil
}
func logInvalidCrosSV(ctx context.Context, crosSV []*sv.StableCrosVersion) {
for _, csv := range crosSV {
if err := svlib.ValidateCrOSVersion(csv.GetVersion()); err != nil {
logging.Debugf(ctx, "invalid cros version: %s, %s", csv.GetKey().GetBuildTarget().GetName(), csv.GetVersion())
}
}
}
func commitNew(ctx context.Context, gc *gitlib.Client, sv *sv.StableVersions) (string, error) {
newContent, err := svlib.WriteSVToString(sv)
if err != nil {
return "", errors.Annotate(err, "convert change").Err()
}
u := map[string]string{
cmd.StableVersionConfigPath: newContent,
}
changeInfo, err := gc.UpdateFiles(ctx, "Update stable version (automatically)", u)
if err != nil {
return "", errors.Annotate(err, "update change").Err()
}
gerritURL, err := gc.SubmitChange(ctx, changeInfo)
if err != nil {
return "", errors.Annotate(err, "submit change").Err()
}
return gerritURL, nil
}
func localMetaFilePath(crosSV *sv.StableCrosVersion) string {
return fmt.Sprintf("%s-%s", crosSV.GetKey().GetBuildTarget().GetName(), crosSV.GetVersion())
}