-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathattach.go
More file actions
100 lines (88 loc) · 3.35 KB
/
attach.go
File metadata and controls
100 lines (88 loc) · 3.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package devcontainer
import (
"context"
"fmt"
"os"
"github.com/crunchloop/devcontainer/config"
"github.com/crunchloop/devcontainer/feature"
"github.com/crunchloop/devcontainer/runtime"
)
// AttachOptions configures Engine.Attach. The default zero value is fine
// for most callers — it discovers the container by label and reads the
// host process environment for any localEnv references the substituter
// might still need.
type AttachOptions struct {
// LocalEnv overrides os.Environ() for the substituter's localEnv
// pass. Nil means use the current process environment.
LocalEnv map[string]string
}
// Attach finds an existing workspace container by its devcontainer id and
// returns a *Workspace with a substituter bound to its live env.
//
// Returns *runtime.ContainerNotFoundError if no container with the
// matching label exists. The returned workspace's Config.LocalEnv is the
// AttachOptions.LocalEnv (or os.Environ() if nil) — note that
// LocalWorkspaceFolder and ConfigPath cannot be recovered from a running
// container alone, so callers needing those should use Up.
func (e *Engine) Attach(ctx context.Context, id WorkspaceID) (*Workspace, error) {
return e.AttachWith(ctx, id, AttachOptions{})
}
// AttachWith is Attach plus options.
func (e *Engine) AttachWith(ctx context.Context, id WorkspaceID, opts AttachOptions) (*Workspace, error) {
if err := ctxIfDone(ctx); err != nil {
return nil, err
}
if id == "" {
return nil, fmt.Errorf("Engine.Attach: WorkspaceID is required")
}
c, err := e.runtime.FindContainerByLabel(ctx, LabelDevcontainerID, string(id))
if err != nil {
return nil, fmt.Errorf("find container for workspace %s: %w", id, err)
}
if c == nil {
return nil, &runtime.ContainerNotFoundError{ID: string(id)}
}
details, err := e.runtime.InspectContainer(ctx, c.ID)
if err != nil {
return nil, err
}
// Reconstruct just enough config for the substituter. Attach can't
// reproduce the full ResolvedConfig (the source devcontainer.json may
// have changed since Up); callers that need it should Resolve again.
cfg := configFromContainerLabels(details)
cfg.DevcontainerID = string(id)
// The container's image carries the merged-config metadata label
// from when Up created it; folding it in here means Attach-only
// callers see the same RemoteUser / lifecycle hooks / probe config
// as Up. Failures to read or parse the label are non-fatal — Attach
// then gives back a minimal cfg as before.
var baseLayers []config.FeatureMetadata
if details.Image != "" {
if imgDetails, err := e.runtime.InspectImage(ctx, details.Image); err == nil && imgDetails != nil {
if label := imgDetails.Labels[feature.MetadataLabel]; label != "" {
if parsed, err := feature.ParseMetadataLabel(label); err == nil {
baseLayers = parsed
}
}
}
}
config.MergeMetadata(cfg, baseLayers)
cfg.Finalize()
localEnv := opts.LocalEnv
if localEnv == nil {
localEnv = environAsMap(os.Environ())
}
ws := &Workspace{
ID: id,
Config: cfg,
Container: details,
subst: newSubstituter(cfg, details, localEnv),
}
// Re-probe on attach so subsequent Exec calls see PATH additions
// from the user's rc files. The original Up populated probedEnv,
// but a fresh Attach doesn't share that workspace value.
if probed, err := e.probeUserEnv(ctx, ws, cfg.UserEnvProbe); err == nil {
ws.probedEnv = probed
}
return ws, nil
}