| package actions |
| |
| import ( |
| "context" |
| "errors" |
| "fmt" |
| "os" |
| "os/exec" |
| "path" |
| "strings" |
| |
| "cloud.google.com/go/storage" |
| "cos.googlesource.com/cos/tools.git/src/pkg/cos" |
| "cos.googlesource.com/cos/tools.git/src/pkg/dkms" |
| "cos.googlesource.com/cos/tools.git/src/pkg/fs" |
| "cos.googlesource.com/cos/tools.git/src/pkg/gcs" |
| "cos.googlesource.com/cos/tools.git/src/pkg/utils" |
| "github.com/golang/glog" |
| "github.com/spf13/cobra" |
| "google.golang.org/api/option" |
| ) |
| |
| func FetchKernelVersion() (string, error) { |
| kernelVersion := os.Getenv("KERNEL_VERSION") |
| if kernelVersion != "" { |
| return kernelVersion, nil |
| } |
| |
| cmd := exec.Command("uname", "-r") |
| out, err := cmd.CombinedOutput() |
| if err == nil { |
| glog.Info(string(out)) |
| } else { |
| glog.Error(string(out)) |
| return "", err |
| } |
| return strings.Trim(string(out), "\n"), nil |
| } |
| |
| // FetchKernelVersionFromHeaders downloads the COS kernel headers for the |
| // given build id and board to a temporary directory and reads the kernel |
| // release version from include/config/kernel.release. |
| func FetchKernelVersionFromHeaders(buildId, board string) (string, error) { |
| ctx := context.Background() |
| client, err := storage.NewClient(ctx, option.WithoutAuthentication()) |
| if err != nil { |
| return "", err |
| } |
| |
| prefix := path.Join(buildId, board) |
| downloader := cos.NewGCSDownloader(client, nil, "", prefix, "", "") |
| |
| tmpDir, err := os.MkdirTemp("", "") |
| if err != nil { |
| return "", err |
| } |
| defer os.RemoveAll(tmpDir) |
| |
| glog.Infof("installing kernel headers to determine kernel version") |
| if err := dkms.InstallKernelHeaders(ctx, downloader, tmpDir); err != nil { |
| return "", err |
| } |
| |
| versionPath := path.Join(tmpDir, "include/config/kernel.release") |
| versionBytes, err := os.ReadFile(versionPath) |
| if err != nil { |
| return "", err |
| } |
| version := strings.TrimSpace(string(versionBytes)) |
| |
| return version, nil |
| } |
| |
| func FetchArch() (string, error) { |
| arch := os.Getenv("ARCH") |
| if arch != "" { |
| return arch, nil |
| } |
| |
| cmd := exec.Command("uname", "-m") |
| out, err := cmd.CombinedOutput() |
| if err == nil { |
| glog.Info(string(out)) |
| } else { |
| glog.Error(string(out)) |
| return "", err |
| } |
| return strings.Trim(string(out), "\n"), nil |
| } |
| |
| func ParsePackageNameAndVersion(name, version string) (string, string, error) { |
| if name == "" { |
| return "", "", errors.New("must provide a package name and version as <package>/<version>") |
| } |
| |
| parts := strings.Split(name, "/") |
| name = parts[0] |
| if name == "" { |
| return "", "", errors.New("package name must not be empty") |
| } |
| |
| if version == "" { |
| if len(parts) != 2 { |
| return "", "", errors.New("must provide a package and version as <package>/<version>") |
| } |
| |
| version = parts[1] |
| if version == "" { |
| return "", "", errors.New("package version must not be empty") |
| } |
| } else { |
| if len(parts) != 1 { |
| return "", "", errors.New("cannot provide explicit package version and <package>/<version> simultaneously") |
| } |
| } |
| |
| return name, version, nil |
| } |
| |
| func initPackage(pkg *dkms.Package) error { |
| name, version, err := ParsePackageNameAndVersion(pkg.Name, pkg.Version) |
| if err != nil { |
| return err |
| } |
| pkg.Name = name |
| pkg.Version = version |
| |
| if pkg.KernelVersion == "" { |
| kernelVersion, err := FetchKernelVersion() |
| if err != nil { |
| return fmt.Errorf("could not determine kernel version: %v", err) |
| } |
| pkg.KernelVersion = kernelVersion |
| } else if pkg.KernelVersion == "cos-default" { |
| kernelVersion, err := FetchKernelVersionFromHeaders(pkg.BuildId, pkg.Board) |
| if err != nil { |
| return fmt.Errorf("could not determine kernel version from headers: %v", err) |
| } |
| pkg.KernelVersion = kernelVersion |
| } |
| |
| if pkg.Arch == "" { |
| arch, err := FetchArch() |
| if err != nil { |
| return fmt.Errorf("could not determine arch: %v", err) |
| } |
| pkg.Arch = arch |
| } |
| |
| if pkg.Trees.Source == "" { |
| pkg.Trees.Source = "/usr/src" |
| } |
| |
| if pkg.Trees.Install == "" { |
| pkg.Trees.Install = fmt.Sprintf("/lib/modules/%s", pkg.KernelVersion) |
| } |
| |
| if pkg.Trees.Kernel == "" { |
| pkg.Trees.Kernel = fmt.Sprintf("/lib/modules/%s/build", pkg.KernelVersion) |
| } |
| |
| if pkg.Trees.KernelModules == "" { |
| pkg.Trees.KernelModules = pkg.Trees.Install |
| } |
| |
| if fs.IsDir(pkg.SourceDir()) { |
| config, err := dkms.LoadConfig(pkg) |
| if err != nil { |
| glog.Warningf("error loading dkms config: %v", err) |
| } else { |
| pkg.Config = config |
| } |
| } |
| |
| return nil |
| } |
| |
| func parseKeyCertPaths(options *dkms.Options) { |
| privateKeyPath := os.Getenv("MODULES_SIGN_KEY") |
| if privateKeyPath == "" { |
| privateKeyPath = "/var/lib/dkms/mok.key" |
| } |
| |
| certificatePath := os.Getenv("MODULES_SIGN_CERT") |
| if certificatePath == "" { |
| certificatePath = "/var/lib/dkms/mok.cert" |
| } |
| |
| options.PrivateKeyPath = privateKeyPath |
| options.CertificatePath = certificatePath |
| } |
| |
| func FetchLatestCompatiblePackageVersion(pkg *dkms.Package, cache *gcs.GCSBucket) (string, error) { |
| versions, err := dkms.CompatiblePackageVersions(pkg) |
| if err != nil { |
| return "", err |
| } |
| |
| if cache != nil { |
| ctx := context.Background() |
| cachedVersions, err := dkms.CachedCompatiblePackageVersions(ctx, pkg, cache) |
| if err != nil { |
| return "", err |
| } |
| |
| versions = append(versions, cachedVersions...) |
| } |
| |
| if len(versions) == 0 { |
| if cache != nil { |
| return "", fmt.Errorf("could not find any usable versions of the specified package in the local or cache DKMS tree; make sure the package is added first before using --latest") |
| } else { |
| return "", fmt.Errorf("could not find any usable versions of the specified package in the local DKMS tree; make sure the package is added first before using --latest") |
| } |
| } |
| |
| latestVersion := versions[0] |
| for _, version := range versions[1:] { |
| if dkms.CompareVersions(latestVersion, version) < 0 { |
| latestVersion = version |
| } |
| } |
| |
| return latestVersion, nil |
| } |
| |
| func InitPackage(pkg *dkms.Package, cache *gcs.GCSBucket, latest bool) error { |
| // We need all of the other package fields initialized before we can |
| // determine the latest version of the package, so we fill in a |
| // placeholder value of the version so that the rest of the |
| // initialization works as normal |
| if latest { |
| if pkg.Version != "" { |
| return fmt.Errorf("cannot specify explicit package version (%s) and --latest simultaneously", pkg.Version) |
| } |
| |
| pkg.Version = "placeholder" |
| } |
| |
| if err := initPackage(pkg); err != nil { |
| return err |
| } |
| |
| // After the other fields are initialized, we can get the actual latest |
| // package version. |
| if latest { |
| version, err := FetchLatestCompatiblePackageVersion(pkg, cache) |
| if err != nil { |
| return err |
| } |
| pkg.Version = version |
| } |
| glog.Infof("using package version %s", pkg.Version) |
| |
| return nil |
| } |
| |
| func InitCache(gcsPath, buildId, board string) (*gcs.GCSBucket, error) { |
| if gcsPath == "" { |
| gcsPath = os.Getenv("GCS_BUCKET") |
| } |
| |
| if gcsPath == "" { |
| return nil, nil |
| } |
| |
| if gcsPath == "cos-default" { |
| bucket := cos.DefaultCOSArtifactsBucket() |
| gcsPath = fmt.Sprintf("gs://%s/%s/%s", bucket, buildId, board) |
| } |
| |
| ctx := context.Background() |
| client, err := storage.NewClient(ctx) |
| if err != nil { |
| return nil, fmt.Errorf("failed to create storage client: %v", err) |
| } |
| return gcs.GCSBucketFromPath(client, gcsPath) |
| } |
| |
| func parsePositionalArgs(args []string, pkg *dkms.Package) error { |
| if len(args) >= 1 { |
| if pkg.Name != "" { |
| return fmt.Errorf("cannot specify package name as both positional argument and flag") |
| } |
| |
| pkg.Name = args[0] |
| } |
| |
| if len(args) >= 2 { |
| if pkg.Trees.Source != "" { |
| return fmt.Errorf("cannot specify source tree as both positional argument and flag") |
| } |
| |
| pkg.Trees.Source = args[1] |
| } |
| |
| if len(args) > 2 { |
| return fmt.Errorf("expected at most 2 positional arguments; received %d", len(args)) |
| } |
| |
| return nil |
| } |
| |
| // InitCOSValues initializes the package values which are specific to COS, |
| // namely BuildId and Board. |
| func InitCOSValues(pkg *dkms.Package, lsbReleasePath string) { |
| if pkg.BuildId == "" { |
| pkg.BuildId = os.Getenv("BUILD_ID") |
| } |
| |
| if pkg.Board == "" { |
| pkg.Board = os.Getenv("BOARD") |
| } |
| |
| if pkg.BuildId != "" && pkg.Board != "" { |
| return |
| } |
| |
| lsbReleaseVars, err := utils.LoadEnvFromFile("", lsbReleasePath) |
| if err != nil { |
| glog.Warningf("could not read lsb-release file at %s: %v", err) |
| } |
| if pkg.BuildId == "" { |
| pkg.BuildId = lsbReleaseVars["CHROMEOS_RELEASE_VERSION"] |
| } |
| if pkg.Board == "" { |
| pkg.Board = lsbReleaseVars["CHROMEOS_RELEASE_BOARD"] |
| } |
| } |
| |
| // ParseArgs parses the cobra positional arguments and flags to populate the |
| // options, pkg, and RootGCSBucket variables. |
| func ParseArgs(cmd *cobra.Command, args []string) error { |
| if err := parsePositionalArgs(args, RootPackage); err != nil { |
| return err |
| } |
| parseKeyCertPaths(RootOptions) |
| |
| InitCOSValues(RootPackage, RootOptions.LSBReleasePath) |
| |
| var err error |
| RootGCSBucket, err = InitCache(RootOptions.GCSBucket, RootPackage.BuildId, RootPackage.Board) |
| if err != nil { |
| return err |
| } |
| |
| if err := InitPackage(RootPackage, RootGCSBucket, RootOptions.Latest); err != nil { |
| return err |
| } |
| |
| glog.V(2).Infof("options: %v", RootOptions) |
| glog.V(2).Infof("package: %v", RootPackage) |
| glog.V(2).Infof("trees: %v", RootTrees) |
| glog.V(2).Infof("gcs bucket: %v", RootGCSBucket) |
| |
| return nil |
| } |