mirror of https://github.com/databricks/cli.git
Use real terraform in acceptance tests (#2267)
## Changes - Add a script install_terraform.py that downloads terraform and provider and generates a config to use, inspired by https://gist.github.com/pietern/1cb6b6f3e0a452328e13cdc75031105e - Make acceptance tests run this script once before running the tests and set the required env vars to make cli use this terraform installation. - Use OS-specific directory for things that are build by acceptance test runner (CLI and terraform). This enables acceptance tests against cloud #2242 and local test for bundle deploy #2254. ## Tests - Add an acceptance test for standalone terraform. This is useful to debug terraform with TF_LOG=DEBUG to see that it uses local provider. - Other acceptance tests are updated with regard to terraform exec path. - The overall time for tests locally is unchanged (if terraform is already fetched).
This commit is contained in:
parent
787dbe9099
commit
e5730bf57e
|
@ -0,0 +1 @@
|
||||||
|
build
|
|
@ -77,6 +77,11 @@ func testAccept(t *testing.T, InprocessMode bool, singleTest string) int {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
buildDir := filepath.Join(cwd, "build", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH))
|
||||||
|
|
||||||
|
// Download terraform and provider and create config; this also creates build directory.
|
||||||
|
RunCommand(t, []string{"python3", filepath.Join(cwd, "install_terraform.py"), "--targetdir", buildDir}, ".")
|
||||||
|
|
||||||
coverDir := os.Getenv("CLI_GOCOVERDIR")
|
coverDir := os.Getenv("CLI_GOCOVERDIR")
|
||||||
|
|
||||||
if coverDir != "" {
|
if coverDir != "" {
|
||||||
|
@ -93,7 +98,7 @@ func testAccept(t *testing.T, InprocessMode bool, singleTest string) int {
|
||||||
t.Setenv("CMD_SERVER_URL", cmdServer.URL)
|
t.Setenv("CMD_SERVER_URL", cmdServer.URL)
|
||||||
execPath = filepath.Join(cwd, "bin", "callserver.py")
|
execPath = filepath.Join(cwd, "bin", "callserver.py")
|
||||||
} else {
|
} else {
|
||||||
execPath = BuildCLI(t, cwd, coverDir)
|
execPath = BuildCLI(t, buildDir, coverDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Setenv("CLI", execPath)
|
t.Setenv("CLI", execPath)
|
||||||
|
@ -123,11 +128,24 @@ func testAccept(t *testing.T, InprocessMode bool, singleTest string) int {
|
||||||
homeDir := t.TempDir()
|
homeDir := t.TempDir()
|
||||||
// Do not read user's ~/.databrickscfg
|
// Do not read user's ~/.databrickscfg
|
||||||
t.Setenv(env.HomeEnvVar(), homeDir)
|
t.Setenv(env.HomeEnvVar(), homeDir)
|
||||||
|
|
||||||
// Prevent CLI from downloading terraform in each test:
|
|
||||||
t.Setenv("DATABRICKS_TF_EXEC_PATH", tempHomeDir)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
terraformrcPath := filepath.Join(buildDir, ".terraformrc")
|
||||||
|
t.Setenv("TF_CLI_CONFIG_FILE", terraformrcPath)
|
||||||
|
t.Setenv("DATABRICKS_TF_CLI_CONFIG_FILE", terraformrcPath)
|
||||||
|
repls.SetPath(terraformrcPath, "$DATABRICKS_TF_CLI_CONFIG_FILE")
|
||||||
|
|
||||||
|
terraformExecPath := filepath.Join(buildDir, "terraform")
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
terraformExecPath += ".exe"
|
||||||
|
}
|
||||||
|
t.Setenv("DATABRICKS_TF_EXEC_PATH", terraformExecPath)
|
||||||
|
t.Setenv("TERRAFORM", terraformExecPath)
|
||||||
|
repls.SetPath(terraformExecPath, "$TERRAFORM")
|
||||||
|
|
||||||
|
// do it last so that full paths match first:
|
||||||
|
repls.SetPath(buildDir, "$BUILD_DIR")
|
||||||
|
|
||||||
workspaceClient, err := databricks.NewWorkspaceClient()
|
workspaceClient, err := databricks.NewWorkspaceClient()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -406,13 +424,12 @@ func readMergedScriptContents(t *testing.T, dir string) string {
|
||||||
return strings.Join(prepares, "\n")
|
return strings.Join(prepares, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildCLI(t *testing.T, cwd, coverDir string) string {
|
func BuildCLI(t *testing.T, buildDir, coverDir string) string {
|
||||||
execPath := filepath.Join(cwd, "build", "databricks")
|
execPath := filepath.Join(buildDir, "databricks")
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
execPath += ".exe"
|
execPath += ".exe"
|
||||||
}
|
}
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
args := []string{
|
args := []string{
|
||||||
"go", "build",
|
"go", "build",
|
||||||
"-mod", "vendor",
|
"-mod", "vendor",
|
||||||
|
@ -430,20 +447,8 @@ func BuildCLI(t *testing.T, cwd, coverDir string) string {
|
||||||
args = append(args, "-buildvcs=false")
|
args = append(args, "-buildvcs=false")
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(args[0], args[1:]...)
|
RunCommand(t, args, "..")
|
||||||
cmd.Dir = ".."
|
RunCommand(t, []string{execPath, "--version"}, ".")
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
elapsed := time.Since(start)
|
|
||||||
t.Logf("%s took %s", args, elapsed)
|
|
||||||
require.NoError(t, err, "go build failed: %s: %s\n%s", args, err, out)
|
|
||||||
if len(out) > 0 {
|
|
||||||
t.Logf("go build output: %s: %s", args, out)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Quick check + warm up cache:
|
|
||||||
cmd = exec.Command(execPath, "--version")
|
|
||||||
out, err = cmd.CombinedOutput()
|
|
||||||
require.NoError(t, err, "%s --version failed: %s\n%s", execPath, err, out)
|
|
||||||
return execPath
|
return execPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -581,3 +586,17 @@ func getUVDefaultCacheDir(t *testing.T) string {
|
||||||
return cacheDir + "/uv"
|
return cacheDir + "/uv"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RunCommand(t *testing.T, args []string, dir string) {
|
||||||
|
start := time.Now()
|
||||||
|
cmd := exec.Command(args[0], args[1:]...)
|
||||||
|
cmd.Dir = dir
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
elapsed := time.Since(start)
|
||||||
|
t.Logf("%s took %s", args, elapsed)
|
||||||
|
|
||||||
|
require.NoError(t, err, "%s failed: %s\n%s", args, err, out)
|
||||||
|
if len(out) > 0 {
|
||||||
|
t.Logf("%s output: %s", args, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
databricks
|
|
|
@ -11,7 +11,7 @@
|
||||||
"name": "git",
|
"name": "git",
|
||||||
"target": "prod",
|
"target": "prod",
|
||||||
"terraform": {
|
"terraform": {
|
||||||
"exec_path": "$TMPHOME"
|
"exec_path": "$TERRAFORM"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sync": {
|
"sync": {
|
||||||
|
@ -61,7 +61,7 @@ Validation OK!
|
||||||
"name": "git",
|
"name": "git",
|
||||||
"target": "dev",
|
"target": "dev",
|
||||||
"terraform": {
|
"terraform": {
|
||||||
"exec_path": "$TMPHOME"
|
"exec_path": "$TERRAFORM"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sync": {
|
"sync": {
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
},
|
},
|
||||||
"target": "dev",
|
"target": "dev",
|
||||||
"terraform": {
|
"terraform": {
|
||||||
"exec_path": "$TMPHOME"
|
"exec_path": "$TERRAFORM"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"resources": {
|
"resources": {
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Script to set up terraform and databricks terraform provider in a local directory:
|
||||||
|
|
||||||
|
- Download terraform.
|
||||||
|
- Download databricks provider.
|
||||||
|
- Write a .terraformrc config file that uses this directory.
|
||||||
|
- The config file contains env vars that need to be set so that databricks CLI uses this terraform and provider.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import zipfile
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from urllib.request import urlretrieve
|
||||||
|
|
||||||
|
os_name = platform.system().lower()
|
||||||
|
|
||||||
|
arch = platform.machine().lower()
|
||||||
|
arch = {"x86_64": "amd64"}.get(arch, arch)
|
||||||
|
if os_name == "windows" and arch not in ("386", "amd64"):
|
||||||
|
# terraform 1.5.5 only has builds for these two.
|
||||||
|
arch = "amd64"
|
||||||
|
|
||||||
|
terraform_version = "1.5.5"
|
||||||
|
terraform_file = f"terraform_{terraform_version}_{os_name}_{arch}.zip"
|
||||||
|
terraform_url = f"https://releases.hashicorp.com/terraform/{terraform_version}/{terraform_file}"
|
||||||
|
terraform_binary = "terraform.exe" if os_name == "windows" else "terraform"
|
||||||
|
|
||||||
|
|
||||||
|
def retrieve(url, path):
|
||||||
|
if not path.exists():
|
||||||
|
print(f"Downloading {url} -> {path}")
|
||||||
|
urlretrieve(url, path)
|
||||||
|
|
||||||
|
|
||||||
|
def read_version(path):
|
||||||
|
for line in path.open():
|
||||||
|
if "ProviderVersion" in line:
|
||||||
|
# Expecting 'const ProviderVersion = "1.64.1"'
|
||||||
|
items = line.strip().split()
|
||||||
|
assert len(items) >= 3, items
|
||||||
|
assert items[-3:-1] == ["ProviderVersion", "="], items
|
||||||
|
version = items[-1].strip('"')
|
||||||
|
assert version, items
|
||||||
|
return version
|
||||||
|
raise SystemExit(f"Could not find ProviderVersion in {path}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--targetdir", default="build", type=Path)
|
||||||
|
parser.add_argument("--provider-version")
|
||||||
|
args = parser.parse_args()
|
||||||
|
target = args.targetdir
|
||||||
|
|
||||||
|
if not args.provider_version:
|
||||||
|
version_file = Path(__file__).parent.parent / "bundle/internal/tf/codegen/schema/version.go"
|
||||||
|
assert version_file.exists(), version_file
|
||||||
|
terraform_provider_version = read_version(version_file)
|
||||||
|
print(f"Read version {terraform_provider_version} from {version_file}")
|
||||||
|
else:
|
||||||
|
terraform_provider_version = args.provider_version
|
||||||
|
|
||||||
|
terraform_provider_file = f"terraform-provider-databricks_{terraform_provider_version}_{os_name}_{arch}.zip"
|
||||||
|
terraform_provider_url = (
|
||||||
|
f"https://github.com/databricks/terraform-provider-databricks/releases/download/v{terraform_provider_version}/{terraform_provider_file}"
|
||||||
|
)
|
||||||
|
|
||||||
|
target.mkdir(exist_ok=True, parents=True)
|
||||||
|
|
||||||
|
zip_path = target / terraform_file
|
||||||
|
terraform_path = target / terraform_binary
|
||||||
|
terraform_provider_path = target / terraform_provider_file
|
||||||
|
|
||||||
|
retrieve(terraform_url, zip_path)
|
||||||
|
retrieve(terraform_provider_url, terraform_provider_path)
|
||||||
|
|
||||||
|
if not terraform_path.exists():
|
||||||
|
print(f"Extracting {zip_path} -> {terraform_path}")
|
||||||
|
|
||||||
|
with zipfile.ZipFile(zip_path, "r") as zip_ref:
|
||||||
|
zip_ref.extractall(target)
|
||||||
|
|
||||||
|
terraform_path.chmod(0o755)
|
||||||
|
|
||||||
|
tfplugins_path = target / "tfplugins"
|
||||||
|
provider_dir = Path(tfplugins_path / f"registry.terraform.io/databricks/databricks/{terraform_provider_version}/{os_name}_{arch}")
|
||||||
|
if not provider_dir.exists():
|
||||||
|
print(f"Extracting {terraform_provider_path} -> {provider_dir}")
|
||||||
|
os.makedirs(provider_dir, exist_ok=True)
|
||||||
|
with zipfile.ZipFile(terraform_provider_path, "r") as zip_ref:
|
||||||
|
zip_ref.extractall(provider_dir)
|
||||||
|
|
||||||
|
files = list(provider_dir.iterdir())
|
||||||
|
assert files, provider_dir
|
||||||
|
|
||||||
|
for f in files:
|
||||||
|
f.chmod(0o755)
|
||||||
|
|
||||||
|
terraformrc_path = target / ".terraformrc"
|
||||||
|
if not terraformrc_path.exists():
|
||||||
|
path = json.dumps(str(tfplugins_path.absolute()))
|
||||||
|
text = f"""# Set these env variables before running databricks cli:
|
||||||
|
# export DATABRICKS_TF_CLI_CONFIG_FILE={terraformrc_path.absolute()}
|
||||||
|
# export DATABRICKS_TF_EXEC_PATH={terraform_path.absolute()}
|
||||||
|
|
||||||
|
provider_installation {{
|
||||||
|
filesystem_mirror {{
|
||||||
|
path = {path}
|
||||||
|
include = ["registry.terraform.io/databricks/databricks"]
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
print(f"Writing {terraformrc_path}:\n{text}")
|
||||||
|
terraformrc_path.write_text(text)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -0,0 +1,25 @@
|
||||||
|
terraform {
|
||||||
|
required_providers {
|
||||||
|
databricks = {
|
||||||
|
source = "databricks/databricks"
|
||||||
|
version = "1.64.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
required_version = "= 1.5.5"
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "databricks" {
|
||||||
|
# Optionally, specify the Databricks host and token
|
||||||
|
# host = "https://<your-databricks-instance>"
|
||||||
|
# token = "<YOUR_PERSONAL_ACCESS_TOKEN>"
|
||||||
|
}
|
||||||
|
|
||||||
|
data "databricks_current_user" "me" {
|
||||||
|
# Retrieves the current user's information
|
||||||
|
}
|
||||||
|
|
||||||
|
output "username" {
|
||||||
|
description = "Username"
|
||||||
|
value = "${data.databricks_current_user.me.user_name}"
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
|
||||||
|
>>> $TERRAFORM init -no-color -get=false
|
||||||
|
|
||||||
|
Initializing the backend...
|
||||||
|
|
||||||
|
Initializing provider plugins...
|
||||||
|
- Finding databricks/databricks versions matching "1.64.1"...
|
||||||
|
- Installing databricks/databricks v1.64.1...
|
||||||
|
- Installed databricks/databricks v1.64.1 (unauthenticated)
|
||||||
|
|
||||||
|
Terraform has created a lock file .terraform.lock.hcl to record the provider
|
||||||
|
selections it made above. Include this file in your version control repository
|
||||||
|
so that Terraform can guarantee to make the same selections by default when
|
||||||
|
you run "terraform init" in the future.
|
||||||
|
|
||||||
|
|
||||||
|
Warning: Incomplete lock file information for providers
|
||||||
|
|
||||||
|
Due to your customized provider installation methods, Terraform was forced to
|
||||||
|
calculate lock file checksums locally for the following providers:
|
||||||
|
- databricks/databricks
|
||||||
|
|
||||||
|
|
||||||
|
To calculate additional checksums for another platform, run:
|
||||||
|
terraform providers lock -platform=linux_amd64
|
||||||
|
(where linux_amd64 is the platform to generate)
|
||||||
|
|
||||||
|
Terraform has been successfully initialized!
|
||||||
|
|
||||||
|
You may now begin working with Terraform. Try running "terraform plan" to see
|
||||||
|
any changes that are required for your infrastructure. All Terraform commands
|
||||||
|
should now work.
|
||||||
|
|
||||||
|
If you ever set or change modules or backend configuration for Terraform,
|
||||||
|
rerun this command to reinitialize your working directory. If you forget, other
|
||||||
|
commands will detect it and remind you to do so if necessary.
|
||||||
|
|
||||||
|
>>> $TERRAFORM plan -no-color
|
||||||
|
data.databricks_current_user.me: Reading...
|
||||||
|
data.databricks_current_user.me: Read complete after 0s [id=$USER.Id]
|
||||||
|
|
||||||
|
Changes to Outputs:
|
||||||
|
+ username = "$USERNAME"
|
||||||
|
|
||||||
|
You can apply this plan to save these new output values to the Terraform
|
||||||
|
state, without changing any real infrastructure.
|
||||||
|
|
||||||
|
─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
Note: You didn't use the -out option to save this plan, so Terraform can't
|
||||||
|
guarantee to take exactly these actions if you run "terraform apply" now.
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Want to filter out these message:
|
||||||
|
# Mac:
|
||||||
|
# The current .terraform.lock.hcl file only includes checksums for
|
||||||
|
# darwin_arm64, so Terraform running on another platform will fail to install
|
||||||
|
# these providers.
|
||||||
|
#
|
||||||
|
# Linux:
|
||||||
|
# The current .terraform.lock.hcl file only includes checksums for linux_amd64,
|
||||||
|
# so Terraform running on another platform will fail to install these
|
||||||
|
# providers.
|
||||||
|
|
||||||
|
trace $TERRAFORM init -no-color -get=false | grep -v 'includes checksums for' | grep -v 'so Terraform running on another' | grep -v 'providers\.'
|
||||||
|
trace $TERRAFORM plan -no-color
|
||||||
|
rm -fr .terraform.lock.hcl .terraform
|
Loading…
Reference in New Issue