Skip to content

Commit

Permalink
Workflow Template Enhancements (Azure#324)
Browse files Browse the repository at this point in the history
Co-authored-by: Vidya Reddy <[email protected]>
  • Loading branch information
meecethereese and Vidya2606 authored Jun 28, 2024
1 parent 5cf07bd commit 96f500a
Show file tree
Hide file tree
Showing 18 changed files with 499 additions and 136 deletions.
72 changes: 36 additions & 36 deletions .github/workflows/integration-linux.yml

Large diffs are not rendered by default.

24 changes: 6 additions & 18 deletions cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ func (cc *createCmd) generateDockerfile(langConfig *config.DraftConfig, lowerLan
return err
}
} else {
inputs, err = validateConfigInputsToPrompts(langConfig.Variables, cc.createConfig.LanguageVariables)
inputs, err = validateConfigInputsToPrompts(langConfig, cc.createConfig.LanguageVariables)
if err != nil {
return err
}
Expand Down Expand Up @@ -330,7 +330,7 @@ func (cc *createCmd) createDeployment() error {
if deployConfig == nil {
return errors.New("invalid deployment type")
}
customInputs, err = validateConfigInputsToPrompts(deployConfig.Variables, cc.createConfig.DeployVariables)
customInputs, err = validateConfigInputsToPrompts(deployConfig, cc.createConfig.DeployVariables)
if err != nil {
return err
}
Expand Down Expand Up @@ -464,31 +464,19 @@ func init() {
rootCmd.AddCommand(newCreateCmd())
}

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

// set inputs to provided values
for _, variable := range provided {
customInputs[variable.Name] = variable.Value
}

// fill in missing vars using variable default references
for _, variable := range required {
if customInputs[variable.Name] == "" && variable.Default.ReferenceVar != "" {
log.Debugf("variable %s is empty, using default referenceVar value from %s", variable.Name, variable.Default.ReferenceVar)
customInputs[variable.Name] = customInputs[variable.Default.ReferenceVar]
}
}

// fill in missing vars using variable default values
for _, variable := range required {
if customInputs[variable.Name] == "" && variable.Default.Value != "" {
log.Debugf("variable %s is empty, using default value %s", variable.Name, variable.Default.Value)
customInputs[variable.Name] = variable.Default.Value
}
if err := draftConfig.ApplyDefaultVariables(customInputs); err != nil {
return nil, fmt.Errorf("validate config inputs to prompts: %w", err)
}

for _, variable := range required {
for _, variable := range draftConfig.Variables {
value, ok := customInputs[variable.Name]
if !ok {
return nil, fmt.Errorf("config missing required variable: %s with description: %s", variable.Name, variable.Description)
Expand Down
36 changes: 20 additions & 16 deletions cmd/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,40 +142,44 @@ func TestInitConfig(t *testing.T) {
}

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

vars, err := validateConfigInputsToPrompts(required, provided)
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 := config.DraftConfig{
Variables: []config.BuilderVar{
{
Name: "REQUIRED_PROVIDED",
},
{
Name: "REQUIRED_MISSING",
},
},
}
provided := []UserInputs{
{Name: "REQUIRED_PROVIDED"},
}

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

Expand Down
19 changes: 12 additions & 7 deletions cmd/generate-workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,20 @@ with draft on AKS. This command assumes the 'setup-gh' command has been run prop
}

f := cmd.Flags()
f.StringVarP(&gwCmd.workflowConfig.AksClusterName, "cluster-name", "c", emptyDefaultFlagValue, "specify the AKS cluster name")
f.StringVarP(&gwCmd.workflowConfig.AcrName, "registry-name", "r", emptyDefaultFlagValue, "specify the Azure container registry name")
f.StringVar(&gwCmd.workflowConfig.ContainerName, "container-name", emptyDefaultFlagValue, "specify the container image name")
f.StringVarP(&gwCmd.workflowConfig.ResourceGroupName, "resource-group", "g", emptyDefaultFlagValue, "specify the Azure resource group of your AKS cluster")
f.StringVarP(&gwCmd.dest, "destination", "d", currentDirDefaultFlagValue, "specify the path to the project directory")
f.StringVarP(&gwCmd.workflowConfig.BranchName, "branch", "b", emptyDefaultFlagValue, "specify the Github branch to automatically deploy from")
f.StringVar(&gwCmd.deployType, "deploy-type", emptyDefaultFlagValue, "specify the type of deployment")
f.StringArrayVarP(&gwCmd.flagVariables, "variable", "", []string{}, "pass additional variables")
f.StringVarP(&gwCmd.workflowConfig.WorkflowName, "workflow", "w", emptyDefaultFlagValue, "specify the Github workflow name")
f.StringVarP(&gwCmd.workflowConfig.BranchName, "branch", "b", emptyDefaultFlagValue, "specify the Github branch to automatically deploy from")
f.StringVar(&gwCmd.workflowConfig.AcrResourceGroup, "acr-resource-group", emptyDefaultFlagValue, "specify the Azure container registry resource group")
f.StringVarP(&gwCmd.workflowConfig.AcrName, "registry-name", "r", emptyDefaultFlagValue, "specify the Azure container registry name")
f.StringVar(&gwCmd.workflowConfig.ContainerName, "container-name", emptyDefaultFlagValue, "specify the container image name")
f.StringVarP(&gwCmd.workflowConfig.ClusterResourceGroup, "cluster-resource-group", "g", emptyDefaultFlagValue, "specify the Azure resource group of your AKS cluster")
f.StringVarP(&gwCmd.workflowConfig.ClusterName, "cluster-name", "c", emptyDefaultFlagValue, "specify the AKS cluster name")
f.StringVar(&gwCmd.workflowConfig.Dockerfile, "dockerfile", emptyDefaultFlagValue, "specify the path to the Dockerfile")
f.StringVarP(&gwCmd.workflowConfig.BuildContextPath, "build-context-path", "x", emptyDefaultFlagValue, "specify the docker build context path")
f.StringVarP(&gwCmd.workflowConfig.Namespace, "namespace", "n", emptyDefaultFlagValue, "specify the Kubernetes namespace")
f.StringVar(&gwCmd.workflowConfig.PrivateCluster, "private-cluster", emptyDefaultFlagValue, "specify if the AKS cluster is private")
f.StringArrayVarP(&gwCmd.flagVariables, "variable", "", []string{}, "pass additional variables")
gwCmd.templateWriter = &writers.LocalFSWriter{}
return cmd
}
Expand Down Expand Up @@ -109,4 +114,4 @@ func (gwc *generateWorkflowCmd) generateWorkflows(dest string, deployType string
maps.Copy(customInputs, flagValuesMap)

return workflow.CreateWorkflowFiles(deployType, customInputs, templateWriter)
}
}
68 changes: 62 additions & 6 deletions pkg/config/draftconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package config

import (
"errors"
"fmt"

log "github.com/sirupsen/logrus"
)
Expand All @@ -26,6 +27,7 @@ type BuilderVar struct {
Description string `yaml:"description"`
ExampleValues []string `yaml:"exampleValues"`
Type string `yaml:"type"`
Value string `yaml:"value"`
}

type BuilderVarDefault struct {
Expand Down Expand Up @@ -67,21 +69,50 @@ 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) error {
func (d *DraftConfig) ApplyDefaultVariables(customInputs map[string]string) error {
varIdxMap := VariableIdxMap(d.Variables)

for _, variable := range d.Variables {
// handle where variable is not set or is set to an empty string from cli handling
if val, ok := customConfig[variable.Name]; !ok || val == "" {
if variable.Default.Value == "" {
return errors.New("variable " + variable.Name + " has no default value")
if customInputs[variable.Name] == "" {
if variable.Default.ReferenceVar != "" {
defaultVal, err := recurseReferenceVars(d.Variables, d.Variables[varIdxMap[variable.Default.ReferenceVar]], customInputs, varIdxMap, d.Variables[varIdxMap[variable.Default.ReferenceVar]], true)
if err != nil {
return fmt.Errorf("apply default variables: %w", err)
}
log.Infof("Variable %s defaulting to value %s", variable.Name, customInputs[variable.Name])
customInputs[variable.Name] = defaultVal
}

if customInputs[variable.Name] == "" {
if variable.Default.Value != "" {
log.Infof("Variable %s defaulting to value %s", variable.Name, variable.Default.Value)
customInputs[variable.Name] = variable.Default.Value
} else {
return fmt.Errorf("variable %s has no default value", variable.Name)
}
}
log.Infof("Variable %s defaulting to value %s", variable.Name, variable.Default.Value)
customConfig[variable.Name] = variable.Default.Value
}
}

return nil
}

// recurseReferenceVars recursively checks each variable's ReferenceVar if it doesn't have a custom input. If there's no more ReferenceVars, it will return the default value of the last ReferenceVar.
func recurseReferenceVars(variables []BuilderVar, variable BuilderVar, customInputs map[string]string, varIdxMap map[string]int, variableCheck BuilderVar, isFirst bool) (string, error) {
if !isFirst && variable.Name == variableCheck.Name {
return "", errors.New("cyclical reference detected")
}

if customInputs[variable.Name] != "" {
return customInputs[variable.Name], nil
} else if variable.Default.ReferenceVar != "" {
return recurseReferenceVars(variables, variables[varIdxMap[variable.Default.ReferenceVar]], customInputs, varIdxMap, variableCheck, false)
}

return variable.Default.Value, nil
}

func VariableIdxMap(variables []BuilderVar) map[string]int {
varIdxMap := make(map[string]int)

Expand All @@ -92,6 +123,31 @@ func VariableIdxMap(variables []BuilderVar) map[string]int {
return varIdxMap
}

func (d *DraftConfig) VariableMap() (map[string]string, error) {
envArgs := make(map[string]string)

for _, variable := range d.Variables {
envArgs[variable.Name] = variable.Value
}

err := d.ApplyDefaultVariables(envArgs)
if err != nil {
return nil, fmt.Errorf("creating variable map: %w", err)
}

return envArgs, nil
}

func (d *DraftConfig) VariableIdxMap() map[string]int {
varIdxMap := make(map[string]int)

for i, variable := range d.Variables {
varIdxMap[variable.Name] = i
}

return varIdxMap
}

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

0 comments on commit 96f500a

Please sign in to comment.