Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 32 additions & 7 deletions cmd/lk/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,32 @@ var (
Action: runTask,
},
{
Hidden: true,
Name: "env",
Usage: "Manage environment variables",
Before: requireProject,
Name: "env",
Usage: "Print project environment variables expanded from a .env.example file",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "w",
Aliases: []string{"write"},
Usage: "Write environment variables to .env.local file",
},
},
ArgsUsage: "[DIR] location of the project directory (default: current directory)",
Before: requireProject,
Action: func(ctx context.Context, cmd *cli.Command) error {
return instantiateEnv(ctx, cmd, ".", nil)
rootDir := cmd.Args().First()
if rootDir == "" {
rootDir = "."
}

env, err := instantiateEnv(ctx, cmd, rootDir, nil)
if err != nil {
return err
}
if cmd.Bool("write") {
return bootstrap.WriteDotEnv(rootDir, env)
} else {
return bootstrap.PrintDotEnv(env)
}
},
},
},
Expand Down Expand Up @@ -254,9 +274,11 @@ func setupTemplate(ctx context.Context, cmd *cli.Command) error {

fmt.Println("Instantiating environment...")
addlEnv := &map[string]string{"LIVEKIT_SANDBOX_ID": sandboxID}
if err := instantiateEnv(ctx, cmd, appName, addlEnv); err != nil {
env, err := instantiateEnv(ctx, cmd, appName, addlEnv)
if err != nil {
return err
}
bootstrap.WriteDotEnv(appName, env)

if install {
fmt.Println("Installing template...")
Expand Down Expand Up @@ -307,7 +329,7 @@ func cleanupTemplate(ctx context.Context, cmd *cli.Command, appName string) erro
return bootstrap.CleanupTemplate(appName)
}

func instantiateEnv(ctx context.Context, cmd *cli.Command, rootPath string, addlEnv *map[string]string) error {
func instantiateEnv(ctx context.Context, cmd *cli.Command, rootPath string, addlEnv *map[string]string) (map[string]string, error) {
env := map[string]string{
"LIVEKIT_API_KEY": project.APIKey,
"LIVEKIT_API_SECRET": project.APISecret,
Expand Down Expand Up @@ -350,6 +372,9 @@ func doPostCreate(ctx context.Context, _ *cli.Command, rootPath string, verbose
if err != nil {
return err
}
if tf == nil {
return nil
}

task, err := bootstrap.NewTask(ctx, tf, rootPath, string(bootstrap.TaskPostCreate), verbose)
if task == nil || err != nil {
Expand Down
99 changes: 57 additions & 42 deletions pkg/bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"io/fs"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"strings"

Expand Down Expand Up @@ -122,7 +122,14 @@ func FetchSandboxDetails(ctx context.Context, sid, token, serverURL string) (*Sa
}

func ParseTaskfile(rootPath string) (*ast.Taskfile, error) {
file, err := os.ReadFile(path.Join(rootPath, TaskFile))
taskfilePath := path.Join(rootPath, TaskFile)

// taskfile.yaml is optional
if _, err := os.Stat(taskfilePath); err != nil && errors.Is(err, fs.ErrNotExist) {
return nil, nil
}

file, err := os.ReadFile(taskfilePath)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -180,54 +187,62 @@ func NewTask(ctx context.Context, tf *ast.Taskfile, dir, taskName string, verbos

type PromptFunc func(key string, value string) (string, error)

// Recursively walk the repo, reading in any .env.example file if present in
// that directory, replacing all `substitutions`, prompting for others, and
// writing to .env.local in that directory.
func InstantiateDotEnv(ctx context.Context, rootDir string, substitutions map[string]string, verbose bool, prompt PromptFunc) error {
// Read .env.example file if present in rootDir, replacing all `substitutions`,
// prompting for others, and returning the result as a map.
func InstantiateDotEnv(ctx context.Context, rootDir string, substitutions map[string]string, verbose bool, prompt PromptFunc) (map[string]string, error) {
promptedVars := map[string]string{}

return filepath.WalkDir(rootDir, func(filePath string, d fs.DirEntry, err error) error {
if err != nil {
return err
}

if d.Name() == EnvExampleFile {
envMap, err := godotenv.Read(filePath)
if err != nil {
return err
}
envExamplePath := path.Join(rootDir, EnvExampleFile)
stat, err := os.Stat(envExamplePath)
if err != nil {
return nil, err
}
if stat.IsDir() {
return nil, errors.New("env.example file is a directory")
}

for key, oldValue := range envMap {
// if key is a substitution, replace it
if value, ok := substitutions[key]; ok {
envMap[key] = value
// if key was already promped, use that value
} else if alreadyPromptedValue, ok := promptedVars[key]; ok {
envMap[key] = alreadyPromptedValue
} else {
// prompt for value
newValue, err := prompt(key, oldValue)
if err != nil {
return err
}
envMap[key] = newValue
promptedVars[key] = newValue
}
}
envMap, err := godotenv.Read(envExamplePath)
if err != nil {
return nil, err
}

envContents, err := godotenv.Marshal(envMap)
for key, oldValue := range envMap {
// if key is a substitution, replace it
if value, ok := substitutions[key]; ok {
envMap[key] = value
// if key was already promped, use that value
} else if alreadyPromptedValue, ok := promptedVars[key]; ok {
envMap[key] = alreadyPromptedValue
} else {
// prompt for value
newValue, err := prompt(key, oldValue)
if err != nil {
return err
}

envLocalPath := path.Join(path.Dir(filePath), EnvLocalFile)
if err := os.WriteFile(envLocalPath, []byte(envContents), 0700); err != nil {
return err
return nil, err
}
envMap[key] = newValue
promptedVars[key] = newValue
}
}

return envMap, nil
}

return nil
})
func PrintDotEnv(envMap map[string]string) error {
envContents, err := godotenv.Marshal(envMap)
if err != nil {
return err
}
_, err = fmt.Println(envContents)
return err
}

func WriteDotEnv(rootDir string, envMap map[string]string) error {
envContents, err := godotenv.Marshal(envMap)
if err != nil {
return err
}
envLocalPath := path.Join(rootDir, EnvLocalFile)
return os.WriteFile(envLocalPath, []byte(envContents), 0700)
}

func CloneTemplate(url, dir string) (string, string, error) {
Expand Down
Loading