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

Add tilt.dev #270

Merged
merged 5 commits into from May 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -9,3 +9,4 @@ Go reference library for parsing and loading Compose files as specified by the
* [compose-ref](https://github.com/compose-spec/compose-ref)
* [containerd/nerdctl](https://github.com/containerd/nerdctl)
* [compose-cli](https://github.com/docker/compose-cli)
* [tilt.dev](https://github.com/tilt-dev/tilt)
85 changes: 38 additions & 47 deletions cli/options.go
Expand Up @@ -145,7 +145,7 @@ func WithDefaultConfigPath(o *ProjectOptions) error {
// WithEnv defines a key=value set of variables used for compose file interpolation
func WithEnv(env []string) ProjectOptionsFn {
return func(o *ProjectOptions) error {
for k, v := range getAsEqualsMap(env) {
for k, v := range utils.GetAsEqualsMap(env) {
o.Environment[k] = v
}
return nil
Expand All @@ -169,7 +169,7 @@ func WithLoadOptions(loadOptions ...func(*loader.Options)) ProjectOptionsFn {

// WithOsEnv imports environment variables from OS
func WithOsEnv(o *ProjectOptions) error {
for k, v := range getAsEqualsMap(os.Environ()) {
for k, v := range utils.GetAsEqualsMap(os.Environ()) {
if _, set := o.Environment[k]; set {
continue
}
Expand All @@ -188,66 +188,76 @@ func WithEnvFile(file string) ProjectOptionsFn {

// WithDotEnv imports environment variables from .env file
func WithDotEnv(o *ProjectOptions) error {
dotEnvFile := o.EnvFile
wd, err := o.GetWorkingDir()
if err != nil {
return err
}
envMap, err := GetEnvFromFile(o.Environment, wd, o.EnvFile)
if err != nil {
return err
}
for k, v := range envMap {
o.Environment[k] = v
}
return nil
}

func GetEnvFromFile(currentEnv map[string]string, workingDir string, filename string) (map[string]string, error) {
envMap := make(map[string]string)

dotEnvFile := filename
if dotEnvFile == "" {
wd, err := o.GetWorkingDir()
if err != nil {
return err
}
dotEnvFile = filepath.Join(wd, ".env")
dotEnvFile = filepath.Join(workingDir, ".env")
}
abs, err := filepath.Abs(dotEnvFile)
if err != nil {
return err
return envMap, err
}
dotEnvFile = abs

s, err := os.Stat(dotEnvFile)
if os.IsNotExist(err) {
if o.EnvFile != "" {
return errors.Errorf("Couldn't find env file: %s", o.EnvFile)
if filename != "" {
return nil, errors.Errorf("Couldn't find env file: %s", filename)
}
return nil
return envMap, nil
}
if err != nil {
return err
return envMap, err
}

if s.IsDir() {
if o.EnvFile == "" {
return nil
if filename == "" {
return envMap, nil
}
return errors.Errorf("%s is a directory", dotEnvFile)
return envMap, errors.Errorf("%s is a directory", dotEnvFile)
}

file, err := os.Open(dotEnvFile)
if err != nil {
return err
return envMap, err
}
defer file.Close()

notInEnvSet := make(map[string]interface{})
env, err := dotenv.ParseWithLookup(file, func(k string) (string, bool) {
v, ok := o.Environment[k]
v, ok := currentEnv[k]
if !ok {
notInEnvSet[k] = nil
return "", true

return "", false
}
return v, true
})
if err != nil {
return err
return envMap, err
}
for k, v := range env {
if _, ok := notInEnvSet[k]; ok {
if _, set := currentEnv[k]; set {
continue
}
if _, set := o.Environment[k]; set {
continue
}
o.Environment[k] = v
envMap[k] = v
}
return nil

return envMap, nil
}

// WithInterpolation set ProjectOptions to enable/skip interpolation
Expand Down Expand Up @@ -416,22 +426,3 @@ func absolutePaths(p []string) ([]string, error) {
}
return paths, nil
}

// getAsEqualsMap split key=value formatted strings into a key : value map
func getAsEqualsMap(em []string) map[string]string {
m := make(map[string]string)
for _, v := range em {
kv := strings.SplitN(v, "=", 2)
m[kv[0]] = kv[1]
}
return m
}

// getAsEqualsMap format a key : value map into key=value strings
func getAsStringList(em map[string]string) []string {
m := make([]string, 0, len(em))
for k, v := range em {
m = append(m, fmt.Sprintf("%s=%s", k, v))
}
return m
}
5 changes: 3 additions & 2 deletions cli/options_test.go
Expand Up @@ -23,6 +23,7 @@ import (
"testing"

"github.com/compose-spec/compose-go/consts"
"github.com/compose-spec/compose-go/utils"
"gotest.tools/v3/assert"
)

Expand Down Expand Up @@ -231,8 +232,8 @@ func TestProjectNameFromWorkingDir(t *testing.T) {
func TestEnvMap(t *testing.T) {
m := map[string]string{}
m["foo"] = "bar"
l := getAsStringList(m)
l := utils.GetAsStringList(m)
assert.Equal(t, l[0], "foo=bar")
m = getAsEqualsMap(l)
m = utils.GetAsEqualsMap(l)
assert.Equal(t, m["foo"], "bar")
}
31 changes: 31 additions & 0 deletions dotenv/godotenv_test.go
Expand Up @@ -612,3 +612,34 @@ func TestExpendingEnvironmentWithLookup(t *testing.T) {
t.Errorf("Expected '%v' to parse as '%v' => '%v', got '%v' => '%v' instead", rawEnvLine, key, expectedValue, key, value)
}
}

func TestSubstitutionsWithShellEnvPrecedence(t *testing.T) {
os.Clearenv()
const envKey = "OPTION_A"
const envVal = "5"
os.Setenv(envKey, envVal)
defer os.Unsetenv(envKey)

envFileName := "fixtures/substitutions.env"
expectedValues := map[string]string{
"OPTION_A": "5",
"OPTION_B": "5",
"OPTION_C": "5",
"OPTION_D": "55",
"OPTION_E": "",
}

envMap, err := ReadWithLookup(os.LookupEnv, envFileName)
if err != nil {
t.Error("Error reading file")
}
if len(envMap) != len(expectedValues) {
t.Error("Didn't get the right size map back")
}

for key, value := range expectedValues {
if envMap[key] != value {
t.Errorf("Read got one of the keys wrong, [%q]->%q", key, envMap[key])
}
}
}
9 changes: 6 additions & 3 deletions dotenv/parser.go
Expand Up @@ -18,6 +18,9 @@ const (

func parseBytes(src []byte, out map[string]string, lookupFn LookupFn) error {
cutset := src
if lookupFn == nil {
lookupFn = noLookupFn
}
for {
cutset = getStatementStart(cutset)
if cutset == nil {
Expand All @@ -34,9 +37,6 @@ func parseBytes(src []byte, out map[string]string, lookupFn LookupFn) error {
}

if inherited {
if lookupFn == nil {
lookupFn = noLookupFn
}

value, ok := lookupFn(key)
if ok {
Expand All @@ -50,6 +50,9 @@ func parseBytes(src []byte, out map[string]string, lookupFn LookupFn) error {
if err != nil {
return err
}
if lookUpValue, ok := lookupFn(key); ok {
value = lookUpValue
}

out[key] = value
cutset = left
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -7,7 +7,7 @@ require (
github.com/docker/go-connections v0.4.0
github.com/docker/go-units v0.4.0
github.com/google/go-cmp v0.5.5
github.com/imdario/mergo v0.3.12
github.com/imdario/mergo v0.3.13
github.com/mattn/go-shellwords v1.0.12
github.com/mitchellh/mapstructure v1.5.0
github.com/opencontainers/go-digest v1.0.0
Expand Down
7 changes: 4 additions & 3 deletions go.sum
Expand Up @@ -46,8 +46,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
Expand Down Expand Up @@ -165,8 +165,9 @@ gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789 h1:NMiUjDZiD6qDVeBOzpImftxX
gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.2.0 h1:I0DwBVMGAx26dttAj1BtJLAkVGncrkkUXfJLC4Flt/I=
gotest.tools/v3 v3.2.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A=
2 changes: 2 additions & 0 deletions loader/full-example.yml
Expand Up @@ -405,6 +405,7 @@ configs:
external: true
config4:
name: foo
file: ~/config_data
x-bar: baz
x-foo: bar

Expand All @@ -420,6 +421,7 @@ secrets:
external: true
secret4:
name: bar
environment: BAR
x-bar: baz
x-foo: bar
x-bar: baz
Expand Down
26 changes: 12 additions & 14 deletions loader/full-struct_test.go
Expand Up @@ -31,7 +31,7 @@ func fullExampleConfig(workingDir, homeDir string) *types.Config {
Services: services(workingDir, homeDir),
Networks: networks(),
Volumes: volumes(),
Configs: configs(workingDir),
Configs: configs(workingDir, homeDir),
Secrets: secrets(workingDir),
Extensions: map[string]interface{}{
"x-foo": "bar",
Expand Down Expand Up @@ -165,7 +165,7 @@ func services(workingDir, homeDir string) []types.ServiceConfig {
Entrypoint: []string{"/code/entrypoint.sh", "-p", "3000"},
Environment: map[string]*string{
"FOO": strPtr("foo_from_env_file"),
"BAR": strPtr("bar_from_env_file_2"),
"BAR": strPtr("this is a secret"),
"BAZ": strPtr("baz_from_service_def"),
"QUX": strPtr("qux_from_environment"),
},
Expand Down Expand Up @@ -520,7 +520,7 @@ func volumes() map[string]types.VolumeConfig {
}
}

func configs(workingDir string) map[string]types.ConfigObjConfig {
func configs(workingDir string, homeDir string) map[string]types.ConfigObjConfig {
return map[string]types.ConfigObjConfig{
"config1": {
File: filepath.Join(workingDir, "config_data"),
Expand All @@ -538,7 +538,7 @@ func configs(workingDir string) map[string]types.ConfigObjConfig {
},
"config4": {
Name: "foo",
File: workingDir,
File: filepath.Join(homeDir, "config_data"),
Extensions: map[string]interface{}{
"x-bar": "baz",
"x-foo": "bar",
Expand All @@ -564,8 +564,8 @@ func secrets(workingDir string) map[string]types.SecretConfig {
External: types.External{External: true},
},
"secret4": {
Name: "bar",
File: workingDir,
Name: "bar",
Environment: "BAR",
Extensions: map[string]interface{}{
"x-bar": "baz",
"x-foo": "bar",
Expand Down Expand Up @@ -689,7 +689,7 @@ services:
- -p
- "3000"
environment:
BAR: bar_from_env_file_2
BAR: this is a secret
BAZ: baz_from_service_def
FOO: foo_from_env_file
QUX: qux_from_environment
Expand Down Expand Up @@ -973,7 +973,7 @@ secrets:
external: true
secret4:
name: bar
file: %s
environment: BAR
x-bar: baz
x-foo: bar
configs:
Expand Down Expand Up @@ -1003,9 +1003,8 @@ x-nested:
filepath.Join(homeDir, "configs"),
filepath.Join(workingDir, "opt"),
filepath.Join(workingDir, "secret_data"),
filepath.Join(workingDir),
filepath.Join(workingDir, "config_data"),
filepath.Join(workingDir))
filepath.Join(homeDir, "config_data"))
}

func fullExampleJSON(workingDir, homeDir string) string {
Expand Down Expand Up @@ -1096,7 +1095,7 @@ func fullExampleJSON(workingDir, homeDir string) string {
},
"secret4": {
"name": "bar",
"file": "%s",
"environment": "BAR",
"external": false
}
},
Expand Down Expand Up @@ -1260,7 +1259,7 @@ func fullExampleJSON(workingDir, homeDir string) string {
"3000"
],
"environment": {
"BAR": "bar_from_env_file_2",
"BAR": "this is a secret",
"BAZ": "baz_from_service_def",
"FOO": "foo_from_env_file",
"QUX": "qux_from_environment"
Expand Down Expand Up @@ -1613,10 +1612,9 @@ func fullExampleJSON(workingDir, homeDir string) string {
}
}`,
toPath(workingDir, "config_data"),
toPath(workingDir),
toPath(homeDir, "config_data"),
toPath(workingDir, "secret_data"),
toPath(workingDir),
toPath(workingDir),
toPath(workingDir, "static"),
toPath(homeDir, "configs"),
toPath(workingDir, "opt"))
Expand Down
2 changes: 1 addition & 1 deletion loader/loader.go
Expand Up @@ -804,7 +804,7 @@ func loadFileObjectConfig(name string, objType string, obj types.FileObjectConfi
return obj, errors.Errorf("%[1]s %[2]s: %[1]s.driver and %[1]s.file conflict; only use %[1]s.driver", objType, name)
}
default:
if resolvePaths {
if obj.File != "" && resolvePaths {
obj.File = absPath(details.WorkingDir, obj.File)
}
}
Expand Down