Skip to content

Commit 8a626ed

Browse files
committed
Migrate to user serpent
1 parent ff1b0d0 commit 8a626ed

File tree

8 files changed

+445
-544
lines changed

8 files changed

+445
-544
lines changed

cmd/envbuilder/main.go

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
"github.com/coder/coder/v2/codersdk"
1515
"github.com/coder/coder/v2/codersdk/agentsdk"
1616
"github.com/coder/envbuilder"
17-
"github.com/spf13/cobra"
17+
"github.com/coder/serpent"
1818

1919
// *Never* remove this. Certificates are not bundled as part
2020
// of the container, so this is necessary for all connections
@@ -23,15 +23,14 @@ import (
2323
)
2424

2525
func main() {
26-
root := &cobra.Command{
27-
Use: "envbuilder",
26+
config := envbuilder.Config{}
27+
cmd := serpent.Command{
28+
Use: "envbuilder",
29+
Options: config.Options(),
2830
// Hide usage because we don't want to show the
2931
// "envbuilder [command] --help" output on error.
30-
SilenceUsage: true,
31-
SilenceErrors: true,
32-
RunE: func(cmd *cobra.Command, args []string) error {
33-
options := envbuilder.OptionsFromEnv(os.LookupEnv)
34-
32+
Hidden: true,
33+
Handler: func(inv *serpent.Invocation) error {
3534
var sendLogs func(ctx context.Context, log ...agentsdk.Log) error
3635
agentURL := os.Getenv("CODER_AGENT_URL")
3736
agentToken := os.Getenv("CODER_AGENT_TOKEN")
@@ -48,13 +47,13 @@ func main() {
4847
client.SDK.HTTPClient = &http.Client{
4948
Transport: &http.Transport{
5049
TLSClientConfig: &tls.Config{
51-
InsecureSkipVerify: options.GetBool("Insecure"),
50+
InsecureSkipVerify: config.Insecure,
5251
},
5352
},
5453
}
5554
var flushAndClose func(ctx context.Context) error
5655
sendLogs, flushAndClose = agentsdk.LogsSender(agentsdk.ExternalLogSourceID, client.PatchLogs, slog.Logger{})
57-
defer flushAndClose(cmd.Context())
56+
defer flushAndClose(inv.Context())
5857

5958
// This adds the envbuilder subsystem.
6059
// If telemetry is enabled in a Coder deployment,
@@ -70,24 +69,24 @@ func main() {
7069

7170
logger := func(level codersdk.LogLevel, format string, args ...interface{}) {
7271
output := fmt.Sprintf(format, args...)
73-
fmt.Fprintln(cmd.ErrOrStderr(), output)
72+
fmt.Fprintln(inv.Stderr, output)
7473
if sendLogs != nil {
75-
sendLogs(cmd.Context(), agentsdk.Log{
74+
sendLogs(inv.Context(), agentsdk.Log{
7675
CreatedAt: time.Now(),
7776
Output: output,
7877
Level: level,
7978
})
8079
}
8180
}
8281

83-
err := envbuilder.Run(cmd.Context(), options, nil, logger)
82+
err := envbuilder.Run(inv.Context(), &config, nil, logger)
8483
if err != nil {
8584
logger(codersdk.LogLevelError, "error: %s", err)
8685
}
8786
return err
8887
},
8988
}
90-
err := root.Execute()
89+
err := cmd.Invoke().WithOS().Run()
9190
if err != nil {
9291
fmt.Fprintf(os.Stderr, "error: %v", err)
9392
os.Exit(1)

config.go

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
package envbuilder
2+
3+
import (
4+
"github.com/coder/serpent"
5+
)
6+
7+
type Config struct {
8+
SetupScript string
9+
InitScript string
10+
InitCommand string
11+
InitArgs string
12+
CacheRepo string
13+
BaseImageCacheDir string
14+
LayerCacheDir string
15+
DevcontainerDir string
16+
DevcontainerJSONPath string
17+
DockerfilePath string
18+
BuildContextPath string
19+
CacheTTLDays int64
20+
DockerConfigBase64 string
21+
FallbackImage string
22+
ExitOnBuildFailure bool
23+
ForceSafe bool
24+
Insecure bool
25+
IgnorePaths []string
26+
SkipRebuild bool
27+
GitURL string
28+
GitCloneDepth int64
29+
GitCloneSingleBranch bool
30+
GitUsername string
31+
GitPassword string
32+
GitHTTPProxyURL string
33+
WorkspaceFolder string
34+
SSLCertBase64 string
35+
ExportEnvFile string
36+
PostStartScriptPath string
37+
}
38+
39+
func (c *Config) Options() serpent.OptionSet {
40+
return serpent.OptionSet{
41+
{
42+
Name: "Setup Script",
43+
Env: "SETUP_SCRIPT",
44+
Flag: "setup-script",
45+
Value: serpent.StringOf(&c.SetupScript),
46+
Description: `SetupScript is the script to run before the init script.
47+
It runs as the root user regardless of the user specified
48+
in the devcontainer.json file.
49+
50+
SetupScript is ran as the root user prior to the init script.
51+
It is used to configure envbuilder dynamically during the runtime.
52+
e.g. specifying whether to start ` + "`systemd`" + ` or ` + "`tiny init`" + ` for PID 1.`,
53+
},
54+
{
55+
Name: "Init Script",
56+
Env: "INIT_SCRIPT",
57+
Default: "sleep infinity",
58+
Value: serpent.StringOf(&c.InitScript),
59+
Description: "InitScript is the script to run to initialize the workspace.",
60+
},
61+
{
62+
Name: "Init Command",
63+
Env: "INIT_COMMAND",
64+
Default: "/bin/sh",
65+
Value: serpent.StringOf(&c.InitCommand),
66+
Description: "InitCommand is the command to run to initialize the workspace.",
67+
},
68+
{
69+
Name: "Init Args",
70+
Env: "INIT_ARGS",
71+
Value: serpent.StringOf(&c.InitArgs),
72+
Description: `InitArgs are the arguments to pass to the init command.
73+
They are split according to ` + "`/bin/sh`" + ` rules with
74+
https://github.com/kballard/go-shellquote`,
75+
},
76+
{
77+
Name: "Cache Repo",
78+
Env: "CACHE_REPO",
79+
Value: serpent.StringOf(&c.CacheRepo),
80+
Description: `CacheRepo is the name of the container registry
81+
to push the cache image to. If this is empty, the cache
82+
will not be pushed.`,
83+
},
84+
{
85+
Name: "Base Image Cache Dir",
86+
Env: "BASE_IMAGE_CACHE_DIR",
87+
Value: serpent.StringOf(&c.BaseImageCacheDir),
88+
Description: `BaseImageCacheDir is the path to a directory where the base
89+
image can be found. This should be a read-only directory
90+
solely mounted for the purpose of caching the base image.`,
91+
},
92+
{
93+
Name: "Layer Cache Dir",
94+
Env: "LAYER_CACHE_DIR",
95+
Value: serpent.StringOf(&c.LayerCacheDir),
96+
Description: `LayerCacheDir is the path to a directory where built layers
97+
will be stored. This spawns an in-memory registry to serve
98+
the layers from.`,
99+
},
100+
{
101+
Name: "Devcontainer Dir",
102+
Env: "DEVCONTAINER_DIR",
103+
Value: serpent.StringOf(&c.DevcontainerDir),
104+
Description: `DevcontainerDir is a path to the folder containing
105+
the devcontainer.json file that will be used to build the
106+
workspace and can either be an absolute path or a path
107+
relative to the workspace folder. If not provided, defaults to
108+
` + "`.devcontainer`" + `.`,
109+
},
110+
{
111+
Name: "Devcontainer JSON Path",
112+
Env: "DEVCONTAINER_JSON_PATH",
113+
Value: serpent.StringOf(&c.DevcontainerJSONPath),
114+
Description: `DevcontainerJSONPath is a path to a devcontainer.json file
115+
that is either an absolute path or a path relative to
116+
DevcontainerDir. This can be used in cases where one wants
117+
to substitute an edited devcontainer.json file for the one
118+
that exists in the repo.`,
119+
},
120+
{
121+
Name: "Dockerfile Path",
122+
Env: "DOCKERFILE_PATH",
123+
Value: serpent.StringOf(&c.DockerfilePath),
124+
Description: `DockerfilePath is a relative path to the Dockerfile that
125+
will be used to build the workspace. This is an alternative
126+
to using a devcontainer that some might find simpler.`,
127+
},
128+
{
129+
Name: "Build Context Path",
130+
Env: `BUILD_CONTEXT_PATH`,
131+
Value: serpent.StringOf(&c.BuildContextPath),
132+
Description: `BuildContextPath can be specified when a DockerfilePath is specified outside the base WorkspaceFolder.
133+
This path MUST be relative to the WorkspaceFolder path into which the repo is cloned.`,
134+
},
135+
{
136+
Name: "Cache TTL Days",
137+
Env: "CACHE_TTL_DAYS",
138+
Value: serpent.Int64Of(&c.CacheTTLDays),
139+
Description: `CacheTTLDays is the number of days to use cached layers before
140+
expiring them. Defaults to 7 days.`,
141+
},
142+
{
143+
Name: "Docker Config Base64",
144+
Env: "DOCKER_CONFIG_BASE64",
145+
Value: serpent.StringOf(&c.DockerConfigBase64),
146+
Description: `DockerConfigBase64 is a base64 encoded Docker config
147+
file that will be used to pull images from private
148+
container registries.`,
149+
},
150+
{
151+
Name: "Fallback Image",
152+
Env: "FALLBACK_IMAGE",
153+
Value: serpent.StringOf(&c.FallbackImage),
154+
Description: `FallbackImage specifies an alternative image to use when neither
155+
an image is declared in the devcontainer.json file nor a Dockerfile is present.
156+
If there's a build failure (from a faulty Dockerfile) or a misconfiguration,
157+
this image will be the substitute.
158+
Set ` + "`ExitOnBuildFailure`" + ` to true to halt the container if the build faces an issue.`,
159+
},
160+
{
161+
Env: "EXIT_ON_BUILD_FAILURE",
162+
Value: serpent.BoolOf(&c.ExitOnBuildFailure),
163+
Description: `ExitOnBuildFailure terminates the container upon a build failure.
164+
This is handy when preferring the ` + "`FALLBACK_IMAGE`" + ` in cases where
165+
no devcontainer.json or image is provided. However, it ensures
166+
that the container stops if the build process encounters an error.`,
167+
},
168+
{
169+
Name: "Force Safe",
170+
Env: "FORCE_SAFE",
171+
Value: serpent.BoolOf(&c.ForceSafe),
172+
Description: `ForceSafe ignores any filesystem safety checks.
173+
This could cause serious harm to your system!
174+
This is used in cases where bypass is needed
175+
to unblock customers!`,
176+
},
177+
{
178+
Name: "Insecure",
179+
Env: "INSECURE",
180+
Value: serpent.BoolOf(&c.Insecure),
181+
Description: `Insecure bypasses TLS verification when cloning
182+
and pulling from container registries.`,
183+
},
184+
{
185+
Name: "Ignore Paths",
186+
Env: "IGNORE_PATHS",
187+
Value: serpent.StringArrayOf(&c.IgnorePaths),
188+
// Kubernetes frequently stores secrets in /var/run/secrets, and
189+
// other applications might as well. This seems to be a sensible
190+
// default, but if that changes, it's simple to adjust.
191+
Default: "/var/run",
192+
Description: `IgnorePaths is a comma separated list of paths
193+
to ignore when building the workspace.`,
194+
},
195+
{
196+
Name: "Skip Rebuild",
197+
Env: "SKIP_REBUILD",
198+
Value: serpent.BoolOf(&c.SkipRebuild),
199+
Description: `SkipRebuild skips building if the MagicFile exists.
200+
This is used to skip building when a container is
201+
restarting. e.g. docker stop -> docker start
202+
This value can always be set to true - even if the
203+
container is being started for the first time.`,
204+
},
205+
{
206+
Name: "Git URL",
207+
Env: "GIT_URL",
208+
Value: serpent.StringOf(&c.GitURL),
209+
Description: `GitURL is the URL of the Git repository to clone.
210+
This is optional!`,
211+
},
212+
{
213+
Name: "Git Clone Depth",
214+
Env: "GIT_CLONE_DEPTH",
215+
Value: serpent.Int64Of(&c.GitCloneDepth),
216+
Description: `GitCloneDepth is the depth to use when cloning
217+
the Git repository.`,
218+
},
219+
{
220+
Name: "Git Clone Single Branch",
221+
Env: "GIT_CLONE_SINGLE_BRANCH",
222+
Value: serpent.BoolOf(&c.GitCloneSingleBranch),
223+
Description: `GitCloneSingleBranch clones only a single branch
224+
of the Git repository.`,
225+
},
226+
{
227+
Name: "Git Username",
228+
Env: "GIT_USERNAME",
229+
Value: serpent.StringOf(&c.GitUsername),
230+
Description: `GitUsername is the username to use for Git authentication.
231+
This is optional!`,
232+
},
233+
{
234+
Name: "Git Password",
235+
Env: "GIT_PASSWORD",
236+
Value: serpent.StringOf(&c.GitPassword),
237+
Description: `GitPassword is the password to use for Git authentication.
238+
This is optional!`,
239+
},
240+
{
241+
Name: "Git HTTP Proxy URL",
242+
Env: "GIT_HTTP_PROXY_URL",
243+
Value: serpent.StringOf(&c.GitHTTPProxyURL),
244+
Description: `GitHTTPProxyURL is the url for the http proxy.
245+
This is optional!`,
246+
},
247+
{
248+
Name: "Workspace Folder",
249+
Env: "WORKSPACE_FOLDER",
250+
Value: serpent.StringOf(&c.WorkspaceFolder),
251+
Description: `WorkspaceFolder is the path to the workspace folder
252+
that will be built. This is optional!`,
253+
},
254+
{
255+
Name: "SSL Cert Base64",
256+
Env: "SSL_CERT_BASE64",
257+
Value: serpent.StringOf(&c.SSLCertBase64),
258+
Description: `SSLCertBase64 is the content of an SSL cert file.
259+
This is useful for self-signed certificates.`,
260+
},
261+
{
262+
Name: "Export Env File",
263+
Env: "EXPORT_ENV_FILE",
264+
Value: serpent.StringOf(&c.ExportEnvFile),
265+
Description: `ExportEnvFile is an optional file path to a .env file where
266+
envbuilder will dump environment variables from devcontainer.json and
267+
the built container image.`,
268+
},
269+
{
270+
Name: "Post Start Script Path",
271+
Env: "POST_START_SCRIPT_PATH",
272+
Value: serpent.StringOf(&c.PostStartScriptPath),
273+
Description: `PostStartScriptPath is the path to a script that will be created by
274+
envbuilder based on the ` + "`postStartCommand`" + ` in devcontainer.json, if any
275+
is specified (otherwise the script is not created). If this is set, the
276+
specified InitCommand should check for the presence of this script and
277+
execute it after successful startup.`,
278+
},
279+
}
280+
}

0 commit comments

Comments
 (0)