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 }