scripts/check_importer_supports_engine/main.go (107 lines of code) (raw):
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Generates configs from the full example, inits all modules, and checks if any resources are not supported by the importer.
// Meant to be run from the repo root like so:
// go run ./scripts/check_importer_supports_engine
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
"github.com/GoogleCloudPlatform/healthcare-data-protection-suite/internal/tfengine"
"github.com/GoogleCloudPlatform/healthcare-data-protection-suite/internal/tfimport"
)
const examplesDir = "examples/tfengine"
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
func run() error {
examples, err := filepath.Glob(filepath.Join(examplesDir, "*.hcl"))
if err != nil {
return fmt.Errorf("glob .hcl files under %v: %v", examplesDir, err)
}
if len(examples) == 0 {
return fmt.Errorf("found no examples")
}
unsupported := make(map[string]bool)
for _, ex := range examples {
if err := resourcesFromConfig(ex, unsupported); err != nil {
return fmt.Errorf("finding resources from %v: %v", ex, err)
}
}
resources := make([]string, 0, len(unsupported))
for r := range unsupported {
resources = append(resources, r)
}
sort.Strings(resources)
fmt.Println(strings.Join(resources, "\n"))
return nil
}
// resourcesFromConfig generates configs from an engine config, and populates the map unsupported with unsupported resources
func resourcesFromConfig(configPath string, unsupported map[string]bool) error {
// Create tmpdir for outputting the configs
tmp, err := ioutil.TempDir("", "")
if err != nil {
return fmt.Errorf("ioutil.TempDir = %v", err)
}
defer os.RemoveAll(tmp)
// Generate configs from the top-level config
if err := tfengine.Run(configPath, tmp, &tfengine.Options{Format: false, CacheDir: tmp}); err != nil {
return fmt.Errorf("tfengine.Run(%q, %q) = %v", configPath, tmp, err)
}
// Run plan to verify configs.
fs, err := ioutil.ReadDir(tmp)
if err != nil {
return fmt.Errorf("ioutil.ReadDir = %v", err)
}
for _, f := range fs {
if !f.IsDir() {
continue
}
dir := filepath.Join(tmp, f.Name())
// Convert the configs not reference a GCS backend as the state bucket does not exist.
if err := tfengine.ConvertToLocalBackend(dir); err != nil {
return fmt.Errorf("ConvertToLocalBackend(%v): %v", dir, err)
}
init := exec.Command("terraform", "init")
init.Dir = dir
if b, err := init.CombinedOutput(); err != nil {
return fmt.Errorf("command %v in %q: %v\n%v", init.Args, dir, err, string(b))
}
if err := addResources(dir, unsupported); err != nil {
return fmt.Errorf("add resources from %q: %v", dir, err)
}
}
return nil
}
var resourceRE = regexp.MustCompile(`(?s)resource "(.*?)"`)
// addResources search for resource declarations in .tf files and adds them to the map unsupported if they're importable and not supported.
func addResources(path string, unsupported map[string]bool) 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)
}
match := resourceRE.FindAllStringSubmatch(string(b), -1)
for _, m := range match {
resource := m[len(m)-1]
if _, ok := unsupported[resource]; ok {
continue
}
// Skip if it's supported or not importable.
_, supported := tfimport.Importers[resource]
_, unimportable := tfimport.Unimportable[resource]
if supported || unimportable {
continue
}
unsupported[resource] = true
}
return nil
}
if err := filepath.Walk(path, fn); err != nil {
return fmt.Errorf("filepath.Walk = %v", err)
}
return nil
}