Skip to content

Commit a8d150d

Browse files
authored
feat: keep user and workdir from the template in the sandbox (#1349)
1 parent c84fe8a commit a8d150d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+909
-313
lines changed

packages/api/internal/handlers/template_start_build.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/e2b-dev/infra/packages/shared/pkg/models"
1919
"github.com/e2b-dev/infra/packages/shared/pkg/models/envbuild"
2020
"github.com/e2b-dev/infra/packages/shared/pkg/telemetry"
21+
"github.com/e2b-dev/infra/packages/shared/pkg/templates"
2122
"github.com/e2b-dev/infra/packages/shared/pkg/utils"
2223
)
2324

@@ -141,7 +142,7 @@ func (a *APIStore) PostTemplatesTemplateIDBuildsBuildID(c *gin.Context, template
141142
// Call the Template Manager to build the environment
142143
forceRebuild := true
143144
fromImage := ""
144-
buildErr := a.templateManager.CreateTemplate(
145+
err = a.templateManager.CreateTemplate(
145146
ctx,
146147
team.ID,
147148
templateID,
@@ -160,20 +161,22 @@ func (a *APIStore) PostTemplatesTemplateIDBuildsBuildID(c *gin.Context, template
160161
nil,
161162
apiutils.WithClusterFallback(team.ClusterID),
162163
build.ClusterNodeID,
164+
templates.TemplateV1Version,
163165
)
164-
if buildErr != nil {
165-
telemetry.ReportCriticalError(ctx, "build failed", buildErr, telemetry.WithTemplateID(templateID))
166-
a.sendAPIStoreError(c, http.StatusInternalServerError, fmt.Sprintf("Error when starting template build: %s", buildErr))
167-
return
168-
}
169166

170167
a.posthog.CreateAnalyticsUserEvent(userID.String(), team.ID.String(), "built environment", posthog.NewProperties().
171168
Set("user_id", userID).
172169
Set("environment", templateID).
173170
Set("build_id", buildID).
174171
Set("duration", time.Since(startTime).String()).
175-
Set("success", err != nil),
172+
Set("success", err == nil),
176173
)
177174

175+
if err != nil {
176+
telemetry.ReportCriticalError(ctx, "build failed", err, telemetry.WithTemplateID(templateID))
177+
a.sendAPIStoreError(c, http.StatusInternalServerError, fmt.Sprintf("Error when starting template build: %s", err))
178+
return
179+
}
180+
178181
c.Status(http.StatusAccepted)
179182
}

packages/api/internal/handlers/template_start_build_v2.go

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,27 @@ import (
44
"encoding/json"
55
"fmt"
66
"net/http"
7+
"strings"
78
"time"
89

910
"github.com/gin-gonic/gin"
1011
"github.com/google/uuid"
1112
"github.com/posthog/posthog-go"
13+
"go.uber.org/zap"
1214

1315
"github.com/e2b-dev/infra/packages/api/internal/api"
1416
apiutils "github.com/e2b-dev/infra/packages/api/internal/utils"
1517
"github.com/e2b-dev/infra/packages/db/queries"
18+
"github.com/e2b-dev/infra/packages/shared/pkg/logger"
1619
"github.com/e2b-dev/infra/packages/shared/pkg/models/envbuild"
1720
"github.com/e2b-dev/infra/packages/shared/pkg/telemetry"
21+
"github.com/e2b-dev/infra/packages/shared/pkg/templates"
22+
"github.com/e2b-dev/infra/packages/shared/pkg/utils"
23+
)
24+
25+
const (
26+
jsSDKPrefix = "e2b-js-sdk/"
27+
pythonSDKPrefix = "e2b-python-sdk/"
1828
)
1929

2030
type dockerfileStore struct {
@@ -117,8 +127,15 @@ func (a *APIStore) PostV2TemplatesTemplateIDBuildsBuildID(c *gin.Context, templa
117127
return
118128
}
119129

130+
version, err := userAgentToTemplateVersion(zap.L().With(logger.WithTemplateID(templateID), logger.WithBuildID(buildID)), c.Request.UserAgent())
131+
if err != nil {
132+
a.sendAPIStoreError(c, http.StatusBadRequest, fmt.Sprintf("Error when parsing user agent: %s", err))
133+
telemetry.ReportCriticalError(ctx, "error when parsing user agent", err, telemetry.WithTemplateID(templateID))
134+
return
135+
}
136+
120137
// Call the Template Manager to build the environment
121-
buildErr := a.templateManager.CreateTemplate(
138+
err = a.templateManager.CreateTemplate(
122139
ctx,
123140
team.ID,
124141
templateID,
@@ -137,19 +154,56 @@ func (a *APIStore) PostV2TemplatesTemplateIDBuildsBuildID(c *gin.Context, templa
137154
body.Steps,
138155
apiutils.WithClusterFallback(team.ClusterID),
139156
build.ClusterNodeID,
157+
version,
140158
)
141-
if buildErr != nil {
142-
telemetry.ReportCriticalError(ctx, "build failed", buildErr, telemetry.WithTemplateID(templateID))
143-
a.sendAPIStoreError(c, http.StatusInternalServerError, fmt.Sprintf("Error when starting template build: %s", buildErr))
144-
return
145-
}
146159

147160
a.posthog.CreateAnalyticsTeamEvent(team.ID.String(), "built environment", posthog.NewProperties().
148161
Set("environment", templateID).
149162
Set("build_id", buildID).
150163
Set("duration", time.Since(startTime).String()).
151-
Set("success", err != nil),
164+
Set("success", err == nil),
152165
)
153166

167+
if err != nil {
168+
telemetry.ReportCriticalError(ctx, "build failed", err, telemetry.WithTemplateID(templateID))
169+
a.sendAPIStoreError(c, http.StatusInternalServerError, fmt.Sprintf("Error when starting template build: %s", err))
170+
return
171+
}
172+
154173
c.Status(http.StatusAccepted)
155174
}
175+
176+
// userAgentToTemplateVersion returns the template semver version based on the user agent string.
177+
// If the user agent is not recognized, it defaults to the latest stable version.
178+
func userAgentToTemplateVersion(logger *zap.Logger, userAgent string) (string, error) {
179+
version := templates.TemplateV2LatestVersion
180+
181+
switch {
182+
case strings.HasPrefix(userAgent, jsSDKPrefix):
183+
sdk := strings.TrimPrefix(userAgent, jsSDKPrefix)
184+
185+
// Check if the SDK version supports the latest template version
186+
ok, err := utils.IsGTEVersion(sdk, templates.SDKTemplateReleaseVersion)
187+
if err != nil {
188+
return "", fmt.Errorf("parsing JS SDK version: %w", err)
189+
}
190+
if !ok {
191+
version = templates.TemplateV2BetaVersion
192+
}
193+
case strings.HasPrefix(userAgent, pythonSDKPrefix):
194+
sdk := strings.TrimPrefix(userAgent, pythonSDKPrefix)
195+
196+
// Check if the SDK version supports the latest template version
197+
ok, err := utils.IsGTEVersion(sdk, templates.SDKTemplateReleaseVersion)
198+
if err != nil {
199+
return "", fmt.Errorf("parsing Python SDK version: %w", err)
200+
}
201+
if !ok {
202+
version = templates.TemplateV2BetaVersion
203+
}
204+
default:
205+
logger.Debug("Unrecognized user agent, defaulting to the latest template version", zap.String("user_agent", userAgent), zap.String("version", version))
206+
}
207+
208+
return version, nil
209+
}

packages/api/internal/template-manager/create_template.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ func (tm *TemplateManager) CreateTemplate(
5252
steps *[]api.TemplateStep,
5353
clusterID uuid.UUID,
5454
nodeID string,
55+
version string,
5556
) (e error) {
5657
ctx, span := tracer.Start(ctx, "create-template",
5758
trace.WithAttributes(
@@ -152,6 +153,7 @@ func (tm *TemplateManager) CreateTemplate(
152153
reqCtx, &templatemanagergrpc.TemplateCreateRequest{
153154
Template: template,
154155
CacheScope: ut.ToPtr(teamID.String()),
156+
Version: &version,
155157
},
156158
)
157159

packages/envd/internal/api/api.gen.go

Lines changed: 12 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/envd/internal/api/auth.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func (a *API) generateSignature(path string, username string, operation string,
6363
return fmt.Sprintf("v1_%s", hasher.HashWithoutPrefix([]byte(signature))), nil
6464
}
6565

66-
func (a *API) validateSigning(r *http.Request, signature *string, signatureExpiration *int, username string, path string, operation string) (err error) {
66+
func (a *API) validateSigning(r *http.Request, signature *string, signatureExpiration *int, username *string, path string, operation string) (err error) {
6767
var expectedSignature string
6868

6969
// no need to validate signing key if access token is not set
@@ -85,11 +85,17 @@ func (a *API) validateSigning(r *http.Request, signature *string, signatureExpir
8585
return fmt.Errorf("missing signature query parameter")
8686
}
8787

88+
// Empty string is used when no username is provided and the default user should be used
89+
signatureUsername := ""
90+
if username != nil {
91+
signatureUsername = *username
92+
}
93+
8894
if signatureExpiration == nil {
89-
expectedSignature, err = a.generateSignature(path, username, operation, nil)
95+
expectedSignature, err = a.generateSignature(path, signatureUsername, operation, nil)
9096
} else {
9197
exp := int64(*signatureExpiration)
92-
expectedSignature, err = a.generateSignature(path, username, operation, &exp)
98+
expectedSignature, err = a.generateSignature(path, signatureUsername, operation, &exp)
9399
}
94100

95101
if err != nil {

packages/envd/internal/api/download.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"os/user"
99
"time"
1010

11+
"github.com/e2b-dev/infra/packages/envd/internal/execcontext"
1112
"github.com/e2b-dev/infra/packages/envd/internal/logs"
1213
"github.com/e2b-dev/infra/packages/envd/internal/permissions"
1314
)
@@ -33,13 +34,20 @@ func (a *API) GetFiles(w http.ResponseWriter, r *http.Request, params GetFilesPa
3334
return
3435
}
3536

37+
username, err := execcontext.ResolveDefaultUsername(params.Username, a.defaults.User)
38+
if err != nil {
39+
a.logger.Error().Err(err).Str(string(logs.OperationIDKey), operationID).Msg("no user specified")
40+
jsonError(w, http.StatusBadRequest, err)
41+
return
42+
}
43+
3644
defer func() {
3745
l := a.logger.
3846
Err(errMsg).
3947
Str("method", r.Method+" "+r.URL.Path).
4048
Str(string(logs.OperationIDKey), operationID).
4149
Str("path", path).
42-
Str("username", params.Username)
50+
Str("username", username)
4351

4452
if errMsg != nil {
4553
l = l.Int("error_code", errorCode)
@@ -48,16 +56,16 @@ func (a *API) GetFiles(w http.ResponseWriter, r *http.Request, params GetFilesPa
4856
l.Msg("File read")
4957
}()
5058

51-
u, err := user.Lookup(params.Username)
59+
u, err := user.Lookup(username)
5260
if err != nil {
53-
errMsg = fmt.Errorf("error looking up user '%s': %w", params.Username, err)
61+
errMsg = fmt.Errorf("error looking up user '%s': %w", username, err)
5462
errorCode = http.StatusUnauthorized
5563
jsonError(w, errorCode, errMsg)
5664

5765
return
5866
}
5967

60-
resolvedPath, err := permissions.ExpandAndResolve(path, u)
68+
resolvedPath, err := permissions.ExpandAndResolve(path, u, a.defaults.Workdir)
6169
if err != nil {
6270
errMsg = fmt.Errorf("error expanding and resolving path '%s': %w", path, err)
6371
errorCode = http.StatusBadRequest

packages/envd/internal/api/envs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ func (a *API) GetEnvs(w http.ResponseWriter, _ *http.Request) {
1313
a.logger.Debug().Str(string(logs.OperationIDKey), operationID).Msg("Getting env vars")
1414

1515
envs := make(EnvVars)
16-
a.envVars.Range(func(key, value string) bool {
16+
a.defaults.EnvVars.Range(func(key, value string) bool {
1717
envs[key] = value
1818

1919
return true

packages/envd/internal/api/init.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func (a *API) PostInit(w http.ResponseWriter, r *http.Request) {
6969
go func() { //nolint:contextcheck // TODO: fix this later
7070
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
7171
defer cancel()
72-
host.PollForMMDSOpts(ctx, a.mmdsChan, a.envVars)
72+
host.PollForMMDSOpts(ctx, a.mmdsChan, a.defaults.EnvVars)
7373
}()
7474

7575
w.Header().Set("Cache-Control", "no-store")
@@ -99,7 +99,7 @@ func (a *API) SetData(logger zerolog.Logger, data PostInitJSONBody) error {
9999

100100
for key, value := range *data.EnvVars {
101101
logger.Debug().Msgf("Setting env var for %s", key)
102-
a.envVars.Store(key, value)
102+
a.defaults.EnvVars.Store(key, value)
103103
}
104104
}
105105

@@ -117,6 +117,16 @@ func (a *API) SetData(logger zerolog.Logger, data PostInitJSONBody) error {
117117
go a.SetupHyperloop(*data.HyperloopIP)
118118
}
119119

120+
if data.DefaultUser != nil && *data.DefaultUser != "" {
121+
logger.Debug().Msgf("Setting default user to: %s", *data.DefaultUser)
122+
a.defaults.User = *data.DefaultUser
123+
}
124+
125+
if data.DefaultWorkdir != nil && *data.DefaultWorkdir != "" {
126+
logger.Debug().Msgf("Setting default workdir to: %s", *data.DefaultWorkdir)
127+
a.defaults.Workdir = data.DefaultWorkdir
128+
}
129+
120130
return nil
121131
}
122132

@@ -127,7 +137,7 @@ func (a *API) SetupHyperloop(address string) {
127137
if err := rewriteHostsFile(address, "/etc/hosts"); err != nil {
128138
a.logger.Error().Err(err).Msg("failed to modify hosts file")
129139
} else {
130-
a.envVars.Store("E2B_EVENTS_ADDRESS", fmt.Sprintf("http://%s", address))
140+
a.defaults.EnvVars.Store("E2B_EVENTS_ADDRESS", fmt.Sprintf("http://%s", address))
131141
}
132142
}
133143

0 commit comments

Comments
 (0)