From 884c22dd6b69aa68035b85b1c37d464ae2b7fac8 Mon Sep 17 00:00:00 2001 From: magodo Date: Tue, 24 May 2022 16:24:22 +0800 Subject: [PATCH] Improve the CLI: adding long form option and env var source. (#111) --- README.md | 8 +- go.mod | 5 +- go.sum | 10 +- internal/config/config.go | 22 +-- main.go | 364 +++++++++++++++++++++----------------- version.go | 9 + 6 files changed, 242 insertions(+), 176 deletions(-) diff --git a/README.md b/README.md index 353c53e..0d10998 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Although `aztfy` depends on `terraform`, it is not required to have `terraform` Follow the [authentication guide](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs#authenticating-to-azure) from the Terraform AzureRM provider to authenticate to Azure. -Then you can go ahead and run `aztfy [option] `. The tool can run in two modes: interactive mode and batch mode, depending on whether `-b` is specified. +Then you can go ahead and run `aztfy [option] `. The tool can run in two modes: interactive mode and batch mode, depending on whether `--batch`/`-b` is specified. ### Interactive Mode @@ -42,7 +42,7 @@ In interactive mode, `aztfy` list all the resources resides in the specified res In some cases, there are Azure resources that have no corresponding Terraform resource (e.g. due to lacks of Terraform support), or some resource might be created as a side effect of provisioning another resource (e.g. the OS Disk resource is created automatically when provisioning a VM). In these cases, you can skip these resources without typing anything. -> 💡 Option `-m` can be used to specify a resource mapping file, either constructed manually or from other runs of `aztfy` (generated in the output directory with name: _.aztfyResourceMapping.json_). +> 💡 Option `--resource-mapping`/`-m` can be used to specify a resource mapping file, either constructed manually or from other runs of `aztfy` (generated in the output directory with name: _.aztfyResourceMapping.json_). After going through all the resources to be imported, users press `w` to instruct `aztfy` to proceed importing resources into Terraform state and generating the Terraform configuration. @@ -52,7 +52,7 @@ After going through all the resources to be imported, users press `w` to instruc ### Batch Mode -In batch mode, instead of interactively specifying the mapping from Azurem resource id to the Terraform resource address, users are expected to provide that mapping via the resource mapping file (via `-m`), with the following format: +In batch mode, instead of interactively specifying the mapping from Azurem resource id to the Terraform resource address, users are expected to provide that mapping via the resource mapping file, with the following format: ```json { @@ -78,7 +78,7 @@ Then the tool will import each specified resource in the mapping file (if exists Especially if the no resource mapping file is specified, `aztfy` will only import the "recognized" resources for you, based on its limited knowledge on the ARM and Terraform resource mappings. -In the batch import mode, users can further specify the `-k` option to make the tool continue even on hitting import error(s) on any resource. +In the batch import mode, users can further specify the `--continue`/`-k` option to make the tool continue even on hitting import error(s) on any resource. ## Demo diff --git a/go.mod b/go.mod index e3abafb..2d9d0fa 100644 --- a/go.mod +++ b/go.mod @@ -16,21 +16,23 @@ require ( github.com/hashicorp/terraform-exec v0.16.0 github.com/magodo/textinput v0.0.0-20210913072708-7d24f2b4b0c0 github.com/magodo/tfadd v0.7.0 - github.com/meowgorithm/babyenv v1.3.1 github.com/mitchellh/go-wordwrap v1.0.0 github.com/muesli/reflow v0.3.0 github.com/stretchr/testify v1.7.0 github.com/tidwall/gjson v1.14.1 + github.com/urfave/cli/v2 v2.8.0 ) require ( github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0 // indirect github.com/agext/levenshtein v1.2.2 // indirect + github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/charmbracelet/harmonica v0.1.0 // indirect github.com/containerd/console v1.0.2 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.7.0 // indirect github.com/golang-jwt/jwt v3.2.1+incompatible // indirect @@ -65,6 +67,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sahilm/fuzzy v0.1.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect diff --git a/go.sum b/go.sum index 20cc61c..b7312b8 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,8 @@ github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki github.com/andybalholm/crlf v0.0.0-20171020200849-670099aa064f/go.mod h1:k8feO4+kXDxro6ErPXBRTJ/ro2mf0SsFG8s7doP9kJE= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0 h1:R/qAiUxFT3mNgQaNqJe0IVznjKRNm23ohAIh9lgtlzc= +github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0/go.mod h1:v3ZDlfVAL1OrkKHbGSFFK60k0/7hruHPDq2XMs9Gu6U= github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 h1:MzVXffFUye+ZcSR6opIgz9Co7WcDx6ZcY+RjfFHoA0I= @@ -57,6 +59,8 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/console v1.0.2 h1:Pi6D+aZXM+oUw1czuKgH5IJ+y0jhYcwBJfx5/Ghn9dE= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -203,8 +207,6 @@ github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRC github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/meowgorithm/babyenv v1.3.1 h1:18ZEYIgbzoFQfRLF9+lxjRfk/ui6w8U0FWl07CgWvvc= -github.com/meowgorithm/babyenv v1.3.1/go.mod h1:lwNX+J6AGBFqNrMZ2PTLkM6SO+W4X8DOg9zBDO4j3Ig= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -243,6 +245,8 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI= github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= @@ -266,6 +270,8 @@ github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/urfave/cli/v2 v2.8.0 h1:EZsAB20oRW4nHcB99TTL6PrXpBGIEujMEKdjwruY9KQ= +github.com/urfave/cli/v2 v2.8.0/go.mod h1:TYFbtzt/azQoJOrGH5mDfZtS0jIkl/OeFwlRWPR9KRM= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= diff --git a/internal/config/config.go b/internal/config/config.go index eee13a5..4688b87 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -3,15 +3,15 @@ package config import "github.com/Azure/aztfy/internal/resmap" type Config struct { - SubscriptionId string `env:"AZTFY_SUBSCRIPTION_ID" default:""` - ResourceGroupName string // specified via CLI - Logfile string `env:"AZTFY_LOGFILE" default:""` - MockClient bool `env:"AZTFY_MOCK_CLIENT" default:"false"` - OutputDir string // specified via CLI option - ResourceMapping resmap.ResourceMapping // specified via CLI option - ResourceNamePattern string // specified via CLI option - Overwrite bool // specified via CLI option - BatchMode bool // specified via CLI option - BackendType string // specified via CLI option - BackendConfig []string // specified via CLI option + LogPath string + MockClient bool + SubscriptionId string + ResourceMapping resmap.ResourceMapping + ResourceGroupName string + OutputDir string + ResourceNamePattern string + Overwrite bool + BatchMode bool + BackendType string + BackendConfig []string } diff --git a/main.go b/main.go index 87f85fa..3c49dbe 100644 --- a/main.go +++ b/main.go @@ -3,192 +3,240 @@ package main import ( "bytes" "encoding/json" - "errors" "fmt" "io" "log" "os" "os/exec" + "sort" "strconv" "strings" - "flag" - "github.com/Azure/aztfy/internal" "github.com/Azure/aztfy/internal/config" "github.com/Azure/aztfy/internal/ui" azlog "github.com/Azure/azure-sdk-for-go/sdk/azcore/log" - "github.com/meowgorithm/babyenv" -) - -var ( - flagSubscriptionId *string - flagVersion *bool - flagOutputDir *string - flagMappingFile *string - flagContinue *bool - flagBatchMode *bool - flagPattern *string - flagOverwrite *bool - flagBackendType *string + "github.com/urfave/cli/v2" ) -func init() { - flagSubscriptionId = flag.String("s", "", "The subscription id") - flagVersion = flag.Bool("v", false, "Print version") - flagOutputDir = flag.String("o", "", "Specify output dir. Default is the current working directory") - flagMappingFile = flag.String("m", "", "Specify the resource mapping file") - flagContinue = flag.Bool("k", false, "Whether continue on import error (batch mode only)") - flagBatchMode = flag.Bool("b", false, "Batch mode (i.e. Non-interactive mode)") - flagPattern = flag.String("p", "res-", `The pattern of the resource name. The resource name is generated by taking the pattern and adding an auto-incremental integer to the end. If pattern includes a "*", the auto-incremental integer replaces the last "*"`) - flagOverwrite = flag.Bool("f", false, "Whether to overwrite the out dir if it is not empty, use with caution") - flagBackendType = flag.String("backend-type", "local", "The Terraform backend used to store the state") -} - -const usage = `aztfy [option] -` - -func fatal(err error) { - fmt.Fprintln(os.Stderr, "Error: "+err.Error()) - os.Exit(1) -} - -type strSliceFlag struct { - values *[]string -} - -func (o *strSliceFlag) String() string { return "" } -func (o *strSliceFlag) Set(val string) error { - *o.values = append(*o.values, val) - return nil -} - func main() { - flag.Usage = func() { - fmt.Fprintf(flag.CommandLine.Output(), "%s\n", usage) - flag.PrintDefaults() - } + var ( + flagBatchMode bool + flagSubscriptionId string + flagOutputDir string + flagMappingFile string + flagContinue bool + flagPattern string + flagOverwrite bool + flagBackendType string + flagBackendConfig cli.StringSlice + + // hidden flags + hflagLogPath string + hflagMockClient bool + ) + app := &cli.App{ + Name: "aztfy", + Version: getVersion(), + Usage: "Bring existing Azure resources under Terraform's management", + UsageText: "aztfy [option] ", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "batch", + EnvVars: []string{"AZTFY_BATCH"}, + Aliases: []string{"b"}, + Usage: "Batch mode (i.e. Non-interactive mode)", + Destination: &flagBatchMode, + }, + &cli.StringFlag{ + Name: "subscription-id", + // Honor the "ARM_SUBSCRIPTION_ID" as is used by the AzureRM provider, for easier use. + EnvVars: []string{"AZTFY_SUBSCRIPTION_ID", "ARM_SUBSCRIPTION_ID"}, + Aliases: []string{"s"}, + Usage: "The subscription id", + Destination: &flagSubscriptionId, + }, + &cli.StringFlag{ + Name: "output-dir", + EnvVars: []string{"AZTFY_OUTPUT_DIR"}, + Aliases: []string{"o"}, + Usage: "The output directory", + Value: func() string { + dir, _ := os.Getwd() + return dir + }(), + Destination: &flagOutputDir, + }, + &cli.StringFlag{ + Name: "resource-mapping", + EnvVars: []string{"AZTFY_RESOURCE_MAPPING"}, + Aliases: []string{"m"}, + Usage: "The resource mapping file", + Destination: &flagMappingFile, + }, + &cli.BoolFlag{ + Name: "continue", + EnvVars: []string{"AZTFY_CONTINUE"}, + Aliases: []string{"k"}, + Usage: "Whether continue on import error (batch mode only)", + Destination: &flagContinue, + }, + &cli.StringFlag{ + Name: "name-pattern", + EnvVars: []string{"AZTFY_NAME_PATTERN"}, + Aliases: []string{"p"}, + Usage: `The pattern of the resource name. The resource name is generated by taking the pattern and adding an auto-incremental integer to the end. If pattern includes a "*", the auto-incremental integer replaces the last "*"`, + Value: "res-", + Destination: &flagPattern, + }, + &cli.BoolFlag{ + Name: "overwrite", + EnvVars: []string{"AZTFY_OVERWRITE"}, + Aliases: []string{"f"}, + Usage: "Whether to overwrite the output directory if it is not empty (use with caution)", + Destination: &flagOverwrite, + }, + &cli.StringFlag{ + Name: "backend-type", + EnvVars: []string{"AZTFY_BACKEND_TYPE"}, + Usage: "The Terraform backend used to store the state", + Value: "local", + Destination: &flagBackendType, + }, + &cli.StringSliceFlag{ + Name: "backend-config", + EnvVars: []string{"AZTFY_BACKEND_CONFIG"}, + Usage: "The Terraform backend config", + Destination: &flagBackendConfig, + }, + + // Hidden flags + &cli.StringFlag{ + Name: "log-path", + EnvVars: []string{"AZTFY_LOG_PATH"}, + Usage: "The path to store the log", + Hidden: true, + Destination: &hflagLogPath, + }, + + &cli.BoolFlag{ + Name: "mock-client", + EnvVars: []string{"AZTFY_MOCK_CLIENT"}, + Usage: "Whether to mock the client. This is for testing UI", + Hidden: true, + Destination: &hflagMockClient, + }, + }, + Action: func(c *cli.Context) error { + if c.NArg() == 0 { + return fmt.Errorf("No resource group specified") + } + if c.NArg() > 1 { + return fmt.Errorf("More than one resource groups specified") + } + if flagBatchMode && flagMappingFile == "" { + fmt.Println("[WARN]: No resource mapping file specified! Only the recognized resources will be imported.") + } + if flagContinue && !flagBatchMode { + return fmt.Errorf("`--continue` must be used together with `--batch`") + } - var backendConfig []string - flag.Var(&strSliceFlag{ - values: &backendConfig, - }, "backend-config", "The Terraform backend config") + rg := c.Args().First() - flag.Parse() + // Initialize the config + cfg := config.Config{ + LogPath: hflagLogPath, + MockClient: hflagMockClient, + } - if *flagVersion { - if revision != "" { - fmt.Fprintf(flag.CommandLine.Output(), "%s(%s)\n", version, revision) - } else { - fmt.Println(version) - } - os.Exit(0) - } + // The subscription id comes from one of following (starts from the highest priority): + // - Command line option + // - Env variable: AZTFY_SUBSCRIPTION_ID + // - Env variable: ARM_SUBSCRIPTION_ID + // - Output of azure cli, the current active subscription + cfg.SubscriptionId = flagSubscriptionId + if cfg.SubscriptionId == "" { + var stderr bytes.Buffer + var stdout bytes.Buffer + cmd := exec.Command("az", "account", "show", "--query", "id") + cmd.Stderr = &stderr + cmd.Stdout = &stdout + if err := cmd.Run(); err != nil { + err = fmt.Errorf("failed to run azure cli: %v", err) + if stdErrStr := stderr.String(); stdErrStr != "" { + err = fmt.Errorf("%s: %s", err, strings.TrimSpace(stdErrStr)) + } + return err + } + if stdout.String() == "" { + return fmt.Errorf("subscription id is not specified") + } + var err error + cfg.SubscriptionId, err = strconv.Unquote(strings.TrimSpace(stdout.String())) + if err != nil { + return fmt.Errorf("unquoting %s: %v", stdout.String(), err) + } + } - // Flag sanity check - if len(flag.Args()) != 1 { - flag.Usage() - os.Exit(1) - } - if *flagBatchMode && *flagMappingFile == "" { - fmt.Println("[WARN]: No resource mapping file specified! Only the recognized resources will be imported.") - } - if *flagContinue && !*flagBatchMode { - fatal(errors.New("`-k` must be used together with `-q`")) - } + if flagMappingFile != "" { + b, err := os.ReadFile(flagMappingFile) + if err != nil { + return fmt.Errorf("reading mapping file %s: %v", flagMappingFile, err) + } + if err := json.Unmarshal(b, &cfg.ResourceMapping); err != nil { + return fmt.Errorf("unmarshalling the mapping file: %v", err) + } + } - rg := flag.Args()[0] + cfg.ResourceGroupName = rg + cfg.OutputDir = flagOutputDir + cfg.ResourceNamePattern = flagPattern + cfg.Overwrite = flagOverwrite + cfg.BatchMode = flagBatchMode + cfg.BackendType = flagBackendType + cfg.BackendConfig = flagBackendConfig.Value() + + // Initialize logger + log.SetOutput(io.Discard) + if cfg.LogPath != "" { + log.SetPrefix("[aztfy] ") + f, err := os.OpenFile(cfg.LogPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) + if err != nil { + return fmt.Errorf("creating log file %s: %v", cfg.LogPath, err) + } + log.SetOutput(f) - // Initialize the config - var cfg config.Config - if err := babyenv.Parse(&cfg); err != nil { - fatal(err) - } + // Enable the logging for the Azure SDK + os.Setenv("AZURE_SDK_GO_LOGGING", "all") + azlog.SetListener(func(cls azlog.Event, msg string) { + log.Printf("[SDK] %s: %s\n", cls, msg) + }) + } - // The subscription id comes from one of following (starts from the highest priority): - // - Command line option - // - Env variable: AZTFY_SUBSCRIPTION_ID - // - Env variable: ARM_SUBSCRIPTION_ID - // - Output of azure cli, the current active subscription - if *flagSubscriptionId != "" { - cfg.SubscriptionId = *flagSubscriptionId - } - if cfg.SubscriptionId == "" { - // Honor the ARM_SUBSCRIPTION_ID as the AzureRM provider does. - if v := os.Getenv("ARM_SUBSCRIPTION_ID"); v != "" { - cfg.SubscriptionId = v - } else { - var stderr bytes.Buffer - var stdout bytes.Buffer - cmd := exec.Command("az", "account", "show", "--query", "id") - cmd.Stderr = &stderr - cmd.Stdout = &stdout - if err := cmd.Run(); err != nil { - err = fmt.Errorf("failed to run azure cli: %v", err) - if stdErrStr := stderr.String(); stdErrStr != "" { - err = fmt.Errorf("%s: %s", err, strings.TrimSpace(stdErrStr)) + // Run in batch mode + if cfg.BatchMode { + if err := internal.BatchImport(cfg, flagContinue); err != nil { + return err } - fatal(err) + return nil } - if stdout.String() == "" { - fatal(fmt.Errorf("subscription id is not specified")) - } - subid, err := strconv.Unquote(strings.TrimSpace(stdout.String())) + + // Run in interactive mode + prog, err := ui.NewProgram(cfg) if err != nil { - fatal(fmt.Errorf("unquoting %s: %v", stdout.String(), err)) + return err } - cfg.SubscriptionId = subid - } - } - - if *flagMappingFile != "" { - b, err := os.ReadFile(*flagMappingFile) - if err != nil { - fatal(fmt.Errorf("reading mapping file %s: %v", *flagMappingFile, err)) - } - if err := json.Unmarshal(b, &cfg.ResourceMapping); err != nil { - fatal(fmt.Errorf("unmarshalling the mapping file: %v", err)) - } - } - - cfg.ResourceGroupName = rg - cfg.ResourceNamePattern = *flagPattern - cfg.OutputDir = *flagOutputDir - cfg.Overwrite = *flagOverwrite - cfg.BatchMode = *flagBatchMode - cfg.BackendType = *flagBackendType - cfg.BackendConfig = backendConfig - - // Initialize logger - log.SetOutput(io.Discard) - if cfg.Logfile != "" { - log.SetPrefix("[aztfy] ") - f, err := os.OpenFile(cfg.Logfile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) - if err != nil { - fatal(fmt.Errorf("creating log file %s: %v", cfg.Logfile, err)) - } - log.SetOutput(f) - - // Enable the logging for the Azure SDK - os.Setenv("AZURE_SDK_GO_LOGGING", "all") - azlog.SetListener(func(cls azlog.Event, msg string) { - log.Printf("[SDK] %s: %s\n", cls, msg) - }) + if err := prog.Start(); err != nil { + return err + } + return nil + }, } - if cfg.BatchMode { - if err := internal.BatchImport(cfg, *flagContinue); err != nil { - fatal(err) - } - return - } + sort.Sort(cli.FlagsByName(app.Flags)) - prog, err := ui.NewProgram(cfg) - if err != nil { - fatal(err) - } - - if err := prog.Start(); err != nil { - fatal(err) + if err := app.Run(os.Args); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v", err) + os.Exit(1) } } diff --git a/version.go b/version.go index 4123d04..912a55d 100644 --- a/version.go +++ b/version.go @@ -1,5 +1,7 @@ package main +import "fmt" + // To set this from outside, use go build -ldflags "-X 'main.version=$(VERSION)'" var version string = "dev" @@ -7,3 +9,10 @@ var version string = "dev" // This value is extracted by git command when building. // To set this from outside, use go build -ldflags "-X 'main.revision=$(REVISION)'" var revision string + +func getVersion() string { + if revision != "" { + return fmt.Sprintf("%s(%s)\n", version, revision) + } + return version +}