Skip to content

Commit

Permalink
Add support for monorepo-friendly structuring in doppler.yaml setup file
Browse files Browse the repository at this point in the history
  • Loading branch information
watsonian committed Mar 15, 2023
1 parent dd06ffb commit e670e51
Show file tree
Hide file tree
Showing 5 changed files with 296 additions and 102 deletions.
188 changes: 99 additions & 89 deletions pkg/cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package cmd
import (
"errors"
"fmt"
"path/filepath"
"strings"

"github.com/DopplerHQ/cli/pkg/configuration"
Expand Down Expand Up @@ -63,106 +64,115 @@ func setup(cmd *cobra.Command, args []string) {
utils.LogDebugError(err.Unwrap())
}

ignoreRepoConfig :=
// ignore when repo config is blank
(repoConfig.Setup.Project == "" && repoConfig.Setup.Config == "") ||
// ignore when project and config are already specified
(localConfig.EnclaveProject.Source == models.FlagSource.String() && localConfig.EnclaveConfig.Source == models.FlagSource.String())

// default to true so repo config is used on --no-interactive
useRepoConfig := true
if !ignoreRepoConfig && canPromptUser {
useRepoConfig = utils.ConfirmationPrompt("Use settings from repo config file (doppler.yaml)?", true)
}

currentProject := localConfig.EnclaveProject.Value
selectedProject := ""

switch localConfig.EnclaveProject.Source {
case models.FlagSource.String():
selectedProject = localConfig.EnclaveProject.Value
case models.EnvironmentSource.String():
utils.Log(valueFromEnvironmentNotice("DOPPLER_PROJECT"))
selectedProject = localConfig.EnclaveProject.Value
default:
if useRepoConfig && repoConfig.Setup.Project != "" {
utils.Print("Auto-selecting project from repo config file")
selectedProject = repoConfig.Setup.Project
break
for _, repo := range repoConfig.Setup {
expandedPath, _ := filepath.Abs(repo.Path)
scopedConfig = configuration.Get(expandedPath)

ignoreRepoConfig :=
// ignore when repo config is blank
(repo.Project == "" && repo.Config == "") ||
// ignore when project and config are already specified
(localConfig.EnclaveProject.Source == models.FlagSource.String() && localConfig.EnclaveConfig.Source == models.FlagSource.String())

// default to true so repo config is used on --no-interactive
useRepoConfig := true
if !ignoreRepoConfig && canPromptUser {
if len(repoConfig.Setup) > 1 && repo.Path != "" {
useRepoConfig = utils.ConfirmationPrompt(fmt.Sprintf("Use settings from repo config file (doppler.yaml) for %s?", expandedPath), true)
} else {
useRepoConfig = utils.ConfirmationPrompt("Use settings from repo config file (doppler.yaml)?", true)
}
}

projects, httpErr := http.GetProjects(localConfig.APIHost.Value, utils.GetBool(localConfig.VerifyTLS.Value, true), localConfig.Token.Value, 1, 100)
if !httpErr.IsNil() {
utils.HandleError(httpErr.Unwrap(), httpErr.Message)
}
if len(projects) == 0 {
utils.HandleError(errors.New("you do not have access to any projects"))
}
currentProject := localConfig.EnclaveProject.Value
selectedProject := ""

defaultProject := scopedConfig.EnclaveProject.Value
if repoConfig.Setup.Project != "" {
defaultProject = repoConfig.Setup.Project
}

selectedProject = selectProject(projects, defaultProject, canPromptUser)
if selectedProject == "" {
utils.HandleError(errors.New("Invalid project"))
}
}

selectedConfiguredProject := selectedProject == currentProject
selectedConfig := ""

switch localConfig.EnclaveConfig.Source {
case models.FlagSource.String():
selectedConfig = localConfig.EnclaveConfig.Value
case models.EnvironmentSource.String():
utils.Log(valueFromEnvironmentNotice("DOPPLER_CONFIG"))
selectedConfig = localConfig.EnclaveConfig.Value
default:
if useRepoConfig && repoConfig.Setup.Config != "" {
utils.Print("Auto-selecting config from repo config file")
selectedConfig = repoConfig.Setup.Config
break
switch localConfig.EnclaveProject.Source {
case models.FlagSource.String():
selectedProject = localConfig.EnclaveProject.Value
case models.EnvironmentSource.String():
utils.Log(valueFromEnvironmentNotice("DOPPLER_PROJECT"))
selectedProject = localConfig.EnclaveProject.Value
default:
if useRepoConfig && repo.Project != "" {
utils.Print("Auto-selecting project from repo config file")
selectedProject = repo.Project
break
}

projects, httpErr := http.GetProjects(localConfig.APIHost.Value, utils.GetBool(localConfig.VerifyTLS.Value, true), localConfig.Token.Value, 1, 100)
if !httpErr.IsNil() {
utils.HandleError(httpErr.Unwrap(), httpErr.Message)
}
if len(projects) == 0 {
utils.HandleError(errors.New("you do not have access to any projects"))
}

defaultProject := scopedConfig.EnclaveProject.Value
if repo.Project != "" {
defaultProject = repo.Project
}

selectedProject = selectProject(projects, defaultProject, canPromptUser)
if selectedProject == "" {
utils.HandleError(errors.New("Invalid project"))
}
}

configs, apiError := http.GetConfigs(localConfig.APIHost.Value, utils.GetBool(localConfig.VerifyTLS.Value, true), localConfig.Token.Value, selectedProject, "", 1, 100)
if !apiError.IsNil() {
utils.HandleError(apiError.Unwrap(), apiError.Message)
}
if len(configs) == 0 {
utils.Print("You project does not have any configs")
break
}
selectedConfiguredProject := selectedProject == currentProject
selectedConfig := ""

defaultConfig := scopedConfig.EnclaveConfig.Value
if repoConfig.Setup.Config != "" {
defaultConfig = repoConfig.Setup.Config
switch localConfig.EnclaveConfig.Source {
case models.FlagSource.String():
selectedConfig = localConfig.EnclaveConfig.Value
case models.EnvironmentSource.String():
utils.Log(valueFromEnvironmentNotice("DOPPLER_CONFIG"))
selectedConfig = localConfig.EnclaveConfig.Value
default:
if useRepoConfig && repo.Config != "" {
utils.Print("Auto-selecting config from repo config file")
selectedConfig = repo.Config
break
}

configs, apiError := http.GetConfigs(localConfig.APIHost.Value, utils.GetBool(localConfig.VerifyTLS.Value, true), localConfig.Token.Value, selectedProject, "", 1, 100)
if !apiError.IsNil() {
utils.HandleError(apiError.Unwrap(), apiError.Message)
}
if len(configs) == 0 {
utils.Print("You project does not have any configs")
break
}

defaultConfig := scopedConfig.EnclaveConfig.Value
if repo.Config != "" {
defaultConfig = repo.Config
}

selectedConfig = selectConfig(configs, selectedConfiguredProject, defaultConfig, canPromptUser)
if selectedConfig == "" {
utils.HandleError(errors.New("Invalid config"))
}
}

selectedConfig = selectConfig(configs, selectedConfiguredProject, defaultConfig, canPromptUser)
if selectedConfig == "" {
utils.HandleError(errors.New("Invalid config"))
configToSave := map[string]string{
models.ConfigEnclaveProject.String(): selectedProject,
models.ConfigEnclaveConfig.String(): selectedConfig,
}
}

configToSave := map[string]string{
models.ConfigEnclaveProject.String(): selectedProject,
models.ConfigEnclaveConfig.String(): selectedConfig,
}
if saveToken {
configToSave[models.ConfigToken.String()] = localConfig.Token.Value
}
configuration.Set(configuration.Scope, configToSave)

if !utils.Silent {
// do not fetch the LocalConfig since we do not care about env variables or cmd flags
conf := configuration.Get(configuration.Scope)
valuesToPrint := []string{models.ConfigEnclaveConfig.String(), models.ConfigEnclaveProject.String()}
if saveToken {
valuesToPrint = append(valuesToPrint, utils.RedactAuthToken(models.ConfigToken.String()))
configToSave[models.ConfigToken.String()] = localConfig.Token.Value
}
configuration.Set(expandedPath, configToSave)

if !utils.Silent {
// do not fetch the LocalConfig since we do not care about env variables or cmd flags
conf := configuration.Get(expandedPath)
valuesToPrint := []string{models.ConfigEnclaveConfig.String(), models.ConfigEnclaveProject.String()}
if saveToken {
valuesToPrint = append(valuesToPrint, utils.RedactAuthToken(models.ConfigToken.String()))
}
printer.ScopedConfigValues(conf, valuesToPrint, models.ScopedOptionsMap(&conf), utils.OutputJSON, false, false)
}
printer.ScopedConfigValues(conf, valuesToPrint, models.ScopedOptionsMap(&conf), utils.OutputJSON, false, false)
}
}

Expand Down
23 changes: 15 additions & 8 deletions pkg/controllers/repo_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const repoConfigFileName = "doppler.yaml"
const ymlRepoConfigFileName = "doppler.yml"

// RepoConfig Reads the configuration file (doppler.yaml) if exists and returns the set configuration
func RepoConfig() (models.RepoConfig, Error) {
func RepoConfig() (models.MultiRepoConfig, Error) {

repoConfigFile := filepath.Join("./", repoConfigFileName)
ymlRepoConfigFile := filepath.Join("./", ymlRepoConfigFileName)
Expand All @@ -46,21 +46,28 @@ func RepoConfig() (models.RepoConfig, Error) {
var e Error
e.Err = err
e.Message = "Unable to read doppler repo config file"
return models.RepoConfig{}, e
return models.MultiRepoConfig{}, e
}

var repoConfig models.RepoConfig
var repoConfig models.MultiRepoConfig

if err := yaml.Unmarshal(yamlFile, &repoConfig); err != nil {
var e Error
e.Err = err
e.Message = "Unable to parse doppler repo config file"
return models.RepoConfig{}, e
// Try parsing old repoConfig format (i.e., no slice) for backwards compatibility
var oldRepoConfig models.RepoConfig
if err := yaml.Unmarshal(yamlFile, &oldRepoConfig); err != nil {
var e Error
e.Err = err
e.Message = "Unable to parse doppler repo config file"
return models.MultiRepoConfig{}, e
} else {
repoConfig.Setup = append(repoConfig.Setup, oldRepoConfig.Setup)
return repoConfig, Error{}
}
}

return repoConfig, Error{}
} else if utils.Exists(ymlRepoConfigFile) {
utils.LogWarning(fmt.Sprintf("Found %s file, please rename to %s for repo configuration", ymlRepoConfigFile, repoConfigFileName))
}
return models.RepoConfig{}, Error{}
return models.MultiRepoConfig{}, Error{}
}
21 changes: 16 additions & 5 deletions pkg/models/repo_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,21 @@ limitations under the License.

package models

// RepoConfig holds all repo configuration
// Config struct represents the basic project setup values
type Config struct {
Config string `yaml:"config"`
Project string `yaml:"project"`
Path string `yaml:"path"`
}

// RepoConfig struct representing legacy doppler.yaml setup file format
// that only supported a single project and config
type RepoConfig struct {
Setup struct {
Config string `yaml:"config"`
Project string `yaml:"project"`
} `yaml:"setup"`
Setup Config `yaml:"setup"`
}

// MultiRepoConfig struct supports doppler.yaml files containing multiple
// project and config combos
type MultiRepoConfig struct {
Setup []Config `yaml:"setup"`
}
1 change: 1 addition & 0 deletions tests/e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export DOPPLER_CONFIG="e2e"
"$DIR/e2e/install-sh-update-in-place.sh"
"$DIR/e2e/legacy-commands.sh"
"$DIR/e2e/analytics.sh"
"$DIR/e2e/setup.sh"

echo -e "\nAll tests completed successfully!"
exit 0
Loading

0 comments on commit e670e51

Please sign in to comment.