Skip to content
This repository has been archived by the owner on Jun 13, 2021. It is now read-only.

Commit

Permalink
Add warnings for env_file entries not copied
Browse files Browse the repository at this point in the history
As 'env_file' entries are not taken in account,
we warn the user to copy that and fix it's
compose file.

Signed-off-by: Ulysses Souza <[email protected]>
  • Loading branch information
ulyssessouza committed Nov 6, 2019
1 parent 6f5737a commit 067db51
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 22 deletions.
50 changes: 50 additions & 0 deletions e2e/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,56 @@ func TestRenderFormatters(t *testing.T) {
})
}

func checkFileWarning(t *testing.T, goldenFile, composeData string) {
cmd, cleanup := dockerCli.createTestCmd()
defer cleanup()

tmpDir := fs.NewDir(t, "app_input",
fs.WithFile(internal.ComposeFileName, composeData),
)
defer tmpDir.Remove()

cmd.Dir = tmpDir.Path()
cmd.Command = dockerCli.Command("app", "init", "app-test",
"--compose-file", tmpDir.Join(internal.ComposeFileName))
stdErr := icmd.RunCmd(cmd).Assert(t, icmd.Success).Stderr()
golden.Assert(t, stdErr, goldenFile)
}

func TestInitWarningEnvFiles(t *testing.T) {
testCases := []struct {
name string
golden string
compose string
}{
{
name: "initWarningMultipleEnvFilesTest",
golden: "init-output-warning-single-envfile.golden",
compose: `version: "3.2"
services:
nginx:
image: nginx:latest
env_file: myenv1.env`,
},
{
name: "initWarningMultipleEnvFilesTest",
golden: "init-output-warning-multiple-envfiles.golden",
compose: `version: "3.2"
services:
nginx:
image: nginx:latest
env_file:
- myenv1.env
- myenv2.env`,
},
}
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
checkFileWarning(t, test.golden, test.compose)
})
}
}

func TestInit(t *testing.T) {
cmd, cleanup := dockerCli.createTestCmd()
defer cleanup()
Expand Down
2 changes: 2 additions & 0 deletions e2e/testdata/init-output-warning-multiple-envfiles.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
nginx.env_file "myenv1.env" will not be copied into app-test.dockerapp. Please copy it manually and update the path accordingly in the compose file.
nginx.env_file "myenv2.env" will not be copied into app-test.dockerapp. Please copy it manually and update the path accordingly in the compose file.
1 change: 1 addition & 0 deletions e2e/testdata/init-output-warning-single-envfile.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nginx.env_file "myenv1.env" will not be copied into app-test.dockerapp. Please copy it manually and update the path accordingly in the compose file.
2 changes: 1 addition & 1 deletion internal/commands/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func initCmd(dockerCli command.Cli) *cobra.Command {
$ docker app init myapp --compose-file docker-compose.yml`,
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
created, err := packager.Init(args[0], initComposeFile)
created, err := packager.Init(dockerCli.Err(), args[0], initComposeFile)
if err != nil {
return err
}
Expand Down
89 changes: 73 additions & 16 deletions internal/packager/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package packager
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"os/user"
Expand All @@ -27,7 +28,7 @@ import (
// Init is the entrypoint initialization function.
// It generates a new application definition based on the provided parameters
// and returns the path to the created application definition.
func Init(name string, composeFile string) (string, error) {
func Init(errWriter io.Writer, name string, composeFile string) (string, error) {
if err := internal.ValidateAppName(name); err != nil {
return "", err
}
Expand All @@ -48,7 +49,7 @@ func Init(name string, composeFile string) (string, error) {
if composeFile == "" {
err = initFromScratch(name)
} else {
err = initFromComposeFile(name, composeFile)
err = initFromComposeFile(errWriter, name, composeFile)
}
if err != nil {
return "", err
Expand Down Expand Up @@ -79,22 +80,52 @@ func checkComposeFileVersion(compose map[string]interface{}) error {
return schema.Validate(compose, fmt.Sprintf("%v", version))
}

func initFromComposeFile(name string, composeFile string) error {
logrus.Debugf("Initializing from compose file %s", composeFile)

dirName := internal.DirNameFromAppName(name)

composeRaw, err := ioutil.ReadFile(composeFile)
if err != nil {
return errors.Wrap(err, "failed to read compose file")
func getEnvFiles(svcName string, envFileEntry interface{}) ([]string, error) {
var envFiles []string
switch envFileEntry.(type) {
case string:
envFiles = append(envFiles, envFileEntry.(string))
case []interface{}:
for _, env := range envFileEntry.([]interface{}) {
envFiles = append(envFiles, env.(string))
}
default:
return nil, fmt.Errorf("unknown entries in 'env_file' for service %s -> %v",
svcName, envFileEntry)
}
cfgMap, err := composeloader.ParseYAML(composeRaw)
if err != nil {
return errors.Wrap(err, "failed to parse compose file")
return envFiles, nil
}

func checkEnvFiles(errWriter io.Writer, appName string, cfgMap map[string]interface{}) error {
services := cfgMap["services"]
servicesMap, ok := services.(map[string]interface{})
if !ok {
return fmt.Errorf("invalid Compose file")
}
if err := checkComposeFileVersion(cfgMap); err != nil {
return err
for svcName, svc := range servicesMap {
svcContent, ok := svc.(map[string]interface{})
if !ok {
return fmt.Errorf("invalid service %q", svcName)
}
envFileEntry, ok := svcContent["env_file"]
if !ok {
continue
}
envFiles, err := getEnvFiles(svcName, envFileEntry)
if err != nil {
return errors.Wrap(err, "invalid Compose file")
}
for _, envFilePath := range envFiles {
fmt.Fprintf(errWriter,
"%s.env_file %q will not be copied into %s.dockerapp. "+
"Please copy it manually and update the path accordingly in the compose file.\n",
svcName, envFilePath, appName)
}
}
return nil
}

func getParamsFromDefaultEnvFile(composeFile string, composeRaw []byte) (map[string]string, bool, error) {
params := make(map[string]string)
envs, err := opts.ParseEnvFile(filepath.Join(filepath.Dir(composeFile), ".env"))
if err == nil {
Expand All @@ -107,7 +138,7 @@ func initFromComposeFile(name string, composeFile string) error {
}
vars, err := compose.ExtractVariables(composeRaw, compose.ExtrapolationPattern)
if err != nil {
return errors.Wrap(err, "failed to parse compose file")
return nil, false, errors.Wrap(err, "failed to parse compose file")
}
needsFilling := false
for k, v := range vars {
Expand All @@ -120,6 +151,32 @@ func initFromComposeFile(name string, composeFile string) error {
}
}
}
return params, needsFilling, nil
}

func initFromComposeFile(errWriter io.Writer, name string, composeFile string) error {
logrus.Debugf("Initializing from compose file %s", composeFile)

dirName := internal.DirNameFromAppName(name)

composeRaw, err := ioutil.ReadFile(composeFile)
if err != nil {
return errors.Wrapf(err, "failed to read compose file %q", composeFile)
}
cfgMap, err := composeloader.ParseYAML(composeRaw)
if err != nil {
return errors.Wrap(err, "failed to parse compose file")
}
if err := checkComposeFileVersion(cfgMap); err != nil {
return err
}
if err := checkEnvFiles(errWriter, name, cfgMap); err != nil {
return err
}
params, needsFilling, err := getParamsFromDefaultEnvFile(composeFile, composeRaw)
if err != nil {
return err
}
expandedParams, err := parameters.FromFlatten(params)
if err != nil {
return errors.Wrap(err, "failed to expand parameters")
Expand Down
10 changes: 5 additions & 5 deletions internal/packager/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ services:
)
defer dir.Remove()

err := initFromComposeFile(dir.Join(appName), inputDir.Join(internal.ComposeFileName))
err := initFromComposeFile(nil, dir.Join(appName), inputDir.Join(internal.ComposeFileName))
assert.NilError(t, err)

manifest := fs.Expected(
Expand Down Expand Up @@ -73,7 +73,7 @@ services:
)
defer dir.Remove()

err := initFromComposeFile(dir.Join(appName), inputDir.Join(internal.ComposeFileName))
err := initFromComposeFile(nil, dir.Join(appName), inputDir.Join(internal.ComposeFileName))
assert.NilError(t, err)

const expectedParameters = `ports:
Expand Down Expand Up @@ -108,7 +108,7 @@ services:
}

func TestInitFromInvalidComposeFile(t *testing.T) {
err := initFromComposeFile("my.dockerapp", "doesnotexist")
err := initFromComposeFile(nil, "my.dockerapp", "doesnotexist")
assert.ErrorContains(t, err, "failed to read")
}

Expand All @@ -131,7 +131,7 @@ services:
)
defer dir.Remove()

err := initFromComposeFile(dir.Join(appName), inputDir.Join(internal.ComposeFileName))
err := initFromComposeFile(nil, dir.Join(appName), inputDir.Join(internal.ComposeFileName))
assert.ErrorContains(t, err, "unsupported Compose file version")
}

Expand All @@ -151,7 +151,7 @@ nginx:
)
defer dir.Remove()

err := initFromComposeFile(dir.Join(appName), inputDir.Join(internal.ComposeFileName))
err := initFromComposeFile(nil, dir.Join(appName), inputDir.Join(internal.ComposeFileName))
assert.ErrorContains(t, err, "unsupported Compose file version")
}

Expand Down

0 comments on commit 067db51

Please sign in to comment.