command_before_func.go (226 lines of code) (raw):

package main import ( "fmt" "os" "slices" "strings" "github.com/Azure/aztfexport/internal/utils" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph" "github.com/hashicorp/terraform-config-inspect/tfconfig" "github.com/urfave/cli/v2" ) func commandBeforeFunc(fset *FlagSet, mode Mode) func(ctx *cli.Context) error { return func(ctx *cli.Context) error { // Common flags check if fset.flagAppend { if fset.flagOverwrite { return fmt.Errorf("`--append` conflicts with `--overwrite`") } } if !fset.flagNonInteractive { if fset.flagContinue { return fmt.Errorf("`--continue` must be used together with `--non-interactive`") } if fset.flagGenerateMappingFile { return fmt.Errorf("`--generate-mapping-file` must be used together with `--non-interactive`") } } if fset.flagHCLOnly { if fset.flagAppend { return fmt.Errorf("`--append` conflicts with `--hcl-only`") } if fset.flagModulePath != "" { return fmt.Errorf("`--module-path` conflicts with `--hcl-only`") } } if fset.flagModulePath != "" { if !fset.flagAppend { return fmt.Errorf("`--module-path` must be used together with `--append`") } } if fset.flagDevProvider { if fset.flagProviderVersion != "" { return fmt.Errorf("`--dev-provider` conflicts with `--provider-version`") } } if fset.hflagTFClientPluginPath != "" { if !fset.flagHCLOnly { return fmt.Errorf("`--tfclient-plugin-path` must be used together with `--hcl-only`") } } if err := conflictArgs([]argDesc{ { name: "--client-id", isSet: fset.flagClientId != "", }, { name: "--client-id-file-path", isSet: fset.flagClientIdFilePath != "", }, }); err != nil { return err } if err := conflictArgs([]argDesc{ { name: "--client-certificate", isSet: fset.flagClientCertificate != "", }, { name: "--client-certificate-path", isSet: fset.flagClientCertificatePath != "", }, }); err != nil { return err } if err := conflictArgs([]argDesc{ { name: "--client-secret", isSet: fset.flagClientSecret != "", }, { name: "--client-secret-file-path", isSet: fset.flagClientSecretFilePath != "", }, }); err != nil { return err } if err := conflictArgs([]argDesc{ { name: "--oidc-token", isSet: fset.flagOIDCToken != "", }, { name: "--oidc-token-file-path", isSet: fset.flagOIDCTokenFilePath != "", }, }); err != nil { return err } // Mode specific flags check switch mode { case ModeResource: if ctx.Args().Len() > 1 || (ctx.Args().Len() == 1 && strings.HasPrefix(ctx.Args().First(), "@")) { if fset.flagResType != "" { return fmt.Errorf("`--type` can't be specified for multi-resource mode") } if fset.flagResName != "" { return fmt.Errorf("`--name` can't be specified for multi-resource mode") } } case ModeQuery: if fset.flagARGAuthorizationScopeFilter != "" { if !slices.Contains(armresourcegraph.PossibleAuthorizationScopeFilterValues(), armresourcegraph.AuthorizationScopeFilter(fset.flagARGAuthorizationScopeFilter)) { return fmt.Errorf("invalid value of `--arg-authorization-scope-filter`") } } } // Initialize output directory if _, err := os.Stat(fset.flagOutputDir); os.IsNotExist(err) { if err := os.MkdirAll(fset.flagOutputDir, 0750); err != nil { return fmt.Errorf("creating output directory %q: %v", fset.flagOutputDir, err) } } empty, err := utils.DirIsEmpty(fset.flagOutputDir) if err != nil { return fmt.Errorf("failed to check emptiness of output directory %q: %v", fset.flagOutputDir, err) } var tfblock *utils.TerraformBlockDetail if !empty { switch { case fset.flagOverwrite: case fset.flagAppend: tfblock, err = utils.InspecTerraformBlock(fset.flagOutputDir) if err != nil { return fmt.Errorf("determine the backend type from the existing files: %v", err) } default: if fset.flagNonInteractive { return fmt.Errorf("the output directory %q is not empty", fset.flagOutputDir) } // Interactive mode fmt.Printf(` The output directory is not empty. Please choose one of actions below: * Press "Y" to proceed that will likely pollute the existing files and cause errors * Press "N" to append new files and add to the existing state instead * Press other keys to quit > `) var ans string // #nosec G104 fmt.Scanf("%s", &ans) switch strings.ToLower(ans) { case "y": case "n": if fset.flagHCLOnly { return fmt.Errorf("`--hcl-only` can only run within an empty directory. Use `-o` to specify an empty directory.") } fset.flagAppend = true tfblock, err = utils.InspecTerraformBlock(fset.flagOutputDir) if err != nil { return fmt.Errorf("determine the backend type from the existing files: %v", err) } default: return fmt.Errorf("the output directory %q is not empty", fset.flagOutputDir) } } } // Deterimine the real backend type to use var existingBackendType string if tfblock != nil { existingBackendType = "local" if tfblock.BackendType != "" { existingBackendType = tfblock.BackendType } } switch { case fset.flagBackendType != "" && existingBackendType != "": if fset.flagBackendType != existingBackendType { return fmt.Errorf("the backend type defined in existing files (%s) are not the same as is specified in the CLI (%s)", existingBackendType, fset.flagBackendType) } case fset.flagBackendType == "" && existingBackendType == "": fset.flagBackendType = "local" case fset.flagBackendType == "" && existingBackendType != "": fset.flagBackendType = existingBackendType case fset.flagBackendType != "" && existingBackendType == "": // do nothing } // Check backend related flags if len(fset.flagBackendConfig.Value()) != 0 { if existingBackendType != "" { return fmt.Errorf("`--backend-config` should not be specified when appending to a workspace that has terraform block already defined") } if fset.flagBackendType == "local" { return fmt.Errorf("`--backend-config` only works for non-local backend") } } if fset.flagBackendType != "local" { if fset.flagHCLOnly { return fmt.Errorf("`--hcl-only` only works for local backend") } } // Determine any existing provider version constraint if not using a dev provider and the provider version not specified. if !fset.flagDevProvider && fset.flagProviderVersion == "" { module, err := tfconfig.LoadModule(fset.flagOutputDir) if err != nil { return fmt.Errorf("loading terraform config: %v", err) } if azurecfg, ok := module.RequiredProviders[fset.flagProviderName]; ok { fset.flagProviderVersion = strings.Join(azurecfg.VersionConstraints, " ") } } // Identify the subscription id, which comes from one of following (starts from the highest priority): // - Command line option // - Env variable: AZTFEXPORT_SUBSCRIPTION_ID // - Env variable: ARM_SUBSCRIPTION_ID // - Output of azure cli, the current active subscription if fset.flagSubscriptionId == "" { var err error fset.flagSubscriptionId, err = subscriptionIdFromCLI() if err != nil { return fmt.Errorf("retrieving subscription id from CLI: %v", err) } } return nil } } type argDesc struct { name string isSet bool } func conflictArgs(argDescs []argDesc) error { var conflictArgs []string for _, desc := range argDescs { if desc.isSet { conflictArgs = append(conflictArgs, fmt.Sprintf("%q", desc.name)) } } if len(conflictArgs) > 1 { return fmt.Errorf("only one of the followings can be specified: %s", strings.Join(conflictArgs, ", ")) } return nil }