| 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 | |
| } |