зеркало из https://github.com/Azure/aztfexport.git
472 строки
14 KiB
Go
472 строки
14 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/Azure/aztfexport/internal/cfgfile"
|
|
"github.com/Azure/aztfexport/internal/log"
|
|
"github.com/Azure/aztfexport/pkg/config"
|
|
"github.com/Azure/aztfexport/pkg/telemetry"
|
|
"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/gofrs/uuid"
|
|
"github.com/urfave/cli/v2"
|
|
)
|
|
|
|
var flagset FlagSet
|
|
|
|
type FlagSet struct {
|
|
// common flags
|
|
flagSubscriptionId string
|
|
flagOutputDir string
|
|
flagOverwrite bool
|
|
flagAppend bool
|
|
flagDevProvider bool
|
|
flagProviderVersion string
|
|
flagProviderName string
|
|
flagBackendType string
|
|
flagBackendConfig cli.StringSlice
|
|
flagFullConfig bool
|
|
flagMaskSensitive bool
|
|
flagParallelism int
|
|
flagContinue bool
|
|
flagNonInteractive bool
|
|
flagPlainUI bool
|
|
flagGenerateMappingFile bool
|
|
flagHCLOnly bool
|
|
flagModulePath string
|
|
flagGenerateImportBlock bool
|
|
flagLogPath string
|
|
flagLogLevel string
|
|
|
|
// common flags (auth)
|
|
flagEnv string
|
|
flagTenantId string
|
|
flagAuxiliaryTenantIds cli.StringSlice
|
|
flagClientId string
|
|
flagClientIdFilePath string
|
|
flagClientCertificate string
|
|
flagClientCertificatePath string
|
|
flagClientCertificatePassword string
|
|
flagClientSecret string
|
|
flagClientSecretFilePath string
|
|
flagOIDCRequestToken string
|
|
flagOIDCRequestURL string
|
|
flagOIDCTokenFilePath string
|
|
flagOIDCToken string
|
|
flagUseManagedIdentityCred bool
|
|
flagUseAzureCLICred bool
|
|
flagUseOIDCCred bool
|
|
|
|
// common flags (hidden)
|
|
hflagMockClient bool
|
|
hflagProfile string
|
|
hflagTFClientPluginPath string
|
|
|
|
// Subcommand specific flags
|
|
//
|
|
// res:
|
|
// flagResName (for single resource)
|
|
// flagResType (for single resource)
|
|
// flagPattern (for multi resources)
|
|
//
|
|
// rg:
|
|
// flagPattern
|
|
// flagIncludeRoleAssignment
|
|
//
|
|
// query:
|
|
// flagPattern
|
|
// flagRecursive
|
|
// flagIncludeRoleAssignment
|
|
// flagIncludeResourceGroup
|
|
// flagARGTable
|
|
// flagARGAuthorizationScopeFilter
|
|
flagPattern string
|
|
flagRecursive bool
|
|
flagResName string
|
|
flagResType string
|
|
flagIncludeRoleAssignment bool
|
|
flagIncludeResourceGroup bool
|
|
flagARGTable string
|
|
flagARGAuthorizationScopeFilter string
|
|
}
|
|
|
|
type Mode string
|
|
|
|
const (
|
|
ModeResource Mode = "resource"
|
|
ModeResourceGroup Mode = "resource-group"
|
|
ModeQuery Mode = "query"
|
|
ModeMappingFile Mode = "mapping-file"
|
|
)
|
|
|
|
// DescribeCLI construct a description of the CLI based on the flag set and the specified mode.
|
|
// The main reason is to record the usage of some "interesting" options in the telemetry.
|
|
// Note that only insensitive values are recorded (i.e. subscription id, resource id, etc are not recorded)
|
|
func (flag FlagSet) DescribeCLI(mode Mode) string {
|
|
args := []string{string(mode)}
|
|
|
|
// The following flags are skipped eiter not interesting, or might contain sensitive info:
|
|
// - flagOutputDir
|
|
// - flagDevProvider
|
|
// - flagBackendConfig
|
|
// - all hflags
|
|
|
|
if flag.flagSubscriptionId != "" {
|
|
args = append(args, "--subscription=*")
|
|
}
|
|
if flag.flagOverwrite {
|
|
args = append(args, "--overwrite=true")
|
|
}
|
|
if flag.flagAppend {
|
|
args = append(args, "--append=true")
|
|
}
|
|
if flag.flagProviderVersion != "" {
|
|
args = append(args, fmt.Sprintf(`-provider-version=%s`, flag.flagProviderVersion))
|
|
}
|
|
if flag.flagProviderName != "" {
|
|
args = append(args, fmt.Sprintf(`-provider-name=%s`, flag.flagProviderName))
|
|
}
|
|
if flag.flagBackendType != "" {
|
|
args = append(args, "--backend-type="+flag.flagBackendType)
|
|
}
|
|
if flag.flagFullConfig {
|
|
args = append(args, "--full-properties=true")
|
|
}
|
|
if flag.flagMaskSensitive {
|
|
args = append(args, "--mask-sensitive=true")
|
|
}
|
|
if flag.flagParallelism != 0 {
|
|
args = append(args, fmt.Sprintf("--parallelism=%d", flag.flagParallelism))
|
|
}
|
|
if flag.flagNonInteractive {
|
|
args = append(args, "--non-interactive=true")
|
|
}
|
|
if flag.flagPlainUI {
|
|
args = append(args, "--plain-ui=true")
|
|
}
|
|
if flag.flagContinue {
|
|
args = append(args, "--continue=true")
|
|
}
|
|
if flag.flagGenerateMappingFile {
|
|
args = append(args, "--generate-mapping-file=true")
|
|
}
|
|
if flag.flagHCLOnly {
|
|
args = append(args, "--hcl-only=true")
|
|
}
|
|
if flag.flagModulePath != "" {
|
|
args = append(args, "--module-path="+flag.flagModulePath)
|
|
}
|
|
if !flag.flagGenerateImportBlock {
|
|
args = append(args, "--generate-import-block=true")
|
|
}
|
|
|
|
if flag.flagEnv != "" {
|
|
args = append(args, "--env="+flag.flagEnv)
|
|
}
|
|
if flag.flagTenantId != "" {
|
|
args = append(args, "--tenant-id=*")
|
|
}
|
|
if v := flag.flagAuxiliaryTenantIds.Value(); len(v) != 0 {
|
|
args = append(args, fmt.Sprintf("--tenant-id=[%d]", len(v)))
|
|
}
|
|
if flag.flagClientId != "" {
|
|
args = append(args, "--client-id=*")
|
|
}
|
|
if flag.flagClientIdFilePath != "" {
|
|
args = append(args, "--client-id-file-path="+flag.flagClientIdFilePath)
|
|
}
|
|
if flag.flagClientCertificate != "" {
|
|
args = append(args, "--client-certificate=*")
|
|
}
|
|
if flag.flagClientCertificatePath != "" {
|
|
args = append(args, "--client-certificate-path="+flag.flagClientCertificatePath)
|
|
}
|
|
if flag.flagClientCertificatePassword != "" {
|
|
args = append(args, "--client-certificate-password=*")
|
|
}
|
|
if flag.flagClientSecret != "" {
|
|
args = append(args, "--client-secret=***")
|
|
}
|
|
if flag.flagClientSecretFilePath != "" {
|
|
args = append(args, "--client-secret-file-path="+flag.flagClientSecretFilePath)
|
|
}
|
|
if flag.flagOIDCRequestToken != "" {
|
|
args = append(args, "--oidc-request-token=*")
|
|
}
|
|
if flag.flagOIDCRequestURL != "" {
|
|
args = append(args, "--oidc-request-url="+flag.flagOIDCRequestURL)
|
|
}
|
|
if flag.flagOIDCTokenFilePath != "" {
|
|
args = append(args, "--oidc-token-file-path="+flag.flagOIDCTokenFilePath)
|
|
}
|
|
if flag.flagOIDCToken != "" {
|
|
args = append(args, "--oidc-token=*")
|
|
}
|
|
if flag.flagUseManagedIdentityCred {
|
|
args = append(args, "--use-managed-identity-cred=true")
|
|
}
|
|
if flag.flagUseAzureCLICred {
|
|
args = append(args, "--use-azure-cli-cred=true")
|
|
}
|
|
if flag.flagUseOIDCCred {
|
|
args = append(args, "--use-oidc-cred=true")
|
|
}
|
|
|
|
if flag.hflagTFClientPluginPath != "" {
|
|
args = append(args, "--tfclient-plugin-path="+flag.hflagTFClientPluginPath)
|
|
}
|
|
switch mode {
|
|
case ModeResource:
|
|
if flag.flagResName != "" {
|
|
args = append(args, "--name="+flag.flagResName)
|
|
}
|
|
if flag.flagResType != "" {
|
|
args = append(args, "--type="+flag.flagResType)
|
|
}
|
|
if flag.flagPattern != "" {
|
|
args = append(args, "--name-pattern="+flag.flagPattern)
|
|
}
|
|
case ModeResourceGroup:
|
|
if flag.flagPattern != "" {
|
|
args = append(args, "--name-pattern="+flag.flagPattern)
|
|
}
|
|
if flag.flagIncludeRoleAssignment {
|
|
args = append(args, "--include-role-assignment=true")
|
|
}
|
|
case ModeQuery:
|
|
if flag.flagPattern != "" {
|
|
args = append(args, "--name-pattern="+flag.flagPattern)
|
|
}
|
|
if flag.flagRecursive {
|
|
args = append(args, "--recursive=true")
|
|
}
|
|
if flag.flagIncludeRoleAssignment {
|
|
args = append(args, "--include-role-assignment=true")
|
|
}
|
|
if flag.flagIncludeResourceGroup {
|
|
args = append(args, "--include-resource-group=true")
|
|
}
|
|
if flag.flagARGTable != "" {
|
|
args = append(args, "--arg-table="+flag.flagARGTable)
|
|
}
|
|
if flag.flagARGAuthorizationScopeFilter != "" {
|
|
args = append(args, "--arg-authorization-scope-filter="+flag.flagARGAuthorizationScopeFilter)
|
|
}
|
|
}
|
|
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)
|
|
}
|
|
|
|
func (f FlagSet) buildAuthConfig() (*config.AuthConfig, error) {
|
|
clientId := f.flagClientId
|
|
if path := f.flagClientIdFilePath; path != "" {
|
|
// #nosec G304
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading Client ID from file %q: %v", path, err)
|
|
}
|
|
clientId = string(b)
|
|
}
|
|
|
|
clientSecret := f.flagClientSecret
|
|
if path := f.flagClientSecretFilePath; path != "" {
|
|
// #nosec G304
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading Client secret from file %q: %v", path, err)
|
|
}
|
|
clientSecret = string(b)
|
|
}
|
|
|
|
clientCertEncoded := f.flagClientCertificate
|
|
if path := f.flagClientCertificatePath; path != "" {
|
|
// #nosec G304
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading Client certificate from file %q: %v", path, err)
|
|
}
|
|
clientCertEncoded = base64.StdEncoding.EncodeToString(b)
|
|
}
|
|
|
|
oidcToken := f.flagOIDCToken
|
|
if path := f.flagOIDCTokenFilePath; path != "" {
|
|
// #nosec G304
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading OIDC token from file %q: %v", path, err)
|
|
}
|
|
oidcToken = strings.TrimSpace(string(b))
|
|
}
|
|
|
|
c := config.AuthConfig{
|
|
Environment: f.flagEnv,
|
|
TenantID: f.flagTenantId,
|
|
AuxiliaryTenantIDs: f.flagAuxiliaryTenantIds.Value(),
|
|
ClientID: clientId,
|
|
ClientSecret: clientSecret,
|
|
ClientCertificateEncoded: clientCertEncoded,
|
|
ClientCertificatePassword: f.flagClientCertificatePassword,
|
|
OIDCTokenRequestToken: f.flagOIDCRequestToken,
|
|
OIDCTokenRequestURL: f.flagOIDCRequestURL,
|
|
OIDCAssertionToken: oidcToken,
|
|
UseAzureCLI: f.flagUseAzureCLICred,
|
|
UseManagedIdentity: f.flagUseManagedIdentityCred,
|
|
UseOIDC: f.flagUseOIDCCred,
|
|
}
|
|
|
|
return &c, nil
|
|
}
|
|
|
|
// BuildCommonConfig builds the CommonConfig from the FlagSet, except the TFClient, which is built afterwards as it requires a logger.
|
|
func (f FlagSet) BuildCommonConfig() (config.CommonConfig, error) {
|
|
// Logger is only enabled when the log path is specified.
|
|
// This is because either interactive/non-interactive mode controls the terminal rendering,
|
|
// logging to stdout/stderr will impact the rendering.
|
|
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
|
if path := f.flagLogPath; path != "" {
|
|
level, err := logLevel(f.flagLogLevel)
|
|
if err != nil {
|
|
return config.CommonConfig{}, err
|
|
}
|
|
|
|
// #nosec G304
|
|
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
|
|
if err != nil {
|
|
return config.CommonConfig{}, fmt.Errorf("creating log file %s: %v", path, err)
|
|
}
|
|
|
|
logger = slog.New(slog.NewTextHandler(f, &slog.HandlerOptions{Level: level}))
|
|
|
|
// Enable log for azure sdk
|
|
os.Setenv("AZURE_SDK_GO_LOGGING", "all") // #nosec G104
|
|
azlog.SetListener(func(cls azlog.Event, msg string) {
|
|
logger.Log(context.Background(), log.LevelTrace, msg, "event", cls)
|
|
})
|
|
}
|
|
|
|
authConfig, err := f.buildAuthConfig()
|
|
if err != nil {
|
|
return config.CommonConfig{}, err
|
|
}
|
|
|
|
var cloudCfg cloud.Configuration
|
|
switch env := f.flagEnv; strings.ToLower(env) {
|
|
case "public":
|
|
cloudCfg = cloud.AzurePublic
|
|
case "usgovernment":
|
|
cloudCfg = cloud.AzureGovernment
|
|
case "china":
|
|
cloudCfg = cloud.AzureChina
|
|
default:
|
|
return config.CommonConfig{}, fmt.Errorf("unknown environment specified: %q", env)
|
|
}
|
|
|
|
clientOpt := arm.ClientOptions{
|
|
ClientOptions: policy.ClientOptions{
|
|
Cloud: cloudCfg,
|
|
Telemetry: policy.TelemetryOptions{
|
|
ApplicationID: fmt.Sprintf("aztfexport(%s)", f.flagProviderName),
|
|
Disabled: false,
|
|
},
|
|
Logging: policy.LogOptions{
|
|
IncludeBody: true,
|
|
},
|
|
},
|
|
AuxiliaryTenants: authConfig.AuxiliaryTenantIDs,
|
|
DisableRPRegistration: true,
|
|
}
|
|
|
|
cred, err := NewDefaultAzureCredential(*logger, &DefaultAzureCredentialOptions{
|
|
AuthConfig: *authConfig,
|
|
ClientOptions: clientOpt.ClientOptions,
|
|
DisableInstanceDiscovery: false,
|
|
SendCertificateChain: false,
|
|
})
|
|
if err != nil {
|
|
return config.CommonConfig{}, fmt.Errorf("failed to new credential: %v", err)
|
|
}
|
|
|
|
cfg := config.CommonConfig{
|
|
Logger: logger,
|
|
AuthConfig: *authConfig,
|
|
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,
|
|
MaskSensitive: f.flagMaskSensitive,
|
|
Parallelism: f.flagParallelism,
|
|
HCLOnly: f.flagHCLOnly,
|
|
ModulePath: f.flagModulePath,
|
|
GenerateImportBlock: f.flagGenerateImportBlock,
|
|
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",
|
|
}
|
|
}
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
func logLevel(level string) (slog.Level, error) {
|
|
switch strings.ToUpper(level) {
|
|
case "ERROR":
|
|
return slog.LevelError, nil
|
|
case "WARN":
|
|
return slog.LevelWarn, nil
|
|
case "INFO":
|
|
return slog.LevelInfo, nil
|
|
case "DEBUG":
|
|
return slog.LevelDebug, nil
|
|
case "TRACE":
|
|
return log.LevelTrace, nil
|
|
default:
|
|
return slog.Level(0), fmt.Errorf("unknown log level: %s", level)
|
|
}
|
|
}
|