- Notifications
You must be signed in to change notification settings - Fork928
feat: Read params from file for template/workspace creation#1541
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
b35b7b3
e69ae31
dc80543
931e7d5
0e6b2d4
8a075f4
71e94e3
d27d202
692b975
1a12be1
1491620
74989ac
e584e81
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -17,6 +17,7 @@ func create() *cobra.Command { | ||
var ( | ||
workspaceName string | ||
templateName string | ||
parameterFile string | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. tfvars is the standard way to handle this with Terraform. I'd prefer to not have our own solution, because that'd remove the ability for users to do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. @kylecarbs that assumes we use terraform, but our cli should be terraform agnostic right? Also There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Developers also won't have the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. We can be Terraform agnostic while still consuming I don't believe it's necessary to have a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Issue#1377 mentions creating workspaces using parameter files, which is why I went with a non-tfvars approach. However,#1426 does talk about doing this for templates. Would you recommend solving both problems using different approaches or only work on#1426 (template creation via file) via ContributorAuthor
| ||
) | ||
cmd := &cobra.Command{ | ||
Annotations: workspaceCommand, | ||
@@ -116,23 +117,33 @@ func create() *cobra.Command { | ||
return err | ||
} | ||
// parameterMapFromFile can be nil if parameter file is not specified | ||
var parameterMapFromFile map[string]string | ||
if parameterFile != "" { | ||
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render("Attempting to read the variables from the parameter file.")+"\r\n") | ||
parameterMapFromFile, err = createParameterMapFromFile(parameterFile) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
disclaimerPrinted := false | ||
parameters := make([]codersdk.CreateParameterRequest, 0) | ||
for _, parameterSchema := range parameterSchemas { | ||
if !parameterSchema.AllowOverrideSource { | ||
continue | ||
} | ||
if !disclaimerPrinted { | ||
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Paragraph.Render("This template has customizable parameters. Values can be changed after create, but may have unintended side effects (like data loss).")+"\r\n") | ||
disclaimerPrinted = true | ||
} | ||
parameterValue, err :=getParameterValueFromMapOrInput(cmd, parameterMapFromFile, parameterSchema) | ||
if err != nil { | ||
return err | ||
} | ||
parameters = append(parameters, codersdk.CreateParameterRequest{ | ||
Name: parameterSchema.Name, | ||
SourceValue:parameterValue, | ||
SourceScheme: codersdk.ParameterSourceSchemeData, | ||
DestinationScheme: parameterSchema.DefaultDestinationScheme, | ||
}) | ||
@@ -194,5 +205,6 @@ func create() *cobra.Command { | ||
} | ||
cliflag.StringVarP(cmd.Flags(), &templateName, "template", "t", "CODER_TEMPLATE_NAME", "", "Specify a template name.") | ||
cliflag.StringVarP(cmd.Flags(), ¶meterFile, "parameter-file", "", "CODER_PARAMETER_FILE", "", "Specify a file path with parameter values.") | ||
return cmd | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package cli | ||
import ( | ||
"os" | ||
"golang.org/x/xerrors" | ||
"gopkg.in/yaml.v3" | ||
"github.com/coder/coder/cli/cliui" | ||
"github.com/coder/coder/codersdk" | ||
"github.com/spf13/cobra" | ||
) | ||
// Reads a YAML file and populates a string -> string map. | ||
// Throws an error if the file name is empty. | ||
func createParameterMapFromFile(parameterFile string) (map[string]string, error) { | ||
if parameterFile != "" { | ||
parameterMap := make(map[string]string) | ||
parameterFileContents, err := os.ReadFile(parameterFile) | ||
if err != nil { | ||
return nil, err | ||
} | ||
err = yaml.Unmarshal(parameterFileContents, ¶meterMap) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return parameterMap, nil | ||
} | ||
return nil, xerrors.Errorf("Parameter file name is not specified") | ||
} | ||
// Returns a parameter value from a given map, if the map exists, else takes input from the user. | ||
// Throws an error if the map exists but does not include a value for the parameter. | ||
func getParameterValueFromMapOrInput(cmd *cobra.Command, parameterMap map[string]string, parameterSchema codersdk.ParameterSchema) (string, error) { | ||
var parameterValue string | ||
if parameterMap != nil { | ||
var ok bool | ||
parameterValue, ok = parameterMap[parameterSchema.Name] | ||
if !ok { | ||
return "", xerrors.Errorf("Parameter value absent in parameter file for %q!", parameterSchema.Name) | ||
} | ||
} else { | ||
var err error | ||
parameterValue, err = cliui.ParameterSchema(cmd, parameterSchema) | ||
if err != nil { | ||
return "", err | ||
} | ||
} | ||
return parameterValue, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package cli | ||
import ( | ||
"os" | ||
"runtime" | ||
"testing" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
func TestCreateParameterMapFromFile(t *testing.T) { | ||
t.Parallel() | ||
t.Run("CreateParameterMapFromFile", func(t *testing.T) { | ||
t.Parallel() | ||
tempDir := t.TempDir() | ||
parameterFile, _ := os.CreateTemp(tempDir, "testParameterFile*.yaml") | ||
_, _ = parameterFile.WriteString("region: \"bananas\"\ndisk: \"20\"\n") | ||
parameterMapFromFile, err := createParameterMapFromFile(parameterFile.Name()) | ||
expectedMap := map[string]string{ | ||
"region": "bananas", | ||
"disk": "20", | ||
} | ||
assert.Equal(t, expectedMap, parameterMapFromFile) | ||
assert.Nil(t, err) | ||
removeTmpDirUntilSuccess(t, tempDir) | ||
}) | ||
t.Run("WithEmptyFilename", func(t *testing.T) { | ||
t.Parallel() | ||
parameterMapFromFile, err := createParameterMapFromFile("") | ||
assert.Nil(t, parameterMapFromFile) | ||
assert.EqualError(t, err, "Parameter file name is not specified") | ||
}) | ||
t.Run("WithInvalidFilename", func(t *testing.T) { | ||
t.Parallel() | ||
parameterMapFromFile, err := createParameterMapFromFile("invalidFile.yaml") | ||
assert.Nil(t, parameterMapFromFile) | ||
// On Unix based systems, it is: `open invalidFile.yaml: no such file or directory` | ||
// On Windows, it is `open invalidFile.yaml: The system cannot find the file specified.` | ||
if runtime.GOOS == "windows" { | ||
assert.EqualError(t, err, "open invalidFile.yaml: The system cannot find the file specified.") | ||
} else { | ||
assert.EqualError(t, err, "open invalidFile.yaml: no such file or directory") | ||
} | ||
}) | ||
t.Run("WithInvalidYAML", func(t *testing.T) { | ||
t.Parallel() | ||
tempDir := t.TempDir() | ||
parameterFile, _ := os.CreateTemp(tempDir, "testParameterFile*.yaml") | ||
_, _ = parameterFile.WriteString("region = \"bananas\"\ndisk = \"20\"\n") | ||
parameterMapFromFile, err := createParameterMapFromFile(parameterFile.Name()) | ||
assert.Nil(t, parameterMapFromFile) | ||
assert.EqualError(t, err, "yaml: unmarshal errors:\n line 1: cannot unmarshal !!str `region ...` into map[string]string") | ||
removeTmpDirUntilSuccess(t, tempDir) | ||
}) | ||
} | ||
// Need this for Windows because of a known issue with Go: | ||
// https://github.com/golang/go/issues/52986 | ||
func removeTmpDirUntilSuccess(t *testing.T, tempDir string) { | ||
t.Helper() | ||
t.Cleanup(func() { | ||
err := os.RemoveAll(tempDir) | ||
for err != nil { | ||
err = os.RemoveAll(tempDir) | ||
} | ||
}) | ||
} |
Uh oh!
There was an error while loading.Please reload this page.