Repository: score-spec/score-compose
Pull request #93 (2024-03-21 09:22:49)

feat: support for --env-file on generate
This PR adds a backwards compatibility feature to generate that was present in the existing run command.

Now you can specify `--env-file` to output a compose-style environment file that can be filled in with relevant values.

It's recommended to check this into the repo along with the score file so that developers know what to provide here.

eg: with a score file like:

```
apiVersion: score.dev/v1b1

metadata:
  name: hello-world

containers:
  hello:
    image: busybox
    variables:
      LOG_LEVEL: ${resources.env.LOG_LEVEL}
      STAGE: ${resources.env.STAGE}

resources:
  env:
    type: environment
```

Running with `--env-file .env` will output:

```
LOG_LEVEL=
STAGE=
```

These should both be filled in appropriately before running `docker compose up`




Patch:
diff --git a/README.md b/README.md
index 4a0e45f..e16ef27 100644
--- a/README.md
+++ b/README.md
@@ -108,6 +108,7 @@ Examples:
 
 Flags:
       --build stringArray               An optional build context to use for the given container --build=container=./dir or --build=container={'"context":"./dir"}
+      --env-file string                 Location to store a skeleton .env file for compose - this will override existing content
   -h, --help                            help for generate
       --image string                    An optional container image to use for any container with image == '.'
   -o, --output string                   The output file to write the composed compose file to (default "compose.yaml")
diff --git a/examples/02-environment/README.md b/examples/02-environment/README.md
index b5962af..cc9a88c 100644
--- a/examples/02-environment/README.md
+++ b/examples/02-environment/README.md
@@ -82,3 +82,11 @@ Attaching to compose-hello-world-1
 compose-hello-world-1  | Hello Bob!
 compose-hello-world-1  | Hello Bob!
 ```
+
+`score-compose` can generate the initial env file for you if you're not sure what variables are used. To do this, specify the `--env-file` flag when running the `generate` subcommand.
+
+```
+$ score-compose generate --env-file sample.env
+$ cat sample.env
+NAME=
+```
diff --git a/internal/command/generate.go b/internal/command/generate.go
index 9c1d9c1..f73588d 100644
--- a/internal/command/generate.go
+++ b/internal/command/generate.go
@@ -43,6 +43,8 @@ const (
 	generateCmdOverridePropertyFlag = "override-property"
 	generateCmdImageFlag            = "image"
 	generateCmdBuildFlag            = "build"
+	generateCmdOutputFlag           = "output"
+	generateCmdEnvFileFlag          = "env-file"
 )
 
 var generateCommand = &cobra.Command{
@@ -261,16 +263,29 @@ arguments.
 
 		raw, _ := yaml.Marshal(superProject)
 
-		v, _ := cmd.Flags().GetString("output")
+		v, _ := cmd.Flags().GetString(generateCmdOutputFlag)
 		if v == "" {
 			return fmt.Errorf("no output file specified")
 		} else if v == "-" {
 			_, _ = fmt.Fprint(cmd.OutOrStdout(), string(raw))
-		} else if err := os.WriteFile(v+".temp", raw, 0755); err != nil {
+		} else if err := os.WriteFile(v+".temp", raw, 0644); err != nil {
 			return fmt.Errorf("failed to write output file: %w", err)
 		} else if err := os.Rename(v+".temp", v); err != nil {
 			return fmt.Errorf("failed to complete writing output file: %w", err)
 		}
+
+		if v, _ := cmd.Flags().GetString(generateCmdEnvFileFlag); v != "" {
+			content := new(strings.Builder)
+			for k := range environmentProvisioner.Accessed() {
+				_, _ = content.WriteString(k)
+				_, _ = content.WriteRune('=')
+				_, _ = content.WriteRune('\n')
+			}
+			slog.Info(fmt.Sprintf("Writing env var file to '%s'", v))
+			if err := os.WriteFile(v, []byte(content.String()), 0644); err != nil {
+				return fmt.Errorf("failed to write env var file: %w", err)
+			}
+		}
 		return nil
 	},
 }
@@ -304,11 +319,12 @@ func loadRawScoreFiles(fileNames []string) ([]string, map[string]map[string]inte
 }
 
 func init() {
-	generateCommand.Flags().StringP("output", "o", "compose.yaml", "The output file to write the composed compose file to")
+	generateCommand.Flags().StringP(generateCmdOutputFlag, "o", "compose.yaml", "The output file to write the composed compose file to")
 	generateCommand.Flags().String(generateCmdOverridesFileFlag, "", "An optional file of Score overrides to merge in")
 	generateCommand.Flags().StringArray(generateCmdOverridePropertyFlag, []string{}, "An optional set of path=key overrides to set or remove")
 	generateCommand.Flags().String(generateCmdImageFlag, "", "An optional container image to use for any container with image == '.'")
 	generateCommand.Flags().StringArray(generateCmdBuildFlag, []string{}, "An optional build context to use for the given container --build=container=./dir or --build=container={'\"context\":\"./dir\"}")
+	generateCommand.Flags().String(generateCmdEnvFileFlag, "", "Location to store a skeleton .env file for compose - this will override existing content")
 	rootCmd.AddCommand(generateCommand)
 }
 


Test patch:
diff --git a/internal/command/generate_test.go b/internal/command/generate_test.go
index 5399fef..5d456ee 100644
--- a/internal/command/generate_test.go
+++ b/internal/command/generate_test.go
@@ -54,6 +54,7 @@ Examples:
 
 Flags:
       --build stringArray               An optional build context to use for the given container --build=container=./dir or --build=container={'"context":"./dir"}
+      --env-file string                 Location to store a skeleton .env file for compose - this will override existing content
   -h, --help                            help for generate
       --image string                    An optional container image to use for any container with image == '.'
   -o, --output string                   The output file to write the composed compose file to (default "compose.yaml")