Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft Config Refactor #320

Merged
merged 23 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 24 additions & 27 deletions cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,21 +265,14 @@ func (cc *createCmd) generateDockerfile(langConfig *config.DraftConfig, lowerLan
return err
}

// Check for existing duplicate defualts
// Check for existing duplicate defaults
for k, v := range extractedValues {
variableExists := false
for i, varD := range langConfig.VariableDefaults {
if k == varD.Name {
variableExists = true
langConfig.VariableDefaults[i].Value = v
break
}
}
if !variableExists {
langConfig.VariableDefaults = append(langConfig.VariableDefaults, config.BuilderVarDefault{
Name: k,
if builderVar, ok := langConfig.Variables[k]; ok {
builderVar.Value = v
} else {
langConfig.Variables[k] = config.BuilderVar{
Value: v,
})
}
}
}

Expand All @@ -290,7 +283,7 @@ func (cc *createCmd) generateDockerfile(langConfig *config.DraftConfig, lowerLan
return err
}
} else {
inputs, err = validateConfigInputsToPrompts(langConfig.Variables, cc.createConfig.LanguageVariables, langConfig.VariableDefaults)
inputs, err = validateConfigInputsToPrompts(langConfig.Variables, cc.createConfig.LanguageVariables)
if err != nil {
return err
}
Expand Down Expand Up @@ -328,7 +321,7 @@ func (cc *createCmd) createDeployment() error {
if deployConfig == nil {
return errors.New("invalid deployment type")
}
customInputs, err = validateConfigInputsToPrompts(deployConfig.Variables, cc.createConfig.DeployVariables, deployConfig.VariableDefaults)
customInputs, err = validateConfigInputsToPrompts(deployConfig.Variables, cc.createConfig.DeployVariables)
if err != nil {
return err
}
Expand Down Expand Up @@ -462,7 +455,7 @@ func init() {
rootCmd.AddCommand(newCreateCmd())
}

func validateConfigInputsToPrompts(required []config.BuilderVar, provided []UserInputs, defaults []config.BuilderVarDefault) (map[string]string, error) {
func validateConfigInputsToPrompts(required map[string]config.BuilderVar, provided []UserInputs) (map[string]string, error) {
customInputs := make(map[string]string)

// set inputs to provided values
Expand All @@ -471,24 +464,28 @@ func validateConfigInputsToPrompts(required []config.BuilderVar, provided []User
}

// fill in missing vars using variable default references
for _, variableDefault := range defaults {
if customInputs[variableDefault.Name] == "" && variableDefault.ReferenceVar != "" {
log.Debugf("variable %s is empty, using default referenceVar value from %s", variableDefault.Name, variableDefault.ReferenceVar)
customInputs[variableDefault.Name] = customInputs[variableDefault.ReferenceVar]
for name, variable := range required {
if customInputs[name] == "" && variable.ReferenceVar != "" {
if _, ok := customInputs[variable.ReferenceVar]; !ok {
log.Debugf("reference variable %s is empty, adding it in", variable.ReferenceVar)
customInputs[variable.ReferenceVar] = required[variable.ReferenceVar].DefaultValue
}
log.Debugf("variable %s is empty, using default referenceVar value from %s", name, variable.ReferenceVar)
customInputs[name] = customInputs[variable.ReferenceVar]
}
}

// fill in missing vars using variable default values
for _, variableDefault := range defaults {
if customInputs[variableDefault.Name] == "" && variableDefault.Value != "" {
log.Debugf("setting default value for %s to %s", variableDefault.Name, variableDefault.Value)
customInputs[variableDefault.Name] = variableDefault.Value
for name, variable := range required {
if customInputs[name] == "" && variable.DefaultValue != "" {
log.Debugf("variable %s is empty, using default value %s", name, variable.DefaultValue)
customInputs[name] = variable.DefaultValue
}
}

for _, variable := range required {
if _, ok := customInputs[variable.Name]; !ok {
return nil, fmt.Errorf("config missing required variable: %s with description: %s", variable.Name, variable.Description)
for name, variable := range required {
if _, ok := customInputs[name]; !ok {
return nil, fmt.Errorf("config missing required variable: %s with description: %s", name, variable.Description)
}
}

Expand Down
20 changes: 8 additions & 12 deletions cmd/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,33 +142,29 @@ func TestInitConfig(t *testing.T) {
}

func TestValidateConfigInputsToPromptsPass(t *testing.T) {
required := []config.BuilderVar{
{Name: "REQUIRED_PROVIDED"},
{Name: "REQUIRED_DEFAULTED"},
required := map[string]config.BuilderVar{
"REQUIRED_PROVIDED": {},
"REQUIRED_DEFAULTED": {DefaultValue: "DEFAULT_VALUE"},
}
provided := []UserInputs{
{Name: "REQUIRED_PROVIDED", Value: "PROVIDED_VALUE"},
}
defaults := []config.BuilderVarDefault{
{Name: "REQUIRED_DEFAULTED", Value: "DEFAULT_VALUE"},
}

vars, err := validateConfigInputsToPrompts(required, provided, defaults)
vars, err := validateConfigInputsToPrompts(required, provided)
assert.True(t, err == nil)
assert.Equal(t, vars["REQUIRED_DEFAULTED"], "DEFAULT_VALUE")
}

func TestValidateConfigInputsToPromptsMissing(t *testing.T) {
required := []config.BuilderVar{
{Name: "REQUIRED_PROVIDED"},
{Name: "REQUIRED_MISSING"},
required := map[string]config.BuilderVar{
"REQUIRED_PROVIDED": {},
"REQUIRED_MISSING": {},
}
provided := []UserInputs{
{Name: "REQUIRED_PROVIDED"},
}
defaults := []config.BuilderVarDefault{}

_, err := validateConfigInputsToPrompts(required, provided, defaults)
_, err := validateConfigInputsToPrompts(required, provided)
assert.NotNil(t, err)
}

Expand Down
4 changes: 2 additions & 2 deletions cmd/setup-gh.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func fillSetUpConfig(sc *providers.SetUpCmd) error {
return fmt.Errorf("getting subscription labels: %w", err)
}

sc.SubscriptionID, err = GetAzSubscriptionId(subLabels, currentSub)
sc.SubscriptionID, err = getAzSubscriptionId(subLabels, currentSub)
if err != nil {
return fmt.Errorf("getting subscription ID: %w", err)
}
Expand Down Expand Up @@ -223,7 +223,7 @@ func getCloudProvider() string {
return selectResponse
}

func GetAzSubscriptionId(subLabels []providers.SubLabel, currentSub providers.SubLabel) (string, error) {
func getAzSubscriptionId(subLabels []providers.SubLabel, currentSub providers.SubLabel) (string, error) {
subLabel, err := prompts.Select("Please choose the subscription ID you would like to use", subLabels, &prompts.SelectOpt[providers.SubLabel]{
Field: func(subLabel providers.SubLabel) string {
return subLabel.Name + " (" + subLabel.ID + ")"
Expand Down
56 changes: 36 additions & 20 deletions pkg/config/draftconfig.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package config

import (
"errors"

log "github.com/sirupsen/logrus"
)

// TODO: remove Name Overrides since we don't need them anymore
type DraftConfig struct {
DisplayName string `yaml:"displayName"`
NameOverrides []FileNameOverride `yaml:"nameOverrides"`
Variables []BuilderVar `yaml:"variables"`
VariableDefaults []BuilderVarDefault `yaml:"variableDefaults"`
DisplayName string `yaml:"displayName"`
NameOverrides []FileNameOverride `yaml:"nameOverrides"`
Variables map[string]BuilderVar `yaml:"variables"`
meecethereese marked this conversation as resolved.
Show resolved Hide resolved

nameOverrideMap map[string]string
}
Expand All @@ -20,26 +21,24 @@ type FileNameOverride struct {
}

type BuilderVar struct {
Name string `yaml:"name"`
DefaultValue string `yaml:"default"`
Description string `yaml:"description"`
VarType string `yaml:"type"`
ExampleValues []string `yaml:"exampleValues"`
}

type BuilderVarDefault struct {
Name string `yaml:"name"`
Value string `yaml:"value"`
ReferenceVar string `yaml:"referenceVar"`
IsPromptDisabled bool `yaml:"disablePrompt"`
IsPromptDisabled bool `yaml:"disablePrompt"`
meecethereese marked this conversation as resolved.
Show resolved Hide resolved
ReferenceVar string `yaml:"referenceVar"`
ResourceType string `yaml:"resource"`
Type string `yaml:"type"`
Value string `yaml:"value"`
}

func (d *DraftConfig) GetVariableExampleValues() map[string][]string {
variableExampleValues := make(map[string][]string)
for _, variable := range d.Variables {
for name, variable := range d.Variables {
if len(variable.ExampleValues) > 0 {
variableExampleValues[variable.Name] = variable.ExampleValues
variableExampleValues[name] = variable.ExampleValues
}
}

return variableExampleValues
}

Expand All @@ -65,17 +64,34 @@ func (d *DraftConfig) GetNameOverride(path string) string {
}

// ApplyDefaultVariables will apply the defaults to variables that are not already set
func (d *DraftConfig) ApplyDefaultVariables(customConfig map[string]string) {
for _, variable := range d.VariableDefaults {
func (d *DraftConfig) ApplyDefaultVariables(customConfig map[string]string) error {
for name, variable := range d.Variables {
// handle where variable is not set or is set to an empty string from cli handling
if defaultVal, ok := customConfig[variable.Name]; !ok || defaultVal == "" {
log.Infof("Variable %s defaulting to value %s", variable.Name, variable.Value)
customConfig[variable.Name] = variable.Value
if val, ok := customConfig[name]; !ok || val == "" {
if variable.DefaultValue == "" && name != "DOCKERFILE" {
return errors.New("variable " + name + " has no default value")
}
log.Infof("Variable %s defaulting to value %s", name, variable.DefaultValue)
customConfig[name] = variable.DefaultValue
}
}

return nil
}

// TemplateVariableRecorder is an interface for recording variables that are used read using draft configs
type TemplateVariableRecorder interface {
Record(key, value string)
}

func (d *DraftConfig) VariableMap() (map[string]string, error) {
varMap := make(map[string]string)
for name, variable := range d.Variables {
if variable.Value == "" {
return nil, errors.New("variable " + name + " has no default value")
}
varMap[name] = variable.Value
}

return varMap, nil
}
8 changes: 4 additions & 4 deletions pkg/osutil/osutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,12 @@ func CopyDir(
return err
}
} else {
fileContent, err := replaceTemplateVariables(fileSys, srcPath, customInputs)
fileContent, err := ReplaceTemplateVariables(fileSys, srcPath, customInputs)
if err != nil {
return err
}

if err = checkAllVariablesSubstituted(string(fileContent)); err != nil {
if err = CheckAllVariablesSubstituted(string(fileContent)); err != nil {
return fmt.Errorf("error substituting file %s: %w", srcPath, err)
}

Expand All @@ -131,15 +131,15 @@ If any draft variables are found, an error is returned.
Draft variables are defined as a string of non-whitespace characters starting with a non-period character wrapped in double curly braces.
The non-period first character constraint is used to avoid matching helm template functions.
*/
func checkAllVariablesSubstituted(fileContent string) error {
func CheckAllVariablesSubstituted(fileContent string) error {
if unsubstitutedVars := draftVariableRegex.FindAllString(fileContent, -1); len(unsubstitutedVars) > 0 {
unsubstitutedVarsString := strings.Join(unsubstitutedVars, ", ")
return fmt.Errorf("unsubstituted variable: %s", unsubstitutedVarsString)
}
return nil
}

func replaceTemplateVariables(fileSys fs.FS, srcPath string, customInputs map[string]string) ([]byte, error) {
func ReplaceTemplateVariables(fileSys fs.FS, srcPath string, customInputs map[string]string) ([]byte, error) {
file, err := fs.ReadFile(fileSys, srcPath)
if err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion pkg/osutil/osutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func TestAllVariablesSubstituted(t *testing.T) {

for _, test := range tests {
t.Run(test.String, func(t *testing.T) {
err := checkAllVariablesSubstituted(test.String)
err := CheckAllVariablesSubstituted(test.String)
didError := err != nil
assert.Equal(t, test.ExpectError, didError)
})
Expand Down
72 changes: 27 additions & 45 deletions pkg/prompts/prompts.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,74 +31,56 @@ func RunPromptsFromConfigWithSkipsIO(config *config.DraftConfig, varsToSkip []st

inputs := make(map[string]string)

for _, customPrompt := range config.Variables {
promptVariableName := customPrompt.Name
if _, ok := skipMap[promptVariableName]; ok {
log.Debugf("Skipping prompt for %s", promptVariableName)
for name, variable := range config.Variables {
if val, ok := skipMap[name]; ok && val != "" {
log.Debugf("Skipping prompt for %s", name)
continue
}
if GetIsPromptDisabled(customPrompt.Name, config.VariableDefaults) {
log.Debugf("Skipping prompt for %s as it has IsPromptDisabled=true", promptVariableName)
noPromptDefaultValue := GetVariableDefaultValue(promptVariableName, config.VariableDefaults, inputs)
if noPromptDefaultValue == "" {
return nil, fmt.Errorf("IsPromptDisabled is true for %s but no default value was found", promptVariableName)

if variable.IsPromptDisabled {
log.Debugf("Skipping prompt for %s as it has IsPromptDisabled=true", name)
noPromptDefaultValue := GetVariableDefaultValue(name, variable, inputs)
if noPromptDefaultValue == "" && name != "DOCKERFILE" {
return nil, fmt.Errorf("IsPromptDisabled is true for %s but no default value was found", name)
}
log.Debugf("Using default value %s for %s", noPromptDefaultValue, promptVariableName)
inputs[promptVariableName] = noPromptDefaultValue
log.Debugf("Using default value %s for %s", noPromptDefaultValue, name)
inputs[name] = noPromptDefaultValue
continue
}

log.Debugf("constructing prompt for: %s", promptVariableName)
if customPrompt.VarType == "bool" {
input, err := RunBoolPrompt(customPrompt, Stdin, Stdout)
log.Debugf("constructing prompt for: %s", name)
if variable.Type == "bool" {
input, err := RunBoolPrompt(variable, Stdin, Stdout)
if err != nil {
return nil, err
}
inputs[promptVariableName] = input
inputs[name] = input
} else {
defaultValue := GetVariableDefaultValue(promptVariableName, config.VariableDefaults, inputs)
defaultValue := GetVariableDefaultValue(name, variable, inputs)

stringInput, err := RunDefaultableStringPrompt(customPrompt, defaultValue, nil, Stdin, Stdout)
stringInput, err := RunDefaultableStringPrompt(variable, defaultValue, nil, Stdin, Stdout)
if err != nil {
return nil, err
}
inputs[promptVariableName] = stringInput
}
}

// Substitute the default value for variables where the user didn't enter anything
for _, variableDefault := range config.VariableDefaults {
if inputs[variableDefault.Name] == "" {
inputs[variableDefault.Name] = variableDefault.Value
inputs[name] = stringInput
}
}

return inputs, nil
}

// GetVariableDefaultValue returns the default value for a variable, if one is set in variableDefaults from a ReferenceVar or literal VariableDefault.Value in that order.
func GetVariableDefaultValue(variableName string, variableDefaults []config.BuilderVarDefault, inputs map[string]string) string {
// GetVariableDefaultValue returns the default value for a variable, if one is set in variableDefaults from a ReferenceVar or literal Variable.DefaultValue in that order.
func GetVariableDefaultValue(variableName string, variable config.BuilderVar, inputs map[string]string) string {
defaultValue := ""
for _, variableDefault := range variableDefaults {
if variableDefault.Name == variableName {
defaultValue = variableDefault.Value
log.Debugf("setting default value for %s to %s from variable default rule", variableName, defaultValue)
if variableDefault.ReferenceVar != "" && inputs[variableDefault.ReferenceVar] != "" {
defaultValue = inputs[variableDefault.ReferenceVar]
log.Debugf("setting default value for %s to %s from referenceVar %s", variableName, defaultValue, variableDefault.ReferenceVar)
}
}
}
return defaultValue
}

func GetIsPromptDisabled(variableName string, variableDefaults []config.BuilderVarDefault) bool {
for _, variableDefault := range variableDefaults {
if variableDefault.Name == variableName {
return variableDefault.IsPromptDisabled
}
defaultValue = variable.DefaultValue
log.Debugf("setting default value for %s to %s from variable default rule", variableName, defaultValue)
if variable.ReferenceVar != "" && inputs[variable.ReferenceVar] != "" {
defaultValue = inputs[variable.ReferenceVar]
log.Debugf("setting default value for %s to %s from referenceVar %s", variableName, defaultValue, variable.ReferenceVar)
}
return false

return defaultValue
}

func RunBoolPrompt(customPrompt config.BuilderVar, Stdin io.ReadCloser, Stdout io.WriteCloser) (string, error) {
Expand Down
Loading
Loading