func findUnmanaged(generatedDir, outputDir string, deleteFiles bool) error {
	managed := make(map[string]bool)
	mkFn := func(addToManaged bool, root string) func(path string, info os.FileInfo, err error) error {
		root = filepath.Clean(root)
		return func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return fmt.Errorf("walk path %q: %v", path, err)
			}
			trimmedPath := strings.TrimPrefix(filepath.Clean(path), root)
			if addToManaged {
				managed[trimmedPath] = true
				return nil
			}
			if _, ok := managed[trimmedPath]; ok {
				return nil
			}
			if !deleteFiles {
				log.Printf("INFO: Unmanaged file: %v\n", path)
				return nil
			}
			fullPath, err := fileutil.Expand(path)
			if err != nil {
				return err
			}
			log.Printf("WARNING: Deleting unmanaged file: %v\n", path)
			return os.RemoveAll(fullPath)
		}
	}
	if err := filepath.Walk(generatedDir, mkFn(true, generatedDir)); err != nil {
		return fmt.Errorf("walk %qs: %v", generatedDir, err)
	}
	if err := filepath.Walk(outputDir, mkFn(false, outputDir)); err != nil {
		return fmt.Errorf("walk %qs: %v", outputDir, err)
	}
	return nil
}
findUnmanaged walks both directories and shows all files in the output
directory which aren't in the generated files directory.
If the deleteFiles flag is set, it will also delete these files.
package tfengine
import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"reflect"
	"regexp"
	"strings"
	"github.com/GoogleCloudPlatform/healthcare-data-protection-suite/cmd"
	"github.com/GoogleCloudPlatform/healthcare-data-protection-suite/internal/fileutil"
	"github.com/GoogleCloudPlatform/healthcare-data-protection-suite/internal/hcl"
	"github.com/GoogleCloudPlatform/healthcare-data-protection-suite/internal/jsonschema"
	"github.com/GoogleCloudPlatform/healthcare-data-protection-suite/internal/licenseutil"
	"github.com/GoogleCloudPlatform/healthcare-data-protection-suite/internal/runner"
	"github.com/GoogleCloudPlatform/healthcare-data-protection-suite/internal/template"
	"github.com/GoogleCloudPlatform/healthcare-data-protection-suite/internal/version"
	"github.com/otiai10/copy"
)
type Options struct {
	Format          bool
	AddLicenses     bool
	CacheDir        string
	ShowUnmanaged   bool
	DeleteUnmanaged bool
	WantedTemplates map[string]bool
}
func Run(confPath, outPath string, opts *Options) error {
	var err error
	confPath, err = fileutil.Expand(confPath)
	if err != nil {
		return err
	}
	outPath, err = fileutil.Expand(outPath)
	if err != nil {
		return err
	}
	c, err := loadConfig(confPath, nil)
	if err != nil {
		return err
	}
	compat, err := version.IsCompatible(c.Version)
	if err != nil {
		return err
	}
	if !compat {
		return fmt.Errorf("Binary version %v incompatible with template version constraint %v in %v", cmd.Version, c.Version, confPath)
	}
	tmpDir, err := ioutil.TempDir("", "")
	if err != nil {
		return err
	}
	defer os.RemoveAll(tmpDir)
	if err := dump(c, filepath.Dir(confPath), opts.CacheDir, tmpDir, opts.WantedTemplates); err != nil {
		return err
	}
	if err := os.MkdirAll(outPath, 0755); err != nil {
		return fmt.Errorf("mkdir %q: %v", outPath, err)
	}
	var errs []string
	if opts.AddLicenses {
		if err := licenseutil.AddLicense(tmpDir); err != nil {
			errs = append(errs, fmt.Sprintf("add license header: %v", err))
		}
	}
	if opts.Format {
		if err := hcl.FormatDir(&runner.Default{Quiet: true}, tmpDir); err != nil {
			errs = append(errs, fmt.Sprintf("format output dir: %v", err))
		}
	}
	if opts.ShowUnmanaged {
		if err := findUnmanaged(tmpDir, outPath, opts.DeleteUnmanaged); err != nil {
			errs = append(errs, err.Error())
		}
	}
	if err := copy.Copy(tmpDir, outPath); err != nil {
		errs = append(errs, fmt.Sprintf("copy temp dir to output dir: %v", err))
	}
	if len(errs) > 0 {
		return errors.New(strings.Join(errs, "\n"))
	}
	return nil
}
func dump(conf *Config, pwd, cacheDir, outputPath string, wantedTemplates map[string]bool) error {
	for _, ti := range conf.Templates {
		if len(wantedTemplates) > 0 {
			if _, ok := wantedTemplates[ti.Name]; !ok {
				continue
			}
			wantedTemplates[ti.Name] = false
		}
		if err := dumpTemplate(conf, pwd, cacheDir, outputPath, ti); err != nil {
			return fmt.Errorf("template %q: %v", ti.Name, err)
		}
	}
	var missingTemplates []string
	for t, missed := range wantedTemplates {
		if missed {
			missingTemplates = append(missingTemplates, t)
		}
		wantedTemplates[t] = true
	}
	if len(missingTemplates) > 0 {
		return fmt.Errorf("templates not found in engine config: %v", strings.Join(missingTemplates, ","))
	}
	return nil
}
func dumpTemplate(conf *Config, pwd, cacheDir, outputPath string, ti *templateInfo) error {
	outputPath = filepath.Join(outputPath, ti.OutputPath)
	data := make(map[string]interface{})
	trimDescriptions(conf.Schema)
	data["__schema__"] = conf.Schema
	if err := template.MergeData(data, conf.Data); err != nil {
		return err
	}
	flattenedData, err := template.FlattenData(data, ti.Flatten)
	if err != nil {
		return err
	}
	if err := template.MergeData(ti.Data, flattenedData); err != nil {
		return err
	}
	if err := template.MergeData(data, ti.Data); err != nil {
		return err
	}
	for _, k := range ti.Passthrough {
		if v, ok := conf.Data[k]; ok {
			ti.Data[k] = v
		}
	}
	switch {
	case ti.RecipePath != "":
		rp, err := fileutil.Fetch(ti.RecipePath, pwd, cacheDir)
		if err != nil {
			return err
		}
		rc, err := loadConfig(rp, data)
		if err != nil {
			return fmt.Errorf("load recipe %q: %v", rp, err)
		}
		compat, err := version.IsCompatible(rc.Version)
		if err != nil {
			return err
		}
		if !compat {
			return fmt.Errorf("binary version %v incompatible with template version constraint %v in %v", cmd.Version, rc.Version, rp)
		}
		if len(rc.Schema) > 0 {
			if err := jsonschema.ValidateMap(rc.Schema, ti.Data); err != nil {
				return fmt.Errorf("recipe %q: %v", rp, err)
			}
		}
		if err := template.MergeData(rc.Data, data); err != nil {
			return err
		}
		if err := dump(rc, filepath.Dir(rp), cacheDir, outputPath, nil); err != nil {
			return fmt.Errorf("recipe %q: %v", rp, err)
		}
	case ti.ComponentPath != "":
		cp, err := fileutil.Fetch(ti.ComponentPath, pwd, cacheDir)
		if err != nil {
			return err
		}
		write := template.WriteDir
		info, err := os.Stat(cp)
		if err != nil {
			return fmt.Errorf("stat %q: %v", cp, err)
		}
		if info.Mode().IsRegular() {
			write = template.WriteFile
		}
		if err := write(cp, outputPath, data); err != nil {
			return fmt.Errorf("component %q: %v", cp, err)
		}
	}
	return nil
}
var backendRE = regexp.MustCompile(`(?sU)backend "gcs" {.*}`)
var remoteStateResourceRE = regexp.MustCompile(`(?sU)data "terraform_remote_state" .* {.*}\n}`)
var remoteStateDataRE = regexp.MustCompile(`("\${data\.terraform_remote_state\..*}")|(data\.terraform_remote_state\.\S*)`)
func ConvertToLocalBackend(path string) error {
	fn := func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return fmt.Errorf("walk path %q: %v", path, err)
		}
		if filepath.Ext(path) != ".tf" {
			return nil
		}
		b, err := ioutil.ReadFile(path)
		if err != nil {
			return fmt.Errorf("read file %q: %v", path, err)
		}
		b = backendRE.ReplaceAll(b, nil)
		b = remoteStateResourceRE.ReplaceAll(b, nil)
		b = remoteStateDataRE.ReplaceAll(b, []byte(`"000"`))
		if err := ioutil.WriteFile(path, b, 0644); err != nil {
			return fmt.Errorf("write file %q: %v", path, err)
		}
		return nil
	}
	if err := filepath.Walk(path, fn); err != nil {
		return fmt.Errorf("walk %qs: %v", path, err)
	}
	return nil
}