From cefe67639f696ce5a9c52ea56bbd3b03f46269dc Mon Sep 17 00:00:00 2001 From: tmonty12 Date: Tue, 15 Oct 2024 17:22:38 -0700 Subject: [PATCH 1/6] add cloudflare proxy download --- pkg/cmd/refresh/refresh.go | 15 ++++++ pkg/entity/entity.go | 54 +++++++++++---------- pkg/files/files.go | 6 +++ pkg/ssh/sshconfigurer.go | 89 ++++++++++++++++++++++++++++------- pkg/ssh/sshconfigurer_test.go | 62 ++++++++++++++++++++++-- pkg/store/cloudflare.go | 71 ++++++++++++++++++++++++++++ pkg/store/cloudflare_test.go | 43 +++++++++++++++++ pkg/store/ssh.go | 9 ++++ 8 files changed, 302 insertions(+), 47 deletions(-) create mode 100644 pkg/store/cloudflare.go create mode 100644 pkg/store/cloudflare_test.go diff --git a/pkg/cmd/refresh/refresh.go b/pkg/cmd/refresh/refresh.go index 0612ffe7..d4503933 100644 --- a/pkg/cmd/refresh/refresh.go +++ b/pkg/cmd/refresh/refresh.go @@ -3,12 +3,14 @@ package refresh import ( "fmt" + "io/fs" "sync" "github.com/brevdev/brev-cli/pkg/cmdcontext" "github.com/brevdev/brev-cli/pkg/entity" breverrors "github.com/brevdev/brev-cli/pkg/errors" "github.com/brevdev/brev-cli/pkg/ssh" + "github.com/brevdev/brev-cli/pkg/store" "github.com/brevdev/brev-cli/pkg/terminal" "github.com/spf13/cobra" @@ -19,6 +21,8 @@ type RefreshStore interface { ssh.SSHConfigurerV2Store GetCurrentUser() (*entity.User, error) GetCurrentUserKeys() (*entity.UserKeys, error) + Chmod(string, fs.FileMode) error + GetBrevCloudflaredBinaryPath() (string, error) } func NewCmdRefresh(t *terminal.Terminal, store RefreshStore) *cobra.Command { @@ -51,6 +55,12 @@ func NewCmdRefresh(t *terminal.Terminal, store RefreshStore) *cobra.Command { } func RunRefresh(store RefreshStore) error { + cl := GetCloudflare(store) + err := cl.DownloadCloudflaredBinaryIfItDNE() + if err != nil { + return breverrors.WrapAndTrace(err) + } + cu, err := GetConfigUpdater(store) if err != nil { return breverrors.WrapAndTrace(err) @@ -105,3 +115,8 @@ func GetConfigUpdater(store RefreshStore) (*ssh.ConfigUpdater, error) { return cu, nil } + +func GetCloudflare(refreshStore RefreshStore) store.Cloudflare { + cl := store.NewCloudflare(refreshStore) + return cl +} diff --git a/pkg/entity/entity.go b/pkg/entity/entity.go index 1e27d5ce..d4a5f302 100644 --- a/pkg/entity/entity.go +++ b/pkg/entity/entity.go @@ -284,32 +284,34 @@ type WorkspaceGroup struct { } // @Name WorkspaceGroup type Workspace struct { - ID string `json:"id"` - Name string `json:"name"` - WorkspaceGroupID string `json:"workspaceGroupId"` - OrganizationID string `json:"organizationId"` - WorkspaceClassID string `json:"workspaceClassId"` // WorkspaceClassID is resources, like "2x8" - InstanceType string `json:"instanceType,omitempty"` - CreatedByUserID string `json:"createdByUserId"` - DNS string `json:"dns"` - Status string `json:"status"` - Password string `json:"password"` - GitRepo string `json:"gitRepo"` - Version string `json:"version"` - WorkspaceTemplate WorkspaceTemplate `json:"workspaceTemplate"` - NetworkID string `json:"networkId"` - StartupScriptPath string `json:"startupScriptPath"` - ReposV0 ReposV0 `json:"repos"` - ExecsV0 ExecsV0 `json:"execs"` - ReposV1 *ReposV1 `json:"reposV1"` - ExecsV1 *ExecsV1 `json:"execsV1"` - IDEConfig IDEConfig `json:"ideConfig"` - SSHPort int `json:"sshPort"` - SSHUser string `json:"sshUser"` - HostSSHPort int `json:"hostSshPort"` - HostSSHUser string `json:"hostSshUser"` - VerbBuildStatus VerbBuildStatus `json:"verbBuildStatus"` - VerbYaml string `json:"verbYaml"` + ID string `json:"id"` + Name string `json:"name"` + WorkspaceGroupID string `json:"workspaceGroupId"` + OrganizationID string `json:"organizationId"` + WorkspaceClassID string `json:"workspaceClassId"` // WorkspaceClassID is resources, like "2x8" + InstanceType string `json:"instanceType,omitempty"` + CreatedByUserID string `json:"createdByUserId"` + DNS string `json:"dns"` + Status string `json:"status"` + Password string `json:"password"` + GitRepo string `json:"gitRepo"` + Version string `json:"version"` + WorkspaceTemplate WorkspaceTemplate `json:"workspaceTemplate"` + NetworkID string `json:"networkId"` + StartupScriptPath string `json:"startupScriptPath"` + ReposV0 ReposV0 `json:"repos"` + ExecsV0 ExecsV0 `json:"execs"` + ReposV1 *ReposV1 `json:"reposV1"` + ExecsV1 *ExecsV1 `json:"execsV1"` + IDEConfig IDEConfig `json:"ideConfig"` + SSHPort int `json:"sshPort"` + SSHUser string `json:"sshUser"` + SSHProxyHostname string `json:"sshProxyHostname"` + HostSSHPort int `json:"hostSshPort"` + HostSSHUser string `json:"hostSshUser"` + HostSSHProxyHostname string `json:"hostSshProxyHostname"` + VerbBuildStatus VerbBuildStatus `json:"verbBuildStatus"` + VerbYaml string `json:"verbYaml"` // PrimaryApplicationId string `json:"primaryApplicationId,omitempty"` // LastOnlineAt string `json:"lastOnlineAt,omitempty"` // CreatedAt string `json:"createdAt,omitempty"` diff --git a/pkg/files/files.go b/pkg/files/files.go index b3edcbd1..81ac7cc1 100644 --- a/pkg/files/files.go +++ b/pkg/files/files.go @@ -114,6 +114,12 @@ func GetBrevSSHConfigPath(home string) string { return brevSSHConfigPath } +func GetBrevCloudflaredBinaryPath(home string) string { + path := GetBrevHome(home) + brevCloudflaredBinaryPath := filepath.Join(path, "cloudflared") + return brevCloudflaredBinaryPath +} + func GetOnboardingStepPath(home string) string { path := GetBrevHome(home) brevOnboardingFilePath := filepath.Join(path, "onboarding_step.json") diff --git a/pkg/ssh/sshconfigurer.go b/pkg/ssh/sshconfigurer.go index d4763f64..73374d0a 100644 --- a/pkg/ssh/sshconfigurer.go +++ b/pkg/ssh/sshconfigurer.go @@ -110,6 +110,7 @@ type SSHConfigurerV2Store interface { GetWSLHostBrevSSHConfigPath() (string, error) GetWSLUserSSHConfig() (string, error) WriteWSLUserSSHConfig(config string) error + GetBrevCloudflaredBinaryPath() (string, error) } var _ Config = SSHConfigurerV2{} @@ -170,7 +171,12 @@ func (s SSHConfigurerV2) CreateWSLConfig(workspaces []entity.Workspace) (string, pkpath := files.GetSSHPrivateKeyPath(homedir) - sshConfig, err := makeNewSSHConfig(toWindowsPath(configPath), workspaces, toWindowsPath(pkpath)) + cloudflaredBinaryPath, err := s.store.GetBrevCloudflaredBinaryPath() + if err != nil { + return "", breverrors.WrapAndTrace(err) + } + + sshConfig, err := makeNewSSHConfig(toWindowsPath(configPath), workspaces, toWindowsPath(pkpath), toWindowsPath(cloudflaredBinaryPath)) if err != nil { return "", breverrors.WrapAndTrace(err) } @@ -188,18 +194,23 @@ func (s SSHConfigurerV2) CreateNewSSHConfig(workspaces []entity.Workspace) (stri return "", breverrors.WrapAndTrace(err) } - sshConfig, err := makeNewSSHConfig(configPath, workspaces, pkPath) + cloudflaredBinaryPath, err := s.store.GetBrevCloudflaredBinaryPath() + if err != nil { + return "", breverrors.WrapAndTrace(err) + } + + sshConfig, err := makeNewSSHConfig(configPath, workspaces, pkPath, cloudflaredBinaryPath) if err != nil { return "", breverrors.WrapAndTrace(err) } return sshConfig, nil } -func makeNewSSHConfig(configPath string, workspaces []entity.Workspace, pkpath string) (string, error) { +func makeNewSSHConfig(configPath string, workspaces []entity.Workspace, pkpath string, cloudflaredBinaryPath string) (string, error) { sshConfig := fmt.Sprintf("# included in %s\n", configPath) for _, w := range workspaces { - entry, err := makeSSHConfigEntryV2(w, pkpath) + entry, err := makeSSHConfigEntryV2(w, pkpath, cloudflaredBinaryPath) if err != nil { return "", breverrors.WrapAndTrace(err) } @@ -270,7 +281,7 @@ func tmplAndValToString(tmpl *template.Template, val interface{}) (string, error return buf.String(), nil } -func makeSSHConfigEntryV2(workspace entity.Workspace, privateKeyPath string) (string, error) { +func makeSSHConfigEntryV2(workspace entity.Workspace, privateKeyPath string, cloudflaredBinaryPath string) (string, error) { //nolint:funlen // ok alias := string(workspace.GetLocalIdentifier()) privateKeyPath = "\"" + privateKeyPath + "\"" if workspace.IsLegacy() { @@ -291,10 +302,13 @@ func makeSSHConfigEntryV2(workspace entity.Workspace, privateKeyPath string) (st return "", breverrors.WrapAndTrace(err) } return val, nil - } else { - hostname := workspace.GetHostname() + } + + var sshVal string + user := workspace.GetSSHUser() + hostname := workspace.GetHostname() + if workspace.SSHProxyHostname == "" { port := workspace.GetSSHPort() - user := workspace.GetSSHUser() entry := SSHConfigEntryV2{ Alias: alias, IdentityFile: privateKeyPath, @@ -307,15 +321,35 @@ func makeSSHConfigEntryV2(workspace entity.Workspace, privateKeyPath string) (st if err != nil { return "", breverrors.WrapAndTrace(err) } - sshVal, err := tmplAndValToString(tmpl, entry) + sshVal, err = tmplAndValToString(tmpl, entry) + if err != nil { + return "", breverrors.WrapAndTrace(err) + } + } else { + proxyCommand := makeCloudflareSSHProxyCommand(cloudflaredBinaryPath, workspace.SSHProxyHostname) + entry := SSHConfigEntryV2{ + Alias: alias, + IdentityFile: privateKeyPath, + User: user, + ProxyCommand: proxyCommand, + Dir: workspace.GetProjectFolderPath(), + } + tmpl, err := template.New(alias).Parse(SSHConfigEntryTemplateV2) + if err != nil { + return "", breverrors.WrapAndTrace(err) + } + sshVal, err = tmplAndValToString(tmpl, entry) if err != nil { return "", breverrors.WrapAndTrace(err) } + } - port = workspace.GetHostSSHPort() + alias = fmt.Sprintf("%s-host", alias) + var hostSSHVal string + if workspace.HostSSHProxyHostname == "" { + port := workspace.GetHostSSHPort() user = workspace.GetHostSSHUser() - alias = fmt.Sprintf("%s-host", alias) - entry = SSHConfigEntryV2{ + entry := SSHConfigEntryV2{ Alias: alias, IdentityFile: privateKeyPath, User: user, @@ -323,18 +357,35 @@ func makeSSHConfigEntryV2(workspace entity.Workspace, privateKeyPath string) (st HostName: hostname, Port: port, } - tmpl, err = template.New(alias).Parse(SSHConfigEntryTemplateV3) + tmpl, err := template.New(alias).Parse(SSHConfigEntryTemplateV3) if err != nil { return "", breverrors.WrapAndTrace(err) } - hostSSHVal, err := tmplAndValToString(tmpl, entry) + hostSSHVal, err = tmplAndValToString(tmpl, entry) + if err != nil { + return "", breverrors.WrapAndTrace(err) + } + } else { + proxyCommand := makeCloudflareSSHProxyCommand(cloudflaredBinaryPath, workspace.HostSSHProxyHostname) + entry := SSHConfigEntryV2{ + Alias: alias, + IdentityFile: privateKeyPath, + User: user, + ProxyCommand: proxyCommand, + Dir: workspace.GetProjectFolderPath(), + } + tmpl, err := template.New(alias).Parse(SSHConfigEntryTemplateV2) + if err != nil { + return "", breverrors.WrapAndTrace(err) + } + hostSSHVal, err = tmplAndValToString(tmpl, entry) if err != nil { return "", breverrors.WrapAndTrace(err) } - - val := fmt.Sprintf("%s%s", sshVal, hostSSHVal) - return val, nil } + + val := fmt.Sprintf("%s%s", sshVal, hostSSHVal) + return val, nil } // func makeSSHConfigEntryV2(workspace entity.Workspace, privateKeyPath string) (string, error) { @@ -392,6 +443,10 @@ func makeSSHConfigEntryV2(workspace entity.Workspace, privateKeyPath string) (st // } // } +func makeCloudflareSSHProxyCommand(cloudflaredBinaryPath string, hostname string) string { + return fmt.Sprintf("%s access ssh --hostname %s", cloudflaredBinaryPath, hostname) +} + func makeProxyCommand(workspaceID string) string { huproxyExec := "brev proxy" return fmt.Sprintf("%s %s", huproxyExec, workspaceID) diff --git a/pkg/ssh/sshconfigurer_test.go b/pkg/ssh/sshconfigurer_test.go index ce4c4208..67d11b06 100644 --- a/pkg/ssh/sshconfigurer_test.go +++ b/pkg/ssh/sshconfigurer_test.go @@ -125,6 +125,10 @@ func (d DummySSHConfigurerV2Store) WriteWSLUserSSHConfig(_ string) error { return nil } +func (d DummySSHConfigurerV2Store) GetBrevCloudflaredBinaryPath() (string, error) { + return "", nil +} + func TestCreateNewSSHConfig(t *testing.T) { c := NewSSHConfigurerV2(DummySSHConfigurerV2Store{}) cStr, err := c.CreateNewSSHConfig(somePlainWorkspaces) @@ -262,9 +266,10 @@ blaksdf;asdf; func Test_makeSSHConfigEntryV2(t *testing.T) { //nolint:funlen // test type args struct { - workspace entity.Workspace - privateKeyPath string - runRemoteCMD bool + workspace entity.Workspace + privateKeyPath string + cloudflaredBinaryPath string + runRemoteCMD bool } tests := []struct { name string @@ -486,12 +491,61 @@ Host testName2-host ForwardAgent yes RequestTTY yes +`, + }, + { + name: "test default ssh proxy", + args: args{ + workspace: entity.Workspace{ + ID: "test-id-2", + Name: "testName2", + WorkspaceGroupID: "test-id-2", + OrganizationID: "oi", + WorkspaceClassID: "wci", + CreatedByUserID: "cui", + DNS: "test2-dns-org.brev.sh", + Status: entity.Running, + Password: "sdfal", + GitRepo: "gitrepo", + SSHProxyHostname: "test-verb-proxy.com", + HostSSHProxyHostname: "test-host-proxy.com", + }, + privateKeyPath: "/my/priv/key.pem", + cloudflaredBinaryPath: "/Users/tmontfort/.brev/cloudflared", + runRemoteCMD: true, + }, + want: `Host testName2 + IdentityFile "/my/priv/key.pem" + User ubuntu + ProxyCommand /Users/tmontfort/.brev/cloudflared access ssh --hostname test-verb-proxy.com + ServerAliveInterval 30 + UserKnownHostsFile /dev/null + IdentitiesOnly yes + StrictHostKeyChecking no + PasswordAuthentication no + AddKeysToAgent yes + ForwardAgent yes + RequestTTY yes + +Host testName2-host + IdentityFile "/my/priv/key.pem" + User ubuntu + ProxyCommand /Users/tmontfort/.brev/cloudflared access ssh --hostname test-host-proxy.com + ServerAliveInterval 30 + UserKnownHostsFile /dev/null + IdentitiesOnly yes + StrictHostKeyChecking no + PasswordAuthentication no + AddKeysToAgent yes + ForwardAgent yes + RequestTTY yes + `, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := makeSSHConfigEntryV2(tt.args.workspace, tt.args.privateKeyPath) + got, err := makeSSHConfigEntryV2(tt.args.workspace, tt.args.privateKeyPath, tt.args.cloudflaredBinaryPath) if (err != nil) != tt.wantErr { t.Errorf("makeSSHConfigEntryV2() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/pkg/store/cloudflare.go b/pkg/store/cloudflare.go new file mode 100644 index 00000000..9bbd5bc9 --- /dev/null +++ b/pkg/store/cloudflare.go @@ -0,0 +1,71 @@ +package store + +import ( + "fmt" + "io/fs" + "runtime" + + "github.com/brevdev/brev-cli/pkg/errors" +) + +type CloudflareStore interface { + GetBrevCloudflaredBinaryPath() (string, error) + FileExists(string) (bool, error) + DownloadBinary(string, string) error + Chmod(string, fs.FileMode) error +} + +type Cloudflare struct { + store CloudflareStore +} + +func NewCloudflare(store CloudflareStore) Cloudflare { + return Cloudflare{ + store: store, + } +} + +var CloudflaredVersion = "2024.10.0" + +func (c Cloudflare) DownloadCloudflaredBinaryIfItDNE() error { + binaryPath, err := c.store.GetBrevCloudflaredBinaryPath() + if err != nil { + return errors.WrapAndTrace(err) + } + binaryExists, err := c.store.FileExists(binaryPath) + if err != nil { + return errors.WrapAndTrace(err) + } + if binaryExists { + return nil + } + binaryURL, err := getCloudflaredBinaryDownloadURL() + if err != nil { + return errors.WrapAndTrace(err) + } + err = c.store.DownloadBinary(binaryURL, binaryPath) + if err != nil { + return errors.WrapAndTrace(err) + } + err = c.store.Chmod(binaryPath, 0o755) + if err != nil { + return errors.WrapAndTrace(err) + } + return nil +} + +func getCloudflaredBinaryDownloadURL() (string, error) { + switch runtime.GOOS { + case "linux": + return fmt.Sprintf("https://github.com/cloudflare/cloudflared/releases/download/%s/cloudflared-fips-linux-amd64", CloudflaredVersion), nil + case "windows": + return fmt.Sprintf("https://github.com/cloudflare/cloudflared/releases/download/%s/cloudflared-amd64.exe", CloudflaredVersion), nil + case "darwin": + if runtime.GOARCH == "arm64" { + return fmt.Sprintf("https://github.com/cloudflare/cloudflared/releases/download/%s/cloudflared-darwin-arm64.tgz", CloudflaredVersion), nil + } + return fmt.Sprintf("https://github.com/cloudflare/cloudflared/releases/download/%s/cloudflared-darwin-amd64.tgz", CloudflaredVersion), nil + default: + return "", fmt.Errorf("unsupported OS %s for downloading Cloudflare binary", runtime.GOOS) + } +} diff --git a/pkg/store/cloudflare_test.go b/pkg/store/cloudflare_test.go new file mode 100644 index 00000000..1c974d4c --- /dev/null +++ b/pkg/store/cloudflare_test.go @@ -0,0 +1,43 @@ +package store + +import ( + "testing" + + "github.com/brevdev/brev-cli/pkg/auth" + "github.com/brevdev/brev-cli/pkg/config" + "github.com/brevdev/brev-cli/pkg/files" + "github.com/fatih/color" + "github.com/stretchr/testify/assert" +) + +func makeCloudflare() Cloudflare { + conf := config.NewConstants() + fs := files.AppFs + authenticator := auth.Authenticator{ + Audience: "https://brevdev.us.auth0.com/api/v2/", + ClientID: "JaqJRLEsdat5w7Tb0WqmTxzIeqwqepmk", + DeviceCodeEndpoint: "https://brevdev.us.auth0.com/oauth/device/code", + OauthTokenEndpoint: "https://brevdev.us.auth0.com/oauth/token", + } + // super annoying. this is needed to make the import stay + _ = color.New(color.FgYellow, color.Bold).SprintFunc() + + fsStore := NewBasicStore(). + WithFileSystem(fs) + loginAuth := auth.NewLoginAuth(fsStore, authenticator) + + loginCmdStore := fsStore.WithNoAuthHTTPClient( + NewNoAuthHTTPClient(conf.GetBrevAPIURl()), + ). + WithAuth(loginAuth, WithDebug(conf.GetDebugHTTP())) + return Cloudflare{ + store: loginCmdStore, + } +} + +func TestTask_DownloadCloudflaredBinary(t *testing.T) { + client := makeCloudflare() + + err := client.DownloadCloudflaredBinaryIfItDNE() + assert.NoError(t, err) +} diff --git a/pkg/store/ssh.go b/pkg/store/ssh.go index 7c32d4c6..61f195ed 100644 --- a/pkg/store/ssh.go +++ b/pkg/store/ssh.go @@ -107,6 +107,15 @@ func (f FileStore) GetWSLHostBrevSSHConfigPath() (string, error) { return path, nil } +func (f FileStore) GetBrevCloudflaredBinaryPath() (string, error) { + home, err := f.UserHomeDir() + if err != nil { + return "", breverrors.WrapAndTrace(err) + } + path := files.GetBrevCloudflaredBinaryPath(home) + return path, nil +} + func (f FileStore) WriteUserSSHConfig(config string) error { home, err := f.UserHomeDir() if err != nil { From cf8345ad5ae66744b551b1f2bf00f1c9bc486569 Mon Sep 17 00:00:00 2001 From: tmonty12 Date: Tue, 15 Oct 2024 17:26:32 -0700 Subject: [PATCH 2/6] fix linux cloudflare binary download url --- pkg/store/cloudflare.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/store/cloudflare.go b/pkg/store/cloudflare.go index 9bbd5bc9..ab2d4481 100644 --- a/pkg/store/cloudflare.go +++ b/pkg/store/cloudflare.go @@ -57,7 +57,7 @@ func (c Cloudflare) DownloadCloudflaredBinaryIfItDNE() error { func getCloudflaredBinaryDownloadURL() (string, error) { switch runtime.GOOS { case "linux": - return fmt.Sprintf("https://github.com/cloudflare/cloudflared/releases/download/%s/cloudflared-fips-linux-amd64", CloudflaredVersion), nil + return fmt.Sprintf("https://github.com/cloudflare/cloudflared/releases/download/%s/cloudflared-linux-amd64", CloudflaredVersion), nil case "windows": return fmt.Sprintf("https://github.com/cloudflare/cloudflared/releases/download/%s/cloudflared-amd64.exe", CloudflaredVersion), nil case "darwin": From 39ad3a4080732e34238c237aaa79d5db6f72b82e Mon Sep 17 00:00:00 2001 From: tmonty12 Date: Wed, 16 Oct 2024 01:37:22 +0000 Subject: [PATCH 3/6] fix linux download --- pkg/cmd/refresh/refresh.go | 3 +++ pkg/store/cloudflare.go | 49 +++++++++++++++++++++++++++++++++++--- pkg/store/file.go | 16 +++++++++++++ 3 files changed, 65 insertions(+), 3 deletions(-) diff --git a/pkg/cmd/refresh/refresh.go b/pkg/cmd/refresh/refresh.go index d4503933..1f4f7031 100644 --- a/pkg/cmd/refresh/refresh.go +++ b/pkg/cmd/refresh/refresh.go @@ -3,6 +3,7 @@ package refresh import ( "fmt" + "io" "io/fs" "sync" @@ -22,7 +23,9 @@ type RefreshStore interface { GetCurrentUser() (*entity.User, error) GetCurrentUserKeys() (*entity.UserKeys, error) Chmod(string, fs.FileMode) error + MkdirAll(string, fs.FileMode) error GetBrevCloudflaredBinaryPath() (string, error) + Create(string) (io.WriteCloser, error) } func NewCmdRefresh(t *terminal.Terminal, store RefreshStore) *cobra.Command { diff --git a/pkg/store/cloudflare.go b/pkg/store/cloudflare.go index ab2d4481..8149b93d 100644 --- a/pkg/store/cloudflare.go +++ b/pkg/store/cloudflare.go @@ -1,10 +1,15 @@ package store import ( + "context" "fmt" + "io" "io/fs" + "net/http" "runtime" + "strings" + "github.com/brevdev/brev-cli/pkg/collections" "github.com/brevdev/brev-cli/pkg/errors" ) @@ -13,6 +18,8 @@ type CloudflareStore interface { FileExists(string) (bool, error) DownloadBinary(string, string) error Chmod(string, fs.FileMode) error + MkdirAll(string, fs.FileMode) error + Create(string) (io.WriteCloser, error) } type Cloudflare struct { @@ -43,7 +50,7 @@ func (c Cloudflare) DownloadCloudflaredBinaryIfItDNE() error { if err != nil { return errors.WrapAndTrace(err) } - err = c.store.DownloadBinary(binaryURL, binaryPath) + err = c.DownloadBinary(context.TODO(), binaryPath, binaryURL) if err != nil { return errors.WrapAndTrace(err) } @@ -54,12 +61,48 @@ func (c Cloudflare) DownloadCloudflaredBinaryIfItDNE() error { return nil } +func (c Cloudflare) DownloadBinary(ctx context.Context, binaryPath, binaryURL string) error { + if strings.HasSuffix(binaryURL, ".tgz") { + resp, err := collections.GetRequestWithContext(ctx, binaryURL) + if err != nil { + return errors.WrapAndTrace(err) + } + defer resp.Body.Close() //nolint:errcheck // defer + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + out, err := c.store.Create(binaryPath) + if err != nil { + return errors.WrapAndTrace(err) + } + + defer out.Close() //nolint:errcheck // defer + var src io.Reader + if strings.HasSuffix(binaryURL, ".tgz") { + src = trytoUnTarGZ(resp.Body) + } else { + src = resp.Body + } + + _, err = io.Copy(out, src) + if err != nil { + return fmt.Errorf("error saving downloaded file: %v", err) + } + + err = c.store.Chmod(binaryPath, 0o755) + if err != nil { + return errors.WrapAndTrace(err) + } + } + return nil +} + func getCloudflaredBinaryDownloadURL() (string, error) { switch runtime.GOOS { case "linux": return fmt.Sprintf("https://github.com/cloudflare/cloudflared/releases/download/%s/cloudflared-linux-amd64", CloudflaredVersion), nil - case "windows": - return fmt.Sprintf("https://github.com/cloudflare/cloudflared/releases/download/%s/cloudflared-amd64.exe", CloudflaredVersion), nil case "darwin": if runtime.GOARCH == "arm64" { return fmt.Sprintf("https://github.com/cloudflare/cloudflared/releases/download/%s/cloudflared-darwin-arm64.tgz", CloudflaredVersion), nil diff --git a/pkg/store/file.go b/pkg/store/file.go index 79181170..55ea44ff 100644 --- a/pkg/store/file.go +++ b/pkg/store/file.go @@ -360,6 +360,22 @@ func (f FileStore) Chmod(path string, mode os.FileMode) error { return nil } +func (f FileStore) Create(target string) (io.WriteCloser, error) { + file, err := f.fs.Create(target) + if err != nil { + return nil, breverrors.WrapAndTrace(err) + } + return file, nil +} + +func (f FileStore) MkdirAll(path string, mode os.FileMode) error { + err := f.fs.MkdirAll(path, mode) + if err != nil { + return breverrors.WrapAndTrace(err) + } + return nil +} + func getUserHomeDir(f *FileStore) func() (string, error) { return func() (string, error) { if f.User != nil { From a6ba5dca1c8b0c8517e94912207c939108a37c73 Mon Sep 17 00:00:00 2001 From: tmonty12 Date: Wed, 16 Oct 2024 01:44:05 +0000 Subject: [PATCH 4/6] fix issues on linux --- pkg/store/cloudflare.go | 61 ++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/pkg/store/cloudflare.go b/pkg/store/cloudflare.go index 8149b93d..2f0558eb 100644 --- a/pkg/store/cloudflare.go +++ b/pkg/store/cloudflare.go @@ -6,11 +6,13 @@ import ( "io" "io/fs" "net/http" + "path/filepath" "runtime" "strings" "github.com/brevdev/brev-cli/pkg/collections" "github.com/brevdev/brev-cli/pkg/errors" + breverrors "github.com/brevdev/brev-cli/pkg/errors" ) type CloudflareStore interface { @@ -62,39 +64,42 @@ func (c Cloudflare) DownloadCloudflaredBinaryIfItDNE() error { } func (c Cloudflare) DownloadBinary(ctx context.Context, binaryPath, binaryURL string) error { - if strings.HasSuffix(binaryURL, ".tgz") { - resp, err := collections.GetRequestWithContext(ctx, binaryURL) - if err != nil { - return errors.WrapAndTrace(err) - } - defer resp.Body.Close() //nolint:errcheck // defer + resp, err := collections.GetRequestWithContext(ctx, binaryURL) + if err != nil { + return errors.WrapAndTrace(err) + } + defer resp.Body.Close() //nolint:errcheck // defer - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("unexpected status code: %d", resp.StatusCode) - } + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } - out, err := c.store.Create(binaryPath) - if err != nil { - return errors.WrapAndTrace(err) - } + err = c.store.MkdirAll(filepath.Dir(binaryPath), 0o755) + if err != nil { + return breverrors.WrapAndTrace(err) + } - defer out.Close() //nolint:errcheck // defer - var src io.Reader - if strings.HasSuffix(binaryURL, ".tgz") { - src = trytoUnTarGZ(resp.Body) - } else { - src = resp.Body - } + out, err := c.store.Create(binaryPath) + if err != nil { + return errors.WrapAndTrace(err) + } + defer out.Close() //nolint:errcheck // defer - _, err = io.Copy(out, src) - if err != nil { - return fmt.Errorf("error saving downloaded file: %v", err) - } + var src io.Reader + if strings.HasSuffix(binaryURL, ".tgz") { + src = trytoUnTarGZ(resp.Body) + } else { + src = resp.Body + } - err = c.store.Chmod(binaryPath, 0o755) - if err != nil { - return errors.WrapAndTrace(err) - } + _, err = io.Copy(out, src) + if err != nil { + return fmt.Errorf("error saving downloaded file: %v", err) + } + + err = c.store.Chmod(binaryPath, 0o755) + if err != nil { + return errors.WrapAndTrace(err) } return nil } From 91057061a5749fa634784b1d33479339679228ce Mon Sep 17 00:00:00 2001 From: tmonty12 Date: Wed, 16 Oct 2024 01:45:53 +0000 Subject: [PATCH 5/6] rename to cloudflared --- pkg/cmd/refresh/refresh.go | 2 +- pkg/store/{cloudflare.go => cloudflared.go} | 16 ++++++++-------- .../{cloudflare_test.go => cloudflared_test.go} | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) rename pkg/store/{cloudflare.go => cloudflared.go} (88%) rename pkg/store/{cloudflare_test.go => cloudflared_test.go} (95%) diff --git a/pkg/cmd/refresh/refresh.go b/pkg/cmd/refresh/refresh.go index 1f4f7031..59c078d1 100644 --- a/pkg/cmd/refresh/refresh.go +++ b/pkg/cmd/refresh/refresh.go @@ -119,7 +119,7 @@ func GetConfigUpdater(store RefreshStore) (*ssh.ConfigUpdater, error) { return cu, nil } -func GetCloudflare(refreshStore RefreshStore) store.Cloudflare { +func GetCloudflare(refreshStore RefreshStore) store.Cloudflared { cl := store.NewCloudflare(refreshStore) return cl } diff --git a/pkg/store/cloudflare.go b/pkg/store/cloudflared.go similarity index 88% rename from pkg/store/cloudflare.go rename to pkg/store/cloudflared.go index 2f0558eb..11ffdece 100644 --- a/pkg/store/cloudflare.go +++ b/pkg/store/cloudflared.go @@ -15,7 +15,7 @@ import ( breverrors "github.com/brevdev/brev-cli/pkg/errors" ) -type CloudflareStore interface { +type CloudflaredStore interface { GetBrevCloudflaredBinaryPath() (string, error) FileExists(string) (bool, error) DownloadBinary(string, string) error @@ -24,19 +24,19 @@ type CloudflareStore interface { Create(string) (io.WriteCloser, error) } -type Cloudflare struct { - store CloudflareStore +type Cloudflared struct { + store CloudflaredStore } -func NewCloudflare(store CloudflareStore) Cloudflare { - return Cloudflare{ +func NewCloudflare(store CloudflaredStore) Cloudflared { + return Cloudflared{ store: store, } } var CloudflaredVersion = "2024.10.0" -func (c Cloudflare) DownloadCloudflaredBinaryIfItDNE() error { +func (c Cloudflared) DownloadCloudflaredBinaryIfItDNE() error { binaryPath, err := c.store.GetBrevCloudflaredBinaryPath() if err != nil { return errors.WrapAndTrace(err) @@ -63,7 +63,7 @@ func (c Cloudflare) DownloadCloudflaredBinaryIfItDNE() error { return nil } -func (c Cloudflare) DownloadBinary(ctx context.Context, binaryPath, binaryURL string) error { +func (c Cloudflared) DownloadBinary(ctx context.Context, binaryPath, binaryURL string) error { resp, err := collections.GetRequestWithContext(ctx, binaryURL) if err != nil { return errors.WrapAndTrace(err) @@ -114,6 +114,6 @@ func getCloudflaredBinaryDownloadURL() (string, error) { } return fmt.Sprintf("https://github.com/cloudflare/cloudflared/releases/download/%s/cloudflared-darwin-amd64.tgz", CloudflaredVersion), nil default: - return "", fmt.Errorf("unsupported OS %s for downloading Cloudflare binary", runtime.GOOS) + return "", fmt.Errorf("unsupported OS %s for downloading Cloudflared binary", runtime.GOOS) } } diff --git a/pkg/store/cloudflare_test.go b/pkg/store/cloudflared_test.go similarity index 95% rename from pkg/store/cloudflare_test.go rename to pkg/store/cloudflared_test.go index 1c974d4c..ab50c13b 100644 --- a/pkg/store/cloudflare_test.go +++ b/pkg/store/cloudflared_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" ) -func makeCloudflare() Cloudflare { +func makeCloudflare() Cloudflared { conf := config.NewConstants() fs := files.AppFs authenticator := auth.Authenticator{ @@ -30,7 +30,7 @@ func makeCloudflare() Cloudflare { NewNoAuthHTTPClient(conf.GetBrevAPIURl()), ). WithAuth(loginAuth, WithDebug(conf.GetDebugHTTP())) - return Cloudflare{ + return Cloudflared{ store: loginCmdStore, } } From 1dc90573fd042d8880738d419dd845c634c3e301 Mon Sep 17 00:00:00 2001 From: tmonty12 Date: Thu, 17 Oct 2024 11:09:13 -0700 Subject: [PATCH 6/6] remove duplicate import --- pkg/store/cloudflared.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/store/cloudflared.go b/pkg/store/cloudflared.go index 11ffdece..b1ddd33a 100644 --- a/pkg/store/cloudflared.go +++ b/pkg/store/cloudflared.go @@ -12,7 +12,6 @@ import ( "github.com/brevdev/brev-cli/pkg/collections" "github.com/brevdev/brev-cli/pkg/errors" - breverrors "github.com/brevdev/brev-cli/pkg/errors" ) type CloudflaredStore interface { @@ -76,7 +75,7 @@ func (c Cloudflared) DownloadBinary(ctx context.Context, binaryPath, binaryURL s err = c.store.MkdirAll(filepath.Dir(binaryPath), 0o755) if err != nil { - return breverrors.WrapAndTrace(err) + return errors.WrapAndTrace(err) } out, err := c.store.Create(binaryPath)