blob: 2d979a245925375e0e9b83a5936e66a57e793b19 [file] [log] [blame]
// Copyright 2023 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 cipd
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"go.fuchsia.dev/infra/cmd/recipe_wrapper/bcid"
"golang.org/x/exp/slices"
)
type Package struct {
name string
version string
binDir string
}
var (
VPythonPkg = Package{
name: "infra/tools/luci/vpython/${platform}",
version: "git_revision:3d6ee7542a04be92a48ff1c5dc28cb3c5c15dd00",
binDir: "",
}
CPythonPkg = Package{
name: "infra/3pp/tools/cpython/${platform}",
version: "version:2@2.7.18.chromium.44",
binDir: "bin",
}
)
func Install(ctx context.Context, dir string, packages ...Package) ([]string, error) {
cmd := exec.CommandContext(ctx, "cipd", "init", dir, "-force")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("cipd init failed: %w", err)
}
var binDirs []string
for _, pkg := range packages {
cmd := exec.CommandContext(ctx, "cipd", "install", pkg.name, pkg.version, "-root", dir)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("failed to install %s: %w", pkg.name, err)
}
binDirs = append(binDirs, filepath.Join(dir, pkg.binDir))
}
return binDirs, nil
}
type DeferredStep struct {
Name string `json:"name"`
Command []string `json:"command"`
Environment map[string]string `json:"environment"`
}
type deferredSteps struct {
Steps []DeferredStep `json:"steps"`
}
func ParseDeferredSteps(data io.Reader) ([]DeferredStep, error) {
d, err := io.ReadAll(data)
if err != nil {
return nil, err
}
var steps deferredSteps
if err := json.Unmarshal(d, &steps); err != nil {
return nil, err
}
return steps.Steps, nil
}
// RegisteredPkgs returns the filepaths to CIPD packages that were
// registered during the deferred steps.
// Assumes the package filepath is immediately after the `pkg-deploy` command.
// Names are not returned.
func RegisteredPkgs(steps []*DeferredStep) []string {
var paths []string
for _, s := range steps {
if i := slices.Index(s.Command, "pkg-register"); i >= 0 {
fp, err := filepath.Abs(s.Command[i+1])
// Don't attempt to handle the error. Garbage in, garbage out is sufficient here.
if err != nil {
continue
}
paths = append(paths, fp)
}
}
return paths
}
// AttestPkgs searches for packages that were registered to CIPD and
// generates attestations for them.
func AttestPkgs(steps []*DeferredStep, keyID string) ([]*bcid.InTotoBundle, []error) {
var bundles []*bcid.InTotoBundle
var errs []error
for _, pkg := range RegisteredPkgs(steps) {
f, err := os.Open(pkg)
if err != nil {
errs = append(errs, fmt.Errorf("can't open package file %q: %v", pkg, err))
continue
}
defer f.Close()
stmt, err := bcid.NewStmt(&bcid.Subject{
Name: pkg,
Data: f,
})
if err != nil {
errs = append(errs, fmt.Errorf("can't create statement for package %q: %v", pkg, err))
continue
}
b, err := bcid.Attest(context.TODO(), stmt, keyID)
if err != nil {
errs = append(errs, fmt.Errorf("can't attest package %q: %v", pkg, err))
continue
}
bundles = append(bundles, b)
}
return bundles, errs
}