2023-05-15 09:34:05 +00:00
package mutator
import (
"context"
2024-07-09 11:12:42 +00:00
"fmt"
2025-01-23 14:35:33 +00:00
"os"
"path/filepath"
2023-05-15 09:34:05 +00:00
2023-05-16 16:35:39 +00:00
"github.com/databricks/cli/bundle"
"github.com/databricks/cli/bundle/config/variable"
2024-03-25 14:18:47 +00:00
"github.com/databricks/cli/libs/diag"
2024-07-09 11:12:42 +00:00
"github.com/databricks/cli/libs/dyn"
2025-01-23 14:35:33 +00:00
"github.com/databricks/cli/libs/dyn/jsonloader"
2023-09-11 08:18:43 +00:00
"github.com/databricks/cli/libs/env"
2023-05-15 09:34:05 +00:00
)
const bundleVarPrefix = "BUNDLE_VAR_"
type setVariables struct { }
func SetVariables ( ) bundle . Mutator {
return & setVariables { }
}
func ( m * setVariables ) Name ( ) string {
return "SetVariables"
}
2025-01-23 14:35:33 +00:00
func getDefaultVariableFilePath ( target string ) string {
return ".databricks/bundle/" + target + "/variable-overrides.json"
}
func setVariable ( ctx context . Context , v dyn . Value , variable * variable . Variable , name string , fileDefault dyn . Value ) ( dyn . Value , error ) {
2023-05-15 09:34:05 +00:00
// case: variable already has value initialized, so skip
2024-07-09 11:12:42 +00:00
if variable . HasValue ( ) {
return v , nil
2023-05-15 09:34:05 +00:00
}
// case: read and set variable value from process environment
envVarName := bundleVarPrefix + name
2023-09-11 08:18:43 +00:00
if val , ok := env . Lookup ( ctx , envVarName ) ; ok {
2024-07-09 11:12:42 +00:00
if variable . IsComplex ( ) {
return dyn . InvalidValue , fmt . Errorf ( ` setting via environment variables (%s) is not supported for complex variable %s ` , envVarName , name )
2024-06-26 10:25:32 +00:00
}
2024-07-09 11:12:42 +00:00
v , err := dyn . Set ( v , "value" , dyn . V ( val ) )
2023-05-15 09:34:05 +00:00
if err != nil {
2024-07-09 11:12:42 +00:00
return dyn . InvalidValue , fmt . Errorf ( ` failed to assign value "%s" to variable %s from environment variable %s with error: %v ` , val , name , envVarName , err )
2023-05-15 09:34:05 +00:00
}
2024-07-09 11:12:42 +00:00
return v , nil
2023-05-15 09:34:05 +00:00
}
2024-06-19 08:03:06 +00:00
// case: Defined a variable for named lookup for a resource
// It will be resolved later in ResolveResourceReferences mutator
2024-07-09 11:12:42 +00:00
if variable . Lookup != nil {
return v , nil
2024-06-19 08:03:06 +00:00
}
2025-01-23 14:35:33 +00:00
// case: Set the variable to the default value from the variable file
if fileDefault . Kind ( ) != dyn . KindInvalid && fileDefault . Kind ( ) != dyn . KindNil {
hasComplexType := variable . IsComplex ( )
hasComplexValue := fileDefault . Kind ( ) == dyn . KindMap || fileDefault . Kind ( ) == dyn . KindSequence
if hasComplexType && ! hasComplexValue {
return dyn . InvalidValue , fmt . Errorf ( ` variable %s is of type complex, but the value in the variable file is not a complex type ` , name )
}
if ! hasComplexType && hasComplexValue {
return dyn . InvalidValue , fmt . Errorf ( ` variable %s is not of type complex, but the value in the variable file is a complex type ` , name )
}
v , err := dyn . Set ( v , "value" , fileDefault )
if err != nil {
return dyn . InvalidValue , fmt . Errorf ( ` failed to assign default value from variable file to variable %s with error: %v ` , name , err )
}
return v , nil
}
2023-05-15 09:34:05 +00:00
// case: Set the variable to its default value
2024-07-09 11:12:42 +00:00
if variable . HasDefault ( ) {
vDefault , err := dyn . Get ( v , "default" )
2023-05-15 09:34:05 +00:00
if err != nil {
2024-07-09 11:12:42 +00:00
return dyn . InvalidValue , fmt . Errorf ( ` failed to get default value from config "%s" for variable %s with error: %v ` , variable . Default , name , err )
2023-05-15 09:34:05 +00:00
}
2024-07-09 11:12:42 +00:00
v , err := dyn . Set ( v , "value" , vDefault )
if err != nil {
return dyn . InvalidValue , fmt . Errorf ( ` failed to assign default value from config "%s" to variable %s with error: %v ` , variable . Default , name , err )
}
return v , nil
2023-05-15 09:34:05 +00:00
}
// We should have had a value to set for the variable at this point.
2025-01-23 14:35:33 +00:00
return dyn . InvalidValue , fmt . Errorf ( ` no value assigned to required variable %s. Assignment can be done using "--var", by setting the %s environment variable, or in %s file ` , name , bundleVarPrefix + name , getDefaultVariableFilePath ( "<target>" ) )
}
func readVariablesFromFile ( b * bundle . Bundle ) ( dyn . Value , diag . Diagnostics ) {
var diags diag . Diagnostics
filePath := filepath . Join ( b . BundleRootPath , getDefaultVariableFilePath ( b . Config . Bundle . Target ) )
if _ , err := os . Stat ( filePath ) ; err != nil {
return dyn . InvalidValue , nil
}
f , err := os . ReadFile ( filePath )
if err != nil {
return dyn . InvalidValue , diag . FromErr ( fmt . Errorf ( "failed to read variables file: %w" , err ) )
}
val , err := jsonloader . LoadJSON ( f , filePath )
if err != nil {
return dyn . InvalidValue , diag . FromErr ( fmt . Errorf ( "failed to parse variables file %s: %w" , filePath , err ) )
}
if val . Kind ( ) != dyn . KindMap {
return dyn . InvalidValue , diags . Append ( diag . Diagnostic {
Severity : diag . Error ,
Summary : fmt . Sprintf ( "failed to parse variables file %s: invalid format" , filePath ) ,
Detail : "Variables file must be a JSON object with the following format:\n{\"var1\": \"value1\", \"var2\": \"value2\"}" ,
} )
}
return val , nil
2023-05-15 09:34:05 +00:00
}
2024-03-25 14:18:47 +00:00
func ( m * setVariables ) Apply ( ctx context . Context , b * bundle . Bundle ) diag . Diagnostics {
2025-01-23 14:35:33 +00:00
defaults , diags := readVariablesFromFile ( b )
if diags . HasError ( ) {
return diags
}
2024-07-09 11:12:42 +00:00
err := b . Config . Mutate ( func ( v dyn . Value ) ( dyn . Value , error ) {
return dyn . Map ( v , "variables" , dyn . Foreach ( func ( p dyn . Path , variable dyn . Value ) ( dyn . Value , error ) {
name := p [ 1 ] . Key ( )
v , ok := b . Config . Variables [ name ]
if ! ok {
return dyn . InvalidValue , fmt . Errorf ( ` variable "%s" is not defined ` , name )
}
2025-01-23 14:35:33 +00:00
fileDefault , _ := dyn . Get ( defaults , name )
return setVariable ( ctx , variable , v , name , fileDefault )
2024-07-09 11:12:42 +00:00
} ) )
} )
2025-01-23 14:35:33 +00:00
return diags . Extend ( diag . FromErr ( err ) )
2023-05-15 09:34:05 +00:00
}