package terraform import ( "context" "fmt" "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/bundle/deploy/terraform/tfdyn" "github.com/databricks/cli/bundle/internal/tf/schema" "github.com/databricks/cli/libs/dyn" tfjson "github.com/hashicorp/terraform-json" ) // BundleToTerraformWithDynValue converts resources in a bundle configuration // to the equivalent Terraform JSON representation. func BundleToTerraformWithDynValue(ctx context.Context, root dyn.Value) (*schema.Root, error) { tfroot := schema.NewRoot() tfroot.Provider = schema.NewProviders() // Convert each resource in the bundle to the equivalent Terraform representation. dynResources, err := dyn.Get(root, "resources") if err != nil { // If the resources key is missing, return an empty root. if dyn.IsNoSuchKeyError(err) { return tfroot, nil } return nil, err } tfroot.Resource = schema.NewResources() numResources := 0 _, err = dyn.Walk(dynResources, func(p dyn.Path, v dyn.Value) (dyn.Value, error) { if len(p) < 2 { return v, nil } // Skip resources that have been deleted locally. modifiedStatus, err := dyn.Get(v, "modified_status") if err == nil { modifiedStatusStr, ok := modifiedStatus.AsString() if ok && modifiedStatusStr == resources.ModifiedStatusDeleted { return v, dyn.ErrSkip } } typ := p[0].Key() key := p[1].Key() // Lookup the converter based on the resource type. c, ok := tfdyn.GetConverter(typ) if !ok { return dyn.InvalidValue, fmt.Errorf("no converter for resource type %s", typ) } // Convert resource to Terraform representation. err = c.Convert(ctx, key, v, tfroot.Resource) if err != nil { return dyn.InvalidValue, err } numResources++ // Skip traversal of the resource itself. return v, dyn.ErrSkip }) if err != nil { return nil, err } // We explicitly set "resource" to nil to omit it from a JSON encoding. // This is required because the terraform CLI requires >= 1 resources defined // if the "resource" property is used in a .tf.json file. if numResources == 0 { tfroot.Resource = nil } return tfroot, nil } func TerraformToBundle(state *resourcesState, config *config.Root) error { for _, resource := range state.Resources { if resource.Mode != tfjson.ManagedResourceMode { continue } for _, instance := range resource.Instances { switch resource.Type { case "databricks_job": if config.Resources.Jobs == nil { config.Resources.Jobs = make(map[string]*resources.Job) } cur := config.Resources.Jobs[resource.Name] if cur == nil { cur = &resources.Job{ModifiedStatus: resources.ModifiedStatusDeleted} } cur.ID = instance.Attributes.ID config.Resources.Jobs[resource.Name] = cur case "databricks_pipeline": if config.Resources.Pipelines == nil { config.Resources.Pipelines = make(map[string]*resources.Pipeline) } cur := config.Resources.Pipelines[resource.Name] if cur == nil { cur = &resources.Pipeline{ModifiedStatus: resources.ModifiedStatusDeleted} } cur.ID = instance.Attributes.ID config.Resources.Pipelines[resource.Name] = cur case "databricks_mlflow_model": if config.Resources.Models == nil { config.Resources.Models = make(map[string]*resources.MlflowModel) } cur := config.Resources.Models[resource.Name] if cur == nil { cur = &resources.MlflowModel{ModifiedStatus: resources.ModifiedStatusDeleted} } cur.ID = instance.Attributes.ID config.Resources.Models[resource.Name] = cur case "databricks_mlflow_experiment": if config.Resources.Experiments == nil { config.Resources.Experiments = make(map[string]*resources.MlflowExperiment) } cur := config.Resources.Experiments[resource.Name] if cur == nil { cur = &resources.MlflowExperiment{ModifiedStatus: resources.ModifiedStatusDeleted} } cur.ID = instance.Attributes.ID config.Resources.Experiments[resource.Name] = cur case "databricks_model_serving": if config.Resources.ModelServingEndpoints == nil { config.Resources.ModelServingEndpoints = make(map[string]*resources.ModelServingEndpoint) } cur := config.Resources.ModelServingEndpoints[resource.Name] if cur == nil { cur = &resources.ModelServingEndpoint{ModifiedStatus: resources.ModifiedStatusDeleted} } cur.ID = instance.Attributes.ID config.Resources.ModelServingEndpoints[resource.Name] = cur case "databricks_registered_model": if config.Resources.RegisteredModels == nil { config.Resources.RegisteredModels = make(map[string]*resources.RegisteredModel) } cur := config.Resources.RegisteredModels[resource.Name] if cur == nil { cur = &resources.RegisteredModel{ModifiedStatus: resources.ModifiedStatusDeleted} } cur.ID = instance.Attributes.ID config.Resources.RegisteredModels[resource.Name] = cur case "databricks_quality_monitor": if config.Resources.QualityMonitors == nil { config.Resources.QualityMonitors = make(map[string]*resources.QualityMonitor) } cur := config.Resources.QualityMonitors[resource.Name] if cur == nil { cur = &resources.QualityMonitor{ModifiedStatus: resources.ModifiedStatusDeleted} } cur.ID = instance.Attributes.ID config.Resources.QualityMonitors[resource.Name] = cur case "databricks_schema": if config.Resources.Schemas == nil { config.Resources.Schemas = make(map[string]*resources.Schema) } cur := config.Resources.Schemas[resource.Name] if cur == nil { cur = &resources.Schema{ModifiedStatus: resources.ModifiedStatusDeleted} } cur.ID = instance.Attributes.ID config.Resources.Schemas[resource.Name] = cur case "databricks_cluster": if config.Resources.Clusters == nil { config.Resources.Clusters = make(map[string]*resources.Cluster) } cur := config.Resources.Clusters[resource.Name] if cur == nil { cur = &resources.Cluster{ModifiedStatus: resources.ModifiedStatusDeleted} } cur.ID = instance.Attributes.ID config.Resources.Clusters[resource.Name] = cur case "databricks_dashboard": if config.Resources.Dashboards == nil { config.Resources.Dashboards = make(map[string]*resources.Dashboard) } cur := config.Resources.Dashboards[resource.Name] if cur == nil { cur = &resources.Dashboard{ModifiedStatus: resources.ModifiedStatusDeleted} } cur.ID = instance.Attributes.ID config.Resources.Dashboards[resource.Name] = cur case "databricks_permissions": case "databricks_grants": // Ignore; no need to pull these back into the configuration. default: return fmt.Errorf("missing mapping for %s", resource.Type) } } } for _, src := range config.Resources.Jobs { if src.ModifiedStatus == "" && src.ID == "" { src.ModifiedStatus = resources.ModifiedStatusCreated } } for _, src := range config.Resources.Pipelines { if src.ModifiedStatus == "" && src.ID == "" { src.ModifiedStatus = resources.ModifiedStatusCreated } } for _, src := range config.Resources.Models { if src.ModifiedStatus == "" && src.ID == "" { src.ModifiedStatus = resources.ModifiedStatusCreated } } for _, src := range config.Resources.Experiments { if src.ModifiedStatus == "" && src.ID == "" { src.ModifiedStatus = resources.ModifiedStatusCreated } } for _, src := range config.Resources.ModelServingEndpoints { if src.ModifiedStatus == "" && src.ID == "" { src.ModifiedStatus = resources.ModifiedStatusCreated } } for _, src := range config.Resources.RegisteredModels { if src.ModifiedStatus == "" && src.ID == "" { src.ModifiedStatus = resources.ModifiedStatusCreated } } for _, src := range config.Resources.QualityMonitors { if src.ModifiedStatus == "" && src.ID == "" { src.ModifiedStatus = resources.ModifiedStatusCreated } } for _, src := range config.Resources.Schemas { if src.ModifiedStatus == "" && src.ID == "" { src.ModifiedStatus = resources.ModifiedStatusCreated } } for _, src := range config.Resources.Clusters { if src.ModifiedStatus == "" && src.ID == "" { src.ModifiedStatus = resources.ModifiedStatusCreated } } for _, src := range config.Resources.Dashboards { if src.ModifiedStatus == "" && src.ID == "" { src.ModifiedStatus = resources.ModifiedStatusCreated } } return nil }