go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/apps/cnquery/cmd/providers.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package cmd
     5  
     6  import (
     7  	"fmt"
     8  	"net/http"
     9  	"net/url"
    10  	"path/filepath"
    11  	"sort"
    12  	"strconv"
    13  	"strings"
    14  
    15  	"github.com/rs/zerolog/log"
    16  	"github.com/spf13/cobra"
    17  	"go.mondoo.com/cnquery/cli/theme"
    18  	"go.mondoo.com/cnquery/providers"
    19  	"go.mondoo.com/cnquery/utils/sortx"
    20  )
    21  
    22  func init() {
    23  	rootCmd.AddCommand(ProvidersCmd)
    24  	ProvidersCmd.AddCommand(listProvidersCmd)
    25  	ProvidersCmd.AddCommand(installProviderCmd)
    26  
    27  	installProviderCmd.Flags().StringP("file", "f", "", "install a provider via a file")
    28  	installProviderCmd.Flags().String("url", "", "install a provider via URL")
    29  }
    30  
    31  var ProvidersCmd = &cobra.Command{
    32  	Use:    "providers",
    33  	Short:  "Providers add connectivity to all assets.",
    34  	Long:   `Manage your providers. List and install new ones or update existing ones.`,
    35  	PreRun: func(cmd *cobra.Command, args []string) {},
    36  	Run: func(cmd *cobra.Command, args []string) {
    37  		list()
    38  	},
    39  }
    40  
    41  var listProvidersCmd = &cobra.Command{
    42  	Use:    "list",
    43  	Short:  "List all providers on the system.",
    44  	Long:   "",
    45  	PreRun: func(cmd *cobra.Command, args []string) {},
    46  	Run: func(cmd *cobra.Command, args []string) {
    47  		list()
    48  	},
    49  }
    50  
    51  var installProviderCmd = &cobra.Command{
    52  	Use:    "install <NAME[@VERSION]>",
    53  	Short:  "Install or update a provider.",
    54  	Long:   "",
    55  	PreRun: func(cmd *cobra.Command, args []string) {},
    56  	Run: func(cmd *cobra.Command, args []string) {
    57  		// Explicit installs of files will ignore version recommendations.
    58  		// So we just take them and roll with it.
    59  		path, _ := cmd.Flags().GetString("file")
    60  		if path != "" {
    61  			installProviderFile(path)
    62  			return
    63  		}
    64  
    65  		url, _ := cmd.Flags().GetString("url")
    66  		if url != "" {
    67  			installProviderUrl(url)
    68  			return
    69  		}
    70  
    71  		if len(args) == 0 {
    72  			log.Fatal().Msg("no provider specified, use the NAME[@VERSION] format to pass in a provider name")
    73  		}
    74  
    75  		// if no url or file is specified, we default to installing by name from the default upstream
    76  		installProviderByName(args[0])
    77  	},
    78  }
    79  
    80  func installProviderByName(name string) {
    81  	parts := strings.Split(name, "@")
    82  	if len(parts) > 2 {
    83  		log.Fatal().Msg("invalid provider name")
    84  	}
    85  	name = parts[0]
    86  	version := ""
    87  	if len(parts) == 2 {
    88  		// trim the v prefix, allowing users to specify both 9.0.0 and v9.0.0
    89  		version = strings.TrimPrefix(parts[1], "v")
    90  	}
    91  	installed, err := providers.Install(name, version)
    92  	if err != nil {
    93  		log.Fatal().Err(err).Msg("failed to install")
    94  	}
    95  	providers.PrintInstallResults([]*providers.Provider{installed})
    96  }
    97  
    98  func installProviderUrl(u string) {
    99  	if i := strings.Index(u, "://"); i == -1 {
   100  		u = "http://" + u
   101  	}
   102  	uUrl, err := url.Parse(u)
   103  	if err != nil {
   104  		log.Fatal().Err(err).Msg("invalid url")
   105  	}
   106  
   107  	res, err := http.Get(uUrl.String())
   108  	if err != nil {
   109  		log.Fatal().Err(err).Msg("failed to install")
   110  	}
   111  
   112  	installed, err := providers.InstallIO(res.Body, providers.InstallConf{
   113  		Dst: providers.HomePath,
   114  	})
   115  	if err != nil {
   116  		log.Fatal().Err(err).Msg("failed to install")
   117  	}
   118  	providers.PrintInstallResults(installed)
   119  }
   120  
   121  func installProviderFile(path string) {
   122  	installed, err := providers.InstallFile(path, providers.InstallConf{
   123  		Dst: providers.HomePath,
   124  	})
   125  	if err != nil {
   126  		log.Fatal().Err(err).Msg("failed to install")
   127  	}
   128  	providers.PrintInstallResults(installed)
   129  }
   130  
   131  func list() {
   132  	list, err := providers.ListAll()
   133  	if err != nil {
   134  		log.Error().Err(err).Msg("failed to list providers")
   135  	}
   136  
   137  	printProviders(list)
   138  }
   139  
   140  func printProviders(p []*providers.Provider) {
   141  	if len(p) == 0 {
   142  		log.Info().Msg("No providers found.")
   143  		fmt.Println("No providers found.")
   144  		if providers.SystemPath == "" && providers.HomePath == "" {
   145  			fmt.Println("No paths for providers detected.")
   146  		} else {
   147  			fmt.Println("Was checking: " + providers.SystemPath)
   148  		}
   149  	}
   150  
   151  	paths := map[string][]*providers.Provider{}
   152  	for i := range p {
   153  		provider := p[i]
   154  		if provider.Path == "" {
   155  			paths["builtin"] = append(paths["builtin"], provider)
   156  			continue
   157  		}
   158  		dir := filepath.Dir(provider.Path)
   159  		paths[dir] = append(paths[dir], provider)
   160  	}
   161  
   162  	printProviderPath("builtin", paths["builtin"], false)
   163  	printProviderPath(providers.HomePath, paths[providers.HomePath], true)
   164  	printProviderPath(providers.SystemPath, paths[providers.SystemPath], true)
   165  	delete(paths, "builtin")
   166  	delete(paths, providers.HomePath)
   167  	delete(paths, providers.SystemPath)
   168  
   169  	keys := sortx.Keys(paths)
   170  	for _, path := range keys {
   171  		printProviderPath(path, paths[path], true)
   172  	}
   173  
   174  	fmt.Println()
   175  }
   176  
   177  func printProviderPath(path string, list []*providers.Provider, printEmpty bool) {
   178  	if list == nil {
   179  		if printEmpty && path != "" {
   180  			fmt.Println("")
   181  			log.Info().Msg(path + " has no providers")
   182  		}
   183  		return
   184  	}
   185  
   186  	fmt.Println()
   187  	log.Info().Msg(path + " (found " + strconv.Itoa(len(list)) + " providers)")
   188  	fmt.Println()
   189  
   190  	sort.Slice(list, func(i, j int) bool {
   191  		return list[i].Name < list[j].Name
   192  	})
   193  
   194  	for i := range list {
   195  		printProvider(list[i])
   196  	}
   197  }
   198  
   199  func printProvider(p *providers.Provider) {
   200  	conns := make([]string, len(p.Connectors))
   201  	for i := range p.Connectors {
   202  		conns[i] = theme.DefaultTheme.Secondary(p.Connectors[i].Name)
   203  	}
   204  
   205  	name := theme.DefaultTheme.Primary(p.Name)
   206  	supports := ""
   207  	if len(conns) != 0 {
   208  		supports = " with connectors: " + strings.Join(conns, ", ")
   209  	}
   210  
   211  	fmt.Println("  " + name + " " + p.Version + supports)
   212  }