databricks-cli/project/config.go

172 lines
4.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package project
import (
"errors"
"fmt"
"io/ioutil"
"net/url"
"os"
"path"
"reflect"
"strings"
"github.com/databrickslabs/terraform-provider-databricks/clusters"
"github.com/ghodss/yaml"
gitUrls "github.com/whilp/git-urls"
"gopkg.in/ini.v1"
)
type Isolation string
const (
None Isolation = ""
Soft Isolation = "soft"
)
// ConfigFile is the name of project configuration file
const ConfigFile = "databricks.yml"
type Assertions struct {
Groups []string `json:"groups,omitempty"`
Secrets []string `json:"secrets,omitempty"`
ServicePrincipals []string `json:"service_principals,omitempty"`
}
type Project struct {
Name string `json:"name"` // or do default from folder name?..
Profile string `json:"profile,omitempty"` // rename?
Isolation Isolation `json:"isolation,omitempty"`
// TODO: turn to pointer for the easy YAML marshalling
DevCluster *clusters.Cluster `json:"dev_cluster,omitempty"`
// Assertions defines a list of configurations expected to be applied
// to the workspace by a higher-privileged user (or service principal)
// in order for the deploy command to work, as individual project teams
// in almost all the cases dont have admin privileges on Databricks
// workspaces.
//
// This configuration simplifies the flexibility of individual project
// teams, make jobs deployment easier and portable across environments.
// This configuration block would contain the following entities to be
// created by administrator users or admin-level automation, like Terraform
// and/or SCIM provisioning.
Assertions *Assertions `json:"assertions,omitempty"`
}
func (p Project) IsDevClusterDefined() bool {
return reflect.ValueOf(p.DevCluster).IsZero()
}
// IsDevClusterJustReference denotes reference-only clusters.
// This conflicts with Soft isolation. Happens for cost-restricted projects,
// where there's only a single Shared Autoscaling cluster per workspace and
// general users have no ability to create other iteractive clusters.
func (p *Project) IsDevClusterJustReference() bool {
if p.DevCluster.ClusterName == "" {
return false
}
return reflect.DeepEqual(p.DevCluster, &clusters.Cluster{
ClusterName: p.DevCluster.ClusterName,
})
}
// IsDatabricksProject returns true for folders with `databricks.yml`
// in the parent tree
func IsDatabricksProject() bool {
_, err := findProjectRoot()
return err == nil
}
func loadProjectConf() (prj Project, err error) {
root, err := findProjectRoot()
if err != nil {
return
}
config, err := os.Open(fmt.Sprintf("%s/%s", root, ConfigFile))
if err != nil {
return
}
raw, err := ioutil.ReadAll(config)
if err != nil {
return
}
err = yaml.Unmarshal(raw, &prj)
if err != nil {
return
}
return validateAndApplyProjectDefaults(prj)
}
func validateAndApplyProjectDefaults(prj Project) (Project, error) {
// defaultCluster := clusters.Cluster{
// NodeTypeID: "smallest",
// SparkVersion: "latest",
// AutoterminationMinutes: 30,
// }
return prj, nil
}
func findProjectRoot() (string, error) {
return findDirWithLeaf(ConfigFile)
}
// finds the original git repository the project is cloned from, so that
// we could automatically verify if this project is checked out in repos
// home folder of the user according to recommended best practices. Can
// also be used to determine a good enough default project name.
func getGitOrigin() (*url.URL, error) {
root, err := findDirWithLeaf(".git")
if err != nil {
return nil, err
}
file := fmt.Sprintf("%s/.git/config", root)
gitConfig, err := ini.Load(file)
if err != nil {
return nil, err
}
section := gitConfig.Section(`remote "origin"`)
if section == nil {
return nil, fmt.Errorf("remote `origin` is not defined in %s", file)
}
url := section.Key("url")
if url == nil {
return nil, fmt.Errorf("git origin url is not defined")
}
return gitUrls.Parse(url.Value())
}
// GitRepositoryName returns repository name as last path entry from detected
// git repository up the tree or returns error if it fails to do so.
func GitRepositoryName() (string, error) {
origin, err := getGitOrigin()
if err != nil {
return "", err
}
base := path.Base(origin.Path)
return strings.ReplaceAll(base, ".git", ""), nil
}
func findDirWithLeaf(leaf string) (string, error) {
dir, err := os.Getwd()
if err != nil {
return "", fmt.Errorf("cannot find $PWD: %s", err)
}
for {
_, err = os.Stat(fmt.Sprintf("%s/%s", dir, leaf))
if errors.Is(err, os.ErrNotExist) {
// TODO: test on windows
next := path.Dir(dir)
if dir == next { // or stop at $HOME?..
return "", fmt.Errorf("cannot find %s anywhere", leaf)
}
dir = next
continue
}
if err != nil {
return "", err
}
return dir, nil
}
}