2024-09-10 13:55:18 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"reflect"
|
|
|
|
|
|
|
|
"github.com/databricks/cli/bundle/config"
|
2024-10-07 15:13:42 +00:00
|
|
|
"github.com/databricks/cli/bundle/config/resources"
|
2024-09-10 13:55:18 +00:00
|
|
|
"github.com/databricks/cli/bundle/config/variable"
|
|
|
|
"github.com/databricks/cli/libs/jsonschema"
|
2024-10-07 15:13:42 +00:00
|
|
|
"github.com/databricks/databricks-sdk-go/service/jobs"
|
2024-09-10 13:55:18 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func interpolationPattern(s string) string {
|
|
|
|
return fmt.Sprintf(`\$\{(%s(\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\[[0-9]+\])*)+)\}`, s)
|
|
|
|
}
|
|
|
|
|
|
|
|
func addInterpolationPatterns(typ reflect.Type, s jsonschema.Schema) jsonschema.Schema {
|
|
|
|
if typ == reflect.TypeOf(config.Root{}) || typ == reflect.TypeOf(variable.Variable{}) {
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2024-09-10 14:49:34 +00:00
|
|
|
// The variables block in a target override allows for directly specifying
|
|
|
|
// the value of the variable.
|
|
|
|
if typ == reflect.TypeOf(variable.TargetVariable{}) {
|
|
|
|
return jsonschema.Schema{
|
|
|
|
AnyOf: []jsonschema.Schema{
|
|
|
|
// We keep the original schema so that autocomplete suggestions
|
|
|
|
// continue to work.
|
|
|
|
s,
|
|
|
|
// All values are valid for a variable value, be it primitive types
|
|
|
|
// like string/bool or complex ones like objects/arrays. Thus we override
|
|
|
|
// the schema to allow all valid JSON values.
|
|
|
|
{},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-10 13:55:18 +00:00
|
|
|
switch s.Type {
|
|
|
|
case jsonschema.ArrayType, jsonschema.ObjectType:
|
|
|
|
// arrays and objects can have complex variable values specified.
|
|
|
|
return jsonschema.Schema{
|
|
|
|
AnyOf: []jsonschema.Schema{
|
|
|
|
s,
|
|
|
|
{
|
|
|
|
Type: jsonschema.StringType,
|
|
|
|
Pattern: interpolationPattern("var"),
|
|
|
|
}},
|
|
|
|
}
|
|
|
|
case jsonschema.IntegerType, jsonschema.NumberType, jsonschema.BooleanType:
|
|
|
|
// primitives can have variable values, or references like ${bundle.xyz}
|
|
|
|
// or ${workspace.xyz}
|
|
|
|
return jsonschema.Schema{
|
|
|
|
AnyOf: []jsonschema.Schema{
|
|
|
|
s,
|
|
|
|
{Type: jsonschema.StringType, Pattern: interpolationPattern("resources")},
|
|
|
|
{Type: jsonschema.StringType, Pattern: interpolationPattern("bundle")},
|
|
|
|
{Type: jsonschema.StringType, Pattern: interpolationPattern("workspace")},
|
|
|
|
{Type: jsonschema.StringType, Pattern: interpolationPattern("artifacts")},
|
|
|
|
{Type: jsonschema.StringType, Pattern: interpolationPattern("var")},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-07 15:13:42 +00:00
|
|
|
func removeJobsFields(typ reflect.Type, s jsonschema.Schema) jsonschema.Schema {
|
|
|
|
switch typ {
|
|
|
|
case reflect.TypeOf(resources.Job{}):
|
|
|
|
// This field has been deprecated in jobs API v2.1 and is always set to
|
|
|
|
// "MULTI_TASK" in the backend. We should not expose it to the user.
|
|
|
|
delete(s.Properties, "format")
|
|
|
|
|
|
|
|
// These fields are only meant to be set by the DABs client (ie the CLI)
|
|
|
|
// and thus should not be exposed to the user. These are used to annotate
|
|
|
|
// jobs that were created by DABs.
|
|
|
|
delete(s.Properties, "deployment")
|
|
|
|
delete(s.Properties, "edit_mode")
|
|
|
|
|
|
|
|
case reflect.TypeOf(jobs.GitSource{}):
|
|
|
|
// These fields are readonly and are not meant to be set by the user.
|
|
|
|
delete(s.Properties, "job_source")
|
|
|
|
delete(s.Properties, "git_snapshot")
|
|
|
|
|
|
|
|
default:
|
|
|
|
// Do nothing
|
|
|
|
}
|
|
|
|
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2024-12-04 11:05:54 +00:00
|
|
|
// While volume_type is required in the volume create API, DABs automatically sets
|
|
|
|
// it's value to "MANAGED" if it's not provided. Thus, we make it optional
|
|
|
|
// in the bundle schema.
|
|
|
|
func makeVolumeTypeOptional(typ reflect.Type, s jsonschema.Schema) jsonschema.Schema {
|
|
|
|
if typ != reflect.TypeOf(resources.Volume{}) {
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
res := []string{}
|
|
|
|
for _, r := range s.Required {
|
|
|
|
if r != "volume_type" {
|
|
|
|
res = append(res, r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
s.Required = res
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2024-09-10 13:55:18 +00:00
|
|
|
func main() {
|
|
|
|
if len(os.Args) != 2 {
|
|
|
|
fmt.Println("Usage: go run main.go <output-file>")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Output file, where the generated JSON schema will be written to.
|
|
|
|
outputFile := os.Args[1]
|
|
|
|
|
|
|
|
// Input file, the databricks openapi spec.
|
|
|
|
inputFile := os.Getenv("DATABRICKS_OPENAPI_SPEC")
|
|
|
|
if inputFile == "" {
|
|
|
|
log.Fatal("DATABRICKS_OPENAPI_SPEC environment variable not set")
|
|
|
|
}
|
|
|
|
|
|
|
|
p, err := newParser(inputFile)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate the JSON schema from the bundle Go struct.
|
|
|
|
s, err := jsonschema.FromType(reflect.TypeOf(config.Root{}), []func(reflect.Type, jsonschema.Schema) jsonschema.Schema{
|
|
|
|
p.addDescriptions,
|
|
|
|
p.addEnums,
|
2024-10-07 15:13:42 +00:00
|
|
|
removeJobsFields,
|
2024-12-04 11:05:54 +00:00
|
|
|
makeVolumeTypeOptional,
|
2024-09-10 13:55:18 +00:00
|
|
|
addInterpolationPatterns,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
b, err := json.MarshalIndent(s, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write the schema descriptions to the output file.
|
|
|
|
err = os.WriteFile(outputFile, b, 0644)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|