Skip to content

Commit db527d1

Browse files
authored
Add --auto-approve to experimental prompt sites (#5026)
## Why The rest of the CLI already exposes `--auto-approve` on confirmation prompts: `bundle deploy`, `bundle destroy`, `pipelines deploy`, `pipelines destroy`, `completion install`, `completion uninstall`, `auth logout`, and `apps delete` all have it. Five prompts in experimental commands do not, which is inconsistent and also blocks any non-interactive use of those commands (CI, scripts). The five sites: - `databricks ssh setup`: "Host already exists" prompt - `databricks ssh connect`: required IDE extension install - `databricks ssh connect`: IDE settings update - `databricks apps dev-remote`: viewer connection request - `databricks apps init`: optional resource confirmation All five are in `Hidden: true` / experimental commands. ## Changes Before: these prompts could not be bypassed, so these commands didn't match the `--auto-approve` convention the rest of the CLI has settled on. Now: each of the four owning commands accepts `--auto-approve`, and the prompt is skipped when the flag is set. Follows the same convention as the commands listed above: a bool flag on the command, threaded through the options struct, checked before the prompt. No new cmdio helper, no context-based capability, no new pattern - just applying the existing one to the last few places that didn't have it. Behavior details: - **`ssh setup --auto-approve`**: recreates an existing host config without prompting. - **`ssh connect --auto-approve`**: installs the required IDE SSH extension and applies missing IDE settings without prompting. Also removes the `cmdio.IsPromptSupported` short-circuit on the settings path so the flag works in non-TTY contexts (the whole point). - **`apps dev-remote --auto-approve`**: auto-approves every viewer connection for the life of the session. Help text flags the trust implication (anyone with the shareable dev URL is trusted). Intended for trusted environments only. - **`apps init --auto-approve`**: skips the `Configure <optional resource>?` confirmation. Optional resources are only configured when their values are supplied via `--set plugin.resourceKey.field=value`. No `NEXT_CHANGELOG.md` entry since all commands are experimental. ## Test plan - [x] `make checks` clean - [x] `make lint` clean - [x] `go test ./experimental/ssh/... ./libs/apps/vite/... ./cmd/apps/...` passes - [x] Unit tests added: - `TestSetup_AutoApproveRecreatesExistingHost`: Setup with AutoApprove=true recreates a pre-existing host config - `TestCheckIDESSHExtension_AutoApproveMissing_Installs`: extension is installed without a prompt - `TestCheckIDESSHExtension_NoPrompt_WithoutAutoApprove_Errors`: non-interactive without the flag still errors with install instructions - `TestCheckAndUpdateSettings_AutoApproveWithoutPromptSupport`: settings applied even when prompts are unsupported - `TestCheckAndUpdateSettings_AutoApproveCreatesMissingFile`: missing settings file is created - `TestNewBridge_AutoApprove`: bridge stores the flag - `TestBridgeHandleConnectionRequest_AutoApproveSkipsStdin`: connection request is approved without reading stdin - [ ] Manual: `databricks ssh setup --name h --cluster <id>` twice; second run with `--auto-approve` recreates silently - [ ] Manual: `databricks ssh connect --ide vscode --auto-approve` with the SSH extension missing: installs without a prompt - [ ] Manual: `databricks apps dev-remote --name <app> --auto-approve`: external viewer opens the shareable URL and is approved without stdin interaction - [ ] Manual: `databricks apps init --name x --features analytics --auto-approve`: optional resources are skipped when no `--set` supplied; honored when `--set` is supplied
1 parent c20559d commit db527d1

14 files changed

Lines changed: 330 additions & 92 deletions

File tree

cmd/apps/dev.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,10 @@ func startViteDevServer(ctx context.Context, appURL string, port int) (*exec.Cmd
113113

114114
func newDevRemoteCmd() *cobra.Command {
115115
var (
116-
appName string
117-
clientPath string
118-
port int
116+
appName string
117+
clientPath string
118+
port int
119+
autoApprove bool
119120
)
120121

121122
cmd := &cobra.Command{
@@ -174,7 +175,7 @@ Examples:
174175
appName = selected
175176
}
176177

177-
bridge := vite.NewBridge(ctx, w, appName, port)
178+
bridge := vite.NewBridge(ctx, w, appName, port, autoApprove)
178179

179180
// Validate app exists and get domain before starting Vite
180181
var appDomain *url.URL
@@ -234,6 +235,7 @@ Examples:
234235
cmd.Flags().StringVar(&appName, "name", "", "Name of the app to connect to (prompts if not provided)")
235236
cmd.Flags().StringVar(&clientPath, "client-path", "./client", "Path to the Vite client directory")
236237
cmd.Flags().IntVar(&port, "port", vitePort, "Port to run the Vite server on")
238+
cmd.Flags().BoolVar(&autoApprove, "auto-approve", false, "Automatically approve every viewer connection. Anyone with the shareable dev URL will be trusted for the life of the session; use only in trusted environments.")
237239

238240
return cmd
239241
}

cmd/apps/init.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ func newInitCmd() *cobra.Command {
7676
deploy bool
7777
run string
7878
setValues []string
79+
autoApprove bool
7980
)
8081

8182
cmd := &cobra.Command{
@@ -160,6 +161,7 @@ Environment variables:
160161
runChanged: cmd.Flags().Changed("run"),
161162
pluginsChanged: cmd.Flags().Changed("features") || cmd.Flags().Changed("plugins"),
162163
setValues: setValues,
164+
autoApprove: autoApprove,
163165
})
164166
},
165167
}
@@ -178,6 +180,7 @@ Environment variables:
178180
_ = cmd.Flags().MarkHidden("plugins")
179181
cmd.Flags().BoolVar(&deploy, "deploy", false, "Deploy the app after creation")
180182
cmd.Flags().StringVar(&run, "run", "", "Run the app after creation (none, dev, dev-remote)")
183+
cmd.Flags().BoolVar(&autoApprove, "auto-approve", false, "Skip confirmation prompts for optional resources. Optional resources are only configured when their values are provided via --set.")
181184

182185
return cmd
183186
}
@@ -198,6 +201,7 @@ type createOptions struct {
198201
runChanged bool // true if --run flag was explicitly set
199202
pluginsChanged bool // true if --plugins flag was explicitly set
200203
setValues []string // --set plugin.resourceKey.field=value pairs
204+
autoApprove bool
201205
}
202206

203207
// parseSetValues parses --set key=value pairs into the resourceValues map.
@@ -332,7 +336,7 @@ func parseDeployAndRunFlags(deploy bool, run string) (bool, prompt.RunMode, erro
332336

333337
// promptForPluginsAndDeps prompts for plugins and their resource dependencies using the manifest.
334338
// skipDeployRunPrompt indicates whether to skip prompting for deploy/run (because flags were provided).
335-
func promptForPluginsAndDeps(ctx context.Context, m *manifest.Manifest, preSelectedPlugins []string, skipDeployRunPrompt bool) (*prompt.CreateProjectConfig, error) {
339+
func promptForPluginsAndDeps(ctx context.Context, m *manifest.Manifest, preSelectedPlugins []string, skipDeployRunPrompt, autoApprove bool) (*prompt.CreateProjectConfig, error) {
336340
config := &prompt.CreateProjectConfig{
337341
Dependencies: make(map[string]string),
338342
Features: preSelectedPlugins, // Reuse Features field for plugin names
@@ -394,14 +398,18 @@ func promptForPluginsAndDeps(ctx context.Context, m *manifest.Manifest, preSelec
394398
}
395399
}
396400

397-
// Step 3: Prompt for optional plugin resource dependencies
398-
for _, r := range optionalResources {
399-
values, err := promptForResource(ctx, r, theme, false)
400-
if err != nil {
401-
return nil, err
402-
}
403-
for k, v := range values {
404-
config.Dependencies[k] = v
401+
// Step 3: Prompt for optional plugin resource dependencies.
402+
// With --auto-approve, optional resources are skipped here; they're only
403+
// configured when their values are supplied via --set (merged later).
404+
if !autoApprove {
405+
for _, r := range optionalResources {
406+
values, err := promptForResource(ctx, r, theme, false)
407+
if err != nil {
408+
return nil, err
409+
}
410+
for k, v := range values {
411+
config.Dependencies[k] = v
412+
}
405413
}
406414
}
407415

@@ -830,7 +838,7 @@ func runCreate(ctx context.Context, opts createOptions) error {
830838

831839
if isInteractive && !opts.pluginsChanged && !flagsMode {
832840
// Interactive mode without --plugins flag: prompt for plugins, dependencies, description
833-
config, err := promptForPluginsAndDeps(ctx, m, selectedPlugins, skipDeployRunPrompt)
841+
config, err := promptForPluginsAndDeps(ctx, m, selectedPlugins, skipDeployRunPrompt, opts.autoApprove)
834842
if err != nil {
835843
return err
836844
}

experimental/ssh/cmd/connect.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ the SSH server and handling the connection proxy.
3636
var liteswap string
3737
var skipSettingsCheck bool
3838
var environmentVersion int
39+
var autoApprove bool
3940

4041
cmd.Flags().StringVar(&clusterID, "cluster", "", "Databricks cluster ID (for dedicated clusters)")
4142
cmd.Flags().DurationVar(&shutdownDelay, "shutdown-delay", defaultShutdownDelay, "Delay before shutting down the server after the last client disconnects")
@@ -71,6 +72,8 @@ the SSH server and handling the connection proxy.
7172
cmd.Flags().IntVar(&environmentVersion, "environment-version", defaultEnvironmentVersion, "Environment version for serverless compute")
7273
cmd.Flags().MarkHidden("environment-version")
7374

75+
cmd.Flags().BoolVar(&autoApprove, "auto-approve", false, "Skip confirmation prompts, installing IDE extensions and applying IDE settings without asking")
76+
7477
cmd.PreRunE = func(cmd *cobra.Command, args []string) error {
7578
// CLI in the proxy mode is executed by the ssh client and can't prompt for input
7679
if proxyMode {
@@ -110,6 +113,7 @@ the SSH server and handling the connection proxy.
110113
SkipSettingsCheck: skipSettingsCheck,
111114
EnvironmentVersion: environmentVersion,
112115
AdditionalArgs: args,
116+
AutoApprove: autoApprove,
113117
}
114118
if err := opts.Validate(); err != nil {
115119
return err

experimental/ssh/cmd/setup.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ an SSH host configuration to your SSH config file.
2828
var sshConfigPath string
2929
var shutdownDelay time.Duration
3030
var autoStartCluster bool
31+
var autoApprove bool
3132

3233
cmd.Flags().StringVar(&hostName, "name", "", "Host name to use in SSH config")
3334
cmd.MarkFlagRequired("name")
3435
cmd.Flags().StringVar(&clusterID, "cluster", "", "Databricks cluster ID")
3536
cmd.Flags().BoolVar(&autoStartCluster, "auto-start-cluster", true, "Automatically start the cluster when establishing the ssh connection")
3637
cmd.Flags().StringVar(&sshConfigPath, "ssh-config", "", "Path to SSH config file (default ~/.ssh/config)")
3738
cmd.Flags().DurationVar(&shutdownDelay, "shutdown-delay", defaultShutdownDelay, "SSH server will terminate after this delay if there are no active connections")
39+
cmd.Flags().BoolVar(&autoApprove, "auto-approve", false, "Skip confirmation prompts, recreating existing SSH host configs without asking")
3840

3941
cmd.PreRunE = func(cmd *cobra.Command, args []string) error {
4042
// We want to avoid the situation where the setup command works because it pulls the auth config from a bundle,
@@ -53,6 +55,7 @@ an SSH host configuration to your SSH config file.
5355
SSHConfigPath: sshConfigPath,
5456
ShutdownDelay: shutdownDelay,
5557
Profile: wsClient.Config.Profile,
58+
AutoApprove: autoApprove,
5659
}
5760
clientOpts := client.ClientOptions{
5861
ClusterID: setupOpts.ClusterID,

experimental/ssh/internal/client/client.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ type ClientOptions struct {
9999
SkipSettingsCheck bool
100100
// Environment version for serverless compute.
101101
EnvironmentVersion int
102+
// If true, skip confirmation prompts for IDE extension install and IDE settings updates.
103+
AutoApprove bool
102104
}
103105

104106
func (o *ClientOptions) Validate() error {
@@ -234,7 +236,7 @@ func Run(ctx context.Context, client *databricks.WorkspaceClient, opts ClientOpt
234236
if err := vscode.CheckIDECommand(opts.IDE); err != nil {
235237
return err
236238
}
237-
if err := vscode.CheckIDESSHExtension(ctx, opts.IDE); err != nil {
239+
if err := vscode.CheckIDESSHExtension(ctx, opts.IDE, opts.AutoApprove); err != nil {
238240
return err
239241
}
240242
}
@@ -243,12 +245,15 @@ func Run(ctx context.Context, client *databricks.WorkspaceClient, opts ClientOpt
243245
// desired server ports (or socket connection mode) for the connection to go through
244246
// (as the majority of the localhost ports on the remote side are blocked by iptable rules).
245247
// Plus the platform (always linux), and extensions (python and jupyter), to make the initial experience smoother.
246-
if opts.IDE != "" && opts.IsServerlessMode() && !opts.ProxyMode && !opts.SkipSettingsCheck && cmdio.IsPromptSupported(ctx) {
247-
err := vscode.CheckAndUpdateSettings(ctx, opts.IDE, opts.ConnectionName)
248+
if opts.IDE != "" && opts.IsServerlessMode() && !opts.ProxyMode && !opts.SkipSettingsCheck {
249+
err := vscode.CheckAndUpdateSettings(ctx, opts.IDE, opts.ConnectionName, opts.AutoApprove)
248250
if err != nil {
249251
cmdio.LogString(ctx, fmt.Sprintf("Failed to update IDE settings: %v", err))
250252
cmdio.LogString(ctx, vscode.GetManualInstructions(opts.IDE, opts.ConnectionName))
251253
cmdio.LogString(ctx, "Use --skip-settings-check to bypass IDE settings verification.")
254+
if opts.AutoApprove {
255+
return fmt.Errorf("aborted: IDE settings need to be updated manually: %w", err)
256+
}
252257
shouldProceed, promptErr := cmdio.AskYesOrNo(ctx, "Do you want to proceed with the connection?")
253258
if promptErr != nil {
254259
return fmt.Errorf("failed to prompt user: %w", promptErr)

experimental/ssh/internal/setup/setup.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ type SetupOptions struct {
3030
Profile string
3131
// Proxy command to use for the SSH connection
3232
ProxyCommand string
33+
// Skip confirmation prompts (e.g. recreate existing host config without asking)
34+
AutoApprove bool
3335
}
3436

3537
func validateClusterAccess(ctx context.Context, client *databricks.WorkspaceClient, clusterID string) error {
@@ -112,13 +114,18 @@ func Setup(ctx context.Context, client *databricks.WorkspaceClient, opts SetupOp
112114

113115
recreate := false
114116
if exists {
115-
recreate, err = sshconfig.PromptRecreateConfig(ctx, opts.HostName)
116-
if err != nil {
117-
return err
118-
}
119-
if !recreate {
120-
cmdio.LogString(ctx, fmt.Sprintf("Skipping setup for host '%s'", opts.HostName))
121-
return nil
117+
if opts.AutoApprove {
118+
recreate = true
119+
cmdio.LogString(ctx, fmt.Sprintf("Host '%s' already exists, recreating (--auto-approve)", opts.HostName))
120+
} else {
121+
recreate, err = sshconfig.PromptRecreateConfig(ctx, opts.HostName)
122+
if err != nil {
123+
return err
124+
}
125+
if !recreate {
126+
cmdio.LogString(ctx, fmt.Sprintf("Skipping setup for host '%s'", opts.HostName))
127+
return nil
128+
}
122129
}
123130
}
124131

experimental/ssh/internal/setup/setup_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,54 @@ func TestSetup_SuccessfulWithNewConfigFile(t *testing.T) {
256256
assert.Contains(t, hostConfigStr, "--profile=test-profile")
257257
}
258258

259+
func TestSetup_AutoApproveRecreatesExistingHost(t *testing.T) {
260+
ctx := cmdio.MockDiscard(t.Context())
261+
tmpDir := t.TempDir()
262+
t.Setenv("HOME", tmpDir)
263+
t.Setenv("USERPROFILE", tmpDir)
264+
265+
// Pre-seed an existing host config so PromptRecreateConfig would fire without --auto-approve.
266+
hostConfigDir := filepath.Join(tmpDir, ".databricks", "ssh-tunnel-configs")
267+
require.NoError(t, os.MkdirAll(hostConfigDir, 0o700))
268+
existingHostConfig := filepath.Join(hostConfigDir, "test-host")
269+
require.NoError(t, os.WriteFile(existingHostConfig, []byte("# stale\nHost test-host\n User stale\n"), 0o600))
270+
271+
configPath := filepath.Join(tmpDir, "ssh_config")
272+
273+
m := mocks.NewMockWorkspaceClient(t)
274+
clustersAPI := m.GetMockClustersAPI()
275+
clustersAPI.EXPECT().Get(ctx, compute.GetClusterRequest{ClusterId: "cluster-123"}).Return(&compute.ClusterDetails{
276+
DataSecurityMode: compute.DataSecurityModeSingleUser,
277+
}, nil)
278+
279+
opts := SetupOptions{
280+
HostName: "test-host",
281+
ClusterID: "cluster-123",
282+
SSHConfigPath: configPath,
283+
SSHKeysDir: tmpDir,
284+
ShutdownDelay: 30 * time.Second,
285+
AutoApprove: true,
286+
}
287+
288+
clientOpts := client.ClientOptions{
289+
ClusterID: opts.ClusterID,
290+
ShutdownDelay: opts.ShutdownDelay,
291+
}
292+
proxyCommand, err := clientOpts.ToProxyCommand()
293+
require.NoError(t, err)
294+
opts.ProxyCommand = proxyCommand
295+
296+
err = Setup(ctx, m.WorkspaceClient, opts)
297+
assert.NoError(t, err)
298+
299+
// Host config should be recreated (no longer contains the stale User).
300+
content, err := os.ReadFile(existingHostConfig)
301+
require.NoError(t, err)
302+
s := string(content)
303+
assert.NotContains(t, s, "User stale")
304+
assert.Contains(t, s, "--cluster=cluster-123")
305+
}
306+
259307
func TestSetup_SuccessfulWithExistingConfigFile(t *testing.T) {
260308
ctx := cmdio.MockDiscard(t.Context())
261309
tmpDir := t.TempDir()

experimental/ssh/internal/vscode/run.go

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ func isExtensionVersionAtLeast(version, minVersion string) bool {
9595

9696
// CheckIDESSHExtension verifies that the required Remote SSH extension is installed
9797
// with a compatible version, and offers to install/update it if not.
98-
func CheckIDESSHExtension(ctx context.Context, option string) error {
98+
// When autoApprove is true, the extension is installed without asking.
99+
func CheckIDESSHExtension(ctx context.Context, option string, autoApprove bool) error {
99100
ide := getIDE(option)
100101

101102
out, err := exec.CommandContext(ctx, ide.Command, "--list-extensions", "--show-versions").Output()
@@ -116,18 +117,22 @@ func CheckIDESSHExtension(ctx context.Context, option string) error {
116117
ide.SSHExtensionName, version, ide.MinSSHExtensionVersion)
117118
}
118119

119-
if !cmdio.IsPromptSupported(ctx) {
120-
return fmt.Errorf("%s Install it with: %s --install-extension %s",
121-
msg, ide.Command, ide.SSHExtensionID)
122-
}
120+
if !autoApprove {
121+
if !cmdio.IsPromptSupported(ctx) {
122+
return fmt.Errorf("%s Install it with: %s --install-extension %s, or pass --auto-approve",
123+
msg, ide.Command, ide.SSHExtensionID)
124+
}
123125

124-
shouldInstall, err := cmdio.AskYesOrNo(ctx, msg+" Would you like to install it?")
125-
if err != nil {
126-
return fmt.Errorf("failed to prompt user: %w", err)
127-
}
128-
if !shouldInstall {
129-
return fmt.Errorf("%s Install it with: %s --install-extension %s",
130-
msg, ide.Command, ide.SSHExtensionID)
126+
shouldInstall, err := cmdio.AskYesOrNo(ctx, msg+" Would you like to install it?")
127+
if err != nil {
128+
return fmt.Errorf("failed to prompt user: %w", err)
129+
}
130+
if !shouldInstall {
131+
return fmt.Errorf("%s Install it with: %s --install-extension %s",
132+
msg, ide.Command, ide.SSHExtensionID)
133+
}
134+
} else {
135+
cmdio.LogString(ctx, msg+" Installing automatically (--auto-approve).")
131136
}
132137

133138
cmdio.LogString(ctx, fmt.Sprintf("Installing %q...", ide.SSHExtensionName))

experimental/ssh/internal/vscode/run_test.go

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ func TestCheckIDESSHExtension_UpToDate(t *testing.T) {
240240
extensionOutput := "ms-python.python@2024.1.1\nms-vscode-remote.remote-ssh@0.123.0\n"
241241
createFakeIDEExecutable(t, tmpDir, "code", extensionOutput)
242242

243-
err := CheckIDESSHExtension(ctx, VSCodeOption)
243+
err := CheckIDESSHExtension(ctx, VSCodeOption, false)
244244
assert.NoError(t, err)
245245
}
246246

@@ -252,7 +252,7 @@ func TestCheckIDESSHExtension_ExactMinVersion(t *testing.T) {
252252
extensionOutput := "ms-vscode-remote.remote-ssh@0.120.0\n"
253253
createFakeIDEExecutable(t, tmpDir, "code", extensionOutput)
254254

255-
err := CheckIDESSHExtension(ctx, VSCodeOption)
255+
err := CheckIDESSHExtension(ctx, VSCodeOption, false)
256256
assert.NoError(t, err)
257257
}
258258

@@ -264,7 +264,7 @@ func TestCheckIDESSHExtension_Missing(t *testing.T) {
264264
extensionOutput := "ms-python.python@2024.1.1\n"
265265
createFakeIDEExecutable(t, tmpDir, "code", extensionOutput)
266266

267-
err := CheckIDESSHExtension(ctx, VSCodeOption)
267+
err := CheckIDESSHExtension(ctx, VSCodeOption, false)
268268
require.Error(t, err)
269269
assert.Contains(t, err.Error(), `"Remote - SSH"`)
270270
assert.Contains(t, err.Error(), "not installed")
@@ -278,7 +278,7 @@ func TestCheckIDESSHExtension_Outdated(t *testing.T) {
278278
extensionOutput := "ms-vscode-remote.remote-ssh@0.100.0\n"
279279
createFakeIDEExecutable(t, tmpDir, "code", extensionOutput)
280280

281-
err := CheckIDESSHExtension(ctx, VSCodeOption)
281+
err := CheckIDESSHExtension(ctx, VSCodeOption, false)
282282
require.Error(t, err)
283283
assert.Contains(t, err.Error(), "0.100.0")
284284
assert.Contains(t, err.Error(), ">= 0.120.0")
@@ -292,6 +292,31 @@ func TestCheckIDESSHExtension_Cursor(t *testing.T) {
292292
extensionOutput := "anysphere.remote-ssh@1.0.32\n"
293293
createFakeIDEExecutable(t, tmpDir, "cursor", extensionOutput)
294294

295-
err := CheckIDESSHExtension(ctx, CursorOption)
295+
err := CheckIDESSHExtension(ctx, CursorOption, false)
296296
assert.NoError(t, err)
297297
}
298+
299+
func TestCheckIDESSHExtension_AutoApproveMissing_Installs(t *testing.T) {
300+
tmpDir := t.TempDir()
301+
t.Setenv("PATH", tmpDir)
302+
ctx, _ := cmdio.NewTestContextWithStdout(t.Context())
303+
304+
// Fake `code` returns no extensions for --list-extensions, but succeeds for --install-extension.
305+
createFakeIDEExecutable(t, tmpDir, "code", "")
306+
307+
err := CheckIDESSHExtension(ctx, VSCodeOption, true)
308+
assert.NoError(t, err)
309+
}
310+
311+
func TestCheckIDESSHExtension_NoPrompt_WithoutAutoApprove_Errors(t *testing.T) {
312+
tmpDir := t.TempDir()
313+
t.Setenv("PATH", tmpDir)
314+
ctx, _ := cmdio.NewTestContextWithStdout(t.Context())
315+
316+
extensionOutput := "ms-python.python@2024.1.1\n"
317+
createFakeIDEExecutable(t, tmpDir, "code", extensionOutput)
318+
319+
err := CheckIDESSHExtension(ctx, VSCodeOption, false)
320+
require.Error(t, err)
321+
assert.Contains(t, err.Error(), "--install-extension")
322+
}

0 commit comments

Comments
 (0)