-
Notifications
You must be signed in to change notification settings - Fork 9
/
opaque.go
216 lines (180 loc) · 6.43 KB
/
opaque.go
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
// Copyright 2022 Namespace Labs Inc; All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
package opaque
import (
"context"
"fmt"
"strings"
"namespacelabs.dev/foundation/internal/build"
"namespacelabs.dev/foundation/internal/build/assets"
"namespacelabs.dev/foundation/internal/build/binary"
"namespacelabs.dev/foundation/internal/fnerrors"
"namespacelabs.dev/foundation/internal/fnfs/workspace/wsremote"
"namespacelabs.dev/foundation/internal/hotreload"
hrconstants "namespacelabs.dev/foundation/internal/hotreload/constants"
"namespacelabs.dev/foundation/internal/integrations"
"namespacelabs.dev/foundation/internal/parsing"
"namespacelabs.dev/foundation/internal/planning"
"namespacelabs.dev/foundation/internal/runtime"
"namespacelabs.dev/foundation/internal/wscontents"
"namespacelabs.dev/foundation/schema"
"namespacelabs.dev/foundation/std/pkggraph"
"namespacelabs.dev/foundation/std/runtime/constants"
)
func Register() {
integrations.Register(schema.Framework_OPAQUE, OpaqueIntegration{})
}
type OpaqueIntegration struct {
integrations.MaybeGenerate
integrations.MaybeTidy // TODO implement tidy per parser.
}
func (OpaqueIntegration) PrepareBuild(ctx context.Context, assets assets.AvailableBuildAssets, server planning.PlannedServer, isFocus bool) (build.Spec, error) {
binRef := server.MergedFragment.GetMainContainer().GetBinaryRef()
if binRef == nil {
return nil, fnerrors.InternalError("server binary is not set at %s", server.Location)
}
pkg, err := server.SealedContext().LoadByName(ctx, binRef.AsPackageName())
if err != nil {
return nil, err
}
prep, err := binary.Plan(ctx, pkg, binRef.GetName(), server.SealedContext(), assets,
binary.BuildImageOpts{UsePrebuilts: true})
if err != nil {
return nil, err
}
filesyncConfig, err := getFilesyncWorkspacePath(server)
if err != nil {
return nil, err
}
if filesyncConfig != nil {
pkg, err := server.SealedContext().LoadByName(ctx, hrconstants.ControllerPkg.AsPackageName())
if err != nil {
return nil, err
}
ctrlBin, err := binary.Plan(ctx, pkg, hrconstants.ControllerPkg.Name, server.SealedContext(), assets, binary.BuildImageOpts{UsePrebuilts: false})
if err != nil {
return nil, err
}
return binary.MergeSpecs{
Specs: []build.Spec{prep.Plan.Spec, ctrlBin.Plan.Spec},
Descriptions: []string{prep.Name, "workspace sync controller"},
}, nil
} else {
return prep.Plan.Spec, nil
}
}
func (OpaqueIntegration) PrepareRun(ctx context.Context, server planning.PlannedServer, run *runtime.ContainerRunOpts) error {
binRef := server.MergedFragment.GetMainContainer().GetBinaryRef()
if binRef != nil {
_, binary, err := pkggraph.LoadBinary(ctx, server.SealedContext(), binRef)
if err != nil {
return err
}
config := binary.Config
if config != nil {
run.WorkingDir = config.WorkingDir
run.Command = config.Command
run.Args = config.Args
run.Env = config.Env
}
filesyncConfig, err := getFilesyncWorkspacePath(server)
if err != nil {
return err
}
if filesyncConfig != nil {
if len(run.Command) == 0 {
return fnerrors.NewWithLocation(server.Location, "dockerfile command must be explicitly set when there is a workspace sync mount")
}
run.Args = append(append(
[]string{filesyncConfig.mountPath, fmt.Sprint(hrconstants.FileSyncPort)},
run.Command...),
run.Args...)
run.Command = []string{hrconstants.ControllerCommand}
}
}
return nil
}
func (OpaqueIntegration) PrepareDev(ctx context.Context, cluster runtime.ClusterNamespace, server planning.PlannedServer) (context.Context, integrations.DevObserver, error) {
filesyncConfig, err := getFilesyncWorkspacePath(server)
if err != nil {
return nil, nil, err
}
if filesyncConfig != nil {
return hotreload.ConfigureFileSyncDevObserver(ctx, cluster, server.Server)
}
return ctx, nil, nil
}
func (OpaqueIntegration) PreParseServer(ctx context.Context, loc pkggraph.Location, ext *parsing.ServerFrameworkExt) error {
return nil
}
func (OpaqueIntegration) PostParseServer(ctx context.Context, _ *parsing.Sealed) error {
return nil
}
func (OpaqueIntegration) DevelopmentPackages() []schema.PackageName {
return nil
}
func (OpaqueIntegration) PrepareHotReload(ctx context.Context, remote *wsremote.SinkRegistrar, srv planning.PlannedServer) *integrations.HotReloadOpts {
if remote == nil {
return nil
}
filesyncConfig, err := getFilesyncWorkspacePath(srv)
if err != nil {
// Shouldn't happen because getFilesyncWorkspacePath() is already called in PrepareDev().
panic(fnerrors.InternalError("Error from getFilesyncWorkspacePath in PrepareHotReload, shouldn't happen: %v", err))
}
if filesyncConfig == nil {
return nil
}
return &integrations.HotReloadOpts{
// "ModuleName" and "Rel" are empty because we have only one module in the image and
// we put the package content directly under the root "/app" directory.
Sink: remote.For(&wsremote.Signature{ModuleName: "", Rel: ""}),
EventProcessor: func(ev *wscontents.FileEvent) *wscontents.FileEvent {
if strings.HasPrefix(ev.Path, filesyncConfig.srcPath+"/") {
return &wscontents.FileEvent{
Event: ev.Event,
Path: ev.Path[len(filesyncConfig.srcPath)+1:],
NewContents: ev.NewContents,
Mode: ev.Mode,
}
} else {
return nil
}
},
}
}
type filesyncConfig struct {
// Relative to the package
srcPath string
mountPath string
}
// If not nil, filesync is requested and enabled.
func getFilesyncWorkspacePath(server planning.PlannedServer) (*filesyncConfig, error) {
if !UseDevBuild(server.SealedContext().Environment()) {
return nil, nil
}
for _, m := range server.MergedFragment.GetMainContainer().GetMount() {
// Only supporting volumes within the same package for now.
v, err := findVolume(server.Proto().GetSelf().Volume, m.VolumeRef.Name)
if err != nil {
return nil, err
}
if v.Kind == constants.VolumeKindWorkspaceSync {
cv := &schema.WorkspaceSyncVolume{}
if err := v.Definition.UnmarshalTo(cv); err != nil {
return nil, fnerrors.InternalError("%s: failed to unmarshal workspacesync volume definition: %w", v.Name, err)
}
return &filesyncConfig{cv.Path, m.Path}, nil
}
}
return nil, nil
}
func findVolume(volumes []*schema.Volume, name string) (*schema.Volume, error) {
for _, v := range volumes {
if v.Name == name {
return v, nil
}
}
return nil, fnerrors.InternalError("volume %s not found", name)
}