Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
This repository was archived by the owner on Aug 30, 2024. It is now read-only.
/coder-v1-cliPublic archive

Commit288197a

Browse files
authored
feat: Prompt the user to rebuild workspace oncoder sh (#223)
If the workspace is OFF or a rebuild is required, promptthe user to rebuild right away. This prompt only occursin an interactive shell
1 parentdaa3f7a commit288197a

File tree

3 files changed

+167
-27
lines changed

3 files changed

+167
-27
lines changed

‎coder-sdk/env.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,3 +274,12 @@ func (c Client) WaitForEnvironmentReady(ctx context.Context, envID string) error
274274
}
275275
}
276276
}
277+
278+
// EnvironmentByID get the details of an environment by its id.
279+
func (cClient)EnvironmentByID(ctx context.Context,idstring) (*Environment,error) {
280+
varenvEnvironment
281+
iferr:=c.requestBody(ctx,http.MethodGet,"/api/v0/environments/"+id,nil,&env);err!=nil {
282+
returnnil,err
283+
}
284+
return&env,nil
285+
}

‎docs/coder_sh.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ Open a shell and execute commands in a Coder environment
44

55
###Synopsis
66

7-
Execute a remote command on the environment\nIf no command is specified, the default shell is opened.
7+
Execute a remote command on the environment
8+
If no command is specified, the default shell is opened.
9+
If the command is run in an interactive shell, a user prompt will occur if the environment needs to be rebuilt.
810

911
```
1012
coder sh [environment_name] [<command [args...]>] [flags]

‎internal/cmd/shell.go

Lines changed: 155 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99
"time"
1010

11+
"github.com/manifoldco/promptui"
1112
"github.com/spf13/cobra"
1213
"golang.org/x/crypto/ssh/terminal"
1314
"golang.org/x/time/rate"
@@ -65,9 +66,11 @@ func shValidArgs(cmd *cobra.Command, args []string) error {
6566

6667
funcshCmd()*cobra.Command {
6768
return&cobra.Command{
68-
Use:"sh [environment_name] [<command [args...]>]",
69-
Short:"Open a shell and execute commands in a Coder environment",
70-
Long:"Execute a remote command on the environment\\nIf no command is specified, the default shell is opened.",
69+
Use:"sh [environment_name] [<command [args...]>]",
70+
Short:"Open a shell and execute commands in a Coder environment",
71+
Long:`Execute a remote command on the environment
72+
If no command is specified, the default shell is opened.
73+
If the command is run in an interactive shell, a user prompt will occur if the environment needs to be rebuilt.`,
7174
Args:shValidArgs,
7275
DisableFlagParsing:true,
7376
ValidArgsFunction:getEnvsForCompletion(coder.Me),
@@ -92,7 +95,28 @@ func shell(cmd *cobra.Command, cmdArgs []string) error {
9295

9396
envName:=cmdArgs[0]
9497

95-
iferr:=runCommand(ctx,envName,command,args);err!=nil {
98+
// Before the command is run, ensure the workspace is on and ready to accept
99+
// an ssh connection.
100+
client,err:=newClient(ctx)
101+
iferr!=nil {
102+
returnerr
103+
}
104+
105+
env,err:=findEnv(ctx,client,envName,coder.Me)
106+
iferr!=nil {
107+
returnerr
108+
}
109+
110+
// TODO: Verify this is the correct behavior
111+
isInteractive:=terminal.IsTerminal(int(os.Stdout.Fd()))
112+
ifisInteractive {// checkAndRebuildEnvironment requires an interactive shell
113+
// Checks & Rebuilds the environment if needed.
114+
iferr:=checkAndRebuildEnvironment(ctx,client,env);err!=nil {
115+
returnerr
116+
}
117+
}
118+
119+
iferr:=runCommand(ctx,client,env,command,args);err!=nil {
96120
ifexitErr,ok:=err.(wsep.ExitError);ok {
97121
os.Exit(exitErr.Code)
98122
}
@@ -101,6 +125,132 @@ func shell(cmd *cobra.Command, cmdArgs []string) error {
101125
returnnil
102126
}
103127

128+
// rebuildPrompt returns function that prompts the user if they wish to
129+
// rebuild the selected environment if a rebuild is needed. The returned prompt function will
130+
// return an error if the user selects "no".
131+
// This functions returns `nil` if there is no reason to prompt the user to rebuild
132+
// the environment.
133+
funcrebuildPrompt(env*coder.Environment) (promptfunc()error) {
134+
// Option 1: If the environment is off, the rebuild is needed
135+
ifenv.LatestStat.ContainerStatus==coder.EnvironmentOff {
136+
confirm:= promptui.Prompt{
137+
Label:fmt.Sprintf("Environment %q is\"OFF\". Rebuild it now? (this can take several minutes",env.Name),
138+
IsConfirm:true,
139+
}
140+
returnfunc() (errerror) {
141+
_,err=confirm.Run()
142+
return
143+
}
144+
}
145+
146+
// Option 2: If there are required rebuild messages, the rebuild is needed
147+
varlines []string
148+
for_,r:=rangeenv.RebuildMessages {
149+
ifr.Required {
150+
lines=append(lines,clog.Causef(r.Text))
151+
}
152+
}
153+
154+
iflen(lines)>0 {
155+
confirm:= promptui.Prompt{
156+
Label:fmt.Sprintf("Environment %q requires a rebuild to work correctly. Do you wish to rebuild it now? (this will take a moment)",env.Name),
157+
IsConfirm:true,
158+
}
159+
// This function also prints the reasons in a log statement.
160+
// The confirm prompt does not handle new lines well in the label.
161+
returnfunc() (errerror) {
162+
clog.LogWarn("rebuild required",lines...)
163+
_,err=confirm.Run()
164+
return
165+
}
166+
}
167+
168+
// Environment looks good, no need to prompt the user.
169+
returnnil
170+
}
171+
172+
// checkAndRebuildEnvironment will:
173+
//1. Check if an environment needs to be rebuilt to be used
174+
// 2. Prompt the user if they want to rebuild the environment (returns an error if they do not)
175+
//3. Rebuilds the environment and waits for it to be 'ON'
176+
// Conditions for rebuilding are:
177+
//- Environment is offline
178+
//- Environment has rebuild messages requiring a rebuild
179+
funccheckAndRebuildEnvironment(ctx context.Context,client*coder.Client,env*coder.Environment)error {
180+
varerrerror
181+
rebuildPrompt:=rebuildPrompt(env)// Fetch the prompt for rebuilding envs w/ reason
182+
183+
switch {
184+
// If this conditonal is true, a rebuild is **required** to make the sh command work.
185+
caserebuildPrompt!=nil:
186+
// TODO: (@emyrk) I'd like to add a --force and --verbose flags to this command,
187+
//but currently DisableFlagParsing is set to true.
188+
//To enable force/verbose, we'd have to parse the flags ourselves,
189+
//or make the user `coder sh <env> -- [args]`
190+
//
191+
iferr:=rebuildPrompt();err!=nil {
192+
// User selected not to rebuild :(
193+
returnclog.Fatal(
194+
"environment is not ready for use",
195+
"environment requires a rebuild",
196+
fmt.Sprintf("its current status is %q",env.LatestStat.ContainerStatus),
197+
clog.BlankLine,
198+
clog.Tipf("run\"coder envs rebuild %s --follow\" to start the environment",env.Name),
199+
)
200+
}
201+
202+
// Start the rebuild
203+
iferr:=client.RebuildEnvironment(ctx,env.ID);err!=nil {
204+
returnerr
205+
}
206+
207+
fallthrough// Fallthrough to watching the logs
208+
caseenv.LatestStat.ContainerStatus==coder.EnvironmentCreating:
209+
// Environment is in the process of being created, just trail the logs
210+
// and wait until it is done
211+
clog.LogInfo(fmt.Sprintf("Rebuilding %q",env.Name))
212+
213+
// Watch the rebuild.
214+
iferr:=trailBuildLogs(ctx,client,env.ID);err!=nil {
215+
returnerr
216+
}
217+
218+
// newline after trailBuildLogs to place user on a fresh line for their shell
219+
fmt.Println()
220+
221+
// At this point the buildlog is complete, and the status of the env should be 'ON'
222+
env,err=client.EnvironmentByID(ctx,env.ID)
223+
iferr!=nil {
224+
// If this api call failed, it will likely fail again, no point to retry and make the user wait
225+
returnerr
226+
}
227+
228+
ifenv.LatestStat.ContainerStatus!=coder.EnvironmentOn {
229+
// This means we had a timeout
230+
returnclog.Fatal("the environment rebuild ran into an issue",
231+
fmt.Sprintf("environment %q rebuild has failed and will not come online",env.Name),
232+
fmt.Sprintf("its current status is %q",env.LatestStat.ContainerStatus),
233+
clog.BlankLine,
234+
// TODO: (@emyrk) can they check these logs from the cli? Isn't this the logs that
235+
//I just showed them? I'm trying to decide what exactly to tell a user.
236+
clog.Tipf("take a look at the build logs to determine what went wrong"),
237+
)
238+
}
239+
240+
caseenv.LatestStat.ContainerStatus==coder.EnvironmentFailed:
241+
// A failed container might just keep re-failing. I think it should be investigated by the user
242+
returnclog.Fatal("the environment has failed to come online",
243+
fmt.Sprintf("environment %q is not running",env.Name),
244+
fmt.Sprintf("its current status is %q",env.LatestStat.ContainerStatus),
245+
246+
clog.BlankLine,
247+
clog.Tipf("take a look at the build logs to determine what went wrong"),
248+
clog.Tipf("run\"coder envs rebuild %s --follow\" to attempt to rebuild the environment",env.Name),
249+
)
250+
}
251+
returnnil
252+
}
253+
104254
// sendResizeEvents starts watching for the client's terminal resize signals
105255
// and sends the event to the server so the remote tty can match the client.
106256
funcsendResizeEvents(ctx context.Context,termFDuintptr,process wsep.Process) {
@@ -121,28 +271,7 @@ func sendResizeEvents(ctx context.Context, termFD uintptr, process wsep.Process)
121271
}
122272
}
123273

124-
funcrunCommand(ctx context.Context,envName,commandstring,args []string)error {
125-
client,err:=newClient(ctx)
126-
iferr!=nil {
127-
returnerr
128-
}
129-
env,err:=findEnv(ctx,client,envName,coder.Me)
130-
iferr!=nil {
131-
returnxerrors.Errorf("find environment: %w",err)
132-
}
133-
134-
// check if a rebuild is required before attempting to open a shell
135-
for_,r:=rangeenv.RebuildMessages {
136-
// use the first rebuild message that is required
137-
ifr.Required {
138-
returnclog.Error(
139-
fmt.Sprintf(`environment "%s" requires a rebuild`,env.Name),
140-
clog.Causef(r.Text),clog.BlankLine,
141-
clog.Tipf(`run "coder envs rebuild %s" to rebuild`,env.Name),
142-
)
143-
}
144-
}
145-
274+
funcrunCommand(ctx context.Context,client*coder.Client,env*coder.Environment,commandstring,args []string)error {
146275
termFD:=os.Stdout.Fd()
147276

148277
isInteractive:=terminal.IsTerminal(int(termFD))

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp