mirror of
https://github.com/coder/coder.git
synced 2026-06-03 04:58:23 +00:00
bddb808b25
Fixes all our Go file imports to match the preferred spec that we've _mostly_ been using. For example: ``` import ( "context" "time" "github.com/prometheus/client_golang/prometheus" "golang.org/x/xerrors" "gopkg.in/natefinch/lumberjack.v2" "cdr.dev/slog/v3" "github.com/coder/coder/v2/codersdk/agentsdk" "github.com/coder/serpent" ) ``` 3 groups: standard library, 3rd partly libs, Coder libs. This PR makes the change across the codebase. The PR in the stack above modifies our formatting to maintain this state of affairs, and is a separate PR so it's possible to review that one in detail.
273 lines
6.7 KiB
Go
273 lines
6.7 KiB
Go
package codersdk
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/hcl/v2/hclparse"
|
|
"github.com/zclconf/go-cty/cty"
|
|
"golang.org/x/xerrors"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
/**
|
|
* DiscoverVarsFiles function loads vars files in a predefined order:
|
|
* 1. terraform.tfvars
|
|
* 2. terraform.tfvars.json
|
|
* 3. *.auto.tfvars
|
|
* 4. *.auto.tfvars.json
|
|
*/
|
|
func DiscoverVarsFiles(workDir string) ([]string, error) {
|
|
var found []string
|
|
|
|
fi, err := os.Stat(filepath.Join(workDir, "terraform.tfvars"))
|
|
if err == nil {
|
|
found = append(found, filepath.Join(workDir, fi.Name()))
|
|
} else if !os.IsNotExist(err) {
|
|
return nil, err
|
|
}
|
|
|
|
fi, err = os.Stat(filepath.Join(workDir, "terraform.tfvars.json"))
|
|
if err == nil {
|
|
found = append(found, filepath.Join(workDir, fi.Name()))
|
|
} else if !os.IsNotExist(err) {
|
|
return nil, err
|
|
}
|
|
|
|
dirEntries, err := os.ReadDir(workDir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, dirEntry := range dirEntries {
|
|
if strings.HasSuffix(dirEntry.Name(), ".auto.tfvars") || strings.HasSuffix(dirEntry.Name(), ".auto.tfvars.json") {
|
|
found = append(found, filepath.Join(workDir, dirEntry.Name()))
|
|
}
|
|
}
|
|
return found, nil
|
|
}
|
|
|
|
func ParseUserVariableValues(varsFiles []string, variablesFile string, commandLineVariables []string) ([]VariableValue, error) {
|
|
fromVars, err := parseVariableValuesFromVarsFiles(varsFiles)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
fromFile, err := parseVariableValuesFromFile(variablesFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
fromCommandLine, err := parseVariableValuesFromCommandLine(commandLineVariables)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return CombineVariableValues(fromVars, fromFile, fromCommandLine), nil
|
|
}
|
|
|
|
func parseVariableValuesFromVarsFiles(varsFiles []string) ([]VariableValue, error) {
|
|
var parsed []VariableValue
|
|
for _, varsFile := range varsFiles {
|
|
content, err := os.ReadFile(varsFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var t []VariableValue
|
|
ext := filepath.Ext(varsFile)
|
|
switch ext {
|
|
case ".tfvars":
|
|
t, err = parseVariableValuesFromHCL(content)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("unable to parse HCL content: %w", err)
|
|
}
|
|
case ".json":
|
|
t, err = parseVariableValuesFromJSON(content)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("unable to parse JSON content: %w", err)
|
|
}
|
|
default:
|
|
return nil, xerrors.Errorf("unexpected tfvars format: %s", ext)
|
|
}
|
|
|
|
parsed = append(parsed, t...)
|
|
}
|
|
return parsed, nil
|
|
}
|
|
|
|
func parseVariableValuesFromHCL(content []byte) ([]VariableValue, error) {
|
|
parser := hclparse.NewParser()
|
|
hclFile, diags := parser.ParseHCL(content, "file.hcl")
|
|
if diags.HasErrors() {
|
|
return nil, diags
|
|
}
|
|
|
|
attrs, diags := hclFile.Body.JustAttributes()
|
|
if diags.HasErrors() {
|
|
return nil, diags
|
|
}
|
|
|
|
stringData := map[string]string{}
|
|
for _, attribute := range attrs {
|
|
ctyValue, diags := attribute.Expr.Value(nil)
|
|
if diags.HasErrors() {
|
|
return nil, diags
|
|
}
|
|
|
|
ctyType := ctyValue.Type()
|
|
switch {
|
|
case ctyType.Equals(cty.String):
|
|
stringData[attribute.Name] = ctyValue.AsString()
|
|
case ctyType.Equals(cty.Number):
|
|
stringData[attribute.Name] = ctyValue.AsBigFloat().String()
|
|
case ctyType.IsTupleType():
|
|
// In case of tuples, Coder only supports the list(string) type.
|
|
var items []string
|
|
var err error
|
|
_ = ctyValue.ForEachElement(func(_, val cty.Value) (stop bool) {
|
|
if !val.Type().Equals(cty.String) {
|
|
err = xerrors.Errorf("unsupported tuple item type: %s ", val.GoString())
|
|
return true
|
|
}
|
|
items = append(items, val.AsString())
|
|
return false
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
m, err := json.Marshal(items)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stringData[attribute.Name] = string(m)
|
|
default:
|
|
return nil, xerrors.Errorf("unsupported value type (name: %s): %s", attribute.Name, ctyType.GoString())
|
|
}
|
|
}
|
|
|
|
return convertMapIntoVariableValues(stringData), nil
|
|
}
|
|
|
|
// parseVariableValuesFromJSON converts the .tfvars.json content into template variables.
|
|
// The function visits only root-level properties as template variables do not support nested
|
|
// structures.
|
|
func parseVariableValuesFromJSON(content []byte) ([]VariableValue, error) {
|
|
var data map[string]interface{}
|
|
err := json.Unmarshal(content, &data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
stringData := map[string]string{}
|
|
for key, value := range data {
|
|
switch value.(type) {
|
|
case string, int, bool:
|
|
stringData[key] = fmt.Sprintf("%v", value)
|
|
default:
|
|
m, err := json.Marshal(value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stringData[key] = string(m)
|
|
}
|
|
}
|
|
|
|
return convertMapIntoVariableValues(stringData), nil
|
|
}
|
|
|
|
func convertMapIntoVariableValues(m map[string]string) []VariableValue {
|
|
var parsed []VariableValue
|
|
for key, value := range m {
|
|
parsed = append(parsed, VariableValue{
|
|
Name: key,
|
|
Value: value,
|
|
})
|
|
}
|
|
sort.Slice(parsed, func(i, j int) bool {
|
|
return parsed[i].Name < parsed[j].Name
|
|
})
|
|
return parsed
|
|
}
|
|
|
|
func parseVariableValuesFromFile(variablesFile string) ([]VariableValue, error) {
|
|
var values []VariableValue
|
|
if variablesFile == "" {
|
|
return values, nil
|
|
}
|
|
|
|
variablesMap, err := createVariablesMapFromFile(variablesFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for name, value := range variablesMap {
|
|
values = append(values, VariableValue{
|
|
Name: name,
|
|
Value: value,
|
|
})
|
|
}
|
|
return values, nil
|
|
}
|
|
|
|
// Reads a YAML file and populates a string -> string map.
|
|
// Throws an error if the file name is empty.
|
|
func createVariablesMapFromFile(variablesFile string) (map[string]string, error) {
|
|
if variablesFile == "" {
|
|
return nil, xerrors.Errorf("variable file name is not specified")
|
|
}
|
|
|
|
variablesMap := make(map[string]string)
|
|
variablesFileContents, err := os.ReadFile(variablesFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = yaml.Unmarshal(variablesFileContents, &variablesMap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return variablesMap, nil
|
|
}
|
|
|
|
func parseVariableValuesFromCommandLine(variables []string) ([]VariableValue, error) {
|
|
var values []VariableValue
|
|
for _, keyValue := range variables {
|
|
split := strings.SplitN(keyValue, "=", 2)
|
|
if len(split) < 2 {
|
|
return nil, xerrors.Errorf("format key=value expected, but got %s", keyValue)
|
|
}
|
|
|
|
values = append(values, VariableValue{
|
|
Name: split[0],
|
|
Value: split[1],
|
|
})
|
|
}
|
|
return values, nil
|
|
}
|
|
|
|
func CombineVariableValues(valuesSets ...[]VariableValue) []VariableValue {
|
|
combinedValues := make(map[string]string)
|
|
|
|
for _, values := range valuesSets {
|
|
for _, v := range values {
|
|
combinedValues[v.Name] = v.Value
|
|
}
|
|
}
|
|
|
|
var result []VariableValue
|
|
for name, value := range combinedValues {
|
|
result = append(result, VariableValue{Name: name, Value: value})
|
|
}
|
|
|
|
sort.Slice(result, func(i, j int) bool {
|
|
return result[i].Name < result[j].Name
|
|
})
|
|
return result
|
|
}
|