blob: 3e8fcd4870d6d76243bf43bbfd9667fc22730c45 [file] [log] [blame]
// Copyright 2019 The Fuchsia 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 artifacts
import (
"context"
"encoding/json"
"fmt"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"golang.org/x/crypto/ssh"
pmBuild "go.fuchsia.dev/fuchsia/src/sys/pkg/bin/pm/build"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/avb"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/ffx"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/omaha_tool"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/packages"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/paver"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/zbi"
"go.fuchsia.dev/fuchsia/tools/build"
"go.fuchsia.dev/fuchsia/tools/lib/logger"
)
type BlobFetchMode int
const (
// PrefetchBlobs will download all the blobs from a build when `GetPackageRepository()` is called.
PrefetchBlobs BlobFetchMode = iota
// LazilyFetchBlobs will only download blobs when they are accessed.
LazilyFetchBlobs
// Product Bundle manifest which is used to locate VBmeta
ProductBundleManifest = "product_bundle.json"
)
type Build interface {
// String returns a string identifier for this build.
String() string
// GetBootserver returns the path to the bootserver used for paving.
GetBootserver(ctx context.Context) (string, error)
// GetFfx returns the FFXTool from this build.
GetFfx(
ctx context.Context,
ffxIsolateDir ffx.IsolateDir,
) (*ffx.FFXTool, error)
// GetFlashManifest returns the path to the flash manifest used for flashing.
GetFlashManifest(ctx context.Context) (string, error)
// GetPackageRepository returns a Repository for this build.
GetPackageRepository(
ctx context.Context,
blobFetchMode BlobFetchMode,
ffxIsolateDir ffx.IsolateDir,
) (*packages.Repository, error)
// GetPaverDir downloads and returns the directory containing the images
// and image manifest.
GetPaverDir(ctx context.Context) (string, error)
// GetPaver downloads and returns a paver for the build.
GetPaver(
ctx context.Context,
sshPublicKey ssh.PublicKey,
) (paver.Paver, error)
// GetVbmetaPath downloads and returns a path to the zircon-a vbmeta image.
GetVbmetaPath(ctx context.Context) (string, error)
}
// ArtifactsBuild represents the build artifacts for a specific build.
type ArtifactsBuild struct {
id string
archive *Archive
dir string
packages *packages.Repository
buildImageDir string
srcs map[string]struct{}
ffxPath string
}
func (b *ArtifactsBuild) GetBootserver(ctx context.Context) (string, error) {
buildImageDir, err := b.GetBuildImages(ctx)
if err != nil {
return "", err
}
// Use the latest bootserver if possible because the one uploaded with the artifacts may not include bug fixes.
currentBuildId := os.Getenv("BUILDBUCKET_ID")
if currentBuildId == "" {
currentBuildId = b.id
}
bootserverPath := filepath.Join(buildImageDir, "bootserver")
if err := b.archive.download(ctx, currentBuildId, false, bootserverPath, []string{"tools/linux-x64/bootserver"}); err != nil {
return "", fmt.Errorf("failed to download bootserver: %w", err)
}
// Make bootserver executable.
if err := os.Chmod(bootserverPath, os.ModePerm); err != nil {
return "", fmt.Errorf("failed to make bootserver executable: %w", err)
}
return bootserverPath, nil
}
func (b *ArtifactsBuild) GetFfx(
ctx context.Context,
ffxIsolateDir ffx.IsolateDir,
) (*ffx.FFXTool, error) {
buildImageDir, err := b.GetBuildImages(ctx)
if err != nil {
return nil, err
}
currentBuildId := os.Getenv("BUILDBUCKET_ID")
if currentBuildId == "" {
currentBuildId = b.id
}
// Use the latest ffx
ffxPath := filepath.Join(buildImageDir, "ffx")
if b.ffxPath != "" {
ffxPath = b.ffxPath
}
if err := b.archive.download(ctx, currentBuildId, false, ffxPath, []string{"tools/linux-x64/ffx"}); err != nil {
return nil, fmt.Errorf("failed to download ffxPath: %w", err)
}
// Make ffx executable.
if err := os.Chmod(ffxPath, os.ModePerm); err != nil {
return nil, fmt.Errorf("failed to make ffxPath executable: %w", err)
}
return ffx.NewFFXTool(ffxPath, ffxIsolateDir)
}
func (b *ArtifactsBuild) GetFlashManifest(ctx context.Context) (string, error) {
buildImageDir, err := b.GetBuildImages(ctx)
if err != nil {
return "", err
}
flashManifest := filepath.Join(buildImageDir, "flash.json")
if err := b.archive.download(ctx, b.id, false, flashManifest, []string{"images/flash.json"}); err != nil {
return "", fmt.Errorf("failed to download flash.json for flasher: %w", err)
}
return flashManifest, nil
}
// GetPackageRepository returns a Repository for this build. It tries to
// download a package when all the artifacts are stored in individual files,
// which is how modern builds publish their build artifacts.
func (b *ArtifactsBuild) GetPackageRepository(
ctx context.Context,
fetchMode BlobFetchMode,
ffxIsolateDir ffx.IsolateDir,
) (*packages.Repository, error) {
if b.packages != nil {
return b.packages, nil
}
logger.Infof(ctx, "downloading package repository")
// Make sure the blob contains the `packages/all_blobs.json`.
if _, ok := b.srcs["packages/all_blobs.json"]; !ok {
logger.Errorf(ctx, "blobs manifest doesn't exist for build %s", b.id)
return nil, fmt.Errorf("blob manifest doesn't exist for build %s", b.id)
}
packageSrcs := []string{}
for src := range b.srcs {
if strings.HasPrefix(src, "packages/") {
packageSrcs = append(packageSrcs, src)
}
}
packagesDir := filepath.Join(b.dir, b.id, "packages")
if err := b.archive.download(ctx, b.id, false, filepath.Dir(packagesDir), packageSrcs); err != nil {
logger.Errorf(ctx, "failed to download packages for build %s to %s: %v", packagesDir, b.id, err)
return nil, fmt.Errorf("failed to download packages for build %s to %s: %w", packagesDir, b.id, err)
}
blobsManifest := filepath.Join(packagesDir, "all_blobs.json")
blobsData, err := os.ReadFile(blobsManifest)
if err != nil {
return nil, fmt.Errorf("failed to read blobs manifest: %w", err)
}
var blobs []build.Blob
err = json.Unmarshal(blobsData, &blobs)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal blobs JSON: %w", err)
}
deliveryBlobConfigPath := filepath.Join(packagesDir, "delivery_blob_config.json")
blobsDir, err := build.GetBlobsDir(deliveryBlobConfigPath)
if err != nil {
return nil, fmt.Errorf("failed to get blobs dir: %w", err)
}
var blobsList []string
for _, blob := range blobs {
blobsList = append(blobsList, filepath.Join(blobsDir, blob.Merkle))
}
logger.Infof(ctx, "all_blobs contains %d blobs", len(blobs))
blobsDir = filepath.Join(b.dir, "blobs")
if fetchMode == PrefetchBlobs {
if err := b.archive.download(ctx, b.id, true, filepath.Dir(blobsDir), blobsList); err != nil {
logger.Errorf(ctx, "failed to download blobs to %s: %v", blobsDir, err)
return nil, fmt.Errorf("failed to download blobs to %s: %w", blobsDir, err)
}
}
ffx, err := b.GetFfx(ctx, ffxIsolateDir)
if err != nil {
return nil, fmt.Errorf("failed to get ffx: %w", err)
}
blobType, err := build.GetDeliveryBlobType(deliveryBlobConfigPath)
if err != nil {
return nil, fmt.Errorf("failed to get delivery blob type: %w", err)
}
p, err := packages.NewRepository(ctx, packagesDir, &proxyBlobStore{b, blobsDir}, ffx, blobType)
if err != nil {
return nil, err
}
b.packages = p
return b.packages, nil
}
type proxyBlobStore struct {
b *ArtifactsBuild
dir string
}
func (fs *proxyBlobStore) BlobPath(ctx context.Context, deliveryBlobType *int, merkle pmBuild.MerkleRoot) (string, error) {
var path string
if deliveryBlobType == nil {
path = filepath.Join(fs.dir, merkle.String())
} else {
path = filepath.Join(fs.dir, strconv.Itoa(*deliveryBlobType), merkle.String())
}
// First, try to read the blob from the directory
if _, err := os.Stat(path); err == nil {
return path, nil
}
// Otherwise, start downloading the blob. The package resolver will only
// fetch a blob once, so we don't need to deduplicate requests on our side.
var src string
if deliveryBlobType == nil {
src = filepath.Join("blobs", merkle.String())
} else {
src = filepath.Join("blobs", strconv.Itoa(*deliveryBlobType), merkle.String())
}
logger.Infof(ctx, "downloading %s from build %s", src, fs.b.id)
if err := fs.b.archive.download(ctx, fs.b.id, true, path, []string{src}); err != nil {
return "", err
}
return path, nil
}
func (fs *proxyBlobStore) OpenBlob(ctx context.Context, deliveryBlobType *int, merkle pmBuild.MerkleRoot) (*os.File, error) {
path, err := fs.BlobPath(ctx, deliveryBlobType, merkle)
if err != nil {
return nil, err
}
return os.Open(path)
}
func (fs *proxyBlobStore) BlobSize(ctx context.Context, deliveryBlobType *int, merkle pmBuild.MerkleRoot) (uint64, error) {
f, err := fs.OpenBlob(ctx, deliveryBlobType, merkle)
if err != nil {
return 0, err
}
defer f.Close()
if s, err := f.Stat(); err == nil {
if s.Size() < 0 {
return 0, fmt.Errorf("merkle %s has size less than zero: %d", merkle, s.Size())
}
return uint64(s.Size()), nil
} else {
return 0, err
}
}
func (fs *proxyBlobStore) Dir() string {
return fs.dir
}
// GetBuildImages downloads the build images for a specific build id.
// Returns a path to the directory of the downloaded images or an error if it
// fails to download.
func (b *ArtifactsBuild) GetBuildImages(ctx context.Context) (string, error) {
if b.buildImageDir != "" {
return b.buildImageDir, nil
}
logger.Infof(ctx, "downloading build images")
imageDir := filepath.Join(b.dir, b.id, "images")
if err := b.archive.download(ctx, b.id, false, filepath.Join(imageDir, paver.ImageManifest), []string{path.Join("images", paver.ImageManifest)}); err != nil {
return "", fmt.Errorf("failed to download image manifest: %w", err)
}
imagesJSON := filepath.Join(imageDir, paver.ImageManifest)
f, err := os.Open(imagesJSON)
if err != nil {
return "", fmt.Errorf("failed to open %q: %w", imagesJSON, err)
}
defer f.Close()
var items []build.Image
if err := json.NewDecoder(f).Decode(&items); err != nil {
return "", fmt.Errorf("failed to parse %q: %w", imagesJSON, err)
}
// Get list of all available images to download and only download
// the ones needed for flashing or paving.
imageSrcMap := make(map[string]struct{})
for src := range b.srcs {
if strings.HasPrefix(src, "images/") {
imageSrcMap[src] = struct{}{}
}
}
imageSrcs := []string{}
for _, item := range items {
src := path.Join("images", item.Path)
if _, ok := imageSrcMap[src]; ok {
imageSrcs = append(imageSrcs, src)
}
}
if err := b.archive.download(ctx, b.id, false, filepath.Dir(imageDir), imageSrcs); err != nil {
return "", fmt.Errorf("failed to download images to %s: %w", imageDir, err)
}
b.buildImageDir = imageDir
return b.buildImageDir, nil
}
func (b *ArtifactsBuild) GetPaverDir(ctx context.Context) (string, error) {
return b.GetBuildImages(ctx)
}
// GetPaver downloads and returns a paver for the build.
func (b *ArtifactsBuild) GetPaver(
ctx context.Context,
sshPublicKey ssh.PublicKey,
) (paver.Paver, error) {
return b.getPaver(ctx, sshPublicKey)
}
func (b *ArtifactsBuild) getPaver(
ctx context.Context,
sshPublicKey ssh.PublicKey,
) (*paver.BuildPaver, error) {
buildImageDir, err := b.GetPaverDir(ctx)
if err != nil {
return nil, err
}
bootserverPath, err := b.GetBootserver(ctx)
if err != nil {
return nil, err
}
return paver.NewBuildPaver(bootserverPath, buildImageDir, paver.SSHPublicKey(sshPublicKey))
}
func (b *ArtifactsBuild) GetVbmetaPath(ctx context.Context) (string, error) {
buildImageDir, err := b.GetBuildImages(ctx)
if err != nil {
return "", err
}
imagesJSON := filepath.Join(buildImageDir, paver.ImageManifest)
f, err := os.Open(imagesJSON)
if err != nil {
return "", fmt.Errorf("failed to open %q: %w", imagesJSON, err)
}
defer f.Close()
var items []struct {
Name string `json:"name"`
Path string `json:"path"`
Type string `json:"type"`
}
if err := json.NewDecoder(f).Decode(&items); err != nil {
return "", fmt.Errorf("failed to parse %q: %w", imagesJSON, err)
}
for _, item := range items {
if item.Name == "zircon-a" && item.Type == "vbmeta" {
return filepath.Join(buildImageDir, item.Path), nil
}
}
return "", fmt.Errorf("failed to file zircon-a vbmeta in %q", imagesJSON)
}
func (b *ArtifactsBuild) String() string {
return b.id
}
type FuchsiaDirBuild struct {
dir string
}
func NewFuchsiaDirBuild(
dir string,
) *FuchsiaDirBuild {
return &FuchsiaDirBuild{
dir: dir,
}
}
func (b *FuchsiaDirBuild) String() string {
return b.dir
}
func (b *FuchsiaDirBuild) GetBootserver(ctx context.Context) (string, error) {
return filepath.Join(b.dir, "host_x64/bootserver_new"), nil
}
func (b *FuchsiaDirBuild) GetFfx(
ctx context.Context,
ffxIsolateDir ffx.IsolateDir,
) (*ffx.FFXTool, error) {
ffxPath := filepath.Join(b.dir, "host_x64/ffx")
return ffx.NewFFXTool(ffxPath, ffxIsolateDir)
}
func (b *FuchsiaDirBuild) GetFlashManifest(ctx context.Context) (string, error) {
return filepath.Join(b.dir, "flash.json"), nil
}
func (b *FuchsiaDirBuild) GetPackageRepository(
ctx context.Context,
blobFetchMode BlobFetchMode,
ffxIsolateDir ffx.IsolateDir,
) (*packages.Repository, error) {
ffx, err := b.GetFfx(ctx, ffxIsolateDir)
if err != nil {
return nil, fmt.Errorf("failed to get ffx: %w", err)
}
blobFS := packages.NewDirBlobStore(filepath.Join(b.dir, "amber-files", "repository", "blobs"))
blobType, err := build.GetDeliveryBlobType(filepath.Join(b.dir, "delivery_blob_config.json"))
if err != nil {
return nil, fmt.Errorf("failed to get delivery blob type: %w", err)
}
return packages.NewRepository(ctx, filepath.Join(b.dir, "amber-files"), blobFS, ffx, blobType)
}
func (b *FuchsiaDirBuild) GetPaverDir(ctx context.Context) (string, error) {
return b.dir, nil
}
func (b *FuchsiaDirBuild) GetPaver(
ctx context.Context,
sshPublicKey ssh.PublicKey,
) (paver.Paver, error) {
bootserverPath, err := b.GetBootserver(ctx)
if err != nil {
return nil, err
}
return paver.NewBuildPaver(bootserverPath, b.dir, paver.SSHPublicKey(sshPublicKey))
}
func (b *FuchsiaDirBuild) GetVbmetaPath(ctx context.Context) (string, error) {
imagesJSON := filepath.Join(b.dir, paver.ImageManifest)
f, err := os.Open(imagesJSON)
if err != nil {
return "", fmt.Errorf("failed to open %q: %w", imagesJSON, err)
}
defer f.Close()
var items []build.Image
if err := json.NewDecoder(f).Decode(&items); err != nil {
return "", fmt.Errorf("failed to parse %q: %w", imagesJSON, err)
}
for _, item := range items {
if item.Name == "zircon-a" && item.Type == "vbmeta" {
return filepath.Join(b.dir, item.Path), nil
}
}
return "", fmt.Errorf("failed to file zircon-a vbmeta in %q", imagesJSON)
}
type ProductBundleDirBuild struct {
dir string
}
func NewProductBundleDirBuild(
dir string,
) *ProductBundleDirBuild {
return &ProductBundleDirBuild{
dir: dir,
}
}
func (b *ProductBundleDirBuild) String() string {
return b.dir
}
func (b *ProductBundleDirBuild) GetBootserver(ctx context.Context) (string, error) {
// Only flashing is supported for ProductBundle
return "", nil
}
func (b *ProductBundleDirBuild) GetFfx(
ctx context.Context,
ffxIsolateDir ffx.IsolateDir,
) (*ffx.FFXTool, error) {
ffxPath := filepath.Join(b.dir, "ffx")
return ffx.NewFFXTool(ffxPath, ffxIsolateDir)
}
func (b *ProductBundleDirBuild) GetFlashManifest(ctx context.Context) (string, error) {
return b.dir, nil
}
func (b *ProductBundleDirBuild) GetPackageRepository(
ctx context.Context,
blobFetchMode BlobFetchMode,
ffxIsolateDir ffx.IsolateDir,
) (*packages.Repository, error) {
// TODO(https://fxbug.dev/42066028) Change to use ffx tool to start package server
pbJSON := filepath.Join(b.dir, ProductBundleManifest)
f, err := os.Open(pbJSON)
if err != nil {
return nil, fmt.Errorf("failed to open %q: %w", pbJSON, err)
}
defer f.Close()
var productBundle ProductBundle
if err := json.NewDecoder(f).Decode(&productBundle); err != nil {
return nil, fmt.Errorf("failed to parse %q: %w", pbJSON, err)
}
if productBundle.Version != "2" {
return nil, fmt.Errorf("Product bundle version is not 2 %q", pbJSON)
}
ffx, err := b.GetFfx(ctx, ffxIsolateDir)
if err != nil {
return nil, fmt.Errorf("failed to get ffx: %w", err)
}
blobFS := packages.NewDirBlobStore(filepath.Join(b.dir, productBundle.Repositories[0].BlobsPath))
// TODO(https://fxbug.dev/42076853): Read delivery blob type from product bundle.
return packages.NewRepository(ctx, b.dir, blobFS, ffx, nil)
}
func (b *ProductBundleDirBuild) GetPaverDir(ctx context.Context) (string, error) {
// Only flashing is supported for product bundles
return "", fmt.Errorf("paving is not supported with product bundles")
}
func (b *ProductBundleDirBuild) GetPaver(
ctx context.Context,
sshPublicKey ssh.PublicKey,
) (paver.Paver, error) {
// Only flashing is supported for product bundles
return nil, fmt.Errorf("paving is not supported with product bundles")
}
type ProductBundle struct {
SystemA []build.Image `json:"system_a"`
Version string `json:"version"`
Repositories []PBRepository `json:"repositories"`
}
type PBRepository struct {
MetadataPath string `json:"metadata_path"`
BlobsPath string `json:"blobs_path"`
}
func (b *ProductBundleDirBuild) GetVbmetaPath(ctx context.Context) (string, error) {
pbJSON := filepath.Join(b.dir, ProductBundleManifest)
f, err := os.Open(pbJSON)
if err != nil {
return "", fmt.Errorf("failed to open %q: %w", pbJSON, err)
}
defer f.Close()
var productBundle ProductBundle
if err := json.NewDecoder(f).Decode(&productBundle); err != nil {
return "", fmt.Errorf("failed to parse %q: %w", pbJSON, err)
}
if productBundle.Version != "2" {
return "", fmt.Errorf("Product bundle version is not 2 %q", pbJSON)
}
for _, item := range productBundle.SystemA {
if item.Name == "zircon-a" && item.Type == "vbmeta" {
return filepath.Join(b.dir, item.Path), nil
}
}
return "", fmt.Errorf("failed to find zircon-a vbmeta in %q", pbJSON)
}
type OmahaBuild struct {
build Build
omahatool *omaha_tool.OmahaTool
avbtool *avb.AVBTool
zbitool *zbi.ZBITool
}
func NewOmahaBuild(build Build, omahatool *omaha_tool.OmahaTool, avbtool *avb.AVBTool, zbitool *zbi.ZBITool) *OmahaBuild {
return &OmahaBuild{build: build, omahatool: omahatool, avbtool: avbtool, zbitool: zbitool}
}
func (b *OmahaBuild) String() string {
return b.build.String()
}
func (b *OmahaBuild) GetBootserver(ctx context.Context) (string, error) {
return b.build.GetBootserver(ctx)
}
func (b *OmahaBuild) GetFfx(
ctx context.Context,
ffxIsolateDir ffx.IsolateDir,
) (*ffx.FFXTool, error) {
return b.build.GetFfx(ctx, ffxIsolateDir)
}
type versionedFlashManifest struct {
Version int `json:"version"`
Manifest flashManifest `json:"manifest"`
}
type flashManifest struct {
Credentials []string `json:"credentials,omitempty"`
HwRevision string `json:"hw_revision"`
Products []flashProduct `json:"products,omitempty"`
}
type flashProduct struct {
BootloaderPartitions []flashPartition `json:"bootloader_partitions,omitempty"`
Name string `json:"name"`
Partitions []flashPartition `json:"partitions,omitempty"`
RequiresUnlock bool `json:"requires_unlock"`
}
type flashPartition struct {
Name string `json:"name"`
Path string `json:"path"`
Condition *flashCondition `json:"condition,omitempty"`
}
type flashCondition struct {
Value string `json:"value"`
Variable string `json:"variable"`
}
func (b *OmahaBuild) GetFlashManifest(ctx context.Context) (string, error) {
flashManifestPath, err := b.build.GetFlashManifest(ctx)
if err != nil {
return "", err
}
fileInfo, err := os.Stat(flashManifestPath)
if err != nil {
return "", err
}
// Error out if we're dealing with a product bundle build.
if fileInfo.IsDir() {
return "", fmt.Errorf(
"flashing product bundles with omaha builds currently not supported",
)
}
f, err := os.Open(flashManifestPath)
if err != nil {
return "", err
}
defer f.Close()
var manifest versionedFlashManifest
decoder := json.NewDecoder(f)
decoder.DisallowUnknownFields()
if err := decoder.Decode(&manifest); err != nil {
return "", err
}
if manifest.Version != 3 {
return "", fmt.Errorf("Unknown flash manifest version %d", manifest.Version)
}
srcVbmetaPath, err := b.GetVbmetaPath(ctx)
if err != nil {
return "", fmt.Errorf("failed to find zircon-a vbmeta: %w", err)
}
tempDir, err := os.MkdirTemp("", "")
if err != nil {
return "", fmt.Errorf("failed to create temp directory: %w", err)
}
defer os.RemoveAll(tempDir)
// Create a ZBI with the omaha_url argument.
destZbiPath := path.Join(tempDir, "omaha_argument.zbi")
imageArguments := map[string]string{
"omaha_url": b.omahatool.URL(),
"omaha_app_id": b.omahatool.Args.AppId,
}
if err := b.zbitool.MakeImageArgsZbi(ctx, destZbiPath, imageArguments); err != nil {
return "", fmt.Errorf("Failed to create ZBI: %w", err)
}
// Create a vbmeta that includes the ZBI we just created.
propFiles := map[string]string{
"zbi": destZbiPath,
}
paverDir, err := b.GetPaverDir(ctx)
if err != nil {
return "", err
}
destVbmetaPath := filepath.Join(paverDir, "zircon-a-omaha-test.vbmeta")
err = b.avbtool.MakeVBMetaImage(ctx, destVbmetaPath, srcVbmetaPath, propFiles)
if err != nil {
return "", fmt.Errorf("failed to create vbmeta: %w", err)
}
// Update the manifest to point at the new vbmeta.
for i, product := range manifest.Manifest.Products {
if product.Name == "fuchsia" || product.Name == "fuchsia_only" {
for j, partition := range product.Partitions {
if partition.Name == "vbmeta_a" || partition.Name == "vbmeta_b" {
manifest.Manifest.Products[i].Partitions[j].Path = destVbmetaPath
}
}
}
}
// Write out the manifest to a new file and return it..
updatedFlashManifest := filepath.Join(paverDir, "flash-omaha-test.json")
f, err = os.Create(updatedFlashManifest)
if err != nil {
return "", err
}
defer f.Close()
encoder := json.NewEncoder(f)
encoder.SetIndent("", " ")
if err := encoder.Encode(&manifest); err != nil {
return "", err
}
return updatedFlashManifest, nil
}
// GetPackageRepository returns a Repository for this build.
func (b *OmahaBuild) GetPackageRepository(
ctx context.Context,
blobFetchMode BlobFetchMode,
ffxIsolateDir ffx.IsolateDir,
) (*packages.Repository, error) {
return b.build.GetPackageRepository(ctx, blobFetchMode, ffxIsolateDir)
}
func (b *OmahaBuild) GetPaverDir(ctx context.Context) (string, error) {
return b.build.GetPaverDir(ctx)
}
// GetPaver downloads and returns a paver for the build.
func (b *OmahaBuild) GetPaver(
ctx context.Context,
sshPublicKey ssh.PublicKey,
) (paver.Paver, error) {
paverDir, err := b.GetPaverDir(ctx)
if err != nil {
return nil, err
}
bootserverPath, err := b.GetBootserver(ctx)
if err != nil {
return nil, err
}
// Create a ZBI with the omaha_url argument.
tempDir, err := os.MkdirTemp("", "")
if err != nil {
return nil, fmt.Errorf("failed to create temp directory: %w", err)
}
defer os.RemoveAll(tempDir)
// Create a ZBI with the omaha_url argument.
destZbiPath := path.Join(tempDir, "omaha_argument.zbi")
imageArguments := map[string]string{
"omaha_url": b.omahatool.URL(),
"omaha_app_id": b.omahatool.Args.AppId,
}
if err := b.zbitool.MakeImageArgsZbi(ctx, destZbiPath, imageArguments); err != nil {
return nil, fmt.Errorf("Failed to create ZBI: %w", err)
}
// Create a vbmeta that includes the ZBI we just created.
propFiles := map[string]string{
"zbi": destZbiPath,
}
destVbmetaPath := filepath.Join(paverDir, "zircon-a-omaha-test.vbmeta")
srcVbmetaPath, err := b.GetVbmetaPath(ctx)
if err != nil {
return nil, fmt.Errorf("failed to find zircon-a vbmeta: %w", err)
}
err = b.avbtool.MakeVBMetaImage(ctx, destVbmetaPath, srcVbmetaPath, propFiles)
if err != nil {
return nil, fmt.Errorf("failed to create vbmeta: %w", err)
}
return paver.NewBuildPaver(
bootserverPath,
paverDir,
paver.SSHPublicKey(sshPublicKey),
paver.OverrideVBMetaA(destVbmetaPath),
)
}
func (b *OmahaBuild) GetVbmetaPath(ctx context.Context) (string, error) {
return b.build.GetVbmetaPath(ctx)
}