-
Notifications
You must be signed in to change notification settings - Fork 936
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
b35b7b3
Read params from file for template/workspace creation
AbhineetJain e69ae31
Use os.ReadFile
AbhineetJain dc80543
Refactor reading params into a separate module
AbhineetJain 931e7d5
Add comments and unit tests
AbhineetJain 0e6b2d4
Fix merge conflicts and refactor
AbhineetJain 8a075f4
Rename variable
AbhineetJain 71e94e3
Uncomment and fix unit test
AbhineetJain d27d202
Fix comment
AbhineetJain 692b975
Refactor tests
AbhineetJain 1a12be1
Fix unit tests for windows
AbhineetJain 1491620
Fix unit tests for Windows
AbhineetJain 74989ac
Add comments for the hotfix
AbhineetJain e584e81
Merge branch 'main' into 1377-feat-load-parameters-from-file
AbhineetJain File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
}) | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The 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
terraform apply
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The 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
tfvars
is just HCL or yaml. So we could just support multiple file typesThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Developers also won't have the
tf
file when runningcoder workspace create
, only the person who made the template will have thetf
file.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can be Terraform agnostic while still consuming
tfvars
. Latching onto the existing Terraform ecosystem is a much better user experience than forking it with our own parameter format.I don't believe it's necessary to have a
tfvars
equivalent when creating a workspace. Or at least, we haven't seen it as a problem yet.There was a problem hiding this comment.
Choose a reason for hiding this comment
The 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
tfvars
? Let me quickly check how feasible usingtfvars
for template creation is.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a documented flow of how the customers would use
terraform apply
when setting up templates? I tried using thetfvars
file as follows:It did not create a template and calling
coder templates create
after this still asked me for input, so I am not sure what the actual flow should be.