From 7946a8873d8119f42a935cc76cf627159030b6b9 Mon Sep 17 00:00:00 2001 From: Ziv Nevo <79099626+zivnevo@users.noreply.github.com> Date: Wed, 26 Jun 2024 17:24:25 +0300 Subject: [PATCH] Using Cobra for defining CLI commands and flags (#282) * Using Cobra for defining CLI commands and flags Also, making Provider a proper type Signed-off-by: Ziv Nevo --- Makefile | 2 +- README.md | 35 ++++---- cmd/collect/collect_resources.go | 40 +++++++++ cmd/collect/main.go | 35 +------- cmd/collect/parse_args.go | 69 ---------------- cmd/collect/subcmds.go | 82 +++++++++++++++++++ go.mod | 3 + go.sum | 3 + pkg/aws/resources_container.go | 2 +- pkg/common/provider.go | 39 +++++++++ pkg/common/resources_container_inf.go | 5 -- pkg/factory/factory.go | 2 +- .../datamodel/resources_container_model.go | 2 +- 13 files changed, 191 insertions(+), 128 deletions(-) create mode 100644 cmd/collect/collect_resources.go delete mode 100644 cmd/collect/parse_args.go create mode 100644 cmd/collect/subcmds.go create mode 100644 pkg/common/provider.go diff --git a/Makefile b/Makefile index c03e8f5..19b62b5 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ REPOSITORY := github.com/np-guard/cloud-resource-collector -EXE:=collect +EXE:=collector mod: go.mod @echo -- $@ -- diff --git a/README.md b/README.md index e73cece..c27e90c 100644 --- a/README.md +++ b/README.md @@ -46,31 +46,30 @@ export IBMCLOUD_API_KEY= ## Usage +### Collecting resources ``` -$ ./bin/collect -h -Usage of ./bin/collect: - -get-regions - just print the list of regions for the selected provider - -out string - file path to store results - -provider string - cloud provider from which to collect resources - -region value - cloud region from which to collect resources - -resource-group string - resource group id or name from which to collect resources +./bin/collector collect --provider [flags] +Flags: + -h, --help help for collect + --out string file path to store results + -r, --region stringArray cloud region from which to collect resources + --resource-group string resource group id or name from which to collect resources ``` -* Value of `-provider` must be either `ibm` or `aws` -* The `-region` argument can appear multiple times. If running with no `-region` arguments, resources from all regions are collected. -* If running with no `-resource-group` argument, resources from all resource groups are collected. -## Build the project +* Value of `--provider` must be either `ibm` or `aws` +* **IBM only**: The `--region` argument can appear multiple times. If running with no `--region` arguments, resources from all regions are collected. +* If running with no `--resource-group` argument, resources from all resource groups are collected. + +### Listing available regions +``` +./bin/collector get-regions --provider +``` +## Build the project +Requires Go version 1.22 or later. ```shell git clone git@github.com:np-guard/cloud-resource-collector.git cd cloud-resource-collector -make mod make build ``` - diff --git a/cmd/collect/collect_resources.go b/cmd/collect/collect_resources.go new file mode 100644 index 0000000..1610393 --- /dev/null +++ b/cmd/collect/collect_resources.go @@ -0,0 +1,40 @@ +/* +Copyright 2023- IBM Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package main + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/np-guard/cloud-resource-collector/pkg/common" + "github.com/np-guard/cloud-resource-collector/pkg/factory" +) + +func collectResources(cmd *cobra.Command, _ []string) error { + cmd.SilenceUsage = true // if we got this far, flags are syntactically correct, so no need to print usage + + if provider != common.IBM { + if len(regions) > 0 { + return fmt.Errorf("setting regions from the command-line for provider %s is not yet supported. "+ + "Use environment variables or config files instead", provider) + } + if resourceGroupID != "" { + return fmt.Errorf("setting resource-group from the command-line for provider %s is not yet supported. ", provider) + } + } + + resources := factory.GetResourceContainer(provider, regions, resourceGroupID) + // Collect resources from the provider API and generate output + err := resources.CollectResourcesFromAPI() + if err != nil { + return err + } + OutputResources(resources, outputFile) + resources.PrintStats() + return nil +} diff --git a/cmd/collect/main.go b/cmd/collect/main.go index a8d24bd..2ff4a28 100644 --- a/cmd/collect/main.go +++ b/cmd/collect/main.go @@ -7,41 +7,12 @@ SPDX-License-Identifier: Apache-2.0 package main import ( - "fmt" - "log" - "strings" - - "github.com/np-guard/cloud-resource-collector/pkg/factory" - "github.com/np-guard/cloud-resource-collector/pkg/version" + "os" ) func main() { - var inArgs InArgs - err := ParseInArgs(&inArgs) - if err != nil { - log.Fatalf("error parsing arguments: %v. exiting...\n", err) - } - - if *inArgs.version { - fmt.Printf("cloud-resource-collector v%s\n", version.VersionCore) - return - } - - // Initialize a collector for the requested provider - resources := factory.GetResourceContainer(*inArgs.CollectFromProvider, inArgs.regions, *inArgs.resourceGroupID) - - if *inArgs.getRegions { - providerRegions := strings.Join(resources.AllRegions(), ", ") - fmt.Printf("Available regions for provider %s: %s\n", *inArgs.CollectFromProvider, providerRegions) - return - } - - // Collect resources from the provider API and generate output - err = resources.CollectResourcesFromAPI() + err := newRootCommand().Execute() if err != nil { - log.Fatal(err) + os.Exit(1) // error was already printed by Cobra } - OutputResources(resources, *inArgs.OutputFile) - - resources.PrintStats() } diff --git a/cmd/collect/parse_args.go b/cmd/collect/parse_args.go deleted file mode 100644 index 1d7ef33..0000000 --- a/cmd/collect/parse_args.go +++ /dev/null @@ -1,69 +0,0 @@ -/* -Copyright 2023- IBM Inc. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package main - -import ( - "flag" - "fmt" - - "github.com/np-guard/cloud-resource-collector/pkg/common" -) - -type regionList []string - -func (dp *regionList) String() string { - return fmt.Sprintln(*dp) -} - -func (dp *regionList) Set(region string) error { - *dp = append(*dp, region) - return nil -} - -type InArgs struct { - CollectFromProvider *string - regions regionList - getRegions *bool - resourceGroupID *string - OutputFile *string - version *bool -} - -func ParseInArgs(args *InArgs) error { - SupportedProviders := map[string]bool{ - common.AWS: true, - common.IBM: true, - } - - args.CollectFromProvider = flag.String("provider", "", "cloud provider from which to collect resources") - flag.Var(&args.regions, "region", "cloud region from which to collect resources") - args.getRegions = flag.Bool("get-regions", false, "just print the list of regions for the selected provider") - args.resourceGroupID = flag.String("resource-group", "", "resource group id or name from which to collect resources") - args.OutputFile = flag.String("out", "", "file path to store results") - args.version = flag.Bool("version", false, "prints the release version number") - flag.Parse() - - if !SupportedProviders[*args.CollectFromProvider] && !*args.version { - flag.PrintDefaults() - return fmt.Errorf("unsupported provider: %s", *args.CollectFromProvider) - } - - if *args.CollectFromProvider != common.IBM { - if len(args.regions) > 0 { - return fmt.Errorf("setting regions from the command-line for provider %s is not yet supported. "+ - "Use environment variables or config files instead", *args.CollectFromProvider) - } - if *args.getRegions { - return fmt.Errorf("getting the list of regions for provider %s is not yet supported", *args.CollectFromProvider) - } - if *args.resourceGroupID != "" { - return fmt.Errorf("setting resourceGroup from the command-line for provider %s is not yet supported. ", *args.CollectFromProvider) - } - } - - return nil -} diff --git a/cmd/collect/subcmds.go b/cmd/collect/subcmds.go new file mode 100644 index 0000000..925ecf4 --- /dev/null +++ b/cmd/collect/subcmds.go @@ -0,0 +1,82 @@ +/* +Copyright 2023- IBM Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package main + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + "github.com/np-guard/cloud-resource-collector/pkg/common" + "github.com/np-guard/cloud-resource-collector/pkg/factory" + "github.com/np-guard/cloud-resource-collector/pkg/version" +) + +const ( + providerFlag = "provider" +) + +var ( + provider common.Provider + regions []string + resourceGroupID string + outputFile string +) + +func newRootCommand() *cobra.Command { + rootCmd := &cobra.Command{ + Use: "collector", + Short: "cloud-resource-collector is a CLI for collecting VPC-related cloud resources", + Long: `cloud-resource-collector uses cloud-provider SDK to gather VPC-related resources defining network connectivity`, + Version: version.VersionCore, + } + + rootCmd.PersistentFlags().VarP(&provider, providerFlag, "p", "collect resources from an account in this cloud provider") + _ = rootCmd.MarkPersistentFlagRequired(providerFlag) + + rootCmd.AddCommand(newCollectCommand()) + rootCmd.AddCommand(newGetRegionsCommand()) + + rootCmd.SetHelpCommand(&cobra.Command{Hidden: true}) // disable help command. should use --help flag instead + + return rootCmd +} + +func newCollectCommand() *cobra.Command { + collectCmd := &cobra.Command{ + Use: "collect", + Short: "Collect VPC-related cloud resources", + Long: `Use cloud-provider SDK to gather VPC-related resources defining network connectivity`, + Args: cobra.NoArgs, + RunE: collectResources, + } + + collectCmd.Flags().StringArrayVarP(®ions, "region", "r", nil, "cloud region from which to collect resources") + collectCmd.Flags().StringVar(&resourceGroupID, "resource-group", "", "resource group id or name from which to collect resources") + collectCmd.Flags().StringVar(&outputFile, "out", "", "file path to store results") + + return collectCmd +} + +func newGetRegionsCommand() *cobra.Command { + return &cobra.Command{ + Use: "get-regions", + Short: "List available regions for a given provider", + Long: `List all regions that can be used with the --region flag`, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + if provider != common.IBM { + return fmt.Errorf("command not supported for provider %s", provider) + } + resources := factory.GetResourceContainer(provider, nil, "") + providerRegions := strings.Join(resources.AllRegions(), ", ") + fmt.Printf("Available regions for provider %s: %s\n", provider, providerRegions) + return nil + }, + } +} diff --git a/go.mod b/go.mod index 0255a38..18aa8b5 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/IBM/vpc-go-sdk v0.52.0 github.com/aws/aws-sdk-go-v2/config v1.27.21 github.com/aws/aws-sdk-go-v2/service/ec2 v1.163.0 + github.com/spf13/cobra v0.0.3 ) require ( @@ -35,10 +36,12 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/oklog/ulid v1.3.1 // indirect + github.com/spf13/pflag v1.0.3 // indirect go.mongodb.org/mongo-driver v1.15.0 // indirect golang.org/x/crypto v0.24.0 // indirect golang.org/x/net v0.26.0 // indirect diff --git a/go.sum b/go.sum index 5890381..7f2f7a0 100644 --- a/go.sum +++ b/go.sum @@ -128,6 +128,7 @@ github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -190,7 +191,9 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/pkg/aws/resources_container.go b/pkg/aws/resources_container.go index cbdaf55..1ae4d65 100644 --- a/pkg/aws/resources_container.go +++ b/pkg/aws/resources_container.go @@ -40,7 +40,7 @@ func NewResourcesContainer() *ResourcesContainer { SecurityGroupsList: []*aws2.SecurityGroup{}, SubnetsList: []*aws2.Subnet{}, VpcsList: []*aws2.Vpc{}, - ResourceModelMetadata: common.ResourceModelMetadata{Version: version.VersionCore, Provider: common.AWS}, + ResourceModelMetadata: common.ResourceModelMetadata{Version: version.VersionCore, Provider: string(common.AWS)}, } } diff --git a/pkg/common/provider.go b/pkg/common/provider.go new file mode 100644 index 0000000..61b35fa --- /dev/null +++ b/pkg/common/provider.go @@ -0,0 +1,39 @@ +/* +Copyright 2023- IBM Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package common + +import ( + "fmt" + "slices" + "strings" +) + +type Provider string + +const ( + AWS Provider = "aws" + IBM Provider = "ibm" +) + +var AllProviders = []string{string(IBM), string(AWS)} + +func (p *Provider) String() string { + return string(*p) +} + +func (p *Provider) Set(v string) error { + v = strings.ToLower(v) + if slices.Contains(AllProviders, v) { + *p = Provider(v) + return nil + } + return fmt.Errorf("must be one of [%s]", strings.Join(AllProviders, ", ")) +} + +func (p *Provider) Type() string { + return "string" +} diff --git a/pkg/common/resources_container_inf.go b/pkg/common/resources_container_inf.go index a32d610..b2dbf42 100644 --- a/pkg/common/resources_container_inf.go +++ b/pkg/common/resources_container_inf.go @@ -6,11 +6,6 @@ SPDX-License-Identifier: Apache-2.0 package common -const ( - AWS string = "aws" - IBM string = "ibm" -) - // ResourcesContainerInf is the interface common to all resources containers type ResourcesContainerInf interface { CollectResourcesFromAPI() error diff --git a/pkg/factory/factory.go b/pkg/factory/factory.go index 7b6e52c..2aa75fb 100644 --- a/pkg/factory/factory.go +++ b/pkg/factory/factory.go @@ -12,7 +12,7 @@ import ( "github.com/np-guard/cloud-resource-collector/pkg/ibm" ) -func GetResourceContainer(provider string, regions []string, resourceGroup string) common.ResourcesContainerInf { +func GetResourceContainer(provider common.Provider, regions []string, resourceGroup string) common.ResourcesContainerInf { var resources common.ResourcesContainerInf switch provider { case common.AWS: diff --git a/pkg/ibm/datamodel/resources_container_model.go b/pkg/ibm/datamodel/resources_container_model.go index fb9e15e..ecc8bac 100644 --- a/pkg/ibm/datamodel/resources_container_model.go +++ b/pkg/ibm/datamodel/resources_container_model.go @@ -48,7 +48,7 @@ func NewResourcesContainerModel() *ResourcesContainerModel { TransitConnectionList: []*TransitConnection{}, TransitGatewayList: []*TransitGateway{}, IKSClusters: []*IKSCluster{}, - ResourceModelMetadata: common.ResourceModelMetadata{Version: version.VersionCore, Provider: common.IBM}, + ResourceModelMetadata: common.ResourceModelMetadata{Version: version.VersionCore, Provider: string(common.IBM)}, } }