diff --git a/internal/config/config.go b/internal/config/config.go index 1f4063e..14820f9 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -6,6 +6,7 @@ type Config struct { ResourceGroupName string // specified via CLI Logfile string `env:"AZTFY_LOGFILE" default:""` Debug bool `env:"AZTFY_DEBUG" default:"false"` + MockClient bool `env:"AZTFY_MOCK_CLIENT" default:"false"` } func NewConfig(rg string) (*Config, error) { diff --git a/internal/meta/meta.go b/internal/meta/meta.go index 799c904..287aebf 100644 --- a/internal/meta/meta.go +++ b/internal/meta/meta.go @@ -1,320 +1,20 @@ package meta -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "os" - "path" - "path/filepath" - "strings" +import "github.com/magodo/aztfy/internal/config" - "github.com/magodo/aztfy/internal/armtemplate" - "github.com/magodo/aztfy/schema" - - "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2020-06-01/resources" - "github.com/hashicorp/go-version" - "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/hcl/v2/hclwrite" - "github.com/hashicorp/terraform-exec/tfexec" -) - -// The minimun required terraform version that has the `terraform add` command. -var minRequiredTFVersion = version.Must(version.NewSemver("1.1.0-alpha20210811")) - -type Meta struct { - subscriptionId string - resourceGroup string - workspace string - tf *tfexec.Terraform - auth *Authorizer - armTemplate armtemplate.Template -} - -func NewMeta(ctx context.Context, rg string) (*Meta, error) { - // Initialize the workspace - cachedir, err := os.UserCacheDir() - if err != nil { - return nil, fmt.Errorf("error finding the user cache directory: %w", err) - } - - // Initialize the workspace - rootDir := filepath.Join(cachedir, "aztfy") - if err := os.MkdirAll(rootDir, 0755); err != nil { - return nil, fmt.Errorf("creating workspace root %q: %w", rootDir, err) - } - - tfDir := filepath.Join(rootDir, "terraform") - if err := os.MkdirAll(tfDir, 0755); err != nil { - return nil, fmt.Errorf("creating terraform cache dir %q: %w", tfDir, err) - } - - wsp := filepath.Join(rootDir, rg) - if err := os.RemoveAll(wsp); err != nil { - return nil, fmt.Errorf("removing existing workspace %q: %w", wsp, err) - } - if err := os.MkdirAll(wsp, 0755); err != nil { - return nil, fmt.Errorf("creating workspace %q: %w", wsp, err) - } - - // Authentication - auth, err := NewAuthorizer() - if err != nil { - return nil, fmt.Errorf("building authorizer: %w", err) - } - - // Initialize the Terraform - execPath, err := FindTerraform(ctx, tfDir, minRequiredTFVersion) - if err != nil { - return nil, fmt.Errorf("error finding a terraform exectuable: %w", err) - } - - tf, err := tfexec.NewTerraform(wsp, execPath) - if err != nil { - return nil, fmt.Errorf("error running NewTerraform: %w", err) - } - - return &Meta{ - subscriptionId: auth.Config.SubscriptionID, - resourceGroup: rg, - workspace: wsp, - tf: tf, - auth: auth, - }, nil -} - -func providerConfig() string { - return fmt.Sprintf(`terraform { - required_providers { - azurerm = { - source = "hashicorp/azurerm" - version = "%s" - } - } -} - -provider "azurerm" { - features {} -} -`, schema.ProviderVersion) -} - -func (meta Meta) ResourceGroupName() string { - return meta.resourceGroup -} - -func (meta Meta) Workspace() string { - return meta.workspace +type Meta interface { + Init() error + ResourceGroupName() string + Workspace() string + ListResource() ImportList + CleanTFState() + Import(item ImportItem) error + GenerateCfg(l ImportList) error } -func (meta *Meta) Init(ctx context.Context) error { - if err := meta.initProvider(ctx); err != nil { - return err - } - if err := meta.exportArmTemplate(ctx); err != nil { - return err - } - return nil -} - -func (meta Meta) ListResource() ImportList { - var ids []string - for _, res := range meta.armTemplate.Resources { - ids = append(ids, res.ID(meta.subscriptionId, meta.resourceGroup)) - } - ids = append(ids, armtemplate.ResourceGroupId.ID(meta.subscriptionId, meta.resourceGroup)) - - l := make(ImportList, 0, len(ids)) - for _, id := range ids { - l = append(l, ImportItem{ - ResourceID: id, - }) - } - return l -} - -func (meta *Meta) CleanTFState() { - os.Remove(path.Join(meta.Workspace(), "terraform.tfstate")) -} - -func (meta *Meta) Import(ctx context.Context, item ImportItem) error { - // Generate a temp Terraform config to include the empty template for each resource. - // This is required for the following importing. - cfgFile := filepath.Join(meta.workspace, "main.tf") - tpl, err := meta.tf.Add(ctx, item.TFAddr()) - if err != nil { - return fmt.Errorf("generating resource template for %s: %w", item.TFAddr(), err) - } - if err := os.WriteFile(cfgFile, []byte(tpl), 0644); err != nil { - return fmt.Errorf("generating resource template file: %w", err) - } - defer os.Remove(cfgFile) - - // Import resources - return meta.tf.Import(ctx, item.TFAddr(), item.ResourceID) -} - -func (meta Meta) GenerateCfg(ctx context.Context, l ImportList) error { - cfginfos, err := meta.stateToConfig(ctx, l) - if err != nil { - return fmt.Errorf("converting from state to configurations: %w", err) - } - cfginfos, err = meta.resolveDependency(ctx, cfginfos) - if err != nil { - return fmt.Errorf("resolving cross resource dependencies: %w", err) +func NewMeta(cfg config.Config) (Meta, error) { + if cfg.MockClient { + return newMetaDummy(cfg.ResourceGroupName) } - return meta.generateConfig(cfginfos) -} - -func (meta *Meta) initProvider(ctx context.Context) error { - cfgFile := filepath.Join(meta.workspace, "provider.tf") - - // Always use the latest provider version here, as this is a one shot tool, which should guarantees to work with the latest version. - if err := os.WriteFile(cfgFile, []byte(providerConfig()), 0644); err != nil { - return fmt.Errorf("error creating provider config: %w", err) - } - - if err := meta.tf.Init(ctx); err != nil { - return fmt.Errorf("error running terraform init: %s", err) - } - - return nil -} - -func (meta *Meta) exportArmTemplate(ctx context.Context) error { - client := meta.auth.NewResourceGroupClient() - - exportOpt := "SkipAllParameterization" - future, err := client.ExportTemplate(ctx, meta.resourceGroup, resources.ExportTemplateRequest{ - ResourcesProperty: &[]string{"*"}, - Options: &exportOpt, - }) - if err != nil { - return fmt.Errorf("exporting arm template of resource group %s: %w", meta.resourceGroup, err) - } - - if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { - return fmt.Errorf("waiting for exporting arm template of resource group %s: %w", meta.resourceGroup, err) - } - - result, err := future.Result(client) - if err != nil { - return fmt.Errorf("getting the arm template of resource group %s: %w", meta.resourceGroup, err) - } - - // The response has been read into the ".Template" field as an interface, and the reader has been drained. - // As we have defined some (useful) types for the arm template, so we will do a json marshal then unmarshal here - // to convert the ".Template" (interface{}) into that artificial type. - raw, err := json.Marshal(result.Template) - if err != nil { - return fmt.Errorf("marshalling the template: %w", err) - } - if err := json.Unmarshal(raw, &meta.armTemplate); err != nil { - return fmt.Errorf("unmarshalling the template: %w", err) - } - - return nil -} - -func (meta Meta) stateToConfig(ctx context.Context, list ImportList) (ConfigInfos, error) { - out := ConfigInfos{} - - for _, item := range list.Imported() { - tpl, err := meta.tf.Add(ctx, item.TFAddr(), tfexec.FromState(true)) - if err != nil { - return nil, fmt.Errorf("converting terraform state to config for resource %s: %w", item.TFAddr(), err) - } - f, diag := hclwrite.ParseConfig([]byte(tpl), "", hcl.InitialPos) - if diag.HasErrors() { - return nil, fmt.Errorf("parsing the HCL generated by \"terraform add\" of %s: %s", item.TFAddr(), diag.Error()) - } - - rb := f.Body().Blocks()[0].Body() - sch := schema.ProviderSchemaInfo.ResourceSchemas[item.TFResourceType] - if err := tuneHCLSchemaForResource(rb, sch); err != nil { - return nil, err - } - - out = append(out, ConfigInfo{ - ImportItem: item, - hcl: f, - }) - } - - return out, nil -} - -func (meta Meta) resolveDependency(ctx context.Context, configs ConfigInfos) (ConfigInfos, error) { - depInfo := meta.armTemplate.DependencyInfo() - - configSet := map[armtemplate.ResourceId]ConfigInfo{} - for _, cfg := range configs { - armId, err := armtemplate.NewResourceId(cfg.ResourceID) - if err != nil { - return nil, fmt.Errorf("new arm tempalte resource id from azure resource id: %w", err) - } - configSet[*armId] = cfg - } - - // Iterate each config to add dependency by querying the dependency info from arm template. - var out ConfigInfos - for armId, cfg := range configSet { - if armId == armtemplate.ResourceGroupId { - out = append(out, cfg) - continue - } - // This should never happen as we always ensure there is at least one implicit dependency on the resource group for each resource. - if _, ok := depInfo[armId]; !ok { - return nil, fmt.Errorf("can't find resource %q in the arm template", armId.ID(meta.subscriptionId, meta.resourceGroup)) - } - - if err := meta.hclBlockAppendDependency(cfg.hcl.Body().Blocks()[0].Body(), depInfo[armId], configSet); err != nil { - return nil, err - } - out = append(out, cfg) - } - - return out, nil -} - -func (meta Meta) hclBlockAppendDependency(body *hclwrite.Body, armIds []armtemplate.ResourceId, cfgset map[armtemplate.ResourceId]ConfigInfo) error { - dependencies := []string{} - for _, armid := range armIds { - cfg, ok := cfgset[armid] - if !ok { - dependencies = append(dependencies, fmt.Sprintf("# Depending on %q, which is not imported by Terraform.", armid.ID(meta.subscriptionId, meta.resourceGroup))) - continue - } - dependencies = append(dependencies, cfg.TFAddr()+",") - } - if len(dependencies) > 0 { - src := []byte("depends_on = [\n" + strings.Join(dependencies, "\n") + "\n]") - expr, diags := hclwrite.ParseConfig(src, "generate_depends_on", hcl.InitialPos) - if diags.HasErrors() { - return fmt.Errorf(`building "depends_on" attribute: %s`, diags.Error()) - } - - body.SetAttributeRaw("depends_on", expr.Body().GetAttribute("depends_on").Expr().BuildTokens(nil)) - } - - return nil -} - -func (meta Meta) generateConfig(cfgs ConfigInfos) error { - cfgFile := filepath.Join(meta.workspace, "main.tf") - buf := bytes.NewBuffer([]byte{}) - for i, cfg := range cfgs { - if _, err := cfg.DumpHCL(buf); err != nil { - return err - } - if i != len(cfgs)-1 { - buf.Write([]byte("\n")) - } - } - if err := os.WriteFile(cfgFile, buf.Bytes(), 0644); err != nil { - return fmt.Errorf("generating main configuration file: %w", err) - } - - return nil + return newMetaImpl(cfg.ResourceGroupName) } diff --git a/internal/meta/meta_dummy.go b/internal/meta/meta_dummy.go new file mode 100644 index 0000000..988c8e2 --- /dev/null +++ b/internal/meta/meta_dummy.go @@ -0,0 +1,59 @@ +package meta + +import "time" + +type MetaDummy struct { + rg string +} + +func newMetaDummy(rg string) (Meta, error) { + return MetaDummy{rg: rg}, nil +} + +func (m MetaDummy) Init() error { + time.Sleep(500 * time.Millisecond) + return nil +} + +func (m MetaDummy) ResourceGroupName() string { + return m.rg +} + +func (m MetaDummy) Workspace() string { + return "example-workspace" +} + +func (m MetaDummy) ListResource() ImportList { + time.Sleep(500 * time.Millisecond) + return ImportList{ + ImportItem{ + ResourceID: "/subscriptions/0000000-0000-0000-0000-00000000000/resourceGroups/example-rg/providers/Microsoft.Network/virtualNetworks/example-network", + }, + ImportItem{ + ResourceID: "/subscriptions/0000000-0000-0000-0000-00000000000/resourceGroups/example-rg/providers/Microsoft.Compute/virtualMachines/example-machine", + }, + ImportItem{ + ResourceID: "/subscriptions/0000000-0000-0000-0000-00000000000/resourceGroups/example-rg/providers/Microsoft.Network/networkInterfaces/example-nic", + }, + ImportItem{ + ResourceID: "/subscriptions/0000000-0000-0000-0000-00000000000/resourceGroups/example-rg/providers/virtualNetworks/example-network/subnets/internal", + }, + ImportItem{ + ResourceID: "/subscriptions/0000000-0000-0000-0000-00000000000/resourceGroups/example-rg", + }, + } +} + +func (m MetaDummy) CleanTFState() { + return +} + +func (m MetaDummy) Import(item ImportItem) error { + time.Sleep(500 * time.Millisecond) + return nil +} + +func (m MetaDummy) GenerateCfg(l ImportList) error { + time.Sleep(500 * time.Millisecond) + return nil +} diff --git a/internal/meta/meta_impl.go b/internal/meta/meta_impl.go new file mode 100644 index 0000000..8744f58 --- /dev/null +++ b/internal/meta/meta_impl.go @@ -0,0 +1,327 @@ +package meta + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "os" + "path" + "path/filepath" + "strings" + + "github.com/magodo/aztfy/internal/armtemplate" + "github.com/magodo/aztfy/schema" + + "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2020-06-01/resources" + "github.com/hashicorp/go-version" + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclwrite" + "github.com/hashicorp/terraform-exec/tfexec" +) + +// The minimun required terraform version that has the `terraform add` command. +var minRequiredTFVersion = version.Must(version.NewSemver("1.1.0-alpha20210811")) + +type MetaImpl struct { + subscriptionId string + resourceGroup string + workspace string + tf *tfexec.Terraform + auth *Authorizer + armTemplate armtemplate.Template +} + +func newMetaImpl(rg string) (Meta, error) { + ctx := context.TODO() + + // Initialize the workspace + cachedir, err := os.UserCacheDir() + if err != nil { + return nil, fmt.Errorf("error finding the user cache directory: %w", err) + } + + // Initialize the workspace + rootDir := filepath.Join(cachedir, "aztfy") + if err := os.MkdirAll(rootDir, 0755); err != nil { + return nil, fmt.Errorf("creating workspace root %q: %w", rootDir, err) + } + + tfDir := filepath.Join(rootDir, "terraform") + if err := os.MkdirAll(tfDir, 0755); err != nil { + return nil, fmt.Errorf("creating terraform cache dir %q: %w", tfDir, err) + } + + wsp := filepath.Join(rootDir, rg) + if err := os.RemoveAll(wsp); err != nil { + return nil, fmt.Errorf("removing existing workspace %q: %w", wsp, err) + } + if err := os.MkdirAll(wsp, 0755); err != nil { + return nil, fmt.Errorf("creating workspace %q: %w", wsp, err) + } + + // Authentication + auth, err := NewAuthorizer() + if err != nil { + return nil, fmt.Errorf("building authorizer: %w", err) + } + + // Initialize the Terraform + execPath, err := FindTerraform(ctx, tfDir, minRequiredTFVersion) + if err != nil { + return nil, fmt.Errorf("error finding a terraform exectuable: %w", err) + } + + tf, err := tfexec.NewTerraform(wsp, execPath) + if err != nil { + return nil, fmt.Errorf("error running NewTerraform: %w", err) + } + + return &MetaImpl{ + subscriptionId: auth.Config.SubscriptionID, + resourceGroup: rg, + workspace: wsp, + tf: tf, + auth: auth, + }, nil +} + +func (meta MetaImpl) ResourceGroupName() string { + return meta.resourceGroup +} + +func (meta MetaImpl) Workspace() string { + return meta.workspace +} + +func (meta *MetaImpl) Init() error { + ctx := context.TODO() + if err := meta.initProvider(ctx); err != nil { + return err + } + if err := meta.exportArmTemplate(ctx); err != nil { + return err + } + return nil +} + +func (meta MetaImpl) ListResource() ImportList { + var ids []string + for _, res := range meta.armTemplate.Resources { + ids = append(ids, res.ID(meta.subscriptionId, meta.resourceGroup)) + } + ids = append(ids, armtemplate.ResourceGroupId.ID(meta.subscriptionId, meta.resourceGroup)) + + l := make(ImportList, 0, len(ids)) + for _, id := range ids { + l = append(l, ImportItem{ + ResourceID: id, + }) + } + return l +} + +func (meta *MetaImpl) CleanTFState() { + os.Remove(path.Join(meta.Workspace(), "terraform.tfstate")) +} + +func (meta MetaImpl) Import(item ImportItem) error { + ctx := context.TODO() + + // Generate a temp Terraform config to include the empty template for each resource. + // This is required for the following importing. + cfgFile := filepath.Join(meta.workspace, "main.tf") + tpl, err := meta.tf.Add(ctx, item.TFAddr()) + if err != nil { + return fmt.Errorf("generating resource template for %s: %w", item.TFAddr(), err) + } + if err := os.WriteFile(cfgFile, []byte(tpl), 0644); err != nil { + return fmt.Errorf("generating resource template file: %w", err) + } + defer os.Remove(cfgFile) + + // Import resources + return meta.tf.Import(ctx, item.TFAddr(), item.ResourceID) +} + +func (meta MetaImpl) GenerateCfg(l ImportList) error { + ctx := context.TODO() + + cfginfos, err := meta.stateToConfig(ctx, l) + if err != nil { + return fmt.Errorf("converting from state to configurations: %w", err) + } + cfginfos, err = meta.resolveDependency(cfginfos) + if err != nil { + return fmt.Errorf("resolving cross resource dependencies: %w", err) + } + return meta.generateConfig(cfginfos) +} + +func providerConfig() string { + return fmt.Sprintf(`terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "%s" + } + } +} + +provider "azurerm" { + features {} +} +`, schema.ProviderVersion) +} + +func (meta *MetaImpl) initProvider(ctx context.Context) error { + cfgFile := filepath.Join(meta.workspace, "provider.tf") + + // Always use the latest provider version here, as this is a one shot tool, which should guarantees to work with the latest version. + if err := os.WriteFile(cfgFile, []byte(providerConfig()), 0644); err != nil { + return fmt.Errorf("error creating provider config: %w", err) + } + + if err := meta.tf.Init(ctx); err != nil { + return fmt.Errorf("error running terraform init: %s", err) + } + + return nil +} + +func (meta *MetaImpl) exportArmTemplate(ctx context.Context) error { + client := meta.auth.NewResourceGroupClient() + + exportOpt := "SkipAllParameterization" + future, err := client.ExportTemplate(ctx, meta.resourceGroup, resources.ExportTemplateRequest{ + ResourcesProperty: &[]string{"*"}, + Options: &exportOpt, + }) + if err != nil { + return fmt.Errorf("exporting arm template of resource group %s: %w", meta.resourceGroup, err) + } + + if err := future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for exporting arm template of resource group %s: %w", meta.resourceGroup, err) + } + + result, err := future.Result(client) + if err != nil { + return fmt.Errorf("getting the arm template of resource group %s: %w", meta.resourceGroup, err) + } + + // The response has been read into the ".Template" field as an interface, and the reader has been drained. + // As we have defined some (useful) types for the arm template, so we will do a json marshal then unmarshal here + // to convert the ".Template" (interface{}) into that artificial type. + raw, err := json.Marshal(result.Template) + if err != nil { + return fmt.Errorf("marshalling the template: %w", err) + } + if err := json.Unmarshal(raw, &meta.armTemplate); err != nil { + return fmt.Errorf("unmarshalling the template: %w", err) + } + + return nil +} + +func (meta MetaImpl) stateToConfig(ctx context.Context, list ImportList) (ConfigInfos, error) { + out := ConfigInfos{} + + for _, item := range list.Imported() { + tpl, err := meta.tf.Add(ctx, item.TFAddr(), tfexec.FromState(true)) + if err != nil { + return nil, fmt.Errorf("converting terraform state to config for resource %s: %w", item.TFAddr(), err) + } + f, diag := hclwrite.ParseConfig([]byte(tpl), "", hcl.InitialPos) + if diag.HasErrors() { + return nil, fmt.Errorf("parsing the HCL generated by \"terraform add\" of %s: %s", item.TFAddr(), diag.Error()) + } + + rb := f.Body().Blocks()[0].Body() + sch := schema.ProviderSchemaInfo.ResourceSchemas[item.TFResourceType] + if err := tuneHCLSchemaForResource(rb, sch); err != nil { + return nil, err + } + + out = append(out, ConfigInfo{ + ImportItem: item, + hcl: f, + }) + } + + return out, nil +} + +func (meta MetaImpl) resolveDependency(configs ConfigInfos) (ConfigInfos, error) { + depInfo := meta.armTemplate.DependencyInfo() + + configSet := map[armtemplate.ResourceId]ConfigInfo{} + for _, cfg := range configs { + armId, err := armtemplate.NewResourceId(cfg.ResourceID) + if err != nil { + return nil, fmt.Errorf("new arm tempalte resource id from azure resource id: %w", err) + } + configSet[*armId] = cfg + } + + // Iterate each config to add dependency by querying the dependency info from arm template. + var out ConfigInfos + for armId, cfg := range configSet { + if armId == armtemplate.ResourceGroupId { + out = append(out, cfg) + continue + } + // This should never happen as we always ensure there is at least one implicit dependency on the resource group for each resource. + if _, ok := depInfo[armId]; !ok { + return nil, fmt.Errorf("can't find resource %q in the arm template", armId.ID(meta.subscriptionId, meta.resourceGroup)) + } + + if err := meta.hclBlockAppendDependency(cfg.hcl.Body().Blocks()[0].Body(), depInfo[armId], configSet); err != nil { + return nil, err + } + out = append(out, cfg) + } + + return out, nil +} + +func (meta MetaImpl) hclBlockAppendDependency(body *hclwrite.Body, armIds []armtemplate.ResourceId, cfgset map[armtemplate.ResourceId]ConfigInfo) error { + dependencies := []string{} + for _, armid := range armIds { + cfg, ok := cfgset[armid] + if !ok { + dependencies = append(dependencies, fmt.Sprintf("# Depending on %q, which is not imported by Terraform.", armid.ID(meta.subscriptionId, meta.resourceGroup))) + continue + } + dependencies = append(dependencies, cfg.TFAddr()+",") + } + if len(dependencies) > 0 { + src := []byte("depends_on = [\n" + strings.Join(dependencies, "\n") + "\n]") + expr, diags := hclwrite.ParseConfig(src, "generate_depends_on", hcl.InitialPos) + if diags.HasErrors() { + return fmt.Errorf(`building "depends_on" attribute: %s`, diags.Error()) + } + + body.SetAttributeRaw("depends_on", expr.Body().GetAttribute("depends_on").Expr().BuildTokens(nil)) + } + + return nil +} + +func (meta MetaImpl) generateConfig(cfgs ConfigInfos) error { + cfgFile := filepath.Join(meta.workspace, "main.tf") + buf := bytes.NewBuffer([]byte{}) + for i, cfg := range cfgs { + if _, err := cfg.DumpHCL(buf); err != nil { + return err + } + if i != len(cfgs)-1 { + buf.Write([]byte("\n")) + } + } + if err := os.WriteFile(cfgFile, buf.Bytes(), 0644); err != nil { + return fmt.Errorf("generating main configuration file: %w", err) + } + + return nil +} diff --git a/internal/ui/aztfyclient/client.go b/internal/ui/aztfyclient/client.go index e6b486a..157625c 100644 --- a/internal/ui/aztfyclient/client.go +++ b/internal/ui/aztfyclient/client.go @@ -1,13 +1,13 @@ package aztfyclient import ( - "context" + "github.com/magodo/aztfy/internal/config" "github.com/magodo/aztfy/internal/meta" tea "github.com/charmbracelet/bubbletea" ) -type NewClientMsg *meta.Meta +type NewClientMsg meta.Meta type ErrMsg error @@ -39,9 +39,9 @@ type GenerateCfgDoneMsg struct{} type QuitMsg struct{} -func NewClient(rg string) tea.Cmd { +func NewClient(cfg config.Config) tea.Cmd { return func() tea.Msg { - c, err := meta.NewMeta(context.TODO(), rg) + c, err := meta.NewMeta(cfg) if err != nil { return ErrMsg(err) } @@ -49,9 +49,9 @@ func NewClient(rg string) tea.Cmd { } } -func Init(c *meta.Meta) tea.Cmd { +func Init(c meta.Meta) tea.Cmd { return func() tea.Msg { - err := c.Init(context.TODO()) + err := c.Init() if err != nil { return ErrMsg(err) } @@ -59,7 +59,7 @@ func Init(c *meta.Meta) tea.Cmd { } } -func ListResource(c *meta.Meta) tea.Cmd { +func ListResource(c meta.Meta) tea.Cmd { return func() tea.Msg { return ListResourceDoneMsg{List: c.ListResource()} } @@ -71,17 +71,17 @@ func ShowImportError(item meta.ImportItem, idx int, l meta.ImportList) tea.Cmd { } } -func StartImport(c *meta.Meta, l meta.ImportList) tea.Cmd { +func StartImport(c meta.Meta, l meta.ImportList) tea.Cmd { return func() tea.Msg { c.CleanTFState() return StartImportMsg{List: l} } } -func ImportOneItem(c *meta.Meta, item meta.ImportItem) tea.Cmd { +func ImportOneItem(c meta.Meta, item meta.ImportItem) tea.Cmd { return func() tea.Msg { if !item.Skip() { - item.ImportError = c.Import(context.TODO(), item) + item.ImportError = c.Import(item) } return ImportOneItemDoneMsg{Item: item} } @@ -93,9 +93,9 @@ func FinishImport(l meta.ImportList) tea.Cmd { } } -func GenerateCfg(c *meta.Meta, l meta.ImportList) tea.Cmd { +func GenerateCfg(c meta.Meta, l meta.ImportList) tea.Cmd { return func() tea.Msg { - if err := c.GenerateCfg(context.TODO(), l); err != nil { + if err := c.GenerateCfg(l); err != nil { return ErrMsg(err) } return GenerateCfgDoneMsg{} diff --git a/internal/ui/importlist/importlist.go b/internal/ui/importlist/importlist.go index f97ecd2..53271d6 100644 --- a/internal/ui/importlist/importlist.go +++ b/internal/ui/importlist/importlist.go @@ -2,12 +2,13 @@ package importlist import ( "fmt" + "sort" + "time" + "github.com/magodo/aztfy/internal/meta" "github.com/magodo/aztfy/internal/ui/aztfyclient" "github.com/magodo/aztfy/internal/ui/common" "github.com/magodo/aztfy/schema" - "sort" - "time" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" @@ -16,13 +17,13 @@ import ( ) type Model struct { - c *meta.Meta + c meta.Meta listkeys listKeyMap list list.Model } -func NewModel(c *meta.Meta, l meta.ImportList, idx int) Model { +func NewModel(c meta.Meta, l meta.ImportList, idx int) Model { // Build candidate words for the textinput candidates := make([]string, 0, len(schema.ProviderSchemaInfo.ResourceSchemas)) for rt := range schema.ProviderSchemaInfo.ResourceSchemas { diff --git a/internal/ui/progress/progress.go b/internal/ui/progress/progress.go index f21da3a..dfed9cf 100644 --- a/internal/ui/progress/progress.go +++ b/internal/ui/progress/progress.go @@ -2,6 +2,7 @@ package progress import ( "fmt" + tea "github.com/charmbracelet/bubbletea" "github.com/magodo/aztfy/internal/meta" "github.com/magodo/aztfy/internal/ui/aztfyclient" @@ -14,13 +15,13 @@ type result struct { } type Model struct { - c *meta.Meta - l meta.ImportList - idx int + c meta.Meta + l meta.ImportList + idx int results []result } -func NewModel(c *meta.Meta, l meta.ImportList) Model { +func NewModel(c meta.Meta, l meta.ImportList) Model { return Model{ c: c, l: l, diff --git a/internal/ui/ui.go b/internal/ui/ui.go index dc1d373..73fc0d8 100644 --- a/internal/ui/ui.go +++ b/internal/ui/ui.go @@ -2,14 +2,15 @@ package ui import ( "fmt" + "io" + "log" + "os" + "github.com/magodo/aztfy/internal/config" "github.com/magodo/aztfy/internal/meta" "github.com/magodo/aztfy/internal/ui/aztfyclient" "github.com/magodo/aztfy/internal/ui/common" "github.com/mitchellh/go-wordwrap" - "io" - "log" - "os" "github.com/muesli/reflow/indent" @@ -70,7 +71,7 @@ func (s status) String() string { type model struct { cfg config.Config - meta *meta.Meta + meta meta.Meta status status err error logger *log.Logger @@ -98,7 +99,7 @@ func newModel(cfg config.Config, logger *log.Logger) model { func (m model) Init() tea.Cmd { return tea.Batch( - aztfyclient.NewClient(m.cfg.ResourceGroupName), + aztfyclient.NewClient(m.cfg), spinner.Tick, ) }