diff --git a/azlist/azlist.go b/azlist/azlist.go index 91d8846..e2f8c47 100644 --- a/azlist/azlist.go +++ b/azlist/azlist.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph" "github.com/magodo/armid" "github.com/magodo/workerpool" @@ -36,6 +37,12 @@ type ARMSchemaEntry struct { } type Option struct { + // Required + SubscriptionId string + Cred azcore.TokenCredential + ClientOpt arm.ClientOptions + + // Optional Parallelism int Recursive bool IncludeManaged bool @@ -62,20 +69,27 @@ type ListResult struct { Errors []ListError } -func List(ctx context.Context, subscriptionId, predicate string, opt *Option) (*ListResult, error) { - if opt == nil { - opt = defaultOption() +func List(ctx context.Context, predicate string, opt Option) (*ListResult, error) { + if opt.Cred == nil { + return nil, fmt.Errorf("token credential is empty") + } + if opt.SubscriptionId == "" { + return nil, fmt.Errorf("subscription id is empty") + } + if opt.Parallelism == 0 { + opt.Parallelism = runtime.NumCPU() } - log.Printf("[INFO] List for subscription %s via predicate %s, with option %#v", subscriptionId, predicate, opt) + log.Printf("[INFO] List for subscription %s via predicate %s (parallelism: %d | recursive %t | include managed %t)", opt.SubscriptionId, predicate, opt.Parallelism, opt.Recursive, opt.IncludeManaged) - client, err := NewClient(subscriptionId) + log.Printf("[INFO] New Client") + client, err := NewClient(opt.SubscriptionId, opt.Cred, opt.ClientOpt) if err != nil { return nil, fmt.Errorf("new client: %v", err) } log.Printf("[INFO] Listing tracked resources") - rl, err := ListTrackedResources(ctx, client, subscriptionId, predicate) + rl, err := ListTrackedResources(ctx, client, opt.SubscriptionId, predicate) if err != nil { return nil, err } diff --git a/azlist/client.go b/azlist/client.go index 1438433..f2f54c0 100644 --- a/azlist/client.go +++ b/azlist/client.go @@ -1,14 +1,8 @@ package azlist import ( - "fmt" - "os" - "strings" - + "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" - "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" - "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" - "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph" "github.com/magodo/azlist/armresources" ) @@ -18,67 +12,13 @@ type Client struct { resourceGraph *armresourcegraph.Client } -func NewClient(subscriptionId string) (*Client, error) { - env := "public" - if v := os.Getenv("ARM_ENVIRONMENT"); v != "" { - env = v - } - - var cloudCfg cloud.Configuration - switch strings.ToLower(env) { - case "public": - cloudCfg = cloud.AzurePublic - case "usgovernment": - cloudCfg = cloud.AzureGovernment - case "china": - cloudCfg = cloud.AzureChina - default: - return nil, fmt.Errorf("unknown environment specified: %q", env) - } - - // Maps the auth related environment variables used in the provider to what azidentity honors. - if v, ok := os.LookupEnv("ARM_TENANT_ID"); ok { - os.Setenv("AZURE_TENANT_ID", v) - } - if v, ok := os.LookupEnv("ARM_CLIENT_ID"); ok { - os.Setenv("AZURE_CLIENT_ID", v) - } - if v, ok := os.LookupEnv("ARM_CLIENT_SECRET"); ok { - os.Setenv("AZURE_CLIENT_SECRET", v) - } - if v, ok := os.LookupEnv("ARM_CLIENT_CERTIFICATE_PATH"); ok { - os.Setenv("AZURE_CLIENT_CERTIFICATE_PATH", v) - } - - cred, err := azidentity.NewDefaultAzureCredential(&azidentity.DefaultAzureCredentialOptions{ - ClientOptions: policy.ClientOptions{ - Cloud: cloudCfg, - }, - TenantID: os.Getenv("ARM_TENANT_ID"), - }) - if err != nil { - return nil, fmt.Errorf("failed to obtain a credential: %v", err) - } - - opt := &arm.ClientOptions{ - ClientOptions: policy.ClientOptions{ - Cloud: cloudCfg, - Telemetry: policy.TelemetryOptions{ - ApplicationID: "azlist", - Disabled: false, - }, - Logging: policy.LogOptions{ - IncludeBody: true, - }, - }, - } - - resClient, err := armresources.NewClient(subscriptionId, cred, opt) +func NewClient(subscriptionId string, cred azcore.TokenCredential, clientOpt arm.ClientOptions) (*Client, error) { + resClient, err := armresources.NewClient(subscriptionId, cred, &clientOpt) if err != nil { return nil, err } - argClient, err := armresourcegraph.NewClient(cred, opt) + argClient, err := armresourcegraph.NewClient(cred, &clientOpt) if err != nil { return nil, err } diff --git a/go.mod b/go.mod index 8504a1b..e5c237a 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module github.com/magodo/azlist go 1.19 require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.3 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.1 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.6.0 github.com/hashicorp/go-hclog v1.3.1 github.com/magodo/armid v0.0.0-20220915030809-9ed860f93894 @@ -14,12 +14,12 @@ require ( ) require ( - github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v0.8.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.13.0 // indirect - github.com/golang-jwt/jwt v3.2.1+incompatible // indirect + github.com/golang-jwt/jwt/v4 v4.4.2 // indirect github.com/google/uuid v1.1.1 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect diff --git a/go.sum b/go.sum index cf2e79f..6e487a3 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,13 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.3 h1:8LoU8N2lIUzkmstvwXvVfniMZlFbesfT2AmA1aqvRr8= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.3/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 h1:QkAcEIAKbNL4KoFr4SathZPhDhF4mVwpBMFlYjyAqy8= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0/go.mod h1:bhXu1AjYL+wutSL/kpSq6s7733q2Rb0yuot9Zgfqa/0= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 h1:jp0dGvZ7ZK0mgqnTSClMxa5xuRL7NZgHameVYF6BurY= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.0 h1:VuHAcMq8pU1IWNT/m5yRaGqbK0BiQKHT8X4DTp9CHdI= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.3.0/go.mod h1:tZoQYdDZNOiIjdSn0dVWVfl0NEPGOJqVLzSrcFk4Is0= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.1 h1:T8quHYlUGyb/oqtSTwqlCr1ilJHrDv+ZtpSfo+hm1BU= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.1/go.mod h1:gLa1CL2RNE4s7M3yopJ/p0iq5DdY6Yv5ZUt9MTRZOQM= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2 h1:+5VZ72z0Qan5Bog5C+ZkgSqUbeVUd9wgtHOrIKuc5b8= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.6.0 h1:ofIfA+/dTgrqhykfrz+GbFtPAtE697LAOCSw/8AQbwI= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.6.0/go.mod h1:KKrvyReEXgIA2D4ez2Jq5dRynJW4bOjRDkONdze2qjs= -github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1 h1:BWe8a+f/t+7KY7zH2mqygeUD0t8hNFXe08p1Pb3/jKE= -github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4= +github.com/AzureAD/microsoft-authentication-library-for-go v0.8.1 h1:oPdPEZFSbl7oSPEAIPMPBMUmiL+mqgzBJwM/9qYcwNg= +github.com/AzureAD/microsoft-authentication-library-for-go v0.8.1/go.mod h1:4qFor3D/HDsvBME35Xy9rwW9DecL+M2sNw1ybjPtwA0= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -16,9 +16,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= -github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= +github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= @@ -39,7 +38,6 @@ github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI= github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -62,7 +60,6 @@ golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR3 golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/main.go b/main.go index e41bd8e..6d01909 100644 --- a/main.go +++ b/main.go @@ -4,8 +4,13 @@ import ( "encoding/json" "fmt" "os" + "strings" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" azlog "github.com/Azure/azure-sdk-for-go/sdk/azcore/log" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/hashicorp/go-hclog" "github.com/magodo/azlist/azlist" @@ -14,6 +19,7 @@ import ( func main() { var ( + flagEnvironment string flagSubscriptionId string flagRecursive bool flagWithBody bool @@ -30,8 +36,14 @@ func main() { UsageText: "azlist [option] ", Flags: []cli.Flag{ &cli.StringFlag{ - Name: "subscription-id", - // Honor the "ARM_SUBSCRIPTION_ID" as is used by the AzureRM provider, for easier use. + Name: "env", + EnvVars: []string{"AZLIST_ENV"}, + Usage: `The environment. Can be one of "public", "china", "usgovernment".`, + Destination: &flagEnvironment, + Value: "public", + }, + &cli.StringFlag{ + Name: "subscription-id", EnvVars: []string{"AZLIST_SUBSCRIPTION_ID", "ARM_SUBSCRIPTION_ID"}, Aliases: []string{"s"}, Required: true, @@ -104,13 +116,63 @@ func main() { }) } - opt := &azlist.Option{ + cloudCfg := cloud.AzurePublic + switch strings.ToLower(flagEnvironment) { + case "public": + cloudCfg = cloud.AzurePublic + case "usgovernment": + cloudCfg = cloud.AzureGovernment + case "china": + cloudCfg = cloud.AzureChina + default: + return fmt.Errorf("unknown environment specified: %q", flagEnvironment) + } + + if v, ok := os.LookupEnv("ARM_TENANT_ID"); ok { + os.Setenv("AZURE_TENANT_ID", v) + } + if v, ok := os.LookupEnv("ARM_CLIENT_ID"); ok { + os.Setenv("AZURE_CLIENT_ID", v) + } + if v, ok := os.LookupEnv("ARM_CLIENT_SECRET"); ok { + os.Setenv("AZURE_CLIENT_SECRET", v) + } + if v, ok := os.LookupEnv("ARM_CLIENT_CERTIFICATE_PATH"); ok { + os.Setenv("AZURE_CLIENT_CERTIFICATE_PATH", v) + } + + clientOpt := arm.ClientOptions{ + ClientOptions: policy.ClientOptions{ + Cloud: cloudCfg, + Telemetry: policy.TelemetryOptions{ + ApplicationID: "azlist", + Disabled: false, + }, + Logging: policy.LogOptions{ + IncludeBody: true, + }, + }, + } + + cred, err := azidentity.NewDefaultAzureCredential(&azidentity.DefaultAzureCredentialOptions{ + ClientOptions: clientOpt.ClientOptions, + TenantID: os.Getenv("ARM_TENANT_ID"), + }) + if err != nil { + return fmt.Errorf("failed to obtain a credential: %v", err) + } + + opt := azlist.Option{ + SubscriptionId: flagSubscriptionId, + Cred: cred, + ClientOpt: clientOpt, + Parallelism: flagParallelism, Recursive: flagRecursive, IncludeManaged: flagIncludeManaged, } - result, err := azlist.List(ctx.Context, flagSubscriptionId, ctx.Args().First(), opt) + result, err := azlist.List(ctx.Context, ctx.Args().First(), opt) if err != nil { return err }