Skip to content

Add rawsetenv message type for provider plugins#13742

Open
rajyan wants to merge 2 commits into
docker:mainfrom
rajyan:13727-rawsetenv
Open

Add rawsetenv message type for provider plugins#13742
rajyan wants to merge 2 commits into
docker:mainfrom
rajyan:13727-rawsetenv

Conversation

@rajyan

@rajyan rajyan commented Apr 16, 2026

Copy link
Copy Markdown

What I did

Added a new rawsetenv message type to the provider plugin protocol. This allows providers to inject environment variables into dependent services without the automatic service name prefix.

Currently, setenv always prefixes variables with the service name (e.g., URL becomes DATABASE_URL). This works well for connection strings, but some applications require exact variable names that cannot be altered. There is no way to inject these as-is today.

With this change, providers can choose per-variable whether to use the prefixed (setenv) or unprefixed (rawsetenv) behavior:

{"type": "setenv", "message": "URL=https://example.com"}
{"type": "rawsetenv", "message": "SECRET_KEY=xxx"}

Changes

  • pkg/compose/plugins.go — Add RawSetEnvType constant and pluginVariables struct to separate prefixed/raw variables
  • docs/extension.md — Document rawsetenv in the protocol specification
  • docs/examples/provider.go — Add rawsetenv usage to the example provider
  • pkg/e2e/providers_test.go — Test for single-provider rawsetenv and multi-provider rawsetenv
  • pkg/e2e/fixtures/providers/rawsetenv.yaml — Test fixture

Related issue

resolves #13727

Providers can now send rawsetenv messages to inject environment
variables into dependent services without the automatic service name
prefix. This enables use cases where applications require exact
variable names that cannot be altered.

Closes docker#13727

Signed-off-by: Yohta Kimura <38206553+rajyan@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@rajyan rajyan force-pushed the 13727-rawsetenv branch from 3ab9b49 to 101a41c Compare June 3, 2026 12:43
@phillias

Copy link
Copy Markdown

this patch worked for my issue with mariadb requiring MYSQL_ROOT_PASSWORD with no override, not LOCKET_MYSQL_ROOT_PASSWORD as the compose provider was passing.

@glours glours left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Few things to address before we can merge.
Also we need to add a sibling fixture to pkg/e2e/fixtures/providers/rawsetenv.yaml that sets environment: { CLOUD_REGION: user-value } on the test service, and assert that the resulting value matches the documented behavior (skipped write, warning, or overwrite, depending on which resolution is chosen).

Comment thread pkg/compose/plugins.go Outdated
}

variables, err := s.executePlugin(cmd, command, service)
vars, err := s.executePlugin(cmd, command, service)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why this rename?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

There's no good reason for it. Reverted to variables.

Comment thread pkg/compose/plugins.go
s.Environment[prefix+key] = &val
}
for key, val := range vars.raw {
s.Environment[key] = &val

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

rawsetenv silently overwrites user-defined env vars

If a user sets environment: { CLOUD_REGION: eu-west-1 } and a provider emits rawsetenv CLOUD_REGION=us-east-1, the user value is silently clobbered. setenv was immune thanks to the prefix.
Please either skip writes when the key already exists, or we need to document this precedence explicitly.

@ndeloof what is you preference here? I'm in favor of the overwrite + warning message in logs, and you?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Went with overwrite + warning as suggested. One clarification on the framing though: setenv normally doesn't overwrite anything, since the prefix gives each provider its own namespace and collisions are rare. But the write has no existence check either, so if a service does define the exact prefixed key (e.g. a user-set SECRETS_URL and a provider named "secret" set URL), setenv silently overwrites it the same way. I kept the warning on rawsetenv only since prefixed collisions are unlikely, but can extend it to setenv if you'd prefer consistency.

Comment thread docs/extension.md Outdated
Comment on lines +108 to +111
This is useful when injecting secrets or configuration values that must match exact variable names expected by
applications or frameworks. Unlike `setenv`, which avoids collisions through automatic prefixing, `rawsetenv` keys
are the provider's responsibility to keep unique. If multiple providers emit the same `rawsetenv` key, the last one
to run will overwrite previous values.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

executor.go runs plan nodes concurrently via errgroup. Two providers without a mutual dependency run in parallel; mux serializes the writes but not their order. The doc says "the last one to run will overwrite previous values", literally true, but readers will assume declaration order wins.
Either tighten the doc, or detect cross-provider rawsetenv collisions and fail.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Reworded. Dropped the "last one to run will overwrite" phrasing and now state that providers without a depends_on link may run concurrently, so the resulting value is non-deterministic on collision, and that an override logs a warning.

Comment thread pkg/e2e/providers_test.go Outdated
env := getEnv(res.Combined(), false)
assert.Check(t, slices.Contains(env, "PROVIDER1_URL=https://magic.cloud/provider1"), env)
assert.Check(t, slices.Contains(env, "PROVIDER2_URL=https://magic.cloud/provider2"), env)
assert.Check(t, slices.Contains(env, "CLOUD_REGION=us-east-1"), env)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

both providers emit the same CLOUD_REGION value, so the conflict path is never exercised. Please add a test where a rawsetenv key collides with a user-defined env var

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed the CLOUD_REGION assertion from TestDependsOnMultipleProviders since, as you noted, both providers emit the same value so it never exercised a conflict, and a real cross-provider conflict can't be asserted deterministically (independent providers run concurrently, so it would be flaky). The deterministic conflict is now covered by TestProviderRawSetEnvOverridesUserEnv, which asserts the provider value wins over a user-defined one and that the override is logged as a warning rather than happening silently. The non-deterministic cross-provider case is documented in extension.md.

rawsetenv injects provider variables without the service-name prefix, so
a key can collide with a value already set on the dependent service,
whether declared by the user in environment or emitted by another
provider. Log a warning and overwrite on collision, document the
precedence and the non-deterministic ordering between concurrent
providers, and cover the user-environment override with an e2e test.

Signed-off-by: Yohta Kimura <38206553+rajyan@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Provider services: cannot inject environment variables without service name prefix

3 participants