github.com/turbot/steampipe@v1.7.0-rc.0.0.20240517123944-7cef272d4458/cmd/mod.go (about) 1 package cmd 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 8 "github.com/spf13/cobra" 9 "github.com/spf13/viper" 10 "github.com/turbot/go-kit/helpers" 11 "github.com/turbot/steampipe/pkg/cmdconfig" 12 "github.com/turbot/steampipe/pkg/constants" 13 "github.com/turbot/steampipe/pkg/error_helpers" 14 "github.com/turbot/steampipe/pkg/filepaths" 15 "github.com/turbot/steampipe/pkg/modinstaller" 16 "github.com/turbot/steampipe/pkg/steampipeconfig/modconfig" 17 "github.com/turbot/steampipe/pkg/steampipeconfig/parse" 18 "github.com/turbot/steampipe/pkg/utils" 19 ) 20 21 // mod management commands 22 func modCmd() *cobra.Command { 23 var cmd = &cobra.Command{ 24 Use: "mod [command]", 25 Args: cobra.NoArgs, 26 Short: "Steampipe mod management", 27 Long: `Steampipe mod management. 28 29 Mods enable you to run, build, and share dashboards, benchmarks and other resources. 30 31 Find pre-built mods in the public registry at https://hub.steampipe.io. 32 33 Examples: 34 35 # Create a new mod in the current directory 36 steampipe mod init 37 38 # Install a mod 39 steampipe mod install github.com/turbot/steampipe-mod-aws-compliance 40 41 # Update a mod 42 steampipe mod update github.com/turbot/steampipe-mod-aws-compliance 43 44 # List installed mods 45 steampipe mod list 46 47 # Uninstall a mod 48 steampipe mod uninstall github.com/turbot/steampipe-mod-aws-compliance 49 `, 50 } 51 52 cmd.AddCommand(modInstallCmd()) 53 cmd.AddCommand(modUninstallCmd()) 54 cmd.AddCommand(modUpdateCmd()) 55 cmd.AddCommand(modListCmd()) 56 cmd.AddCommand(modInitCmd()) 57 cmd.Flags().BoolP(constants.ArgHelp, "h", false, "Help for mod") 58 59 cmdconfig.OnCmd(cmd). 60 AddModLocationFlag() 61 62 return cmd 63 } 64 65 // install 66 func modInstallCmd() *cobra.Command { 67 var cmd = &cobra.Command{ 68 Use: "install", 69 Run: runModInstallCmd, 70 Short: "Install one or more mods and their dependencies", 71 Long: `Install one or more mods and their dependencies. 72 73 Mods provide an easy way to share Steampipe queries, controls, and benchmarks. 74 Find mods using the public registry at hub.steampipe.io. 75 76 Examples: 77 78 # Install a mod(steampipe-mod-aws-compliance) 79 steampipe mod install github.com/turbot/steampipe-mod-aws-compliance 80 81 # Install a specific version of a mod 82 steampipe mod install github.com/turbot/steampipe-mod-aws-compliance@0.1 83 84 # Install a version of a mod using a semver constraint 85 steampipe mod install github.com/turbot/steampipe-mod-aws-compliance@'^1' 86 87 # Install all mods specified in the mod.sp and their dependencies 88 steampipe mod install 89 90 # Preview what steampipe mod install will do, without actually installing anything 91 steampipe mod install --dry-run`, 92 } 93 94 cmdconfig.OnCmd(cmd). 95 AddBoolFlag(constants.ArgPrune, true, "Remove unused dependencies after installation is complete"). 96 AddBoolFlag(constants.ArgDryRun, false, "Show which mods would be installed/updated/uninstalled without modifying them"). 97 AddBoolFlag(constants.ArgForce, false, "Install mods even if plugin/cli version requirements are not met (cannot be used with --dry-run)"). 98 AddBoolFlag(constants.ArgHelp, false, "Help for install", cmdconfig.FlagOptions.WithShortHand("h")). 99 AddModLocationFlag() 100 101 return cmd 102 } 103 104 func runModInstallCmd(cmd *cobra.Command, args []string) { 105 ctx := cmd.Context() 106 utils.LogTime("cmd.runModInstallCmd") 107 defer func() { 108 utils.LogTime("cmd.runModInstallCmd end") 109 if r := recover(); r != nil { 110 error_helpers.ShowError(ctx, helpers.ToError(r)) 111 exitCode = constants.ExitCodeUnknownErrorPanic 112 } 113 }() 114 115 // try to load the workspace mod definition 116 // - if it does not exist, this will return a nil mod and a nil error 117 workspacePath := viper.GetString(constants.ArgModLocation) 118 workspaceMod, err := parse.LoadModfile(workspacePath) 119 error_helpers.FailOnErrorWithMessage(err, "failed to load mod definition") 120 121 // if no mod was loaded, create a default 122 if workspaceMod == nil { 123 workspaceMod, err = createWorkspaceMod(ctx, cmd, workspacePath) 124 if err != nil { 125 exitCode = constants.ExitCodeModInstallFailed 126 error_helpers.FailOnError(err) 127 } 128 } 129 130 // if any mod names were passed as args, convert into formed mod names 131 opts := modinstaller.NewInstallOpts(workspaceMod, args...) 132 trimGitUrls(opts) 133 installData, err := modinstaller.InstallWorkspaceDependencies(ctx, opts) 134 if err != nil { 135 exitCode = constants.ExitCodeModInstallFailed 136 error_helpers.FailOnError(err) 137 } 138 139 fmt.Println(modinstaller.BuildInstallSummary(installData)) 140 } 141 142 // uninstall 143 func modUninstallCmd() *cobra.Command { 144 var cmd = &cobra.Command{ 145 Use: "uninstall", 146 Run: runModUninstallCmd, 147 Short: "Uninstall a mod and its dependencies", 148 Long: `Uninstall a mod and its dependencies. 149 150 Example: 151 152 # Uninstall a mod 153 steampipe mod uninstall github.com/turbot/steampipe-mod-azure-compliance`, 154 } 155 156 cmdconfig.OnCmd(cmd). 157 AddBoolFlag(constants.ArgPrune, true, "Remove unused dependencies after uninstallation is complete"). 158 AddBoolFlag(constants.ArgDryRun, false, "Show which mods would be uninstalled without modifying them"). 159 AddBoolFlag(constants.ArgHelp, false, "Help for uninstall", cmdconfig.FlagOptions.WithShortHand("h")). 160 AddModLocationFlag() 161 162 return cmd 163 } 164 165 func runModUninstallCmd(cmd *cobra.Command, args []string) { 166 ctx := cmd.Context() 167 utils.LogTime("cmd.runModInstallCmd") 168 defer func() { 169 utils.LogTime("cmd.runModInstallCmd end") 170 if r := recover(); r != nil { 171 error_helpers.ShowError(ctx, helpers.ToError(r)) 172 exitCode = constants.ExitCodeUnknownErrorPanic 173 } 174 }() 175 176 // try to load the workspace mod definition 177 // - if it does not exist, this will return a nil mod and a nil error 178 workspaceMod, err := parse.LoadModfile(viper.GetString(constants.ArgModLocation)) 179 error_helpers.FailOnErrorWithMessage(err, "failed to load mod definition") 180 if workspaceMod == nil { 181 fmt.Println("No mods installed.") 182 return 183 } 184 opts := modinstaller.NewInstallOpts(workspaceMod, args...) 185 trimGitUrls(opts) 186 installData, err := modinstaller.UninstallWorkspaceDependencies(ctx, opts) 187 error_helpers.FailOnError(err) 188 189 fmt.Println(modinstaller.BuildUninstallSummary(installData)) 190 } 191 192 // update 193 func modUpdateCmd() *cobra.Command { 194 var cmd = &cobra.Command{ 195 Use: "update", 196 Run: runModUpdateCmd, 197 Short: "Update one or more mods and their dependencies", 198 Long: `Update one or more mods and their dependencies. 199 200 Example: 201 202 # Update a mod to the latest version allowed by its current constraint 203 steampipe mod update github.com/turbot/steampipe-mod-aws-compliance 204 205 # Update all mods specified in the mod.sp and their dependencies to the latest versions that meet their constraints, and install any that are missing 206 steampipe mod update`, 207 } 208 209 cmdconfig.OnCmd(cmd). 210 AddBoolFlag(constants.ArgPrune, true, "Remove unused dependencies after update is complete"). 211 AddBoolFlag(constants.ArgForce, false, "Update mods even if plugin/cli version requirements are not met (cannot be used with --dry-run)"). 212 AddBoolFlag(constants.ArgDryRun, false, "Show which mods would be updated without modifying them"). 213 AddBoolFlag(constants.ArgHelp, false, "Help for update", cmdconfig.FlagOptions.WithShortHand("h")). 214 AddModLocationFlag() 215 216 return cmd 217 } 218 219 func runModUpdateCmd(cmd *cobra.Command, args []string) { 220 ctx := cmd.Context() 221 utils.LogTime("cmd.runModUpdateCmd") 222 defer func() { 223 utils.LogTime("cmd.runModUpdateCmd end") 224 if r := recover(); r != nil { 225 error_helpers.ShowError(ctx, helpers.ToError(r)) 226 exitCode = constants.ExitCodeUnknownErrorPanic 227 } 228 }() 229 230 // try to load the workspace mod definition 231 // - if it does not exist, this will return a nil mod and a nil error 232 workspaceMod, err := parse.LoadModfile(viper.GetString(constants.ArgModLocation)) 233 error_helpers.FailOnErrorWithMessage(err, "failed to load mod definition") 234 if workspaceMod == nil { 235 fmt.Println("No mods installed.") 236 return 237 } 238 239 opts := modinstaller.NewInstallOpts(workspaceMod, args...) 240 trimGitUrls(opts) 241 installData, err := modinstaller.InstallWorkspaceDependencies(ctx, opts) 242 error_helpers.FailOnError(err) 243 244 fmt.Println(modinstaller.BuildInstallSummary(installData)) 245 } 246 247 // list 248 func modListCmd() *cobra.Command { 249 var cmd = &cobra.Command{ 250 Use: "list", 251 Run: runModListCmd, 252 Short: "List currently installed mods", 253 Long: `List currently installed mods. 254 255 Example: 256 257 # List installed mods 258 steampipe mod list`, 259 } 260 261 cmdconfig.OnCmd(cmd). 262 AddBoolFlag(constants.ArgHelp, false, "Help for list", cmdconfig.FlagOptions.WithShortHand("h")). 263 AddModLocationFlag() 264 return cmd 265 } 266 267 func runModListCmd(cmd *cobra.Command, _ []string) { 268 ctx := cmd.Context() 269 utils.LogTime("cmd.runModListCmd") 270 defer func() { 271 utils.LogTime("cmd.runModListCmd end") 272 if r := recover(); r != nil { 273 error_helpers.ShowError(ctx, helpers.ToError(r)) 274 exitCode = constants.ExitCodeUnknownErrorPanic 275 } 276 }() 277 278 // try to load the workspace mod definition 279 // - if it does not exist, this will return a nil mod and a nil error 280 workspaceMod, err := parse.LoadModfile(viper.GetString(constants.ArgModLocation)) 281 error_helpers.FailOnErrorWithMessage(err, "failed to load mod definition") 282 if workspaceMod == nil { 283 fmt.Println("No mods installed.") 284 return 285 } 286 287 opts := modinstaller.NewInstallOpts(workspaceMod) 288 installer, err := modinstaller.NewModInstaller(ctx, opts) 289 error_helpers.FailOnError(err) 290 291 treeString := installer.GetModList() 292 if len(strings.Split(treeString, "\n")) > 1 { 293 fmt.Println() 294 } 295 fmt.Println(treeString) 296 } 297 298 // init 299 func modInitCmd() *cobra.Command { 300 var cmd = &cobra.Command{ 301 Use: "init", 302 Run: runModInitCmd, 303 Short: "Initialize the current directory with a mod.sp file", 304 Long: `Initialize the current directory with a mod.sp file. 305 306 Example: 307 308 # Initialize the current directory with a mod.sp file 309 steampipe mod init`, 310 } 311 312 cmdconfig.OnCmd(cmd). 313 AddBoolFlag(constants.ArgHelp, false, "Help for init", cmdconfig.FlagOptions.WithShortHand("h")). 314 AddModLocationFlag() 315 return cmd 316 } 317 318 func runModInitCmd(cmd *cobra.Command, args []string) { 319 utils.LogTime("cmd.runModInitCmd") 320 ctx := cmd.Context() 321 322 defer func() { 323 utils.LogTime("cmd.runModInitCmd end") 324 if r := recover(); r != nil { 325 error_helpers.ShowError(ctx, helpers.ToError(r)) 326 exitCode = constants.ExitCodeUnknownErrorPanic 327 } 328 }() 329 workspacePath := viper.GetString(constants.ArgModLocation) 330 if _, err := createWorkspaceMod(ctx, cmd, workspacePath); err != nil { 331 exitCode = constants.ExitCodeModInitFailed 332 error_helpers.FailOnError(err) 333 } 334 } 335 336 // helpers 337 func createWorkspaceMod(ctx context.Context, cmd *cobra.Command, workspacePath string) (*modconfig.Mod, error) { 338 cancel, err := modinstaller.ValidateModLocation(ctx, workspacePath) 339 if err != nil { 340 return nil, err 341 } 342 if !cancel { 343 return nil, fmt.Errorf("mod %s cancelled", cmd.Name()) 344 } 345 346 if _, exists := parse.ModfileExists(workspacePath); exists { 347 fmt.Println("Working folder already contains a mod definition file") 348 return nil, nil 349 } 350 // write mod definition file 351 mod := modconfig.CreateDefaultMod(workspacePath) 352 if err := mod.Save(); err != nil { 353 return nil, err 354 } 355 // only print message for mod init (not for mod install) 356 if cmd.Name() == "init" { 357 fmt.Printf("Created mod definition file '%s'\n", filepaths.DefaultModFilePath(workspacePath)) 358 } 359 360 // load up the written mod file so that we get the updated 361 // block ranges 362 mod, err = parse.LoadModfile(workspacePath) 363 if err != nil { 364 return nil, err 365 } 366 367 return mod, nil 368 } 369 370 // Modifies(trims) the URL if contains http ot https in arguments 371 func trimGitUrls(opts *modinstaller.InstallOpts) { 372 for i, url := range opts.ModArgs { 373 opts.ModArgs[i] = strings.TrimPrefix(url, "http://") 374 opts.ModArgs[i] = strings.TrimPrefix(url, "https://") 375 } 376 }