Thanks to visit codestin.com
Credit goes to github.com

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
33 changes: 33 additions & 0 deletions envbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,11 @@ type Options struct {
// This is useful for self-signed certificates.
SSLCertBase64 string `env:"SSL_CERT_BASE64"`

// ExportEnvFile is an optional file path to a .env file where
// envbuilder will dump environment variables from devcontainer.json and
// the built container image.
ExportEnvFile string `env:"EXPORT_ENV_FILE"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A comment describing the use case would also be helpful, mainly because of the export directive, which is technically sh syntax, not .env file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should make this a proper .env file being exported instead. Would that make it less useful?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed this to create a .env file instead (key/value pairs separated by =, no quoting). The sh syntax is more useful for me, but I can use something like this to process the .env file: https://stackoverflow.com/a/66118031


// Logger is the logger to use for all operations.
Logger func(level codersdk.LogLevel, format string, args ...interface{})

Expand Down Expand Up @@ -524,6 +529,7 @@ func Run(ctx context.Context, options Options) error {
})
}

skippedRebuild := false
build := func() (v1.Image, error) {
_, err := options.Filesystem.Stat(MagicFile)
if err == nil && options.SkipRebuild {
Expand All @@ -537,6 +543,7 @@ func Run(ctx context.Context, options Options) error {
return nil, fmt.Errorf("image from remote: %w", err)
}
endStage("🏗️ Found image from remote!")
skippedRebuild = true
return image, nil
}

Expand Down Expand Up @@ -668,6 +675,25 @@ func Run(ctx context.Context, options Options) error {
}
_ = file.Close()

var exportEnvFile *os.File
// Do not export env if we skipped a rebuild, because ENV directives
// from the Dockerfile would not have been processed and we'd miss these
// in the export. We should have generated a complete set of environment
// on the intial build, so exporting environment variables a second time
// isn't useful anyway.
if options.ExportEnvFile != "" && !skippedRebuild {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A mention as to why we don't want to do this on rebuild would be helpful. Essentially your GitHub comments!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment explaining this.

exportEnvFile, err = os.Create(options.ExportEnvFile)
if err != nil {
return fmt.Errorf("failed to open EXPORT_ENV_FILE %q: %w", options.ExportEnvFile, err)
}
}
exportEnv := func(key, value string) {
if exportEnvFile == nil {
return
}
fmt.Fprintf(exportEnvFile, "%s=%s\n", key, value)
}

configFile, err := image.ConfigFile()
if err != nil {
return fmt.Errorf("get image config: %w", err)
Expand Down Expand Up @@ -695,6 +721,7 @@ func Run(ctx context.Context, options Options) error {
if container.RemoteEnv != nil {
for key, value := range container.RemoteEnv {
os.Setenv(key, value)
exportEnv(key, value)
}
}
}
Expand Down Expand Up @@ -724,10 +751,16 @@ func Run(ctx context.Context, options Options) error {
for _, env := range configFile.Config.Env {
pair := strings.SplitN(env, "=", 2)
os.Setenv(pair[0], pair[1])
exportEnv(pair[0], pair[1])
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Environment variables declared with ENV in the Dockerfile don't seem to actually show up in configFile.Config.Env. This is very strange and I'm not sure why this isn't working as expected.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird, it works in the test but isn't working in my actual setup 🤔. Trying to dig into this...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, found the problem. Env vars from the Dockerfile get skipped when SKIP_REBUILD=true and we are starting an existing container. I will update this to skip writing the env export file in this scenario - we only want to write it when we do the actual build.

}
for _, env := range buildParams.Env {
pair := strings.SplitN(env, "=", 2)
os.Setenv(pair[0], pair[1])
exportEnv(pair[0], pair[1])
}

if exportEnvFile != nil {
exportEnvFile.Close()
}

username := configFile.Config.User
Expand Down
33 changes: 33 additions & 0 deletions integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,39 @@ func TestExitBuildOnFailure(t *testing.T) {
require.ErrorContains(t, err, "parsing dockerfile")
}

func TestExportEnvFile(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great test! 😍

t.Parallel()

// Ensures that a Git repository with a devcontainer.json is cloned and built.
url := createGitServer(t, gitServerOptions{
files: map[string]string{
".devcontainer/devcontainer.json": `{
"name": "Test",
"build": {
"dockerfile": "Dockerfile"
},
"build": {
"dockerfile": "Dockerfile"
},
"remoteEnv": {
"FROM_DEVCONTAINER_JSON": "bar"
}
}`,
".devcontainer/Dockerfile": "FROM alpine:latest\nENV FROM_DOCKERFILE=foo",
},
})
ctr, err := runEnvbuilder(t, options{env: []string{
"GIT_URL=" + url,
"EXPORT_ENV_FILE=/env",
}})
require.NoError(t, err)

output := execContainer(t, ctr, "cat /env")
require.Contains(t, strings.TrimSpace(output),
`FROM_DOCKERFILE=foo
FROM_DEVCONTAINER_JSON=bar`)
}

func TestPrivateRegistry(t *testing.T) {
t.Parallel()
t.Run("NoAuth", func(t *testing.T) {
Expand Down