blob: bae950354e13fccaa9506d1cbd990bc02f11da6a [file] [edit]
package commands
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
kernelmods "cos-extensions/extensions/kernel-mods"
"cos-extensions/tools/gcs"
"cos-extensions/tools/osutils"
"github.com/golang/glog"
"github.com/spf13/cobra"
)
const (
defaultLocalDir = "/var/lib/modules"
osReleasePath = "/etc/os-release"
defaultGCSBucket = "cos-tools"
)
var (
statelessPaths = []string{"/var/lib/cloud", "/mnt/disks", "/tmp", "/etc"}
kernelModsListCmd = &cobra.Command{
Use: "kernel-mods",
Short: "Lists the available compiled kernel modules.",
Long: `Lists the available compiled kernel modules
Additional Description:
cos-extensions list kernel-mods [flags] lists all the available compiled kernel modules
in a gcs bucket directory.`,
Args: cobra.NoArgs,
PreRunE: func(cmd *cobra.Command, args []string) error {
installed, _ := cmd.Flags().GetBool("installed")
downloaded, _ := cmd.Flags().GetBool("downloaded")
isGCSset := cmd.Flags().Changed("gcs_path")
if installed && isGCSset {
return fmt.Errorf("flags installed and gcs_path cannot be used together.")
}
if downloaded && isGCSset {
return fmt.Errorf("flags downloaded and gcs_path cannot be used together.")
}
if downloaded && installed {
return fmt.Errorf("flags downloaded and installed cannot be used together.")
}
return nil
},
RunE: runKernelModsList,
}
kernelModsInstallCmd = &cobra.Command{
Use: "kernel-mods [module files] [flags] -- [module params]",
DisableFlagsInUseLine: true,
Short: "Installs kernel modules.",
Example: `cos-extensions install kernel-mods a.ko,b.ko --local-dir='/module' installs a.ko, b.ko from default gcs path to '/module'
cos-extensions install kernel-mods -r installs all modules in a gcs path recursively to default local directory
cos-extensions install kernel-mods var/a.ko --local-dir='/module' installs module a.ko from gcs sub directory 'var' to '/modules/var'`,
Long: `Installs compiled kernel modules
Additional Description:
cos-extensions install kernel-mods [module files] downloads specified complied modules from a gcs bucket directory
[flags] -- [module params] and loads them to the kernel. Each module file should be
separated by a comma.
Note that module params should be used when downloading only one
module for appropriate behavior.`,
PreRunE: func(cmd *cobra.Command, args []string) error {
localOnly, _ := cmd.Flags().GetBool("local_only")
downloadOnly, _ := cmd.Flags().GetBool("download_only")
recursive, _ := cmd.Flags().GetBool("recursive")
if localOnly && downloadOnly {
return fmt.Errorf("flags local_only and download_only cannot be used together.")
} else if localOnly && recursive {
return fmt.Errorf("flags local_only and recursive cannot be used together.")
}
if len(args) == 0 && !recursive {
return fmt.Errorf("requires at least 1 arg(s), only received 0. Arguments are only not required with the recursive flag.")
} else if len(args) != 0 && recursive {
return fmt.Errorf("flag recursive cannot be used with module files.")
}
return nil
},
RunE: runKernelModsInstall,
}
)
func init() {
// Registering subcommands for kernel-mods extension
listCmd.AddCommand(kernelModsListCmd)
installCmd.AddCommand(kernelModsInstallCmd)
defaultGCSPath, err := setDefaultGCSPath()
if err != nil {
glog.Errorf("Failed to retrieve default GCS path: %v\n", err)
}
// Flag definitions for cos-extensions install kernel-mods
kernelModsInstallCmd.Flags().String("gcs_path", defaultGCSPath,
"GCS path which stores the available kernel modules to be installed. The expected syntax is gs://<bucket name>/<directory path>.")
kernelModsInstallCmd.Flags().String("local_dir", defaultLocalDir, "local directory where the kernel modules would be installed.")
kernelModsInstallCmd.Flags().BoolP("download_only", "d", false, "whether to download the module to the local directory without installing. "+
"This flag cannot be used with the local_only flag.")
kernelModsInstallCmd.Flags().BoolP("local_only", "l", false, "whether to install modules directly from local directory without downloading. "+
"This flag cannot be used with the download_only flag.")
kernelModsInstallCmd.Flags().BoolP("recursive", "r", false, "whether to install modules from a gcs bucket directory and its subdirectories. "+
"This flag cannot be used with the local_only flag nor specified module files. Note that the modules are downloaded with their subdirectory path.")
// Flag definitions for cos-extensions list kernel-mods
kernelModsListCmd.Flags().String("gcs_path", defaultGCSPath,
"GCS path from which available kernel modules would be listed. The expected syntax is gs://<bucket name>/<directory path>.")
kernelModsListCmd.Flags().String("local_dir", defaultLocalDir, "local directory from which kernel modules would be listed.")
kernelModsListCmd.Flags().BoolP("downloaded", "d", false, "whether to list only the downloaded compiled modules in a local directory. "+
"This can be used with local_dir flag, else it uses default directory. It can be used with the recursive flag to include "+
"downloaded modules in the subdirectories.")
kernelModsListCmd.Flags().BoolP("installed", "i", false, "whether to list only the installed kernel modules in a local directory. "+
"This can be used with local_dir flag, else it uses the default directory. It can be used with the recursive flag to include "+
"installed modules in subdirectories.")
kernelModsListCmd.Flags().BoolP("recursive", "r", false, "whether to list all the available compiled modules in the gcs path and its subdirectories. "+
"This can be used with gcs_path flag, else it uses the default gcs path. It can be used with installed flag to list all installed kernels in the "+
"directory and its subdirectories. It can also be used with the downloaded flag.")
}
// runKernelModsList displays all available kernel modules in a gcs path if no flags
// are given or the gcs_path is set. It also displays all downloaded modules in a local
// directory if the downloaded flag is set, and all installed/loaded modules if the
// installed flag is set.
func runKernelModsList(cmd *cobra.Command, args []string) error {
ctx := context.Background()
downloaded, _ := cmd.Flags().GetBool("downloaded")
installed, _ := cmd.Flags().GetBool("installed")
// Displays all available modules if installed and downloaded are not set.
// Default behavior.
if !downloaded && !installed {
err := displayAvailableModules(ctx, cmd)
if err != nil {
return fmt.Errorf("failed to list available kernel modules: %v", err)
}
}
if downloaded {
err := displayDownloadedModules(cmd)
if err != nil {
return fmt.Errorf("failed to list downloaded kernel modules: %v", err)
}
}
if installed {
err := displayInstalledModules(cmd)
if err != nil {
return fmt.Errorf("failed to list installed kernel modules: %v", err)
}
}
return nil
}
// runKernelModsInstall downloads the specified module files from a GCS path to a local directory.
// If the download_only flag is not set, loads the downloaded modules into the kernel.
// If local_only flag is set, loads the specified module files from a local directory into the kernel.
func runKernelModsInstall(cmd *cobra.Command, args []string) error {
ctx := context.Background()
gcsPath, err := cmd.Flags().GetString("gcs_path")
if err != nil {
return fmt.Errorf("failed to retrieve gcs path (%q): %v", gcsPath, err)
}
localDir, err := cmd.Flags().GetString("local_dir")
if err != nil {
return fmt.Errorf("failed to retrieve gcs path (%q): %v", localDir, err)
}
localOnly, _ := cmd.Flags().GetBool("local_only")
downloadOnly, _ := cmd.Flags().GetBool("download_only")
recursive, _ := cmd.Flags().GetBool("recursive")
// Displaying warning if local directory is stateless.
absPath, err := filepath.Abs(localDir)
if err != nil {
glog.Errorf("Failed to retrieve absolute path for file (%s): %v", localDir, err)
} else {
for _, dir := range statelessPaths {
if strings.HasPrefix(absPath, dir) {
glog.Warningf("WARNING: This local directory (%s) is stateless and would not retain downloaded files.", localDir)
}
}
}
var downloadedModules, moduleFiles []string
if !recursive {
// Retrieving modules from positional arguments.
// Expected syntax of modules: mod1.ko,mod2.ko,mod3.ko
moduleFiles = strings.Split(args[0], ",")
}
// Skipping downloading the module from gcs bucket if flag local_only is set.
if !localOnly {
// Creating a gcs client
client, err := gcs.NewClient(ctx)
if err != nil {
return fmt.Errorf("failed to create a new gcs client: %v", err)
}
var cfg gcs.GCSConfig
if err = cfg.Init(ctx, gcsPath, client); err != nil {
return fmt.Errorf("failed to initialize gcs config: %v", err)
}
glog.Infof("Beginning download for module file(s): %s", moduleFiles)
downloadedModules, err = kernelmods.DownloadModules(ctx, &cfg, moduleFiles, localDir, recursive)
if err != nil {
return fmt.Errorf("failed to download modules (%s) from gcs path (%s): %v", moduleFiles, gcsPath, err)
}
} else {
// Retrieving the modules full path in the local directory
for _, module := range moduleFiles {
// Skipping if file doesn't exist.
modulePath := filepath.Join(localDir, module)
if _, err := os.Stat(modulePath); errors.Is(err, os.ErrNotExist) {
glog.Errorf("File (%s) does not exist [skipping]: %v\n", modulePath, err)
continue
}
downloadedModules = append(downloadedModules, modulePath)
}
}
// Skipping installation if flag download_only set or downloadedModules is empty.
var installedModules []string
if !downloadOnly && len(downloadedModules) > 0 {
glog.Infof("Beginning installation of module file(s): %s", downloadedModules)
var moduleArgs []string
if len(args) > 1 {
moduleArgs = args[1:]
if len(downloadedModules) > 1 {
glog.Warning("WARNING: The module parameters set will be applied to all modules being installed. " +
"This might lead to unfavorable results if different settings are required for individual modules.")
}
}
installedModules, err = kernelmods.InstallModules(downloadedModules, moduleArgs)
if err != nil {
return fmt.Errorf("failed to complete installation of modules (%s): %v", downloadedModules, err)
}
}
// Generating log summary
glog.Info("Installation summary: ")
if !localOnly {
glog.Infof("Downloaded %d module file(s) - (%s)", len(downloadedModules), downloadedModules)
}
if !downloadOnly {
glog.Infof("Installed %d module(s) - (%s)", len(installedModules), installedModules)
}
return nil
}
func setDefaultGCSPath() (string, error) {
defaultGCSPath := fmt.Sprintf("gs://%s", defaultGCSBucket)
osfile, err := osutils.Parse(osReleasePath)
if err != nil {
return "", fmt.Errorf("failed to parse os release file: %v", err)
}
buildID, ok := osfile["BUILD_ID"]
if !ok {
return "", fmt.Errorf("failed to retrieve build id from os release file (%s)", osReleasePath)
}
defaultGCSPath = fmt.Sprintf("%s/%s/kernel-mods", defaultGCSPath, buildID)
return defaultGCSPath, nil
}
func displayInstalledModules(cmd *cobra.Command) error {
localDir, err := cmd.Flags().GetString("local_dir")
if err != nil {
return fmt.Errorf("failed to retieve local directory (%q): %v", localDir, err)
}
recursive, _ := cmd.Flags().GetBool("recursive")
installedModules, err := kernelmods.ListInstalledModules(localDir, recursive)
if err != nil {
return fmt.Errorf("failed to retrieve installed modules in directory (%s): %v", localDir, err)
}
if len(installedModules) > 0 {
fmt.Fprintf(os.Stdout, "Installed modules in local directory (%s):\n", localDir)
fmt.Fprintf(os.Stdout, "%-20s %-20s\n", "Module Name", "Path")
for filePath, name := range installedModules {
fmt.Fprintf(os.Stdout, "%-20s %-20s\n", name, filePath)
}
} else {
glog.Infof("No installed modules found in directory (%s)", localDir)
}
return nil
}
func displayDownloadedModules(cmd *cobra.Command) error {
localDir, err := cmd.Flags().GetString("local_dir")
if err != nil {
return fmt.Errorf("failed to retieve local directory (%q): %v", localDir, err)
}
recursive, _ := cmd.Flags().GetBool("recursive")
downloadedModules, err := kernelmods.ListDownloadedModules(localDir, recursive)
if err != nil {
return fmt.Errorf("failed to retrieve downloaded modules in directory (%s): %v", localDir, err)
}
if len(downloadedModules) > 0 {
fmt.Fprintf(os.Stdout, "Downloaded modules in local directory (%s):\n", localDir)
fmt.Fprintf(os.Stdout, "%-20s %-20s\n", "Module Name", "Path")
for filePath, name := range downloadedModules {
fmt.Fprintf(os.Stdout, "%-20s %-20s\n", name, filePath)
}
} else {
glog.Infof("No downloaded modules found in directory (%s)", localDir)
}
return nil
}
func displayAvailableModules(ctx context.Context, cmd *cobra.Command) error {
gcsPath, err := cmd.Flags().GetString("gcs_path")
if err != nil {
return fmt.Errorf("failed to retieve gcs path (%q): %v", gcsPath, err)
}
recursive, _ := cmd.Flags().GetBool("recursive")
// Creating a gcs client
client, err := gcs.NewClient(ctx)
if err != nil {
return fmt.Errorf("failed to create a new gcs client: %v", err)
}
var cfg gcs.GCSConfig
if err = cfg.Init(ctx, gcsPath, client); err != nil {
return fmt.Errorf("failed to initialize gcs config: %v", err)
}
availableModules, err := cfg.ListModules(ctx, recursive)
if err != nil {
return fmt.Errorf("failed to retieve available modules from gcs: %v", err)
}
fmt.Fprintf(os.Stdout, "Available modules in gcs path (%s):\n", gcsPath)
for _, mod := range availableModules {
fmt.Fprintln(os.Stdout, mod)
}
return nil
}