Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit 2136d8d

Browse files
authored
chore: use access url from env resource pool (#216)
1 parent cf6e030 commit 2136d8d

File tree

10 files changed

+136
-67
lines changed

10 files changed

+136
-67
lines changed

coder-sdk/env.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package coder
33
import (
44
"context"
55
"net/http"
6+
"net/url"
67
"time"
78

89
"cdr.dev/wsep"
@@ -32,7 +33,6 @@ type Environment struct {
3233
LastOpenedAt time.Time `json:"last_opened_at" table:"-"`
3334
LastConnectionAt time.Time `json:"last_connection_at" table:"-"`
3435
AutoOffThreshold Duration `json:"auto_off_threshold" table:"-"`
35-
SSHAvailable bool `json:"ssh_available" table:"-"`
3636
UseContainerVM bool `json:"use_container_vm" table:"CVM"`
3737
ResourcePoolID string `json:"resource_pool_id" table:"-"`
3838
}
@@ -147,22 +147,22 @@ func (c Client) EditEnvironment(ctx context.Context, envID string, req UpdateEnv
147147

148148
// DialWsep dials an environments command execution interface
149149
// See https://github.com/cdr/wsep for details.
150-
func (c Client) DialWsep(ctx context.Context, envID string) (*websocket.Conn, error) {
151-
return c.dialWebsocket(ctx, "/proxy/environments/"+envID+"/wsep")
150+
func (c Client) DialWsep(ctx context.Context, baseURL *url.URL, envID string) (*websocket.Conn, error) {
151+
return c.dialWebsocket(ctx, "/proxy/environments/"+envID+"/wsep", withBaseURL(baseURL))
152152
}
153153

154154
// DialExecutor gives a remote execution interface for performing commands inside an environment.
155-
func (c Client) DialExecutor(ctx context.Context, envID string) (wsep.Execer, error) {
156-
ws, err := c.DialWsep(ctx, envID)
155+
func (c Client) DialExecutor(ctx context.Context, baseURL *url.URL, envID string) (wsep.Execer, error) {
156+
ws, err := c.DialWsep(ctx, baseURL, envID)
157157
if err != nil {
158158
return nil, err
159159
}
160160
return wsep.RemoteExecer(ws), nil
161161
}
162162

163163
// DialIDEStatus opens a websocket connection for cpu load metrics on the environment.
164-
func (c Client) DialIDEStatus(ctx context.Context, envID string) (*websocket.Conn, error) {
165-
return c.dialWebsocket(ctx, "/proxy/environments/"+envID+"/ide/api/status")
164+
func (c Client) DialIDEStatus(ctx context.Context, baseURL *url.URL, envID string) (*websocket.Conn, error) {
165+
return c.dialWebsocket(ctx, "/proxy/environments/"+envID+"/ide/api/status", withBaseURL(baseURL))
166166
}
167167

168168
// DialEnvironmentBuildLog opens a websocket connection for the environment build log messages.

coder-sdk/request.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,21 @@ import (
1212
)
1313

1414
// request is a helper to set the cookie, marshal the payload and execute the request.
15-
func (c Client) request(ctx context.Context, method, path string, in interface{}) (*http.Response, error) {
15+
func (c Client) request(ctx context.Context, method, path string, in interface{}, options ...requestOption) (*http.Response, error) {
1616
// Create a default http client with the auth in the cookie.
1717
client, err := c.newHTTPClient()
1818
if err != nil {
1919
return nil, xerrors.Errorf("new http client: %w", err)
2020
}
21+
url := *c.BaseURL
22+
23+
var config requestOptions
24+
for _, o := range options {
25+
o(&config)
26+
}
27+
if config.BaseURLOverride != nil {
28+
url = *config.BaseURLOverride
29+
}
2130

2231
// If we have incoming data, encode it as json.
2332
var payload io.Reader
@@ -30,7 +39,7 @@ func (c Client) request(ctx context.Context, method, path string, in interface{}
3039
}
3140

3241
// Create the http request.
33-
req, err := http.NewRequestWithContext(ctx, method, c.BaseURL.String()+path, payload)
42+
req, err := http.NewRequestWithContext(ctx, method, url.String()+path, payload)
3443
if err != nil {
3544
return nil, xerrors.Errorf("create request: %w", err)
3645
}

coder-sdk/resourcepools.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ type ResourcePool struct {
1717
DevurlHost string `json:"devurl_host"`
1818
NamespaceWhitelist []string `json:"namespace_whitelist"`
1919
OrgWhitelist []string `json:"org_whitelist"`
20+
SSHEnabled bool `json:"ssh_enabled"`
21+
AccessURL string `json:"envproxy_access_url"`
2022
}
2123

2224
// ResourcePoolByID fetches a resource pool entity by its unique ID.

coder-sdk/ws.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,35 @@ package coder
33
import (
44
"context"
55
"net/http"
6+
"net/url"
67

78
"nhooyr.io/websocket"
89
)
910

11+
type requestOptions struct {
12+
BaseURLOverride *url.URL
13+
}
14+
15+
type requestOption func(*requestOptions)
16+
17+
func withBaseURL(base *url.URL) func(o *requestOptions) {
18+
return func(o *requestOptions) {
19+
o.BaseURLOverride = base
20+
}
21+
}
22+
1023
// dialWebsocket establish the websocket connection while setting the authentication header.
11-
func (c Client) dialWebsocket(ctx context.Context, path string) (*websocket.Conn, error) {
24+
func (c Client) dialWebsocket(ctx context.Context, path string, options ...requestOption) (*websocket.Conn, error) {
1225
// Make a copy of the url so we can update the scheme to ws(s) without mutating the state.
1326
url := *c.BaseURL
27+
var config requestOptions
28+
for _, o := range options {
29+
o(&config)
30+
}
31+
if config.BaseURLOverride != nil {
32+
url = *config.BaseURLOverride
33+
}
34+
1435
if url.Scheme == "https" {
1536
url.Scheme = "wss"
1637
} else {

internal/cmd/configssh.go

Lines changed: 23 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,16 @@ import (
44
"context"
55
"fmt"
66
"io/ioutil"
7-
"net"
87
"net/url"
98
"os"
109
"os/user"
1110
"path/filepath"
1211
"strings"
13-
"time"
1412

1513
"cdr.dev/coder-cli/pkg/clog"
1614

1715
"cdr.dev/coder-cli/coder-sdk"
16+
"cdr.dev/coder-cli/internal/coderutil"
1817
"cdr.dev/coder-cli/internal/config"
1918
"github.com/spf13/cobra"
2019
"golang.org/x/xerrors"
@@ -103,20 +102,17 @@ func configSSH(configpath *string, remove *bool) func(cmd *cobra.Command, _ []st
103102
return xerrors.New("no environments found")
104103
}
105104

106-
if !sshAvailable(envs) {
107-
return xerrors.New("SSH is disabled or not available for any environments in your Coder Enterprise deployment.")
108-
}
109-
110-
err = canConnectSSH(ctx)
105+
envsWithPools, err := coderutil.EnvsWithPool(ctx, client, envs)
111106
if err != nil {
112-
return xerrors.Errorf("check if SSH is available: unable to connect to SSH endpoint: %w", err)
107+
return xerrors.Errorf("resolve env pools: %w", err)
113108
}
114109

115-
newConfig, err := makeNewConfigs(user.Username, envs, privateKeyFilepath)
116-
if err != nil {
117-
return xerrors.Errorf("make new ssh configurations: %w", err)
110+
if !sshAvailable(envsWithPools) {
111+
return xerrors.New("SSH is disabled or not available for any environments in your Coder Enterprise deployment.")
118112
}
119113

114+
newConfig := makeNewConfigs(user.Username, envsWithPools, privateKeyFilepath)
115+
120116
err = os.MkdirAll(filepath.Dir(*configpath), os.ModePerm)
121117
if err != nil {
122118
return xerrors.Errorf("make configuration directory: %w", err)
@@ -159,42 +155,15 @@ func removeOldConfig(config string) (string, bool) {
159155
}
160156

161157
// sshAvailable returns true if SSH is available for at least one environment.
162-
func sshAvailable(envs []coder.Environment) bool {
158+
func sshAvailable(envs []coderutil.EnvWithPool) bool {
163159
for _, env := range envs {
164-
if env.SSHAvailable {
160+
if env.Pool.SSHEnabled {
165161
return true
166162
}
167163
}
168-
169164
return false
170165
}
171166

172-
// canConnectSSH returns an error if we cannot dial the SSH port.
173-
func canConnectSSH(ctx context.Context) error {
174-
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
175-
defer cancel()
176-
177-
host, err := configuredHostname()
178-
if err != nil {
179-
return xerrors.Errorf("get configured manager hostname: %w", err)
180-
}
181-
182-
var (
183-
dialer net.Dialer
184-
hostPort = net.JoinHostPort(host, "22")
185-
)
186-
conn, err := dialer.DialContext(ctx, "tcp", hostPort)
187-
if err != nil {
188-
if err == context.DeadlineExceeded {
189-
err = xerrors.New("timed out after 3 seconds")
190-
}
191-
return xerrors.Errorf("dial tcp://%v: %w", hostPort, err)
192-
}
193-
conn.Close()
194-
195-
return nil
196-
}
197-
198167
func writeSSHKey(ctx context.Context, client *coder.Client, privateKeyPath string) error {
199168
key, err := client.SSHKey(ctx)
200169
if err != nil {
@@ -203,23 +172,26 @@ func writeSSHKey(ctx context.Context, client *coder.Client, privateKeyPath strin
203172
return ioutil.WriteFile(privateKeyPath, []byte(key.PrivateKey), 0400)
204173
}
205174

206-
func makeNewConfigs(userName string, envs []coder.Environment, privateKeyFilepath string) (string, error) {
207-
hostname, err := configuredHostname()
208-
if err != nil {
209-
return "", err
210-
}
211-
175+
func makeNewConfigs(userName string, envs []coderutil.EnvWithPool, privateKeyFilepath string) string {
212176
newConfig := fmt.Sprintf("\n%s\n%s\n\n", sshStartToken, sshStartMessage)
213177
for _, env := range envs {
214-
if !env.SSHAvailable {
178+
if !env.Pool.SSHEnabled {
179+
clog.LogWarn(fmt.Sprintf("SSH is not enabled for pool %q", env.Pool.Name),
180+
clog.BlankLine,
181+
clog.Tipf("ask an infrastructure administrator to enable SSH for this resource pool"),
182+
)
215183
continue
216184
}
217-
218-
newConfig += makeSSHConfig(hostname, userName, env.Name, privateKeyFilepath)
185+
u, err := url.Parse(env.Pool.AccessURL)
186+
if err != nil {
187+
clog.LogWarn("invalid access url", clog.Causef("malformed url: %q", env.Pool.AccessURL))
188+
continue
189+
}
190+
newConfig += makeSSHConfig(u.Host, userName, env.Env.Name, privateKeyFilepath)
219191
}
220192
newConfig += fmt.Sprintf("\n%s\n", sshEndToken)
221193

222-
return newConfig, nil
194+
return newConfig
223195
}
224196

225197
func makeSSHConfig(host, userName, envName, privateKeyFilepath string) string {
@@ -235,6 +207,7 @@ func makeSSHConfig(host, userName, envName, privateKeyFilepath string) string {
235207
`, envName, host, userName, envName, privateKeyFilepath)
236208
}
237209

210+
//nolint:deadcode,unused
238211
func configuredHostname() (string, error) {
239212
u, err := config.URL.Read()
240213
if err != nil {

internal/cmd/shell.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616

1717
"cdr.dev/coder-cli/coder-sdk"
1818
"cdr.dev/coder-cli/internal/activity"
19+
"cdr.dev/coder-cli/internal/coderutil"
1920
"cdr.dev/coder-cli/internal/x/xterminal"
2021
"cdr.dev/coder-cli/pkg/clog"
2122
"cdr.dev/wsep"
@@ -161,9 +162,9 @@ func runCommand(ctx context.Context, envName, command string, args []string) err
161162
ctx, cancel := context.WithCancel(ctx)
162163
defer cancel()
163164

164-
conn, err := client.DialWsep(ctx, env.ID)
165+
conn, err := coderutil.DialEnvWsep(ctx, client, env)
165166
if err != nil {
166-
return xerrors.Errorf("dial websocket: %w", err)
167+
return xerrors.Errorf("dial executor: %w", err)
167168
}
168169
go heartbeat(ctx, conn, 15*time.Second)
169170

internal/coderutil/doc.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Package coderutil providers utilities for high-level operations on coder-sdk entities.
2+
package coderutil

internal/coderutil/env.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package coderutil
2+
3+
import (
4+
"context"
5+
"net/url"
6+
7+
"cdr.dev/coder-cli/coder-sdk"
8+
"golang.org/x/xerrors"
9+
"nhooyr.io/websocket"
10+
)
11+
12+
// DialEnvWsep dials the executor endpoint using the https://github.com/cdr/wsep message protocol.
13+
// The proper resource pool access URL is used.
14+
func DialEnvWsep(ctx context.Context, client *coder.Client, env *coder.Environment) (*websocket.Conn, error) {
15+
resourcePool, err := client.ResourcePoolByID(ctx, env.ResourcePoolID)
16+
if err != nil {
17+
return nil, xerrors.Errorf("get env resource pool: %w", err)
18+
}
19+
accessURL, err := url.Parse(resourcePool.AccessURL)
20+
if err != nil {
21+
return nil, xerrors.Errorf("invalid resource pool access url: %w", err)
22+
}
23+
24+
conn, err := client.DialWsep(ctx, accessURL, env.ID)
25+
if err != nil {
26+
return nil, xerrors.Errorf("dial websocket: %w", err)
27+
}
28+
return conn, nil
29+
}
30+
31+
// EnvWithPool composes an Environment entity with its associated ResourcePool.
32+
type EnvWithPool struct {
33+
Env coder.Environment
34+
Pool coder.ResourcePool
35+
}
36+
37+
// EnvsWithPool performs the composition of each Environment with its associated ResourcePool.
38+
func EnvsWithPool(ctx context.Context, client *coder.Client, envs []coder.Environment) ([]EnvWithPool, error) {
39+
pooledEnvs := make([]EnvWithPool, len(envs))
40+
pools, err := client.ResourcePools(ctx)
41+
if err != nil {
42+
return nil, err
43+
}
44+
poolMap := make(map[string]coder.ResourcePool, len(pools))
45+
for _, p := range pools {
46+
poolMap[p.ID] = p
47+
}
48+
for _, e := range envs {
49+
envPool, ok := poolMap[e.ResourcePoolID]
50+
if !ok {
51+
return nil, xerrors.Errorf("fetch env resource pool: %w", coder.ErrNotFound)
52+
}
53+
pooledEnvs = append(pooledEnvs, EnvWithPool{
54+
Env: e,
55+
Pool: envPool,
56+
})
57+
}
58+
return pooledEnvs, nil
59+
}

internal/sync/singlefile.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@ import (
1010
"strings"
1111

1212
"cdr.dev/coder-cli/coder-sdk"
13+
"cdr.dev/coder-cli/internal/coderutil"
1314
"cdr.dev/wsep"
1415
"golang.org/x/xerrors"
1516
"nhooyr.io/websocket"
1617
)
1718

1819
// SingleFile copies the given file into the remote dir or remote path of the given coder.Environment.
1920
func SingleFile(ctx context.Context, local, remoteDir string, env *coder.Environment, client *coder.Client) error {
20-
conn, err := client.DialWsep(ctx, env.ID)
21+
conn, err := coderutil.DialEnvWsep(ctx, client, env)
2122
if err != nil {
2223
return xerrors.Errorf("dial remote execer: %w", err)
2324
}

internal/sync/sync.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424

2525
"cdr.dev/coder-cli/coder-sdk"
2626
"cdr.dev/coder-cli/internal/activity"
27+
"cdr.dev/coder-cli/internal/coderutil"
2728
"cdr.dev/coder-cli/pkg/clog"
2829
"cdr.dev/wsep"
2930
)
@@ -89,9 +90,9 @@ func (s Sync) syncPaths(delete bool, local, remote string) error {
8990
}
9091

9192
func (s Sync) remoteCmd(ctx context.Context, prog string, args ...string) error {
92-
conn, err := s.Client.DialWsep(ctx, s.Env.ID)
93+
conn, err := coderutil.DialEnvWsep(ctx, s.Client, &s.Env)
9394
if err != nil {
94-
return xerrors.Errorf("dial websocket: %w", err)
95+
return xerrors.Errorf("dial executor: %w", err)
9596
}
9697
defer func() { _ = conn.Close(websocket.CloseNormalClosure, "") }() // Best effort.
9798

@@ -270,9 +271,9 @@ func (s Sync) Version() (string, error) {
270271
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
271272
defer cancel()
272273

273-
conn, err := s.Client.DialWsep(ctx, s.Env.ID)
274+
conn, err := coderutil.DialEnvWsep(ctx, s.Client, &s.Env)
274275
if err != nil {
275-
return "", err
276+
return "", xerrors.Errorf("dial env executor: %w", err)
276277
}
277278
defer func() { _ = conn.Close(websocket.CloseNormalClosure, "") }() // Best effort.
278279

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy