Skip to content

feat(Cubemaster): opt-in envd injection for image-based templates#626

Open
0717dn6hyc8-byte wants to merge 3 commits into
TencentCloud:masterfrom
0717dn6hyc8-byte:feat/envd-bake-into-template-rootfs
Open

feat(Cubemaster): opt-in envd injection for image-based templates#626
0717dn6hyc8-byte wants to merge 3 commits into
TencentCloud:masterfrom
0717dn6hyc8-byte:feat/envd-bake-into-template-rootfs

Conversation

@0717dn6hyc8-byte

@0717dn6hyc8-byte 0717dn6hyc8-byte commented Jun 23, 2026

Copy link
Copy Markdown

Summary

This PR introduces an optional envd injection path for templates created from OCI images. When enabled, CubeMaster embeds a host-side envd binary into the template root filesystem before sealing the ext4 artifact. It also records the binary's SHA256 in the template fingerprint and marks the generated template request, so restored sandboxes can start the embedded envd using an entrypoint wrapper.

By default, this injection feature is disabled to maintain the existing behavior for the community.

Motivation

Some CubeSandbox users need an envd process to be available automatically in restored sandboxes, so developer tooling can attach without requiring image authors to modify every base image manually.

What's changed

1. CLI opt-in controls

Component Change
cubemastercli template create-from-image Adds --enable-inject-envd to explicitly enable envd injection.
cubemastercli template create-from-image Adds --envd-path to select the host-side envd binary.

If --envd-path is omitted, CubeMaster falls back to $CUBE_MASTER_ENVD_HOST_DIR/envd, then /usr/local/share/cubesandbox-envd/envd.

2. Template rootfs injection

Component Behavior
templatecenter Copies envd into /usr/local/bin/envd inside the exported template rootfs before sealing the ext4 artifact.
templatecenter Injection only happens when cube.master.inject_envd=true is present in template container overrides.
templatecenter Missing or unreadable envd binary fails the template build instead of producing an incomplete artifact.

3. Artifact fingerprinting

Component Behavior
templatecenter Includes the injected envd binary SHA256 in the template spec fingbled.
templatecenter Leaves fingerprints unchanged for templates that do not enable envd injection.

This prevents reusing artifacts baked with an older envd binary after the selected envd version changes.

4. Runtime entrypoint wrapping

Component Behavior
CubeMaster sandbox service When a template carries cube.master.inject_envd=true, wraps the main container command .
CubeMaster sandbox service Starts /usr/local/bin/envd in the background and then execs the original command.
CubeMaster sandbox service Adds the default envd exposed port.
CubeMaster sandbox service Skips wrapping if the command is already wrapped.

Assisted-by: Claude Code:ppio/pa/gpt-5.5

@cubesandboxbot

cubesandboxbot Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Summary: PR #626 — Envd Sidecar Injection

This PR adds support for injecting an envd sidecar binary into template rootfs images. Overall the code is well-structured with good separation of concerns and decent test coverage. Below are the noteworthy findings.


♠ 📦 Medium: Unsynchronized envd binary path between injection and wrapper

** envd_inject.go (lines 25-26)** hardcodes the destination path as /usr/local/bin/envd.
** envd_sidecar.go (lines 20-31)** reads the expected path from CUBA_MASTER_ENVD_IN_IMAGE_PATH, defaulting to /usr/local/bin/envd.

If an operator sets CUBE_MASTER_ENVD_IN_IMAGE_PATH=/opt/envd/bin/envd, the wrapper script will reference /opt/envd/bin/envd — but the binary was copied to /usr/local/bin/envd (the hardcoded path). The envd sidecar will silently fail to start at runtime.

Recommendation: Use a single source of truth. Move envdInImagePath() to a shared package so both injection and sidecar wrapper read from the same configurable function.


🔟 Medium: Silently swallowed envd SHA error in fingerprint

fingerprint.go (lines 49-53):

if shouldInjectEnvdIntoTemplate(req) {
    if sha, err := envdBinarySHA256(); err == nil {
        envdSHA = sha
    }
}

When envdBinarySHA256() fails, the error is silently discarded and envdSHA is left empty. The fingerprint is computed without the envd hash component, so the artifact cache could be incorrectly reused when the envd binary changes.

Recommendation: Log a warning when the SHA computation fails.


🔟 Low: Misleading log message in envd_sidecar.go (line 52)

The log message says "baked into template rootfs", but injectEnvdSidecar runs at sandbox-creation time — the binary was already baked during template build. This would mislead operators debugging injection issues. Recommend rewording to "pre-baked at build time" or removing the phrase.


🚢 Info: Redundant envd binary reads during fingerprint computation

envdBinarySHA256() is called twice per template build (in runTemplateImageJob and buildTemplateSpecFingerprintWithCA) plus a third time during the actual copy in injectEnvdIntoRoots. Consider computing the hash once and passing it through.


🚢 Info: Test coverage gaps

  • Non-cubebox InstanceType early return (envd_sidecar.go:38) not tested
  • Empty Containers list (envd_sidecar.go:42) not tested
  • generateTemplateCreateRequest envd annotation passthrough not asserted
  • --disable-inject-envd CLI flag path untested

Reviews contributed by: code-quality, security, test-coverage, performance, and documentation-accuracy reviewers.

envdHostDirDefault = "/usr/local/share/cubesandbox-envd"
envdHostDirEnv = "CUBE_MASTER_ENVD_HOST_DIR"
envdBinaryName = "envd"
envdInImageDir = "/usr/local/bin"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Path desynchronization risk: This hardcodes the in-image destination as /usr/local/bin/envd. However, envd_sidecar.go:20-21 defines a separate envdInImagePathDefault + envdInImagePathEnv (CUBE_MASTER_ENVD_IN_IMAGE_PATH) for where the wrapper expects the binary. If an operator overrides the env var, the wrapper references a path where no binary was injected.

Consider having both packages read from the same configurable function (e.g., a shared helper in constants) to prevent silent runtime failures.

CubeEgressCAFingerprint string `json:"cube_egress_ca_fingerprint,omitempty"`
EnvdBinarySHA256 string `json:"envd_binary_sha256,omitempty"`
}
envdSHA := ""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Silently discarded error: When envdBinarySHA256() fails, the error is discarded and envdSHA stays empty. The fingerprint is computed without the envd hash, so a subsequent envd binary change won't invalidate the artifact cache. The later injectEnvdIntoRootfs call will fail if the binary is missing, but only after the fingerprint has already been used for artifact deduplication.

Consider logging a warning at minimum so operators can detect this inconsistency.

}
wrapEntrypointForEnvd(mainContainer)
out.ExposedPorts = append(out.ExposedPorts, envdPort)
log.G(ctx).Infof("envd: wrapped entrypoint of container %q (envd path=%s, baked into template rootfs)", mainContainer.Name, envdInImagePath())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Misleading log message: This function (injectEnvdSidecar) runs at sandbox-creation time and only wraps the container entrypoint — the binary was already baked into the rootfs during template build (in envd_inject.go). The phrase "baked into template rootfs" would mislead operators debugging issues into thinking this function mutates the rootfs. Consider rewording to "pre-baked at build time" or simply dropping the "baked" qualifier.

envdHostDirDefault = "/usr/local/share/cubesandbox-envd"
envdHostDirEnv = "CUBE_MASTER_ENVD_HOST_DIR"
envdBinaryName = "envd"
envdInImageDir = "/usr/local/bin"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Path desynchronization risk: This hardcodes the in-image destination as /usr/local/bin/envd. However, envd_sidecar.go:20-21 defines a separate envdInImagePathDefault + envdInImagePathEnv (CUBE_MASTER_ENVD_IN_IMAGE_PATH) for where the wrapper expects the binary. If an operator overrides the env var, the wrapper references a path where no binary was injected.

Consider having both packages read from the same configurable function (e.g., a shared helper in constants) to prevent silent runtime failures.

CubeEgressCAFingerprint string `json:"cube_egress_ca_fingerprint,omitempty"`
EnvdBinarySHA256 string `json:"envd_binary_sha256,omitempty"`
}
envdSHA := ""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Silently discarded error: When envdBinarySHA256() fails, the error is discarded and envdSHA stays empty. The fingerprint is computed without the envd hash, so a subsequent envd binary change won't invalidate the artifact cache. The later injectEnvdIntoRootfs call will fail if the binary is missing, but only after the fingerprint has already been used for artifact deduplication.

Consider logging a warning at minimum so operators can detect this inconsistency.

}
wrapEntrypointForEnvd(mainContainer)
out.ExposedPorts = append(out.ExposedPorts, envdPort)
log.G(ctx).Infof("envd: wrapped entrypoint of container %q (envd path=%s, baked into template rootfs)", mainContainer.Name, envdInImagePath())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Misleading log message: This function (injectEnvdSidecar) runs at sandbox-creation time and only wraps the container entrypoint — the binary was already baked into the rootfs during template build (in envd_inject.go). The phrase "baked into template rootfs" would mislead operators debugging issues into thinking this function mutates the rootfs. Consider rewording to "pre-baked at build time" or simply dropping the "baked" qualifier.

envdHostDirEnv = "CUBE_MASTER_ENVD_HOST_DIR"
envdBinaryName = "envd"
envdInImageDir = "/usr/local/bin"
envdInImagePath = "/usr/local/bin/envd"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The destination path /usr/local/bin/envd is defined here as envdInImagePath and also duplicated in envd_sidecar.go:20 as envdInImagePathDefault. There's also envdInImagePath() in envd_sidecar.go which supports an env-var override (CUBE_MASTER_ENVD_IN_IMAGE_PATH), but the injection side (envd_inject.go) doesn't read that same env var — it hardcodes the path.

If an operator sets CUBE_MASTER_ENVD_IN_IMAGE_PATH=/opt/envd/bin/envd, the sidecar wrapper will reference /opt/envd/bin/envd, but the binary was copied to /usr/local/bin/envd (the hardcoded path). The envd sidecar will silently fail to start at runtime.

Suggestion: Move the path resolution to a shared package or define envdInImagePath() in a common location that both packages import, so the injection and sidecar both use the same configurable default + env-var override logic.

Comment on lines +49 to +53
envdSHA := ""
if shouldInjectEnvdIntoTemplate(req) {
if sha, err := envdBinarySHA256(req); err == nil {
envdSHA = sha
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

When envdBinarySHA256(req) fails (e.g. binary missing or permission error), the error is silently discarded and envdSHA stays empty. The fingerprint is then computed without the envd hash component, so the artifact cache could be incorrectly reused when the envd binary changes. This silently produces a cache hit for a build that should produce different output.

At minimum, log a warning when this fails. If the envd binary is required for this build (i.e., shouldInjectEnvdIntoTemplate is true), consider failing the build instead.

Comment on lines +84 to +94
func envdBinarySHA256(req *types.CreateTemplateFromImageReq) (string, error) {
src, err := os.Open(envdHostPath(req))
if err != nil {
return "", err
}
defer src.Close()
hasher := sha256.New()
if _, err := io.Copy(hasher, src); err != nil {
return "", err
}
return hex.EncodeToString(hasher.Sum(nil)), nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This function reads the envd binary from disk again to compute its SHA256. The binary is already read (and hashed via io.TeeReader) during injectEnvdIntoRootfs. That hash is returned but only used in the log message (artifact_build.go:171 ignores it with _).

This means the envd binary is read from disk 3 times per template build: once via buildTemplateSpecFingerprintWithCA -> envdBinarySHA256, again in ensureRootfsArtifact path, and a third time during the actual copy in injectEnvdIntoRootfs.

Consider threading the SHA256 hash from injectEnvdIntoRootfs back to the fingerprint computation, or computing it once at the top of runTemplateImageJob and passing it through.

return envdInImagePathDefault
}

func injectEnvdSidecar(ctx context.Context, req *types.CreateCubeSandboxReq, out *cubebox.RunCubeSandboxRequest) error {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The function signature declares error as a return value, but every return path returns nil. The call site in util.go:159 treats it as fallible, wrapping the error. Consider removing the error return to match the implementation, or make it fallible if there's a planned future use.

srcPath := envdHostPath(req)
src, err := os.Open(srcPath)
if err != nil {
return "", fmt.Errorf("envd-inject: open %q (set %s to override): %w", srcPath, envdHostDirEnv, err)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The error message says "(set CUBE_MASTER_ENVD_HOST_DIR to override)" but the primary user-facing override is --envd-path CLI flag, which flows through req.EnvdPath and is checked before the env var in envdHostPath(). If a user passes --envd-path pointing to a missing binary, this error suggests setting an environment variable instead.

Consider detecting which source was used and mentioning it in the error, or expanding the message to mention both override mechanisms.

@0717dn6hyc8-byte 0717dn6hyc8-byte changed the title feat: Add support for default injection of envd in cubemastercli #0 feat: opt-in envd injection for image-based templates Jun 23, 2026
@0717dn6hyc8-byte 0717dn6hyc8-byte changed the title feat: opt-in envd injection for image-based templates feat(cubemaster): opt-in envd injection for image-based templates Jun 23, 2026
@0717dn6hyc8-byte 0717dn6hyc8-byte changed the title feat(cubemaster): opt-in envd injection for image-based templates feat(Cubemaster): opt-in envd injection for image-based templates Jun 23, 2026
@fslongjin

Copy link
Copy Markdown
Member

I want to understand the motivation behind this PR and under what scenarios it is necessary. I feel that this goes against common usage—since it's built from a container image, why would we need to additionally inject an envd? This will also affect the container's startup commands and such. I find the design of this PR very strange.

@0717dn6hyc8-byte

Copy link
Copy Markdown
Author

I want to understand the motivation behind this PR and under what scenarios it is necessary. I feel that this goes against common usage—since it's built from a container image, why would we need to additionally inject an envd? This will also affect the container's startup commands and such. I find the design of this PR very strange.

For example, in development-environment / code-sandbox scenarios, we want to quickly build interactive development sandboxes from existing base images. envd provides the access layer for the development environment, but many base images do not have it preinstalled. Maintaining a large number of derived images only to integrate envd would add unnecessary cost and complexity.

Therefore, this PR injects envd during template rootfs creation. This allows those base images to gain a consistent development runtime capability without modifying their Dockerfiles or rebuilding the source images.

@fslongjin

Copy link
Copy Markdown
Member

I want to understand the motivation behind this PR and under what scenarios it is necessary. I feel that this goes against common usage—since it's built from a container image, why would we need to additionally inject an envd? This will also affect the container's startup commands and such. I find the design of this PR very strange.

For example, in development-environment / code-sandbox scenarios, we want to quickly build interactive development sandboxes from existing base images. envd provides the access layer for the development environment, but many base images do not have it preinstalled. Maintaining a large number of derived images only to integrate envd would add unnecessary cost and complexity.

Therefore, this PR injects envd during template rootfs creation. This allows those base images to gain a consistent development runtime capability without modifying their Dockerfiles or rebuilding the source images.

Indeed, you make a good point. From that perspective, automatically injecting envd is indeed a great idea! Thank you for your PR. I'll study the code in your PR over the next couple of days.

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.

2 participants