diff --git a/.gitignore b/.gitignore index adfefb3..eb4ad52 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .vscode dist/ +.idea +aztfexport diff --git a/flag.go b/flag.go index c8246e2..f8d7356 100644 --- a/flag.go +++ b/flag.go @@ -2,8 +2,21 @@ package main import ( "fmt" + "os" + "os/exec" "strings" + "github.com/Azure/aztfexport/internal/cfgfile" + "github.com/Azure/aztfexport/pkg/config" + "github.com/Azure/aztfexport/pkg/telemetry" + "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/gofrs/uuid" + "github.com/hashicorp/go-hclog" + "github.com/magodo/terraform-client-go/tfclient" "github.com/urfave/cli/v2" ) @@ -18,6 +31,7 @@ type FlagSet struct { flagAppend bool flagDevProvider bool flagProviderVersion string + flagProviderName string flagBackendType string flagBackendConfig cli.StringSlice flagFullConfig bool @@ -172,3 +186,178 @@ func (flag FlagSet) DescribeCLI(mode string) string { } return "aztfexport " + strings.Join(args, " ") } + +func initTelemetryClient(subscriptionId string) telemetry.Client { + cfg, err := cfgfile.GetConfig() + if err != nil { + return telemetry.NewNullClient() + } + enabled, installId := cfg.TelemetryEnabled, cfg.InstallationId + if !enabled { + return telemetry.NewNullClient() + } + if installId == "" { + uuid, err := uuid.NewV4() + if err == nil { + installId = uuid.String() + } else { + installId = "undefined" + } + } + + sessionId := "undefined" + if uuid, err := uuid.NewV4(); err == nil { + sessionId = uuid.String() + } + return telemetry.NewAppInsight(subscriptionId, installId, sessionId) +} + +// buildAzureSDKCredAndClientOpt builds the Azure SDK credential and client option from multiple sources (i.e. environment variables, MSI, Azure CLI). +func buildAzureSDKCredAndClientOpt(fset FlagSet) (azcore.TokenCredential, *arm.ClientOptions, error) { + var cloudCfg cloud.Configuration + switch env := fset.flagEnv; strings.ToLower(env) { + case "public": + cloudCfg = cloud.AzurePublic + case "usgovernment": + cloudCfg = cloud.AzureGovernment + case "china": + cloudCfg = cloud.AzureChina + default: + return nil, 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 { + // #nosec G104 + os.Setenv("AZURE_TENANT_ID", v) + } + if v, ok := os.LookupEnv("ARM_CLIENT_ID"); ok { + // #nosec G104 + os.Setenv("AZURE_CLIENT_ID", v) + } + if v, ok := os.LookupEnv("ARM_CLIENT_SECRET"); ok { + // #nosec G104 + os.Setenv("AZURE_CLIENT_SECRET", v) + } + if v, ok := os.LookupEnv("ARM_CLIENT_CERTIFICATE_PATH"); ok { + // #nosec G104 + os.Setenv("AZURE_CLIENT_CERTIFICATE_PATH", v) + } + + clientOpt := &arm.ClientOptions{ + ClientOptions: policy.ClientOptions{ + Cloud: cloudCfg, + Telemetry: policy.TelemetryOptions{ + ApplicationID: "aztfexport", + Disabled: false, + }, + Logging: policy.LogOptions{ + IncludeBody: true, + }, + }, + } + + tenantId := os.Getenv("ARM_TENANT_ID") + var ( + cred azcore.TokenCredential + err error + ) + switch { + case fset.flagUseEnvironmentCred: + cred, err = azidentity.NewEnvironmentCredential(&azidentity.EnvironmentCredentialOptions{ + ClientOptions: clientOpt.ClientOptions, + }) + if err != nil { + return nil, nil, fmt.Errorf("failed to new Environment credential: %v", err) + } + return cred, clientOpt, nil + case fset.flagUseManagedIdentityCred: + cred, err = azidentity.NewManagedIdentityCredential(&azidentity.ManagedIdentityCredentialOptions{ + ClientOptions: clientOpt.ClientOptions, + }) + if err != nil { + return nil, nil, fmt.Errorf("failed to new Managed Identity credential: %v", err) + } + return cred, clientOpt, nil + case fset.flagUseAzureCLICred: + cred, err = azidentity.NewAzureCLICredential(&azidentity.AzureCLICredentialOptions{ + TenantID: tenantId, + }) + if err != nil { + return nil, nil, fmt.Errorf("failed to new Azure CLI credential: %v", err) + } + return cred, clientOpt, nil + case fset.flagUseOIDCCred: + cred, err = NewOidcCredential(&OidcCredentialOptions{ + ClientOptions: clientOpt.ClientOptions, + TenantID: tenantId, + ClientID: os.Getenv("ARM_CLIENT_ID"), + RequestToken: fset.flagOIDCRequestToken, + RequestUrl: fset.flagOIDCRequestURL, + Token: fset.flagOIDCToken, + TokenFilePath: fset.flagOIDCTokenFilePath, + }) + if err != nil { + return nil, nil, fmt.Errorf("failed to new OIDC credential: %v", err) + } + return cred, clientOpt, nil + default: + opt := &azidentity.DefaultAzureCredentialOptions{ + ClientOptions: clientOpt.ClientOptions, + TenantID: tenantId, + } + cred, err := azidentity.NewDefaultAzureCredential(opt) + if err != nil { + return nil, nil, fmt.Errorf("failed to new Default credential: %v", err) + } + return cred, clientOpt, nil + } +} + +func (f FlagSet) BuildCommonConfig() (config.CommonConfig, error) { + cred, clientOpt, err := buildAzureSDKCredAndClientOpt(f) + if err != nil { + return config.CommonConfig{}, err + } + + cfg := config.CommonConfig{ + SubscriptionId: f.flagSubscriptionId, + AzureSDKCredential: cred, + AzureSDKClientOption: *clientOpt, + OutputDir: f.flagOutputDir, + ProviderVersion: f.flagProviderVersion, + ProviderName: f.flagProviderName, + DevProvider: f.flagDevProvider, + ContinueOnError: f.flagContinue, + BackendType: f.flagBackendType, + BackendConfig: f.flagBackendConfig.Value(), + FullConfig: f.flagFullConfig, + Parallelism: f.flagParallelism, + HCLOnly: f.flagHCLOnly, + ModulePath: f.flagModulePath, + TelemetryClient: initTelemetryClient(f.flagSubscriptionId), + } + + if f.flagAppend { + cfg.OutputFileNames = config.OutputFileNames{ + TerraformFileName: "terraform.aztfexport.tf", + ProviderFileName: "provider.aztfexport.tf", + MainFileName: "main.aztfexport.tf", + ImportBlockFileName: "import.aztfexport.tf", + } + } + + if f.hflagTFClientPluginPath != "" { + // #nosec G204 + tfc, err := tfclient.New(tfclient.Option{ + Cmd: exec.Command(flagset.hflagTFClientPluginPath), + Logger: hclog.NewNullLogger(), + }) + if err != nil { + return cfg, err + } + cfg.TFClient = tfc + } + + return cfg, nil +} diff --git a/internal/meta/base_meta.go b/internal/meta/base_meta.go index 4a31471..74fa425 100644 --- a/internal/meta/base_meta.go +++ b/internal/meta/base_meta.go @@ -84,6 +84,7 @@ type baseMeta struct { resourceClient *armresources.Client providerVersion string devProvider bool + providerName string backendType string backendConfig []string providerConfig map[string]cty.Value @@ -204,6 +205,7 @@ func NewBaseMeta(cfg config.CommonConfig) (*baseMeta, error) { backendType: cfg.BackendType, backendConfig: cfg.BackendConfig, providerConfig: cfg.ProviderConfig, + providerName: cfg.ProviderName, fullConfig: cfg.FullConfig, parallelism: cfg.Parallelism, hclOnly: cfg.HCLOnly, @@ -257,6 +259,7 @@ func (meta *baseMeta) CleanTFState(ctx context.Context, addr string) { func (meta *baseMeta) ParallelImport(ctx context.Context, items []*ImportItem) error { meta.tc.Trace(telemetry.Info, "ParallelImport Enter") defer meta.tc.Trace(telemetry.Info, "ParallelImport Leave") + itemsCh := make(chan *ImportItem, len(items)) for _, item := range items { itemsCh <- item @@ -512,11 +515,26 @@ func (meta baseMeta) generateCfg(ctx context.Context, l ImportList, cfgTrans ... return meta.generateConfig(cfginfos) } +func (meta *baseMeta) useAzAPI() bool { + return meta.providerName == "azapi" +} + func (meta *baseMeta) buildTerraformConfigForImportDir() string { if meta.devProvider { return "terraform {}" } + if meta.useAzAPI() { + return `terraform { + required_providers { + azapi = { + source = "azure/azapi" + } + } +} +` + } + return fmt.Sprintf(`terraform { required_providers { azurerm = { @@ -536,6 +554,18 @@ func (meta *baseMeta) buildTerraformConfig(backendType string) string { `, backendType) } + if meta.useAzAPI() { + return fmt.Sprintf(`terraform { + backend %q {} + required_providers { + azapi = { + source = "azure/azapi" + } + } +} +`, backendType) + } + return fmt.Sprintf(`terraform { backend %q {} required_providers { @@ -550,10 +580,15 @@ func (meta *baseMeta) buildTerraformConfig(backendType string) string { func (meta *baseMeta) buildProviderConfig() string { f := hclwrite.NewEmptyFile() - body := f.Body().AppendNewBlock("provider", []string{"azurerm"}).Body() - body.AppendNewBlock("features", nil) - for k, v := range meta.providerConfig { - body.SetAttributeValue(k, v) + + if meta.useAzAPI() { + f.Body().AppendNewBlock("provider", []string{"azapi"}).Body() + } else { + body := f.Body().AppendNewBlock("provider", []string{"azurerm"}).Body() + body.AppendNewBlock("features", nil) + for k, v := range meta.providerConfig { + body.SetAttributeValue(k, v) + } } return string(f.Bytes()) } diff --git a/internal/meta/config_info.go b/internal/meta/config_info.go index fd7f4f5..1d92a99 100644 --- a/internal/meta/config_info.go +++ b/internal/meta/config_info.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/hashicorp/hcl/v2/hclwrite" + "github.com/magodo/armid" "github.com/zclconf/go-cty/cty" ) @@ -141,9 +142,9 @@ func (cfgs ConfigInfos) addReferenceDependency() error { // TF resource id to Azure resource ids. // Typically, one TF resource id maps to one Azure resource id. However, there are cases that one one TF resource id maps to multiple Azure resource ids. // E.g. A parent and child resources have the same TF id. Or the association resource's TF id is the same as the master resource's. - m := map[string][]string{} + m := map[string][]armid.ResourceId{} for _, cfg := range cfgs { - m[cfg.TFResourceId] = append(m[cfg.TFResourceId], cfg.AzureResourceID.String()) + m[cfg.TFResourceId] = append(m[cfg.TFResourceId], cfg.AzureResourceID) } for i, cfg := range cfgs { @@ -170,10 +171,14 @@ func (cfgs ConfigInfos) addReferenceDependency() error { var dependingResourceIdsWithoutSelf []string for _, id := range dependingResourceIds[:] { - if id == cfg.AzureResourceID.String() { + if id.String() == cfg.AzureResourceID.String() { continue } - dependingResourceIdsWithoutSelf = append(dependingResourceIdsWithoutSelf, id) + // if cfg is parent of `id` resource, we should skip, or it will cause circular dependency, so skip parent depends on sub resources + if cfg.AzureResourceID.Equal(id.Parent()) { + continue + } + dependingResourceIdsWithoutSelf = append(dependingResourceIdsWithoutSelf, id.String()) } if len(dependingResourceIdsWithoutSelf) != 0 { cfg.DependsOn = append(cfg.DependsOn, Dependency{Candidates: dependingResourceIdsWithoutSelf}) diff --git a/internal/meta/meta_query.go b/internal/meta/meta_query.go index eba9388..22f116d 100644 --- a/internal/meta/meta_query.go +++ b/internal/meta/meta_query.go @@ -50,17 +50,23 @@ func (meta *MetaQuery) ListResource(ctx context.Context) (ImportList, error) { if err != nil { return nil, err } - log.Printf("[DEBUG] Populate resource set") - if err := rset.PopulateResource(); err != nil { - return nil, fmt.Errorf("tweaking single resources in the azure resource set: %v", err) - } - log.Printf("[DEBUG] Reduce resource set") - if err := rset.ReduceResource(); err != nil { - return nil, fmt.Errorf("tweaking across resources in the azure resource set: %v", err) - } + var rl []resourceset.TFResource + if meta.useAzAPI() { + log.Printf("[DEBUG] Azure Resource set map to TF resource set") + rl = rset.ToTFAzAPIResources() + } else { + log.Printf("[DEBUG] Populate resource set") + if err := rset.PopulateResource(); err != nil { + return nil, fmt.Errorf("tweaking single resources in the azure resource set: %v", err) + } + log.Printf("[DEBUG] Reduce resource set") + if err := rset.ReduceResource(); err != nil { + return nil, fmt.Errorf("tweaking across resources in the azure resource set: %v", err) + } - log.Printf("[DEBUG] Azure Resource set map to TF resource set") - rl := rset.ToTFResources(meta.parallelism, meta.azureSDKCred, meta.azureSDKClientOpt) + log.Printf("[DEBUG] Azure Resource set map to TF resource set") + rl = rset.ToTFAzureRMResources(meta.parallelism, meta.azureSDKCred, meta.azureSDKClientOpt) + } var l ImportList for i, res := range rl { diff --git a/internal/meta/meta_res.go b/internal/meta/meta_res.go index d41233a..d8cd1fc 100644 --- a/internal/meta/meta_res.go +++ b/internal/meta/meta_res.go @@ -44,7 +44,7 @@ func (meta MetaResource) ScopeName() string { } func (meta *MetaResource) ListResource(_ context.Context) (ImportList, error) { - resourceSet := resourceset.AzureResourceSet{ + resourceSet := &resourceset.AzureResourceSet{ Resources: []resourceset.AzureResource{ { Id: meta.AzureId, @@ -52,7 +52,13 @@ func (meta *MetaResource) ListResource(_ context.Context) (ImportList, error) { }, } log.Printf("[DEBUG] Azure Resource set map to TF resource set") - rl := resourceSet.ToTFResources(meta.parallelism, meta.azureSDKCred, meta.azureSDKClientOpt) + + var rl []resourceset.TFResource + if meta.useAzAPI() { + rl = resourceSet.ToTFAzAPIResources() + } else { + rl = resourceSet.ToTFAzureRMResources(meta.parallelism, meta.azureSDKCred, meta.azureSDKClientOpt) + } // This is to record known resource types. In case there is a known resource type and there comes another same typed resource, // then we need to modify the resource name. Otherwise, there will be a resource address conflict. @@ -67,7 +73,7 @@ func (meta *MetaResource) ListResource(_ context.Context) (ImportList, error) { name += fmt.Sprintf("-%d", rtCnt[res.TFType]-1) } tfAddr := tfaddr.TFAddr{ - Type: res.TFType, //this might be empty if have multiple matches in aztft + Type: res.TFType, // this might be empty if have multiple matches in aztft Name: name, } item := ImportItem{ @@ -79,7 +85,7 @@ func (meta *MetaResource) ListResource(_ context.Context) (ImportList, error) { // Some special Azure resource is missing the essential property that is used by aztft to detect their TF resource type. // In this case, users can use the `--type` option to manually specify the TF resource type. - if meta.ResourceType != "" { + if meta.ResourceType != "" && !meta.useAzAPI() { if meta.AzureId.Equal(res.AzureId) { tfid, err := aztft.QueryId(meta.AzureId.String(), meta.ResourceType, &aztft.APIOption{ diff --git a/internal/meta/meta_rg.go b/internal/meta/meta_rg.go index 20a7dbd..34ec15e 100644 --- a/internal/meta/meta_rg.go +++ b/internal/meta/meta_rg.go @@ -45,17 +45,24 @@ func (meta *MetaResourceGroup) ListResource(ctx context.Context) (ImportList, er if err != nil { return nil, err } - log.Printf("[DEBUG] Populate resource set") - if err := rset.PopulateResource(); err != nil { - return nil, fmt.Errorf("tweaking single resources in the azure resource set: %v", err) - } - log.Printf("[DEBUG] Reduce resource set") - if err := rset.ReduceResource(); err != nil { - return nil, fmt.Errorf("tweaking across resources in the azure resource set: %v", err) - } - log.Printf("[DEBUG] Azure Resource set map to TF resource set") - rl := rset.ToTFResources(meta.parallelism, meta.azureSDKCred, meta.azureSDKClientOpt) + var rl []resourceset.TFResource + if meta.useAzAPI() { + rl = rset.ToTFAzAPIResources() + } else { + + log.Printf("[DEBUG] Populate resource set") + if err := rset.PopulateResource(); err != nil { + return nil, fmt.Errorf("tweaking single resources in the azure resource set: %v", err) + } + log.Printf("[DEBUG] Reduce resource set") + if err := rset.ReduceResource(); err != nil { + return nil, fmt.Errorf("tweaking across resources in the azure resource set: %v", err) + } + + log.Printf("[DEBUG] Azure Resource set map to TF resource set") + rl = rset.ToTFAzureRMResources(meta.parallelism, meta.azureSDKCred, meta.azureSDKClientOpt) + } var l ImportList for i, res := range rl { diff --git a/internal/resourceset/azure_resource_set.go b/internal/resourceset/azure_resource_set.go index 98ae1dc..2fd6aef 100644 --- a/internal/resourceset/azure_resource_set.go +++ b/internal/resourceset/azure_resource_set.go @@ -26,7 +26,7 @@ type PesudoResourceInfo struct { TFId string } -func (rset AzureResourceSet) ToTFResources(parallelism int, cred azcore.TokenCredential, clientOpt arm.ClientOptions) []TFResource { +func (rset AzureResourceSet) ToTFAzureRMResources(parallelism int, cred azcore.TokenCredential, clientOpt arm.ClientOptions) []TFResource { tfresources := []TFResource{} wp := workerpool.NewWorkPool(parallelism) @@ -100,3 +100,14 @@ func (rset AzureResourceSet) ToTFResources(parallelism int, cred azcore.TokenCre return tfresources } + +func (rset AzureResourceSet) ToTFAzAPIResources() (result []TFResource) { + for _, res := range rset.Resources { + result = append(result, TFResource{ + AzureId: res.Id, + TFId: res.Id.String(), + TFType: "azapi_resource", + }) + } + return +} diff --git a/main.go b/main.go index 49f99a5..4f7f464 100644 --- a/main.go +++ b/main.go @@ -26,17 +26,11 @@ import ( "github.com/hashicorp/go-hclog" "github.com/magodo/armid" "github.com/magodo/azlist/azlist" - "github.com/magodo/terraform-client-go/tfclient" "github.com/magodo/tfadd/providers/azurerm" "github.com/Azure/aztfexport/internal" "github.com/Azure/aztfexport/internal/ui" - "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" 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/urfave/cli/v2" ) @@ -162,6 +156,12 @@ func main() { Usage: fmt.Sprintf("The azurerm provider version to use for importing (default: existing version constraints or %s)", azurerm.ProviderSchemaInfo.Version), Destination: &flagset.flagProviderVersion, }, + &cli.StringFlag{ + Name: "provider-name", + EnvVars: []string{"AZTFEXPORT_PROVIDER_NAME"}, + Usage: fmt.Sprintf("The provider name to use for importing (default: azurerm, possible values are auzrerm and azapi)"), + Destination: &flagset.flagProviderName, + }, &cli.StringFlag{ Name: "backend-type", EnvVars: []string{"AZTFEXPORT_BACKEND_TYPE"}, @@ -354,13 +354,6 @@ func main() { mappingFileFlags := append([]cli.Flag{}, commonFlags...) - safeOutputFileNames := config.OutputFileNames{ - TerraformFileName: "terraform.aztfexport.tf", - ProviderFileName: "provider.aztfexport.tf", - MainFileName: "main.aztfexport.tf", - ImportBlockFileName: "import.aztfexport.tf", - } - app := &cli.App{ Name: "aztfexport", Version: getVersion(), @@ -446,50 +439,19 @@ func main() { return fmt.Errorf("invalid resource id: %v", err) } - cred, clientOpt, err := buildAzureSDKCredAndClientOpt(flagset) + commonConfig, err := flagset.BuildCommonConfig() if err != nil { return err } // Initialize the config cfg := config.Config{ - CommonConfig: config.CommonConfig{ - SubscriptionId: flagset.flagSubscriptionId, - AzureSDKCredential: cred, - AzureSDKClientOption: *clientOpt, - OutputDir: flagset.flagOutputDir, - ProviderVersion: flagset.flagProviderVersion, - DevProvider: flagset.flagDevProvider, - ContinueOnError: flagset.flagContinue, - BackendType: flagset.flagBackendType, - BackendConfig: flagset.flagBackendConfig.Value(), - FullConfig: flagset.flagFullConfig, - Parallelism: flagset.flagParallelism, - HCLOnly: flagset.flagHCLOnly, - ModulePath: flagset.flagModulePath, - TelemetryClient: initTelemetryClient(flagset.flagSubscriptionId), - }, + CommonConfig: commonConfig, ResourceId: resId, TFResourceName: flagset.flagResName, TFResourceType: flagset.flagResType, } - if flagset.flagAppend { - cfg.CommonConfig.OutputFileNames = safeOutputFileNames - } - - if flagset.hflagTFClientPluginPath != "" { - // #nosec G204 - tfc, err := tfclient.New(tfclient.Option{ - Cmd: exec.Command(flagset.hflagTFClientPluginPath), - Logger: hclog.NewNullLogger(), - }) - if err != nil { - return err - } - cfg.CommonConfig.TFClient = tfc - } - return realMain(c.Context, cfg, flagset.flagNonInteractive, flagset.hflagMockClient, flagset.flagPlainUI, flagset.flagGenerateMappingFile, flagset.hflagProfile, flagset.DescribeCLI(ModeResource)) }, }, @@ -510,50 +472,19 @@ func main() { rg := c.Args().First() - cred, clientOpt, err := buildAzureSDKCredAndClientOpt(flagset) + commonConfig, err := flagset.BuildCommonConfig() if err != nil { return err } // Initialize the config cfg := config.Config{ - CommonConfig: config.CommonConfig{ - SubscriptionId: flagset.flagSubscriptionId, - AzureSDKCredential: cred, - AzureSDKClientOption: *clientOpt, - OutputDir: flagset.flagOutputDir, - ProviderVersion: flagset.flagProviderVersion, - DevProvider: flagset.flagDevProvider, - ContinueOnError: flagset.flagContinue, - BackendType: flagset.flagBackendType, - BackendConfig: flagset.flagBackendConfig.Value(), - FullConfig: flagset.flagFullConfig, - Parallelism: flagset.flagParallelism, - HCLOnly: flagset.flagHCLOnly, - ModulePath: flagset.flagModulePath, - TelemetryClient: initTelemetryClient(flagset.flagSubscriptionId), - }, + CommonConfig: commonConfig, ResourceGroupName: rg, ResourceNamePattern: flagset.flagPattern, RecursiveQuery: true, } - if flagset.flagAppend { - cfg.CommonConfig.OutputFileNames = safeOutputFileNames - } - - if flagset.hflagTFClientPluginPath != "" { - // #nosec G204 - tfc, err := tfclient.New(tfclient.Option{ - Cmd: exec.Command(flagset.hflagTFClientPluginPath), - Logger: hclog.NewNullLogger(), - }) - if err != nil { - return err - } - cfg.CommonConfig.TFClient = tfc - } - return realMain(c.Context, cfg, flagset.flagNonInteractive, flagset.hflagMockClient, flagset.flagPlainUI, flagset.flagGenerateMappingFile, flagset.hflagProfile, flagset.DescribeCLI(ModeResourceGroup)) }, }, @@ -573,50 +504,19 @@ func main() { predicate := c.Args().First() - cred, clientOpt, err := buildAzureSDKCredAndClientOpt(flagset) + commonConfig, err := flagset.BuildCommonConfig() if err != nil { return err } // Initialize the config cfg := config.Config{ - CommonConfig: config.CommonConfig{ - SubscriptionId: flagset.flagSubscriptionId, - AzureSDKCredential: cred, - AzureSDKClientOption: *clientOpt, - OutputDir: flagset.flagOutputDir, - ProviderVersion: flagset.flagProviderVersion, - DevProvider: flagset.flagDevProvider, - ContinueOnError: flagset.flagContinue, - BackendType: flagset.flagBackendType, - BackendConfig: flagset.flagBackendConfig.Value(), - FullConfig: flagset.flagFullConfig, - Parallelism: flagset.flagParallelism, - HCLOnly: flagset.flagHCLOnly, - ModulePath: flagset.flagModulePath, - TelemetryClient: initTelemetryClient(flagset.flagSubscriptionId), - }, + CommonConfig: commonConfig, ARGPredicate: predicate, ResourceNamePattern: flagset.flagPattern, RecursiveQuery: flagset.flagRecursive, } - if flagset.flagAppend { - cfg.CommonConfig.OutputFileNames = safeOutputFileNames - } - - if flagset.hflagTFClientPluginPath != "" { - // #nosec G204 - tfc, err := tfclient.New(tfclient.Option{ - Cmd: exec.Command(flagset.hflagTFClientPluginPath), - Logger: hclog.NewNullLogger(), - }) - if err != nil { - return err - } - cfg.CommonConfig.TFClient = tfc - } - return realMain(c.Context, cfg, flagset.flagNonInteractive, flagset.hflagMockClient, flagset.flagPlainUI, flagset.flagGenerateMappingFile, flagset.hflagProfile, flagset.DescribeCLI(ModeQuery)) }, }, @@ -637,46 +537,15 @@ func main() { mapFile := c.Args().First() - cred, clientOpt, err := buildAzureSDKCredAndClientOpt(flagset) + commonConfig, err := flagset.BuildCommonConfig() if err != nil { return err } // Initialize the config cfg := config.Config{ - CommonConfig: config.CommonConfig{ - SubscriptionId: flagset.flagSubscriptionId, - AzureSDKCredential: cred, - AzureSDKClientOption: *clientOpt, - OutputDir: flagset.flagOutputDir, - ProviderVersion: flagset.flagProviderVersion, - DevProvider: flagset.flagDevProvider, - ContinueOnError: flagset.flagContinue, - BackendType: flagset.flagBackendType, - BackendConfig: flagset.flagBackendConfig.Value(), - FullConfig: flagset.flagFullConfig, - Parallelism: flagset.flagParallelism, - HCLOnly: flagset.flagHCLOnly, - ModulePath: flagset.flagModulePath, - TelemetryClient: initTelemetryClient(flagset.flagSubscriptionId), - }, - MappingFile: mapFile, - } - - if flagset.flagAppend { - cfg.CommonConfig.OutputFileNames = safeOutputFileNames - } - - if flagset.hflagTFClientPluginPath != "" { - // #nosec G204 - tfc, err := tfclient.New(tfclient.Option{ - Cmd: exec.Command(flagset.hflagTFClientPluginPath), - Logger: hclog.NewNullLogger(), - }) - if err != nil { - return err - } - cfg.CommonConfig.TFClient = tfc + CommonConfig: commonConfig, + MappingFile: mapFile, } return realMain(c.Context, cfg, flagset.flagNonInteractive, flagset.hflagMockClient, flagset.flagPlainUI, flagset.flagGenerateMappingFile, flagset.hflagProfile, flagset.DescribeCLI(ModeMappingFile)) @@ -748,133 +617,6 @@ func initLog(path string, flagLevel string) error { return nil } -func initTelemetryClient(subscriptionId string) telemetry.Client { - cfg, err := cfgfile.GetConfig() - if err != nil { - return telemetry.NewNullClient() - } - enabled, installId := cfg.TelemetryEnabled, cfg.InstallationId - if !enabled { - return telemetry.NewNullClient() - } - if installId == "" { - uuid, err := uuid.NewV4() - if err == nil { - installId = uuid.String() - } else { - installId = "undefined" - } - } - - sessionId := "undefined" - if uuid, err := uuid.NewV4(); err == nil { - sessionId = uuid.String() - } - return telemetry.NewAppInsight(subscriptionId, installId, sessionId) -} - -// buildAzureSDKCredAndClientOpt builds the Azure SDK credential and client option from multiple sources (i.e. environment variables, MSI, Azure CLI). -func buildAzureSDKCredAndClientOpt(fset FlagSet) (azcore.TokenCredential, *arm.ClientOptions, error) { - var cloudCfg cloud.Configuration - switch env := fset.flagEnv; strings.ToLower(env) { - case "public": - cloudCfg = cloud.AzurePublic - case "usgovernment": - cloudCfg = cloud.AzureGovernment - case "china": - cloudCfg = cloud.AzureChina - default: - return nil, 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 { - // #nosec G104 - os.Setenv("AZURE_TENANT_ID", v) - } - if v, ok := os.LookupEnv("ARM_CLIENT_ID"); ok { - // #nosec G104 - os.Setenv("AZURE_CLIENT_ID", v) - } - if v, ok := os.LookupEnv("ARM_CLIENT_SECRET"); ok { - // #nosec G104 - os.Setenv("AZURE_CLIENT_SECRET", v) - } - if v, ok := os.LookupEnv("ARM_CLIENT_CERTIFICATE_PATH"); ok { - // #nosec G104 - os.Setenv("AZURE_CLIENT_CERTIFICATE_PATH", v) - } - - clientOpt := &arm.ClientOptions{ - ClientOptions: policy.ClientOptions{ - Cloud: cloudCfg, - Telemetry: policy.TelemetryOptions{ - ApplicationID: "aztfexport", - Disabled: false, - }, - Logging: policy.LogOptions{ - IncludeBody: true, - }, - }, - } - - tenantId := os.Getenv("ARM_TENANT_ID") - var ( - cred azcore.TokenCredential - err error - ) - switch { - case fset.flagUseEnvironmentCred: - cred, err = azidentity.NewEnvironmentCredential(&azidentity.EnvironmentCredentialOptions{ - ClientOptions: clientOpt.ClientOptions, - }) - if err != nil { - return nil, nil, fmt.Errorf("failed to new Environment credential: %v", err) - } - return cred, clientOpt, nil - case fset.flagUseManagedIdentityCred: - cred, err = azidentity.NewManagedIdentityCredential(&azidentity.ManagedIdentityCredentialOptions{ - ClientOptions: clientOpt.ClientOptions, - }) - if err != nil { - return nil, nil, fmt.Errorf("failed to new Managed Identity credential: %v", err) - } - return cred, clientOpt, nil - case fset.flagUseAzureCLICred: - cred, err = azidentity.NewAzureCLICredential(&azidentity.AzureCLICredentialOptions{ - TenantID: tenantId, - }) - if err != nil { - return nil, nil, fmt.Errorf("failed to new Azure CLI credential: %v", err) - } - return cred, clientOpt, nil - case fset.flagUseOIDCCred: - cred, err = NewOidcCredential(&OidcCredentialOptions{ - ClientOptions: clientOpt.ClientOptions, - TenantID: tenantId, - ClientID: os.Getenv("ARM_CLIENT_ID"), - RequestToken: fset.flagOIDCRequestToken, - RequestUrl: fset.flagOIDCRequestURL, - Token: fset.flagOIDCToken, - TokenFilePath: fset.flagOIDCTokenFilePath, - }) - if err != nil { - return nil, nil, fmt.Errorf("failed to new OIDC credential: %v", err) - } - return cred, clientOpt, nil - default: - opt := &azidentity.DefaultAzureCredentialOptions{ - ClientOptions: clientOpt.ClientOptions, - TenantID: tenantId, - } - cred, err := azidentity.NewDefaultAzureCredential(opt) - if err != nil { - return nil, nil, fmt.Errorf("failed to new Default credential: %v", err) - } - return cred, clientOpt, nil - } -} - func subscriptionIdFromCLI() (string, error) { var stderr bytes.Buffer var stdout bytes.Buffer diff --git a/pkg/config/config.go b/pkg/config/config.go index 6fe1adc..3f2cc2e 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -35,6 +35,9 @@ type CommonConfig struct { // DevProvider specifies whether users have configured the `dev_overrides` for the provider, which then uses a development provider built locally rather than using a version pinned provider from official Terraform registry. // Meanwhile, it will also avoid running `terraform init` during `Init()` for the import directories to avoid caculating the provider hash and populating the lock file (See: https://developer.hashicorp.com/terraform/language/files/dependency-lock). Though the init for the output directory is still needed for initializing the backend. DevProvider bool + // ProviderName specifies the provider Name. If this is not set, it will use `azurerm` for importing in order to be consistent with tfadd. + // Supported values: azurerm, azapi + ProviderName string // ContinueOnError specifies whether continue the progress even hit an import error. ContinueOnError bool // BackendType specifies the Terraform backend type.